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

+ 77 - 12
dashboard/src/components/ProvisionerStatus.tsx

@@ -6,9 +6,28 @@ import loading from "assets/loading.gif";
 import styled from "styled-components";
 
 type Props = {
+  modules : TFModule[]
 };
 
-const ProvisionerStatus: React.FC<Props> = () => {
+export interface TFModule {
+  id: number
+  kind: string
+  resources: TFResource[]
+}
+
+export interface TFResource {
+  addr: string,
+  provisioned: boolean,
+  error: string,
+}
+
+const nameMap : { [key: string]: string } = {
+  "eks": "Elastic Kubernetes Service (EKS)",
+  "ecr": "Elastic Container Registry (ECR)",
+}
+
+const ProvisionerStatus: React.FC<Props> = (props) => {
+  const { modules } = props;
 
   const renderStatus = (status: string) => {
     if (status === "successful") {
@@ -32,18 +51,64 @@ const ProvisionerStatus: React.FC<Props> = () => {
     }
   };
 
+  const renderModules = () => {
+    return modules.map((val) => {
+      const totalResources = val.resources?.length
+      const provisionedResources = val.resources?.filter((resource) => {
+        return resource.provisioned
+      }).length
+
+      var errors : string[] = []
+
+      const hasError = val.resources?.filter((resource) => {
+        if (resource.error !== "") {
+          errors.push(resource.error)
+        }
+
+        return resource.error !== ""
+      }).length > 0
+
+      const width = 100 * (provisionedResources / (totalResources * 1.0))
+
+      var error = null
+
+      if (hasError) {
+        error = errors.map((error) => {
+          return <ExpandedError>{error}</ExpandedError>
+        })
+      }
+
+      var loadingFill 
+      var status 
+
+      if (hasError) {
+        loadingFill = <LoadingFill status="error" width={width + "%"} />
+        status = renderStatus("error")
+      } else if (width == 100) {
+        loadingFill = <LoadingFill status="successful" width={width + "%"} />
+        status = renderStatus("successful")
+      } else {
+        loadingFill = <LoadingFill status="loading" width={width + "%"} />
+        status = renderStatus("loading")
+      }
+
+      return <InfraObject key={val.id}>
+        <InfraHeader>
+          {status}
+          {nameMap[val.kind]}
+        </InfraHeader>
+        <LoadingBar>
+          {loadingFill}
+        </LoadingBar>
+        {error}
+      </InfraObject>
+    })
+  }
+
   return (
     <StyledProvisionerStatus>
-        <InfraObject>
-          <InfraHeader>
-            {renderStatus("successful")}
-            Elastic Kubernetes Service (EKS)
-          </InfraHeader>
-          <LoadingBar>
-            <LoadingFill status="successful" width="100%" />
-          </LoadingBar>
-        </InfraObject>
-        <InfraObject>
+        {renderModules()}
+        {/* <InfraObject>
           <InfraHeader>
             {renderStatus("loading")}
             Elastic Kubernetes Service (EKS)
@@ -63,7 +128,7 @@ const ProvisionerStatus: React.FC<Props> = () => {
           <ExpandedError>
             422 validation error: autoscaling failed because sometimes infrastructure is a bit mysterious and hard to predict.
           </ExpandedError>
-        </InfraObject>
+        </InfraObject> */}
     </StyledProvisionerStatus>
   );
 };

+ 8 - 4
dashboard/src/main/home/onboarding/steps/ProvisionResources/ProvisionResources.tsx

@@ -40,7 +40,11 @@ const ProvisionResources: React.FC<Props> = ({
   enable_go_back,
   goBack,
 }) => {
-  const { step } = useParams<{ step: any }>();
+  // TODO: remove this
+  // const { step } = useParams<{ step: any }>();
+
+  
+  const step = "status"
 
   return (
     <div>
@@ -60,9 +64,9 @@ const ProvisionResources: React.FC<Props> = ({
         applications.
       </Helper>
 
-      <ProvisionerStatus />
+      {/* <ProvisionerStatus /> */}
 
-      {/*
+      
       {provider ? (
         provider !== "external" ? (
           <FormFlowWrapper
@@ -85,7 +89,7 @@ const ProvisionResources: React.FC<Props> = ({
           />
         </>
       )}
-      */}
+     
     </div>
   );
 };

+ 4 - 4
dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/FormFlow.tsx

@@ -28,17 +28,17 @@ const Forms = {
   aws: {
     credentials: AWSCredentialsForm,
     settings: AWSSettingsForm,
-    // status: AWSProvisionerStatus,
+    status: AWSProvisionerStatus,
   },
   gcp: {
     credentials: GCPCredentialsForm,
     settings: GCPSettingsForm,
-    // status: GCPProvisionerStatus,
+    status: GCPProvisionerStatus,
   },
   do: {
     credentials: DOCredentialsForm,
     settings: DOSettingsForm,
-    // status: DOProvisionerStatus,
+    status: DOProvisionerStatus,
   },
 };
 
@@ -52,7 +52,7 @@ type Props = {
   onSaveCredentials: (credentials: any) => void;
   onSaveSettings: (settings: any) => void;
   provider: SupportedProviders | "external";
-  currentStep: "credentials" | "settings";
+  currentStep: "credentials" | "settings" | "status";
   project: { id: number; name: string };
 };
 

+ 106 - 3
dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/_DOProvisionerForm.tsx

@@ -1,6 +1,7 @@
 import Helper from "components/form-components/Helper";
 import InputRow from "components/form-components/InputRow";
 import SelectRow from "components/form-components/SelectRow";
+import ProvisionerStatus, { TFModule, TFResource } from "components/ProvisionerStatus";
 import SaveButton from "components/SaveButton";
 import { OFState } from "main/home/onboarding/state";
 import { DOProvisionerConfig } from "main/home/onboarding/types";
@@ -8,6 +9,7 @@ import React, { useEffect, useState } from "react";
 import api from "shared/api";
 import styled from "styled-components";
 import { useSnapshot } from "valtio";
+import { useWebsockets } from "shared/hooks/useWebsockets";
 
 const tierOptions = [
   { value: "basic", label: "Basic" },
@@ -204,10 +206,111 @@ export const SettingsForm: React.FC<{
 export const Status: React.FC<{
   nextFormStep: () => void;
   project: any;
-}> = ({ nextFormStep }) => {
+}> = ({ nextFormStep, project }) => {
+  const {
+    newWebsocket,
+    openWebsocket,
+    closeWebsocket,
+    closeAllWebsockets,
+  } = useWebsockets();
+
+  const [tfModules, setTFModules] = useState<TFModule[]>([]);
+
+  const setupInfraWebsocket = (
+    websocketID: string,
+    module: TFModule
+  ) => {
+    let apiPath = `/api/projects/${project?.id}/infras/${module.id}/logs`;
+
+    const wsConfig = {
+      onopen: () => {
+        console.log(`connected to websocket: ${websocketID}`);
+      },
+      onmessage: (evt: MessageEvent) => {
+        console.log("EVENT IS", evt)
+      },
+
+      onclose: () => {
+        console.log(`closing websocket: ${websocketID}`);
+      },
+
+      onerror: (err: ErrorEvent) => {
+        console.log(err);
+        closeWebsocket(websocketID);
+      },
+    };
+
+    newWebsocket(websocketID, apiPath, wsConfig);
+    openWebsocket(websocketID);
+  };
+
+  useEffect(() => {
+    const filter : string[] = ["doks", "docr"]
+
+    api.getInfra("<token>", {}, { project_id: project?.id }).then((res) => {
+      var matchedInfras : Map<string, any>
+
+      res.data.forEach((infra : any) => {
+        if (filter.includes(infra.kind) && matchedInfras.get(infra.Kind)?.id < infra.id) {
+          matchedInfras.set(infra.Kind, infra)
+        }
+      })
+
+      // query for desired and current state, and convert to tf module
+      matchedInfras.forEach((kind, infra : any) => {
+        api.getInfraDesired("<token>", {}, { project_id: project?.id, infra_id: infra?.id }).then((resDesired) => {
+          api.getInfraCurrent("<token>", {}, { project_id: project?.id, infra_id: infra?.id }).then((resCurrent) => {
+            var desired = resDesired.data
+            var current = resCurrent.data
+
+            // convert current state to a lookup table
+            var currentMap : Map<string, string>
+
+            current?.resources?.forEach((val : any) => {
+              currentMap.set(val?.type + "." + val?.name, "")
+            })
+
+            // map desired state to list of resources
+            var resources : TFResource[] = desired?.map((val : any) => {
+              return {
+                addr: val?.addr,
+                provisioned: currentMap.has(val?.addr),
+                // TODO: add error types
+                error: "",
+              }
+            })
+
+            var module : TFModule = {
+              id: infra.id,
+              kind: infra.kind,
+              resources: resources,
+            }
+
+            setTFModules([...tfModules, module])
+          })
+        })
+      });
+    })
+  }, [])
+
+  useEffect(() => {
+    tfModules.forEach((val) => {
+      setupInfraWebsocket(val.id + "", val);
+    })
+
+    return () => {
+      tfModules.forEach((val) => {
+        closeWebsocket(val.id + "");        
+      })
+    };
+  }, [tfModules]);
+
   return (
     <>
-      <SaveButton
+      <ProvisionerStatus 
+        modules={tfModules}
+      />
+      {/* <SaveButton
         text="Continue"
         disabled={false}
         onClick={nextFormStep}
@@ -215,7 +318,7 @@ export const Status: React.FC<{
         clearPosition={true}
         status={""}
         statusPosition={"right"}
-      />
+      /> */}
     </>
   );
 };

+ 22 - 0
dashboard/src/shared/api.tsx

@@ -564,6 +564,26 @@ const getInfra = baseApi<
   return `/api/projects/${pathParams.project_id}/infra`;
 });
 
+const getInfraDesired = baseApi<
+  {},
+  {
+    project_id: number;
+    infra_id: number;
+  }
+>("GET", (pathParams) => {
+  return `/api/projects/${pathParams.project_id}/infras/${pathParams.infra_id}/desired`;
+});
+
+const getInfraCurrent = baseApi<
+  {},
+  {
+    project_id: number;
+    infra_id: number;
+  }
+>("GET", (pathParams) => {
+  return `/api/projects/${pathParams.project_id}/infras/${pathParams.infra_id}/current`;
+});
+
 const getIngress = baseApi<
   {},
   { namespace: string; cluster_id: number; name: string; id: number }
@@ -1155,6 +1175,8 @@ export default {
   getImageRepos,
   getImageTags,
   getInfra,
+  getInfraDesired,
+  getInfraCurrent,
   getIngress,
   getInvites,
   getJobs,

+ 1 - 0
internal/kubernetes/provisioner/input/eks.go

@@ -7,6 +7,7 @@ import (
 type EKS struct {
 	AWSRegion   string `json:"aws_region"`
 	ClusterName string `json:"cluster_name"`
+	MachineType string `json:"machine_type"`
 }
 
 func (eks *EKS) GetInput() ([]byte, error) {