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

Merge branch 'stacks-v1' of github.com:porter-dev/porter into stacks-v1

Feroze Mohideen 3 лет назад
Родитель
Сommit
034ca7602b
27 измененных файлов с 431 добавлено и 229 удалено
  1. 3 0
      api/server/handlers/stacks/update_porter_app.go
  2. BIN
      dashboard/src/assets/refresh.png
  3. 21 3
      dashboard/src/components/porter/Banner.tsx
  4. 21 6
      dashboard/src/components/porter/Link.tsx
  5. 14 26
      dashboard/src/components/repo-selector/DetectContentsList.tsx
  6. 27 33
      dashboard/src/main/home/Home.tsx
  7. 60 64
      dashboard/src/main/home/app-dashboard/AppDashboard.tsx
  8. 3 6
      dashboard/src/main/home/app-dashboard/expanded-app/AppEvents.tsx
  9. 36 28
      dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx
  10. 120 0
      dashboard/src/main/home/app-dashboard/expanded-app/GHABanner.tsx
  11. 8 1
      dashboard/src/main/home/app-dashboard/new-app-flow/AdvancedBuildSettings.tsx
  12. 74 31
      dashboard/src/main/home/app-dashboard/new-app-flow/GithubActionModal.tsx
  13. 18 13
      dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx
  14. 13 6
      dashboard/src/main/home/app-dashboard/new-app-flow/Services.tsx
  15. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/NotificationSettingsSection.tsx
  16. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/build-settings/BuildSettingsTab.tsx
  17. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/logs-section/LogsSection.tsx
  18. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/components/PreviewEnvironmentsHeader.tsx
  19. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentDetail.tsx
  20. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx
  21. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreateBranchEnvironment.tsx
  22. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreatePREnvironment.tsx
  23. 1 1
      dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentSettings.tsx
  24. 1 1
      dashboard/src/main/home/dashboard/ClusterSection.tsx
  25. 1 1
      dashboard/src/main/home/dashboard/Dashboard.tsx
  26. 1 1
      dashboard/src/main/home/modals/UsageWarningModal.tsx
  27. 1 0
      dashboard/src/shared/api.tsx

+ 3 - 0
api/server/handlers/stacks/update_porter_app.go

@@ -67,6 +67,9 @@ func (c *UpdatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	if request.ImageRepoURI != "" {
 		porterApp.ImageRepoURI = request.ImageRepoURI
 	}
+	if request.PullRequestURL != "" {
+		porterApp.PullRequestURL = request.PullRequestURL
+	}
 
 	updatedPorterApp, err := c.Repo().PorterApp().UpdatePorterApp(porterApp)
 	if err != nil {

BIN
dashboard/src/assets/refresh.png


+ 21 - 3
dashboard/src/components/Banner.tsx → dashboard/src/components/porter/Banner.tsx

@@ -9,9 +9,16 @@ interface Props {
   icon?: React.ReactNode;
   children: React.ReactNode;
   noMargin?: boolean;
+  suffix?: React.ReactNode;
 }
 
-const Banner: React.FC<Props> = ({ type, icon, children, noMargin }) => {
+const Banner: React.FC<Props> = ({ 
+  type,
+  icon,
+  children,
+  noMargin,
+  suffix,
+}) => {
   const renderIcon = () => {
     if (icon) {
       return icon;
@@ -28,14 +35,24 @@ const Banner: React.FC<Props> = ({ type, icon, children, noMargin }) => {
       color={type === "error" ? "#ff385d" : type === "warning" && "#f5cb42"}
       noMargin={noMargin}
     >
+      <>
       {renderIcon()}
-      {children}
+      <span>{children}</span>
+      </>
+      {suffix && (
+        <Suffix>{suffix}</Suffix>
+      )}
     </StyledBanner>
   );
 };
 
 export default Banner;
 
+const Suffix = styled.div`
+  margin-left: auto;
+  padding-left: 10px;
+`;
+
 const StyledBanner = styled.div<{
   color?: string;
   noMargin?: boolean;
@@ -52,7 +69,8 @@ const StyledBanner = styled.div<{
   padding: 10px 14px;
   color: ${(props) => props.color || "#ffffff"};
   align-items: center;
-  background: #ffffff11;
+  jusrify-content: space-between;
+  background: ${({ theme }) => theme.fg};
   > img {
     margin-right: 10px;
     width: 20px;

+ 21 - 6
dashboard/src/components/porter/Link.tsx

@@ -7,6 +7,7 @@ type Props = {
   onClick?: () => void;
   children: React.ReactNode;
   target?: string;
+  underline?: boolean;
 };
 
 const Link: React.FC<Props> = ({
@@ -14,13 +15,25 @@ const Link: React.FC<Props> = ({
   onClick,
   children,
   target,
+  underline,
 }) => {
   return (
     <>
       {to ? (
-        <StyledLink to={to} target={target}>{children}</StyledLink>
+        <StyledLink 
+          to={to} 
+          target={target}
+          underline={underline}
+        >
+          {children}
+        </StyledLink>
       ) : (
-        <Div onClick={onClick}>{children}</Div>
+        <Div 
+          onClick={onClick}
+          underline={underline}
+        >
+          {children}
+        </Div>
       )}
     </>
   );
@@ -28,14 +41,16 @@ const Link: React.FC<Props> = ({
 
 export default Link;
 
-const Div = styled.span`
-  color: #8590ff;
+const Div = styled.span<{ underline?: boolean }>`
+  color: #ffffff;
   cursor: pointer;
   display: inline;
+  text-decoration: ${props => props.underline ? "underline" : ""};
 `;
 
-const StyledLink = styled(DynamicLink)`
-  color: #8590ff;
+const StyledLink = styled(DynamicLink)<{ underline?: boolean }>`
+  color: #ffffff;
   display: inline;
   cursor: pointer;
+  text-decoration: ${props => props.underline ? "underline" : ""};
 `;

+ 14 - 26
dashboard/src/components/repo-selector/DetectContentsList.tsx

@@ -223,32 +223,20 @@ const DetectContentsList: React.FC<PropsType> = (props) => {
   };
   return (
     <>
-      {renderContentList() &&
-      props.dockerfilePath != "" &&
-      props.dockerfilePath != null ? (
-        <AdvancedBuildSettings
-          dockerfilePath={props.dockerfilePath}
-          setDockerfilePath={props.setDockerfilePath}
-          setBuildConfig={props.setBuildConfig}
-          autoBuildPack={autoBuildpack}
-          showSettings={false}
-          buildView={"buildpacks"}
-          actionConfig={props.actionConfig}
-          branch={props.branch}
-          folderPath={props.folderPath}
-        />
-      ) : (
-        <AdvancedBuildSettings
-          dockerfilePath={props.dockerfilePath}
-          setDockerfilePath={props.setDockerfilePath}
-          setBuildConfig={props.setBuildConfig}
-          autoBuildPack={autoBuildpack}
-          showSettings={false}
-          buildView={"docker"}
-          actionConfig={props.actionConfig}
-          branch={props.branch}
-          folderPath={props.folderPath}
-        />
+      {renderContentList() && (
+        <>
+          <AdvancedBuildSettings
+            dockerfilePath={props.dockerfilePath}
+            setDockerfilePath={props.setDockerfilePath}
+            setBuildConfig={props.setBuildConfig}
+            autoBuildPack={autoBuildpack}
+            showSettings={false}
+            buildView={props.dockerfilePath ? "dockerfile" : "buildpacks"}
+            actionConfig={props.actionConfig}
+            branch={props.branch}
+            folderPath={props.folderPath}
+          />
+        </>
       )}
     </>
   );

+ 27 - 33
dashboard/src/main/home/Home.tsx

@@ -191,7 +191,7 @@ const Home: React.FC<Props> = (props) => {
       } else {
         setHasFinishedOnboarding(true);
       }
-    } catch (error) { }
+    } catch (error) {}
   };
 
   useEffect(() => {
@@ -365,7 +365,9 @@ const Home: React.FC<Props> = (props) => {
 
   const { cluster, baseRoute } = props.match.params as any;
   return (
-    <ThemeProvider theme={currentProject?.simplified_view_enabled ? midnight : standard}>
+    <ThemeProvider
+      theme={currentProject?.simplified_view_enabled ? midnight : standard}
+    >
       <StyledHome>
         <ModalHandler setRefreshClusters={setForceRefreshClusters} />
         {currentOverlay &&
@@ -401,27 +403,19 @@ const Home: React.FC<Props> = (props) => {
           />
 
           <Switch>
-            <Route
-              path="/apps/new"
-            >
+            <Route path="/apps/new/app">
               <NewAppFlow />
             </Route>
             <Route path="/apps/:appName">
               <ExpandedApp />
             </Route>
-            <Route
-              path="/apps"
-            >
+            <Route path="/apps">
               <AppDashboard />
             </Route>
-            <Route
-              path="/addons/new"
-            >
+            <Route path="/addons/new">
               <NewAddOnFlow />
             </Route>
-            <Route
-              path="/addons"
-            >
+            <Route path="/addons">
               <AddOnDashboard />
             </Route>
             <Route
@@ -440,17 +434,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={() => {
@@ -519,26 +513,26 @@ 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.
-              Please log out and sign up again with the correct email using the invite link.
+              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} />
             <Text color="helper">
-              You should reach out to the person who sent you the invite link to get the correct email.
+              You should reach out to the person who sent you the invite link to
+              get the correct email.
             </Text>
             <Spacer y={1} />
-            <Button onClick={props.logOut}>
-              Log out
-            </Button>
+            <Button onClick={props.logOut}>Log out</Button>
           </Modal>
-        }
+        )}
       </StyledHome>
     </ThemeProvider>
   );

+ 60 - 64
dashboard/src/main/home/app-dashboard/AppDashboard.tsx

@@ -28,8 +28,7 @@ import PorterLink from "components/porter/Link";
 import Loading from "components/Loading";
 import Fieldset from "components/porter/Fieldset";
 
-type Props = {
-};
+type Props = {};
 
 const icons = [
   "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/ruby/ruby-plain.svg",
@@ -49,8 +48,7 @@ const namespaceBlacklist = [
   "monitoring",
 ];
 
-const AppDashboard: React.FC<Props> = ({
-}) => {
+const AppDashboard: React.FC<Props> = ({}) => {
   const { currentProject, currentCluster } = useContext(Context);
   const [apps, setApps] = useState([]);
   const [charts, setCharts] = useState([]);
@@ -61,14 +59,10 @@ const AppDashboard: React.FC<Props> = ({
   const [shouldLoadTime, setShouldLoadTime] = useState(true);
 
   const filteredApps = useMemo(() => {
-    const filteredBySearch = search(
-      apps ?? [],
-      searchValue,
-      {
-        keys: ["name"],
-        isCaseSensitive: false,
-      }
-    );
+    const filteredBySearch = search(apps ?? [], searchValue, {
+      keys: ["name"],
+      isCaseSensitive: false,
+    });
 
     return _.sortBy(filteredBySearch);
   }, [apps, searchValue]);
@@ -83,14 +77,14 @@ const AppDashboard: React.FC<Props> = ({
           project_id: currentProject.id,
           cluster_id: currentCluster.id,
         }
-      )
+      );
       const apps = res.data;
       const timeRes = await Promise.all(
         apps.map((app: any) => {
           return api.getCharts(
             "<token>",
             {
-              limit: 1, 
+              limit: 1,
               skip: 0,
               byDate: false,
               statusFilter: [
@@ -108,16 +102,17 @@ const AppDashboard: React.FC<Props> = ({
               cluster_id: currentCluster.id,
               namespace: `porter-stack-${app.name}`,
             }
-          )
+          );
         })
       );
       apps.forEach((app: any, i: number) => {
-        app["last_deployed"] = readableDate(timeRes[i].data[0]?.info?.last_deployed);
+        app["last_deployed"] = readableDate(
+          timeRes[i].data[0]?.info?.last_deployed
+        );
       });
       setApps(apps.reverse());
       setIsLoading(false);
-    }
-    catch (err) {
+    } catch (err) {
       setError(err);
       setIsLoading(false);
     }
@@ -130,7 +125,7 @@ const AppDashboard: React.FC<Props> = ({
   }, [currentCluster, currentProject]);
 
   const renderSource = (app: any) => {
-    return(
+    return (
       <>
         {app.repo_name ? (
           <>
@@ -139,7 +134,11 @@ const AppDashboard: React.FC<Props> = ({
           </>
         ) : (
           <>
-            <SmallIcon opacity="0.7" height="18px" src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png" />
+            <SmallIcon
+              opacity="0.7"
+              height="18px"
+              src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png"
+            />
             {app.image_repo_uri}
           </>
         )}
@@ -169,13 +168,7 @@ const AppDashboard: React.FC<Props> = ({
       }
     }
     return (
-      <>
-        {size === "larger" ? (
-          <MidIcon src={src} />
-        ) : (
-          <Icon src={src} />
-        )}
-      </>
+      <>{size === "larger" ? <MidIcon src={src} /> : <Icon src={src} />}</>
     );
   };
 
@@ -188,7 +181,7 @@ const AppDashboard: React.FC<Props> = ({
         disableLineBreak
       />
       <Container row spaced>
-        <SearchBar 
+        <SearchBar
           value={searchValue}
           setValue={setSearchValue}
           placeholder="Search applications . . ."
@@ -204,14 +197,14 @@ const AppDashboard: React.FC<Props> = ({
           setActive={setView}
         />
         <Spacer inline x={2} />
-        <PorterLink to="/apps/new">
+        <PorterLink to="/apps/new/app">
           <Button onClick={() => {}} height="30px" width="160px">
             <I className="material-icons">add</I> New application
           </Button>
         </PorterLink>
       </Container>
       <Spacer y={1} />
-      {(!isLoading && filteredApps.length === 0) && (
+      {!isLoading && filteredApps.length === 0 && (
         <Fieldset>
           <Container row>
             <PlaceholderIcon src={notFound} />
@@ -219,34 +212,36 @@ const AppDashboard: React.FC<Props> = ({
           </Container>
         </Fieldset>
       )}
-      {isLoading ? <Loading offset="-150px" /> : view === "grid" ? (
+      {isLoading ? (
+        <Loading offset="-150px" />
+      ) : view === "grid" ? (
         <GridList>
-         {(filteredApps ?? []).map((app: any, i: number) => {
-           if (!namespaceBlacklist.includes(app.name)) {
-             return (
-              <Link to={`/apps/${app.name}`} key={i}>
-                <Block>
-                  <Container row>
-                    <Text size={14}>
-                      {renderIcon(app["build_packs"])}
-                      {app.name}
+          {(filteredApps ?? []).map((app: any, i: number) => {
+            if (!namespaceBlacklist.includes(app.name)) {
+              return (
+                <Link to={`/apps/${app.name}`} key={i}>
+                  <Block>
+                    <Container row>
+                      <Text size={14}>
+                        {renderIcon(app["build_packs"])}
+                        {app.name}
+                      </Text>
+                      <Spacer inline x={2} />
+                    </Container>
+                    <StatusIcon src={healthy} />
+                    <Text size={13} color="#ffffff44">
+                      {renderSource(app)}
                     </Text>
-                    <Spacer inline x={2} />
-                  </Container>
-                  <StatusIcon src={healthy} />
-                  <Text size={13} color="#ffffff44">
-                    {renderSource(app)}
-                  </Text>
-                  <Text size={13} color="#ffffff44">
-                    <SmallIcon opacity="0.4" src={time} />
-                    {app.last_deployed}
-                  </Text>
-                </Block>
-              </Link>
-             );
-           }
-         })}
-       </GridList>
+                    <Text size={13} color="#ffffff44">
+                      <SmallIcon opacity="0.4" src={time} />
+                      {app.last_deployed}
+                    </Text>
+                  </Block>
+                </Link>
+              );
+            }
+          })}
+        </GridList>
       ) : (
         <List>
           {(filteredApps ?? []).map((app: any, i: number) => {
@@ -290,8 +285,9 @@ const PlaceholderIcon = styled.img`
 const Row = styled.div<{ isAtBottom?: boolean }>`
   cursor: pointer;
   padding: 15px;
-  border-bottom: ${props => props.isAtBottom ? "none" : "1px solid #494b4f"};
-  background: ${props => props.theme.clickable.bg};
+  border-bottom: ${(props) =>
+    props.isAtBottom ? "none" : "1px solid #494b4f"};
+  background: ${(props) => props.theme.clickable.bg};
   position: relative;
   border: 1px solid #494b4f;
   border-radius: 5px;
@@ -327,10 +323,10 @@ const MidIcon = styled.img`
   margin-left: 1px;
 `;
 
-const SmallIcon = styled.img<{ opacity?: string, height?: string }>`
+const SmallIcon = styled.img<{ opacity?: string; height?: string }>`
   margin-left: 2px;
-  height: ${props => props.height || "14px"};
-  opacity: ${props => props.opacity || 1};
+  height: ${(props) => props.height || "14px"};
+  opacity: ${(props) => props.opacity || 1};
   filter: grayscale(100%);
   margin-right: 10px;
 `;
@@ -342,10 +338,10 @@ const Block = styled.div`
   justify-content: space-between;
   cursor: pointer;
   padding: 20px;
-  color: ${props => props.theme.text.primary};
+  color: ${(props) => props.theme.text.primary};
   position: relative;
   border-radius: 5px;
-  background: ${props => props.theme.clickable.bg};
+  background: ${(props) => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;
@@ -381,4 +377,4 @@ const I = styled.i`
 const StyledAppDashboard = styled.div`
   width: 100%;
   height: 100%;
-`;
+`;

+ 3 - 6
dashboard/src/main/home/app-dashboard/expanded-app/AppEvents.tsx

@@ -25,16 +25,13 @@ const AppEvents: React.FC<Props> = ({
     <StyledAppEvents>
       <Fieldset>
         <Text size={16}>
-          Approval required for Porter GitHub Action
+          Dream on
         </Text>
         <Spacer y={0.5} />
         <Text color="helper">
-          We've opened a PR to add the Porter GitHub Action to the {branchName} branch of {repoName}.
-        </Text>
-        <Spacer y={0.5} />
-        <Text color="helper">
-          <Link to="/">Merge Porter PR</Link>
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
         </Text>
+        <Spacer height="10px" />
       </Fieldset>
     </StyledAppEvents>
   );

+ 36 - 28
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -31,11 +31,12 @@ import Services from "../new-app-flow/Services";
 import { Service } from "../new-app-flow/serviceTypes";
 import ConfirmOverlay from "components/porter/ConfirmOverlay";
 import Fieldset from "components/porter/Fieldset";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import AppEvents from "./AppEvents";
 import { PorterJson, createFinalPorterYaml } from "../new-app-flow/schema";
 import EnvGroupArray, { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
 import { PorterYamlSchema } from "../new-app-flow/schema";
+import GHABanner from "./GHABanner";
 
 type Props = RouteComponentProps & {};
 
@@ -54,6 +55,8 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const [isLoading, setIsLoading] = useState(true);
   const [deleting, setDeleting] = useState(false);
   const [appData, setAppData] = useState(null);
+  const [workflowCheckPassed, setWorkflowCheckPassed] = useState<boolean>(false);
+
   const [error, setError] = useState(null);
   const [forceRefreshRevisions, setForceRefreshRevisions] = useState<boolean>(
     false
@@ -61,7 +64,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const [isLoadingChartData, setIsLoadingChartData] = useState<boolean>(true);
   const [imageIsPlaceholder, setImageIsPlaceholer] = useState<boolean>(false);
 
-  const [tab, setTab] = useState("events");
+  const [tab, setTab] = useState("overview");
   const [saveValuesStatus, setSaveValueStatus] = useState<string>(null);
   const [loading, setLoading] = useState<boolean>(false);
   const [components, setComponents] = useState<ResourceType[]>([]);
@@ -423,7 +426,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
               setServices={setServices}
               services={services}
             />
-            <Spacer y={0.5} />
+            <Spacer y={1} />
             <Button
               onClick={() => {
                 updatePorterApp();
@@ -432,12 +435,11 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                 <Error message={updateError} />
               ) : undefined}
               loadingText={"Updating..."}
-              width={"150px"}
               disabled={services.length === 0}
             >
               Update app
             </Button>
-            <Spacer y={0.5} />
+            <Spacer y={3} />
           </>
         )
       case "build-settings":
@@ -581,10 +583,14 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
             </Fieldset>
           ) : (
             <>
-              {true ? (
-                <Banner type="warning">
-                  Your application won't be available until you approve and merge this PR in your GitHub repository.
-                </Banner>
+              {!workflowCheckPassed ? (
+                <GHABanner
+                  repoName={appData.app.repo_name}
+                  branchName={appData.app.git_branch}
+                  pullRequestUrl={appData.app.pull_request_url}
+                  stackName={appData.app.name}
+                  gitRepoId={appData.app.git_repo_id}
+                />
               ) : (
                 <>
                   <DarkMatter />
@@ -611,25 +617,27 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
               )}
               <Spacer y={1} />
               <TabSelector
-                options={
-                  appData.app.git_repo_id
-                    ? [
-                      { label: "Events", value: "events" },
-                      { label: "Logs", value: "logs" },
-                      { label: "Metrics", value: "metrics" },
-                      { label: "Overview", value: "overview" },
-                      { label: "Environment variables", value: "environment-variables" },
-                      { label: "Build settings", value: "build-settings" },
-                      { label: "Settings", value: "settings" },
-                    ]
-                    : [
-                      { label: "Events", value: "events" },
-                      { label: "Logs", value: "logs" },
-                      { label: "Metrics", value: "metrics" },
-                      { label: "Overview", value: "overview" },
-                      { label: "Environment variables", value: "environment-variables" },
-                      { label: "Settings", value: "settings" },
-                    ]
+                options={appData.app.git_repo_id ? (workflowCheckPassed ? [
+                    { label: "Events", value: "events" },
+                    { label: "Logs", value: "logs" },
+                    { label: "Metrics", value: "metrics" },
+                    { label: "Overview", value: "overview" },
+                    { label: "Environment variables", value: "environment-variables" },
+                    { label: "Build settings", value: "build-settings" },
+                    { label: "Settings", value: "settings" },
+                  ] : [
+                    { label: "Overview", value: "overview" },
+                    { label: "Environment variables", value: "environment-variables" },
+                    { label: "Build settings", value: "build-settings" },
+                    { label: "Settings", value: "settings" },
+                  ]) : [
+                    { label: "Events", value: "events" },
+                    { label: "Logs", value: "logs" },
+                    { label: "Metrics", value: "metrics" },
+                    { label: "Overview", value: "overview" },
+                    { label: "Environment variables", value: "environment-variables" },
+                    { label: "Settings", value: "settings" },
+                  ]
                 }
                 currentTab={tab}
                 setCurrentTab={setTab}

+ 120 - 0
dashboard/src/main/home/app-dashboard/expanded-app/GHABanner.tsx

@@ -0,0 +1,120 @@
+import React, { useEffect, useState, useContext } from "react";
+import styled from "styled-components";
+
+import refresh from "assets/refresh.png";
+
+import { Context } from "shared/Context";
+
+import Banner from "components/porter/Banner";
+import Spacer from "components/porter/Spacer";
+import Link from "components/porter/Link";
+import GithubActionModal from "../new-app-flow/GithubActionModal";
+import Container from "components/porter/Container";
+
+
+type Props = {
+  pullRequestUrl: string;
+  branchName: string;
+  repoName: string;
+  stackName: string;
+  gitRepoId: number;
+};
+
+const GHABanner: React.FC<Props> = ({
+  pullRequestUrl,
+  branchName,
+  repoName,
+  stackName,
+  gitRepoId,
+}) => {
+  const { currentProject, currentCluster } = useContext(Context);
+  const [showGHAModal, setShowGHAModal] = useState(false);
+  return (
+    <>
+      <StyledGHABanner>
+        <>
+          {pullRequestUrl ? (
+            <Banner 
+              type="warning"
+              suffix={
+                <RefreshButton onClick={() => window.location.reload()}>
+                  <img src={refresh} /> Refresh
+                </RefreshButton>
+              }
+            >
+              <Container row spaced>
+                Your application will not be available until you merge
+                <Spacer inline width="5px" />
+                <Link
+                  to={pullRequestUrl}
+                  underline
+                >
+                  this PR
+                </Link>
+                <Spacer inline width="5px" />
+                into your branch.
+              </Container>
+            </Banner>
+          ) : (
+            <Banner   
+              type="warning"
+              suffix={
+                <RefreshButton onClick={() => window.location.reload()}>
+                  <img src={refresh} /> Refresh
+                </RefreshButton>
+              }
+            >
+              Your application will not be available until you add the Porter workflow to your branch.
+              <Spacer inline width="5px" />
+              <Link
+                onClick={() => setShowGHAModal(true)}
+                underline
+              >
+                See details
+              </Link>
+            </Banner>
+          )}
+        </>
+      </StyledGHABanner>
+      {showGHAModal && (
+        <GithubActionModal
+          closeModal={() => setShowGHAModal(false)}
+          githubAppInstallationID={gitRepoId}
+          githubRepoOwner={repoName.split("/")[0]}
+          githubRepoName={repoName.split("/")[1]}
+          branch={branchName}
+          stackName={stackName}
+          projectId={currentProject.id}
+          clusterId={currentCluster.id}
+        />
+      )}
+    </>
+  );
+};
+
+export default GHABanner;
+
+const StyledGHABanner = styled.div`
+`;
+
+const RefreshButton = styled.div`
+  color: #ffffff44;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  :hover {
+    color: #ffffff;
+    > img {
+      opacity: 1;
+    }
+  }
+
+  > img {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 11px;
+    margin-right: 10px;
+    opacity: 0.3;
+  }
+`;

+ 8 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/AdvancedBuildSettings.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
@@ -43,6 +43,13 @@ const AdvancedBuildSettings: React.FC<AdvancedBuildSettingsProps> = (props) => {
   const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
     setBuildView(e.target.value);
   };
+  useEffect(() => {
+    if (props.dockerfilePath && props.dockerfilePath != "") {
+      setBuildView("docker");
+    } else {
+      setBuildView("buildpacks");
+    }
+  }, [props.dockerfilePath]);
   const createDockerView = () => {
     return (
       <>

+ 74 - 31
dashboard/src/main/home/app-dashboard/new-app-flow/GithubActionModal.tsx

@@ -14,6 +14,8 @@ import { getGithubAction } from "./utils";
 import AceEditor from "react-ace";
 import YamlEditor from "components/YamlEditor";
 import Error from "components/porter/Error";
+import Container from "components/porter/Container";
+import Checkbox from "components/porter/Checkbox";
 
 
 type Props = RouteComponentProps & {
@@ -25,7 +27,7 @@ type Props = RouteComponentProps & {
   stackName?: string;
   projectId?: number;
   clusterId?: number;
-  deployPorterApp: () => Promise<boolean>;
+  deployPorterApp?: () => Promise<boolean>;
   deploymentError?: string;
 }
 
@@ -46,14 +48,17 @@ const GithubActionModal: React.FC<Props> = ({
 }) => {
   const [choice, setChoice] = React.useState<Choice>("open_pr");
   const [loading, setLoading] = React.useState<boolean>(false);
+  const [isChecked, setIsChecked] = React.useState<boolean>(false);
 
   const submit = async () => {
     if (githubAppInstallationID && githubRepoOwner && githubRepoName && branch && stackName) {
       try {
         setLoading(true)
         // this creates the dummy chart
-
-        const success = await deployPorterApp();
+        var success = true;
+        if (deployPorterApp) {
+          success = await deployPorterApp();
+        }
 
         if (success) {
           // this creates the secret and possibly the PR
@@ -64,7 +69,7 @@ const GithubActionModal: React.FC<Props> = ({
               github_repo_owner: githubRepoOwner,
               github_repo_name: githubRepoName,
               branch,
-              open_pr: choice === "open_pr",
+              open_pr: (choice === "open_pr" || isChecked),
             },
             {
               project_id: projectId,
@@ -73,7 +78,21 @@ const GithubActionModal: React.FC<Props> = ({
             }
           );
           if (res?.data?.url) {
+            const updateRes = await api.updatePorterApp(
+              "<token>",
+              {
+                pull_request_url: res.data.url,
+              },
+              {
+                project_id: projectId,
+                cluster_id: clusterId,
+                name: stackName,
+              }
+            )
             window.open(res.data.url, "_blank", "noreferrer");
+            if (!deployPorterApp) {
+              window.location.reload();
+            }
           }
           props.history.push(`/apps/${stackName}`);
         }
@@ -95,7 +114,7 @@ const GithubActionModal: React.FC<Props> = ({
       <Text color="helper">
         In order to automatically update your services every time new code is pushed to your GitHub branch, the following file must exist in your GitHub repository:
       </Text>
-      <Spacer y={1} />
+      <Spacer y={0.5} />
       <ExpandableSection
         noWrapper
         expandText="[+] Show code"
@@ -119,26 +138,51 @@ const GithubActionModal: React.FC<Props> = ({
         Porter can open a PR for you to approve and merge this file into your repository, or you can add it yourself. If you allow Porter to open a PR, you will be redirected to the PR in a new tab after submitting below.
       </Text>
       <Spacer y={1} />
-      <Select
-        options={[
-          { label: "I authorize Porter to open a PR on my behalf (recommended)", value: "open_pr" },
-          { label: "I will copy the file into my repository myself", value: "copy" },
-        ]}
-        setValue={(x: string) => setChoice(x as Choice)}
-        width="100%"
-      />
-      <StyledButton>
-        <Button
-          onClick={submit}
-          width={"150px"}
-          loadingText={"Submitting..."}
-          status={loading ? "loading" : deploymentError ? (
-            <Error message={deploymentError} />
-          ) : undefined}
-        >
-          Deploy app
-        </Button>
-      </StyledButton>
+      {deployPorterApp ? (
+        <>
+          <Select
+            options={[
+              { label: "I authorize Porter to open a PR on my behalf (recommended)", value: "open_pr" },
+              { label: "I will copy the file into my repository myself", value: "copy" },
+            ]}
+            setValue={(x: string) => setChoice(x as Choice)}
+            width="100%"
+          />
+          <Spacer y={1} />
+          <Button
+            onClick={submit}
+            width={"110px"}
+            loadingText={"Submitting..."}
+            status={loading ? "loading" : deploymentError ? (
+              <Error message={deploymentError} />
+            ) : undefined}
+          >
+            Deploy app
+          </Button>
+        </>
+      ) : (
+        <>
+          <Checkbox
+            checked={isChecked}
+            toggleChecked={() => setIsChecked(!isChecked)}
+          >
+            <Text>
+              I authorize Porter to open a PR on my behalf
+            </Text>
+          </Checkbox>
+          <Spacer y={1} />
+          <Button
+            disabled={!isChecked}
+            onClick={submit}
+            loadingText={"Submitting..."}
+            status={loading ? "loading" : deploymentError ? (
+              <Error message={deploymentError} />
+            ) : undefined}
+          >
+            Open a PR for me
+          </Button>
+        </>
+      )}
     </Modal>
   )
 }
@@ -153,9 +197,8 @@ const Tab = styled.span`
 const ModalHeader = styled.div`
   font-weight: 600;
   font-size: 1.5vw;
-  font-family: monospace; ;
-`;
-
-const StyledButton = styled.div`
-  margin-top: 10px;
-`;
+  font-family: monospace;
+  height: 40px;
+  display: flex;
+  align-items: center;
+`;

+ 18 - 13
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -92,7 +92,9 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [buildConfig, setBuildConfig] = useState({});
   const [porterYaml, setPorterYaml] = useState("");
   const [showGHAModal, setShowGHAModal] = useState<boolean>(false);
-  const [porterJson, setPorterJson] = useState<PorterJson | undefined>(undefined);
+  const [porterJson, setPorterJson] = useState<PorterJson | undefined>(
+    undefined
+  );
   const [detected, setDetected] = useState<Detected | undefined>(undefined);
 
   const validatePorterYaml = (yamlString: string) => {
@@ -127,8 +129,9 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       ) {
         setDetected({
           detected: true,
-          message: `Detected ${Object.keys(porterYamlToJson.apps).length
-            } apps from porter.yaml`,
+          message: `Detected ${
+            Object.keys(porterYamlToJson.apps).length
+          } apps from porter.yaml`,
         });
       } else {
         setDetected({
@@ -144,10 +147,9 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
 
   // Deploys a Helm chart and writes build settings to the DB
   const isAppNameValid = (name: string) => {
-    const regex = /^[a-z0-9-]+$/;
+    const regex = /^[a-z0-9-]{1,61}$/;
     return regex.test(name);
   };
-
   const handleAppNameChange = (name: string) => {
     setCurrentStep(currentStep);
     setFormState({ ...formState, applicationName: name });
@@ -162,7 +164,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const shouldHighlightAppNameInput = () => {
     return (
       formState.applicationName !== "" &&
-      !isAppNameValid(formState.applicationName)
+      (!isAppNameValid(formState.applicationName) ||
+        formState.applicationName.length > 61)
     );
   };
 
@@ -193,11 +196,11 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       const base64Encoded = btoa(yamlString);
       const imageInfo = imageUrl
         ? {
-          image_info: {
-            repository: imageUrl,
-            tag: imageTag,
-          },
-        }
+            image_info: {
+              repository: imageUrl,
+              tag: imageTag,
+            },
+          }
         : {};
 
       // create the dummy chart
@@ -274,19 +277,21 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                 <Text color="helper">
                   Lowercase letters, numbers, and "-" only.
                 </Text>
-                <Spacer y={0.5}></Spacer>
                 <Input
                   placeholder="ex: academic-sophon"
                   value={formState.applicationName}
                   width="300px"
                   error={
                     shouldHighlightAppNameInput() &&
-                    'Lowercase letters, numbers, and "-" only.'
+                    (formState.applicationName.length > 61
+                      ? "Maximum 61 characters allowed."
+                      : 'Lowercase letters, numbers, and "-" only.')
                   }
                   setValue={(e) => {
                     handleAppNameChange(e);
                   }}
                 />
+
                 {shouldHighlightAppNameInput()}
               </>,
               <>

+ 13 - 6
dashboard/src/main/home/app-dashboard/new-app-flow/Services.tsx

@@ -60,10 +60,12 @@ const Services: React.FC<ServicesProps> = ({ services, setServices }) => {
           <Spacer y={0.5} />
         </>
       )}
-      <AddServiceButton onClick={() => {
-        setShowAddServiceModal(true);
-        setServiceType("web");
-      }}>
+      <AddServiceButton
+        onClick={() => {
+          setShowAddServiceModal(true);
+          setServiceType("web");
+        }}
+      >
         <i className="material-icons add-icon">add_icon</i>
         Add a new service
       </AddServiceButton>
@@ -101,6 +103,7 @@ const Services: React.FC<ServicesProps> = ({ services, setServices }) => {
               (serviceName != "" &&
                 !isServiceNameValid(serviceName) &&
                 'Lowercase letters, numbers, and "-" only.') ||
+              (serviceName.length > 61 && "Must be 61 characters or less.") ||
               (isServiceNameDuplicate(serviceName) &&
                 "Service name is duplicate")
             }
@@ -111,7 +114,10 @@ const Services: React.FC<ServicesProps> = ({ services, setServices }) => {
             onClick={() => {
               setServices([
                 ...services,
-                Service.default(serviceName, serviceType, { readOnly: false, value: '' }),
+                Service.default(serviceName, serviceType, {
+                  readOnly: false,
+                  value: "",
+                }),
               ]);
               setShowAddServiceModal(false);
               setServiceName("");
@@ -119,7 +125,8 @@ const Services: React.FC<ServicesProps> = ({ services, setServices }) => {
             }}
             disabled={
               !isServiceNameValid(serviceName) ||
-              isServiceNameDuplicate(serviceName)
+              isServiceNameDuplicate(serviceName) ||
+              serviceName?.length > 61
             }
           >
             <I className="material-icons">add</I> Add service

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/NotificationSettingsSection.tsx

@@ -7,7 +7,7 @@ import api from "shared/api";
 import { Context } from "shared/Context";
 import { ChartType } from "shared/types";
 import Loading from "components/Loading";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import styled from "styled-components";
 
 const NOTIF_CATEGORIES = ["success", "fail"];

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/build-settings/BuildSettingsTab.tsx

@@ -15,7 +15,7 @@ import styled from "styled-components";
 import yaml from "js-yaml";
 import { AxiosError } from "axios";
 import BranchList from "components/repo-selector/BranchList";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import { UpdateBuildconfigResponse } from "./types";
 import BuildpackConfigSection from "./_BuildpackConfigSection";
 import InputRow from "components/form-components/InputRow";

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/logs-section/LogsSection.tsx

@@ -20,7 +20,7 @@ import dayjs from "dayjs";
 import Loading from "components/Loading";
 import _ from "lodash";
 import { ChartType } from "shared/types";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 
 export type InitLogData = Partial<{
   podName: string;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/components/PreviewEnvironmentsHeader.tsx

@@ -3,7 +3,7 @@ import styled from "styled-components";
 import DashboardHeader from "../../DashboardHeader";
 import PullRequestIcon from "assets/pull_request_icon.svg";
 import api from "shared/api";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import Spacer from "components/porter/Spacer";
 
 export const PreviewEnvironmentsHeader = () => {

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentDetail.tsx

@@ -13,7 +13,7 @@ import ChartList from "../../chart/ChartList";
 import github from "assets/github-white.png";
 import { integrationList } from "shared/common";
 import { capitalize } from "shared/string_utils";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import Modal from "main/home/modals/Modal";
 import { validatePorterYAML } from "../utils";
 import Placeholder from "components/Placeholder";

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -13,7 +13,7 @@ import DynamicLink from "components/DynamicLink";
 import DashboardHeader from "../../DashboardHeader";
 import RadioFilter from "components/RadioFilter";
 import Placeholder from "components/Placeholder";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 
 import pullRequestIcon from "assets/pull_request_icon.svg";
 import filterOutline from "assets/filter-outline.svg";

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreateBranchEnvironment.tsx

@@ -6,7 +6,7 @@ import Helper from "components/form-components/Helper";
 import api from "shared/api";
 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
 import { validatePorterYAML } from "../utils";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import { useRouting } from "shared/routing";
 import PorterYAMLErrorsModal from "../components/PorterYAMLErrorsModal";
 import Placeholder from "components/Placeholder";

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/CreatePREnvironment.tsx

@@ -8,7 +8,7 @@ import api from "shared/api";
 import { EllipsisTextWrapper } from "../components/styled";
 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
 import { getPRDeploymentList, validatePorterYAML } from "../utils";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import { useRouting } from "shared/routing";
 import PorterYAMLErrorsModal from "../components/PorterYAMLErrorsModal";
 import Placeholder from "components/Placeholder";

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/preview-environments/environments/EnvironmentSettings.tsx

@@ -14,7 +14,7 @@ import SaveButton from "components/SaveButton";
 import _ from "lodash";
 import { Context } from "shared/Context";
 import PageNotFound from "components/PageNotFound";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import InputRow from "components/form-components/InputRow";
 import Modal from "main/home/modals/Modal";
 import { useRouting } from "shared/routing";

+ 1 - 1
dashboard/src/main/home/dashboard/ClusterSection.tsx

@@ -3,7 +3,7 @@ import styled from "styled-components";
 
 import { Context } from "shared/Context";
 
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 
 import ProvisionerFlow from "components/ProvisionerFlow";
 import ClusterList from "./ClusterList";

+ 1 - 1
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -17,7 +17,7 @@ import FormDebugger from "components/porter-form/FormDebugger";
 import TitleSection from "components/TitleSection";
 import ClusterSection from "./ClusterSection";
 import { StatusPage } from "../onboarding/steps/ProvisionResources/forms/StatusPage";
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 import Spacer from "components/porter/Spacer";
 
 type Props = RouteComponentProps & WithAuthProps & {

+ 1 - 1
dashboard/src/main/home/modals/UsageWarningModal.tsx

@@ -1,7 +1,7 @@
 import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
-import Banner from "components/Banner";
+import Banner from "components/porter/Banner";
 
 import { Context } from "shared/Context";
 import { Usage, UsageData } from "shared/types";

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

@@ -217,6 +217,7 @@ const updatePorterApp = baseApi<
     buildpacks?: string;
     dockerfile?: string;
     image_repo_uri?: string;
+    pull_request_url?: string;
   },
   {
     project_id: number;