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

Merge branch 'nico/por-521-frontend-stacks-work' of github.com:porter-dev/porter into nico/por-521-frontend-stacks-work

jnfrati 3 лет назад
Родитель
Сommit
a30126e541

+ 7 - 7
api/server/handlers/stack/create.go

@@ -129,9 +129,9 @@ func (p *StackCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 		if err != nil {
 			deployErrs = append(deployErrs, err.Error())
+		} else {
+			helmReleaseMap[fmt.Sprintf("%s/%s", namespace, appResource.Name)] = rel
 		}
-
-		helmReleaseMap[fmt.Sprintf("%s/%s", namespace, appResource.Name)] = rel
 	}
 
 	// update stack revision status
@@ -155,12 +155,12 @@ func (p *StackCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	saveErrs := make([]string, 0)
 
 	for _, resource := range revision.Resources {
-		rel := helmReleaseMap[fmt.Sprintf("%s/%s", namespace, resource.Name)]
-
-		_, err = release.CreateAppReleaseFromHelmRelease(p.Config(), proj.ID, cluster.ID, resource.ID, rel)
+		if rel, exists := helmReleaseMap[fmt.Sprintf("%s/%s", namespace, resource.Name)]; exists {
+			_, err = release.CreateAppReleaseFromHelmRelease(p.Config(), proj.ID, cluster.ID, resource.ID, rel)
 
-		if err != nil {
-			saveErrs = append(saveErrs, fmt.Sprintf("the resource %s/%s could not be saved right now", namespace, resource.Name))
+			if err != nil {
+				saveErrs = append(saveErrs, fmt.Sprintf("the resource %s/%s could not be saved right now", namespace, resource.Name))
+			}
 		}
 	}
 

+ 1 - 1
api/server/router/v1/stack.go

@@ -112,7 +112,7 @@ func getV1StackRoutes(
 	// POST /api/v1/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/stacks -> stack.NewStackCreateHandler
 	// swagger:operation POST /api/v1/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/stacks createStack
 	//
-	// Creates a stack
+	// Creates a new stack and triggers a deployment for all resources in the stack.
 	//
 	// ---
 	// produces:

+ 67 - 5
dashboard/src/main/home/cluster-dashboard/stacks/ExpandedStack.tsx

@@ -1,12 +1,23 @@
 import Loading from "components/Loading";
+import TitleSection from "components/TitleSection";
 import React, { useContext, useEffect, useState } from "react";
 import { useParams } from "react-router";
 import api from "shared/api";
 import { Context } from "shared/Context";
+import { readableDate } from "shared/string_utils";
 import styled from "styled-components";
 import ChartList from "../chart/ChartList";
-import DashboardHeader from "../DashboardHeader";
 import SortSelector from "../SortSelector";
+import Status from "./components/Status";
+import {
+  Br,
+  InfoWrapper,
+  LastDeployed,
+  LineBreak,
+  SepDot,
+  Text,
+} from "./components/styles";
+import { getStackStatus, getStackStatusMessage } from "./shared";
 import { Stack } from "./types";
 
 const ExpandedStack = () => {
@@ -53,11 +64,46 @@ const ExpandedStack = () => {
 
   return (
     <div>
-      <DashboardHeader
-        title={stack?.name}
+      <TitleSection
         materialIconClass="material-icons-outlined"
-        image="lan"
-      />
+        icon={"lan"}
+        capitalize
+      >
+        {stack.name}
+      </TitleSection>
+      <Br />
+      <InfoWrapper>
+        <LastDeployed>
+          <Status
+            status={getStackStatus(stack)}
+            message={getStackStatusMessage(stack)}
+          />
+          <SepDot>•</SepDot>
+          <Text color="#aaaabb">
+            {!stack.latest_revision?.id
+              ? `No version found`
+              : `v${stack.latest_revision.id}`}
+          </Text>
+          <SepDot>•</SepDot>
+          Last updated {readableDate(stack.updated_at)}
+        </LastDeployed>
+      </InfoWrapper>
+
+      {/* Stack error message */}
+      {stack.latest_revision &&
+      stack.latest_revision.status === "failed" &&
+      stack.latest_revision.message?.length > 0 ? (
+        <StackErrorMessageStyles.Wrapper>
+          <StackErrorMessageStyles.Title color="#b7b7c9">
+            Error reason:
+          </StackErrorMessageStyles.Title>
+          <StackErrorMessageStyles.Text color="#aaaabb">
+            {stack.latest_revision.message}
+          </StackErrorMessageStyles.Text>
+        </StackErrorMessageStyles.Wrapper>
+      ) : null}
+
+      <LineBreak />
 
       <SortSelector
         setSortType={setSortType}
@@ -89,3 +135,19 @@ const ChartListWrapper = styled.div`
   margin-top: 20px;
   padding-bottom: 125px;
 `;
+
+const StackErrorMessageStyles = {
+  Text: styled(Text)`
+    font-size: 14px;
+    margin-bottom: 10px;
+  `,
+  Wrapper: styled.div`
+    display: flex;
+    flex-direction: column;
+    margin-top: 5px;
+  `,
+  Title: styled(Text)`
+    font-size: 16px;
+    font-weight: bold;
+  `,
+};

+ 25 - 58
dashboard/src/main/home/cluster-dashboard/stacks/_StackList.tsx

@@ -8,6 +8,15 @@ import styled from "styled-components";
 import { Stack } from "./types";
 import { readableDate } from "shared/string_utils";
 import { CardGrid, Card } from "./launch/components/styles";
+import Status, { StatusProps } from "./components/Status";
+import {
+  Flex,
+  InfoWrapper,
+  LastDeployed,
+  SepDot,
+  Text,
+} from "./components/styles";
+import { getStackStatus, getStackStatusMessage } from "./shared";
 
 const StackList = ({ namespace }: { namespace: string }) => {
   const { currentProject, currentCluster, setCurrentError } = useContext(
@@ -105,21 +114,22 @@ const StackList = ({ namespace }: { namespace: string }) => {
                 <span>{stack.name}</span>
               </StackName>
 
-              <Flex>
-                <DeploymentImageContainer>
-                  <InfoWrapper>
-                    <LastDeployed>
-                      <Revision>
-                        {!stack.latest_revision?.id
-                          ? `No version found`
-                          : `v${stack.latest_revision.id}`}
-                      </Revision>
-                      <SepDot>•</SepDot>
-                      Last updated {readableDate(stack.updated_at)}
-                    </LastDeployed>
-                  </InfoWrapper>
-                </DeploymentImageContainer>
-              </Flex>
+              <InfoWrapper>
+                <LastDeployed>
+                  <Status
+                    status={getStackStatus(stack)}
+                    message={getStackStatusMessage(stack)}
+                  />
+                  <SepDot>•</SepDot>
+                  <Text color="#aaaabb">
+                    {!stack.latest_revision?.id
+                      ? `No version found`
+                      : `v${stack.latest_revision.id}`}
+                  </Text>
+                  <SepDot>•</SepDot>
+                  Last updated {readableDate(stack.updated_at)}
+                </LastDeployed>
+              </InfoWrapper>
             </DataContainer>
             <Flex>
               <RowButton
@@ -172,10 +182,6 @@ const RowButton = styled.button`
   }
 `;
 
-const Revision = styled.div`
-  color: #aaaabb;
-`;
-
 const StackIcon = styled.div`
   margin-bottom: -4px;
 
@@ -197,45 +203,6 @@ const StackName = styled.div`
   margin-bottom: 10px;
 `;
 
-const SepDot = styled.div`
-  color: #aaaabb66;
-  margin: 0 9px;
-`;
-
-const InfoWrapper = styled.div`
-  display: flex;
-  align-items: center;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  margin-right: 8px;
-`;
-
-const LastDeployed = styled.div`
-  font-size: 13px;
-  margin-top: -1px;
-  display: flex;
-  align-items: center;
-  color: #aaaabb66;
-`;
-
-const DeploymentImageContainer = styled.div`
-  height: 20px;
-  font-size: 13px;
-  position: relative;
-  display: flex;
-  align-items: center;
-  font-weight: 400;
-  justify-content: center;
-  color: #ffffff66;
-  margin-left: 1px;
-`;
-
-const Flex = styled.div`
-  display: flex;
-  align-items: center;
-`;
-
 const DataContainer = styled.div`
   display: flex;
   flex-direction: column;

+ 25 - 0
dashboard/src/main/home/cluster-dashboard/stacks/components/Status.tsx

@@ -0,0 +1,25 @@
+import React from "react";
+import { StatusStyles } from "./styles";
+import loading from "assets/loading.gif";
+
+export type StatusProps = {
+  status: "loading" | "failed" | "successful" | "unknown";
+  message: string;
+  className?: string;
+};
+
+const Status = ({ status, message, className }: StatusProps) => {
+  return (
+    <>
+      <StatusStyles.Status className={className}>
+        {status === "loading" && <StatusStyles.Spinner src={loading} />}
+        {status === "failed" && <StatusStyles.Failed />}
+        {status === "successful" && <StatusStyles.Successful />}
+        {status === "unknown" && <StatusStyles.Unknown />}
+        {message}
+      </StatusStyles.Status>
+    </>
+  );
+};
+
+export default Status;

+ 98 - 0
dashboard/src/main/home/cluster-dashboard/stacks/components/styles.ts

@@ -0,0 +1,98 @@
+import styled from "styled-components";
+
+const StatusBase = styled.div`
+  margin-top: 1px;
+  width: 8px;
+  height: 8px;
+  border-radius: 20px;
+  margin-left: 3px;
+  margin-right: 16px;
+`;
+
+export const StatusStyles = {
+  Spinner: styled.img`
+    width: 15px;
+    height: 15px;
+    margin-right: 15px;
+    margin-bottom: -3px;
+  `,
+  Failed: styled(StatusBase)`
+    background: #ed5f85;
+  `,
+  Successful: styled(StatusBase)`
+    background: #4797ff;
+  `,
+  Unknown: styled(StatusBase)`
+    background: #f5cb42;
+  `,
+  Status: styled.div`
+    display: flex;
+    height: 20px;
+    font-size: 13px;
+    flex-direction: row;
+    text-transform: capitalize;
+    align-items: center;
+    font-family: "Work Sans", sans-serif;
+    color: #aaaabb;
+    animation: fadeIn 0.5s;
+
+    @keyframes fadeIn {
+      from {
+        opacity: 0;
+      }
+      to {
+        opacity: 1;
+      }
+    }
+  `,
+};
+
+export const LineBreak = styled.div`
+  width: calc(100% - 0px);
+  height: 2px;
+  background: #ffffff20;
+  margin: 10px 0px 35px;
+`;
+
+export const Br = styled.div`
+  width: 100%;
+  height: 1px;
+`;
+
+export const InfoWrapper = styled.div`
+  display: flex;
+  align-items: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin-right: 8px;
+
+  height: 20px;
+  font-size: 13px;
+  position: relative;
+  font-weight: 400;
+  color: #ffffff66;
+  margin-left: 1px;
+`;
+
+export const LastDeployed = styled.div`
+  font-size: 13px;
+  margin-top: -1px;
+  display: flex;
+  align-items: center;
+  color: #aaaabb66;
+`;
+
+export const Text = styled.span<{ color: string }>`
+  color: ${({ color }) => color};
+`;
+
+export const SepDot = styled.div`
+  color: #aaaabb66;
+  margin: 0 9px;
+`;
+
+export const Flex = styled.div`
+  display: flex;
+  align-items: center;
+`;

+ 47 - 0
dashboard/src/main/home/cluster-dashboard/stacks/shared.ts

@@ -0,0 +1,47 @@
+import { StatusProps } from "./components/Status";
+import { Stack } from "./types";
+
+export const getStackStatus = (stack: Stack): StatusProps["status"] => {
+  const latestRevision = stack.latest_revision;
+
+  if (latestRevision === null) {
+    return "unknown";
+  }
+
+  if (latestRevision.status === "deployed") {
+    return "successful";
+  }
+
+  if (latestRevision.status === "deploying") {
+    return "loading";
+  }
+
+  if (latestRevision.status === "failed") {
+    return "failed";
+  }
+
+  return "unknown";
+};
+
+export const getStackStatusMessage = (stack: Stack): StatusProps["message"] => {
+  const latestRevision = stack.latest_revision;
+
+  if (latestRevision === null) {
+    return "";
+  }
+
+  if (latestRevision.status === "failed") {
+    return latestRevision.reason.split(/(?=[A-Z])/).join(" ");
+  }
+
+  switch (latestRevision.status) {
+    case "deploying":
+      return "Deploying";
+    case "deployed":
+      return "Deployed";
+    case "deploying":
+      return "Deploying";
+    default:
+      return "";
+  }
+};

+ 3 - 1
dashboard/src/main/home/cluster-dashboard/stacks/types.ts

@@ -43,8 +43,10 @@ export type Stack = {
 export type StackRevision = {
   id: number;
   created_at: string;
-  status: string; // type with enum
+  status: "deploying" | "deployed" | "failed"; // type with enum
   stack_id: string;
+  reason: "DeployError" | "SaveError" | "RollbackError";
+  message: string;
 };
 
 export type SourceConfig = {