Justin Rhee 3 лет назад
Родитель
Сommit
99bcb8256f

+ 4 - 0
dashboard/src/assets/danger.svg

@@ -0,0 +1,4 @@
+<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.4" d="M17.3164 77.6135C17.2687 77.6135 17.2247 77.6135 17.1734 77.6098C16.0184 77.5511 14.8854 77.3018 13.8074 76.8655C8.50171 74.7095 5.94237 68.6485 8.09471 63.3465L34.9384 16.3178C35.8624 14.6458 37.263 13.2451 38.9717 12.2991C43.9767 9.52715 50.3054 11.3495 53.0737 16.3508L79.7414 63.0201C80.3354 64.4171 80.5884 65.5538 80.6507 66.7125C80.7937 69.4845 79.8477 72.1428 77.9924 74.1998C76.137 76.2568 73.5887 77.4705 70.8204 77.6098L17.5804 77.6135H17.3164Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M40.79 36.743C40.79 34.9757 42.231 33.5347 43.9984 33.5347C45.7657 33.5347 47.2067 34.9757 47.2067 36.743V47.1123C47.2067 48.8833 45.7657 50.3207 43.9984 50.3207C42.231 50.3207 40.79 48.8833 40.79 47.1123V36.743ZM40.79 59.6564C40.79 57.878 42.231 56.4297 43.9984 56.4297C45.7657 56.4297 47.2067 57.8597 47.2067 59.616C47.2067 61.4237 45.7657 62.8647 43.9984 62.8647C42.231 62.8647 40.79 61.4237 40.79 59.6564Z" fill="white"/>
+</svg>

+ 6 - 0
dashboard/src/assets/document.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15.7162 16.2234H8.49622" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M15.7162 12.0369H8.49622" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.2513 7.86011H8.49631" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9086 2.74982C15.9086 2.74982 8.23161 2.75382 8.21961 2.75382C5.45961 2.77082 3.75061 4.58682 3.75061 7.35682V16.5528C3.75061 19.3368 5.47261 21.1598 8.25661 21.1598C8.25661 21.1598 15.9326 21.1568 15.9456 21.1568C18.7056 21.1398 20.4156 19.3228 20.4156 16.5528V7.35682C20.4156 4.57282 18.6926 2.74982 15.9086 2.74982Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 5 - 0
dashboard/src/assets/info-circle.svg

@@ -0,0 +1,5 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.99988 0.750183C15.1089 0.750183 19.2499 4.89218 19.2499 10.0002C19.2499 15.1082 15.1089 19.2502 9.99988 19.2502C4.89188 19.2502 0.749878 15.1082 0.749878 10.0002C0.749878 4.89218 4.89188 0.750183 9.99988 0.750183Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.995 6.20428V10.6233" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.995 13.7961H10.005" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 5 - 0
dashboard/src/assets/info-outlined.svg

@@ -0,0 +1,5 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M11.9899 15.7961V11.3771" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.9899 8.20428H11.9999" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.3346 2.75018H7.66561C4.64461 2.75018 2.75061 4.88918 2.75061 7.91618V16.0842C2.75061 19.1112 4.63561 21.2502 7.66561 21.2502H16.3336C19.3646 21.2502 21.2506 19.1112 21.2506 16.0842V7.91618C21.2506 4.88918 19.3646 2.75018 16.3346 2.75018Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 1 - 3
dashboard/src/main/home/cluster-dashboard/dashboard/events/EventsTab.tsx

@@ -3,7 +3,6 @@ import styled from "styled-components";
 import EventCard from "components/events/EventCard";
 import Loading from "components/Loading";
 import InfiniteScroll from "react-infinite-scroll-component";
-import Dropdown from "components/Dropdown";
 import { useKubeEvents } from "components/events/useEvents";
 import SubEventsList from "components/events/SubEventsList";
 
@@ -39,8 +38,7 @@ const EventsTab = () => {
       <Placeholder>
         <div>
           <Header>We couldn't detect the Porter agent on your cluster</Header>
-          In order to use the events tab, you need to install the Porter agent
-          on your cluster.
+          In order to use the events tab, you need to install the Porter agent.
           <InstallPorterAgentButton onClick={() => triggerInstall()}>
             <i className="material-icons">add</i> Install Porter agent
           </InstallPorterAgentButton>

+ 461 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventList.tsx

@@ -0,0 +1,461 @@
+import React, { useState, useEffect } from "react";
+import { CellProps } from "react-table";
+
+import styled from "styled-components";
+import EventTable from "./EventTable";
+import Loading from "components/Loading";
+import danger from "assets/danger.svg";
+import document from "assets/document.svg";
+import info from "assets/info-outlined.svg";
+import status from "assets/info-circle.svg";
+import { readableDate } from "shared/string_utils";
+import TitleSection from "components/TitleSection";
+import api from "shared/api";
+import Modal from "main/home/modals/Modal";
+import time from "assets/time.svg";
+
+const iconDict: any = {
+};
+
+type Props = {
+  filters: any;
+  setExpandedMonitor: any;
+};
+
+const EventList: React.FC<Props> = (props) => {
+  const [events, setEvents] = useState([]);
+  const [expandedEvent, setExpandedEvent] = useState(null);
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+
+    // Dummy event list query
+    setTimeout(() => {
+      const events = [
+        {
+          "id": "e0311c9231eea5c47e8bb83cc10faf2b",
+          "release_name": "deployment-invalid-image",
+          "release_namespace": "default",
+          "chart_name": "worker",
+          "created_at": "2022-10-06T17:56:06.834088422Z",
+          "updated_at": "2022-10-07T19:32:33.842552438Z",
+          "last_seen": "2022-10-06T17:55:50Z",
+          "status": "active",
+          "summary": "The application has an invalid image",
+          "severity": "normal",
+          "involved_object_kind": "pod",
+          "involved_object_name": "deployment-invalid-image-worker-cf976b476-h782c",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "6818ee8df06db55c310c24a6b0c17cfd",
+          "release_name": "oom-killed",
+          "release_namespace": "default",
+          "chart_name": "web",
+          "created_at": "2022-10-05T17:13:45.563510898Z",
+          "updated_at": "2022-10-07T19:32:02.580935074Z",
+          "last_seen": "2022-10-05T17:59:10Z",
+          "status": "active",
+          "summary": "The application ran out of memory",
+          "severity": "critical",
+          "involved_object_kind": "Deployment",
+          "involved_object_name": "oom-killed-web",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "00f3b38dd0b2adc2ce0bab9586d487b5",
+          "release_name": "non-zero-exit-code",
+          "release_namespace": "default",
+          "chart_name": "web",
+          "created_at": "2022-10-05T18:31:07.80325966Z",
+          "updated_at": "2022-10-07T19:31:34.591091925Z",
+          "last_seen": "2022-10-05T18:31:07Z",
+          "status": "active",
+          "summary": "The application exited with a non-zero exit code",
+          "severity": "normal",
+          "involved_object_kind": "pod",
+          "involved_object_name": "non-zero-exit-code-web-797d5ddb64-g5d7x",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "c435fe96260e472af9808013687a876c",
+          "release_name": "multi-replica-failure-less",
+          "release_namespace": "default",
+          "chart_name": "worker",
+          "created_at": "2022-10-07T15:05:16.823354171Z",
+          "updated_at": "2022-10-07T19:31:29.840878022Z",
+          "last_seen": "2022-10-07T15:09:17Z",
+          "status": "active",
+          "summary": "The application exited with a non-zero exit code",
+          "severity": "critical",
+          "involved_object_kind": "Deployment",
+          "involved_object_name": "multi-replica-failure-less-worker",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "d9aec89e437617f28de47ab700c92cb4",
+          "release_name": "multi-replica-failure-more",
+          "release_namespace": "default",
+          "chart_name": "worker",
+          "created_at": "2022-10-07T15:08:21.405351792Z",
+          "updated_at": "2022-10-07T19:31:14.773187536Z",
+          "last_seen": "2022-10-07T19:25:06Z",
+          "status": "active",
+          "summary": "The application exited with a non-zero exit code",
+          "severity": "critical",
+          "involved_object_kind": "Deployment",
+          "involved_object_name": "multi-replica-failure-more-worker",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "f7f2021acb0e5fd104fb30b8c914a563",
+          "release_name": "oom-killed-2",
+          "release_namespace": "default",
+          "chart_name": "web",
+          "created_at": "2022-10-05T18:00:45.655719615Z",
+          "updated_at": "2022-10-07T19:28:38.571531252Z",
+          "last_seen": "2022-10-05T18:00:44Z",
+          "status": "active",
+          "summary": "The application ran out of memory",
+          "severity": "critical",
+          "involved_object_kind": "deployment",
+          "involved_object_name": "oom-killed-2-web",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "fa76cfb2daa58649e6aa7dc47262f632",
+          "release_name": "deployment-bad-image-tag",
+          "release_namespace": "default",
+          "chart_name": "worker",
+          "created_at": "2022-10-06T17:51:04.846528578Z",
+          "updated_at": "2022-10-07T19:28:11.835881268Z",
+          "last_seen": "2022-10-06T17:50:46Z",
+          "status": "active",
+          "summary": "The application has an invalid image",
+          "severity": "normal",
+          "involved_object_kind": "pod",
+          "involved_object_name": "deployment-bad-image-tag-worker-5f676bdb9-q4d7w",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "b58f6e28bc3884f50cb60c93b8427cb2",
+          "release_name": "deployment-stuck-longtime",
+          "release_namespace": "default",
+          "chart_name": "worker",
+          "created_at": "2022-10-07T15:04:23.807783654Z",
+          "updated_at": "2022-10-07T17:41:27.155369452Z",
+          "last_seen": "2022-10-07T15:04:23.695228777Z",
+          "status": "active",
+          "summary": "The application cannot be scheduled",
+          "severity": "critical",
+          "involved_object_kind": "deployment",
+          "involved_object_name": "deployment-stuck-longtime-worker",
+          "involved_object_namespace": "default"
+        },
+        {
+          "id": "6c6cc62a398d831bfddb6ef5faceafa9",
+          "release_name": "failing-job-run",
+          "release_namespace": "default",
+          "chart_name": "job",
+          "created_at": "2022-10-07T16:43:06.025081995Z",
+          "updated_at": "2022-10-07T17:29:02.722048271Z",
+          "last_seen": "2022-10-07T16:43:48Z",
+          "status": "active",
+          "summary": "The application has an invalid start command",
+          "severity": "normal",
+          "involved_object_kind": "Job",
+          "involved_object_name": "failing-job-run-dr1vbs563d",
+          "involved_object_namespace": "default"
+        }
+      ];
+      setEvents(events);
+      setIsLoading(false);
+    }, 1000);
+  }, []);
+
+  const columns = React.useMemo(
+    () => [
+      {
+        Header: "Monitors",
+        columns: [
+          {
+            Header: "Name",
+            accessor: "release_name",
+            width: 180,
+            Cell: ({ row }: CellProps<any>) => {
+              return (
+                <NameWrapper>
+                  <AlertIcon src={danger} />
+                  {row.original.release_name}
+                  {row?.original &&
+                  row.original.severity === "normal" ? (
+                    <></>
+                  ) : (
+                    <Status color="#cc3d42">Critical</Status>
+                  )}
+                </NameWrapper>
+              );
+            },
+          },
+          {
+            Header: "Summary",
+            accessor: "summary",
+            width: 270,
+          },
+          {
+            Header: "Last updated",
+            accessor: "updated_at",
+            width: 140,
+            Cell: ({ row }: CellProps<any>) => {
+              return (
+                <Flex>
+                  {readableDate(row.original.updated_at)}
+                </Flex>
+              );
+            },
+          },
+          {
+            id: "details",
+            accessor: "",
+            width: 20,
+            Cell: ({ row }: CellProps<any>) => {
+              return (
+                <TableButton onClick={() => {
+                  setExpandedEvent(row.original);
+                }}>
+                  <Icon src={info} />
+                  Details
+                </TableButton>
+              );
+            },
+          },
+          {
+            id: "logs",
+            accessor: "",
+            width: 30,
+            Cell: ({ row }: CellProps<any>) => {
+              return (
+                <TableButton width="102px">
+                  <Icon src={document} />
+                  View logs
+                </TableButton>
+              );
+            },
+          },
+        ],
+      },
+    ],
+    []
+  );
+
+  return (
+    <>
+      {
+        expandedEvent && (
+          <Modal onRequestClose={() => setExpandedEvent(null)} height="auto">
+            <TitleSection icon={danger}>
+              <Text>{expandedEvent.release_name}</Text>
+            </TitleSection>
+            <InfoRow>
+              <InfoTab>
+                <img src={time} /> <Bold>Last updated:</Bold>
+                {readableDate(expandedEvent.updated_at)}
+              </InfoTab>
+              <InfoTab>
+                <img src={info} /> <Bold>Status:</Bold>
+                <Capitalize>{expandedEvent.status}</Capitalize>
+              </InfoTab>
+              <InfoTab>
+                <img src={status} /> <Bold>Priority:</Bold> <Capitalize>{expandedEvent.severity}</Capitalize>
+              </InfoTab>
+            </InfoRow>
+            <Message>
+              <img src={document} /> This is a placeholder message where event details should be.
+            </Message>
+          </Modal>
+        )
+      }
+      {isLoading ? (
+        <LoadWrapper>
+          <Loading />
+        </LoadWrapper>
+      ) : (
+        <>
+        {events.length > 0 ? (
+          <TableWrapper>
+            <EventTable 
+              columns={columns} 
+              data={events} 
+            />
+          </TableWrapper>
+        ) : (
+          <Placeholder>
+            <div>
+            <Title>No results found</Title>
+            There were no results found for this filter.
+            </div>
+          </Placeholder>
+        )}
+        </>
+      )}
+    </>
+  );
+};
+
+export default EventList;
+
+const Message = styled.div`
+  padding: 20px;
+  background: #26292E;
+  border-radius: 5px;
+  line-height: 1.5em;
+  border: 1px solid #aaaabb33;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  > img {
+    width: 13px;
+    margin-right: 20px;
+  }
+`;
+
+const Capitalize = styled.div`
+  text-transform: capitalize;
+`;
+
+const Bold = styled.div`
+  font-weight: 500;
+  margin-right: 5px;
+`;
+
+const InfoTab = styled.div`
+  display: flex;
+  align-items: center;
+  opacity: 50%;
+  font-size: 13px;
+  margin-right: 15px;
+  justify-content: center;
+
+  > img {
+    width: 13px;
+    margin-right: 7px;
+  }
+`;
+
+const InfoRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  margin-bottom: 25px;
+`;
+
+const Text = styled.div`
+  font-weight: 500;
+  font-size: 18px;
+  z-index: 999;
+`;
+
+const Icon = styled.img`
+  width: 16px;
+  margin-right: 6px;
+`;
+
+const TableButton = styled.div<{ width?: string }>`
+  border-radius: 5px;
+  height: 30px;
+  color: white;
+  width: ${props => props.width || "85px"};
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #ffffff11;
+  border: 1px solid #aaaabb33;
+  cursor: pointer;
+  :hover {
+    border: 1px solid #7a7b80;
+  }
+`;
+
+const ClusterName = styled.div`
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  background: blue;
+  width: 100px;
+`;
+
+const Title = styled.div`
+  font-size: 18px;
+  margin-bottom: 10px;
+  color: #ffffff;
+`;
+
+const Placeholder = styled.div`
+  width: 100%;
+  height: 300px;
+  color: #aaaabb55;
+  display: flex;
+  font-size: 14px;
+  padding-right: 50px;
+  align-items: center;
+  justify-content: center;
+`;
+
+const ClusterIcon = styled.img`
+  width: 14px;
+  margin-right: 9px;
+  opacity: 70%;
+`;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const AlertIcon = styled.img`
+  width: 20px;
+  margin-right: 15px;
+  margin-left: 0px;
+`;
+
+const NameWrapper = styled.div`
+  display: flex;
+  align-items: center;
+  color: white;
+`;
+
+const LoadWrapper = styled.div`
+  width: 100%;
+  height: 300px;
+`;
+
+const Status = styled.div<{ color: string }>`
+  padding: 5px 7px;
+  background: ${(props) => props.color};
+  font-size: 12px;
+  border-radius: 3px;
+  word-break: keep-all;
+  display: flex;
+  color: white;
+  margin-right: 50px;
+  align-items: center;
+  margin-left: 15px;
+  justify-content: center;
+  height: 20px;
+`;
+
+const TableWrapper = styled.div`
+  overflow-x: auto;
+  animation: fadeIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+`;
+
+const StyledMonitorList = styled.div`
+  height: 200px;
+  width: 100%;
+  font-size: 13px;
+  background: #ffffff11;
+  border-radius: 5px;
+  border: 1px solid #aaaabb33;
+`;

+ 94 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventTable.tsx

@@ -0,0 +1,94 @@
+import React from "react";
+import {
+  Column,
+  Row,
+  useGlobalFilter,
+  usePagination,
+  useTable,
+} from "react-table";
+import {
+  StyledTd,
+  StyledTable,
+  StyledTHead,
+  StyledTh,
+  StyledTBody,
+} from "./styles";
+
+export type TableProps = {
+  columns: Column<any>[];
+  data: any[];
+  onRowClick?: (row: Row) => void;
+};
+
+const EventTable: React.FC<TableProps> = ({
+  columns: columnsData,
+  data,
+  onRowClick,
+}) => {
+  const {
+    rows,
+    getTableProps,
+    getTableBodyProps,
+    prepareRow,
+    headerGroups,
+  } = useTable(
+    {
+      columns: columnsData,
+      data,
+    },
+    useGlobalFilter,
+    usePagination
+  );
+
+  const renderRows = () => {
+    return (
+      <>
+        {rows.map((row: any) => {
+          prepareRow(row);
+
+          return (
+            <tr
+              {...row.getRowProps()}
+              onClick={() => onRowClick && onRowClick(row)}
+              selected={false}
+            >
+              {row.cells.map((cell: any) => {
+                return (
+                  <StyledTd
+                    {...cell.getCellProps()}
+                    style={{
+                      width: cell.column.totalWidth,
+                    }}
+                  >
+                    {cell.render("Cell")}
+                  </StyledTd>
+                );
+              })}
+            </tr>
+          );
+        })}
+      </>
+    );
+  };
+
+  return (
+    <>
+      <StyledTable {...getTableProps()}>
+        <StyledTHead>
+          {headerGroups.map((headerGroup) => (
+            <tr {...headerGroup.getHeaderGroupProps()}>
+              {headerGroup.headers.map((column) => (
+                <StyledTh {...column.getHeaderProps()}>
+                  {column.render("Header")}
+                </StyledTh>
+              ))}
+            </tr>
+          ))}
+        </StyledTHead>
+        <StyledTBody {...getTableBodyProps()}>{renderRows()}</StyledTBody>
+      </StyledTable>
+    </>
+  );
+};
+
+export default EventTable;

+ 32 - 154
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventsTab.tsx

@@ -1,97 +1,40 @@
-import React, { useEffect, useMemo, useState } from "react";
+import React, { useEffect, useContext, useState } from "react";
+import api from "shared/api";
 import styled from "styled-components";
-import EventCard from "components/events/EventCard";
+import EventList from "./EventList";
 import Loading from "components/Loading";
 import InfiniteScroll from "react-infinite-scroll-component";
+import { Context } from "shared/Context";
 import Dropdown from "components/Dropdown";
-import { useKubeEvents } from "components/events/useEvents";
-import { ChartType } from "shared/types";
-import _, { isEmpty, isObject } from "lodash";
-import SubEventsList from "components/events/SubEventsList";
 
-const availableResourceTypes = [
-  { label: "Pods", value: "pod" },
-  { label: "HPA", value: "hpa" },
-];
-
-const EventsTab: React.FC<{
-  controllers: Record<string, Record<string, any>>;
-}> = (props) => {
-  const { controllers } = props;
-  const [resourceType, setResourceType] = useState(availableResourceTypes[0]);
-  const [currentEvent, setCurrentEvent] = useState(null);
-
-  const [selectedControllerKey, setSelectedControllerKey] = useState(null);
-
-  const [hasControllers, setHasControllers] = useState(null);
-
-  const controllerOptions = useMemo(() => {
-    if (typeof controllers !== "object") {
-      return [];
-    }
-
-    return Object.entries(controllers).map(([key, value]) => ({
-      label: value?.metadata?.name,
-      value: key,
-    }));
-  }, [controllers]);
-
-  const currentControllerOption = useMemo(() => {
-    return (
-      controllerOptions?.find((c) => c.value === selectedControllerKey) ||
-      controllerOptions[0]
-    );
-  }, [selectedControllerKey, controllerOptions]);
-
-  const selectedController = controllers[currentControllerOption?.value];
-
-  const {
-    isLoading,
-    hasPorterAgent,
-    triggerInstall,
-    kubeEvents,
-    loadMoreEvents,
-    hasMore,
-  } = useKubeEvents({
-    resourceType: resourceType.value as any,
-    ownerName: selectedController?.metadata?.name,
-    ownerType: selectedController?.kind,
-    ownerNamespace: selectedController?.metadata?.namespace,
-    shouldWaitForOwner: true,
-  });
+const EventsTab: React.FC = () => {
+  const [hasPorterAgent, setHasPorterAgent] = useState(true);
+  const { currentProject, currentCluster } = useContext(Context);
+  const [isLoading, setIsLoading] = useState(true);
 
   useEffect(() => {
-    let timer: NodeJS.Timeout = null;
-
-    const checkControllers = (counter = 0) => {
-      if (timer !== null) {
-        clearTimeout(timer);
-      }
-
-      if (isEmpty(controllers) && counter === 5) {
-        clearTimeout(timer);
-        setHasControllers(false);
-      } else {
-        if (isEmpty(controllers)) {
-          timer = setTimeout(() => {
-            checkControllers(counter + 1);
-          }, 2000);
-        } else {
-          setHasControllers(true);
-        }
-      }
-    };
-
-    checkControllers();
-
-    return () => {
-      if (timer !== null) {
-        clearTimeout(timer);
-      }
-    };
-  }, [controllers]);
-
-  if (isLoading && hasControllers === null) {
+    setIsLoading(false);
+  }, []);
+
+  const installAgent = async () => {
+    const project_id = currentProject?.id;
+    const cluster_id = currentCluster?.id;
+
+    api
+      .installPorterAgent("<token>", {}, { project_id, cluster_id })
+      .then(() => {
+        setHasPorterAgent(true);
+      })
+      .catch((err) => {
+        console.log(err);
+      });
+  };
+
+  const triggerInstall = () => {
+    installAgent();
+  };
+
+  if (isLoading) {
     return (
       <Placeholder>
         <Loading />
@@ -99,22 +42,12 @@ const EventsTab: React.FC<{
     );
   }
 
-  if (!hasControllers) {
-    return (
-      <Placeholder>
-        <i className="material-icons">search</i>
-        We couldn't find any controllers for this application.
-      </Placeholder>
-    );
-  }
-
   if (!hasPorterAgent) {
     return (
       <Placeholder>
         <div>
           <Header>We couldn't detect the Porter agent on your cluster</Header>
-          In order to use the events tab, you need to install the Porter agent
-          on your cluster.
+          In order to use the events tab, you need to install the Porter agent.
           <InstallPorterAgentButton onClick={() => triggerInstall()}>
             <i className="material-icons">add</i> Install Porter agent
           </InstallPorterAgentButton>
@@ -123,64 +56,9 @@ const EventsTab: React.FC<{
     );
   }
 
-  if (currentEvent) {
-    return (
-      <SubEventsList
-        event={currentEvent}
-        clearSelectedEvent={() => setCurrentEvent(null)}
-      />
-    );
-  }
-
   return (
     <EventsPageWrapper>
-      {kubeEvents.length > 0 ? (
-        <>
-          <ControlRow>
-            {/*
-              <Dropdown
-                selectedOption={resourceType}
-                options={availableResourceTypes}
-                onSelect={(o) => setResourceType({ ...o, value: o.value as string })}
-              />
-              */}
-            <Label>Controller -</Label>
-            <Dropdown
-              selectedOption={currentControllerOption}
-              options={controllerOptions}
-              onSelect={(o) => setSelectedControllerKey(o?.value)}
-            />
-          </ControlRow>
-
-          <InfiniteScroll
-            dataLength={kubeEvents.length}
-            next={loadMoreEvents}
-            hasMore={hasMore}
-            loader={<h4>Loading...</h4>}
-            scrollableTarget="HomeViewWrapper"
-          >
-            <EventsGrid>
-              {kubeEvents.map((event, i) => {
-                return (
-                  <React.Fragment key={i}>
-                    <EventCard
-                      event={event as any}
-                      selectEvent={() => {
-                        setCurrentEvent(event);
-                      }}
-                    />
-                  </React.Fragment>
-                );
-              })}
-            </EventsGrid>
-          </InfiniteScroll>
-        </>
-      ) : (
-        <Placeholder>
-          <i className="material-icons">search</i>
-          No matching events were found.
-        </Placeholder>
-      )}
+      <EventList />
     </EventsPageWrapper>
   );
 };

+ 155 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/styles.ts

@@ -0,0 +1,155 @@
+import styled, { css } from "styled-components";
+
+const textFontStack = css`
+  font-family: "Work Sans", Arial, sans-serif;
+`;
+
+export const theme = {
+  bg: {
+    default: "#FFFFFF",
+    reverse: "#16171A",
+    wash: "#FAFAFA",
+    divider: "#F6F7F8",
+    border: "#EBECED",
+    inactive: "#DFE7EF",
+    shadeone: "#26292E",
+    shadetwo: "#26292E",
+  },
+  line: {
+    default: "1px solid #aaaabb33",
+  },
+  brand: {
+    default: "#4400CC",
+    alt: "#7B16FF",
+    wash: "#E8E5FF",
+    border: "#DDD9FF",
+    dark: "#2A0080",
+  },
+  generic: {
+    default: "#E6ECF7",
+    alt: "#F6FBFC",
+  },
+  space: {
+    default: "#0062D6",
+    alt: "#1CD2F2",
+    wash: "#E5F0FF",
+    border: "#BDD8FF",
+    dark: "#0F015E",
+  },
+  success: {
+    default: "#00B88B",
+    alt: "#00D5BD",
+    dark: "#00663C",
+    wash: "#D9FFF2",
+    border: "#9FF5D9",
+  },
+  text: {
+    default: "#ffffffaa",
+    secondary: "#384047",
+    alt: "#67717A",
+    placeholder: "#7C8894",
+    reverse: "#FFFFFF",
+  },
+  warn: {
+    default: "#E22F2F",
+    alt: "#E2197A",
+    dark: "#85000C",
+    wash: "#FFEDF6",
+    border: "#FFCCE5",
+  },
+};
+
+export const StyledTable = styled.table`
+  width: 100%;
+  min-width: 500px;
+  border-radius: 5px;
+  overflow: hidden;
+  border: 1px solid #aaaabb33;
+  border-spacing: 0;
+`;
+
+export const StyledTHead = styled.thead`
+  width: 100%;
+  position: sticky;
+
+  > tr {
+    background: ${theme.bg.shadeone};
+    line-height: 2.2em;
+
+    > th {
+      border-bottom: ${theme.line.default};
+    }
+  }
+
+  > tr:first-child {
+    > th:first-child {
+      border-top-left-radius: 6px;
+      display: none;
+    }
+
+    > th:last-child {
+      border-top-right-radius: 6px;
+    }
+  }
+`;
+
+export const StyledTBody = styled.tbody`
+  > tr {
+    background: ${theme.bg.shadetwo};
+    height: 80px;
+    line-height: 1.2em;
+
+    > td {
+      border-bottom: ${theme.line.default};
+    }
+
+    > td:first-child {
+    }
+
+    > td:last-child {
+    }
+  }
+
+  > tr:last-child {
+    > td:first-child {
+      border-bottom-left-radius: 6px;
+    }
+
+    > td:last-child {
+      border-bottom-right-radius: 6px;
+    }
+
+    > td {
+      border-bottom: none;
+    }
+  }
+`;
+
+export const StyledTd = styled.td`
+  ${textFontStack}
+  font-size: 13px;
+  color: ${theme.text.default};
+  :first-child {
+    padding-left: 20px;
+  }
+
+  :last-child {
+  }
+
+  user-select: text;
+`;
+
+export const StyledTh = styled.th`
+  ${textFontStack}
+
+  text-align: left;
+  font-size: 13px;
+  font-weight: 400;
+  color: #ffffffaa;
+  :first-child {
+    padding-left: 20px;
+  }
+  :last-child {
+    padding-right: 10px;
+  }
+`;