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

merge basic app placeholder flow

Justin Rhee 3 лет назад
Родитель
Сommit
c15e90a0c8

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

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

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

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

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

@@ -243,8 +243,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
   useEffect(() => {
     setIsReadOnly(
       props.clusterId &&
-        (currentCluster.status === "UPDATING" ||
-          currentCluster.status === "UPDATING_UNAVAILABLE")
+      (currentCluster.status === "UPDATING" ||
+        currentCluster.status === "UPDATING_UNAVAILABLE")
     );
     setClusterName(
       `${currentProject.name}-cluster-${Math.random()
@@ -392,7 +392,7 @@ const ExpandHeader = styled.div<{ isExpanded: boolean }>`
     margin-right: 7px;
     margin-left: -7px;
     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} />
               )
             }
-            <Dot 
+            <Dot
               isActive={i <= currentStep}
             />
             <OpacityWrapper isActive={i <= currentStep}>
@@ -91,4 +91,5 @@ const StepWrapper = 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 Spacer from "components/porter/Spacer";
 import Button from "components/porter/Button";
-import NewAppFlow from "./app-dashboard/NewAppFlow";
+import NewAppFlow from "./app-dashboard/new-app-flow/NewAppFlow";
 
 // Guarded components
 const GuardedProjectSettings = fakeGuardedRoute("settings", "", [
@@ -190,7 +190,7 @@ const Home: React.FC<Props> = (props) => {
       } else {
         setHasFinishedOnboarding(true);
       }
-    } catch (error) {}
+    } catch (error) { }
   };
 
   useEffect(() => {
@@ -436,17 +436,17 @@ const Home: React.FC<Props> = (props) => {
               overrideInfraTabEnabled({
                 projectID: currentProject?.id,
               })) && (
-              <Route
-                path="/infrastructure"
-                render={() => {
-                  return (
-                    <DashboardWrapper>
-                      <InfrastructureRouter />
-                    </DashboardWrapper>
-                  );
-                }}
-              />
-            )}
+                <Route
+                  path="/infrastructure"
+                  render={() => {
+                    return (
+                      <DashboardWrapper>
+                        <InfrastructureRouter />
+                      </DashboardWrapper>
+                    );
+                  }}
+                />
+              )}
             <Route
               path="/dashboard"
               render={() => {
@@ -515,14 +515,14 @@ const Home: React.FC<Props> = (props) => {
           />,
           document.body
         )}
-        {showWrongEmailModal && 
+        {showWrongEmailModal &&
           <Modal>
             <Text size={16}>
               Oops! This invite link wasn't for {user?.email}
             </Text>
             <Spacer y={1} />
             <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.
             </Text>
             <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"};
   :hover {
     background: ${(props: { disabled: boolean }) =>
-      props.disabled ? "" : "#405eddbb"};
+    props.disabled ? "" : "#405eddbb"};
   }
 `;
 
@@ -434,7 +434,7 @@ const Tr = styled.tr`
     props.selected ? "#ffffff11" : ""};
   :hover {
     background: ${(props: { disableHover?: boolean; selected?: boolean }) =>
-      props.disableHover ? "" : "#ffffff22"};
+    props.disableHover ? "" : "#ffffff22"};
   }
 `;
 
@@ -476,6 +476,7 @@ const RevisionHeader = styled.div`
   width: 100%;
   padding-left: 10px;
   cursor: pointer;
+  background: #26292e;
   :hover {
     background: ${(props) => props.showRevisions && "#ffffff18"};
   }
@@ -486,7 +487,7 @@ const RevisionHeader = styled.div`
     cursor: pointer;
     border-radius: 20px;
     transform: ${(props: { showRevisions: boolean; isCurrent: boolean }) =>
-      props.showRevisions ? "" : "rotate(-90deg)"};
+    props.showRevisions ? "" : "rotate(-90deg)"};
   }
 `;