Procházet zdrojové kódy

merge basic app placeholder flow

Justin Rhee před 3 roky
rodič
revize
c15e90a0c8

+ 5 - 6
.github/workflows/prerelease.yaml

@@ -15,7 +15,7 @@ jobs:
       - name: Checkout
       - name: Checkout
         uses: actions/checkout@v3
         uses: actions/checkout@v3
       - name: Setup docker
       - name: Setup docker
-        uses: docker/login-action@v1
+        uses: docker/login-action@v2
         with:
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -234,7 +234,7 @@ jobs:
           unzip ./release/UNSIGNED_portersvr_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
           unzip ./release/UNSIGNED_portersvr_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
           unzip ./release/UNSIGNED_docker-credential-porter_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
           unzip ./release/UNSIGNED_docker-credential-porter_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
       - name: Import Code-Signing Certificates
       - name: Import Code-Signing Certificates
-        uses: Apple-Actions/import-codesign-certs@v1
+        uses: Apple-Actions/import-codesign-certs@v2
         with:
         with:
           # The certificates in a PKCS12 file encoded as a base64 string
           # The certificates in a PKCS12 file encoded as a base64 string
           p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
           p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
@@ -339,12 +339,11 @@ jobs:
           path: release/darwin
           path: release/darwin
       - name: Create Release
       - name: Create Release
         id: create_release
         id: create_release
-        uses: actions/create-release@v1
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        uses: softprops/action-gh-release@v1
         with:
         with:
           tag_name: ${{ github.ref }}
           tag_name: ${{ github.ref }}
-          release_name: Release ${{ github.ref }}
+          name: Release ${{ github.ref }}
+          token: ${{ secrets.GITHUB_TOKEN }}
           draft: false
           draft: false
           prerelease: true
           prerelease: true
       - name: Upload Linux CLI Release Asset
       - name: Upload Linux CLI Release Asset

+ 1 - 1
.github/workflows/release.yaml

@@ -24,7 +24,7 @@ jobs:
         env:
         env:
           GITHUB_TAG: ${{ github.ref }}
           GITHUB_TAG: ${{ github.ref }}
       - name: Setup docker
       - name: Setup docker
-        uses: docker/login-action@v1
+        uses: docker/login-action@v2
         with:
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}

+ 3 - 3
dashboard/src/components/ProvisionerSettings.tsx

@@ -243,8 +243,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
   useEffect(() => {
   useEffect(() => {
     setIsReadOnly(
     setIsReadOnly(
       props.clusterId &&
       props.clusterId &&
-        (currentCluster.status === "UPDATING" ||
-          currentCluster.status === "UPDATING_UNAVAILABLE")
+      (currentCluster.status === "UPDATING" ||
+        currentCluster.status === "UPDATING_UNAVAILABLE")
     );
     );
     setClusterName(
     setClusterName(
       `${currentProject.name}-cluster-${Math.random()
       `${currentProject.name}-cluster-${Math.random()
@@ -392,7 +392,7 @@ const ExpandHeader = styled.div<{ isExpanded: boolean }>`
     margin-right: 7px;
     margin-right: 7px;
     margin-left: -7px;
     margin-left: -7px;
     transform: ${(props) =>
     transform: ${(props) =>
-      props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
+    props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
   }
   }
 `;
 `;
 
 

+ 2 - 1
dashboard/src/components/porter/VerticalSteps.tsx

@@ -22,7 +22,7 @@ const VerticalSteps: React.FC<Props> = ({
                 <Line isActive={i + 1 <= currentStep} />
                 <Line isActive={i + 1 <= currentStep} />
               )
               )
             }
             }
-            <Dot 
+            <Dot
               isActive={i <= currentStep}
               isActive={i <= currentStep}
             />
             />
             <OpacityWrapper isActive={i <= currentStep}>
             <OpacityWrapper isActive={i <= currentStep}>
@@ -91,4 +91,5 @@ const StepWrapper = styled.div<{
 
 
 const StyledVerticalSteps = styled.div<{
 const StyledVerticalSteps = styled.div<{
 }>`
 }>`
+width: 600px;
 `;
 `;

+ 15 - 15
dashboard/src/main/home/Home.tsx

@@ -37,7 +37,7 @@ import Modal from "components/porter/Modal";
 import Text from "components/porter/Text";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import Spacer from "components/porter/Spacer";
 import Button from "components/porter/Button";
 import Button from "components/porter/Button";
-import NewAppFlow from "./app-dashboard/NewAppFlow";
+import NewAppFlow from "./app-dashboard/new-app-flow/NewAppFlow";
 
 
 // Guarded components
 // Guarded components
 const GuardedProjectSettings = fakeGuardedRoute("settings", "", [
 const GuardedProjectSettings = fakeGuardedRoute("settings", "", [
@@ -190,7 +190,7 @@ const Home: React.FC<Props> = (props) => {
       } else {
       } else {
         setHasFinishedOnboarding(true);
         setHasFinishedOnboarding(true);
       }
       }
-    } catch (error) {}
+    } catch (error) { }
   };
   };
 
 
   useEffect(() => {
   useEffect(() => {
@@ -436,17 +436,17 @@ const Home: React.FC<Props> = (props) => {
               overrideInfraTabEnabled({
               overrideInfraTabEnabled({
                 projectID: currentProject?.id,
                 projectID: currentProject?.id,
               })) && (
               })) && (
-              <Route
-                path="/infrastructure"
-                render={() => {
-                  return (
-                    <DashboardWrapper>
-                      <InfrastructureRouter />
-                    </DashboardWrapper>
-                  );
-                }}
-              />
-            )}
+                <Route
+                  path="/infrastructure"
+                  render={() => {
+                    return (
+                      <DashboardWrapper>
+                        <InfrastructureRouter />
+                      </DashboardWrapper>
+                    );
+                  }}
+                />
+              )}
             <Route
             <Route
               path="/dashboard"
               path="/dashboard"
               render={() => {
               render={() => {
@@ -515,14 +515,14 @@ const Home: React.FC<Props> = (props) => {
           />,
           />,
           document.body
           document.body
         )}
         )}
-        {showWrongEmailModal && 
+        {showWrongEmailModal &&
           <Modal>
           <Modal>
             <Text size={16}>
             <Text size={16}>
               Oops! This invite link wasn't for {user?.email}
               Oops! This invite link wasn't for {user?.email}
             </Text>
             </Text>
             <Spacer y={1} />
             <Spacer y={1} />
             <Text color="helper">
             <Text color="helper">
-              Your account email does not match the email associated with this project invite. 
+              Your account email does not match the email associated with this project invite.
               Please log out and sign up again with the correct email using the invite link.
               Please log out and sign up again with the correct email using the invite link.
             </Text>
             </Text>
             <Spacer y={1} />
             <Spacer y={1} />

+ 0 - 179
dashboard/src/main/home/app-dashboard/NewAppFlow.tsx

@@ -1,179 +0,0 @@
-import React, { useEffect, useState, useContext, useMemo } from "react";
-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 web from "assets/web.png";
-
-import Back from "components/porter/Back";
-import DashboardHeader from "../cluster-dashboard/DashboardHeader";
-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 = RouteComponentProps & {
-};
-
-const NewAppFlow: React.FC<Props> = ({
-  ...props
-}) => {
-  const { currentCluster, currentProject } = useContext(Context);
-  const [isLoading, setIsLoading] = useState<boolean>(true);
-  const [currentStep, setCurrentStep] = useState<number>(1);
-
-  return (
-    <StyledConfigureTemplate>
-      <Back to="/apps" />
-      <DashboardHeader
-        prefix={
-          <Icon 
-            src={web}
-          />
-        }
-        title="Deploy a new application"
-        capitalize={false}
-        disableLineBreak
-      />
-      <DarkMatter />
-      <VerticalSteps
-        currentStep={currentStep}
-        steps={[
-          <>
-            <Text size={16}>Application 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=""
-              width="300px"
-              setValue={(e) => {}}
-            />
-          </>,
-          <>
-            <Text size={16}>Hello world</Text>
-            <Spacer y={0.5} />
-            <Text color="helper">
-            Foo bar foo bar.
-            </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=""
-              width="300px"
-              setValue={(e) => {}}
-            />
-            <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=""
-              width="300px"
-              setValue={(e) => {}}
-            />
-          </>,
-          <>
-            <Text size={16}>Some settings</Text>
-            <Spacer y={0.5} />
-            <Text color="helper">
-            Configure settings for this add-on.
-            </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=""
-              width="300px"
-              setValue={(e) => {}}
-            />
-            <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=""
-              width="300px"
-              setValue={(e) => {}}
-            />
-            <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=""
-              width="300px"
-              setValue={(e) => {}}
-            />
-          </>,
-          <>
-            <Text size={16}>More configuration</Text>
-            <Spacer y={0.5} />
-            <Text color="helper">
-            Configure settings for this add-on.
-            </Text>
-            <Spacer y={1} />
-            SOME MORE STUFF HERE
-          </>
-        ]}
-      />
-      <Spacer height="80px" />
-    </StyledConfigureTemplate>
-  );
-};
-
-export default withRouter(NewAppFlow);
-
-const DarkMatter = styled.div`
-  width: 100%;
-  margin-top: -5px;
-`;
-
-const Icon = styled.img`
-  margin-right: 15px;
-  height: 28px;
-  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%;
-`;

+ 161 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/AdvancedBuildSettings.tsx

@@ -0,0 +1,161 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
+import Input from "components/porter/Input";
+import Toggle from "components/porter/Toggle";
+
+interface AdvancedBuildSettingsProps {
+
+}
+
+
+const AdvancedBuildSettings: React.FC<AdvancedBuildSettingsProps> = ({
+}) => {
+    const [showSettings, setShowSettings] = useState(false)
+    const [buildView, setBuildView] = useState<string>('docker')
+
+    const createDockerView = () => {
+        return (
+            <>
+                <Text size={16}>Build settings</Text>
+                <Spacer y={0.5} />
+                <Text color="helper">
+                    Select your Github repository.
+                </Text>
+                <Spacer height="20px" />
+                <Input
+                    placeholder="ex: academic-sophon"
+                    value=""
+                    width="300px"
+                    setValue={(e) => { }}
+                />
+                <Spacer y={0.5} />
+                <Text color="helper">
+                    Select your branch.
+                </Text>
+                <Spacer y={0.5} />
+                <Input
+                    placeholder="ex: academic-sophon"
+                    value=""
+                    width="300px"
+                    setValue={(e) => { }}
+                />
+                <Spacer y={0.5} />
+                <Text color="helper">
+                    Specify your application root path.
+                </Text>
+                <Spacer y={0.5} />
+                <Input
+                    placeholder="ex: academic-sophon"
+                    value="./"
+                    width="300px"
+                    setValue={(e) => { }}
+                />
+                <Spacer y={1} />
+            </>
+        )
+    }
+    return (
+        <>
+            <StyledAdvancedBuildSettings showSettings={showSettings} isCurrent={true} onClick={() => setShowSettings(!showSettings)} >
+                <AdvancedBuildTitle>
+                    <i className="material-icons">arrow_drop_down</i>
+                    Advanced build settings (optional)
+                </AdvancedBuildTitle>
+                <div>
+                    <DetectedBuildMessage>
+                        <i className="material-icons">check</i>
+                        Detected Dockerfile
+                    </DetectedBuildMessage>
+                </div>
+            </StyledAdvancedBuildSettings>
+            {showSettings &&
+                <StyledSourceBox>
+                    <ToggleWrapper>
+                        <Toggle items={[
+                            { label: 'Docker', value: "docker" },
+                            { label: 'Buildpacks', value: "buildpacks" },
+                        ]}
+                            active={buildView}
+                            setActive={setBuildView}
+                        />
+                    </ToggleWrapper>
+                    {buildView === 'docker' && createDockerView()}
+                </StyledSourceBox>
+            }
+        </>
+    );
+}
+
+export default AdvancedBuildSettings
+
+const StyledAdvancedBuildSettings = styled.div`
+  color:  "#ffffff66";
+  background: #26292e;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-radius: 5px;
+  height: 40px;
+  font-size: 13px;
+  width: 100%;
+  padding-left: 10px;
+  cursor: pointer;
+  :hover {
+    background: #7a7b80;
+  }
+  border-bottom-left-radius: ${({ showSettings }) => showSettings && "0px"};
+  border-bottom-right-radius: ${({ showSettings }) => showSettings && "0px"};
+
+  > div > i {
+    margin-right: 8px;
+    font-size: 20px;
+    cursor: pointer;
+    border-radius: 20px;
+    transform: ${(props: { showSettings: boolean; isCurrent: boolean }) =>
+        props.showSettings ? "" : "rotate(-90deg)"};
+  }
+`;
+
+const AdvancedBuildTitle = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const DetectedBuildMessage = styled.div`
+  color: #0f872b;
+  display: flex;
+  align-items: center;
+  padding: 4px 10px;
+  border-radius: 5px;
+  margin-right: 10px;
+
+  > i {
+    margin-right: 6px;
+    font-size: 20px;
+    cursor: pointer;
+    border-radius: 20px;
+    transform: none;
+  }
+`;
+
+const StyledSourceBox = styled.div`
+  width: 100%;
+  color: #ffffff;
+  padding: 14px 35px 20px;
+  position: relative;
+  font-size: 13px;
+  margin-bottom: 25px;
+  border-radius: 5px;
+  background: ${props => props.theme.fg};
+  border: 1px solid #494b4f;
+  border-top: 0px;
+  border-top-left-radius: 0px;
+  border-top-right-radius: 0px;
+`;
+
+const ToggleWrapper = styled.div`
+  display: flex;
+  justify-content: center;
+`

+ 92 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/GitRepoSelector.tsx

@@ -0,0 +1,92 @@
+import React from "react";
+import styled from "styled-components";
+
+interface GitRepoSelectorProps {
+
+}
+
+const GitRepoSelector: React.FC<GitRepoSelectorProps> = ({
+}) => {
+    return (
+        <StyledSourceBox>
+            <CloseButton
+                onClick={() => {
+                    setSourceType("");
+                    setDockerfilePath("");
+                    setFolderPath("");
+                    setProcfilePath("");
+                    setProcfileProcess("");
+                }}
+            >
+                <i className="material-icons">close</i>
+            </CloseButton>
+            <Subtitle>
+                Provide a repo folder to use as source.
+                <Highlight
+                    onClick={() => setCurrentModal("AccountSettingsModal", {})}
+                >
+                    Manage Git repos
+                </Highlight>
+                <Required>*</Required>
+            </Subtitle>
+            <DarkMatter antiHeight="-4px" />
+            <ActionConfEditor
+                actionConfig={actionConfig}
+                branch={branch}
+                setActionConfig={(actionConfig: ActionConfigType) => {
+                    setActionConfig((currentActionConfig: ActionConfigType) => ({
+                        ...currentActionConfig,
+                        ...actionConfig,
+                    }));
+                    setImageUrl(actionConfig.image_repo_uri);
+                    /*
+                    setParentState({ actionConfig }, () =>
+                      setParentState({ imageUrl: actionConfig.image_repo_uri })
+                    )
+                    */
+                }}
+                procfileProcess={procfileProcess}
+                setProcfileProcess={(procfileProcess: string) => {
+                    setProcfileProcess(procfileProcess);
+                    setValuesToOverride((v: any) => ({
+                        ...v,
+                        "container.command": procfileProcess || "",
+                        showStartCommand: !procfileProcess,
+                    }));
+                }}
+                setBranch={setBranch}
+                setDockerfilePath={setDockerfilePath}
+                setProcfilePath={setProcfilePath}
+                procfilePath={procfilePath}
+                dockerfilePath={dockerfilePath}
+                folderPath={folderPath}
+                setFolderPath={setFolderPath}
+                reset={() => {
+                    setActionConfig({ ...defaultActionConfig });
+                    setBranch("");
+                    setDockerfilePath(null);
+                    setFolderPath(null);
+                }}
+                setSelectedRegistry={setSelectedRegistry}
+                selectedRegistry={selectedRegistry}
+                setBuildConfig={setBuildConfig}
+            />
+            <br />
+        </StyledSourceBox>
+    )
+}
+
+export default GitRepoSelector;
+
+const StyledSourceBox = styled.div`
+  width: 100%;
+  color: #ffffff;
+  padding: 14px 35px 20px;
+  position: relative;
+  font-size: 13px;
+  margin-top: 6px;
+  margin-bottom: 25px;
+  border-radius: 5px;
+  background: ${props => props.theme.fg};
+  border: 1px solid #494b4f;
+`;

+ 224 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -0,0 +1,224 @@
+import React, { useEffect, useState, useContext, useMemo } from "react";
+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 web from "assets/web.png";
+
+import Back from "components/porter/Back";
+import DashboardHeader from "../../cluster-dashboard/DashboardHeader";
+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";
+import SourceSelector from "./SourceSelector";
+import AdvancedBuildSettings from "./AdvancedBuildSettings"
+
+type Props = RouteComponentProps & {
+};
+
+export type SourceType = "github" | "docker-registry";
+
+interface FormState {
+  applicationName: string;
+  selectedSourceType: SourceType | undefined;
+}
+
+const INITIAL_STATE: FormState = {
+  applicationName: "",
+  selectedSourceType: undefined,
+};
+
+const Validators: {
+  [key in keyof FormState]: (value: FormState[key]) => boolean;
+} = {
+  applicationName: (value: string) => value.trim().length > 0,
+  selectedSourceType: (value: SourceType | undefined) => value !== undefined,
+};
+
+
+
+const NewAppFlow: React.FC<Props> = ({
+  ...props
+}) => {
+  const { currentCluster, currentProject } = useContext(Context);
+  const [isLoading, setIsLoading] = useState<boolean>(true);
+  const [currentStep, setCurrentStep] = useState<number>(0);
+  const [formState, setFormState] = useState<FormState>(INITIAL_STATE);
+
+  return (
+    <StyledConfigureTemplate>
+      <Back to="/apps" />
+      <DashboardHeader
+        prefix={
+          <Icon
+            src={web}
+          />
+        }
+        title="Deploy a new application"
+        capitalize={false}
+        disableLineBreak
+      />
+      <DarkMatter />
+      <VerticalSteps
+        currentStep={currentStep}
+        steps={[
+          <>
+            <Text size={16}>Application name</Text>
+            <Spacer y={0.5} />
+            <Text color="helper">
+              Lowercase letters, numbers, and "-" only.
+            </Text>
+            <Spacer height="20px" />
+            <Input
+              placeholder="ex: academic-sophon"
+              value={formState.applicationName}
+              width="300px"
+              setValue={(e) => {
+                setFormState({ ...formState, applicationName: e })
+                if (Validators.applicationName(e)) {
+                  setCurrentStep(Math.max(currentStep, 1));
+                }
+              }}
+            />
+          </>,
+          <>
+            <Text size={16}>Deployment method</Text>
+            <Spacer y={0.5} />
+            <Text color="helper">
+              Deploy from a Git repository or a Docker registry.
+              <a
+                href="https://docs.porter.run/deploying-applications/overview"
+                target="_blank"
+              >
+                &nbsp;Learn more.
+              </a>
+            </Text>
+            <Spacer y={0.5} />
+            <SourceSelector
+              selectedSourceType={formState.selectedSourceType}
+              setSourceType={(type) => {
+                setFormState({ ...formState, selectedSourceType: type })
+                if (Validators.selectedSourceType(type)) {
+                  setCurrentStep(Math.max(currentStep, 2));
+                }
+              }}
+            />
+            {formState.selectedSourceType === "github" ? (
+              <>
+                <Text size={16}>Build settings</Text>
+                <Spacer y={0.5} />
+                <Text color="helper">
+                  Select your Github repository.
+                </Text>
+                <Spacer height="20px" />
+                <Input
+                  placeholder="ex: academic-sophon"
+                  value=""
+                  width="300px"
+                  setValue={(e) => { }}
+                />
+                <Spacer y={0.5} />
+                <Text color="helper">
+                  Select your branch.
+                </Text>
+                <Spacer y={0.5} />
+                <Input
+                  placeholder="ex: academic-sophon"
+                  value=""
+                  width="300px"
+                  setValue={(e) => { }}
+                />
+                <Spacer y={0.5} />
+                <Text color="helper">
+                  Specify your application root path.
+                </Text>
+                <Spacer y={0.5} />
+                <Input
+                  placeholder="ex: academic-sophon"
+                  value="./"
+                  width="300px"
+                  setValue={(e) => { }}
+                />
+                <Spacer y={1} />
+                <AdvancedBuildSettings />
+              </>
+            ) :
+              formState.selectedSourceType === 'docker-registry' ? (
+                <>
+                  <Text size={16}>Registry settings</Text>
+                  <Spacer y={0.5} />
+                  <Text color="helper">
+                    Select your Github repository.
+                  </Text>
+                  <Spacer height="20px" />
+                  <Input
+                    placeholder="ex: academic-sophon"
+                    value=""
+                    width="300px"
+                    setValue={(e) => { }}
+                  />
+                  <Spacer y={0.5} />
+                  <Text color="helper">
+                    Select your branch.
+                  </Text>
+                </>
+              ) : undefined
+            }
+          </>,
+          <>
+            <Text size={16}>More configuration</Text>
+            <Spacer y={0.5} />
+            <Text color="helper">
+              Configure settings for this add-on.
+            </Text>
+            <Spacer y={1} />
+            SOME MORE STUFF HERE
+          </>
+        ]}
+      />
+      <Spacer height="80px" />
+    </StyledConfigureTemplate>
+  );
+};
+
+export default withRouter(NewAppFlow);
+
+const DarkMatter = styled.div`
+  width: 100%;
+  margin-top: -5px;
+`;
+
+const Icon = styled.img`
+  margin-right: 15px;
+  height: 28px;
+  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%;
+`;
+

+ 118 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/SourceSelector.tsx

@@ -0,0 +1,118 @@
+import React from "react";
+import styled from "styled-components";
+import { SourceType } from "./NewAppFlow";
+
+interface SourceSelectorProps {
+    selectedSourceType: SourceType | undefined;
+    setSourceType: (sourceType: SourceType) => void;
+}
+
+const SourceSelector: React.FC<SourceSelectorProps> = ({
+    selectedSourceType,
+    setSourceType
+}) => {
+    return (
+        <BlockList>
+            <Block
+                selected={selectedSourceType === 'github'}
+                onClick={() => setSourceType('github')}
+            >
+                <BlockIcon src="https://git-scm.com/images/logos/downloads/Git-Icon-1788C.png" />
+                <BlockTitle>Git repository</BlockTitle>
+                <BlockDescription>
+                    Deploy using source from a Git repo.
+                </BlockDescription>
+            </Block>
+            <Block
+                selected={selectedSourceType === 'docker-registry'}
+                onClick={() => setSourceType('docker-registry')}
+            >
+                <BlockIcon src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png" />
+                <BlockTitle>Docker registry</BlockTitle>
+                <BlockDescription>
+                    Deploy a container from an image registry.
+                </BlockDescription>
+            </Block>
+
+        </BlockList>
+    );
+}
+
+export default SourceSelector;
+
+const Block = styled.div<{ selected?: boolean }>`
+  align-items: center;
+  user-select: none;
+  display: flex;
+  font-size: 13px;
+  overflow: hidden;
+  font-weight: 500;
+  padding: 3px 0px 12px;
+  flex-direction: column;
+  align-item: center;
+  justify-content: space-between;
+  height: 170px;
+  cursor: pointer;
+  color: #ffffff;
+  position: relative;
+
+  border-radius: 5px;
+  background: ${props => props.theme.clickable.bg};
+  border: ${props => props.selected ? "2px solid #8590ff" : "1px solid #494b4f"};
+  :hover {
+    border: ${({ selected }) => (!selected && "1px solid #7a7b80")};
+  }
+
+  animation: fadeIn 0.3s 0s;
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+`;
+
+const BlockList = styled.div`
+  overflow: visible;
+  margin-top: 6px;
+  margin-bottom: 27px;
+  display: grid;
+  grid-column-gap: 25px;
+  grid-row-gap: 25px;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+`;
+
+const BlockIcon = styled.img<{ bw?: boolean }>`
+  height: 38px;
+  padding: 2px;
+  margin-top: 30px;
+  margin-bottom: 15px;
+  filter: ${(props) => (props.bw ? "grayscale(1)" : "")};
+`;
+
+const BlockDescription = styled.div`
+  margin-bottom: 12px;
+  color: #ffffff66;
+  text-align: center;
+  font-weight: default;
+  font-size: 13px;
+  padding: 0px 25px;
+  height: 2.4em;
+  font-size: 12px;
+  display: -webkit-box;
+  overflow: hidden;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+`;
+
+const BlockTitle = styled.div`
+  margin-bottom: 12px;
+  width: 80%;
+  text-align: center;
+  font-size: 14px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;

+ 4 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx

@@ -422,7 +422,7 @@ const RollbackButton = styled.div`
     props.disabled ? "#aaaabbee" : "#616FEEcc"};
     props.disabled ? "#aaaabbee" : "#616FEEcc"};
   :hover {
   :hover {
     background: ${(props: { disabled: boolean }) =>
     background: ${(props: { disabled: boolean }) =>
-      props.disabled ? "" : "#405eddbb"};
+    props.disabled ? "" : "#405eddbb"};
   }
   }
 `;
 `;
 
 
@@ -434,7 +434,7 @@ const Tr = styled.tr`
     props.selected ? "#ffffff11" : ""};
     props.selected ? "#ffffff11" : ""};
   :hover {
   :hover {
     background: ${(props: { disableHover?: boolean; selected?: boolean }) =>
     background: ${(props: { disableHover?: boolean; selected?: boolean }) =>
-      props.disableHover ? "" : "#ffffff22"};
+    props.disableHover ? "" : "#ffffff22"};
   }
   }
 `;
 `;
 
 
@@ -476,6 +476,7 @@ const RevisionHeader = styled.div`
   width: 100%;
   width: 100%;
   padding-left: 10px;
   padding-left: 10px;
   cursor: pointer;
   cursor: pointer;
+  background: #26292e;
   :hover {
   :hover {
     background: ${(props) => props.showRevisions && "#ffffff18"};
     background: ${(props) => props.showRevisions && "#ffffff18"};
   }
   }
@@ -486,7 +487,7 @@ const RevisionHeader = styled.div`
     cursor: pointer;
     cursor: pointer;
     border-radius: 20px;
     border-radius: 20px;
     transform: ${(props: { showRevisions: boolean; isCurrent: boolean }) =>
     transform: ${(props: { showRevisions: boolean; isCurrent: boolean }) =>
-      props.showRevisions ? "" : "rotate(-90deg)"};
+    props.showRevisions ? "" : "rotate(-90deg)"};
   }
   }
 `;
 `;