Browse Source

add-ons integrated

Justin Rhee 3 years ago
parent
commit
159d265bd2

+ 0 - 1
dashboard/src/components/form-components/Heading.tsx

@@ -21,7 +21,6 @@ export default function Heading(props: {
 const StyledHeading = styled.div<{ isAtTop: boolean }>`
   color: white;
   margin-top: ${props => props.isAtTop ? "" : "40px"};
-  font-weight: 500;
   font-size: 16px;
   margin-bottom: 5px;
   display: flex;

+ 14 - 2
dashboard/src/components/porter-form/PorterForm.tsx

@@ -31,6 +31,7 @@ import VeleroForm from "./field-components/VeleroForm";
 import CronInput from "./field-components/CronInput";
 import TextAreaInput from "./field-components/TextAreaInput";
 import UrlLink from "./field-components/UrlLink";
+import Button from "components/porter/Button";
 
 interface Props {
   leftTabOptions?: TabOption[];
@@ -44,6 +45,7 @@ interface Props {
   isInModal?: boolean;
   color?: string;
   addendum?: any;
+  buttonStatus?: React.ReactNode;
   saveValuesStatus?: string;
   showStateDebugger?: boolean;
   currentTab: string;
@@ -223,9 +225,9 @@ const PorterForm: React.FC<Props> = (props) => {
         {renderTab()}
       </TabRegion>
       <br />
-      {showSaveButton() && (
+      {(showSaveButton() && props.buttonStatus === undefined) && (
         <SaveButton
-          text={props.saveButtonText || "Deploy app"}
+          text={props.saveButtonText || "Deploy application"}
           onClick={submit}
           absoluteSave={props.absoluteSave}
           clearPosition={true}
@@ -237,6 +239,16 @@ const PorterForm: React.FC<Props> = (props) => {
           disabled={isDisabled()}
         />
       )}
+      {/* TODO: change button when deploying */}
+      {(props.buttonStatus !== undefined) && (
+        <Button
+          onClick={submit}
+          status={props.buttonStatus}
+          disabled={isDisabled()}
+        >
+          Deploy application
+        </Button>
+      )}
       {props.showStateDebugger && (
         <Pre>{JSON.stringify(formState, undefined, 2)}</Pre>
       )}

+ 3 - 0
dashboard/src/components/porter-form/PorterFormWrapper.tsx

@@ -17,6 +17,7 @@ type PropsType = {
   isInModal?: boolean;
   color?: string;
   addendum?: any;
+  buttonStatus?: React.ReactNode;
   saveValuesStatus?: string;
   showStateDebugger?: boolean;
   isLaunch?: boolean;
@@ -41,6 +42,7 @@ const PorterFormWrapper: React.FC<PropsType> = ({
   isInModal,
   color,
   addendum,
+  buttonStatus,
   saveValuesStatus,
   showStateDebugger,
   isLaunch,
@@ -99,6 +101,7 @@ const PorterFormWrapper: React.FC<PropsType> = ({
         <PorterForm
           showStateDebugger={showStateDebugger}
           addendum={addendum}
+          buttonStatus={buttonStatus}
           isReadOnly={isReadOnly}
           leftTabOptions={leftTabOptions}
           rightTabOptions={rightTabOptions}

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

@@ -125,7 +125,6 @@ const StatusWrapper = styled.div<{
 
 const Wrapper = styled.div`
   display: flex;
-  align-items: center;
 `;
 
 const Text = styled.div`
@@ -145,6 +144,7 @@ const StyledButton = styled.button<{
 }>`
   height: ${props => props.height || "35px"};
   width: ${props => props.width || "auto"};
+  min-width: ${props => props.width || ""};
   font-size: 13px;
   cursor: ${props => props.disabled ? "not-allowed" : "pointer"};
   padding: 15px;

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

@@ -1,7 +1,7 @@
 import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 
-import expand from "assets/expand.png";
+import Spacer from "./Spacer";
 import Modal from "./Modal";
 
 type Props = {

+ 152 - 7
dashboard/src/main/home/add-on-dashboard/ConfigureTemplate.tsx

@@ -3,28 +3,172 @@ import styled from "styled-components";
 import _ from "lodash";
 
 import { hardcodedNames, hardcodedIcons } from "shared/hardcodedNameDict";
+import { Context } from "shared/Context";
+import api from "shared/api";
+import { pushFiltered } from "shared/routing";
 
 import Back from "components/porter/Back";
 import DashboardHeader from "../cluster-dashboard/DashboardHeader";
-import { Context } from "shared/Context";
-import api from "shared/api";
+import Link from "components/porter/Link";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import Input from "components/porter/Input";
 import VerticalSteps from "components/porter/VerticalSteps";
+import PorterFormWrapper from "components/porter-form/PorterFormWrapper";
+import Placeholder from "components/Placeholder";
+import Button from "components/porter/Button";
+import { generateSlug } from "random-word-slugs";
+import { RouteComponentProps, withRouter } from "react-router";
+import Error from "components/porter/Error";
 
-type Props = {
-  goBack: () => void;
+type Props = RouteComponentProps & {
   currentTemplate: any;
+  currentForm?: any;
+  goBack: () => void;
 };
 
 const ConfigureTemplate: React.FC<Props> = ({
-  goBack,
   currentTemplate,
+  currentForm,
+  goBack,
+  ...props
 }) => {
+  const { currentCluster, currentProject, capabilities } = useContext(Context);
   const [isLoading, setIsLoading] = useState<boolean>(true);
   const [currentStep, setCurrentStep] = useState<number>(0);
   const [name, setName] = useState<string>("");
+  const [buttonStatus, setButtonStatus] = useState<string>("");
+
+  const waitForHelmRelease = () => {
+    setTimeout(() => {
+      api.getChart(
+        "<token>",
+        {},
+        {
+          id: currentProject.id,
+          namespace: "default",
+          cluster_id: currentCluster.id,
+          name,
+          revision: 0,
+        }
+      )
+        .then((res) => {
+          if (res?.data?.version) {
+            setButtonStatus("success");
+            pushFiltered(props, "/addons", ["project_id"], {
+              cluster: currentCluster.name,
+            });
+          } else {
+            waitForHelmRelease();
+          }
+        })
+        .catch((err) => {
+          waitForHelmRelease();
+        });
+    }, 500);
+  };
+
+  const deployAddOn = async (wildcard?: any) => {
+    setButtonStatus("loading");
+    
+    let values: any = {};
+    for (let key in wildcard) {
+      _.set(values, key, wildcard[key]);
+    }
+
+    api
+      .deployAddon(
+        "<token>",
+        {
+          template_name: currentTemplate.name,
+          template_version: "latest",
+          values: values,
+          name,
+        },
+        {
+          id: currentProject.id,
+          cluster_id: currentCluster.id,
+          namespace: "default",
+          repo_url: currentTemplate?.repo_url || capabilities.default_addon_helm_repo_url,
+        }
+      )
+      .then((_) => {
+        window.analytics?.track("Deployed Add-on", {
+          name: currentTemplate.name,
+          namespace: "default",
+          values: values,
+        });
+        waitForHelmRelease();
+      })
+      .catch((err) => {
+        let parsedErr = err?.response?.data?.error;
+        err = parsedErr || err.message || JSON.stringify(err);
+        setButtonStatus(err);
+        window.analytics?.track("Failed to Deploy Add-on", {
+          name: currentTemplate.name,
+          namespace: "default",
+          values: values,
+          error: err,
+        });
+        return;
+      });
+  };
+
+  const getStatus = () => {
+    if (!buttonStatus) {
+      return;
+    }
+    if (buttonStatus === "loading" || buttonStatus === "success") {
+      return buttonStatus;
+    } else {
+      return (
+        <Error message={buttonStatus} />
+      );
+    }
+  };
+  
+  const renderAddOnSettings = () => {
+    if (currentForm) {
+      return (
+        <PorterFormWrapper
+          formData={currentForm}
+          valuesToOverride={{
+            namespace: "default",
+            clusterId: currentCluster.id,
+          }}
+          buttonStatus={getStatus()}
+          isLaunch={true}
+          onSubmit={deployAddOn}
+        />
+      );
+    } else {
+      return (
+        <>
+          <Placeholder>
+            <div>
+              To configure this chart through Porter
+              <Spacer inline width="5px" />
+              <Link
+                target="_blank"
+                to="https://github.com/porter-dev/porter-charts/blob/master/docs/form-yaml-reference.md"
+              >
+                refer to our docs
+              </Link>
+              .
+            </div>
+          </Placeholder>
+          <Spacer y={1.2} />
+          <Button
+            width="150px"
+            onClick={deployAddOn}
+            status={getStatus()}
+          >
+            Deploy application
+          </Button>
+        </>
+      );
+    };
+  };
 
   return (
     <StyledConfigureTemplate>
@@ -71,15 +215,16 @@ const ConfigureTemplate: React.FC<Props> = ({
             Configure settings for this add-on.
             </Text>
             <Spacer height="20px" />
-            THIS IS WHERE THE FORM GOES
+            {renderAddOnSettings()}
           </>
         ]}
       />
+      <Spacer height="80px" />
     </StyledConfigureTemplate>
   );
 };
 
-export default ConfigureTemplate;
+export default withRouter(ConfigureTemplate);
 
 const DarkMatter = styled.div`
   width: 100%;

+ 4 - 1
dashboard/src/main/home/add-on-dashboard/ExpandedTemplate.tsx

@@ -94,7 +94,10 @@ const ExpandedTemplate: React.FC<Props> = ({
               <Markdown>{markdown}</Markdown>
             </MarkdownWrapper>
           ) : (
-            <Text>{currentTemplate.description}</Text>
+            <>
+              <Spacer y={0.5} />
+              <Text>{currentTemplate.description}</Text>
+            </>
           )
         )
       }

+ 1 - 2
dashboard/src/main/home/add-on-dashboard/NewAddOnFlow.tsx

@@ -4,14 +4,12 @@ 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";
 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";
@@ -90,6 +88,7 @@ const NewAddOnFlow: React.FC<Props> = ({
         (currentForm && currentTemplate) ? (
           <ConfigureTemplate
             currentTemplate={currentTemplate}
+            currentForm={currentForm}
             goBack={() => setCurrentForm(null)}
           />
         ) : (

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -190,7 +190,7 @@ const SettingsSection: React.FC<PropsType> = ({
 
   const renderWebhookSection = () => {
     if (!currentChart?.form?.hasSource) {
-      return;
+      return <DarkMatter />;
     }
 
     const protocol = window.location.protocol == "https:" ? "https" : "http";
@@ -371,7 +371,7 @@ export default SettingsSection;
 const DarkMatter = styled.div`
   width: 100%;
   height: 0;
-  margin-top: -10px;
+  margin-top: -40px;
 `;
 
 const Br = styled.div`