|
|
@@ -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;
|
|
|
+`;
|