ソースを参照

fix status footer

Feroze Mohideen 2 年 前
コミット
526589a09e

+ 6 - 1
api/server/handlers/porter_app/service_status.go

@@ -138,8 +138,13 @@ func (c *ServiceStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	appRevisions := listAppRevisionsResp.Msg.AppRevisions
+	if appRevisions == nil {
+		appRevisions = []*porterv1.AppRevision{}
+	}
+
 	var revisions []porter_app.Revision
-	for _, revision := range listAppRevisionsResp.Msg.AppRevisions {
+	for _, revision := range appRevisions {
 		encodedRevision, err := porter_app.EncodedRevisionFromProto(ctx, revision)
 		if err != nil {
 			err := telemetry.Error(ctx, span, err, "error getting encoded revision from proto")

+ 5 - 11
dashboard/src/lib/hooks/useAppStatus.ts

@@ -11,6 +11,8 @@ import {
   type NewWebsocketOptions,
 } from "shared/hooks/useWebsockets";
 
+export type AppServiceStatus = Record<string, ClientServiceStatus>;
+
 export type ServiceStatusDescriptor =
   | "running"
   | "pending"
@@ -32,7 +34,7 @@ export type ClientServiceVersionStatus = {
 
 export type ClientServiceVersionInstanceStatus = {
   status: ServiceStatusDescriptor;
-  message: string;
+  revisionId: string;
   crashLoopReason: string;
   restartCount: number;
   name: string;
@@ -73,7 +75,7 @@ export const useAppStatus = ({
   appName: string;
   kind?: string;
 }): {
-  appServiceStatus: Record<string, ClientServiceStatus>;
+  appServiceStatus: AppServiceStatus;
 } => {
   const [serviceStatusMap, setServiceStatusMap] = useState<
     Record<string, SerializedServiceStatus>
@@ -179,22 +181,14 @@ export const useAppStatus = ({
               .otherwise(() => "unknown" as const);
             const clientServiceVersionInstanceStatus: ClientServiceVersionInstanceStatus =
               {
+                revisionId: revisionStatus.revision_id,
                 status,
-                message: "",
                 crashLoopReason: "",
                 restartCount: instanceStatus.restart_count,
                 name: instanceStatus.name,
                 creationTimestamp: instanceStatus.creation_timestamp,
               };
 
-            if (instanceStatus.status === "PENDING") {
-              clientServiceVersionInstanceStatus.message = `Instance is pending at Version ${revisionStatus.revision_number}`;
-            } else if (instanceStatus.status === "RUNNING") {
-              clientServiceVersionInstanceStatus.message = `Instance is running at Version ${revisionStatus.revision_number}`;
-            } else if (instanceStatus.status === "FAILED") {
-              clientServiceVersionInstanceStatus.message = `Instance is failing at Version ${revisionStatus.revision_number}`;
-            }
-
             return clientServiceVersionInstanceStatus;
           });
 

+ 2 - 2
dashboard/src/main/home/app-dashboard/app-view/tabs/Overview.tsx

@@ -36,7 +36,7 @@ const Overview: React.FC<Props> = ({ buttonStatus }) => {
     clusterId,
   });
 
-  const { serviceVersionStatus } = useAppStatus({
+  const { appServiceStatus } = useAppStatus({
     projectId,
     clusterId,
     services: latestClientServices.filter(
@@ -63,7 +63,7 @@ const Overview: React.FC<Props> = ({ buttonStatus }) => {
         addNewText={"Add a new service"}
         fieldArrayName={"app.services"}
         existingServiceNames={latestProto.serviceList.map((s) => s.name)}
-        serviceVersionStatus={serviceVersionStatus}
+        appServiceStatus={appServiceStatus}
         internalNetworkingDetails={{
           namespace: deploymentTarget.namespace,
           appName: porterApp.name,

+ 2 - 0
dashboard/src/main/home/app-dashboard/app-view/tabs/status/ServiceStatus.tsx

@@ -31,6 +31,8 @@ const ServiceStatus: React.FC<Props> = ({ serviceStatus, service }) => {
         return <Icon src={job} />;
       case "predeploy":
         return <Icon src={job} />;
+      case "initdeploy":
+        return <Icon src={job} />;
     }
   };
 

+ 5 - 2
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/ServiceContainer.tsx

@@ -6,7 +6,7 @@ import styled, { keyframes } from "styled-components";
 import { match } from "ts-pattern";
 
 import Spacer from "components/porter/Spacer";
-import { type ClientServiceVersionInstanceStatus } from "lib/hooks/useAppStatus";
+import { type ClientServiceStatus } from "lib/hooks/useAppStatus";
 import { type PorterAppFormData } from "lib/porter-apps";
 import {
   isClientJobService,
@@ -33,7 +33,7 @@ type ServiceProps = {
     "app.services" | "app.predeploy"
   >;
   remove: (index: number) => void;
-  status?: ClientServiceStatus[];
+  status?: ClientServiceStatus;
   internalNetworkingDetails: {
     namespace: string;
     appName: string;
@@ -68,6 +68,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
       .with({ config: { type: "predeploy" } }, (svc) => (
         <JobTabs index={index} service={svc} isPredeploy />
       ))
+      .with({ config: { type: "initdeploy" } }, () => <></>)
       .exhaustive();
   };
 
@@ -81,6 +82,8 @@ const ServiceContainer: React.FC<ServiceProps> = ({
         return <Icon src={job} />;
       case "predeploy":
         return <Icon src={job} />;
+      case "initdeploy":
+        return <Icon src={job} />;
     }
   };
 

+ 4 - 4
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/ServiceList.tsx

@@ -16,7 +16,7 @@ import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
 import { useClusterContext } from "main/home/infrastructure-dashboard/ClusterContextProvider";
 import { type ClientCluster } from "lib/clusters/types";
-import { type ClientServiceStatus } from "lib/hooks/useAppStatus";
+import { type AppServiceStatus } from "lib/hooks/useAppStatus";
 import { type PorterAppFormData } from "lib/porter-apps";
 import {
   defaultSerialized,
@@ -50,7 +50,7 @@ type ServiceListProps = {
   isPredeploy?: boolean;
   existingServiceNames?: string[];
   fieldArrayName: "app.services" | "app.predeploy";
-  serviceVersionStatus?: Record<string, ClientServiceStatus[]>;
+  appServiceStatus?: AppServiceStatus;
   internalNetworkingDetails?: {
     namespace: string;
     appName: string;
@@ -64,7 +64,7 @@ const ServiceList: React.FC<ServiceListProps> = ({
   fieldArrayName,
   isPredeploy = false,
   existingServiceNames = [],
-  serviceVersionStatus,
+  appServiceStatus,
   internalNetworkingDetails = {
     namespace: "",
     appName: "",
@@ -238,7 +238,7 @@ const ServiceList: React.FC<ServiceListProps> = ({
                 service={svc}
                 update={update}
                 remove={onRemove}
-                status={serviceVersionStatus?.[svc.name.value]}
+                status={appServiceStatus?.[svc.name.value]}
                 internalNetworkingDetails={internalNetworkingDetails}
                 existingServiceNames={existingServiceNames}
               />

+ 63 - 97
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/footers/ServiceStatusFooter.tsx

@@ -1,15 +1,17 @@
-import React, { useState } from "react";
+import React, { useMemo } from "react";
 import _ from "lodash";
-import AnimateHeight, { type Height } from "react-animate-height";
+import pluralize from "pluralize";
 import styled from "styled-components";
 import { match } from "ts-pattern";
 
-import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Link from "components/porter/Link";
 import Tag from "components/porter/Tag";
 import Text from "components/porter/Text";
-import { type ClientServiceVersionInstanceStatus } from "lib/hooks/useAppStatus";
+import {
+  type ClientServiceStatus,
+  type ServiceStatusDescriptor,
+} from "lib/hooks/useAppStatus";
 import { isClientServiceNotification } from "lib/porter-apps/notification";
 
 import alert from "assets/alert-warning.svg";
@@ -17,78 +19,78 @@ import alert from "assets/alert-warning.svg";
 import { useLatestRevision } from "../../../app-view/LatestRevisionContext";
 
 type ServiceStatusFooterProps = {
-  status: ClientServiceVersionInstanceStatus[];
+  status: ClientServiceStatus;
   name: string;
 };
 const ServiceStatusFooter: React.FC<ServiceStatusFooterProps> = ({
   status,
   name,
 }) => {
-  const [expanded, setExpanded] = useState<boolean>(false);
   const { latestClientNotifications, tabUrlGenerator } = useLatestRevision();
-  const [height, setHeight] = useState<Height>(0);
+
+  // group instances by revision number and status
+  const instanceGroups: Array<{
+    revisionId: string;
+    revisionNumber: number;
+    status: ServiceStatusDescriptor;
+    numInstances: number;
+    restartCount: number;
+  }> = useMemo(() => {
+    return status.versionStatusList
+      .map((versionStatus) => {
+        const groupByStatus = _.groupBy(
+          versionStatus.instanceStatusList,
+          (instance) => instance.status
+        );
+        return Object.keys(groupByStatus).map((status) => {
+          return {
+            revisionId: versionStatus.revisionId,
+            revisionNumber: versionStatus.revisionNumber,
+            status: status as ServiceStatusDescriptor,
+            numInstances: groupByStatus[status].length,
+            restartCount: groupByStatus[status].reduce(
+              (acc, instance) => acc + instance.restartCount,
+              0
+            ),
+          };
+        });
+      })
+      .flat()
+      .filter((group) => group.status !== "unknown");
+  }, [status]);
 
   return (
-    <>
-      {status.map((versionStatus, i) => {
+    <div>
+      {instanceGroups.map((instanceGroup, i) => {
         const versionNotifications = latestClientNotifications
           .filter(isClientServiceNotification)
-          .filter((n) => n.appRevisionId === versionStatus.revisionId)
+          .filter((n) => n.appRevisionId === instanceGroup.revisionId)
           .filter((n) => n.service.name.value === name);
         return (
           <div key={i}>
-            <StyledStatusFooterTop expanded={expanded}>
+            <StyledStatusFooterTop>
               <StyledContainer row spaced>
-                {match(versionStatus)
-                  .with({ status: "failing" }, (vs) => {
-                    return (
-                      <>
-                        <Running>
-                          <StatusDot color="#ff0000" />
-                          <Text color="helper">{vs.message}</Text>
-                        </Running>
-                        {vs.crashLoopReason && (
-                          <Button
-                            onClick={() => {
-                              expanded ? setHeight(0) : setHeight(122);
-                              setExpanded(!expanded);
-                            }}
-                            height="20px"
-                            color="#ffffff11"
-                            withBorder
-                          >
-                            {expanded ? (
-                              <I className="material-icons">arrow_drop_up</I>
-                            ) : (
-                              <I className="material-icons">arrow_drop_down</I>
-                            )}
-                            <Text color="helper">See failure reason</Text>
-                          </Button>
-                        )}
-                      </>
-                    );
-                  })
-                  .with({ status: "pending" }, (vs) => {
-                    return (
-                      <Running>
-                        <StatusDot color="#FFA500" />
-                        <Text color="helper">{vs.message}</Text>
-                      </Running>
-                    );
-                  })
-                  .with({ status: "running" }, (vs) => {
-                    return (
-                      <Running>
-                        <StatusDot />
-                        <Text color="helper">{vs.message}</Text>
-                      </Running>
-                    );
-                  })
-                  .exhaustive()}
+                <Running>
+                  <StatusDot />
+                  <Text color="helper">{`${
+                    instanceGroup.numInstances
+                  } ${pluralize(
+                    "instance",
+                    instanceGroup.numInstances
+                  )} ${pluralize("is", instanceGroup.numInstances)} ${match(
+                    instanceGroup
+                  )
+                    .with({ status: "failing" }, () => "failing to run")
+                    .with({ status: "pending" }, () => "pending")
+                    .with({ status: "running" }, () => "running")
+                    .otherwise(() => "")} at Version ${
+                    instanceGroup.revisionNumber
+                  }`}</Text>
+                </Running>
                 <Container row style={{ gap: "10px" }}>
-                  {(versionStatus.restartCount ?? 0) > 0 && (
+                  {(instanceGroup.restartCount ?? 0) > 0 && (
                     <Text color="helper">
-                      Restarts: {versionStatus.restartCount}
+                      Restarts: {instanceGroup.restartCount}
                     </Text>
                   )}
                   {versionNotifications.length > 0 && (
@@ -107,17 +109,10 @@ const ServiceStatusFooter: React.FC<ServiceStatusFooterProps> = ({
                 </Container>
               </StyledContainer>
             </StyledStatusFooterTop>
-            {versionStatus.crashLoopReason && (
-              <AnimateHeight height={height}>
-                <StyledStatusFooter>
-                  <Message>{versionStatus.crashLoopReason}</Message>
-                </StyledStatusFooter>
-              </AnimateHeight>
-            )}
           </div>
         );
       })}
-    </>
+    </div>
   );
 };
 
@@ -152,11 +147,6 @@ const StatusDot = styled.div<{ color?: string }>`
   }
 `;
 
-const I = styled.i`
-  font-size: 14px;
-  margin-right: 5px;
-`;
-
 const Running = styled.div`
   display: flex;
   align-items: center;
@@ -185,32 +175,8 @@ const StyledStatusFooter = styled.div`
   }
 `;
 
-const StyledStatusFooterTop = styled(StyledStatusFooter)<{
-  expanded: boolean;
-}>`
+const StyledStatusFooterTop = styled(StyledStatusFooter)`
   height: 40px;
-  border-bottom: ${({ expanded }) => expanded && "0px"};
-  border-bottom-left-radius: ${({ expanded }) => expanded && "0px"};
-  border-bottom-right-radius: ${({ expanded }) => expanded && "0px"};
-`;
-
-const Message = styled.div`
-  padding: 20px;
-  background: #000000;
-  border-radius: 5px;
-  line-height: 1.5em;
-  border: 1px solid #aaaabb33;
-  font-family: monospace;
-  font-size: 13px;
-  display: flex;
-  align-items: center;
-  > img {
-    width: 13px;
-    margin-right: 20px;
-  }
-  width: 100%;
-  height: 101px;
-  overflow: hidden;
 `;
 
 const StyledContainer = styled.div<{