浏览代码

Create flow v1 without edition

jnfrati 4 年之前
父节点
当前提交
27c746cae5

+ 24 - 3
dashboard/src/main/home/cluster-dashboard/stacks/launch/NewApp.tsx

@@ -1,3 +1,4 @@
+import Helper from "components/form-components/Helper";
 import InputRow from "components/form-components/InputRow";
 import Loading from "components/Loading";
 import PorterFormWrapper from "components/porter-form/PorterFormWrapper";
@@ -19,6 +20,7 @@ const NewApp = () => {
   const [template, setTemplate] = useState<ExpandedPorterTemplate>();
   const [isLoading, setIsLoading] = useState(true);
   const [hasError, setHasError] = useState(false);
+  const [saveButtonStatus, setSaveButtonStatus] = useState("");
 
   const [appName, setAppName] = useState("");
 
@@ -68,6 +70,11 @@ const NewApp = () => {
   }
 
   const handleSubmit = (values: any) => {
+    if (appName === "") {
+      setSaveButtonStatus("App name cannot be empty");
+      return;
+    }
+
     addAppResource({
       name: appName,
       source_config_name: newStack.source_configs[0]?.name || "",
@@ -75,17 +82,31 @@ const NewApp = () => {
       template_version: params.version,
       values,
     });
-    pushFiltered("/stacks/launch/overview", []);
+
+    setSaveButtonStatus("successful");
+    setTimeout(() => {
+      setSaveButtonStatus("");
+      pushFiltered("/stacks/launch/overview", []);
+    }, 1000);
   };
 
   return (
-    <div>
+    <div style={{ position: "relative" }}>
+      <Helper>App name</Helper>
       <InputRow
         type="string"
         value={appName}
         setValue={(val: string) => setAppName(val)}
+        width={"300px"}
+      />
+      <Helper>App settings</Helper>
+      <PorterFormWrapper
+        formData={template.form}
+        onSubmit={handleSubmit}
+        isLaunch
+        saveValuesStatus={saveButtonStatus}
+        saveButtonText="Add application"
       />
-      <PorterFormWrapper formData={template.form} onSubmit={handleSubmit} />
     </div>
   );
 };

+ 93 - 11
dashboard/src/main/home/cluster-dashboard/stacks/launch/Overview.tsx

@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, useRef, useState } from "react";
+import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
 import semver from "semver";
 import { StacksLaunchContext } from "./Store";
 import InputRow from "components/form-components/InputRow";
@@ -11,6 +11,8 @@ import DynamicLink from "components/DynamicLink";
 import styled from "styled-components";
 import { useOutsideAlerter } from "shared/hooks/useOutsideAlerter";
 import { capitalize } from "shared/string_utils";
+import SaveButton from "components/SaveButton";
+import { useRouting } from "shared/routing";
 
 const Overview = () => {
   const {
@@ -20,6 +22,7 @@ const Overview = () => {
     setStackName,
     setStackNamespace,
     setStackCluster,
+    submit,
   } = useContext(StacksLaunchContext);
   const { currentProject } = useContext(Context);
   const [isAuthorized] = useAuth();
@@ -32,6 +35,10 @@ const Overview = () => {
     { label: string; value: string }[]
   >([]);
 
+  const [submitButtonStatus, setSubmitButtonStatus] = useState("");
+
+  const { pushFiltered } = useRouting();
+
   const getClusters = () => {
     return api
       .getClusters("<token>", {}, { id: currentProject.id })
@@ -87,6 +94,18 @@ const Overview = () => {
       .catch(console.log);
   };
 
+  const handleSubmit = () => {
+    setSubmitButtonStatus("loading");
+
+    submit().then(() => {
+      console.log("submit");
+      setTimeout(() => {
+        setSubmitButtonStatus("");
+        pushFiltered("/stacks", []);
+      }, 1000);
+    });
+  };
+
   useEffect(() => {
     getClusters();
   }, []);
@@ -98,8 +117,21 @@ const Overview = () => {
     updateNamespaces(clusterId);
   }, [clusterId]);
 
+  const isValid = useMemo(() => {
+    if (namespace === "") {
+      return false;
+    }
+    if (isNaN(clusterId)) {
+      return false;
+    }
+    if (newStack.name === "") {
+      return false;
+    }
+    return true;
+  }, [namespace, clusterId, newStack.name]);
+
   return (
-    <>
+    <div style={{ position: "relative" }}>
       <InputRow
         type="string"
         value={newStack.name}
@@ -132,12 +164,25 @@ const Overview = () => {
       />
 
       <br />
-      {newStack.app_resources.map((app) => (
-        <div key={app.name}>{app.name}</div>
-      ))}
-
-      <AddResourceButton />
-    </>
+      <CardGrid>
+        {newStack.app_resources.map((app) => (
+          <Card key={app.name}>{app.name}</Card>
+        ))}
+
+        <AddResourceButton />
+      </CardGrid>
+
+      <SubmitButton
+        disabled={!isValid || submitButtonStatus !== ""}
+        text="Create Stack"
+        onClick={handleSubmit}
+        clearPosition
+        statusPosition="left"
+        status={submitButtonStatus}
+      >
+        Create stack
+      </SubmitButton>
+    </div>
   );
 };
 
@@ -320,11 +365,48 @@ const VersionSelector = ({
   );
 };
 
+const CardGrid = styled.div`
+  margin-top: 32px;
+  margin-bottom: 32px;
+  display: grid;
+  grid-row-gap: 25px;
+`;
+
+const Card = styled.div`
+  display: flex;
+  color: #ffffff;
+  background: #2b2e3699;
+  justify-content: space-between;
+  border-radius: 5px;
+  cursor: pointer;
+  height: 75px;
+  padding: 12px;
+  padding-left: 14px;
+  border: 1px solid #ffffff0f;
+
+  :hover {
+    border: 1px solid #ffffff3c;
+  }
+  animation: fadeIn 0.5s;
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+`;
+
+const SubmitButton = styled(SaveButton)`
+  width: 100%;
+  display: flex;
+  justify-content: flex-end;
+`;
+
 const AddResourceButtonStyles = {
-  Wrapper: styled.div`
-    display: flex;
+  Wrapper: styled(Card)`
     align-items: center;
-    justify-content: space-between;
   `,
   Text: styled.span`
     font-size: 20px;

+ 17 - 5
dashboard/src/main/home/cluster-dashboard/stacks/launch/SelectSource.tsx

@@ -3,6 +3,8 @@ import React, { useContext, useState } from "react";
 import { StacksLaunchContext } from "./Store";
 import { CreateStackBody } from "../types";
 import { useRouting } from "shared/routing";
+import styled from "styled-components";
+import SaveButton from "components/SaveButton";
 
 const SelectSource = () => {
   const { addSourceConfig } = useContext(StacksLaunchContext);
@@ -26,7 +28,7 @@ const SelectSource = () => {
   };
 
   return (
-    <>
+    <div style={{ position: "relative" }}>
       <ImageSelector
         selectedImageUrl={imageUrl}
         setSelectedImageUrl={setImageUrl}
@@ -35,11 +37,21 @@ const SelectSource = () => {
         forceExpanded
       />
 
-      <button disabled={!imageUrl || !imageTag} onClick={handleNext}>
-        Next
-      </button>
-    </>
+      <SubmitButton
+        disabled={!imageUrl || !imageTag}
+        onClick={handleNext}
+        text="Next"
+        clearPosition
+        makeFlush
+      />
+    </div>
   );
 };
 
 export default SelectSource;
+
+const SubmitButton = styled(SaveButton)`
+  width: 100%;
+  display: flex;
+  justify-content: flex-end;
+`;

+ 16 - 2
dashboard/src/main/home/cluster-dashboard/stacks/launch/Store.tsx

@@ -1,4 +1,6 @@
-import React, { createContext, useState } from "react";
+import React, { createContext, useContext, useState } from "react";
+import api from "shared/api";
+import { Context } from "shared/Context";
 import { CreateStackBody } from "../types";
 
 export type StacksLaunchContextType = {
@@ -48,6 +50,7 @@ export const StacksLaunchContext = createContext<StacksLaunchContextType>(
 );
 
 const StacksLaunchContextProvider: React.FC<{}> = ({ children }) => {
+  const { currentProject, setCurrentError } = useContext(Context);
   const [newStack, setNewStack] = useState<CreateStackBody>(
     defaultValues.newStack
   );
@@ -100,7 +103,18 @@ const StacksLaunchContextProvider: React.FC<{}> = ({ children }) => {
     }));
   };
 
-  const submit: StacksLaunchContextType["submit"] = async () => {};
+  const submit: StacksLaunchContextType["submit"] = async () => {
+    try {
+      await api.createStack("<token>", newStack, {
+        cluster_id: clusterId,
+        namespace: namespace,
+        project_id: currentProject.id,
+      });
+    } catch (error) {
+      setCurrentError(error);
+      throw error;
+    }
+  };
 
   return (
     <StacksLaunchContext.Provider

+ 25 - 16
dashboard/src/main/home/cluster-dashboard/stacks/launch/index.tsx

@@ -1,5 +1,6 @@
 import React from "react";
 import { Redirect, Route, Switch, useRouteMatch } from "react-router";
+import styled from "styled-components";
 import NewApp from "./NewApp";
 import Overview from "./Overview";
 import SelectSource from "./SelectSource";
@@ -9,23 +10,31 @@ const LaunchRoutes = () => {
   const { path } = useRouteMatch();
 
   return (
-    <StacksLaunchContextProvider>
-      <Switch>
-        <Route path={`${path}/source`}>
-          <SelectSource />
-        </Route>
-        <Route path={`${path}/overview`}>
-          <Overview />
-        </Route>
-        <Route path={`${path}/new-app/:template_name/:version`}>
-          <NewApp />
-        </Route>
-        <Route path={`*`}>
-          <Redirect to={`${path}/source`} />
-        </Route>
-      </Switch>
-    </StacksLaunchContextProvider>
+    <LaunchContainer>
+      <StacksLaunchContextProvider>
+        <Switch>
+          <Route path={`${path}/source`}>
+            <SelectSource />
+          </Route>
+          <Route path={`${path}/overview`}>
+            <Overview />
+          </Route>
+          <Route path={`${path}/new-app/:template_name/:version/:repo_url?`}>
+            <NewApp />
+          </Route>
+          <Route path={`*`}>
+            <Redirect to={`${path}/source`} />
+          </Route>
+        </Switch>
+      </StacksLaunchContextProvider>
+    </LaunchContainer>
   );
 };
 
 export default LaunchRoutes;
+
+const LaunchContainer = styled.div`
+  max-width: 780px;
+  margin: 0 auto;
+  padding: 0 20px;
+`;

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

@@ -5,6 +5,7 @@ export type CreateStackBody = {
     source_config_name: string;
     template_name: string;
     template_version: string;
+    template_repo_url?: string;
     values: unknown;
   }[];
   source_configs: {

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

@@ -4,6 +4,7 @@ import { release } from "process";
 import { baseApi } from "./baseApi";
 
 import { BuildConfig, FullActionConfigType, StorageType } from "./types";
+import { CreateStackBody } from "main/home/cluster-dashboard/stacks/types";
 
 /**
  * Generic api call format
@@ -1927,6 +1928,73 @@ const getGitlabFolderContent = baseApi<
     `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/${repo_owner}/${repo_name}/${branch}/contents`
 );
 
+// STACKS
+
+const createStack = baseApi<
+  CreateStackBody,
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+  }
+>(
+  "POST",
+  ({ project_id, cluster_id, namespace }) =>
+    `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks`
+);
+
+const listStacks = baseApi<
+  {},
+  { project_id: number; cluster_id: number; namespace: string }
+>(
+  "GET",
+  ({ project_id, cluster_id, namespace }) =>
+    `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks`
+);
+
+const getStack = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    stack_id: string;
+  }
+>(
+  "GET",
+  ({ project_id, cluster_id, namespace, stack_id }) =>
+    `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks/${stack_id}`
+);
+
+const getStackRevision = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    stack_id: string;
+    revision_id: string;
+  }
+>(
+  "GET",
+  ({ project_id, cluster_id, namespace, stack_id, revision_id }) =>
+    `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks/${stack_id}/${revision_id}`
+);
+
+const rollbackStack = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    stack_id: string;
+  }
+>(
+  "POST",
+  ({ project_id, cluster_id, namespace, stack_id }) =>
+    `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks/${stack_id}/rollback`
+);
+
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
   checkAuth,
@@ -2109,4 +2177,11 @@ export default {
   getGitlabRepos,
   getGitlabBranches,
   getGitlabFolderContent,
+
+  // STACKS
+  listStacks,
+  getStack,
+  getStackRevision,
+  createStack,
+  rollbackStack,
 };