sunguroku 5 лет назад
Родитель
Сommit
0133151da9

+ 2 - 1
dashboard/src/components/ResourceTab.tsx

@@ -15,6 +15,7 @@ type PropsType = {
     available?: number,
     total?: number,
   } | null
+  expanded?: boolean,
 };
 
 type StateType = {
@@ -24,7 +25,7 @@ type StateType = {
 
 export default class ResourceTab extends Component<PropsType, StateType> {
   state = {
-    expanded: false,
+    expanded: this.props.expanded || false,
     showTooltip: false,
   }
 

+ 8 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx

@@ -10,6 +10,7 @@ type PropsType = {
   selectedPod: any,
   selectPod: Function,
   isLast?: boolean,
+  isFirst?: boolean,
 };
 
 type StateType = {
@@ -26,7 +27,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
 
   componentDidMount() {
     let { currentCluster, currentProject, setCurrentError } = this.context;
-    let { controller } = this.props;
+    let { controller, selectPod, isFirst } = this.props;
 
     let selectors = [] as string[];
     let ml = controller?.spec?.selector?.matchLabels || controller?.spec?.selector;
@@ -61,6 +62,10 @@ export default class ControllerTab extends Component<PropsType, StateType> {
       });
       
       this.setState({ pods, raw: res.data });
+      
+      if (isFirst) {
+        selectPod(res.data[0])
+      }
     })
   }
 
@@ -104,7 +109,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
   }
 
   render() {
-    let { controller, selectedPod, isLast, selectPod } = this.props;
+    let { controller, selectedPod, isLast, selectPod, isFirst } = this.props;
     let [available, total] = this.getAvailability(controller.kind, controller);
     let status = (available == total) ? 'running' : 'waiting'
     return (
@@ -113,6 +118,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
         name={controller.metadata.name}
         status={{ label: status, available, total }}
         isLast={isLast}
+        expanded={isFirst}
       >
         {
           this.state.raw.map((pod, i) => {

+ 114 - 29
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -8,20 +8,28 @@ type PropsType = {
 
 type StateType = {
   logs: string[],
-  ws: any
+  ws: any,
+  scroll: boolean,
 };
 
 export default class Logs extends Component<PropsType, StateType> {
   
   state = {
     logs: [] as string[],
-    ws : null as any
+    ws : null as any,
+    scroll: true,
   }
 
+  ws = null as any;
   scrollRef = React.createRef<HTMLDivElement>()
+  parentRef = React.createRef<HTMLDivElement>()
 
-  scrollToBottom = () => {
-    this.scrollRef.current.scrollTop = this.scrollRef.current.scrollHeight
+  scrollToBottom = (smooth: boolean) => {
+    if (smooth) {
+      this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "smooth" })
+    } else {
+      this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "auto" })
+    }
   }
 
   renderLogs = () => {
@@ -37,45 +45,73 @@ export default class Logs extends Component<PropsType, StateType> {
     })
   }
 
-  componentDidMount() {
+  setupWebsocket = () => {  
     let { currentCluster, currentProject } = this.context;
     let { selectedPod } = this.props;
     if (!selectedPod.metadata?.name) return
+
     let protocol = process.env.NODE_ENV == 'production' ? 'wss' : 'ws'
-    let ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?cluster_id=${currentCluster.id}&service_account_id=${currentCluster.service_account_id}`)
-    
-    this.setState({ ws }, () => {
-      if (!this.state.ws) return;
-  
-      this.state.ws.onopen = () => {
-        console.log('connected to websocket')
-      }
-  
-      this.state.ws.onmessage = (evt: MessageEvent) => {
-        this.setState({ logs: [...this.state.logs, evt.data] }, () => {
-          this.scrollToBottom()
-        })
-      }
-  
-      this.state.ws.onerror = (err: ErrorEvent) => {
-        console.log(err)
-      }
-    })
+    this.ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?cluster_id=${currentCluster.id}&service_account_id=${currentCluster.service_account_id}`)
+
+    this.ws.onopen = () => {
+      console.log('connected to websocket')
+    }
+
+    this.ws.onmessage = (evt: MessageEvent) => {
+      this.setState({ logs: [...this.state.logs, evt.data] }, () => {
+        if (this.state.scroll && this.state.logs.length >50) {
+          this.scrollToBottom(false)
+        }
+      })
+    }
+
+    this.ws.onerror = (err: ErrorEvent) => {
+      console.log("websocket error:", err)
+    }
+
+    this.ws.onclose = () => {
+      console.log("closing pod logs")
+    }
+  }
+
+  refreshLogs = () => {
+    if (this.ws) {
+      this.ws.close();
+      this.ws = null;
+      this.setState({logs: []})
+      this.setupWebsocket();
+    }
+  }
+
+  componentDidMount() {
+    this.setupWebsocket()
+    this.scrollToBottom(false);
   }
 
   componentWillUnmount() {
-    if (this.state.ws) {
-      console.log('closing websockets')
-      this.state.ws.close()
+    console.log('log unmount')
+    if (this.ws) {
+      this.ws.close()
     }
   }
 
   render() {
     return (
-      <LogStream ref={this.scrollRef}>
-        <Wrapper>
+      <LogStream>
+        <Wrapper ref={this.parentRef}>
           {this.renderLogs()}
+          <div ref={this.scrollRef} />
         </Wrapper>
+        <Options>
+          <Scroll onClick={()=> {this.setState({scroll: !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>
     );
   }
@@ -83,6 +119,54 @@ export default class Logs extends Component<PropsType, StateType> {
 
 Logs.contextType = Context;
 
+const Scroll = styled.div`
+  align-items: center;
+  display: flex;
+  cursor: pointer;
+  width: 145px;
+  height: 100%;
+
+  :hover {
+    background: #2468d6;
+  }
+
+  > input {
+    width; 18px;
+    margin-left: 10px;
+    margin-right: 6px;
+    pointer-events: none;
+  }
+`
+
+const Refresh = styled.div`
+  display: flex;
+  align-items: center;
+  width: 87px;
+  user-select: none;
+  cursor: pointer;
+  height: 100%;
+
+  > i {
+    margin-left: 6px;
+    font-size: 17px;
+    margin-right: 6px;
+  }
+
+  :hover {
+    background: #2468d6;
+  }
+`
+
+const Options = styled.div`
+  width: 100%;
+  height: 25px;
+  background: #397ae3;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+`
+
 const Wrapper = styled.div`
   width: 100%;
   height: 100%;
@@ -92,6 +176,7 @@ const Wrapper = styled.div`
 
 const LogStream = styled.div`
   display: flex;
+  flex-direction: column;
   flex: 1;
   float: right;
   height: 100%;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -51,6 +51,7 @@ export default class StatusSection extends Component<PropsType, StateType> {
           selectPod={this.selectPod.bind(this)}
           controller={c}
           isLast={i === this.state.controllers.length - 1}
+          isFirst={i === 0}
         />
       )
     })

+ 4 - 1
internal/kubernetes/agent.go

@@ -123,9 +123,12 @@ func (a *Agent) GetPodsByLabel(selector string) (*v1.PodList, error) {
 
 // GetPodLogs streams real-time logs from a given pod.
 func (a *Agent) GetPodLogs(namespace string, name string, conn *websocket.Conn) error {
+	tails := int64(300)
+
 	// follow logs
 	podLogOpts := v1.PodLogOptions{
-		Follow: true,
+		Follow:    true,
+		TailLines: &tails,
 	}
 	req := a.Clientset.CoreV1().Pods(namespace).GetLogs(name, &podLogOpts)
 	podLogs, err := req.Stream(context.TODO())