Procházet zdrojové kódy

cluster vanity name support

Justin Rhee před 3 roky
rodič
revize
56c5d2c540

+ 49 - 0
api/server/handlers/cluster/rename.go

@@ -0,0 +1,49 @@
+package cluster
+
+import (
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"net/http"
+)
+
+type RenameClusterHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewRenameClusterHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *RenameClusterHandler {
+	return &RenameClusterHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *RenameClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.UpdateClusterRequest{}
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	if request.Name != "" && cluster.VanityName != request.Name {
+		cluster.VanityName = request.Name
+	}
+
+	cluster, err := c.Repo().Cluster().UpdateCluster(cluster)
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, cluster.ToClusterType())
+}

+ 30 - 0
api/server/router/cluster.go

@@ -558,6 +558,36 @@ func getClusterRoutes(
 			Router:   r,
 		})
 
+		// POST /api/projects/{project_id}/clusters/{cluster_id}/rename -> cluster.NewRenameClusterHandler
+		renameClusterEndpoint := factory.NewAPIEndpoint(
+			&types.APIRequestMetadata{
+				Verb:   types.APIVerbCreate,
+				Method: types.HTTPVerbPost,
+				Path: &types.Path{
+					Parent:       basePath,
+					RelativePath: relPath + "/rename",
+				},
+				Scopes: []types.PermissionScope{
+					types.UserScope,
+					types.ProjectScope,
+					types.ClusterScope,
+					types.PreviewEnvironmentScope,
+				},
+			},
+		)
+
+		renameClusterHandler := cluster.NewRenameClusterHandler(
+			config,
+			factory.GetDecoderValidator(),
+			factory.GetResultWriter(),
+		)
+
+		routes = append(routes, &router.Route{
+			Endpoint: renameClusterEndpoint,
+			Handler:  renameClusterHandler,
+			Router:   r,
+		})
+
 		// DELETE /api/projects/{project_id}/clusters/{cluster_id}/deployments/{deployment_id} ->
 		// environment.NewDeleteDeploymentHandler
 		deleteDeploymentEndpoint := factory.NewAPIEndpoint(

+ 4 - 0
api/types/cluster.go

@@ -308,6 +308,10 @@ type UpdateClusterRequest struct {
 	PreviewEnvsEnabled *bool `json:"preview_envs_enabled"`
 }
 
+type RenameClusterRequest struct {
+	Name string `json:"name"`
+}
+
 type ListClusterResponse []*Cluster
 
 type CreateClusterCandidateResponse []*ClusterCandidate

+ 0 - 8
dashboard/src/components/ProvisionerSettings.tsx

@@ -115,7 +115,6 @@ const ProvisionerSettings: React.FC<Props> = props => {
       data["cluster"]["clusterId"] = props.clusterId;
     }
 
-    console.log(0);
     try {
       const res = await api.createContract(
         "<token>",
@@ -123,12 +122,8 @@ const ProvisionerSettings: React.FC<Props> = props => {
         { project_id: currentProject.id }
       );
 
-      console.log("res is:", res);
-      console.log("cluster id is:", res.data.contract_revision?.cluster_id);
-
       // Only refresh and set clusters on initial create
       if (!props.clusterId) {
-        console.log(1);
         setShouldRefreshClusters(true);
         api.getClusters(
           "<token>",
@@ -136,12 +131,9 @@ const ProvisionerSettings: React.FC<Props> = props => {
           { id: currentProject.id },
         )
           .then(({ data }) => {
-            console.log(2);
             data.forEach((cluster: ClusterType) => {
-              console.log("cluster id:", cluster.id)
               if (cluster.id === res.data.contract_revision?.cluster_id) {
                 // setHasFinishedOnboarding(true);
-                console.log(3);
                 setCurrentCluster(cluster);
                 OFState.actions.goTo("clean_up");
                 pushFiltered(props, "/cluster-dashboard", ["project_id"], {

+ 6 - 3
dashboard/src/components/porter/Button.tsx

@@ -5,6 +5,7 @@ import loading from "assets/loading.gif";
 
 type Props = {
   children: React.ReactNode;
+  onClick: () => void;
   disabled?: boolean;
   status?: string;
   loadingText?: string;
@@ -13,6 +14,7 @@ type Props = {
 
 const Button: React.FC<Props> = ({
   children,
+  onClick,
   disabled,
   status,
   loadingText,
@@ -48,6 +50,7 @@ const Button: React.FC<Props> = ({
     <Wrapper>
       <StyledButton
         disabled={disabled}
+        onClick={() => !disabled && onClick()}
       >
         <Text>{children}</Text>
       </StyledButton>
@@ -114,19 +117,19 @@ const StyledButton = styled.button<{
 }>`
   height: 35px;
   font-size: 13px;
-  cursor: pointer;
+  cursor: ${props => props.disabled ? "not-allowed" : "pointer"};
   padding: 15px;
   border: none;
   outline: none;
   font-weight: 500;
   color: white;
-  background: #5561C0;
+  background: ${props => props.disabled ? "#aaaabb" : "#5561C0"};
   display: flex;
   ailgn-items: center;
   justify-content: center;
   border-radius: 5px;
 
   :hover {
-    filter: ${props => !props.disabled ? "brightness(120%)" : ""};
+    filter: ${props => props.disabled ? "" : "brightness(120%)"};
   }
 `;

+ 34 - 2
dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettingsModal.tsx

@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
 import { Context } from "shared/Context";
+import api from "shared/api";
 
 import Modal from "main/home/modals/Modal";
 import Input from "components/porter/Input";
@@ -13,13 +14,38 @@ type Props = {
 
 const ClusterSettingsModal: React.FC<Props> = ({
 }) => {
-  const { setCurrentModal, currentCluster } = useContext(Context);
+  const { 
+    setCurrentModal, 
+    currentCluster, 
+    currentProject,
+    setShouldRefreshClusters,
+  } = useContext(Context);
   const [clusterName, setClusterName] = useState("");
+  const [status, setStatus] = useState("");
 
   useEffect(() => {
     setClusterName(currentCluster.vanity_name || currentCluster.name);
   }, []);
 
+  const renameCluster = async () => {
+    setStatus("loading");
+    try {
+      const res = await api.renameCluster(
+        "<token>",
+        { name: clusterName },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+        }
+      );
+      setStatus("success");
+      setShouldRefreshClusters(true);
+    } catch (err) {
+      setStatus("error");
+      console.log(err);
+    }
+  }
+
   return (
     <Modal
       width="600px"
@@ -94,7 +120,13 @@ const ClusterSettingsModal: React.FC<Props> = ({
         />
       </Flex>
       <Spacer y={1} />
-      <Button>Save</Button>
+      <Button 
+        onClick={renameCluster}
+        disabled={clusterName === ""}
+        status={status}
+      >
+        Save
+      </Button>
     </Modal>
   );
 };

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

@@ -113,6 +113,18 @@ const updateCluster = baseApi<
   return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}`;
 });
 
+const renameCluster = baseApi<
+  {
+    name: string;
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+  }
+>("POST", (pathParams) => {
+  return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/rename`;
+});
+
 const createAzureIntegration = baseApi<
   {
     azure_client_id: string;
@@ -2391,6 +2403,7 @@ export default {
   createAWSIntegration,
   overwriteAWSIntegration,
   updateCluster,
+  renameCluster,
   createAzureIntegration,
   createGitlabIntegration,
   createEmailVerification,