Przeglądaj źródła

Merge pull request #453 from porter-dev/master

Merge fix into staging
abelanger5 5 lat temu
rodzic
commit
1af034d13b

BIN
dashboard/src/assets/close_rounded.png


+ 62 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx

@@ -29,7 +29,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
     showTooltip: [] as boolean[]
   };
 
-  componentDidMount() {
+  updatePods = () => {
     let { currentCluster, currentProject, setCurrentError } = this.context;
     let { controller, selectPod, isFirst } = this.props;
 
@@ -64,6 +64,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
       )
       .then(res => {
         let pods = res?.data?.map((pod: any) => {
+          console.log(pod?.metadata?.namespace)
           return {
             namespace: pod?.metadata?.namespace,
             name: pod?.metadata?.name,
@@ -93,6 +94,10 @@ export default class ControllerTab extends Component<PropsType, StateType> {
       });
   }
 
+  componentDidMount() {
+    this.updatePods();
+  }
+
   getAvailability = (kind: string, c: any) => {
     switch (kind?.toLowerCase()) {
       case "deployment":
@@ -150,6 +155,42 @@ export default class ControllerTab extends Component<PropsType, StateType> {
     }
   };
 
+  handleDeletePod = (pod: any) => {
+    console.log("clusterId", this.context.currentCluster.id);
+    console.log("name", pod.metadata?.name);
+    console.log("namespace", pod.metadata?.namespace);
+    console.log("id", this.context.currentProject.id);
+    api
+      .deletePod(
+        "<token>",
+        {
+          cluster_id: this.context.currentCluster.id
+        },
+        {
+          name: pod.metadata?.name,
+          namespace: pod.metadata?.namespace,
+          id: this.context.currentProject.id,
+        }
+      )
+      .then(res => {
+        this.updatePods();
+      })
+      .catch(err => {
+        this.context.setCurrentError(JSON.stringify(err));
+      });
+  }
+
+  renderDeleteButton = (pod: any) => {
+    return (
+      <CloseIcon 
+        className="material-icons-outlined"
+        onClick={() => this.handleDeletePod(pod)}
+      >
+        close
+      </CloseIcon>
+    );
+  }
+
   render() {
     let { controller, selectedPod, isLast, selectPod, isFirst } = this.props;
     let [available, total] = this.getAvailability(controller.kind, controller);
@@ -205,6 +246,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
               <Status>
                 <StatusColor status={status} />
                 {status}
+                {status === "failed" && this.renderDeleteButton(pod)}
               </Status>
             </Tab>
           );
@@ -216,6 +258,24 @@ export default class ControllerTab extends Component<PropsType, StateType> {
 
 ControllerTab.contextType = Context;
 
+const CloseIcon = styled.i`
+  font-size: 14px;
+  display: flex;
+  font-weight: bold;
+  align-items: center;
+  justify-content: center;
+  border-radius: 5px;
+  background: #ffffff22;
+  width: 18px;
+  height: 18px;
+  margin-right: -6px;
+  margin-left: 10px;
+  cursor: pointer;
+  :hover {
+    background: #ffffff44;
+  }
+`;
+
 const Rail = styled.div`
   width: 2px;
   background: ${(props: { lastTab?: boolean }) =>
@@ -245,9 +305,9 @@ const Gutter = styled.div`
 
 const Status = styled.div`
   display: flex;
-  width: 50px;
   font-size: 12px;
   text-transform: capitalize;
+  margin-left: 5px;
   justify-content: flex-end;
   align-items: center;
   font-family: "Work Sans", sans-serif;
@@ -280,7 +340,6 @@ const StatusColor = styled.div`
 `;
 
 const Name = styled.div`
-  max-width: calc(100% - 106px);
   overflow: hidden;
   text-overflow: ellipsis;
   line-height: 16px;

+ 2 - 1
dashboard/src/main/home/navbar/Feedback.tsx

@@ -44,8 +44,9 @@ export default class Feedback extends Component<PropsType, StateType> {
       ": " +
       this.state.feedbackText;
     handleSubmitFeedback(msg, () => {
-      this.setState({ feedbackSent: true, feedbackText: "" });
+      // console.log("submitted")
     });
+    this.setState({ feedbackSent: true, feedbackText: "" });
   };
 
   renderFeedbackDropdown = () => {

+ 17 - 8
dashboard/src/main/home/navbar/Navbar.tsx

@@ -44,15 +44,16 @@ export default class Navbar extends Component<PropsType, StateType> {
     return (
       <StyledNavbar>
         <Feedback currentView={this.props.currentView} />
-        <NavButton selected={this.state.showDropdown}>
-          <i
-            className="material-icons-outlined"
-            onClick={() =>
-              this.setState({ showDropdown: !this.state.showDropdown })
-            }
-          >
+        <NavButton 
+          selected={this.state.showDropdown} 
+          onClick={() =>
+            this.setState({ showDropdown: !this.state.showDropdown })
+          }
+        >
+          <I className="material-icons-outlined">
             account_circle
-          </i>
+          </I>
+          {this.context.user.email}
           {this.renderSettingsDropdown()}
         </NavButton>
       </StyledNavbar>
@@ -62,6 +63,10 @@ export default class Navbar extends Component<PropsType, StateType> {
 
 Navbar.contextType = Context;
 
+const I = styled.i`
+  margin-right: 7px;
+`;
+
 const CloseOverlay = styled.div`
   position: fixed;
   width: 100vw;
@@ -191,12 +196,16 @@ const NavButton = styled.a`
   display: flex;
   position: relative;
   align-items: center;
+  font-size: 14px;
+  color: #ffffff88;
+  cursor: pointer;
   justify-content: center;
   margin-right: 15px;
   :hover {
     > i {
       color: #ffffff;
     }
+    color: #ffffff;
   }
 
   > i {

+ 4 - 5
dashboard/src/main/home/provisioner/Provisioner.tsx

@@ -82,10 +82,6 @@ class Provisioner extends Component<PropsType, StateType> {
   };
 
   updateInfras = () => {
-    this.setState({
-      loading: true
-    });
-
     let { currentProject } = this.state;
 
     api
@@ -143,7 +139,10 @@ class Provisioner extends Component<PropsType, StateType> {
     return (
       <StyledProvisioner>
         You have not provisioned any resources for this project through Porter.{" "}
-        <RefreshText onClick={this.refresh}>Refresh</RefreshText>
+        <RefreshText onClick={() => {
+          this.setState({ loading: true }); 
+          this.refresh()
+        }}>Refresh</RefreshText>
       </StyledProvisioner>
     );
   }

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

@@ -220,6 +220,15 @@ const deleteInvite = baseApi<{}, { id: number; invId: number }>(
   }
 );
 
+const deletePod = baseApi<
+{
+  cluster_id: number;
+},
+{ name: string; namespace: string; id: number }
+>("DELETE", pathParams => {
+  return `/api/projects/${pathParams.id}/k8s/pods`;
+});
+
 const deleteProject = baseApi<{}, { id: number }>("DELETE", pathParams => {
   return `/api/projects/${pathParams.id}`;
 });
@@ -700,6 +709,7 @@ export default {
   deleteCluster,
   deleteGitRepoIntegration,
   deleteInvite,
+  deletePod,
   deleteProject,
   deleteRegistryIntegration,
   createSubdomain,

+ 9 - 0
internal/kubernetes/agent.go

@@ -140,6 +140,15 @@ func (a *Agent) GetPodsByLabel(selector string) (*v1.PodList, error) {
 	)
 }
 
+// DeletePod deletes a pod by name and namespace
+func (a *Agent) DeletePod(namespace string, name string) error {
+	return a.Clientset.CoreV1().Pods(namespace).Delete(
+		context.TODO(),
+		name,
+		metav1.DeleteOptions{},
+	)
+}
+
 // GetPodLogs streams real-time logs from a given pod.
 func (a *Agent) GetPodLogs(namespace string, name string, conn *websocket.Conn) error {
 	tails := int64(400)

+ 49 - 0
server/api/k8s_handler.go

@@ -262,6 +262,55 @@ func (app *App) HandleListPods(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+// HandleDeletePod deletes the pod given the name and namespace.
+func (app *App) HandleDeletePod(w http.ResponseWriter, r *http.Request) {
+	// get path parameters
+	namespace := chi.URLParam(r, "namespace")
+	name := chi.URLParam(r, "name")
+
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+
+	// get the filter options
+	form := &forms.K8sForm{
+		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
+			Repo:              app.Repo,
+			DigitalOceanOAuth: app.DOConf,
+		},
+	}
+
+	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrK8sValidate, w)
+		return
+	}
+
+	// create a new agent
+	var agent *kubernetes.Agent
+
+	if app.ServerConf.IsTesting {
+		agent = app.TestAgents.K8sAgent
+	} else {
+		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
+	}
+
+	err = agent.DeletePod(namespace, name)
+
+	if err != nil {
+		app.handleErrorInternal(err, w)
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+	return
+}
+
 // HandleStreamControllerStatus test calls
 // TODO: Refactor repeated calls.
 func (app *App) HandleStreamControllerStatus(w http.ResponseWriter, r *http.Request) {

+ 14 - 0
server/router/router.go

@@ -1182,6 +1182,20 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"DELETE",
+			"/projects/{project_id}/k8s/pods",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveClusterAccess(
+					requestlog.NewHandler(a.HandleDeletePod, l),
+					mw.URLParam,
+					mw.QueryParam,
+				),
+				mw.URLParam,
+				mw.ReadAccess,
+			),
+		)
+
 		// /api/projects/{project_id}/subdomain routes
 		r.Method(
 			"POST",