Sfoglia il codice sorgente

Merge pull request #1728 from porter-dev/nico/fix-logs-performance

[FIX] Improve logs performance and add CLI instructions to get logs from shell
Nicolas Frati 4 anni fa
parent
commit
061b7104e8

+ 1 - 0
dashboard/src/index.html

@@ -181,5 +181,6 @@
   </head>
   <body>
     <div id="output"></div>
+    <div id="modal-root"></div>
   </body>
 </html>

+ 52 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ConnectToLogsInstructionModal.tsx

@@ -0,0 +1,52 @@
+import Modal from "main/home/modals/Modal";
+import React from "react";
+import styled from "styled-components";
+
+const ConnectToLogsInstructionModal: React.FC<{
+  show: boolean;
+  onClose: () => void;
+  chartName: string;
+  namespace: string;
+}> = ({ show, chartName, namespace, onClose }) => {
+  if (!show) {
+    return null;
+  }
+
+  return (
+    <Modal
+      onRequestClose={() => onClose()}
+      width="700px"
+      height="300px"
+      title="Shell Access Instructions"
+    >
+      To get shell live logs for this pod, make sure you have the Porter CLI
+      installed (installation instructions&nbsp;
+      <a href={"https://docs.porter.run/cli/installation"} target="_blank">
+        here
+      </a>
+      ).
+      <br />
+      <br />
+      Run the following line of code:
+      <Code>
+        porter logs {chartName || "[APP-NAME]"} --follow --namespace{" "}
+        {namespace || "[NAMESPACE]"}
+      </Code>
+    </Modal>
+  );
+};
+
+export default ConnectToLogsInstructionModal;
+
+const Code = styled.div`
+  background: #181b21;
+  padding: 10px 15px;
+  border: 1px solid #ffffff44;
+  border-radius: 5px;
+  margin: 10px 0px 15px;
+  color: #ffffff;
+  font-size: 13px;
+  user-select: text;
+  line-height: 1em;
+  font-family: monospace;
+`;

+ 63 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -11,8 +11,10 @@ import { Context } from "shared/Context";
 import * as Anser from "anser";
 import api from "shared/api";
 import { NewWebsocketOptions, useWebsockets } from "shared/hooks/useWebsockets";
+import CommandLineIcon from "assets/command-line-icon";
+import ConnectToLogsInstructionModal from "./ConnectToLogsInstructionModal";
 
-const MAX_LOGS = 1000;
+const MAX_LOGS = 250;
 
 type SelectedPodType = {
   spec: {
@@ -25,6 +27,9 @@ type SelectedPodType = {
   metadata: {
     name: string;
     namespace: string;
+    labels: {
+      [key: string]: string;
+    };
   };
   status: {
     phase: string;
@@ -49,6 +54,8 @@ const LogsFC: React.FC<{
 
   const [isScrollToBottomEnabled, setIsScrollToBottomEnabled] = useState(true);
 
+  const [showConnectionModal, setShowConnectionModal] = useState(false);
+
   const wrapperRef = useRef<HTMLDivElement>();
 
   const scrollToBottom = (smooth: boolean) => {
@@ -151,6 +158,21 @@ const LogsFC: React.FC<{
 
   const renderContent = () => (
     <>
+      <ConnectToLogsInstructionModal
+        show={showConnectionModal}
+        onClose={() => setShowConnectionModal(false)}
+        chartName={selectedPod?.metadata?.labels["app.kubernetes.io/instance"]}
+        namespace={selectedPod?.metadata?.namespace}
+      />
+      <CLIModalIconWrapper
+        onClick={(e) => {
+          e.preventDefault();
+          setShowConnectionModal(true);
+        }}
+      >
+        <CLIModalIcon />
+        Shell logs livestream
+      </CLIModalIconWrapper>
       <Wrapper ref={wrapperRef}>{renderLogs()}</Wrapper>
       <LogTabs>
         {containers.map((containerName, _i, arr) => {
@@ -572,3 +594,43 @@ const LogSpan = styled.span`
   background-color: ${(props: { ansi: Anser.AnserJsonEntry }) =>
     props.ansi?.bg ? `rgb(${props.ansi?.bg})` : "transparent"};
 `;
+
+const CLIModalIconWrapper = styled.div`
+  max-width: 200px;
+  height: 35px;
+  margin: 10px;
+  font-size: 13px;
+  font-weight: 500;
+  font-family: "Work Sans", sans-serif;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 6px 20px 6px 10px;
+  text-align: left;
+  border: 1px solid #ffffff55;
+  border-radius: 8px;
+  background: #ffffff11;
+  color: #ffffffdd;
+  cursor: pointer;
+  :hover {
+    cursor: pointer;
+    background: #ffffff22;
+    > path {
+      fill: #ffffff77;
+    }
+  }
+
+  > path {
+    fill: #ffffff99;
+  }
+`;
+
+const CLIModalIcon = styled(CommandLineIcon)`
+  width: 32px;
+  height: 32px;
+  padding: 8px;
+
+  > path {
+    fill: #ffffff99;
+  }
+`;

+ 44 - 11
dashboard/src/main/home/modals/Modal.tsx

@@ -1,5 +1,6 @@
 import React, { Component } from "react";
 import styled from "styled-components";
+import ReactDOM from "react-dom";
 
 type PropsType = {
   onRequestClose?: () => void;
@@ -10,6 +11,8 @@ type PropsType = {
 
 type StateType = {};
 
+const modalRoot = document.getElementById("modal-root");
+
 export default class Modal extends Component<PropsType, StateType> {
   wrapperRef: any = React.createRef();
 
@@ -38,21 +41,51 @@ export default class Modal extends Component<PropsType, StateType> {
   render() {
     let { width, height } = this.props;
     return (
-      <Overlay>
-        <StyledModal ref={this.wrapperRef} width={width} height={height}>
-          {this.props.onRequestClose && (
-            <CloseButton onClick={this.props.onRequestClose}>
-              <i className="material-icons">close</i>
-            </CloseButton>
-          )}
-          {this.props.title && <ModalTitle>{this.props.title}</ModalTitle>}
-          {this.props.children}
-        </StyledModal>
-      </Overlay>
+      <PortalModal>
+        <Overlay>
+          <StyledModal ref={this.wrapperRef} width={width} height={height}>
+            {this.props.onRequestClose && (
+              <CloseButton onClick={this.props.onRequestClose}>
+                <i className="material-icons">close</i>
+              </CloseButton>
+            )}
+            {this.props.title && <ModalTitle>{this.props.title}</ModalTitle>}
+            {this.props.children}
+          </StyledModal>
+        </Overlay>
+      </PortalModal>
     );
   }
 }
 
+export class PortalModal extends Component {
+  el: Element;
+  constructor(props: any) {
+    super(props);
+    this.el = document.createElement("div");
+  }
+
+  componentDidMount() {
+    // The portal element is inserted in the DOM tree after
+    // the Modal's children are mounted, meaning that children
+    // will be mounted on a detached DOM node. If a child
+    // component requires to be attached to the DOM tree
+    // immediately when mounted, for example to measure a
+    // DOM node, or uses 'autoFocus' in a descendant, add
+    // state to Modal and only render the children when Modal
+    // is inserted in the DOM tree.
+    modalRoot.appendChild(this.el);
+  }
+
+  componentWillUnmount() {
+    modalRoot.removeChild(this.el);
+  }
+
+  render() {
+    return ReactDOM.createPortal(this.props.children, this.el);
+  }
+}
+
 const ModalTitle = styled.div`
   font-size: 18px;
   font-weight: 500;