Просмотр исходного кода

basic live streaming of infra status

Alexander Belanger 4 лет назад
Родитель
Сommit
142ec43272

+ 18 - 10
dashboard/src/components/ProvisionerStatus.tsx

@@ -13,6 +13,7 @@ export interface TFModule {
   id: number
   kind: string
   status: string
+  global_errors?: TFResourceError[]
   // optional resources, if not created
   resources?: TFResource[]
 }
@@ -33,11 +34,11 @@ const nameMap: { [key: string]: string } = {
   ecr: "Elastic Container Registry (ECR)",
   doks: "DigitalOcean Kubernetes Service (DOKS)",
   docr: "DigitalOcean Container Registry (DOCR)",
+  gke: "Google Kubernetes Engine (GKE)",
+  gcr: "Google Container Registry (GCR)",
 };
 
-const ProvisionerStatus: React.FC<Props> = (props) => {
-  const { modules } = props;
-
+const ProvisionerStatus: React.FC<Props> = ({ modules }) => {
   const renderStatus = (status: string) => {
     if (status === "successful") {
       return (
@@ -67,9 +68,9 @@ const ProvisionerStatus: React.FC<Props> = (props) => {
         return resource.provisioned;
       }).length;
 
-      var errors: string[] = [];
+      let errors: string[] = [];
 
-      const hasError = val.resources?.filter((resource) => {
+      let hasError = val.resources?.filter((resource) => {
         if (resource.errored?.errored_out) {
           errors.push(resource.errored?.error_context)
         }
@@ -77,20 +78,27 @@ const ProvisionerStatus: React.FC<Props> = (props) => {
         return resource.errored?.errored_out
       }).length > 0
 
+      if (val.global_errors) {
+        for (let globalErr of val.global_errors) {
+          errors.push("Global error: " + globalErr.error_context)
+          hasError = true
+        }
+      }
+
       const width = 100 * (provisionedResources / (totalResources * 1.0)) || 100
 
-      var error = null;
+      let error = null;
 
       if (hasError) {
-        error = errors.map((error) => {
-          return <ExpandedError>{error}</ExpandedError>
+        error = errors.map((error, index) => {
+          return <ExpandedError key={index}>{error}</ExpandedError>
         })
       } else if (val.status == "destroyed") {
         error = <ExpandedError>This infrastructure was destroyed.</ExpandedError>
       }
 
-      var loadingFill;
-      var status;
+      let loadingFill;
+      let status;
 
       if (hasError || val.status == "destroyed") {
         loadingFill = <LoadingFill status="error" width={width + "%"} />

+ 104 - 20
dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/SharedStatus.tsx

@@ -1,4 +1,4 @@
-import ProvisionerStatus, { TFModule, TFResource } from "components/ProvisionerStatus";
+import ProvisionerStatus, { TFModule, TFResource, TFResourceError } from "components/ProvisionerStatus";
 import React, { useEffect, useState } from "react";
 import api from "shared/api";
 import { useWebsockets } from "shared/hooks/useWebsockets";
@@ -16,10 +16,59 @@ export const SharedStatus: React.FC<{
     } = useWebsockets();
   
     const [tfModules, setTFModules] = useState<TFModule[]>([]);
+
+    const updateTFModules = (
+      index : number,
+      addedResources : TFResource[],
+      erroredResources : TFResource[],
+      globalErrors : TFResourceError[],
+    ) => {
+      if (!tfModules[index]?.resources) {
+        tfModules[index].resources = []
+      }
+
+      if (!tfModules[index]?.global_errors) {
+        tfModules[index].global_errors = []
+      }
+
+      let resources = tfModules[index].resources
+
+      // construct map of tf resources addresses to indices
+      let resourceAddrMap = new Map<string, number>()
+
+      tfModules[index].resources.forEach((resource, index) => {
+        resourceAddrMap.set(resource.addr, index)
+      });
+
+      for (let addedResource of addedResources) {
+        // if exists, update state to provisioned
+        if (resourceAddrMap.has(addedResource.addr)) {
+          resources[resourceAddrMap.get(addedResource.addr)] = addedResource
+        } else {
+          resources.push(addedResource)
+          resourceAddrMap.set(addedResource.addr, resources.length - 1)
+        }
+      }
+
+      for (let erroredResource of erroredResources) {
+        // if exists, update state to provisioned
+        if (resourceAddrMap.has(erroredResource.addr)) {
+          resources[resourceAddrMap.get(erroredResource.addr)] = erroredResource
+        } else {
+          resources.push(erroredResource)
+          resourceAddrMap.set(erroredResource.addr, resources.length - 1)
+        }
+      }
+
+      tfModules[index].global_errors = [...tfModules[index].global_errors, ...globalErrors]
+
+      setTFModules([...tfModules])
+    }
   
     const setupInfraWebsocket = (
       websocketID: string,
-      module: TFModule
+      module: TFModule,
+      index: number,
     ) => {
       let apiPath = `/api/projects/${project_id}/infras/${module.id}/logs`;
   
@@ -28,7 +77,50 @@ export const SharedStatus: React.FC<{
           console.log(`connected to websocket: ${websocketID}`);
         },
         onmessage: (evt: MessageEvent) => {
-          console.log("EVENT IS", evt)
+          // parse the data
+          let parsedData = JSON.parse(evt.data)
+
+          let addedResources : TFResource[] = []
+          let erroredResources : TFResource[] = []
+          let globalErrors : TFResourceError[] = []
+
+          for (let streamVal of parsedData) {
+            let streamValData = JSON.parse(streamVal?.Values?.data)
+
+            switch (streamValData?.type) {
+              case "apply_complete":
+                addedResources.push({
+                  addr: streamValData?.hook?.resource?.addr,
+                  provisioned: true,
+                  errored: {
+                    errored_out: false,
+                  },
+                })
+
+                break
+              case "diagnostic":
+                if (streamValData["@level"] == "error") {
+                  if (streamValData?.hook?.resource?.addr != "") {
+                    erroredResources.push({
+                      addr: streamValData?.hook?.resource?.addr,
+                      provisioned: false,
+                      errored: {
+                        errored_out: true,
+                        error_context: streamValData["@message"],
+                      },
+                    })
+                  } else {
+                    globalErrors.push({
+                      errored_out: true,
+                      error_context: streamValData["@message"],
+                    })
+                  }
+                }
+              default:
+            }
+          }
+
+          updateTFModules(index, addedResources, erroredResources, globalErrors)
         },
   
         onclose: () => {
@@ -57,9 +149,7 @@ export const SharedStatus: React.FC<{
             matchedInfras.set(infra.kind, infra)
           }
         })
-  
-        var modules : TFModule[] = []
-        
+          
         // query for desired and current state, and convert to tf module
         matchedInfras.forEach((infra : any) => {
           var module : TFModule = {
@@ -98,25 +188,19 @@ export const SharedStatus: React.FC<{
             }).catch((err) => console.log(err))
           }
 
-          modules.push(module)
+          tfModules.push(module)
         });
   
-        setTFModules(modules)
+        setTFModules([...tfModules])
+
+        tfModules.forEach((val, index) => {
+          setupInfraWebsocket(val.id + "", val, index);
+        }) 
       })
+
+      return closeAllWebsockets
     }, [])
   
-    useEffect(() => {
-      tfModules.forEach((val) => {
-        setupInfraWebsocket(val.id + "", val);
-      })
-  
-      return () => {
-        tfModules.forEach((val) => {
-          closeWebsocket(val.id + "");        
-        })
-      };
-    }, [tfModules]);
-  
     return (
       <>
         <ProvisionerStatus