Просмотр исходного кода

Implemented drawer and added api endpoints

jnfrati 4 лет назад
Родитель
Сommit
b3207c476a

+ 104 - 3
dashboard/src/main/home/cluster-dashboard/dashboard/incidents/EventDrawer.tsx

@@ -1,7 +1,108 @@
-import React from "react";
+import React, { useContext, useEffect, useState } from "react";
+import api from "shared/api";
+import { Context } from "shared/Context";
+import styled from "styled-components";
+import { IncidentEvent } from "./IncidentPage";
 
-const EventDrawer = () => {
-  return <div>EventDrawer</div>;
+const EventDrawer: React.FC<{ event: IncidentEvent }> = ({ event }) => {
+  const { currentProject, currentCluster } = useContext(Context);
+
+  const [containerLogs, setContainerLogs] = useState<{ [key: string]: string }>(
+    null
+  );
+
+  useEffect(() => {
+    if (!event) {
+      return () => {};
+    }
+
+    let isSubscribed = true;
+    const promises = event.container_events.map((container) => {
+      return api
+        .getIncidentLogsByLogId<{ contents: string }>(
+          "<token>",
+          {},
+          {
+            project_id: currentProject.id,
+            cluster_id: currentCluster.id,
+            namespace: event.namespace,
+            release_name: event.release_name,
+            log_id: container.log_id,
+          }
+        )
+        .then((res) => ({
+          contents: res.data?.contents,
+          container_name: container.container_name,
+        }));
+    });
+
+    Promise.all(promises).then((data) => {
+      if (!isSubscribed) {
+        return;
+      }
+
+      const tmpContainerLogs = data.reduce<{ [key: string]: string }>(
+        (acc, c) => {
+          acc[c.container_name] = c.contents;
+          return acc;
+        },
+        {}
+      );
+
+      setContainerLogs(tmpContainerLogs);
+    });
+
+    return () => {
+      isSubscribed = false;
+    };
+  }, [event]);
+
+  if (!event) {
+    return null;
+  }
+
+  return (
+    <EventDrawerContainer>
+      <EventDrawerTitle>{event?.pod_name}</EventDrawerTitle>
+      <span>{event?.message}</span>
+
+      <div>
+        <span>Pod Phase: {event?.pod_phase}</span>
+        <span>Pod Status: {event?.pod_status}</span>
+      </div>
+      {event.container_events.map((container) => {
+        return (
+          <>
+            <h3>{container.container_name}</h3>
+            <span>
+              {container.message} - Exit Code: {container.exit_code}
+            </span>
+            <div>{containerLogs[container.container_name]}</div>
+          </>
+        );
+      })}
+      {Object.entries(containerLogs || {}).map(([key, value]) => {
+        return (
+          <>
+            <h3>{key}</h3>
+            <div>{value}</div>
+          </>
+        );
+      })}
+    </EventDrawerContainer>
+  );
 };
 
 export default EventDrawer;
+
+const EventDrawerContainer = styled.div`
+  color: #ffffff;
+  padding: 25px 30px;
+`;
+
+const EventDrawerTitle = styled.span`
+  display: block;
+  font-size: 24px;
+  font-weight: bold;
+  color: #ffffff90;
+`;

+ 165 - 19
dashboard/src/main/home/cluster-dashboard/dashboard/incidents/IncidentPage.tsx

@@ -7,6 +7,8 @@ import TitleSection from "components/TitleSection";
 import backArrow from "assets/back_arrow.png";
 import nodePng from "assets/node.png";
 import StatusSection from "components/StatusSection";
+import { Drawer, withStyles } from "@material-ui/core";
+import EventDrawer from "./EventDrawer";
 
 type IncidentPageParams = {
   incident_id: string;
@@ -17,6 +19,8 @@ const IncidentPage = () => {
 
   const [incident, setIncident] = useState<Incident>(null);
 
+  const [selectedEvent, setSelectedEvent] = useState<IncidentEvent>(null);
+
   useEffect(() => {
     let isSubscribed = true;
 
@@ -59,16 +63,58 @@ const IncidentPage = () => {
       <BodyWrapper>
         {Object.entries(events).map(([date, events_list]) => {
           return (
-            <div>
-              <div>{date}</div>
+            <>
+              <StyledDate>{date}</StyledDate>
 
               {events_list.map((event) => {
-                return <>{event.event_id}</>;
+                return (
+                  <StyledCard
+                    onClick={() => setSelectedEvent(event)}
+                    active={selectedEvent?.event_id === event.event_id}
+                  >
+                    <ContentContainer>
+                      <Icon
+                        status={"normal"}
+                        className="material-icons-outlined"
+                      >
+                        info
+                      </Icon>
+                      <EventInformation>
+                        <EventName>
+                          <Helper>Pod:</Helper>
+                          {event.pod_name}
+                        </EventName>
+                        <EventReason>{event.message}</EventReason>
+                      </EventInformation>
+                    </ContentContainer>
+                    <ActionContainer>
+                      <TimestampContainer>
+                        <TimestampIcon className="material-icons-outlined">
+                          access_time
+                        </TimestampIcon>
+                        <span>
+                          {Intl.DateTimeFormat([], {
+                            // @ts-ignore
+                            dateStyle: "full",
+                            timeStyle: "long",
+                          }).format(new Date(event.timestamp))}
+                        </span>
+                      </TimestampContainer>
+                    </ActionContainer>
+                  </StyledCard>
+                );
               })}
-            </div>
+            </>
           );
         })}
       </BodyWrapper>
+      <StyledDrawer
+        anchor="right"
+        open={!!selectedEvent}
+        onClose={() => setSelectedEvent(null)}
+      >
+        <EventDrawer event={selectedEvent} />
+      </StyledDrawer>
     </StyledExpandedNodeView>
   );
 };
@@ -184,7 +230,7 @@ const incident_mock = {
   ],
 };
 
-type IncidentContainerEvent = {
+export type IncidentContainerEvent = {
   container_name: string;
   reason: string;
   message: string;
@@ -192,7 +238,7 @@ type IncidentContainerEvent = {
   log_id: string;
 };
 
-type IncidentEvent = {
+export type IncidentEvent = {
   event_id: string;
   pod_name: string;
   cluster: string;
@@ -207,7 +253,7 @@ type IncidentEvent = {
   container_events: IncidentContainerEvent[];
 };
 
-type Incident = {
+export type Incident = {
   incident_id: string;
   release_name: string; // eg: "sample-web"
   latest_state: string; // "ONGOING" or "RESOLVED"
@@ -273,18 +319,6 @@ const BackButtonImg = styled.img`
   opacity: 0.75;
 `;
 
-const StatusWrapper = styled.div`
-  margin-left: 3px;
-  margin-bottom: 20px;
-`;
-
-const InstanceType = styled.div`
-  font-weight: 400;
-  color: #ffffff44;
-  margin-left: 12px;
-  font-size: 16px;
-`;
-
 const BodyWrapper = styled.div`
   width: 100%;
   height: 100%;
@@ -316,3 +350,115 @@ const StyledExpandedNodeView = styled.div`
     }
   }
 `;
+
+const StyledDate = styled.div`
+  font-size: 18px;
+  font-weight: bold;
+  color: #ffffff;
+  margin-bottom: 20px;
+  margin-top: 20px;
+  :first-child {
+    margin-top: 0px;
+  }
+`;
+
+const StyledCard = styled.div<{ active: boolean }>`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  border: 1px solid ${({ active }) => (active ? "#819bfd" : "#ffffff44")};
+  background: #ffffff08;
+  margin-bottom: 5px;
+  border-radius: 10px;
+  padding: 14px;
+  overflow: hidden;
+  height: 80px;
+  font-size: 13px;
+  cursor: pointer;
+  :hover {
+    background: #ffffff11;
+    border: 1px solid ${({ active }) => (active ? "#819bfd" : "#ffffff66")};
+  }
+  animation: fadeIn 0.5s;
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+  :not(:last-child) {
+    margin-bottom: 15px;
+  }
+`;
+
+const ContentContainer = styled.div`
+  display: flex;
+  height: 100%;
+  width: 100%;
+  align-items: center;
+`;
+
+const Icon = styled.span<{ status: "critical" | "normal" }>`
+  font-size: 20px;
+  margin-left: 10px;
+  margin-right: 20px;
+  color: ${({ status }) => (status === "critical" ? "#ff385d" : "#aaaabb")};
+`;
+
+const EventInformation = styled.div`
+  display: flex;
+  flex-direction: column;
+  justify-content: space-around;
+  height: 100%;
+`;
+
+const EventName = styled.div`
+  font-family: "Work Sans", sans-serif;
+  font-weight: 500;
+  color: #ffffff;
+`;
+
+const Helper = styled.span`
+  text-transform: capitalize;
+  color: #ffffff44;
+  margin-right: 5px;
+`;
+
+const EventReason = styled.div`
+  font-family: "Work Sans", sans-serif;
+  color: #aaaabb;
+  margin-top: 5px;
+`;
+
+const ActionContainer = styled.div`
+  display: flex;
+  align-items: center;
+  white-space: nowrap;
+  height: 100%;
+`;
+
+const TimestampContainer = styled.div`
+  display: flex;
+  white-space: nowrap;
+  align-items: center;
+  justify-self: flex-end;
+  color: #ffffff55;
+  margin-right: 10px;
+  font-size: 13px;
+  min-width: 130px;
+  justify-content: space-between;
+`;
+
+const TimestampIcon = styled.span`
+  margin-right: 7px;
+  font-size: 18px;
+`;
+
+const StyledDrawer = withStyles({
+  paperAnchorRight: {
+    background: "#202227",
+    minWidth: "700px",
+  },
+})(Drawer);

+ 61 - 0
dashboard/src/shared/api.tsx

@@ -1588,6 +1588,63 @@ const getPreviousLogsForContainer = baseApi<
     `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/pod/${name}/previous_logs`
 );
 
+const getIncidents = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+  }
+>(
+  "GET",
+  ({ project_id, cluster_id }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/incidents`
+);
+
+const getIncidentsByReleaseName = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    release_name: string;
+  }
+>(
+  "GET",
+  ({ project_id, cluster_id, namespace, release_name: name }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${name}/incidents`
+);
+
+const getIncidentById = baseApi<
+  {
+    incident_id: string;
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    release_name: string;
+  }
+>(
+  "GET",
+  ({ project_id, cluster_id, namespace, release_name: name }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${name}/incidents`
+);
+
+const getIncidentLogsByLogId = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    release_name: string;
+    log_id: string;
+  }
+>(
+  "GET",
+  ({ project_id, cluster_id, namespace, release_name: name, log_id }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${name}/incidents/logs/${log_id}`
+);
+
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
   checkAuth,
@@ -1741,4 +1798,8 @@ export default {
   provisionDatabase,
   getDatabases,
   getPreviousLogsForContainer,
+  getIncidents,
+  getIncidentsByReleaseName,
+  getIncidentById,
+  getIncidentLogsByLogId,
 };