Просмотр исходного кода

Merge pull request #1099 from porter-dev/fix-chart-sort

Fix chart sort and live update
Anukul Sangwan 4 лет назад
Родитель
Сommit
ff4dfd6b48

+ 2 - 7
dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx

@@ -13,7 +13,6 @@ type Props = {
   chart: ChartType;
   controllers: Record<string, any>;
   isJob: boolean;
-  release: any;
 };
 
 type JobStatusType = {
@@ -25,7 +24,6 @@ const Chart: React.FunctionComponent<Props> = ({
   chart,
   controllers,
   isJob,
-  release,
 }) => {
   const [expand, setExpand] = useState<boolean>(false);
   const [chartControllers, setChartControllers] = useState<any>([]);
@@ -191,10 +189,7 @@ const Chart: React.FunctionComponent<Props> = ({
               <>
                 <Dot>•</Dot>
                 <JobStatus>
-                  Last deployed{" "}
-                  {readableDate(
-                    release?.info?.last_deployed || chart.info.last_deployed
-                  )}
+                  Last deployed {readableDate(chart.info.last_deployed)}
                 </JobStatus>
               </>
             )}
@@ -208,7 +203,7 @@ const Chart: React.FunctionComponent<Props> = ({
       </BottomWrapper>
 
       <TopRightContainer>
-        <span>v{release?.version || chart.version}</span>
+        <span>v{chart.version}</span>
       </TopRightContainer>
     </StyledChart>
   );

+ 83 - 63
dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx

@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, useState } from "react";
+import React, { useContext, useEffect, useMemo, useState } from "react";
 import styled from "styled-components";
 
 import { Context } from "shared/Context";
@@ -33,7 +33,6 @@ const ChartList: React.FunctionComponent<Props> = ({
   const [controllers, setControllers] = useState<
     Record<string, Record<string, any>>
   >({});
-  const [releases, setReleases] = useState<Record<string, any>>({});
   const [isLoading, setIsLoading] = useState(false);
   const [isError, setIsError] = useState(false);
 
@@ -66,37 +65,8 @@ const ChartList: React.FunctionComponent<Props> = ({
         { id: currentProject.id }
       );
       const charts = res.data || [];
-
-      // filter charts based on the current view
-      const filteredCharts = charts.filter((chart: ChartType) => {
-        return (
-          (currentView == "jobs" && chart.chart.metadata.name == "job") ||
-          ((currentView == "applications" ||
-            currentView == "cluster-dashboard") &&
-            chart.chart.metadata.name != "job")
-        );
-      });
-
-      let sortedCharts = filteredCharts;
-
-      if (sortType == "Newest") {
-        sortedCharts.sort((a: any, b: any) =>
-          Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)
-            ? -1
-            : 1
-        );
-      } else if (sortType == "Oldest") {
-        sortedCharts.sort((a: any, b: any) =>
-          Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)
-            ? 1
-            : -1
-        );
-      } else if (sortType == "Alphabetical") {
-        sortedCharts.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
-      }
-
       setIsError(false);
-      return sortedCharts;
+      return charts;
     } catch (error) {
       console.log(error);
       context.setCurrentError(JSON.stringify(error));
@@ -104,8 +74,14 @@ const ChartList: React.FunctionComponent<Props> = ({
     }
   };
 
-  const setupHelmReleasesWebsocket = () => {
-    const apiPath = `/api/projects/${context.currentProject.id}/k8s/helm_releases?cluster_id=${context.currentCluster.id}`;
+  const setupHelmReleasesWebsocket = (
+    websocketID: string,
+    namespace: string
+  ) => {
+    let apiPath = `/api/projects/${context.currentProject.id}/k8s/helm_releases?cluster_id=${context.currentCluster.id}`;
+    if (namespace) {
+      apiPath += `&namespace=${namespace}`;
+    }
 
     const wsConfig = {
       onopen: () => {
@@ -113,21 +89,29 @@ const ChartList: React.FunctionComponent<Props> = ({
       },
       onmessage: (evt: MessageEvent) => {
         let event = JSON.parse(evt.data);
-        const object = event.Object;
-        setReleases((oldReleases) => {
-          const currentRelease = oldReleases[object?.name];
-          const currentReleaseVersion = Number(currentRelease?.version);
-          const newReleaseVersion = Number(object?.version);
-          if (currentReleaseVersion > newReleaseVersion) {
-            return {
-              ...oldReleases,
-            };
+        const newChart: ChartType = event.Object;
+        const isSameChart = (chart: ChartType) =>
+          chart.name === newChart.name &&
+          chart.namespace === newChart.namespace;
+        setCharts((currentCharts) => {
+          switch (event.event_type) {
+            case "ADD":
+              if (currentCharts.find(isSameChart)) {
+                return currentCharts;
+              }
+              return currentCharts.concat(newChart);
+            case "UPDATE":
+              return currentCharts.map((chart) => {
+                if (isSameChart(chart) && newChart.version >= chart.version) {
+                  return newChart;
+                }
+                return chart;
+              });
+            case "DELETE":
+              return currentCharts.filter((chart) => !isSameChart(chart));
+            default:
+              return currentCharts;
           }
-
-          return {
-            ...oldReleases,
-            [object.name]: object,
-          };
         });
       },
 
@@ -141,11 +125,11 @@ const ChartList: React.FunctionComponent<Props> = ({
       },
     };
 
-    newWebsocket("helm_releases", apiPath, wsConfig);
-    openWebsocket("helm_releases");
+    newWebsocket(websocketID, apiPath, wsConfig);
+    openWebsocket(websocketID);
   };
 
-  const setupWebsocket = (kind: string) => {
+  const setupControllerWebsocket = (kind: string) => {
     let { currentCluster, currentProject } = context;
     const apiPath = `/api/projects/${currentProject.id}/k8s/${kind}/status?cluster_id=${currentCluster.id}`;
 
@@ -177,27 +161,35 @@ const ChartList: React.FunctionComponent<Props> = ({
     openWebsocket(kind);
   };
 
-  const setControllerWebsockets = (controllers: any[]) => {
-    controllers.map((kind: string) => {
-      return setupWebsocket(kind);
-    });
+  const setupControllerWebsockets = (controllers: string[]) => {
+    controllers.map((kind) => setupControllerWebsocket(kind));
   };
 
-  // Setup basic websockets on start
   useEffect(() => {
-    setControllerWebsockets([
+    const controllers = [
       "deployment",
       "statefulset",
       "daemonset",
       "replicaset",
-    ]);
-    setupHelmReleasesWebsocket();
+    ];
+
+    setupControllerWebsockets(controllers);
 
     return () => {
-      closeAllWebsockets();
+      controllers.map((controller) => closeWebsocket(controller));
     };
   }, []);
 
+  useEffect(() => {
+    const websocketID = "helm_releases";
+
+    setupHelmReleasesWebsocket(websocketID, namespace);
+
+    return () => {
+      closeWebsocket(websocketID);
+    };
+  }, [namespace]);
+
   useEffect(() => {
     let isSubscribed = true;
 
@@ -212,6 +204,35 @@ const ChartList: React.FunctionComponent<Props> = ({
     return () => (isSubscribed = false);
   }, [namespace, currentView]);
 
+  const filteredCharts = useMemo(() => {
+    const result = charts.filter((chart: ChartType) => {
+      return (
+        (currentView == "jobs" && chart.chart.metadata.name == "job") ||
+        ((currentView == "applications" ||
+          currentView == "cluster-dashboard") &&
+          chart.chart.metadata.name != "job")
+      );
+    });
+
+    if (sortType == "Newest") {
+      result.sort((a: any, b: any) =>
+        Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)
+          ? -1
+          : 1
+      );
+    } else if (sortType == "Oldest") {
+      result.sort((a: any, b: any) =>
+        Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)
+          ? 1
+          : -1
+      );
+    } else if (sortType == "Alphabetical") {
+      result.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
+    }
+
+    return result;
+  }, [charts, sortType]);
+
   const renderChartList = () => {
     if (isLoading || (!namespace && namespace !== "")) {
       return (
@@ -225,7 +246,7 @@ const ChartList: React.FunctionComponent<Props> = ({
           <i className="material-icons">error</i> Error connecting to cluster.
         </Placeholder>
       );
-    } else if (charts.length === 0) {
+    } else if (filteredCharts.length === 0) {
       return (
         <Placeholder>
           <i className="material-icons">category</i> No
@@ -235,14 +256,13 @@ const ChartList: React.FunctionComponent<Props> = ({
       );
     }
 
-    return charts.map((chart: ChartType, i: number) => {
+    return filteredCharts.map((chart: ChartType, i: number) => {
       return (
         <Chart
           key={`${chart.namespace}-${chart.name}`}
           chart={chart}
           controllers={controllers || {}}
           isJob={currentView === "jobs"}
-          release={releases[chart.name] || {}}
         />
       );
     });

+ 2 - 1
internal/kubernetes/agent.go

@@ -704,7 +704,7 @@ func parseSecretToHelmRelease(secret v1.Secret, chartList []string) (*rspb.Relea
 	return helm_object, false, nil
 }
 
-func (a *Agent) StreamHelmReleases(conn *websocket.Conn, chartList []string, selectors string) error {
+func (a *Agent) StreamHelmReleases(conn *websocket.Conn, namespace string, chartList []string, selectors string) error {
 	tweakListOptionsFunc := func(options *metav1.ListOptions) {
 		options.LabelSelector = selectors
 	}
@@ -713,6 +713,7 @@ func (a *Agent) StreamHelmReleases(conn *websocket.Conn, chartList []string, sel
 		a.Clientset,
 		0,
 		informers.WithTweakListOptions(tweakListOptionsFunc),
+		informers.WithNamespace(namespace),
 	)
 
 	informer := factory.Core().V1().Secrets().Informer()

+ 6 - 1
server/api/k8s_handler.go

@@ -1228,7 +1228,12 @@ func (app *App) HandleStreamHelmReleases(w http.ResponseWriter, r *http.Request)
 		chartList = vals["charts"]
 	}
 
-	err = agent.StreamHelmReleases(conn, chartList, selectors)
+	namespace := v1.NamespaceAll
+	if vals["namespace"] != nil {
+		namespace = vals["namespace"][0]
+	}
+
+	err = agent.StreamHelmReleases(conn, namespace, chartList, selectors)
 
 	if err != nil {
 		app.handleErrorWebsocketWrite(err, w)