Bläddra i källkod

Merge pull request #2539 from porter-dev/feat/event-list-logs-improvement

Feat/event list logs improvement
Porter Support 3 år sedan
förälder
incheckning
78cca177df

+ 224 - 60
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventList.tsx

@@ -16,21 +16,50 @@ import Modal from "main/home/modals/Modal";
 import time from "assets/time.svg";
 import { Context } from "shared/Context";
 import { InitLogData } from "../logs-section/LogsSection";
-import { setServers } from "dns";
+import { Direction, Log, parseLogs } from "../logs-section/useAgentLogs";
+import dayjs from "dayjs";
+import Anser from "anser";
 
 type Props = {
+  namespace: string;
   filters: any;
   setLogData?: (logData: InitLogData) => void;
 };
 
-const EventList: React.FC<Props> = ({ filters, setLogData }) => {
+const EventList: React.FC<Props> = ({ filters, namespace, setLogData }) => {
   const { currentProject, currentCluster } = useContext(Context);
   const [events, setEvents] = useState([]);
+  const [logs, setLogs] = useState<Log[]>([]);
   const [expandedEvent, setExpandedEvent] = useState(null);
   const [expandedIncidentEvents, setExpandedIncidentEvents] = useState(null);
   const [isLoading, setIsLoading] = useState(true);
   const [refresh, setRefresh] = useState(true);
 
+  const redirectToLogs = (incident: any) => {
+    api
+      .getIncidentEvents(
+        "<token>",
+        {
+          incident_id: incident.id,
+        },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+        }
+      )
+      .then((res) => {
+        const podName = res.data?.events[0]?.pod_name;
+        const timestamp = res.data?.events[0]?.last_seen;
+        const revision = res.data?.events[0]?.revision;
+
+        setLogData({
+          podName,
+          timestamp,
+          revision,
+        });
+      });
+  };
+
   useEffect(() => {
     if (!refresh) {
       return;
@@ -78,34 +107,45 @@ const EventList: React.FC<Props> = ({ filters, setLogData }) => {
         }
       )
       .then((res) => {
-        setExpandedIncidentEvents(res.data.events);
-      });
-  }, [expandedEvent]);
-
-  const redirectToLogs = (incident: any) => {
-    api
-      .getIncidentEvents(
-        "<token>",
-        {
-          incident_id: incident.id,
-        },
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
+        if (!expandedEvent.should_view_logs) {
+          return null;
         }
-      )
-      .then((res) => {
-        const podName = res.data?.events[0]?.pod_name;
-        const timestamp = res.data?.events[0]?.last_seen;
-        const revision = res.data?.events[0]?.revision;
 
-        setLogData({
-          podName,
-          timestamp,
-          revision,
-        });
+        const events = res.data?.events ?? [];
+
+        api
+          .getLogs(
+            "<token>",
+            {
+              pod_selector: events[0]?.pod_name,
+              namespace,
+              revision: events[0]?.revision,
+              start_range: dayjs(events[0]?.updated_at)
+                .subtract(14, "day")
+                .toISOString(),
+              end_range: dayjs(events[0]?.updated_at).toISOString(),
+              limit: 100,
+              direction: Direction.backward,
+              search_param: "",
+            },
+            {
+              cluster_id: currentCluster.id,
+              project_id: currentProject.id,
+            }
+          )
+          .then((res) => {
+            const logs = parseLogs(
+              res.data.logs
+                ?.filter(Boolean)
+                .map((logLine: any) => logLine.line)
+                .reverse()
+            );
+            setLogs(logs);
+          });
+
+        setExpandedIncidentEvents(res.data.events);
       });
-  };
+  }, [expandedEvent]);
 
   const renderExpandedEventMessage = () => {
     if (!expandedIncidentEvents) {
@@ -113,10 +153,60 @@ const EventList: React.FC<Props> = ({ filters, setLogData }) => {
     }
 
     return (
-      <Message>
-        <img src={document} />
-        {expandedIncidentEvents[0].detail}
-      </Message>
+      <>
+        <Message>
+          <img src={document} />
+          {expandedIncidentEvents[0].detail}
+        </Message>
+        {logs.length ? (
+          <LogsSectionWrapper>
+            <StyledLogsSection>
+              {logs?.map((log, i) => {
+                return (
+                  <LogSpan key={[log.lineNumber, i].join(".")}>
+                    <span className="line-number">{log.lineNumber}.</span>
+                    <span className="line-timestamp">
+                      {dayjs(log.timestamp).format("MMM D, YYYY HH:mm:ss")}
+                    </span>
+                    <LogOuter key={[log.lineNumber, i].join(".")}>
+                      {log.line?.map((ansi, j) => {
+                        if (ansi.clearLine) {
+                          return null;
+                        }
+
+                        return (
+                          <LogInnerSpan
+                            key={[log.lineNumber, i, j].join(".")}
+                            ansi={ansi}
+                          >
+                            {ansi.content.replace(/ /g, "\u00a0")}
+                          </LogInnerSpan>
+                        );
+                      })}
+                    </LogOuter>
+                  </LogSpan>
+                );
+              })}
+            </StyledLogsSection>
+            <ViewLogsWrapper>
+              <DocsLink
+                onClick={(e) => {
+                  e.preventDefault();
+                  e.stopPropagation();
+                  redirectToLogs(expandedEvent);
+                }}
+              >
+                View complete log history
+                <i className="material-icons">open_in_new</i>{" "}
+              </DocsLink>
+            </ViewLogsWrapper>
+          </LogsSectionWrapper>
+        ) : (
+          <LogsLoadWrapper>
+            <Loading />
+          </LogsLoadWrapper>
+        )}
+      </>
     );
   };
 
@@ -185,7 +275,7 @@ const EventList: React.FC<Props> = ({ filters, setLogData }) => {
             },
           },
           {
-            Header: "Last Seen",
+            Header: "Last seen",
             accessor: "timestamp",
             width: 140,
             Cell: ({ row }: CellProps<any>) => {
@@ -213,32 +303,6 @@ const EventList: React.FC<Props> = ({ filters, setLogData }) => {
               return null;
             },
           },
-          {
-            id: "logs",
-            accessor: "",
-            width: 30,
-            Cell: ({ row }: CellProps<any>) => {
-              if (row.original.type != "incident") {
-                return null;
-              }
-
-              if (!row.original.data.should_view_logs) {
-                return null;
-              }
-
-              return (
-                <TableButton
-                  width="102px"
-                  onClick={() => {
-                    redirectToLogs(row.original.data);
-                  }}
-                >
-                  <Icon src={document} />
-                  View logs
-                </TableButton>
-              );
-            },
-          },
         ],
       },
     ],
@@ -248,7 +312,13 @@ const EventList: React.FC<Props> = ({ filters, setLogData }) => {
   return (
     <>
       {expandedEvent && (
-        <Modal onRequestClose={() => setExpandedEvent(null)} height="auto">
+        <Modal
+          onRequestClose={() => {
+            setExpandedEvent(null);
+            setLogs([]);
+          }}
+          height="auto"
+        >
           <TitleSection icon={danger}>
             <Text>{expandedEvent.release_name}</Text>
           </TitleSection>
@@ -268,7 +338,7 @@ const EventList: React.FC<Props> = ({ filters, setLogData }) => {
           </InfoRow>
           {expandedEvent?.porter_doc_link && (
             <DocsLink target="_blank" href={expandedEvent?.porter_doc_link}>
-              View troubleshooting steps{" "}
+              View troubleshooting steps
               <i className="material-icons">open_in_new</i>{" "}
             </DocsLink>
           )}
@@ -303,6 +373,10 @@ const EventList: React.FC<Props> = ({ filters, setLogData }) => {
 
 export default EventList;
 
+const LogsLoadWrapper = styled.div`
+  height: 50px;
+`;
+
 const Message = styled.div`
   padding: 20px;
   background: #26292e;
@@ -369,6 +443,7 @@ const TableButton = styled.div<{ width?: string }>`
   justify-content: center;
   background: #ffffff11;
   border: 1px solid #aaaabb33;
+  margin-right: -17px;
   cursor: pointer;
   :hover {
     border: 1px solid #7a7b80;
@@ -503,5 +578,94 @@ const DocsLink = styled.a`
 
   > i {
     font-size: 12px;
+    margin-left: 5px;
+  }
+`;
+
+const LogsSectionWrapper = styled.div`
+  position: relative;
+`;
+
+const StyledLogsSection = styled.div`
+  margin-top: 20px;
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  font-size: 13px;
+  max-height: 400px;
+  border-radius: 8px;
+  border: 1px solid #ffffff33;
+  border-top: none;
+  background: #101420;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  overflow-y: auto;
+  overflow-wrap: break-word;
+  position: relative;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
   }
 `;
+
+const LogSpan = styled.div`
+  font-family: monospace;
+  user-select: text;
+  display: flex;
+  align-items: flex-end;
+  gap: 8px;
+  width: 100%;
+  & > * {
+    padding-block: 5px;
+  }
+  & > .line-timestamp {
+    height: 100%;
+    color: #949effff;
+    opacity: 0.5;
+    font-family: monospace;
+    min-width: fit-content;
+    padding-inline-end: 5px;
+  }
+  & > .line-number {
+    height: 100%;
+    background: #202538;
+    display: inline-block;
+    text-align: right;
+    min-width: 45px;
+    padding-inline-end: 5px;
+    opacity: 0.3;
+    font-family: monospace;
+  }
+`;
+
+const LogOuter = styled.div`
+  display: inline-block;
+  word-wrap: anywhere;
+  flex-grow: 1;
+  font-family: monospace, sans-serif;
+  font-size: 12px;
+`;
+
+const LogInnerSpan = styled.span`
+  font-family: monospace, sans-serif;
+  font-size: 12px;
+  font-weight: ${(props: { ansi: Anser.AnserJsonEntry }) =>
+    props.ansi?.decoration && props.ansi?.decoration == "bold" ? "700" : "400"};
+  color: ${(props: { ansi: Anser.AnserJsonEntry }) =>
+    props.ansi?.fg ? `rgb(${props.ansi?.fg})` : "white"};
+  background-color: ${(props: { ansi: Anser.AnserJsonEntry }) =>
+    props.ansi?.bg ? `rgb(${props.ansi?.bg})` : "transparent"};
+`;
+
+export const ViewLogsWrapper = styled.div`
+  margin-bottom: -15px;
+  margin-top: 15px;
+`;

+ 5 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventsTab.tsx

@@ -147,7 +147,11 @@ const EventsTab: React.FC<Props> = ({
 
   return (
     <EventsPageWrapper>
-      <EventList setLogData={setLogData} filters={getFilters()} />
+      <EventList
+        namespace={currentChart.namespace}
+        setLogData={setLogData}
+        filters={getFilters()}
+      />
     </EventsPageWrapper>
   );
 };

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/logs-section/useAgentLogs.ts

@@ -17,7 +17,7 @@ export enum Direction {
   backward = "backward",
 }
 
-interface Log {
+export interface Log {
   line: AnserJsonEntry[];
   lineNumber: number;
   timestamp: string;
@@ -29,7 +29,7 @@ interface LogLine {
   time: string;
 }
 
-const parseLogs = (logs: string[] = []): Log[] => {
+export const parseLogs = (logs: string[] = []): Log[] => {
   return logs
     .filter(Boolean)
     .filter(isJSON)

+ 2 - 0
dashboard/src/main/home/modals/Modal.tsx

@@ -138,6 +138,7 @@ const StyledModal = styled.div`
   max-width: 80vw;
   height: ${(props: { width?: string; height?: string }) =>
     props.height ? props.height : "425px"};
+  max-height: calc(100vh - 30px);
   overflow: visible;
   padding: 25px 32px;
   z-index: 999;
@@ -145,6 +146,7 @@ const StyledModal = styled.div`
   border-radius: 10px;
   background: #202227;
   border: 1px solid #ffffff55;
+  overflow: auto;
   color: #ffffff;
   animation: floatInModal 0.5s 0s;
   @keyframes floatInModal {