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

Merge branch 'simplified-view' of github.com:porter-dev/porter into simplified-view

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

+ 7 - 0
api/server/handlers/infra/forms.go

@@ -735,6 +735,13 @@ tabs:
     - type: array-input
       variable: azs
       label: Availability Zones
+  - name: net_settings_single_az_nat_gateway
+    contents:
+    - type: checkbox
+      variable: single_az_nat_gateway
+      label: "Place a NAT gateway inside a single AZ. Disabling this will place a NAT gateway in each AZ, for each subnet in your cluster's VPC."
+      settings:
+        default: true
   - name: nginx_settings
     contents:
     - type: heading

+ 1 - 4
api/server/shared/config/env/envconfs.go

@@ -46,10 +46,7 @@ type ServerConf struct {
 	GithubAppWebhookSecret string `env:"GITHUB_APP_WEBHOOK_SECRET"`
 	GithubAppID            string `env:"GITHUB_APP_ID"`
 	GithubAppSecretPath    string `env:"GITHUB_APP_SECRET_PATH"`
-	// GithubAppSecretBase64 is a base64 encoded version of the GithubAppSecret. This can be used instead of GithubAppSecretPath to pass in a key, allowing for support in systems where mounting the secret is not possible.
-	// If GithubAppSecretBase64 is set, it will check for a file at GithubAppSecretPath. If a file is found, the file will NOT be overwritten. If no file it found, then GithubAppSecretBase64 will be decoded and written to GithubAppSecretPath.
-	GithubAppSecretBase64 string `env:"GITHUB_APP_SECRET_BASE64"`
-	GithubAppSecret       []byte
+	GithubAppSecret        []byte
 
 	GoogleClientID         string `env:"GOOGLE_CLIENT_ID"`
 	GoogleClientSecret     string `env:"GOOGLE_CLIENT_SECRET"`

+ 8 - 39
api/server/shared/config/loader/loader.go

@@ -1,10 +1,8 @@
 package loader
 
 import (
-	"encoding/base64"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -186,51 +184,22 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 		res.Logger.Info().Msg("Created Github client")
 	}
 
-	if sc.GithubAppSecretBase64 != "" {
-		if sc.GithubAppSecretPath == "" {
-			sc.GithubAppSecretPath = "github-app-secret-key"
-		}
-		_, err := os.Stat(sc.GithubAppSecretPath)
-		if err != nil {
-			if !errors.Is(err, os.ErrNotExist) {
-				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error checking if GITHUB_APP_SECRET_PATH exists: %w", err)
-			}
-			secret, err := base64.StdEncoding.DecodeString(sc.GithubAppSecretBase64)
-			if err != nil {
-				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error decoding: %w", err)
-			}
-			_, err = createDirectoryRecursively(sc.GithubAppSecretPath)
-			if err != nil {
-				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error creating directory for GITHUB_APP_SECRET_PATH: %w", err)
-			}
-			err = os.WriteFile(sc.GithubAppSecretPath, secret, os.ModePerm)
-			if err != nil {
-				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error writing to GITHUB_APP_SECRET_PATH: %w", err)
-			}
-		}
-	}
-
 	if sc.GithubAppClientID != "" &&
 		sc.GithubAppClientSecret != "" &&
 		sc.GithubAppName != "" &&
 		sc.GithubAppWebhookSecret != "" &&
 		sc.GithubAppSecretPath != "" &&
 		sc.GithubAppID != "" {
-		if AppID, err := strconv.ParseInt(sc.GithubAppID, 10, 64); err == nil {
-			res.GithubAppConf = oauth.NewGithubAppClient(&oauth.Config{
-				ClientID:     sc.GithubAppClientID,
-				ClientSecret: sc.GithubAppClientSecret,
-				Scopes:       []string{"read:user"},
-				BaseURL:      sc.ServerURL,
-			}, sc.GithubAppName, sc.GithubAppWebhookSecret, sc.GithubAppSecretPath, AppID)
-		}
-
-		secret, err := ioutil.ReadFile(sc.GithubAppSecretPath)
+		AppID, err := strconv.Atoi(sc.GithubAppID)
 		if err != nil {
-			return nil, fmt.Errorf("could not read github app secret: %s", err)
+			return nil, fmt.Errorf("could not read github App ID: %s", err)
 		}
-
-		sc.GithubAppSecret = append(sc.GithubAppSecret, secret...)
+		res.GithubAppConf = oauth.NewGithubAppClient(&oauth.Config{
+			ClientID:     sc.GithubAppClientID,
+			ClientSecret: sc.GithubAppClientSecret,
+			Scopes:       []string{"read:user"},
+			BaseURL:      sc.ServerURL,
+		}, sc.GithubAppName, sc.GithubAppWebhookSecret, sc.GithubAppSecretPath, int64(AppID))
 	}
 
 	if sc.SlackClientID != "" && sc.SlackClientSecret != "" {

+ 2 - 0
dashboard/src/components/repo-selector/ActionConfBranchSelector.tsx

@@ -50,6 +50,7 @@ const ActionConfEditorStack: React.FC<Props> = (props) => {
       </ExpandedWrapperAlt>
     );
   } else if (!branch) {
+    props.setFolderPath("./");
     return (
       <>
         <ExpandedWrapperAlt>
@@ -74,6 +75,7 @@ const ActionConfEditorStack: React.FC<Props> = (props) => {
       <BackButton
         width="145px"
         onClick={() => {
+          props.setFolderPath("");
           setBranch("");
           props.setDockerfilePath("");
         }}

+ 1 - 0
dashboard/src/components/repo-selector/ActionConfEditorStack.tsx

@@ -64,6 +64,7 @@ const ActionConfEditorStack: React.FC<Props> = (props) => {
           onClick={() => {
             setActionConfig({ ...defaultActionConfig });
             setBranch("");
+            props.setFolderPath("");
             props.setDockerfilePath("");
           }}
         >

+ 740 - 0
dashboard/src/components/repo-selector/DetectContentsList.tsx

@@ -0,0 +1,740 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+import file from "assets/file.svg";
+import folder from "assets/folder.svg";
+import info from "assets/info.svg";
+import close from "assets/close.png";
+
+import api from "../../shared/api";
+import { Context } from "../../shared/Context";
+import { ActionConfigType, FileType } from "../../shared/types";
+
+import Loading from "../Loading";
+import Spacer from "components/porter/Spacer";
+import AdvancedBuildSettings from "main/home/app-dashboard/new-app-flow/AdvancedBuildSettings";
+
+interface AutoBuildpack {
+  name?: string;
+  valid: boolean;
+}
+
+type PropsType = {
+  actionConfig: ActionConfigType | null;
+  branch: string;
+  dockerfilePath?: string;
+  folderPath: string;
+  procfilePath?: string;
+  setActionConfig: (x: ActionConfigType) => void;
+  setProcfileProcess?: (x: string) => void;
+  setDockerfilePath: (x: string) => void;
+  setProcfilePath: (x: string) => void;
+  setFolderPath: (x: string) => void;
+};
+
+type StateType = {
+  loading: boolean;
+  error: boolean;
+  contents: FileType[];
+  currentDir: string;
+  dockerfiles: string[];
+  processes: Record<string, string>;
+  autoBuildpack: AutoBuildpack;
+  showingBuildContextPrompt: boolean;
+};
+
+export default class DetectContentsList extends Component<
+  PropsType,
+  StateType
+> {
+  state = {
+    loading: true,
+    error: false,
+    contents: [] as FileType[],
+    currentDir: "",
+    dockerfiles: [] as string[],
+    processes: null as Record<string, string>,
+    autoBuildpack: {
+      valid: false,
+      name: "",
+    },
+    showingBuildContextPrompt: true,
+  };
+
+  componentDidMount() {
+    this.updateContents();
+  }
+
+  setSubdirectory = (x: string) => {
+    this.setState({ currentDir: x }, () => this.updateContents());
+  };
+
+  fetchContents = () => {
+    let { currentProject } = this.context;
+    const { actionConfig, branch } = this.props;
+
+    if (actionConfig.kind === "gitlab") {
+      return api
+        .getGitlabFolderContent(
+          "<token>",
+          { dir: this.state.currentDir || "./" },
+          {
+            project_id: currentProject.id,
+            integration_id: actionConfig.gitlab_integration_id,
+            repo_owner: actionConfig.git_repo.split("/")[0],
+            repo_name: actionConfig.git_repo.split("/")[1],
+            branch: branch,
+          }
+        )
+        .then((res) => {
+          const { data } = res;
+
+          return {
+            data: data.map((x: FileType) => ({
+              ...x,
+              type: x.type === "tree" ? "dir" : "file",
+            })),
+          };
+        });
+    }
+    return api.getBranchContents(
+      "<token>",
+      { dir: this.state.currentDir || "./" },
+      {
+        project_id: currentProject.id,
+        git_repo_id: actionConfig.git_repo_id,
+        kind: "github",
+        owner: actionConfig.git_repo.split("/")[0],
+        name: actionConfig.git_repo.split("/")[1],
+        branch: branch,
+      }
+    );
+  };
+
+  detectBuildpacks = () => {
+    let { currentProject } = this.context;
+    let { actionConfig, branch } = this.props;
+
+    if (actionConfig.kind === "github") {
+      return api.detectBuildpack(
+        "<token>",
+        {
+          dir: this.state.currentDir || ".",
+        },
+        {
+          project_id: currentProject.id,
+          git_repo_id: actionConfig.git_repo_id,
+          kind: "github",
+          owner: actionConfig.git_repo.split("/")[0],
+          name: actionConfig.git_repo.split("/")[1],
+          branch: branch,
+        }
+      );
+    }
+
+    return api.detectGitlabBuildpack(
+      "<token>",
+      { dir: this.state.currentDir || "." },
+      {
+        project_id: currentProject.id,
+        integration_id: actionConfig.gitlab_integration_id,
+
+        repo_owner: actionConfig.git_repo.split("/")[0],
+        repo_name: actionConfig.git_repo.split("/")[1],
+        branch: branch,
+      }
+    );
+  };
+
+  fetchProcfileContent = (procfilePath: string) => {
+    let { currentProject } = this.context;
+    let { actionConfig, branch } = this.props;
+    if (actionConfig.kind === "github") {
+      return api.getProcfileContents(
+        "<token>",
+        {
+          path: procfilePath,
+        },
+        {
+          project_id: currentProject.id,
+          git_repo_id: actionConfig.git_repo_id,
+          kind: "github",
+          owner: actionConfig.git_repo.split("/")[0],
+          name: actionConfig.git_repo.split("/")[1],
+          branch: branch,
+        }
+      );
+    }
+
+    return api.getGitlabProcfileContents(
+      "<token>",
+      { path: procfilePath },
+      {
+        project_id: currentProject.id,
+        integration_id: actionConfig.gitlab_integration_id,
+        owner: actionConfig.git_repo.split("/")[0],
+        name: actionConfig.git_repo.split("/")[1],
+        branch: branch,
+      }
+    );
+  };
+
+  updateContents = () => {
+    // Get branch contents
+    this.fetchContents()
+      .then((res) => {
+        let files = [] as FileType[];
+        let folders = [] as FileType[];
+        res.data.map((x: FileType, i: number) => {
+          x.type === "dir" ? folders.push(x) : files.push(x);
+        });
+
+        folders.sort((a: FileType, b: FileType) => {
+          return a.path < b.path ? 1 : 0;
+        });
+        files.sort((a: FileType, b: FileType) => {
+          return a.path < b.path ? 1 : 0;
+        });
+        let contents = folders.concat(files);
+
+        this.setState({ contents, loading: false, error: false });
+      })
+      .catch((err) => {
+        console.log(err);
+
+        this.setState({ loading: false, error: true });
+      });
+
+    this.detectBuildpacks()
+      .then(({ data }) => {
+        this.setState({
+          autoBuildpack: data,
+        });
+      })
+      .catch((err) => {
+        console.log(err);
+        this.setState({
+          autoBuildpack: {
+            valid: false,
+          },
+        });
+      });
+
+    let ppath =
+      this.props.procfilePath ||
+      `${this.state.currentDir ? this.state.currentDir : "."}/Procfile`;
+    this.fetchProcfileContent(ppath)
+      .then(({ data }) => {
+        this.setState({ processes: data });
+      })
+      .catch((err) => {
+        console.log(err);
+      });
+  };
+
+  renderContentList = () => {
+    let { contents, loading, error } = this.state;
+    if (loading) {
+      return (
+        <LoadingWrapper>
+          <Loading />
+        </LoadingWrapper>
+      );
+    } else if (error || !contents) {
+      return <LoadingWrapper>Error loading repo contents.</LoadingWrapper>;
+    }
+    return contents.map((item: FileType, i: number) => {
+      let splits = item.path.split("/");
+      let fileName = splits[splits.length - 1];
+      console.log(fileName);
+      if (fileName.includes("Dockerfile")) {
+        this.props.setDockerfilePath(item.path);
+        return (
+          <DetectedBuildMessage>
+            <i className="material-icons">check</i>
+            Detected Dockerfile at ./{item.path}
+          </DetectedBuildMessage>
+        );
+      }
+    });
+  };
+
+  renderJumpToParent = () => {
+    if (this.state.currentDir !== "") {
+      let splits = this.state.currentDir.split("/");
+      let subdir = "";
+      if (splits.length !== 1) {
+        subdir = this.state.currentDir.replace(splits[splits.length - 1], "");
+        if (subdir.charAt(subdir.length - 1) === "/") {
+          subdir = subdir.slice(0, subdir.length - 1);
+        }
+      }
+
+      return (
+        <Item lastItem={false} onClick={() => this.setSubdirectory(subdir)}>
+          <BackLabel>..</BackLabel>
+        </Item>
+      );
+    }
+
+    return (
+      <FileItem lastItem={false}>
+        <img src={info} />
+        Select{" "}
+        {this.props.dockerfilePath
+          ? "Docker Build Context"
+          : "Application Folder"}
+      </FileItem>
+    );
+  };
+
+  renderOverlay = () => {
+    if (this.props.procfilePath) {
+      let processes = this.state.processes
+        ? Object.keys(this.state.processes)
+        : [];
+      if (this.state.processes == null) {
+        return (
+          <Overlay>
+            <BgOverlay>
+              <LoadingWrapper>
+                <Loading />
+              </LoadingWrapper>
+            </BgOverlay>
+          </Overlay>
+        );
+      }
+
+      if (processes.length == 0) {
+        this.props.setProcfilePath("");
+      }
+
+      return (
+        <Overlay>
+          <BgOverlay
+            onClick={() =>
+              this.setState({ dockerfiles: [] }, () => {
+                this.props.setFolderPath("");
+                this.props.setProcfilePath("");
+              })
+            }
+          />
+          <CloseButton
+            onClick={() =>
+              this.setState({ dockerfiles: [] }, () => {
+                this.props.setProcfilePath("");
+              })
+            }
+          >
+            <CloseButtonImg src={close} />
+          </CloseButton>
+          <Label>
+            Porter has detected a Procfile in this folder. Which process would
+            you like to run?
+          </Label>
+          <DockerfileList>
+            {processes.map((process: string, i: number) => {
+              return (
+                <Row
+                  key={i}
+                  onClick={() => {
+                    if (
+                      !this.props.folderPath ||
+                      this.props.folderPath === ""
+                    ) {
+                      this.props.setFolderPath("./");
+                    }
+                    this.props.setProcfileProcess(process);
+                  }}
+                  isLast={processes.length - 1 === i}
+                >
+                  <Indicator selected={false} />
+                  {process}
+                </Row>
+              );
+            })}
+          </DockerfileList>
+        </Overlay>
+      );
+    }
+    if (this.state.dockerfiles.length > 0 && !this.props.dockerfilePath) {
+      return (
+        <Overlay>
+          <BgOverlay onClick={() => this.setState({ dockerfiles: [] })} />
+          <CloseButton onClick={() => this.setState({ dockerfiles: [] })}>
+            <CloseButtonImg src={close} />
+          </CloseButton>
+          <Label>
+            Porter has detected at least one Dockerfile in this folder. Would
+            you like to use an existing Dockerfile?
+          </Label>
+          <DockerfileList>
+            {this.state.dockerfiles.map((dockerfile: string, i: number) => {
+              return (
+                <Row
+                  key={i}
+                  onClick={() =>
+                    this.props.setDockerfilePath(
+                      `${this.state.currentDir || "."}/${dockerfile}`
+                    )
+                  }
+                  isLast={this.state.dockerfiles.length - 1 === i}
+                >
+                  <Indicator selected={false}></Indicator>
+                  {dockerfile}
+                </Row>
+              );
+            })}
+          </DockerfileList>
+          <ConfirmButton
+            onClick={() => {
+              this.props.setFolderPath(this.state.currentDir || "./");
+              if (
+                this.state.processes &&
+                Object.keys(this.state.processes).length > 0
+              ) {
+                this.props.setProcfilePath("./Procfile");
+              }
+            }}
+          >
+            No, I don't want to use a Dockerfile
+          </ConfirmButton>
+        </Overlay>
+      );
+    }
+    if (
+      this.props.dockerfilePath &&
+      !this.props.folderPath &&
+      this.state.showingBuildContextPrompt
+    ) {
+      return (
+        <Overlay>
+          <BgOverlay onClick={() => this.props.setDockerfilePath("")} />
+          <CloseButton
+            onClick={() =>
+              this.props.setFolderPath(this.state.currentDir || "./")
+            }
+          >
+            <CloseButtonImg src={close} />
+          </CloseButton>
+          <Label>
+            Would you like to set the Docker build context to a different
+            directory?
+          </Label>
+          <MultiSelectRow>
+            <ConfirmButton
+              onClick={() => {
+                this.setState({ showingBuildContextPrompt: false });
+                this.setSubdirectory("");
+              }}
+            >
+              Yes
+            </ConfirmButton>
+            <ConfirmButton
+              onClick={() =>
+                this.props.setFolderPath(this.state.currentDir || "./")
+              }
+            >
+              No
+            </ConfirmButton>
+          </MultiSelectRow>
+        </Overlay>
+      );
+    }
+  };
+
+  render() {
+    return (
+      <>
+        {this.renderContentList()}
+
+        {this.state.autoBuildpack && this.state.autoBuildpack.valid ? (
+          <Banner>
+            <i className="material-icons">info</i>{" "}
+            <p>
+              <b>{this.state.autoBuildpack.name}</b> buildpack was{" "}
+              <a
+                href="https://docs.porter.run/deploying-applications/deploying-from-github/selecting-application-and-build-method#customizing-buildpacks"
+                target="_blank"
+              >
+                detected automatically
+              </a>
+              . Alternatively, select an application folder below:
+            </p>
+          </Banner>
+        ) : (
+          <>
+            <Spacer y={1} />
+            <AdvancedBuildSettings />
+          </>
+        )}
+      </>
+    );
+  }
+}
+
+DetectContentsList.contextType = Context;
+
+const FlexWrapper = styled.div`
+  position: absolute;
+  bottom: 28px;
+  left: 195px;
+  display: flex;
+  align-items: center;
+`;
+
+const StatusWrapper = styled.a<{ successful?: boolean }>`
+  display: flex;
+  align-items: center;
+  font-family: "Work Sans", sans-serif;
+  font-size: 13px;
+  color: #949eff;
+  margin-right: 25px;
+  margin-left: 20px;
+  cursor: pointer;
+  text-decoration: none;
+
+  > i {
+    font-size: 18px;
+    margin-right: 8px;
+  }
+`;
+
+const BgOverlay = styled.div`
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+  background-color: rgba(0, 0, 0, 0.8);
+  z-index: -1;
+`;
+
+const CloseButton = styled.div`
+  position: absolute;
+  display: block;
+  width: 40px;
+  height: 40px;
+  padding: 13px 0 12px 0;
+  z-index: 1;
+  text-align: center;
+  border-radius: 50%;
+  right: 15px;
+  top: 12px;
+  cursor: pointer;
+  :hover {
+    background-color: #ffffff11;
+  }
+`;
+
+const CloseButtonImg = styled.img`
+  width: 14px;
+  margin: 0 auto;
+`;
+
+const Indicator = styled.div<{ selected: boolean }>`
+  border-radius: 15px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 16px;
+  height: 16px;
+  border: 1px solid #ffffff55;
+  margin: 1px 10px 0px 1px;
+  margin-right: 13px;
+  background: ${(props) => (props.selected ? "#ffffff22" : "#ffffff11")};
+`;
+
+const Label = styled.div`
+  max-width: 500px;
+  line-height: 1.5em;
+  text-align: center;
+  font-size: 14px;
+`;
+
+const MultiSelectRow = styled.div`
+  display: flex;
+  min-width: 150px;
+  justify-content: space-between;
+`;
+
+const DockerfileList = styled.div`
+  border-radius: 3px;
+  margin-top: 20px;
+  border: 1px solid #aaaabb;
+  background: #ffffff22;
+  width: 100%;
+  max-width: 500px;
+  max-height: 140px;
+  overflow-y: auto;
+`;
+
+const Row = styled.div<{ isLast: boolean }>`
+  height: 35px;
+  padding-left: 10px;
+  display: flex;
+  align-items: center;
+  border-bottom: ${(props) => !props.isLast && "1px solid #aaaabb"};
+  cursor: pointer;
+  :hover {
+    background: #ffffff22;
+  }
+`;
+
+const ConfirmButton = styled.div`
+  font-size: 18px;
+  padding: 7px 12px;
+  outline: none;
+  border: 1px solid white;
+  margin-top: 25px;
+  border-radius: 10px;
+  text-align: center;
+  cursor: pointer;
+  opacity: 0;
+  font-family: "Work Sans", sans-serif;
+  font-size: 14px;
+  font-weight: 500;
+  animation: linEnter 0.3s 0.1s;
+  animation-fill-mode: forwards;
+  @keyframes linEnter {
+    from {
+      transform: translateY(20px);
+      opacity: 0;
+    }
+    to {
+      transform: translateY(0px);
+      opacity: 1;
+    }
+  }
+  :hover {
+    background: white;
+    color: #232323;
+  }
+`;
+
+const Overlay = styled.div`
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  padding: 0 90px;
+`;
+
+const UseButton = styled.div`
+  height: 35px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #616feecc;
+  font-weight: 500;
+  padding: 10px 15px;
+  border-radius: 100px;
+  cursor: pointer;
+  :hover {
+    filter: brightness(120%);
+  }
+`;
+
+const BackLabel = styled.div`
+  font-size: 16px;
+  padding-left: 16px;
+  margin-top: -4px;
+  padding-bottom: 4px;
+`;
+
+const Item = styled.div`
+  display: flex;
+  width: 100%;
+  font-size: 13px;
+  border-bottom: 1px solid
+    ${(props: { lastItem: boolean; isSelected?: boolean }) =>
+      props.lastItem ? "#00000000" : "#606166"};
+  color: #ffffff;
+  user-select: none;
+  align-items: center;
+  padding: 10px 0px;
+  cursor: pointer;
+  background: ${(props: { isSelected?: boolean; lastItem: boolean }) =>
+    props.isSelected ? "#ffffff22" : "#ffffff11"};
+  :hover {
+    background: #ffffff22;
+
+    > i {
+      background: #ffffff22;
+    }
+  }
+
+  > img {
+    width: 18px;
+    height: 18px;
+    margin-left: 12px;
+    margin-right: 12px;
+  }
+`;
+
+const FileItem = styled(Item)`
+  cursor: ${(props: { isADocker?: boolean }) =>
+    props.isADocker ? "pointer" : "default"};
+  color: ${(props: { isADocker?: boolean }) =>
+    props.isADocker ? "#fff" : "#ffffff55"};
+  :hover {
+    background: ${(props: { isADocker?: boolean }) =>
+      props.isADocker ? "#ffffff22" : "#ffffff11"};
+  }
+`;
+
+const LoadingWrapper = styled.div`
+  padding: 30px 0px;
+  background: #ffffff11;
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #ffffff44;
+`;
+
+const ExpandedWrapper = styled.div`
+  margin-top: 10px;
+  width: 100%;
+  border-radius: 3px;
+  border: 1px solid #ffffff44;
+  max-height: 275px;
+  overflow-y: auto;
+`;
+
+const ExpandedWrapperAlt = styled(ExpandedWrapper)``;
+
+const Banner = styled.div`
+  height: 40px;
+  width: 100%;
+  margin: 5px 0 10px;
+  font-size: 13px;
+  display: flex;
+  border-radius: 8px;
+  padding-left: 15px;
+  align-items: center;
+  background: #ffffff11;
+  > i {
+    margin-right: 10px;
+    font-size: 18px;
+  }
+`;
+const DetectedBuildMessage = styled.div`
+  color: #0f872b;
+  display: flex;
+  align-items: center;
+  border-radius: 5px;
+  margin-right: 10px;
+
+  > i {
+    margin-right: 6px;
+    font-size: 20px;
+    border-radius: 20px;
+    transform: none;
+  }
+`;

+ 22 - 21
dashboard/src/main/home/app-dashboard/new-app-flow/SourceSettings.tsx

@@ -11,6 +11,7 @@ import { ActionConfigType } from "shared/types";
 import { RouteComponentProps } from "react-router";
 import { Context } from "shared/Context";
 import ActionConfBranchSelector from "components/repo-selector/ActionConfBranchSelector";
+import DetectContentsList from "components/repo-selector/DetectContentsList";
 
 type PropsType = RouteComponentProps & {
   source: SourceType | undefined;
@@ -73,17 +74,6 @@ class SourceSettings extends Component<PropsType, StateType> {
         {
           <>
             {" "}
-            {/* <CloseButton
-        onClick={() => {
-          setSourceType("");
-          setDockerfilePath("");
-          setFolderPath("");
-          setProcfilePath("");
-          setProcfileProcess("");
-        }}
-      >
-        <i className="material-icons">close</i>
-      </CloseButton> */}
             <Subtitle>
               Provide a repo folder to use as source.
               {/* <Highlight
@@ -196,12 +186,13 @@ class SourceSettings extends Component<PropsType, StateType> {
         <Text color="helper">Specify your application root path.</Text>
         <Spacer y={0.5} />
         <Input
+          disabled={!this.props.branch ? true : false}
           placeholder="ex: ./"
-          value=""
+          value={this.props.folderPath}
           width="100%"
-          setValue={(e) => {}}
+          setValue={this.props.setFolderPath}
         />
-        <Spacer y={0.5} />
+        {/* <Spacer y={0.5} />
         <Text color="helper">
           Specify your porter.yaml path. <a>&nbsp;What is this?</a>
         </Text>
@@ -212,13 +203,23 @@ class SourceSettings extends Component<PropsType, StateType> {
           width="100%"
           setValue={(e) => {}}
         />
-        <Spacer y={1} />
-        <DetectedBuildMessage>
-          <i className="material-icons">check</i>
-          Detected Dockerfile at ./Dockerfile
-        </DetectedBuildMessage>
-        <Spacer y={1} />
-        <AdvancedBuildSettings />
+        */}
+        {this.props.actionConfig.git_repo && this.props.branch ? (
+          <DetectContentsList
+            actionConfig={this.props.actionConfig}
+            branch={this.props.branch}
+            dockerfilePath={this.props.dockerfilePath}
+            procfilePath={this.props.procfilePath}
+            folderPath={this.props.folderPath}
+            setActionConfig={this.props.setActionConfig}
+            setDockerfilePath={(x: string) => this.props.setDockerfilePath(x)}
+            setProcfilePath={(x: string) => this.props.setProcfilePath(x)}
+            setProcfileProcess={(x: string) => this.props.setProcfileProcess(x)}
+            setFolderPath={(x: string) => this.props.setFolderPath(x)}
+          />
+        ) : (
+          <></>
+        )}
       </>
     );
   };

+ 1 - 5
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupArray.tsx

@@ -153,11 +153,7 @@ const EnvGroupArray = ({
                   {!disabled && (
                     <DeleteButton
                       onClick={() => {
-                        let _values = values;
-                        _values = _values.filter(
-                          (val) => val.key !== entry.key
-                        );
-                        setValues(_values);
+                        setValues(values.filter((val, index) => index !== i));
                       }}
                     >
                       <i className="material-icons">cancel</i>

+ 5 - 4
docs/developing/setup.md

@@ -28,14 +28,15 @@ First, in `/dashboard/.env`:
 
 ```
 NODE_ENV=development
-API_SERVER=localhost:8080
+API_SERVER=http://localhost:8081
+ENABLE_PROXY=true
 ```
 
 Next, in `/docker/.env`:
 
 ```
-SERVER_URL=http://localhost:8080
-SERVER_PORT=8080
+SERVER_URL=http://localhost:8081
+SERVER_PORT=8081
 DB_HOST=postgres
 DB_PORT=5432
 DB_USER=porter
@@ -44,7 +45,7 @@ DB_NAME=porter
 SQL_LITE=false
 ```
 
-Once you've done this, go to the root repository, and run `docker-compose -f docker-compose.dev.yaml up`. You should see postgres, webpack, and porter containers spin up. When the webpack and porter containers have finished compiling and have spun up successfully (this will take 5-10 minutes after the containers start), .
+Finally, run `make start-dev` from the root of the project. If you have all the dependencies set up correctly, things should just work.
 
 At this point, you can make a change to any `.go` file to trigger a backend rebuild, and any file in `/dashboard/src` to trigger a hot reload.
 

+ 1 - 1
scripts/dev-environment/SetupEnvironment.sh

@@ -31,7 +31,7 @@ fi
 
 if [[ ! -d ./dashboard/node_modules ]]; then 
   echo "Couldn't find node_modules, installing npm packages"
-  cd ./dashboard && npm install;
+  cd ./dashboard && npm install --legacy-peer-deps;
   cd ../;  
 else
   echo "Node modules found! Proceeding to start server"