|
|
@@ -13,426 +13,6 @@ 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;
|
|
|
-};
|
|
|
-
|
|
|
-class Logs extends Component<PropsType, StateType> {
|
|
|
- state = {
|
|
|
- logs: [] as [number, Anser.AnserJsonEntry[]][],
|
|
|
- numLogs: 0,
|
|
|
- ws: null as any,
|
|
|
- scroll: true,
|
|
|
- currentTab: "Application",
|
|
|
- getPreviousLogs: false,
|
|
|
- };
|
|
|
-
|
|
|
- ws = null as any;
|
|
|
- parentRef = React.createRef<HTMLDivElement>();
|
|
|
-
|
|
|
- getPodStatus = (status: any) => {
|
|
|
- if (
|
|
|
- status?.phase === "Pending" &&
|
|
|
- status?.containerStatuses !== undefined
|
|
|
- ) {
|
|
|
- return status.containerStatuses[0].state.waiting.reason;
|
|
|
- } else if (status?.phase === "Pending") {
|
|
|
- return "Pending";
|
|
|
- }
|
|
|
-
|
|
|
- if (status?.phase === "Failed") {
|
|
|
- return "failed";
|
|
|
- }
|
|
|
-
|
|
|
- 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;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- scrollToBottom = (smooth: boolean) => {
|
|
|
- if (smooth) {
|
|
|
- this.parentRef.current.lastElementChild.scrollIntoView({
|
|
|
- behavior: "smooth",
|
|
|
- block: "nearest",
|
|
|
- inline: "start",
|
|
|
- });
|
|
|
- } else {
|
|
|
- this.parentRef.current.lastElementChild.scrollIntoView({
|
|
|
- behavior: "auto",
|
|
|
- block: "nearest",
|
|
|
- inline: "start",
|
|
|
- });
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- renderLogs = () => {
|
|
|
- let { selectedPod, podError } = this.props;
|
|
|
-
|
|
|
- if (podError && podError != "") {
|
|
|
- return <Message>{this.props.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) {
|
|
|
- return (
|
|
|
- <Message>
|
|
|
- ⌛ This job has been completed. You can now delete this job.
|
|
|
- </Message>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- if (
|
|
|
- this.getPodStatus(selectedPod.status) === "failed" &&
|
|
|
- this.state.logs.length === 0
|
|
|
- ) {
|
|
|
- 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>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- if (this.state.logs.length == 0) {
|
|
|
- return (
|
|
|
- <Message>
|
|
|
- No logs to display from this pod.
|
|
|
- <Highlight onClick={this.refreshLogs}>
|
|
|
- <i className="material-icons">autorenew</i>
|
|
|
- Refresh
|
|
|
- </Highlight>
|
|
|
- </Message>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- return this.state.logs.map((log, i) => {
|
|
|
- const key = log[0];
|
|
|
- return (
|
|
|
- <Log key={key}>
|
|
|
- {log[1].map((ansi, j) => {
|
|
|
- if (ansi.clearLine) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- return (
|
|
|
- <LogSpan key={key + "." + j} ansi={ansi}>
|
|
|
- {ansi.content.replace(/ /g, "\u00a0")}
|
|
|
- </LogSpan>
|
|
|
- );
|
|
|
- })}
|
|
|
- </Log>
|
|
|
- );
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- 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`
|
|
|
- );
|
|
|
- } 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}`
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- this.ws.onopen = () => {};
|
|
|
-
|
|
|
- this.ws.onmessage = (evt: MessageEvent) => {
|
|
|
- let ansiLog = Anser.ansiToJson(evt.data);
|
|
|
-
|
|
|
- let logs = this.state.logs;
|
|
|
- logs.push([this.state.numLogs, 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 (logs.length > MAX_LOGS) {
|
|
|
- logs.shift();
|
|
|
- }
|
|
|
-
|
|
|
- this.setState(
|
|
|
- (prev) => {
|
|
|
- return {
|
|
|
- logs: prev.logs,
|
|
|
- numLogs: prev.numLogs + 1,
|
|
|
- };
|
|
|
- },
|
|
|
- () => {
|
|
|
- if (this.state.scroll) {
|
|
|
- this.scrollToBottom(false);
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- this.ws.onerror = (err: ErrorEvent) => {};
|
|
|
-
|
|
|
- this.ws.onclose = () => {};
|
|
|
- };
|
|
|
-
|
|
|
- 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);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- componentDidUpdate = (prevProps: any, prevState: any) => {
|
|
|
- if (prevState.currentTab !== this.state.currentTab) {
|
|
|
- let { selectedPod } = this.props;
|
|
|
-
|
|
|
- this.ws?.close();
|
|
|
-
|
|
|
- this.setState({ logs: [] });
|
|
|
-
|
|
|
- if (this.state.currentTab == "System") {
|
|
|
- this.retrieveEvents(selectedPod);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- this.setState({ getPreviousLogs: false });
|
|
|
- this.setupWebsocket();
|
|
|
- this.scrollToBottom(false);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- 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]);
|
|
|
- });
|
|
|
- this.setState({ logs: logs });
|
|
|
- console.log(res);
|
|
|
- })
|
|
|
- .catch((err) => {
|
|
|
- console.log(err);
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- componentDidMount() {
|
|
|
- let { selectedPod } = this.props;
|
|
|
-
|
|
|
- if (selectedPod?.spec?.containers?.length > 1) {
|
|
|
- const firstContainer = selectedPod?.spec?.containers[0];
|
|
|
- this.setState({ currentTab: firstContainer?.name }, () => {
|
|
|
- this.setupWebsocket();
|
|
|
- this.scrollToBottom(false);
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (this.state.currentTab == "Application") {
|
|
|
- this.setupWebsocket();
|
|
|
- this.scrollToBottom(false);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- this.retrieveEvents(selectedPod);
|
|
|
- }
|
|
|
-
|
|
|
- componentWillUnmount() {
|
|
|
- if (this.ws) {
|
|
|
- this.ws.close();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- 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>
|
|
|
- );
|
|
|
- })}
|
|
|
- </>
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- 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>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- 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>
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-Logs.contextType = Context;
|
|
|
-
|
|
|
type SelectedPodType = {
|
|
|
spec: {
|
|
|
[key: string]: any;
|