|
|
@@ -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,41 @@ 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]?.last_seen)
|
|
|
+ .subtract(14, "day")
|
|
|
+ .toISOString(),
|
|
|
+ end_range: dayjs(events[0]?.last_seen).toISOString(),
|
|
|
+ limit: 100,
|
|
|
+ direction: Direction.backward,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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 +149,44 @@ 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>
|
|
|
+ </LogsSectionWrapper>
|
|
|
+ ) : null}
|
|
|
+ </>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
@@ -213,32 +283,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 +292,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>
|
|
|
@@ -505,3 +555,86 @@ const DocsLink = styled.a`
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
`;
|
|
|
+
|
|
|
+const LogsSectionWrapper = styled.div`
|
|
|
+ position: relative;
|
|
|
+`;
|
|
|
+
|
|
|
+const StyledLogsSection = styled.div`
|
|
|
+ margin-top: 20px;
|
|
|
+ width: 100%;
|
|
|
+ height: 200px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+ font-size: 13px;
|
|
|
+ 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"};
|
|
|
+`;
|