فهرست منبع

add azure credentials form to frontend and improve infra

Alexander Belanger 4 سال پیش
والد
کامیت
a62177d789

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

@@ -394,16 +394,21 @@ type OperationDetailsProps = {
   infra: Infrastructure;
   can_delete?: boolean;
   refreshInfra: (completed?: boolean, errored?: boolean) => void;
+  useOperation?: Operation;
+  padding?: string;
 };
 
-const OperationDetails: React.FunctionComponent<OperationDetailsProps> = ({
+export const OperationDetails: React.FunctionComponent<OperationDetailsProps> = ({
   infra,
   can_delete,
   refreshInfra,
+  useOperation,
+  padding,
 }) => {
-  const [isLoading, setIsLoading] = useState(true);
+  const [isLoading, setIsLoading] = useState(!useOperation);
   const [hasError, setHasError] = useState(false);
-  const [operation, setOperation] = useState<Operation>(null);
+  const [operation, setOperation] = useState<Operation>(useOperation);
+
   const [infraState, setInfraState] = useState<TFState>(null);
   const [infraStateInitialized, setInfraStateInitialized] = useState(false);
   const { currentProject, setCurrentError } = useContext(Context);
@@ -526,7 +531,7 @@ const OperationDetails: React.FunctionComponent<OperationDetailsProps> = ({
         {
           project_id: currentProject.id,
           infra_id: infra.id,
-          operation_id: infra.latest_operation.id,
+          operation_id: useOperation?.id || infra.latest_operation.id,
         }
       )
       .then(({ data }) => {
@@ -760,7 +765,7 @@ const OperationDetails: React.FunctionComponent<OperationDetailsProps> = ({
   };
 
   return (
-    <StyledCard>
+    <StyledCard padding={padding}>
       {renderLoadingBar(
         createdResources.length + deletedResources.length,
         createdResources.length +
@@ -780,8 +785,8 @@ const OperationDetails: React.FunctionComponent<OperationDetailsProps> = ({
   );
 };
 
-const StyledCard = styled.div`
-  padding: 12px 20px;
+const StyledCard = styled.div<{ padding?: string }>`
+  padding: ${(props) => props.padding || "12px 20px"};
   max-height: 300px;
   overflow-y: auto;
 `;

+ 10 - 8
dashboard/src/main/home/infrastructure/ExpandedInfra.tsx

@@ -37,8 +37,10 @@ const ExpandedInfra: React.FunctionComponent = () => {
       return;
     }
 
-    let isSubscribed = true;
+    refreshInfra();
+  }, [currentProject, infra_id]);
 
+  const refreshInfra = () => {
     api
       .getInfraByID(
         "<token>",
@@ -49,10 +51,6 @@ const ExpandedInfra: React.FunctionComponent = () => {
         }
       )
       .then(({ data }) => {
-        if (!isSubscribed) {
-          return;
-        }
-
         setInfra(data);
       })
       .catch((err) => {
@@ -60,7 +58,7 @@ const ExpandedInfra: React.FunctionComponent = () => {
         setHasError(true);
         setCurrentError(err.response?.data?.error);
       });
-  }, [currentProject, infra_id]);
+  };
 
   useEffect(() => {
     if (!currentProject || !infra) {
@@ -132,12 +130,16 @@ const ExpandedInfra: React.FunctionComponent = () => {
     switch (newTab) {
       case "deploys":
         return (
-          <DeployList infra={infra} setLatestOperation={setLatestOperation} />
+          <DeployList
+            infra={infra}
+            setLatestOperation={setLatestOperation}
+            refreshInfra={refreshInfra}
+          />
         );
       case "resources":
         return <InfraResourceList infra_id={infra_id} />;
       case "settings":
-        return <InfraSettings infra_id={infra_id} onDelete={() => {}} />;
+        return <InfraSettings infra_id={infra_id} onDelete={refreshInfra} />;
     }
   };
 

+ 4 - 1
dashboard/src/main/home/infrastructure/components/DeployList.tsx

@@ -17,11 +17,13 @@ import ExpandedOperation from "./ExpandedOperation";
 type Props = {
   infra: Infrastructure;
   setLatestOperation: (operation: Operation) => void;
+  refreshInfra: () => void;
 };
 
 const DeployList: React.FunctionComponent<Props> = ({
   infra,
   setLatestOperation,
+  refreshInfra,
 }) => {
   const [isLoading, setIsLoading] = useState(true);
   const [hasError, setHasError] = useState(false);
@@ -180,8 +182,9 @@ const DeployList: React.FunctionComponent<Props> = ({
       return (
         <ExpandedOperation
           operation_id={selectedOperation.id}
-          infra_id={selectedOperation.infra_id}
+          infra={infra}
           back={backFromExpandedOperation}
+          refreshInfra={refreshInfra}
         />
       );
     }

+ 45 - 15
dashboard/src/main/home/infrastructure/components/ExpandedOperation.tsx

@@ -3,7 +3,12 @@ import { Context } from "shared/Context";
 import api from "shared/api";
 import styled from "styled-components";
 import Loading from "components/Loading";
-import { Operation, OperationStatus, OperationType } from "shared/types";
+import {
+  Infrastructure,
+  Operation,
+  OperationStatus,
+  OperationType,
+} from "shared/types";
 import { readableDate } from "shared/string_utils";
 import Placeholder from "components/Placeholder";
 import { useWebsockets } from "shared/hooks/useWebsockets";
@@ -11,17 +16,20 @@ import Heading from "components/form-components/Heading";
 import SaveButton from "components/SaveButton";
 import PorterFormWrapper from "components/porter-form/PorterFormWrapper";
 import Description from "components/Description";
+import { OperationDetails } from "components/ProvisionerStatus";
 
 type Props = {
-  infra_id: number;
+  infra: Infrastructure;
   operation_id: string;
   back: (operation?: Operation) => void;
+  refreshInfra: () => void;
 };
 
 const ExpandedOperation: React.FunctionComponent<Props> = ({
-  infra_id,
+  infra,
   operation_id,
   back,
+  refreshInfra,
 }) => {
   const [isLoading, setIsLoading] = useState(true);
   const [hasError, setHasError] = useState(false);
@@ -38,7 +46,7 @@ const ExpandedOperation: React.FunctionComponent<Props> = ({
         {},
         {
           project_id: currentProject.id,
-          infra_id: infra_id,
+          infra_id: infra.id,
           operation_id: operation_id,
         }
       )
@@ -109,7 +117,7 @@ const ExpandedOperation: React.FunctionComponent<Props> = ({
           {},
           {
             project_id: currentProject.id,
-            infra_id: infra_id,
+            infra_id: infra.id,
             operation_id: operation_id,
           }
         )
@@ -129,7 +137,7 @@ const ExpandedOperation: React.FunctionComponent<Props> = ({
   const retry = () => {
     let pathParams = {
       project_id: currentProject.id,
-      infra_id: infra_id,
+      infra_id: infra.id,
     };
 
     let apiCall = api.updateInfra;
@@ -240,6 +248,35 @@ const ExpandedOperation: React.FunctionComponent<Props> = ({
     return logs.map((l, i) => <Log key={i}>{l}</Log>);
   };
 
+  const renderOperationDetails = () => {
+    if (infra.latest_operation.id == operation.id) {
+      return (
+        <>
+          <Description>Infrastructure progress:</Description>
+          <OperationDetails
+            infra={infra}
+            refreshInfra={refreshInfra}
+            useOperation={operation}
+            padding={"12px 0"}
+          />
+        </>
+      );
+    }
+
+    return (
+      <>
+        <Description>
+          {getOperationDescription(
+            operation.type,
+            operation.status,
+            operation.last_updated
+          )}
+        </Description>
+        <Br />
+      </>
+    );
+  };
+
   return (
     <StyledCard>
       <BackArrowContainer>
@@ -250,14 +287,7 @@ const ExpandedOperation: React.FunctionComponent<Props> = ({
       </BackArrowContainer>
       <MetadataContainer>
         <Heading>Deployment Summary</Heading>
-        <Description>
-          {getOperationDescription(
-            operation.type,
-            operation.status,
-            operation.last_updated
-          )}
-        </Description>
-        <Br />
+        {renderOperationDetails()}
         {renderRerunButton()}
       </MetadataContainer>
       <MetadataContainer>
@@ -332,7 +362,7 @@ const MetadataContainer = styled.div`
   margin-bottom: 3px;
   border-radius: 6px;
   background: #2e3135;
-  padding: 0 20px;
+  padding: 0 20px 16px 20px;
   overflow-y: auto;
   min-height: 180px;
   font-size: 13px;

+ 41 - 23
dashboard/src/main/home/infrastructure/components/InfraSettings.tsx

@@ -1,18 +1,24 @@
-import React, { useContext } from "react";
+import React, { useContext, useState } from "react";
 import { Context } from "shared/Context";
 import api from "shared/api";
 import styled from "styled-components";
 import Heading from "components/form-components/Heading";
 import SaveButton from "components/SaveButton";
 import Description from "components/Description";
+import ConfirmOverlay from "components/ConfirmOverlay";
 
 type Props = {
   infra_id: number;
   onDelete: () => void;
 };
 
-const InfraSettings: React.FunctionComponent<Props> = ({ infra_id }) => {
-  const { currentProject, setCurrentError } = useContext(Context);
+const InfraSettings: React.FunctionComponent<Props> = ({
+  infra_id,
+  onDelete,
+}) => {
+  const { currentProject, setCurrentError, setCurrentOverlay } = useContext(
+    Context
+  );
 
   const deleteInfra = () => {
     api
@@ -24,7 +30,10 @@ const InfraSettings: React.FunctionComponent<Props> = ({ infra_id }) => {
           infra_id: infra_id,
         }
       )
-      .then()
+      .then(() => {
+        setCurrentOverlay(null);
+        onDelete();
+      })
       .catch((err) => {
         console.error(err);
         setCurrentError(err.response?.data?.error);
@@ -32,25 +41,34 @@ const InfraSettings: React.FunctionComponent<Props> = ({ infra_id }) => {
   };
 
   return (
-    <StyledCard>
-      <MetadataContainer>
-        <Heading>Delete Infrastructure</Heading>
-        <Description>
-          This will destroy all of the existing cloud infrastructure attached to
-          this module.
-        </Description>
-        <Br />
-        <SaveButton
-          onClick={deleteInfra}
-          text="Delete Infrastructure"
-          color="#b91133"
-          disabled={false}
-          makeFlush={true}
-          clearPosition={true}
-          saveText="Deletion process started, see the Deploys tab for info."
-        />
-      </MetadataContainer>
-    </StyledCard>
+    <>
+      <StyledCard>
+        <MetadataContainer>
+          <Heading>Delete Infrastructure</Heading>
+          <Description>
+            This will destroy all of the existing cloud infrastructure attached
+            to this module.
+          </Description>
+          <Br />
+
+          <SaveButton
+            onClick={() =>
+              setCurrentOverlay({
+                message: `Are you sure you want to delete this infrastructure?`,
+                onYes: deleteInfra,
+                onNo: () => setCurrentOverlay(null),
+              })
+            }
+            text="Delete Infrastructure"
+            color="#b91133"
+            disabled={false}
+            makeFlush={true}
+            clearPosition={true}
+            saveText="Deletion process started, see the Deploys tab for info."
+          />
+        </MetadataContainer>
+      </StyledCard>
+    </>
   );
 };
 

+ 140 - 0
dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialForm.tsx

@@ -0,0 +1,140 @@
+import React, { useContext, useState } from "react";
+import InputRow from "components/form-components/InputRow";
+import SaveButton from "components/SaveButton";
+
+import { Context } from "shared/Context";
+import api from "shared/api";
+import styled from "styled-components";
+import Loading from "components/Loading";
+import Placeholder from "components/Placeholder";
+
+type Props = {
+  setCreatedCredential: (aws_integration_id: number) => void;
+  cancel: () => void;
+};
+
+const AzureCredentialForm: React.FunctionComponent<Props> = ({
+  setCreatedCredential,
+}) => {
+  const { currentProject, setCurrentError } = useContext(Context);
+  const [clientId, setClientId] = useState("");
+  const [servicePrincipalKey, setServicePrincipalKey] = useState("");
+  const [tenantId, setTenantId] = useState("");
+  const [subscriptionId, setSubscriptionId] = useState("");
+  const [buttonStatus, setButtonStatus] = useState("");
+  const [isLoading, setIsLoading] = useState(false);
+  const [hasError, setHasError] = useState(false);
+
+  const submit = () => {
+    setIsLoading(true);
+
+    api
+      .createAzureIntegration(
+        "<token>",
+        {
+          azure_client_id: clientId,
+          azure_subscription_id: subscriptionId,
+          azure_tenant_id: tenantId,
+          service_principal_key: servicePrincipalKey,
+        },
+        {
+          id: currentProject.id,
+        }
+      )
+      .then(({ data }) => {
+        setCreatedCredential(data.id);
+        setIsLoading(false);
+      })
+      .catch((err) => {
+        console.error(err);
+        setHasError(true);
+        setCurrentError(err.response?.data?.error);
+        setIsLoading(false);
+      });
+  };
+
+  if (hasError) {
+    return <Placeholder>Error</Placeholder>;
+  }
+
+  if (isLoading) {
+    return (
+      <Placeholder>
+        <Loading />
+      </Placeholder>
+    );
+  }
+
+  return (
+    <>
+      <InputRow
+        type="text"
+        value={clientId}
+        setValue={(x: string) => {
+          setClientId(x);
+        }}
+        label="👤 Azure Client ID"
+        placeholder="ex. 12345678-abcd-1234-abcd-12345678abcd"
+        width="100%"
+        isRequired={true}
+      />
+      <InputRow
+        type="password"
+        value={servicePrincipalKey}
+        setValue={(x: string) => {
+          setServicePrincipalKey(x);
+        }}
+        label="🔒 Azure Service Principal Key"
+        placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
+        width="100%"
+        isRequired={true}
+      />
+      <InputRow
+        type="text"
+        value={tenantId}
+        setValue={(x: string) => {
+          setTenantId(x);
+        }}
+        label="Azure Tenant ID"
+        placeholder="ex. 12345678-abcd-1234-abcd-12345678abcd"
+        width="100%"
+        isRequired={true}
+      />
+      <InputRow
+        type="text"
+        value={subscriptionId}
+        setValue={(x: string) => {
+          setSubscriptionId(x);
+        }}
+        label="Azure Subscription ID"
+        placeholder="ex. 12345678-abcd-1234-abcd-12345678abcd"
+        width="100%"
+        isRequired={true}
+      />
+      <Flex>
+        <SaveButton
+          text="Continue"
+          disabled={false}
+          onClick={submit}
+          makeFlush={true}
+          clearPosition={true}
+          status={buttonStatus}
+          statusPosition={"right"}
+        />
+      </Flex>
+    </>
+  );
+};
+
+export default AzureCredentialForm;
+
+const Flex = styled.div`
+  display: flex;
+  color: #ffffff;
+  align-items: center;
+  > i {
+    color: #aaaabb;
+    font-size: 20px;
+    margin-right: 10px;
+  }
+`;

+ 9 - 9
dashboard/src/main/home/infrastructure/components/credentials/AzureCredentialList.tsx

@@ -4,7 +4,7 @@ import api from "shared/api";
 import styled from "styled-components";
 import Loading from "components/Loading";
 import Placeholder from "components/Placeholder";
-// import AzureCredentialForm from "./AzureCredentialForm";
+import AzureCredentialForm from "./AzureCredentialForm";
 import CredentialList from "./CredentialList";
 import Description from "components/Description";
 
@@ -69,14 +69,14 @@ const AzureCredentialsList: React.FunctionComponent<Props> = ({
   }
 
   const renderContents = () => {
-    // if (shouldCreateCred) {
-    //   return (
-    //     <AzureCredentialForm
-    //       setCreatedCredential={selectCredential}
-    //       cancel={() => {}}
-    //     />
-    //   );
-    // }
+    if (shouldCreateCred) {
+      return (
+        <AzureCredentialForm
+          setCreatedCredential={selectCredential}
+          cancel={() => {}}
+        />
+      );
+    }
 
     return (
       <>

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

@@ -87,6 +87,18 @@ const overwriteAWSIntegration = baseApi<
   return `/api/projects/${pathParams.project_id}/integrations/aws/overwrite`;
 });
 
+const createAzureIntegration = baseApi<
+  {
+    azure_client_id: string;
+    azure_subscription_id: string;
+    azure_tenant_id: string;
+    service_principal_key: string;
+  },
+  { id: number }
+>("POST", (pathParams) => {
+  return `/api/projects/${pathParams.id}/integrations/azure`;
+});
+
 const createEmailVerification = baseApi<{}, {}>("POST", (pathParams) => {
   return `/api/email/verify/initiate`;
 });
@@ -1796,6 +1808,7 @@ export default {
   getAzureIntegration,
   createAWSIntegration,
   overwriteAWSIntegration,
+  createAzureIntegration,
   createEmailVerification,
   createEnvironment,
   deleteEnvironment,

+ 2 - 2
provisioner/server/handlers/state/delete_resource.go

@@ -68,9 +68,9 @@ func (c *DeleteResourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 	// switch on the kind of resource and write the corresponding objects to the database
 	switch infra.Kind {
-	case types.InfraECR, types.InfraGCR, types.InfraDOCR:
+	case types.InfraECR, types.InfraGCR, types.InfraDOCR, types.InfraACR:
 		_, err = deleteRegistry(c.Config, infra, operation)
-	case types.InfraEKS, types.InfraDOKS, types.InfraGKE:
+	case types.InfraEKS, types.InfraDOKS, types.InfraGKE, types.InfraAKS:
 		_, err = deleteCluster(c.Config, infra, operation)
 	case types.InfraRDS:
 		_, err = deleteDatabase(c.Config, infra, operation)