Justin Rhee 3 роки тому
батько
коміт
732871c271

+ 2 - 2
dashboard/src/components/SaveButton.tsx

@@ -80,7 +80,7 @@ const SaveButton: React.FC<Props> = (props) => {
         rounded={props.rounded}
         disabled={props.disabled}
         onClick={props.onClick}
-        color={props.color || "#5561C0"}
+        color={props.color}
       >
         {props.children || props.text}
       </Button>
@@ -199,7 +199,7 @@ const Button = styled.button<{
   text-align: left;
   border: 0;
   border-radius: ${(props) => (props.rounded ? "100px" : "5px")};
-  background: ${(props) => (!props.disabled ? props.color : "#aaaabb")};
+  background: ${(props) => (!props.disabled ? (props.color || props.theme.button) : "#aaaabb")};
   cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
   user-select: none;
   :focus {

+ 73 - 0
dashboard/src/components/porter/Back.tsx

@@ -0,0 +1,73 @@
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+
+import leftArrow from "assets/left-arrow.svg";
+import Text from "./Text";
+import Container from "./Container";
+import { Link } from "react-router-dom";
+
+type Props = {
+  to?: string;
+  onClick?: () => void;
+};
+
+const Back: React.FC<Props> = ({
+  to,
+  onClick,
+}) => {
+  return (
+    <Container row>
+      {to ? (
+        <BackLink to={to}>
+          <ArrowIcon src={leftArrow} />
+          Back
+        </BackLink>
+      ) : (
+        <StyledBack onClick={onClick}>
+          <ArrowIcon src={leftArrow} />
+          Back
+        </StyledBack>
+      )}
+    </Container>
+  );
+};
+
+export default Back;
+
+const ArrowIcon = styled.img`
+  width: 15px;
+  margin-right: 8px;
+  opacity: 50%;
+`;
+
+const BackLink = styled(Link)`
+  color: #aaaabb88;
+  font-size: 13px;
+  margin-bottom: 15px;
+  display: flex;
+  margin-top: -10px;
+  z-index: 999;
+  padding: 5px;
+  padding-right: 7px;
+  border-radius: 5px;
+  cursor: pointer;
+  :hover {
+    background: #ffffff11;
+  }
+`;
+
+const StyledBack = styled.div`
+color: #aaaabb88;
+font-size: 13px;
+margin-bottom: 15px;
+display: flex;
+margin-top: -10px;
+z-index: 999;
+padding: 5px;
+padding-right: 7px;
+border-radius: 5px;
+cursor: pointer;
+:hover {
+  background: #ffffff11;
+}
+`;

+ 1 - 1
dashboard/src/components/porter/Button.tsx

@@ -155,7 +155,7 @@ const StyledButton = styled.button<{
     if (props.alt) {
       return props.theme.fg;
     }
-    return (props.disabled && !props.color) ? "#aaaabb" : (props.color || "#5561C0");
+    return (props.disabled && !props.color) ? "#aaaabb" : (props.color || props.theme.button);
   }};
   display: flex;
   ailgn-items: center;

+ 86 - 0
dashboard/src/components/porter/VerticalSteps.tsx

@@ -0,0 +1,86 @@
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+
+type Props = {
+  steps: React.ReactNode[];
+  currentStep: number;
+};
+
+const VerticalSteps: React.FC<Props> = ({
+  steps,
+  currentStep,
+}) => {
+  const [isExpanded, setIsExpanded] = useState(false);
+
+  return (
+    <StyledVerticalSteps>
+      {steps.map((step, i) => {
+        return (
+          <StepWrapper isActive={i <= currentStep}>
+            {
+              (i !== steps.length - 1) && (
+                <Line isActive={i + 1 <= currentStep} />
+              )
+            }
+            <Dot 
+              isActive={i <= currentStep}
+            />
+            {step}
+            {
+              i > currentStep && (
+                <ReadOnlyOverlay />
+              )
+            }
+          </StepWrapper>
+        );
+      })}
+    </StyledVerticalSteps>
+  );
+};
+
+export default VerticalSteps;
+
+const ReadOnlyOverlay = styled.div`
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+  z-index: 999;
+`;
+
+const Line = styled.div<{
+  isActive: boolean;
+}>`
+  width: 1px;
+  height: calc(100% + 35px);
+  background: ${props => props.isActive ? "#fff" : "#ffffff33"};
+  position: absolute;
+  left: 4px;
+  top: 8px;
+`;
+
+const Dot = styled.div<{
+  isActive: boolean;
+}>`
+  width: 9px;
+  height: 9px;
+  background: ${props => props.isActive ? "#fff" : "#ffffff33"};
+  border-radius: 50%;
+  position: absolute;
+  left: 0;
+  top: 7px;
+`;
+
+const StepWrapper = styled.div<{
+  isActive: boolean;
+}>`
+  padding-left: 22px;
+  position: relative;
+  margin-bottom: 35px;
+  opacity: ${props => props.isActive ? 1 : 0.5};
+`;
+
+const StyledVerticalSteps = styled.div<{
+}>`
+`;

+ 13 - 9
dashboard/src/main/home/add-on-dashboard/AddOnDashboard.tsx

@@ -18,6 +18,7 @@ import list from "assets/list.png";
 import { Context } from "shared/Context";
 import { search } from "shared/search";
 import api from "shared/api";
+import { hardcodedIcons } from "shared/hardcodedNameDict";
 
 import DashboardHeader from "../cluster-dashboard/DashboardHeader";
 
@@ -34,13 +35,6 @@ import { Link } from "react-router-dom";
 type Props = {
 };
 
-const icons = [
-  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/ruby/ruby-plain.svg",
-  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nodejs/nodejs-plain.svg",
-  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/python/python-plain.svg",
-  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/go/go-original-wordmark.svg",
-];
-
 const namespaceBlacklist = [
   "cert-manager",
   "ingress-nginx",
@@ -174,7 +168,12 @@ const AppDashboard: React.FC<Props> = ({
               return (
                 <Block to={getExpandedChartLinkURL(app)}>
                   <Text size={14}>
-                    <Icon src={app.chart.metadata.icon} />
+                    <Icon 
+                      src={
+                        hardcodedIcons[app.chart.metadata.name] ||
+                        app.chart.metadata.icon
+                      }
+                    />
                     {app.name}
                   </Text>
                   <StatusIcon src={healthy} />
@@ -197,7 +196,12 @@ const AppDashboard: React.FC<Props> = ({
               return (
                 <Row to={getExpandedChartLinkURL(app)}>
                   <Text size={14}>
-                    <MidIcon src={app.chart.metadata.icon} />
+                    <MidIcon
+                      src={
+                        hardcodedIcons[app.chart.metadata.name] ||
+                        app.chart.metadata.icon
+                      }
+                    />
                     {app.name}
                     <Spacer inline x={1} />
                     <MidIcon src={healthy} height="16px" />

+ 110 - 0
dashboard/src/main/home/add-on-dashboard/ConfigureTemplate.tsx

@@ -0,0 +1,110 @@
+import React, { useEffect, useState, useContext, useMemo } from "react";
+import styled from "styled-components";
+import _ from "lodash";
+
+import { hardcodedNames, hardcodedIcons } from "shared/hardcodedNameDict";
+
+import Back from "components/porter/Back";
+import DashboardHeader from "../cluster-dashboard/DashboardHeader";
+import { Context } from "shared/Context";
+import api from "shared/api";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
+import Input from "components/porter/Input";
+import VerticalSteps from "components/porter/VerticalSteps";
+
+type Props = {
+  goBack: () => void;
+  currentTemplate: any;
+};
+
+const ConfigureTemplate: React.FC<Props> = ({
+  goBack,
+  currentTemplate,
+}) => {
+  const [isLoading, setIsLoading] = useState<boolean>(true);
+  const [currentStep, setCurrentStep] = useState<number>(0);
+  const [name, setName] = useState<string>("");
+
+  return (
+    <StyledConfigureTemplate>
+      <Back onClick={goBack} />
+      <DashboardHeader
+        prefix={
+          <Icon 
+            src={hardcodedIcons[currentTemplate.name] || currentTemplate.icon}
+          />
+        }
+        title={`Configure new ${hardcodedNames[currentTemplate.name] || currentTemplate.name} instance`}
+        capitalize={false}
+        disableLineBreak
+      />
+      <DarkMatter />
+      <VerticalSteps
+        currentStep={currentStep}
+        steps={[
+          <>
+            <Text size={16}>Add-on name</Text>
+            <Spacer y={0.5} />
+            <Text color="helper">
+              Randomly generated if left blank (lowercase letters, numbers, and "-" only).
+            </Text>
+            <Spacer height="20px" />
+            <Input
+              placeholder="ex: academic-sophon"
+              value={name}
+              width="300px"
+              setValue={(e) => {
+                if (e) {
+                  setCurrentStep(1);
+                } else {
+                  setCurrentStep(0);
+                }
+                setName(e);
+              }}
+            />
+          </>,
+          <>
+            <Text size={16}>Add-on settings</Text>
+            <Spacer y={0.5} />
+            <Text color="helper">
+            Configure settings for this add-on.
+            </Text>
+            <Spacer height="20px" />
+            THIS IS WHERE THE FORM GOES
+          </>
+        ]}
+      />
+    </StyledConfigureTemplate>
+  );
+};
+
+export default ConfigureTemplate;
+
+const DarkMatter = styled.div`
+  width: 100%;
+  margin-top: -5px;
+`;
+
+const Icon = styled.img`
+  margin-right: 15px;
+  height: 30px;
+  animation: floatIn 0.5s;
+  animation-fill-mode: forwards;
+
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(20px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
+`;
+
+const StyledConfigureTemplate = styled.div`
+  width: 100%;
+  height: 100%;
+`;

+ 117 - 18
dashboard/src/main/home/add-on-dashboard/ExpandedTemplate.tsx

@@ -1,57 +1,156 @@
 import React, { useEffect, useState, useContext, useMemo } from "react";
 import styled from "styled-components";
-import DashboardHeader from "../cluster-dashboard/DashboardHeader";
-import semver from "semver";
 import _ from "lodash";
 
-import addOn from "assets/add-ons.png";
-
 import { Context } from "shared/Context";
 import api from "shared/api";
-import { search } from "shared/search";
 
-import Link from "components/porter/Link";
-import TemplateList from "../launch/TemplateList";
-import SearchBar from "components/porter/SearchBar";
 import Spacer from "components/porter/Spacer";
 import Loading from "components/Loading";
 import Button from "components/porter/Button";
+import Container from "components/porter/Container";
+import Text from "components/porter/Text";
+import Markdown from "markdown-to-jsx";
+
+import { hardcodedNames, hardcodedIcons } from "shared/hardcodedNameDict";
 
 type Props = {
   currentTemplate: any;
+  proceed: (form?: any) => void;
   goBack: () => void;
 };
 
 const ExpandedTemplate: React.FC<Props> = ({
   currentTemplate,
+  proceed,
   goBack,
 }) => {
   const { capabilities, currentProject } = useContext(Context);
   const [isLoading, setIsLoading] = useState<boolean>(true);
-  const [searchValue, setSearchValue] = useState("");
-  const [addOnTemplates, setAddOnTemplates] = useState<any[]>([]);
+  const [form, setForm] = useState<any>(null);
+  const [values, setValues] = useState("");
+  const [markdown, setMarkdown] = useState<any>(null);
+  const [keywords, setKeywords] = useState<any[]>([]);
+
+  const getTemplateInfo = async () => {
+    setIsLoading(true);
+    let params = {
+      repo_url: capabilities?.default_addon_helm_repo_url,
+    };
+
+    api.getTemplateInfo("<token>", params, {
+      project_id: currentProject.id,
+      name: currentTemplate.name.toLowerCase().trim(),
+      version: currentTemplate.currentVersion,
+    })
+      .then((res) => {
+        let { form, values, markdown, metadata } = res.data;
+        let keywords = metadata.keywords;
+        setForm(form);
+        setValues(values);
+        setMarkdown(markdown);
+        setKeywords(keywords);
+        setIsLoading(false);
+      })
+      .catch((err) => {
+        setIsLoading(false);
+      });
+  }
+
+  useEffect(() => {
+    getTemplateInfo();
+  }, [currentTemplate]);
 
   return (
     <StyledExpandedTemplate>
-      <Button 
-        onClick={goBack}
-        alt
-      >
-        <I className="material-icons">first_page</I>
-        <Spacer inline x={1} />
-        Select template
-      </Button>
+      <Container row spaced>
+        <Container row>
+          <Button 
+            onClick={goBack}
+            alt
+          >
+            <I className="material-icons">first_page</I>
+            <Spacer inline x={1} />
+            Select template
+          </Button>
+          <Spacer x={1} inline />
+          <Icon src={hardcodedIcons[currentTemplate.name] || currentTemplate.icon} />
+          <Text size={16}>
+            <Capitalize>
+              {hardcodedNames[currentTemplate.name] || currentTemplate.name}
+            </Capitalize>
+          </Text>
+        </Container>
+        <Button onClick={() => proceed(form)}>
+          <AddI className="material-icons">add</AddI>
+          Deploy add-on
+        </Button>
+      </Container>
+      <Spacer height="15px" />
+      {
+        isLoading ? <Loading offset="-150px" /> : (
+          markdown ? (
+            <MarkdownWrapper>
+              <Markdown>{markdown}</Markdown>
+            </MarkdownWrapper>
+          ) : (
+            <Text>{currentTemplate.description}</Text>
+          )
+        )
+      }
     </StyledExpandedTemplate>
   );
 };
 
 export default ExpandedTemplate;
 
+const MarkdownWrapper = styled.div`
+  font-size: 13px;
+  line-height: 1.5;
+  color: #aaaabb;
+  > div {
+    > h1 {
+      color: ${({ theme }) => theme.text.primary};
+      font-size: 16px;
+      font-weight: 400;
+    }
+    > h2 {
+      color: ${({ theme }) => theme.text.primary};
+      font-size: 16px;
+      font-weight: 400;
+    }
+    > h3 {
+      color: ${({ theme }) => theme.text.primary};
+      font-size: 16px;
+      font-weight: 400;
+    }
+  }
+  padding-bottom: 80px;
+`;
+
+const Icon = styled.img`
+  height: 22px;
+  margin-right: 15px;
+`;
+
+const Capitalize = styled.span`
+  text-transform: capitalize;
+`;
+
 const I = styled.i`
   color: white;
   font-size: 16px;
 `;
 
+const AddI = styled.i`
+  color: white;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  margin-right: 10px;
+  justify-content: center;
+`;
+
 const StyledExpandedTemplate = styled.div`
   width: 100%;
   height: 100%;

+ 46 - 30
dashboard/src/main/home/add-on-dashboard/NewAddOnFlow.tsx

@@ -4,6 +4,7 @@ import DashboardHeader from "../cluster-dashboard/DashboardHeader";
 import semver from "semver";
 import _ from "lodash";
 
+import leftArrow from "assets/left-arrow.svg";
 import addOn from "assets/add-ons.png";
 
 import { Context } from "shared/Context";
@@ -16,6 +17,8 @@ import SearchBar from "components/porter/SearchBar";
 import Spacer from "components/porter/Spacer";
 import Loading from "components/Loading";
 import ExpandedTemplate from "./ExpandedTemplate";
+import ConfigureTemplate from "./ConfigureTemplate";
+import Back from "components/porter/Back";
 
 type Props = {
 };
@@ -29,6 +32,7 @@ const NewAddOnFlow: React.FC<Props> = ({
   const [searchValue, setSearchValue] = useState("");
   const [addOnTemplates, setAddOnTemplates] = useState<any[]>([]);
   const [currentTemplate, setCurrentTemplate] = useState<any>(null);
+  const [currentForm, setCurrentForm] = useState<any>(null);
 
   const filteredTemplates = useMemo(() => {
     const filteredBySearch = search(
@@ -82,43 +86,55 @@ const NewAddOnFlow: React.FC<Props> = ({
 
   return (
     <StyledTemplateComponent>
-      <DashboardHeader
-        prefix={(
-          <Link to="/addons">
-            <I className="material-icons">keyboard_backspace</I>
-          </Link>
-        )}
-        image={addOn}
-        title="Deploy a new add-on"
-        capitalize={false}
-        description="Create a new add-ons for this project."
-        disableLineBreak
-      />
-      <SearchBar 
-        value={searchValue}
-        setValue={setSearchValue}
-        placeholder="Search available add-ons . . ."
-        width="100%"
-      />
-      <Spacer y={1} />
-
-      {/* Temporary space reducer for legacy template list */}
-      {isLoading ? <Loading offset="-150px" /> : (
-        currentTemplate ? (
-          <ExpandedTemplate 
+      {
+        (currentForm && currentTemplate) ? (
+          <ConfigureTemplate
             currentTemplate={currentTemplate}
-            goBack={() => setCurrentTemplate(null)}
+            goBack={() => setCurrentForm(null)}
           />
         ) : (
           <>
-            <DarkMatter />
-            <TemplateList
-              templates={filteredTemplates}
-              setCurrentTemplate={(x) => setCurrentTemplate(x)}
+            <Back to="/addons" />
+            <DashboardHeader
+              image={addOn}
+              title="Deploy a new add-on"
+              capitalize={false}
+              description="Select an add-on to deploy to this project."
+              disableLineBreak
             />
+            {
+              currentTemplate ? (
+                <ExpandedTemplate 
+                  currentTemplate={currentTemplate}
+                  proceed={(form?: any) => setCurrentForm(form)}
+                  goBack={() => setCurrentTemplate(null)}
+                />
+              ) : (
+                <>
+                  <SearchBar 
+                    value={searchValue}
+                    setValue={setSearchValue}
+                    placeholder="Search available add-ons . . ."
+                    width="100%"
+                  />
+                  <Spacer y={1} />
+
+                  {/* Temporary space reducer for legacy template list */}
+                  {isLoading ? <Loading offset="-150px" /> : (
+                    <>
+                      <DarkMatter />
+                      <TemplateList
+                        templates={filteredTemplates}
+                        setCurrentTemplate={(x) => setCurrentTemplate(x)}
+                      />
+                    </>
+                  )}
+                </>
+              )
+            }
           </>
         )
-      )}
+      }
     </StyledTemplateComponent>
   );
 };

+ 4 - 1
dashboard/src/main/home/launch/TemplateList.tsx

@@ -4,7 +4,7 @@ import api from "shared/api";
 import styled from "styled-components";
 
 import Loading from "components/Loading";
-import { hardcodedNames } from "shared/hardcodedNameDict";
+import { hardcodedIcons, hardcodedNames } from "shared/hardcodedNameDict";
 import { PorterTemplate } from "shared/types";
 import semver from "semver";
 
@@ -113,6 +113,9 @@ const TemplateList: React.FC<Props> = ({
     if (name === "job") {
       return <NewIcon src={job} />;
     }
+    if (hardcodedIcons[name]) {
+      return <Icon src={hardcodedIcons[name]} />;
+    }
     if (icon) {
       return <Icon src={icon} />;
     }

+ 13 - 4
dashboard/src/shared/hardcodedNameDict.tsx

@@ -9,6 +9,7 @@ const hardcodedNames: { [key: string]: string } = {
   mysql: "MySQL",
   postgresql: "PostgreSQL",
   redis: "Redis",
+  "node-local": "Node Local DNS",
   ubuntu: "Ubuntu",
   web: "Web Service",
   worker: "Worker",
@@ -19,6 +20,11 @@ const hardcodedNames: { [key: string]: string } = {
   rabbitmq: "RabbitMQ",
   logdna: "LogDNA",
   "tailscale-relay": "Tailscale",
+  questdb: "QuestDB",
+  "postgres-toolbox": "PostgreSQL Toolbox",
+  keda: "KEDA",
+  "grafana-agent": "Grafana Agent",
+  "ecr-secrets-updater": "ECR Secrets Updater",
 };
 
 const hardcodedIcons: { [key: string]: string } = {
@@ -27,14 +33,14 @@ const hardcodedIcons: { [key: string]: string } = {
   metabase:
     "https://pbs.twimg.com/profile_images/961380992727465985/4unoiuHt.jpg",
   mongodb:
-    "https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png",
+    "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/mongodb/mongodb-original.svg",
   datadog: "https://datadog-live.imgix.net/img/dd_logo_70x75.png",
   wallarm:
     "https://assets.website-files.com/5fe3434623c64c793987363d/6006cb97f71f76f8a5e85a32_Frame%201923.png",
   agones: "https://avatars.githubusercontent.com/u/36940055?v=4",
   mysql: "https://www.mysql.com/common/logos/logo-mysql-170x115.png",
   postgresql:
-    "https://bitnami.com/assets/stacks/postgresql/img/postgresql-stack-110x117.png",
+    "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/postgresql/postgresql-original.svg",
   redis:
     "https://cdn4.iconfinder.com/data/icons/redis-2/1451/Untitled-2-512.png",
   ubuntu: "Ubuntu",
@@ -51,10 +57,13 @@ const hardcodedIcons: { [key: string]: string } = {
   prometheus:
     "https://raw.githubusercontent.com/prometheus/prometheus.github.io/master/assets/prometheus_logo-cb55bb5c346.png",
   rabbitmq:
-    "https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png",
+    "https://static-00.iconduck.com/assets.00/rabbitmq-icon-484x512-s9lfaapn.png",
   logdna:
     "https://user-images.githubusercontent.com/65516095/118185526-a2447480-b40a-11eb-9bdb-82aa0a306f26.png",
-  "tailscale-relay": "Tailscale",
+  "node-local": "https://hostingdata.co.uk/wp-content/uploads/2020/06/dns-png-6.png",
+  "tailscale-relay": "https://play-lh.googleusercontent.com/wczDL05-AOb39FcL58L32h6j_TrzzGTXDLlOrOmJ-aNsnoGsT1Gkk2vU4qyTb7tGxRw=w240-h480-rw",
+  "postgres-toolbox": "https://cdn-icons-png.flaticon.com/512/5133/5133626.png",
+  "ecr-secrets-updater": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/amazonwebservices/amazonwebservices-original.svg",
 };
 
 export { hardcodedNames, hardcodedIcons };

+ 1 - 0
dashboard/src/shared/themes/midnight.ts

@@ -2,6 +2,7 @@ const theme = {
   bg: "#121212",
   fg: "#171B21",
   border: "#494b4f",
+  button: "#3A48CA",
   clickable: {
     bg: "linear-gradient(180deg, #171B21, #121212)",
   },

+ 1 - 0
dashboard/src/shared/themes/standard.ts

@@ -1,6 +1,7 @@
 const theme = {
   bg: "#202227",
   fg: "#27292e",
+  button: "#5561C0",
   clickable: {
     bg: "linear-gradient(180deg, #26292e, #24272c)",
   },