Justin Rhee 3 lat temu
rodzic
commit
161d90f30f

+ 46 - 29
Tiltfile

@@ -16,6 +16,36 @@ build_args = "GOOS=linux GOARCH=arm64"
 if os.getenv("PLATFORM") == "amd64":
     build_args = "GOOS=linux GOARCH=amd64"
 
+allow_k8s_contexts('kind-porter')
+cluster = str(local('kubectl config current-context')).strip()
+if (cluster.startswith("kind-")):
+    install = kustomize('zarf/helm', flags=["--enable-helm"])
+    decoded = decode_yaml_stream(install)
+    for d in decoded:
+        if d.get('kind') == 'Deployment':
+            if "securityContext" in d['spec']['template']['spec']:
+                d['spec']['template']['spec'].pop('securityContext')
+            for c in d['spec']['template']['spec']['containers']:
+                if "securityContext" in c:
+                    c.pop('securityContext')
+
+    updated_install = encode_yaml_stream(decoded)
+
+    k8s_yaml(updated_install)
+
+    k8s_resource(
+        workload='porter-server-web',
+        port_forwards="8080:8080",
+        labels=["porter"],
+        resource_deps=["porter-binary"],
+    )
+else:
+    local("echo 'Be careful that you aren't connected to a staging or prod cluster' && exit 1")
+    exit()
+
+watch_file('zarf/helm/.server.env')
+watch_file('zarf/helm/.dashboard.env')
+
 ## Build binary locally for faster devexp
 local_resource(
   name='porter-binary',
@@ -29,7 +59,7 @@ local_resource(
     "pkg",
   ],
   resource_deps=["postgresql"],
-  labels=["porter"]
+  labels=["z_binaries"]
 )
 
 docker_build_with_restart(
@@ -48,16 +78,23 @@ docker_build_with_restart(
     ], 
 ) 
 
+local_resource(
+  name='reload-server-config',
+  cmd='kubectl rollout restart deployment porter-server-web',
+  deps=[
+    "zarf/helm/.server.env"
+  ],
+  labels=["porter"],
+  resource_deps=["porter-server-web"]
+)
+
 # Frontend
 local_resource(
     name="porter-dashboard",
     serve_cmd="npm start",
     serve_dir="dashboard",
     serve_env={
-        "NODE_ENV": "development",
-        "DEV_SERVER_PORT": "8081",
-        "ENABLE_PROXY": "true",
-        "API_SERVER": "http://localhost:8080"
+        "ENV_FILE": "../zarf/helm/.dashboard.env"
     },
     resource_deps=["postgresql"],
     labels=["porter"]
@@ -70,34 +107,14 @@ local_resource(
     name="migrations-binary",
     cmd='''GOWORK=off CGO_ENABLED=0 %s go build -mod vendor -gcflags '-N -l' -tags ee -o ./bin/migrate ./cmd/migrate/main.go ./cmd/migrate/migrate_ee.go''' % build_args,
     resource_deps=["postgresql"],
-    labels=["porter"],
+    labels=["z_binaries"],
 )
 local_resource(
     name="run-migrations",
     cmd='''kubectl exec -it deploy/porter-server-web -- /app/migrate''',
-    resource_deps=["migrations-binary", "porter-binary"],
+    resource_deps=["migrations-binary", "porter-binary", "porter-server-web", "postgresql"],
+    deps=["postgresql"],
     labels=["porter"],
     trigger_mode=TRIGGER_MODE_MANUAL,
+    auto_init=False
 )
-
-
-allow_k8s_contexts('kind-porter')
-
-cluster = str(local('kubectl config current-context')).strip()
-if (cluster.startswith("kind-")):
-    install = kustomize('zarf/helm', flags=["--enable-helm"])
-    decoded = decode_yaml_stream(install)
-    for d in decoded:
-        if d.get('kind') == 'Deployment':
-            if "securityContext" in d['spec']['template']['spec']:
-                d['spec']['template']['spec'].pop('securityContext')
-            for c in d['spec']['template']['spec']['containers']:
-                if "securityContext" in c:
-                    c.pop('securityContext')
-
-    updated_install = encode_yaml_stream(decoded)
-    k8s_yaml(updated_install)
-    k8s_resource(workload='porter-server-web', port_forwards="8080:8080", labels=["porter"])
-else:
-    local("echo 'Be careful that you aren't connected to a staging or prod cluster' && exit 1")
-    exit()

+ 24 - 3
api/server/authn/handler.go

@@ -112,12 +112,33 @@ func (authn *AuthN) handleForbiddenForSession(
 
 		session.Save(r, w)
 
-		http.Redirect(w, r, "/dashboard", 302)
+		// special logic for GET /api/projects/{project_id}/invites/{token}
+		if r.Method == "GET" && strings.Contains(r.URL.Path, "/invites/") &&
+			!strings.HasSuffix(r.URL.Path, "/invites/") {
+			pathSegments := strings.Split(r.URL.Path, "/")
+			inviteToken := pathSegments[len(pathSegments)-1]
+
+			invite, err := authn.config.Repo.Invite().ReadInviteByToken(inviteToken)
+			if err != nil || invite.ProjectID == 0 || invite.Email == "" {
+				apierrors.HandleAPIError(authn.config.Logger, authn.config.Alerter, w, r,
+					apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid invite token"), http.StatusBadRequest), true)
+				return
+			}
+
+			if invite.IsExpired() || invite.IsAccepted() {
+				apierrors.HandleAPIError(authn.config.Logger, authn.config.Alerter, w, r,
+					apierrors.NewErrPassThroughToClient(fmt.Errorf("invite has expired"), http.StatusBadRequest), true)
+				return
+			}
+
+			http.Redirect(w, r, "/register?email="+invite.Email, http.StatusTemporaryRedirect)
+			return
+		}
+
+		http.Redirect(w, r, "/dashboard", http.StatusFound)
 	} else {
 		authn.sendForbiddenError(err, w, r)
 	}
-
-	return
 }
 
 func (authn *AuthN) verifyTokenWithNext(w http.ResponseWriter, r *http.Request, tok *token.Token) {

+ 0 - 2
api/server/authz/api_contract.go

@@ -45,7 +45,6 @@ func (n *APIContractRevisionMiddleware) ServeHTTP(w http.ResponseWriter, r *http
 		return
 	}
 
-	fmt.Println("STEFAN", uid)
 	rev, err := n.config.Repo.APIContractRevisioner().Get(ctx, uid)
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
@@ -57,7 +56,6 @@ func (n *APIContractRevisionMiddleware) ServeHTTP(w http.ResponseWriter, r *http
 		apierrors.HandleAPIError(n.config.Logger, n.config.Alerter, w, r, apierrors.NewErrInternal(err), true)
 		return
 	}
-	fmt.Println("STEFANREV", rev)
 
 	r = r.Clone(NewAPIContractRevisionContext(ctx, rev))
 	n.next.ServeHTTP(w, r)

+ 7 - 2
api/server/handlers/cluster/get_kubeconfig.go

@@ -2,6 +2,7 @@ package cluster
 
 import (
 	"context"
+	"encoding/base64"
 	"errors"
 	"fmt"
 	"net/http"
@@ -57,13 +58,17 @@ func (c *GetTemporaryKubeconfigHandler) ServeHTTP(w http.ResponseWriter, r *http
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting temporary capi config: %w", err)))
 			return
 		}
-
 		if kubeconfigResp.Msg == nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error reading temporary capi config: %w", err)))
 			return
 		}
+		b64, err := base64.StdEncoding.DecodeString(kubeconfigResp.Msg.KubeConfig)
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("unable to decode base64 kubeconfig: %w", err)))
+			return
+		}
 		res := &types.GetTemporaryKubeconfigResponse{
-			Kubeconfig: []byte(kubeconfigResp.Msg.KubeConfig),
+			Kubeconfig: b64,
 		}
 		c.WriteResult(w, r, res)
 		return

+ 1 - 1
api/server/handlers/project/create.go

@@ -41,7 +41,7 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	proj := &models.Project{
 		Name:                   request.Name,
-		CapiProvisionerEnabled: true,
+		// CapiProvisionerEnabled: true,
 	}
 
 	var err error

+ 1 - 1
api/server/handlers/project/create_test.go

@@ -42,7 +42,7 @@ func TestCreateProjectSuccessful(t *testing.T) {
 				ProjectID: 1,
 			},
 		},
-		CapiProvisionerEnabled: true,
+		CapiProvisionerEnabled: false,
 	}
 
 	gotProject := &types.CreateProjectResponse{}

+ 39 - 35
dashboard/src/components/ProvisionerSettings.tsx

@@ -120,10 +120,11 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
   };
   const isDisabled = () => {
     return (
-      (!clusterName && true) ||
-      (isReadOnly && props.provisionerError === "") ||
-      props.provisionerError === "" ||
-      isClicked
+      !user.email.endsWith("porter.run") &&
+      ((!clusterName && true) ||
+        (isReadOnly && props.provisionerError === "") ||
+        props.provisionerError === "" ||
+        isClicked)
     );
   };
   const createCluster = async () => {
@@ -176,46 +177,49 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     try {
       setIsReadOnly(true);
       setErrorMessage(undefined);
-      markStepStarted("pre-provisioning-check-started");
 
-      await api.preflightCheckAWSUsage(
-        "<token>",
-        {
-          target_arn: props.credentialId,
-          region: awsRegion,
-        },
-        {
-          id: currentProject.id,
-        }
-      );
+      if (!props.clusterId) {
+        markStepStarted("pre-provisioning-check-started");
+
+        await api.preflightCheckAWSUsage(
+          "<token>",
+          {
+            target_arn: props.credentialId,
+            region: awsRegion,
+          },
+          {
+            id: currentProject.id,
+          }
+        );
 
-      markStepStarted("provisioning-started");
+        markStepStarted("provisioning-started");
+      }
 
       const res = await api.createContract("<token>", data, {
         project_id: currentProject.id,
       });
 
       // Only refresh and set clusters on initial create
-      if (!props.clusterId) {
-        setShouldRefreshClusters(true);
-        api
-          .getClusters("<token>", {}, { id: currentProject.id })
-          .then(({ data }) => {
-            data.forEach((cluster: ClusterType) => {
-              if (cluster.id === res.data.contract_revision?.cluster_id) {
-                // setHasFinishedOnboarding(true);
-                setCurrentCluster(cluster);
-                OFState.actions.goTo("clean_up");
-                pushFiltered(props, "/cluster-dashboard", ["project_id"], {
-                  cluster: cluster.name,
-                });
-              }
-            });
-          })
-          .catch((err) => {
-            console.error(err);
+      // if (!props.clusterId) {
+      setShouldRefreshClusters(true);
+      api
+        .getClusters("<token>", {}, { id: currentProject.id })
+        .then(({ data }) => {
+          data.forEach((cluster: ClusterType) => {
+            if (cluster.id === res.data.contract_revision?.cluster_id) {
+              // setHasFinishedOnboarding(true);
+              setCurrentCluster(cluster);
+              OFState.actions.goTo("clean_up");
+              pushFiltered(props, "/cluster-dashboard", ["project_id"], {
+                cluster: cluster.name,
+              });
+            }
           });
-      }
+        })
+        .catch((err) => {
+          console.error(err);
+        });
+      // }
       setErrorMessage(undefined);
     } catch (err) {
       const errMessage = err.response.data.error.replace("unknown: ", "");

+ 7 - 3
dashboard/src/components/porter/Input.tsx

@@ -91,6 +91,7 @@ const StyledInput = styled.input<{
   width: string;
   height: string;
   hasError: boolean;
+  disabled: boolean;
 }>`
   height: ${props => props.height || "35px"};
   padding: 5px 10px;
@@ -100,9 +101,12 @@ const StyledInput = styled.input<{
   outline: none;
   border-radius: 5px;
   background: #26292e;
+  cursor: ${props => props.disabled ? "not-allowed" : ""};
 
   border: 1px solid ${props => props.hasError ? "#ff3b62" : "#494b4f"};
-  :hover {
-    border: 1px solid ${props => props.hasError ? "#ff3b62" : "#7a7b80"};
-  }
+  ${props => !props.disabled && `
+    :hover {
+      border: 1px solid ${props.hasError ? "#ff3b62" : "#7a7b80"};
+    }
+  `}
 `;

+ 18 - 9
dashboard/src/main/auth/Register.tsx

@@ -96,7 +96,12 @@ const Register: React.FC<Props> = ({
             authenticate();
           }
         })
-        .catch((err) => setCurrentError(err.response.data.error));
+        .catch((err) => {
+          console.log("registration:", err);
+          if (err.response?.data?.error) {
+            setCurrentError(err.response.data.error)
+          }
+        });
     }
   };
 
@@ -194,7 +199,7 @@ const Register: React.FC<Props> = ({
           Create your Porter account
         </Heading>
         <Spacer y={1} />
-        {(hasGithub || hasGoogle) && (
+        {((hasGithub || hasGoogle) && !disabled) && (
           <>
             <Container row>
               {hasGithub && (
@@ -305,13 +310,17 @@ const Register: React.FC<Props> = ({
             </Button>
           </>
         )}
-        <Spacer y={1} />
-        <Text 
-          size={13}
-          color="helper"
-        >
-          Already have an account?<Spacer width="5px" inline /><Link to="/login">Log in</Link>
-        </Text>
+        {!disabled && (
+          <>
+            <Spacer y={1} />
+            <Text 
+              size={13}
+              color="helper"
+            >
+              Already have an account?<Spacer width="5px" inline /><Link to="/login">Log in</Link>
+            </Text>
+          </>
+        )}
       </Wrapper>
     </StyledRegister>
   );

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

@@ -180,7 +180,7 @@ const ModalHandler: React.FC<{
         <Modal
           onRequestClose={() => setCurrentModal(null, null)}
           width="760px"
-          height="440px"
+          height="480px"
           title="Account Settings"
         >
           <AccountSettingsModal />

+ 86 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/GithubActionModal.tsx

@@ -0,0 +1,86 @@
+import Modal from "components/porter/Modal";
+import React from "react";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
+import ExpandableSection from "components/porter/ExpandableSection";
+import Fieldset from "components/porter/Fieldset";
+import styled from "styled-components";
+import Button from "components/porter/Button";
+import Input from "components/porter/Input";
+import Select from "components/porter/Select";
+
+interface GithubActionModalProps {
+    closeModal: () => void;
+}
+
+const GithubActionModal: React.FC<GithubActionModalProps> = ({
+    closeModal,
+}) => {
+    return (
+        <Modal closeModal={closeModal}>
+            <Text size={16}>
+                Continuous Integration (CI) with GitHub Actions
+            </Text>
+            <Spacer height="15px" />
+            <Text color="helper">
+                In order to automatically update your services every time new code is pushed to your GitHub branch, you will need the following file in your Github repository:
+            </Text>
+            <Spacer y={1} />
+            <ExpandableSection
+                noWrapper
+                expandText="[+] Show code"
+                collapseText="[-] Hide code"
+                Header={
+                    <ModalHeader>./github/workflows/porter_deploy.yml</ModalHeader>
+                }
+                ExpandedSection={
+                    <>
+                        <Spacer height="15px" />
+                        <Fieldset background="#1b1d2688">
+                            • Amazon Elastic Kubernetes Service (EKS) = $73/mo
+                            <Spacer height="15px" />
+                            • Amazon EC2:
+                            <Spacer height="15px" />
+                            <Tab />+ System workloads: t3.medium instance (2) = $60.74/mo
+                            <Spacer height="15px" />
+                            <Tab />+ Monitoring workloads: t3.large instance (1) = $60.74/mo
+                            <Spacer height="15px" />
+                            <Tab />+ Application workloads: t3.xlarge instance (1) = $121.47/mo
+                        </Fieldset>
+                    </>
+                }
+            />
+            <Spacer y={1} />
+            <Text color="helper">
+                Porter can open a PR for you to approve and merge this file into your repository, or you can add it yourself.
+            </Text>
+            <Spacer y={1} />
+            <Select
+                options={[
+                    { label: "I authorize Porter to open a PR on my behalf", value: "I authorize Porter to open a PR on my behalf" },
+                    { label: "I will copy the file into my repository myself", value: "I will copy the file into my repository myself" },
+                ]}
+                onChange={(x: any) => console.log(x)}
+            />
+            <Button
+                onClick={closeModal}
+            >
+                Complete
+            </Button>
+        </Modal>
+    )
+}
+
+export default GithubActionModal;
+
+const Tab = styled.span`
+  margin-left: 20px;
+  height: 1px;
+`;
+
+const ModalHeader = styled.div`
+  font-weight: 600;
+  font-size: 20px;
+  font-family: monospace; ;
+
+`;

+ 6 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -26,6 +26,7 @@ import SourceSettings from "./SourceSettings"
 import Services from "./Services";
 import EnvGroupArray, { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
 import Select from "components/porter/Select";
+import GithubActionModal from "./GithubActionModal";
 
 type Props = RouteComponentProps & {
 };
@@ -63,6 +64,7 @@ const NewAppFlow: React.FC<Props> = ({
   const [isLoading, setIsLoading] = useState<boolean>(true);
   const [currentStep, setCurrentStep] = useState<number>(0);
   const [formState, setFormState] = useState<FormState>(INITIAL_STATE);
+  const [showGHAModal, setShowGHAModal] = useState<boolean>(false);
 
   return (
     <CenterWrapper>
@@ -169,11 +171,12 @@ const NewAppFlow: React.FC<Props> = ({
           ]}
         />
         <Spacer y={1} />
-        <Button onClick={() => ({})}>
+        <Button onClick={() => setShowGHAModal(true)}>
           DEPLYOY
         </Button>
       </StyledConfigureTemplate>
       </Div>
+      {showGHAModal && <GithubActionModal closeModal={() => setShowGHAModal(false)} />}
     </CenterWrapper>
   );
 };
@@ -219,3 +222,5 @@ const StyledConfigureTemplate = styled.div`
   height: 100%;
 `;
 
+
+

+ 9 - 7
dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx

@@ -51,7 +51,7 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
       .then((res) => {
         if (_isMounted) {
           let namespaceOptions: { label: string; value: string }[] = [
-            { label: "All", value: "ALL" },
+            // { label: "All", value: "ALL" },
           ];
 
           // Set namespace from URL if specified
@@ -79,10 +79,12 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
             setDefaultNamespace("default");
           }
           availableNamespaces.forEach((x: { name: string }, i: number) => {
-            namespaceOptions.push({
-              label: x.name,
-              value: x.name,
-            });
+            if (!x.name.startsWith("pr-")) {
+              namespaceOptions.push({
+                label: x.name,
+                value: x.name,
+              });
+            }
             if (x.name === urlNamespace) {
               setDefaultNamespace(urlNamespace);
             }
@@ -92,7 +94,7 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
       })
       .catch((err) => {
         if (_isMounted) {
-          setNamespaceOptions([{ label: "All", value: "ALL" }]);
+          setNamespaceOptions([]);
         }
       });
   };
@@ -105,7 +107,7 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
       defaultNamespace === "" ||
       urlNamespace === "ALL"
     ) {
-      setNamespace("ALL");
+      setNamespace("default");
     } else if (namespace !== defaultNamespace) {
       setNamespace(defaultNamespace);
     }

+ 24 - 20
dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx

@@ -30,6 +30,7 @@ type Props = {
   closeChartRedirectUrl?: string;
   selectedTag?: any;
   appFilters?: string[];
+  noPlaceholder?: boolean;
 };
 
 interface JobStatusWithTimeAndVersion extends JobStatusWithTimeType {
@@ -45,6 +46,7 @@ const ChartList: React.FunctionComponent<Props> = ({
   closeChartRedirectUrl,
   selectedTag,
   appFilters,
+  noPlaceholder,
 }) => {
   const {
     newWebsocket,
@@ -438,26 +440,28 @@ const ChartList: React.FunctionComponent<Props> = ({
   }, [charts, sortType, jobStatus, lastRunStatus, selectedTag]);
 
   const renderChartList = () => {
-    if (isLoading || (!namespace && namespace !== "")) {
-      return (
-        <LoadingWrapper>
-          <Loading />
-        </LoadingWrapper>
-      );
-    } else if (isError) {
-      return (
-        <Placeholder height="370px">
-          <i className="material-icons">error</i> Error connecting to cluster.
-        </Placeholder>
-      );
-    } else if (filteredCharts?.length === 0) {
-      return (
-        <Placeholder height="370px">
-          <i className="material-icons">category</i> No
-          {currentView === "jobs" ? ` jobs` : ` charts`} found with the given
-          filters.
-        </Placeholder>
-      );
+    if (!noPlaceholder) {
+      if (isLoading || (!namespace && namespace !== "")) {
+        return (
+          <LoadingWrapper>
+            <Loading />
+          </LoadingWrapper>
+        );
+      } else if (isError) {
+        return (
+          <Placeholder height="370px">
+            <i className="material-icons">error</i> Error connecting to cluster.
+          </Placeholder>
+        );
+      } else if (filteredCharts?.length === 0) {
+        return (
+          <Placeholder height="370px">
+            <i className="material-icons">category</i> No
+            {currentView === "jobs" ? ` jobs` : ` charts`} found with the given
+            filters.
+          </Placeholder>
+        );
+      }
     }
 
     return filteredCharts?.map((chart: ChartType, i: number) => {

+ 4 - 37
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx

@@ -478,39 +478,7 @@ const EnvGroupVariablesEditor = ({
   handleUpdateValues: () => void;
 }) => {
   const [isAuthorized] = useAuth();
-  const [localVariables, setLocalVariables] = useState(variables);
 
-  const handleVisibilityChange = () => {
-    if (document.hidden) {
-      localStorage.setItem("envGroupVariables", JSON.stringify(localVariables));
-    } else {
-      const savedVariables = localStorage.getItem("envGroupVariables");
-      if (savedVariables) {
-        setLocalVariables(JSON.parse(savedVariables));
-        localStorage.removeItem("envGroupVariables");
-      }
-    }
-  };
-
-  useEffect(() => {
-    document.addEventListener("visibilitychange", handleVisibilityChange);
-
-    return () => {
-      document.removeEventListener("visibilitychange", handleVisibilityChange);
-    };
-  }, [localVariables]);
-
-  useEffect(() => {
-    return () => {
-      // Reset to the original state when the component is unmounted
-      localStorage.removeItem("envGroupVariables");
-    };
-  }, []);
-
-  const handleUpdate = () => {
-    handleUpdateValues();
-    localStorage.removeItem("envGroupVariables");
-  };
   return (
     <TabWrapper>
       <InnerWrapper>
@@ -520,9 +488,8 @@ const EnvGroupVariablesEditor = ({
           configuration.
         </Helper>
         <EnvGroupArray
-          values={localVariables}
-          setValues={(x) => {
-            setLocalVariables(x);
+          values={variables}
+          setValues={(x: any) => {
             onChange(x);
           }}
           fileUpload={true}
@@ -540,7 +507,7 @@ const EnvGroupVariablesEditor = ({
       {isAuthorized("env_group", "", ["get", "update"]) && (
         <SaveButton
           text="Update"
-          onClick={handleUpdate}
+          onClick={() => handleUpdateValues()}
           status={buttonStatus}
           makeFlush={true}
           clearPosition={true}
@@ -913,7 +880,7 @@ const InnerWrapper = styled.div<{ full?: boolean }>`
   overflow: auto;
   margin-bottom: 30px;
   border-radius: 5px;
-  background: ${props => props.theme.fg};
+  background: ${(props) => props.theme.fg};
   border: 1px solid #494b4f;
 `;
 

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

@@ -373,7 +373,11 @@ const BuildSettingsTab: React.FC<Props> = ({
             {!chart.git_action_config.dockerfile_path && (
               <InputRow
                 disabled={true}
-                label="Dockerfile path"
+                label={
+                  chart.git_action_config.dockerfilePath
+                    ? "Docker build context"
+                    : "Application folder"
+                }
                 type="text"
                 width="100%"
                 value={chart.git_action_config.folder_path}
@@ -381,32 +385,6 @@ const BuildSettingsTab: React.FC<Props> = ({
             )}
           </div>
         )}
-        <Heading>
-          <ExpandHeader
-            onClick={() => setEnvVariablesExpanded(!envVariablesExpanded)}
-            isExpanded={!envVariablesExpanded}
-          >
-            Build environment variables
-            <i className="material-icons">arrow_drop_down</i>
-          </ExpandHeader>
-        </Heading>
-
-        {envVariablesExpanded && (
-          <div>
-            <KeyValueArray
-              values={envVariables}
-              envLoader
-              externalValues={{
-                namespace: chart.namespace,
-                clusterId: currentCluster.id,
-              }}
-              setValues={(values) => {
-                setEnvVariables(values);
-              }}
-            ></KeyValueArray>
-          </div>
-        )}
-
         <Heading>
           <ExpandHeader
             onClick={() => setBranchSelectionExpanded(!branchSelectionExpanded)}
@@ -433,6 +411,32 @@ const BuildSettingsTab: React.FC<Props> = ({
           </div>
         )}
 
+        <Heading>
+          <ExpandHeader
+            onClick={() => setEnvVariablesExpanded(!envVariablesExpanded)}
+            isExpanded={!envVariablesExpanded}
+          >
+            Build environment variables
+            <i className="material-icons">arrow_drop_down</i>
+          </ExpandHeader>
+        </Heading>
+
+        {envVariablesExpanded && (
+          <div>
+            <KeyValueArray
+              values={envVariables}
+              envLoader
+              externalValues={{
+                namespace: chart.namespace,
+                clusterId: currentCluster.id,
+              }}
+              setValues={(values) => {
+                setEnvVariables(values);
+              }}
+            ></KeyValueArray>
+          </div>
+        )}
+
         {!chart.git_action_config.dockerfile_path ? (
           <>
             <Heading>

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

@@ -337,6 +337,14 @@ const DeploymentDetail = () => {
             disableBottomPadding
             closeChartRedirectUrl={`${window.location.pathname}${window.location.search}`}
           />
+          <ChartList
+            currentView="jobs"
+            noPlaceholder={true}
+            lastRunStatus="all"
+            currentCluster={currentCluster}
+            namespace={prDeployment.namespace}
+            sortType="Newest"
+          />
         </ChartListWrapper>
       </StyledExpandedChart>
     </>

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

@@ -109,7 +109,7 @@ const CreatePREnvironment = ({ environmentID }: Props) => {
       searchValue,
       {
         isCaseSensitive: false,
-        keys: ["pr_title", "branch_from", "branch_into"],
+        keys: ["pr_title"],
       }
     );
 

+ 1 - 1
dashboard/src/main/home/launch/launch-flow/SettingsPage.tsx

@@ -100,7 +100,7 @@ class SettingsPage extends Component<PropsType, StateType> {
       .then((res) => {
         if (res.data) {
           const availableNamespaces = res.data.filter((namespace: any) => {
-            return namespace.status !== "Terminating";
+            return (namespace.status !== "Terminating" && !namespace.name.startsWith("pr-"));
           });
           const namespaceOptions = availableNamespaces.map(
             (x: { name: string }) => {

+ 13 - 10
dashboard/src/main/home/onboarding/state/index.ts

@@ -35,16 +35,19 @@ export const OFState = proxy({
     saveState: () => {
       const state = compressState(OFState);
 
-      api
-        .saveOnboardingState(
-          "<token>",
-          {
-            ...state,
-          },
-          { project_id: OFState.StateHandler?.project?.id }
-        )
-        .then((res) => console.log(res))
-        .catch((err) => console.log(err));
+      if (OFState.StateHandler?.project?.id) {
+        api
+          .saveOnboardingState(
+            "<token>",
+            {
+              ...state,
+            },
+            { project_id: OFState.StateHandler?.project?.id }
+          )
+          .then((res) => console.log(res))
+          .catch((err) => console.log(err));
+      }
+
     },
     restoreState: (state: any) => {
       const prevState = decompressState(state);

+ 8 - 1
dashboard/webpack.config.js

@@ -11,11 +11,18 @@ const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
 const TerserPlugin = require("terser-webpack-plugin");
 
 module.exports = () => {
-  let env = dotenv.config().parsed;
+  let envPath = ".env";
+  if (process.env.ENV_FILE !== "") {
+    envPath = process.env.ENV_FILE;
+  }
+  console.log(`using envfile from path ${envPath}`);
 
+  let env = dotenv.config({ path: envPath }).parsed;
+  console.log(env);
   if (!env) {
     env = process.env;
   }
+
   const envKeys = Object.keys(env).reduce((prev, next) => {
     const varName = `process.env.${next}`;
     if (typeof env[next] !== "string") return prev;

+ 6 - 1
internal/kubernetes/config.go

@@ -2,6 +2,7 @@ package kubernetes
 
 import (
 	"context"
+	"encoding/base64"
 	"errors"
 	"fmt"
 	"os"
@@ -125,7 +126,11 @@ func kubeConfigForCAPICluster(ctx context.Context, mgmtClusterConnection porterv
 	if kubeconfigResp.Msg.KubeConfig == "" {
 		return "", errors.New("no kubeconfig returned for capi cluster")
 	}
-	return kubeconfigResp.Msg.KubeConfig, nil
+	decodedKubeconfig, err := base64.StdEncoding.DecodeString(kubeconfigResp.Msg.KubeConfig)
+	if err != nil {
+		return "", fmt.Errorf("error decoding kubeconfig: %w", err)
+	}
+	return string(decodedKubeconfig), nil
 }
 
 // writeKubeConfigToFileAndRestClient writes a literal kubeconfig to a temporary file

+ 12 - 1
zarf/helm/.serverenv

@@ -1,6 +1,7 @@
 # Fill out this file, and renamed to '.server.env' in order to run this with Tilt
 
 # Required parameters
+
 SQL_LITE=false
 DB_NAME=porter
 DB_USER=porter
@@ -9,22 +10,32 @@ DB_HOST=postgresql
 DB_PORT=5432
 
 # Required for accessing cluster control plane. If ENABLE_CAPI_PROVISIONER=false, nothing in this section will be used
+
 ENABLE_CAPI_PROVISIONER=false
 NATS_URL=nats:4222
 CLUSTER_CONTROL_PLANE_ADDRESS=http://ccp-web:7833
 
 # Github Login OAuth
+
 GITHUB_LOGIN_ENABLED=false
 
 # Github App for repo deployments, and preview environments. Remove these if you are not using preview environments or deploying from a repo locally
+
 GITHUB_APP_CLIENT_ID=<github_app_id>
 GITHUB_APP_CLIENT_SECRET=<github_secret>
 GITHUB_APP_WEBHOOK_SECRET=<webhook_secret>
 GITHUB_APP_NAME=<github_app_name>
 GITHUB_APP_ID=<github_app_id>
+
 # GITHUB_APP_SECRET_PATH is the path to your secret within the container. Tilt will sync your ~/.ssh/ folder into /app/ssh automatically. This will likely be /app/ssh/your_ssh_pem_name
+
 GITHUB_APP_SECRET_PATH=<path_to_secret>
 
 # Optional parameters
+
 HELM_APP_REPO_URL=https://charts.getporter.dev
-HELM_ADD_ON_REPO_URL=https://charts.getporter.dev
+HELM_ADD_ON_REPO_URL=https://charts.getporter.dev
+
+# SERVER_URL must be set to your ngrok url, If you are using ngrok for your github app setup on the backend
+
+# SERVER_URL=

+ 0 - 10
zarf/helm/kustomization.yaml

@@ -27,16 +27,6 @@ patchesStrategicMerge:
           envFrom:
           - configMapRef: 
               name: porter-server-env
-- |-
-  apiVersion: apps/v1
-  kind: Deployment
-  metadata:
-    name: porter-server-web
-  spec:
-    template:
-      spec:
-        containers:
-        - name: web
           volumeMounts:
           - mountPath: /app/ssh
             name: ssh-keys