Răsfoiți Sursa

provisioner WIP

sunguroku 5 ani în urmă
părinte
comite
f721859ed6

+ 8 - 25
dashboard/src/main/home/Home.tsx

@@ -19,7 +19,6 @@ import IntegrationsModal from './modals/IntegrationsModal';
 import IntegrationsInstructionsModal from './modals/IntegrationsInstructionsModal';
 import NewProject from './new-project/NewProject';
 import Navbar from './navbar/Navbar';
-import ProvisionerStatus from './provisioner/ProvisionerStatus';
 import ProjectSettings from './project-settings/ProjectSettings';
 import ConfirmOverlay from 'components/ConfirmOverlay';
 import Modal from './modals/Modal';
@@ -61,28 +60,16 @@ class Home extends Component<PropsType, StateType> {
   // TODO: Refactor and prevent flash + multiple reload
   initializeView = () => {
     let { currentProject } = this.props;
-    let { currentCluster } = this.context;
     
     if (!currentProject) return;
 
-    // Check if current project is provisioning
-    api.getInfra('<token>', {}, { project_id: currentProject.id }, (err: any, res: any) => {
-      if (err) {
-        console.log(err);
-        return;
-      }
-
-      if (res.data.length > 0 && (!currentCluster && !includesCompletedInfraSet(res.data))) {
-        this.props.history.push("provisioner");
-        this.setState({ sidebarReady: true });
-      } else if (this.state.ghRedirect) {
-        this.props.history.push("integrations");
-        this.setState({ sidebarReady: true, ghRedirect: false });
-      } else {
-        this.props.history.push("dashboard");
-        this.setState({ sidebarReady: true });
-      }
-    });
+    if (this.state.ghRedirect) {
+      this.props.history.push("integrations");
+      this.setState({ sidebarReady: true, ghRedirect: false });
+    } else {
+      this.props.history.push("dashboard");
+      this.setState({ sidebarReady: true });
+    }
   }
 
   getProjects = (id?: number) => {
@@ -215,7 +202,7 @@ class Home extends Component<PropsType, StateType> {
     this.setState({ ghRedirect: urlParams.get('gh_oauth') !== null });
     urlParams.delete('gh_oauth');
     
-    window.location.href.indexOf('127.0.0.1') === -1 && posthog.init(process.env.POSTHOG_API_KEY || 'placeholder', {
+    window.location.href.indexOf('localhost') === -1 && posthog.init(process.env.POSTHOG_API_KEY || 'placeholder', {
       api_host: process.env.POSTHOG_HOST || 'placeholder',
       loaded: function(posthog: any) { 
         posthog.identify(user.userId) 
@@ -292,10 +279,6 @@ class Home extends Component<PropsType, StateType> {
         );
       } else if (currentView === 'integrations') {
         return <Integrations />;
-      } else if (currentView === 'provisioner') {
-        return (
-          <ProvisionerStatus/>
-        );
       } else if (currentView === 'project-settings') {
         return (
           <ProjectSettings />

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

@@ -76,14 +76,14 @@ export default class StatusSection extends Component<PropsType, StateType> {
           {this.renderLogs()}
         </Wrapper>
       )
-    } else {
-      return (
-        <NoControllers> 
-          <i className="material-icons">category</i> 
-          No objects to display. This might happen while your app is still deploying.
-        </NoControllers>
-      )
     }
+
+    return (
+      <NoControllers> 
+        <i className="material-icons">category</i> 
+        No objects to display. This might happen while your app is still deploying.
+      </NoControllers>
+    )
   }
 
   componentDidMount() {

+ 5 - 1
dashboard/src/main/home/provisioner/InfraStatuses.tsx

@@ -7,6 +7,7 @@ import { infraNames } from 'shared/common';
 
 type PropsType = {
   infras: InfraType[],
+  selectInfra: (infra: InfraType) => void,
 };
 
 type StateType = {
@@ -31,7 +32,10 @@ export default class InfraStatuses extends Component<PropsType, StateType> {
       <StyledInfraStatuses>
         {this.props.infras.map((infra: InfraType, i: number) => {
           return (
-            <InfraRow key={infra.id}>
+            <InfraRow 
+              key={infra.id}
+              onClick={() => this.props.selectInfra(infra)}
+            >
               {this.renderStatusIcon(infra.status)}
               {infraNames[infra.kind]}
             </InfraRow>

+ 65 - 144
dashboard/src/main/home/provisioner/Provisioner.tsx

@@ -8,9 +8,11 @@ import ansiparse from 'shared/ansiparser'
 import loading from 'assets/loading.gif';
 import warning from 'assets/warning.png';
 import { InfraType } from 'shared/types';
+import Loading from 'components/Loading';
 
 import Helper from 'components/values-form/Helper';
 import InfraStatuses from './InfraStatuses';
+import ProvisionerLogs from './ProvisionerLogs'
 import { RouteComponentProps, withRouter } from "react-router";
 import { Link } from "react-router-dom";
 
@@ -24,6 +26,8 @@ type StateType = {
   currentStep: Record<string, number>,
   triggerEnd: boolean,
   infras: InfraType[],
+  loading: boolean,
+  selectedInfra: InfraType,
 };
 
 class Provisioner extends Component<PropsType, StateType> {
@@ -35,168 +39,57 @@ class Provisioner extends Component<PropsType, StateType> {
     currentStep: {} as Record<string, number>,
     triggerEnd: false,
     infras: [] as InfraType[],
+    selectedInfra: null as InfraType,
+    loading: true,
   }
 
-  parentRef = React.createRef<HTMLDivElement>()
-
-  scrollToBottom = (smooth: boolean) => {
-    if (smooth) {
-      this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "smooth" })
-    } else {
-      this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "auto" })
-    }
-  }
-
-  isJSON = (str: string) => {
-    try {
-      JSON.parse(str);
-    } catch (e) {
-      return false;
-    }
-    return true;
-  }
-
-  setupWebsocket = (ws: WebSocket, infra: any) => {
-    ws.onopen = () => {
-      console.log('connected to websocket')
-    }
-
-    ws.onmessage = (evt: MessageEvent) => {
-      let event = JSON.parse(evt.data);
-      let validEvents = [] as any[];
-      let err = null;
-      
-      for (var i = 0; i < event.length; i++) {
-        let msg = event[i];
-        if (msg["Values"] && msg["Values"]["data"] && this.isJSON(msg["Values"]["data"])) { 
-          let d = JSON.parse(msg["Values"]["data"]);
-
-          if (d["kind"] == "error") {
-            err = d["log"];
-            break;
-          }
-
-          // add only valid events
-          if (d["log"] != null && d["created_resources"] != null && d["total_resources"] != null) {
-            validEvents.push(d);
-          }
-        }
-      }
-
-      if (err) {
-        posthog.capture('Provisioning Error', {error: err});
-
-        let e = ansiparse(err).map((el: any) => {
-          return el.text;
-        })
-
-        let index = this.state.infras.findIndex(el => el.kind === infra.kind)
-        infra.status = "error"
-        let infras = this.state.infras
-        infras[index] = infra
-        this.setState({ logs: [...this.state.logs, ...e], error: true, infras });
-        return;
-      }
-
-      if (validEvents.length == 0) {
-        return;
-      }
-
-      if (!this.state.maxStep[infra.kind] || !this.state.maxStep[infra.kind]["total_resources"]) {
-        this.setState({
-          maxStep: {
-            ...this.state.maxStep,
-            [infra.kind] : validEvents[validEvents.length - 1]["total_resources"]
-          }
-        })
-      }
-      
-      let logs = [] as any[]
-      validEvents.forEach((e: any) => {
-        logs.push(...ansiparse(e["log"]))
-      })
-
-      logs = logs.map((log: any) => {
-        return log.text
-      })
-
-      this.setState({ 
-        logs: [...this.state.logs, ...logs], 
-        currentStep: {
-          ...this.state.currentStep,
-          [infra.kind] : validEvents[validEvents.length - 1]["created_resources"]
-        },
-      }, () => {
-        this.scrollToBottom(false)
-      })
-    }
-
-    ws.onerror = (err: ErrorEvent) => {
-      console.log('websocket err', err)
-    }
-
-    ws.onclose = () => {
-      console.log('closing provisioner websocket')
-    }
-
-    return ws
-  }
-
-  onEnd = () => {
-    let myInterval = setInterval(() => {
-      api.getClusters('<token>', {}, { 
-        id: this.context.currentProject.id 
-      }, (err: any, res: any) => {
-        if (err) {
-          console.log(err);
-        } else if (res.data) {
-          let clusters = res.data;
-          if (clusters.length > 0) {
-            this.props.history.push("dashboard");
-            clearInterval(myInterval);
-          }
-        }
-      });
-    }, 1000);
-  }
-
-  renderTabs = () => {
-    return this.state.infras.map((infra, i) => {
-      console.log(infra)
-      // return (
-      //   <ControllerTab 
-      //     key={i} 
-      //     selectedPod={this.state.selectedPod} 
-      //     selectPod={this.selectPod.bind(this)}
-      //     controller={c}
-      //     isLast={i === this.state.controllers.length - 1}
-      //     isFirst={i === 0}
-      //   />
-      // )
-    })
+  selectInfra = (infra: InfraType) => {
+    this.setState({ selectedInfra: infra })
   }
 
   componentDidMount() {
     let { currentProject } = this.context;
 
-    // Check if current project is provisioning
     api.getInfra('<token>', {}, { 
       project_id: currentProject.id 
     }, (err: any, res: any) => {
       if (err) return;
       
-      let infras = res.data;
-      let error = false;
-      this.setState({ error, infras });
+      this.setState({ 
+        error: false, 
+        infras: res.data, 
+        loading: false 
+      });
     });
   }
 
   render() {
-    console.log(this.state.infras)
+    if (this.state.loading) {
+      return (
+        <StyledProvisioner> 
+          <Loading />
+        </StyledProvisioner>
+      )
+    }
+
+    if (this.state.infras.length > 0) {
+      return (
+        <StyledProvisioner>
+          <TabWrapper>
+            <InfraStatuses 
+              infras={this.state.infras} 
+              selectInfra={this.selectInfra.bind(this)}
+            />
+          </TabWrapper>
+
+          <ProvisionerLogs selectedInfra={this.state.selectedInfra} />
+        </StyledProvisioner>
+      )
+    }
+
     return (
       <StyledProvisioner>
-        {this.renderTabs()}
-        [TODO: implement provisioner]
+        You have not provisioned any resources for this project through Porter.
       </StyledProvisioner>
     );
   }
@@ -206,6 +99,27 @@ Provisioner.contextType = Context;
 
 export default withRouter(Provisioner);
 
+const Wrapper = styled.div`
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  padding: 20px 25px;
+`;
+
+const Log = styled.div`
+  font-family: monospace;
+`;
+
+const LogStream = styled.div`
+  height: 300px;
+  margin-top: 20px;
+  font-size: 13px;
+  border: 2px solid #ffffff55;
+  border-radius: 10px;
+  width: 100%;
+  background: #00000022;
+  user-select: text;
+`;
 
 const StyledProvisioner = styled.div`
   width: 100%;
@@ -217,4 +131,11 @@ const StyledProvisioner = styled.div`
   justify-content: center;
   font-size: 13px;
   margin-top: 10px;
+`;
+
+const TabWrapper = styled.div`
+  width: 35%;
+  min-width: 250px;
+  height: 100%;
+  overflow-y: auto;
 `;

+ 239 - 0
dashboard/src/main/home/provisioner/ProvisionerLogs.tsx

@@ -0,0 +1,239 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import { Context } from 'shared/Context';
+import { InfraType } from 'shared/types';
+import posthog from 'posthog-js';
+
+import ansiparse from 'shared/ansiparser'
+import loading from 'assets/loading.gif';
+import warning from 'assets/warning.png';
+
+type PropsType = {
+  selectedInfra: InfraType,
+};
+
+type StateType = {
+  logs: string[],
+  ws: any,
+  scroll: boolean,
+  maxStep: number,
+  currentStep: number,
+};
+
+export default class Logs extends Component<PropsType, StateType> {
+  
+  state = {
+    logs: [] as string[],
+    ws : null as any,
+    scroll: true,
+    maxStep: 0,
+    currentStep: 0,
+  }
+
+  ws = null as any;
+  parentRef = React.createRef<HTMLDivElement>()
+
+  scrollToBottom = () => {
+    this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "auto" })
+  }
+
+  renderLogs = () => {
+    let { selectedInfra } = this.props;
+    if (!selectedInfra) {
+      return <Message>Please select a pod to view its logs.</Message>
+    }
+    if (this.state.logs.length == 0) {
+      return <Message>No logs to display from this pod.</Message>
+    }
+    return this.state.logs.map((log, i) => {
+        return <Log key={i}>{log}</Log>
+    })
+  }
+
+  isJSON = (str: string) => {
+    try {
+      JSON.parse(str);
+    } catch (e) {
+      return false;
+    }
+    return true;
+  }
+
+  setupWebsocket = () => {
+    let { selectedInfra }= this.props;
+    this.ws.onopen = () => {
+      console.log('connected to websocket')
+    }
+
+    this.ws.onmessage = (evt: MessageEvent) => {
+      let event = JSON.parse(evt.data);
+      let validEvents = [] as any[];
+      let err = null;
+      
+      for (var i = 0; i < event.length; i++) {
+        let msg = event[i];
+        if (msg["Values"] && msg["Values"]["data"] && this.isJSON(msg["Values"]["data"])) { 
+          let d = JSON.parse(msg["Values"]["data"]);
+
+          if (d["kind"] == "error") {
+            err = d["log"];
+            break;
+          }
+
+          // add only valid events
+          if (d["log"] != null && d["created_resources"] != null && d["total_resources"] != null) {
+            validEvents.push(d);
+          }
+        }
+      }
+
+      if (err) {
+        posthog.capture('Provisioning Error', {error: err});
+
+        let e = ansiparse(err).map((el: any) => {
+          return el.text;
+        })
+
+        this.setState({ logs: [...this.state.logs, ...e] });
+        return;
+      }
+
+      if (validEvents.length == 0) {
+        return;
+      }
+
+      this.setState({
+          maxStep: validEvents[validEvents.length - 1]["total_resources"]
+      })
+      
+      let logs = [] as any[]
+      validEvents.forEach((e: any) => {
+        logs.push(...ansiparse(e["log"]))
+      })
+
+      logs = logs.map((log: any) => {
+        return log.text
+      })
+
+      this.setState({ 
+        logs: [...this.state.logs, ...logs], 
+        currentStep: validEvents[validEvents.length - 1]["created_resources"],
+      }, () => {
+        this.scrollToBottom()
+      })
+    }
+
+    this.ws.onerror = (err: ErrorEvent) => {
+      console.log('websocket err', err)
+    }
+
+    this.ws.onclose = () => {
+      console.log('closing provisioner websocket')
+    }
+  }
+
+  componentDidMount() {
+    this.setupWebsocket()
+    this.scrollToBottom();
+  }
+
+  componentWillUnmount() {
+    if (this.ws) {
+      this.ws.close()
+    }
+  }
+
+  render() {
+    return (
+      <LogStream>
+        <Wrapper ref={this.parentRef}>
+          {this.renderLogs()}
+        </Wrapper>
+      </LogStream>
+    );
+  }
+}
+
+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%;
+  overflow: auto;
+  padding: 25px 30px;
+`;
+
+const LogStream = styled.div`
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  float: right;
+  height: 100%;
+  background: #202227;
+  user-select: text;
+  max-width: 65%;
+  overflow-y: auto;
+  overflow-wrap: break-word; 
+`;
+
+const Message = styled.div`
+  display: flex;
+  height: 100%;
+  width: 100%;
+  align-items: center;
+  justify-content: center;
+  color: #ffffff44;
+  font-size: 13px;
+`;
+
+const Log = styled.div`
+  font-family: monospace;
+`;

+ 1 - 2
dashboard/src/main/home/provisioner/ProvisionerStatus.tsx

@@ -315,8 +315,7 @@ class ProvisionerStatus extends Component<PropsType, StateType> {
             }
           />
         </LoadingBar>
-        <InfraStatuses infras={infras} />
-
+        
         <LogStream>
           <Wrapper ref={this.parentRef}>{this.renderLogs()}</Wrapper>
         </LogStream>

+ 11 - 0
staging.sh

@@ -0,0 +1,11 @@
+{
+export API_SERVER_CONTAINER=porter-server-657f5c594c-nvdd2; 
+export WEBPACK_SERVER_CONTAINER=porter-webpack-64d48578b5-vnk7x; 
+kubectl port-forward $API_SERVER_CONTAINER 8081:8080 & 
+kubectl port-forward $WEBPACK_SERVER_CONTAINER 8082:8080 & 
+devspace sync --upload-only --container-path /webpack --local-path ./dashboard/ --pod $WEBPACK_SERVER_CONTAINER & 
+devspace sync --upload-only --exclude dashboard --pod $API_SERVER_CONTAINER & 
+kubectl logs $API_SERVER_CONTAINER -f & 
+kubectl logs $WEBPACK_SERVER_CONTAINER -f & 
+nginx -c $(pwd)/docker/nginx_remote.conf;
+}