|
|
@@ -1,82 +1,69 @@
|
|
|
-import React, { Component, useEffect, useRef, useState } from "react";
|
|
|
+import React, {
|
|
|
+ Component,
|
|
|
+ useContext,
|
|
|
+ useEffect,
|
|
|
+ useMemo,
|
|
|
+ useRef,
|
|
|
+ useState,
|
|
|
+} from "react";
|
|
|
import styled from "styled-components";
|
|
|
import { Context } from "shared/Context";
|
|
|
import * as Anser from "anser";
|
|
|
import api from "shared/api";
|
|
|
-import { useWebsockets } from "shared/hooks/useWebsockets";
|
|
|
+import { NewWebsocketOptions, useWebsockets } from "shared/hooks/useWebsockets";
|
|
|
|
|
|
const MAX_LOGS = 1000;
|
|
|
|
|
|
-type PropsType = {
|
|
|
- selectedPod: any;
|
|
|
- podError: string;
|
|
|
- rawText?: boolean;
|
|
|
-};
|
|
|
-
|
|
|
-type StateType = {
|
|
|
- logs: [number, Anser.AnserJsonEntry[]][];
|
|
|
- numLogs: number;
|
|
|
- ws: any;
|
|
|
- scroll: boolean;
|
|
|
- currentTab: string;
|
|
|
- getPreviousLogs: boolean;
|
|
|
+type SelectedPodType = {
|
|
|
+ spec: {
|
|
|
+ [key: string]: any;
|
|
|
+ containers: {
|
|
|
+ [key: string]: any;
|
|
|
+ name: string;
|
|
|
+ }[];
|
|
|
+ };
|
|
|
+ metadata: {
|
|
|
+ name: string;
|
|
|
+ namespace: string;
|
|
|
+ };
|
|
|
+ status: {
|
|
|
+ phase: string;
|
|
|
+ };
|
|
|
};
|
|
|
|
|
|
-export default class Logs extends Component<PropsType, StateType> {
|
|
|
- private numLogs: React.RefObject<number>;
|
|
|
-
|
|
|
- state = {
|
|
|
- logs: [] as [number, Anser.AnserJsonEntry[]][],
|
|
|
- numLogs: 0,
|
|
|
- ws: null as any,
|
|
|
- scroll: true,
|
|
|
- currentTab: "Application",
|
|
|
- getPreviousLogs: false,
|
|
|
- };
|
|
|
+const LogsFC: React.FC<{
|
|
|
+ selectedPod: SelectedPodType;
|
|
|
+ podError: string;
|
|
|
+ rawText?: boolean;
|
|
|
+}> = ({ selectedPod, podError, rawText }) => {
|
|
|
+ const {
|
|
|
+ logs,
|
|
|
+ previousLogs,
|
|
|
+ containers,
|
|
|
+ currentContainer,
|
|
|
+ setCurrentContainer,
|
|
|
+ refresh,
|
|
|
+ } = useLogs(selectedPod);
|
|
|
|
|
|
- ws = null as any;
|
|
|
- parentRef = React.createRef<HTMLDivElement>();
|
|
|
+ const [showPreviousLogs, setShowPreviousLogs] = useState<boolean>(false);
|
|
|
|
|
|
- getPodStatus = (status: any) => {
|
|
|
- if (
|
|
|
- status?.phase === "Pending" &&
|
|
|
- status?.containerStatuses !== undefined
|
|
|
- ) {
|
|
|
- return status.containerStatuses[0].state.waiting.reason;
|
|
|
- } else if (status?.phase === "Pending") {
|
|
|
- return "Pending";
|
|
|
- }
|
|
|
+ const [isScrollToBottomEnabled, setIsScrollToBottomEnabled] = useState(true);
|
|
|
|
|
|
- if (status?.phase === "Failed") {
|
|
|
- return "failed";
|
|
|
- }
|
|
|
+ const wrapperRef = useRef<HTMLDivElement>();
|
|
|
|
|
|
- if (status?.phase === "Running") {
|
|
|
- let collatedStatus = "running";
|
|
|
-
|
|
|
- status?.containerStatuses?.forEach((s: any) => {
|
|
|
- if (s.state?.waiting) {
|
|
|
- collatedStatus =
|
|
|
- s.state?.waiting.reason === "CrashLoopBackOff"
|
|
|
- ? "failed"
|
|
|
- : "waiting";
|
|
|
- } else if (s.state?.terminated) {
|
|
|
- collatedStatus = "failed";
|
|
|
- }
|
|
|
- });
|
|
|
- return collatedStatus;
|
|
|
+ const scrollToBottom = (smooth: boolean) => {
|
|
|
+ if (!wrapperRef.current) {
|
|
|
+ return;
|
|
|
}
|
|
|
- };
|
|
|
|
|
|
- scrollToBottom = (smooth: boolean) => {
|
|
|
if (smooth) {
|
|
|
- this.parentRef.current.lastElementChild.scrollIntoView({
|
|
|
+ wrapperRef.current.lastElementChild.scrollIntoView({
|
|
|
behavior: "smooth",
|
|
|
block: "nearest",
|
|
|
inline: "start",
|
|
|
});
|
|
|
} else {
|
|
|
- this.parentRef.current.lastElementChild.scrollIntoView({
|
|
|
+ wrapperRef.current.lastElementChild.scrollIntoView({
|
|
|
behavior: "auto",
|
|
|
block: "nearest",
|
|
|
inline: "start",
|
|
|
@@ -84,18 +71,22 @@ export default class Logs extends Component<PropsType, StateType> {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- renderLogs = () => {
|
|
|
- let { selectedPod, podError } = this.props;
|
|
|
+ useEffect(() => {
|
|
|
+ if (isScrollToBottomEnabled) {
|
|
|
+ scrollToBottom(true);
|
|
|
+ }
|
|
|
+ }, [isScrollToBottomEnabled, logs]);
|
|
|
|
|
|
+ const renderLogs = () => {
|
|
|
if (podError && podError != "") {
|
|
|
- return <Message>{this.props.podError}</Message>;
|
|
|
+ return <Message>{podError}</Message>;
|
|
|
}
|
|
|
|
|
|
if (!selectedPod?.metadata?.name) {
|
|
|
return <Message>Please select a pod to view its logs.</Message>;
|
|
|
}
|
|
|
|
|
|
- if (selectedPod?.status.phase === "Succeeded" && !this.props.rawText) {
|
|
|
+ if (selectedPod?.status.phase === "Succeeded" && !rawText) {
|
|
|
return (
|
|
|
<Message>
|
|
|
⌛ This job has been completed. You can now delete this job.
|
|
|
@@ -104,31 +95,34 @@ export default class Logs extends Component<PropsType, StateType> {
|
|
|
}
|
|
|
|
|
|
if (
|
|
|
- this.getPodStatus(selectedPod.status) === "failed" &&
|
|
|
- this.state.logs.length === 0
|
|
|
+ showPreviousLogs &&
|
|
|
+ Array.isArray(previousLogs) &&
|
|
|
+ previousLogs.length
|
|
|
) {
|
|
|
- return (
|
|
|
- <Message>
|
|
|
- No logs to display from this pod.
|
|
|
- <Highlight
|
|
|
- onClick={() => {
|
|
|
- this.setState({ getPreviousLogs: true }, () => {
|
|
|
- this.refreshLogs();
|
|
|
- });
|
|
|
- }}
|
|
|
- >
|
|
|
- <i className="material-icons">autorenew</i>
|
|
|
- Get logs from crashed pod
|
|
|
- </Highlight>
|
|
|
- </Message>
|
|
|
- );
|
|
|
+ return previousLogs?.map((log, i) => {
|
|
|
+ return (
|
|
|
+ <Log key={i}>
|
|
|
+ {log.map((ansi, j) => {
|
|
|
+ if (ansi.clearLine) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <LogSpan key={i + "." + j} ansi={ansi}>
|
|
|
+ {ansi.content.replace(/ /g, "\u00a0")}
|
|
|
+ </LogSpan>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </Log>
|
|
|
+ );
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- if (this.state.logs.length == 0) {
|
|
|
+ if (!Array.isArray(logs) || logs?.length === 0) {
|
|
|
return (
|
|
|
<Message>
|
|
|
No logs to display from this pod.
|
|
|
- <Highlight onClick={this.refreshLogs}>
|
|
|
+ <Highlight onClick={refresh}>
|
|
|
<i className="material-icons">autorenew</i>
|
|
|
Refresh
|
|
|
</Highlight>
|
|
|
@@ -136,17 +130,16 @@ export default class Logs extends Component<PropsType, StateType> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- return this.state.logs.map((log, i) => {
|
|
|
- const key = log[0];
|
|
|
+ return logs?.map((log, i) => {
|
|
|
return (
|
|
|
- <Log key={key}>
|
|
|
- {this.state.logs[i][1].map((ansi, j) => {
|
|
|
+ <Log key={i}>
|
|
|
+ {log.map((ansi, j) => {
|
|
|
if (ansi.clearLine) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
- <LogSpan key={key + "." + j} ansi={ansi}>
|
|
|
+ <LogSpan key={i + "." + j} ansi={ansi}>
|
|
|
{ansi.content.replace(/ /g, "\u00a0")}
|
|
|
</LogSpan>
|
|
|
);
|
|
|
@@ -156,278 +149,288 @@ export default class Logs extends Component<PropsType, StateType> {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
- setupWebsocket = () => {
|
|
|
- let { currentCluster, currentProject } = this.context;
|
|
|
- let { selectedPod } = this.props;
|
|
|
- if (!selectedPod?.metadata?.name) return;
|
|
|
- let protocol = window.location.protocol == "https:" ? "wss" : "ws";
|
|
|
- const currentTab = this.state.currentTab;
|
|
|
- if (currentTab === "Application") {
|
|
|
- this.ws = new WebSocket(
|
|
|
- `${protocol}://${window.location.host}/api/projects/${currentProject.id}/clusters/${currentCluster.id}/namespaces/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?previous=${this.state.getPreviousLogs}`
|
|
|
- );
|
|
|
- } else {
|
|
|
- this.ws = new WebSocket(
|
|
|
- `${protocol}://${window.location.host}/api/projects/${currentProject.id}/clusters/${currentCluster.id}/namespaces/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?container_name=${currentTab}&previous=${this.state.getPreviousLogs}`
|
|
|
- );
|
|
|
- }
|
|
|
+ const renderContent = () => (
|
|
|
+ <>
|
|
|
+ <Wrapper ref={wrapperRef}>{renderLogs()}</Wrapper>
|
|
|
+ <LogTabs>
|
|
|
+ {containers.map((containerName, _i, arr) => {
|
|
|
+ return (
|
|
|
+ <Tab
|
|
|
+ key={containerName}
|
|
|
+ onClick={() => {
|
|
|
+ setCurrentContainer(containerName);
|
|
|
+ }}
|
|
|
+ clicked={currentContainer === containerName}
|
|
|
+ >
|
|
|
+ {arr.length > 1 ? containerName : "Application"}
|
|
|
+ </Tab>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ <Tab
|
|
|
+ onClick={() => {
|
|
|
+ setCurrentContainer("system");
|
|
|
+ }}
|
|
|
+ clicked={currentContainer == "system"}
|
|
|
+ >
|
|
|
+ System
|
|
|
+ </Tab>
|
|
|
+ </LogTabs>
|
|
|
+ <Options>
|
|
|
+ <Scroll
|
|
|
+ onClick={() => {
|
|
|
+ setIsScrollToBottomEnabled(!isScrollToBottomEnabled);
|
|
|
+ if (isScrollToBottomEnabled) {
|
|
|
+ scrollToBottom(true);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ checked={isScrollToBottomEnabled}
|
|
|
+ onChange={() => {}}
|
|
|
+ />
|
|
|
+ Scroll to Bottom
|
|
|
+ </Scroll>
|
|
|
+ {Array.isArray(previousLogs) && previousLogs.length > 0 && (
|
|
|
+ <Scroll
|
|
|
+ onClick={() => {
|
|
|
+ setShowPreviousLogs(!showPreviousLogs);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ checked={showPreviousLogs}
|
|
|
+ onChange={() => {}}
|
|
|
+ />
|
|
|
+ Show previous Logs
|
|
|
+ </Scroll>
|
|
|
+ )}
|
|
|
+ <Refresh
|
|
|
+ onClick={() => {
|
|
|
+ // this.refreshLogs();
|
|
|
+ console.log("Refresh logs");
|
|
|
+ refresh();
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <i className="material-icons">autorenew</i>
|
|
|
+ Refresh
|
|
|
+ </Refresh>
|
|
|
+ </Options>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!containers?.length) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
|
|
|
- this.ws.onopen = () => { };
|
|
|
+ if (rawText) {
|
|
|
+ return <LogStreamAlt>{renderContent()}</LogStreamAlt>;
|
|
|
+ }
|
|
|
|
|
|
- this.ws.onmessage = (evt: MessageEvent) => {
|
|
|
- let ansiLog = Anser.ansiToJson(evt.data);
|
|
|
+ return <LogStream>{renderContent()}</LogStream>;
|
|
|
+};
|
|
|
|
|
|
- let logs = this.state.logs;
|
|
|
- logs.push([this.state.numLogs, ansiLog]);
|
|
|
+export default LogsFC;
|
|
|
|
|
|
- // this is technically not as efficient as things could be
|
|
|
- // if there are performance issues, a deque can be used in place of a list
|
|
|
- // for storing logs
|
|
|
- if (logs.length > MAX_LOGS) {
|
|
|
- logs.shift();
|
|
|
- }
|
|
|
+const useLogs = (currentPod: SelectedPodType) => {
|
|
|
+ const currentPodName = useRef<string>();
|
|
|
|
|
|
- this.setState(
|
|
|
- (prev) => {
|
|
|
- return {
|
|
|
- logs: prev.logs,
|
|
|
- numLogs: prev.numLogs + 1,
|
|
|
- };
|
|
|
- },
|
|
|
- () => {
|
|
|
- if (this.state.scroll) {
|
|
|
- this.scrollToBottom(false);
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
- };
|
|
|
+ const { currentCluster, currentProject } = useContext(Context);
|
|
|
+ const [containers, setContainers] = useState<string[]>([]);
|
|
|
+ const [currentContainer, setCurrentContainer] = useState<string>("");
|
|
|
+ const [logs, setLogs] = useState<{
|
|
|
+ [key: string]: Anser.AnserJsonEntry[][];
|
|
|
+ }>({});
|
|
|
|
|
|
- this.ws.onerror = (err: ErrorEvent) => { };
|
|
|
+ const [prevLogs, setPrevLogs] = useState<{
|
|
|
+ [key: string]: Anser.AnserJsonEntry[][];
|
|
|
+ }>({});
|
|
|
|
|
|
- this.ws.onclose = () => { };
|
|
|
- };
|
|
|
+ const {
|
|
|
+ newWebsocket,
|
|
|
+ openWebsocket,
|
|
|
+ closeAllWebsockets,
|
|
|
+ getWebsocket,
|
|
|
+ closeWebsocket,
|
|
|
+ } = useWebsockets();
|
|
|
|
|
|
- refreshLogs = () => {
|
|
|
- let { selectedPod } = this.props;
|
|
|
- if (
|
|
|
- this.ws &&
|
|
|
- typeof this.state.currentTab === "string" &&
|
|
|
- this.state.currentTab != "System"
|
|
|
- ) {
|
|
|
- this.ws.close();
|
|
|
- this.ws = null;
|
|
|
- this.setState({ logs: [] });
|
|
|
- this.setupWebsocket();
|
|
|
- } else if (this.state.currentTab == "System") {
|
|
|
- this.retrieveEvents(selectedPod);
|
|
|
- }
|
|
|
- };
|
|
|
+ const getSystemLogs = async () => {
|
|
|
+ const events = await api
|
|
|
+ .getPodEvents(
|
|
|
+ "<token>",
|
|
|
+ {},
|
|
|
+ {
|
|
|
+ name: currentPod?.metadata?.name,
|
|
|
+ namespace: currentPod?.metadata?.namespace,
|
|
|
+ cluster_id: currentCluster?.id,
|
|
|
+ id: currentProject?.id,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then((res) => res.data);
|
|
|
|
|
|
- componentDidUpdate = (prevProps: any, prevState: any) => {
|
|
|
- if (prevState.currentTab !== this.state.currentTab) {
|
|
|
- let { selectedPod } = this.props;
|
|
|
+ let processedLogs = [] as Anser.AnserJsonEntry[][];
|
|
|
|
|
|
- this.ws?.close();
|
|
|
+ events.items.forEach((evt: any) => {
|
|
|
+ let ansiEvtType = evt.type == "Warning" ? "\u001b[31m" : "\u001b[32m";
|
|
|
+ let ansiLog = Anser.ansiToJson(
|
|
|
+ `${ansiEvtType}${evt.type}\u001b[0m \t \u001b[43m\u001b[34m\t${evt.reason} \u001b[0m \t ${evt.message}`
|
|
|
+ );
|
|
|
+ processedLogs.push(ansiLog);
|
|
|
+ });
|
|
|
|
|
|
- this.setState({ logs: [] });
|
|
|
+ // SET LOGS FOR SYSTEM
|
|
|
+ setLogs((prevState) => ({
|
|
|
+ ...prevState,
|
|
|
+ system: processedLogs,
|
|
|
+ }));
|
|
|
+ };
|
|
|
|
|
|
- if (this.state.currentTab == "System") {
|
|
|
- this.retrieveEvents(selectedPod);
|
|
|
- return;
|
|
|
- }
|
|
|
+ const getContainerPreviousLogs = async (containerName: string) => {
|
|
|
+ try {
|
|
|
+ const logs = await api
|
|
|
+ .getPreviousLogsForContainer<{ previous_logs: string[] }>(
|
|
|
+ "<token>",
|
|
|
+ {
|
|
|
+ container_name: containerName,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ pod_name: currentPod?.metadata?.name,
|
|
|
+ namespace: currentPod?.metadata?.namespace,
|
|
|
+ cluster_id: currentCluster?.id,
|
|
|
+ project_id: currentProject?.id,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then((res) => res.data);
|
|
|
+ // Process logs
|
|
|
+ const processedLogs: Anser.AnserJsonEntry[][] = logs.previous_logs.map(
|
|
|
+ (currentLog) => {
|
|
|
+ let ansiLog = Anser.ansiToJson(currentLog);
|
|
|
+ return ansiLog;
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- this.setState({ getPreviousLogs: false });
|
|
|
- this.setupWebsocket();
|
|
|
- this.scrollToBottom(false);
|
|
|
- }
|
|
|
+ setPrevLogs((pl) => ({
|
|
|
+ ...pl,
|
|
|
+ [containerName]: processedLogs,
|
|
|
+ }));
|
|
|
+ } catch (error) {}
|
|
|
};
|
|
|
|
|
|
- retrieveEvents = (selectedPod: any) => {
|
|
|
- api
|
|
|
- .getPodEvents(
|
|
|
- "<token>",
|
|
|
- {},
|
|
|
- {
|
|
|
- name: selectedPod?.metadata?.name,
|
|
|
- namespace: selectedPod?.metadata?.namespace,
|
|
|
- cluster_id: this.context.currentCluster.id,
|
|
|
- id: this.context.currentProject.id,
|
|
|
- }
|
|
|
- )
|
|
|
- .then((res) => {
|
|
|
- let logs = [] as [number, Anser.AnserJsonEntry[]][];
|
|
|
- // TODO: column view
|
|
|
- // logs.push(Anser.ansiToJson("\u001b[33;5;196mEvent Type\u001b[0m \t || \t \u001b[43m\u001b[34m\tReason\t\u001b[0m \t ||\tMessage"))
|
|
|
-
|
|
|
- res.data.items.forEach((evt: any) => {
|
|
|
- let ansiEvtType = evt.type == "Warning" ? "\u001b[31m" : "\u001b[32m";
|
|
|
- let ansiLog = Anser.ansiToJson(
|
|
|
- `${ansiEvtType}${evt.type}\u001b[0m \t \u001b[43m\u001b[34m\t${evt.reason} \u001b[0m \t ${evt.message}`
|
|
|
- );
|
|
|
- logs.push([logs.length, ansiLog]);
|
|
|
+ const setupWebsocket = (containerName: string, websocketKey: string) => {
|
|
|
+ if (!currentPod?.metadata?.name) return;
|
|
|
+
|
|
|
+ const endpoint = `/api/projects/${currentProject.id}/clusters/${currentCluster.id}/namespaces/${currentPod?.metadata?.namespace}/pod/${currentPod?.metadata?.name}/logs?container_name=${containerName}`;
|
|
|
+
|
|
|
+ const config: NewWebsocketOptions = {
|
|
|
+ onopen: () => {
|
|
|
+ console.log("Opened websocket:", websocketKey);
|
|
|
+ },
|
|
|
+ onmessage: (evt: MessageEvent) => {
|
|
|
+ let ansiLog = Anser.ansiToJson(evt.data);
|
|
|
+ setLogs((logs) => {
|
|
|
+ const tmpLogs = { ...logs };
|
|
|
+ let containerLogs = tmpLogs[containerName] || [];
|
|
|
+
|
|
|
+ containerLogs.push(ansiLog);
|
|
|
+ // this is technically not as efficient as things could be
|
|
|
+ // if there are performance issues, a deque can be used in place of a list
|
|
|
+ // for storing logs
|
|
|
+ if (containerLogs.length > MAX_LOGS) {
|
|
|
+ containerLogs.shift();
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...logs,
|
|
|
+ [containerName]: containerLogs,
|
|
|
+ };
|
|
|
});
|
|
|
- this.setState({ logs: logs });
|
|
|
- console.log(res);
|
|
|
- })
|
|
|
- .catch((err) => {
|
|
|
- console.log(err);
|
|
|
- });
|
|
|
+ },
|
|
|
+ onclose: () => {
|
|
|
+ console.log("Closed websocket:", websocketKey);
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ newWebsocket(websocketKey, endpoint, config);
|
|
|
+ openWebsocket(websocketKey);
|
|
|
};
|
|
|
|
|
|
- componentDidMount() {
|
|
|
- let { selectedPod } = this.props;
|
|
|
+ const refresh = () => {
|
|
|
+ const websocketKey = `${currentPodName.current}-${currentContainer}-websocket`;
|
|
|
+ closeWebsocket(websocketKey);
|
|
|
|
|
|
- if (selectedPod?.spec?.containers?.length > 1) {
|
|
|
- const firstContainer = selectedPod?.spec?.containers[0];
|
|
|
- this.setState({ currentTab: firstContainer?.name }, () => {
|
|
|
- this.setupWebsocket();
|
|
|
- this.scrollToBottom(false);
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
+ setPrevLogs((prev) => ({ ...prev, [currentContainer]: [] }));
|
|
|
+ setLogs((prev) => ({ ...prev, [currentContainer]: [] }));
|
|
|
|
|
|
- if (this.state.currentTab == "Application") {
|
|
|
- this.setupWebsocket();
|
|
|
- this.scrollToBottom(false);
|
|
|
+ if (!Array.isArray(containers)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- this.retrieveEvents(selectedPod);
|
|
|
- }
|
|
|
-
|
|
|
- componentWillUnmount() {
|
|
|
- if (this.ws) {
|
|
|
- this.ws.close();
|
|
|
+ if (currentContainer === "system") {
|
|
|
+ getSystemLogs();
|
|
|
+ } else {
|
|
|
+ getContainerPreviousLogs(currentContainer);
|
|
|
+ setupWebsocket(currentContainer, websocketKey);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- renderContainerTabs = () => {
|
|
|
- const containers = this.props.selectedPod?.spec?.containers;
|
|
|
+ };
|
|
|
|
|
|
- if (!Array.isArray(containers) || containers?.length <= 1) {
|
|
|
- return (
|
|
|
- <Tab
|
|
|
- onClick={() => {
|
|
|
- this.setState({ currentTab: "Application" });
|
|
|
- }}
|
|
|
- clicked={this.state.currentTab == "Application"}
|
|
|
- >
|
|
|
- Application
|
|
|
- </Tab>
|
|
|
- );
|
|
|
+ useEffect(() => {
|
|
|
+ console.log("Selected pod updated");
|
|
|
+ if (currentPod?.metadata?.name === currentPodName.current) {
|
|
|
+ return () => {
|
|
|
+ closeAllWebsockets();
|
|
|
+ };
|
|
|
}
|
|
|
+ currentPodName.current = currentPod?.metadata?.name;
|
|
|
+ const currentContainers =
|
|
|
+ currentPod?.spec?.containers?.map((container) => container?.name) || [];
|
|
|
+
|
|
|
+ setContainers(currentContainers);
|
|
|
+ setCurrentContainer(currentContainers[0]);
|
|
|
+ return () => {
|
|
|
+ closeAllWebsockets();
|
|
|
+ };
|
|
|
+ }, [currentPod]);
|
|
|
|
|
|
- return (
|
|
|
- <>
|
|
|
- {containers.map((container: any) => {
|
|
|
- return (
|
|
|
- <Tab
|
|
|
- key={container.name}
|
|
|
- onClick={() => {
|
|
|
- this.setState({ currentTab: container.name });
|
|
|
- }}
|
|
|
- clicked={this.state.currentTab == container.name}
|
|
|
- >
|
|
|
- {container.name}
|
|
|
- </Tab>
|
|
|
- );
|
|
|
- })}
|
|
|
- </>
|
|
|
- );
|
|
|
- };
|
|
|
+ // Retrieve all previous logs for containers
|
|
|
+ useEffect(() => {
|
|
|
+ closeAllWebsockets();
|
|
|
|
|
|
- render() {
|
|
|
- if (this.props.rawText) {
|
|
|
- return (
|
|
|
- <LogStreamAlt>
|
|
|
- <Wrapper ref={this.parentRef}>{this.renderLogs()}</Wrapper>
|
|
|
- <LogTabs>
|
|
|
- {this.renderContainerTabs()}
|
|
|
- <Tab
|
|
|
- onClick={() => {
|
|
|
- this.setState({ currentTab: "System" });
|
|
|
- }}
|
|
|
- clicked={this.state.currentTab == "System"}
|
|
|
- >
|
|
|
- System
|
|
|
- </Tab>
|
|
|
- </LogTabs>
|
|
|
- <Options>
|
|
|
- <Scroll
|
|
|
- onClick={() => {
|
|
|
- this.setState({ scroll: !this.state.scroll }, () => {
|
|
|
- if (this.state.scroll) {
|
|
|
- this.scrollToBottom(true);
|
|
|
- }
|
|
|
- });
|
|
|
- }}
|
|
|
- >
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- checked={this.state.scroll}
|
|
|
- onChange={() => { }}
|
|
|
- />
|
|
|
- Scroll to Bottom
|
|
|
- </Scroll>
|
|
|
- <Refresh
|
|
|
- onClick={() => {
|
|
|
- this.refreshLogs();
|
|
|
- }}
|
|
|
- >
|
|
|
- <i className="material-icons">autorenew</i>
|
|
|
- Refresh
|
|
|
- </Refresh>
|
|
|
- </Options>
|
|
|
- </LogStreamAlt>
|
|
|
- );
|
|
|
+ setPrevLogs({});
|
|
|
+ setLogs({});
|
|
|
+
|
|
|
+ if (!Array.isArray(containers)) {
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- return (
|
|
|
- <LogStream>
|
|
|
- <Wrapper ref={this.parentRef}>{this.renderLogs()}</Wrapper>
|
|
|
- <LogTabs>
|
|
|
- {this.renderContainerTabs()}
|
|
|
- <Tab
|
|
|
- onClick={() => {
|
|
|
- this.setState({ currentTab: "System" });
|
|
|
- }}
|
|
|
- clicked={this.state.currentTab == "System"}
|
|
|
- >
|
|
|
- System
|
|
|
- </Tab>
|
|
|
- </LogTabs>
|
|
|
- <Options>
|
|
|
- <Scroll
|
|
|
- onClick={() => {
|
|
|
- this.setState({ scroll: !this.state.scroll }, () => {
|
|
|
- if (this.state.scroll) {
|
|
|
- this.scrollToBottom(true);
|
|
|
- }
|
|
|
- });
|
|
|
- }}
|
|
|
- >
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- checked={this.state.scroll}
|
|
|
- onChange={() => { }}
|
|
|
- />
|
|
|
- Scroll to Bottom
|
|
|
- </Scroll>
|
|
|
- <Refresh
|
|
|
- onClick={() => {
|
|
|
- this.refreshLogs();
|
|
|
- }}
|
|
|
- >
|
|
|
- <i className="material-icons">autorenew</i>
|
|
|
- Refresh
|
|
|
- </Refresh>
|
|
|
- </Options>
|
|
|
- </LogStream>
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
+ getSystemLogs();
|
|
|
+ containers.forEach((containerName) => {
|
|
|
+ const websocketKey = `${currentPodName.current}-${containerName}-websocket`;
|
|
|
|
|
|
-Logs.contextType = Context;
|
|
|
+ getContainerPreviousLogs(containerName);
|
|
|
+
|
|
|
+ if (!getWebsocket(websocketKey)) {
|
|
|
+ setupWebsocket(containerName, websocketKey);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }, [containers]);
|
|
|
+
|
|
|
+ const currentLogs = useMemo(() => {
|
|
|
+ return logs[currentContainer] || [];
|
|
|
+ }, [currentContainer, logs]);
|
|
|
+
|
|
|
+ const currentPreviousLogs = useMemo(() => {
|
|
|
+ return prevLogs[currentContainer] || [];
|
|
|
+ }, [currentContainer, prevLogs]);
|
|
|
+
|
|
|
+ return {
|
|
|
+ containers,
|
|
|
+ currentContainer,
|
|
|
+ setCurrentContainer,
|
|
|
+ logs: currentLogs,
|
|
|
+ previousLogs: currentPreviousLogs,
|
|
|
+ refresh,
|
|
|
+ };
|
|
|
+};
|
|
|
|
|
|
const Highlight = styled.div`
|
|
|
display: flex;
|
|
|
@@ -447,7 +450,7 @@ const Scroll = styled.div`
|
|
|
align-items: center;
|
|
|
display: flex;
|
|
|
cursor: pointer;
|
|
|
- width: 145px;
|
|
|
+ width: max-content;
|
|
|
height: 100%;
|
|
|
|
|
|
:hover {
|