Selaa lähdekoodia

POR-1997 Delete preview env btn (#3853)

ianedwards 2 vuotta sitten
vanhempi
sitoutus
ab36aa5356

+ 66 - 9
dashboard/src/main/home/app-dashboard/apps/Apps.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useContext } from "react";
+import React, { useState, useContext, useCallback } from "react";
 import styled from "styled-components";
 import _ from "lodash";
 
@@ -29,17 +29,22 @@ import AppGrid from "./AppGrid";
 import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
 import { z } from "zod";
 import { useDeploymentTarget } from "shared/DeploymentTargetContext";
+import DeleteEnvModal from "main/home/cluster-dashboard/preview-environments/v2/DeleteEnvModal";
+import { useHistory } from "react-router";
 
 type Props = {};
 
-const Apps: React.FC<Props> = ({ }) => {
+const Apps: React.FC<Props> = ({}) => {
   const { currentProject, currentCluster } = useContext(Context);
   const { updateAppStep } = useAppAnalytics();
   const { currentDeploymentTarget } = useDeploymentTarget();
+  const history = useHistory();
 
   const [searchValue, setSearchValue] = useState("");
   const [view, setView] = useState<"grid" | "list">("grid");
   const [sort, setSort] = useState<"calendar" | "letter">("calendar");
+  const [showDeleteEnvModal, setShowDeleteEnvModal] = useState(false);
+  const [envDeleting, setEnvDeleting] = useState(false);
 
   const { data: apps = [], status } = useQuery(
     [
@@ -127,6 +132,38 @@ const Apps: React.FC<Props> = ({ }) => {
     }
   );
 
+  const deletePreviewEnv = useCallback(async () => {
+    try {
+      if (!currentCluster || !currentProject || !currentDeploymentTarget) {
+        return;
+      }
+      setEnvDeleting(true);
+
+      await api.deleteDeploymentTarget(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          deployment_target_id: currentDeploymentTarget.id,
+        }
+      );
+
+      history.push("/preview-environments");
+    } catch (err) {
+    } finally {
+      setShowDeleteEnvModal(false);
+      setEnvDeleting(false);
+    }
+  }, [
+    currentCluster,
+    currentProject,
+    currentDeploymentTarget,
+    history,
+    setShowDeleteEnvModal,
+    setEnvDeleting,
+  ]);
+
   const renderContents = () => {
     if (currentCluster?.status === "UPDATING_UNAVAILABLE") {
       return <ClusterProvisioningPlaceholder />;
@@ -212,7 +249,7 @@ const Apps: React.FC<Props> = ({ }) => {
               }
             }}
             inactiveColor={"#ffffff11"}
-            activeColor={"transparent"}          
+            activeColor={"transparent"}
           />
           <Spacer inline x={1} />
           <Toggle
@@ -232,17 +269,30 @@ const Apps: React.FC<Props> = ({ }) => {
             activeColor={"transparent"}
           />
           <Spacer inline x={2} />
-          <PorterLink to="/apps/new/app">
+          {currentDeploymentTarget?.preview ? (
             <Button
-              onClick={async () =>
-                updateAppStep({ step: "stack-launch-start" })
-              }
+              onClick={async () => {
+                setShowDeleteEnvModal(true);
+              }}
               height="30px"
               width="160px"
+              color="#b91133"
             >
-              <I className="material-icons">add</I> New application
+              Delete Environment
             </Button>
-          </PorterLink>
+          ) : (
+            <PorterLink to="/apps/new/app">
+              <Button
+                onClick={async () =>
+                  updateAppStep({ step: "stack-launch-start" })
+                }
+                height="30px"
+                width="160px"
+              >
+                <I className="material-icons">add</I> New application
+              </Button>
+            </PorterLink>
+          )}
         </Container>
         <Spacer y={1} />
         <AppGrid
@@ -267,6 +317,13 @@ const Apps: React.FC<Props> = ({ }) => {
       )}
       {renderContents()}
       <Spacer y={5} />
+      {showDeleteEnvModal && (
+        <DeleteEnvModal
+          closeModal={() => setShowDeleteEnvModal(false)}
+          deleteEnv={deletePreviewEnv}
+          loading={envDeleting}
+        />
+      )}
     </StyledAppDashboard>
   );
 };

+ 51 - 0
dashboard/src/main/home/cluster-dashboard/preview-environments/v2/DeleteEnvModal.tsx

@@ -0,0 +1,51 @@
+import Button from "components/porter/Button";
+import Modal from "components/porter/Modal";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+
+import React from "react";
+import styled from "styled-components";
+
+type Props = {
+  closeModal: () => void;
+  deleteEnv: () => Promise<void>;
+  loading?: boolean;
+};
+
+const DeleteEnvModal: React.FC<Props> = ({
+  closeModal,
+  deleteEnv,
+  loading = false,
+}) => {
+  return (
+    <Modal closeModal={closeModal}>
+      <Text size={16}>Confirm deletion</Text>
+      <Spacer y={0.5} />
+      <Text color="helper">
+        Click the button below to confirm environment deletion. This action is
+        irreversible.
+      </Text>
+      <Spacer y={0.5} />
+      <Text color="helper">
+        Deleting this environment will tear down all apps and any associated
+        resources.
+      </Text>
+      <Spacer y={1} />
+      <Button
+        onClick={() => deleteEnv()}
+        color="#b91133"
+        status={loading ? "loading" : ""}
+        loadingText="Deleting..."
+        disabled={loading}
+      >
+        Delete
+      </Button>
+    </Modal>
+  );
+};
+
+export default DeleteEnvModal;
+
+const Code = styled.span`
+  font-family: monospace;
+`;

+ 76 - 44
dashboard/src/shared/api.tsx

@@ -11,7 +11,11 @@ import {
   CreateStackBody,
   SourceConfig,
 } from "main/home/cluster-dashboard/stacks/types";
-import { Contract, PreflightCheckRequest, QuotaIncreaseRequest } from "@porter-dev/api-contracts";
+import {
+  Contract,
+  PreflightCheckRequest,
+  QuotaIncreaseRequest,
+} from "@porter-dev/api-contracts";
 
 /**
  * Generic api call format
@@ -89,7 +93,6 @@ const requestQuotaIncrease = baseApi<QuotaIncreaseRequest, { id: number }>(
   }
 );
 
-
 const createAWSIntegration = baseApi<
   {
     aws_region?: string;
@@ -367,8 +370,9 @@ const getFeedEvents = baseApi<
   }
 >("GET", (pathParams) => {
   let { project_id, cluster_id, stack_name, page } = pathParams;
-  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${page || 1
-    }`;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${
+    page || 1
+  }`;
 });
 
 const createEnvironment = baseApi<
@@ -793,9 +797,11 @@ const detectBuildpack = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
 });
 
 const detectGitlabBuildpack = baseApi<
@@ -826,9 +832,11 @@ const getBranchContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/contents`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/contents`;
 });
 
 const getProcfileContents = baseApi<
@@ -844,9 +852,11 @@ const getProcfileContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/procfile`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/procfile`;
 });
 
 const getPorterYamlContents = baseApi<
@@ -862,9 +872,11 @@ const getPorterYamlContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
 });
 
 const parsePorterYaml = baseApi<
@@ -890,6 +902,17 @@ const getDefaultDeploymentTarget = baseApi<
   return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/default-deployment-target`;
 });
 
+const deleteDeploymentTarget = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    deployment_target_id: string;
+  }
+>("DELETE", ({ project_id, cluster_id, deployment_target_id }) => {
+  return `/api/projects/${project_id}/clusters/${cluster_id}/deployment-targets/${deployment_target_id}`;
+});
+
 const getBranchHead = baseApi<
   {},
   {
@@ -901,9 +924,11 @@ const getBranchHead = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/head`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/head`;
 });
 
 const validatePorterApp = baseApi<
@@ -916,10 +941,13 @@ const validatePorterApp = baseApi<
       predeploy: string[];
       env_variable_names: string[];
       env_group_names: string[];
-      service_deletions: Record<string, {
-        domain_names: string[];
-        ingress_annotation_keys: string[];
-      }>
+      service_deletions: Record<
+        string,
+        {
+          domain_names: string[];
+          ingress_annotation_keys: string[];
+        }
+      >;
     };
   },
   {
@@ -932,21 +960,21 @@ const validatePorterApp = baseApi<
 
 const createApp = baseApi<
   | {
-    name: string;
-    type: "github";
-    git_repo_id: number;
-    git_branch: string;
-    git_repo_name: string;
-    porter_yaml_path: string;
-  }
+      name: string;
+      type: "github";
+      git_repo_id: number;
+      git_branch: string;
+      git_repo_name: string;
+      porter_yaml_path: string;
+    }
   | {
-    name: string;
-    type: "docker-registry";
-    image: {
-      repository: string;
-      tag: string;
-    };
-  },
+      name: string;
+      type: "docker-registry";
+      image: {
+        repository: string;
+        tag: string;
+      };
+    },
   {
     project_id: number;
     cluster_id: number;
@@ -1088,9 +1116,10 @@ const getAppTemplate = baseApi<
     project_id: number;
     cluster_id: number;
     porter_app_name: string;
-  }>("GET", ({ project_id, cluster_id, porter_app_name }) => {
-    return `/api/projects/${project_id}/clusters/${cluster_id}/apps/${porter_app_name}/templates`;
-  })
+  }
+>("GET", ({ project_id, cluster_id, porter_app_name }) => {
+  return `/api/projects/${project_id}/clusters/${cluster_id}/apps/${porter_app_name}/templates`;
+});
 
 const getGitlabProcfileContents = baseApi<
   {
@@ -2008,9 +2037,11 @@ const getEnvGroup = baseApi<
     version?: number;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/clusters/${pathParams.cluster_id
-    }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${pathParams.version ? "&version=" + pathParams.version : ""
-    }`;
+  return `/api/projects/${pathParams.id}/clusters/${
+    pathParams.cluster_id
+  }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${
+    pathParams.version ? "&version=" + pathParams.version : ""
+  }`;
 });
 
 const getConfigMap = baseApi<
@@ -3068,7 +3099,7 @@ const removeStackEnvGroup = baseApi<
     `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks/${stack_id}/remove_env_group/${env_group_name}`
 );
 
-const getGithubStatus = baseApi<{}, {}>("GET", ({ }) => `/api/status/github`);
+const getGithubStatus = baseApi<{}, {}>("GET", ({}) => `/api/status/github`);
 
 const createSecretAndOpenGitHubPullRequest = baseApi<
   {
@@ -3222,6 +3253,7 @@ export default {
   getPorterYamlContents,
   parsePorterYaml,
   getDefaultDeploymentTarget,
+  deleteDeploymentTarget,
   getBranchHead,
   validatePorterApp,
   createApp,