Browse Source

Provisioner Linter

Soham Dessai 2 years ago
parent
commit
9976fc2e56

+ 8 - 0
.github/dependabot.yml

@@ -0,0 +1,8 @@
+---
+version: 2
+updates:
+  - package-ecosystem: gomod
+    directory: "/"
+    schedule:
+      interval: daily
+    open-pull-requests-limit: 2

+ 8 - 8
dashboard/package-lock.json

@@ -152,7 +152,7 @@
         "terser-webpack-plugin": "^4.2.3",
         "ts-loader": "^8.0.4",
         "type-fest": "^4.3.1",
-        "typescript": "^4.1.2",
+        "typescript": "^5.2.2",
         "webpack": "^4.44.2",
         "webpack-bundle-analyzer": "^4.4.2",
         "webpack-cli": "^3.3.12",
@@ -16035,16 +16035,16 @@
       "dev": true
     },
     "node_modules/typescript": {
-      "version": "4.9.5",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
-      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+      "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
       "dev": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
       },
       "engines": {
-        "node": ">=4.2.0"
+        "node": ">=14.17"
       }
     },
     "node_modules/unbox-primitive": {
@@ -30347,9 +30347,9 @@
       "dev": true
     },
     "typescript": {
-      "version": "4.9.5",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
-      "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+      "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
       "dev": true
     },
     "unbox-primitive": {

+ 3 - 3
dashboard/package.json

@@ -88,7 +88,7 @@
     "build": "NODE_ENV=\"production\" webpack",
     "build-and-analyze": "ENABLE_ANALYZER=true NODE_ENV=\"production\" ./node_modules/webpack/bin/webpack.js",
     "prepare": "cd .. && husky install dashboard/.husky",
-    "lint-staged": "lint-staged"
+    "lint-staged": "tsc --noEmit && lint-staged"
   },
   "devDependencies": {
     "@babel/core": "^7.15.0",
@@ -159,14 +159,14 @@
     "terser-webpack-plugin": "^4.2.3",
     "ts-loader": "^8.0.4",
     "type-fest": "^4.3.1",
-    "typescript": "^4.1.2",
+    "typescript": "^5.2.2",
     "webpack": "^4.44.2",
     "webpack-bundle-analyzer": "^4.4.2",
     "webpack-cli": "^3.3.12",
     "webpack-dev-server": "^3.11.0"
   },
   "lint-staged": {
-    "**/*.{js,ts,jsx,tsx}": [
+    "src/**/*.{js,ts,jsx,tsx}": [
       "eslint --fix",
       "prettier --write"
     ],

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

@@ -198,7 +198,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
           project_id: currentProject ? currentProject.id : 0,
         }
       );
-    } catch (err) {}
+    } catch (err) { }
   };
 
   const getStatus = ():
@@ -453,8 +453,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
   useEffect(() => {
     setIsReadOnly(
       props.clusterId &&
-        (currentCluster.status === "UPDATING" ||
-          currentCluster.status === "UPDATING_UNAVAILABLE")
+      (currentCluster.status === "UPDATING" ||
+        currentCluster.status === "UPDATING_UNAVAILABLE")
     );
     handleClusterStateChange(
       "clusterName",
@@ -514,8 +514,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 
         const awsTags = eksValues.loadBalancer.tags
           ? Object.entries(eksValues.loadBalancer.tags)
-              .map(([key, value]) => `${key}=${value}`)
-              .join(",")
+            .map(([key, value]) => `${key}=${value}`)
+            .join(",")
           : "";
         handleClusterStateChange("awsTags", awsTags);
 
@@ -672,7 +672,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                   handleClusterStateChange("clusterVersion", x);
                 }}
                 label="Cluster version (only shown to porter.run emails)"
-              />
+                placeholder={""} />
             )}
             <Spacer y={1} />
             <Select
@@ -740,7 +740,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
               label="CIDR range for Kubernetes internal services"
               placeholder="ex: 172.20.0.0/16"
             />
-            {currentProject && !currentProject.simplified_view_enabled && (
+            {currentProject && (
               <>
                 <Spacer y={1} />
                 <Checkbox
@@ -1051,11 +1051,11 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 
                         {(clusterState.wafV2ARN === undefined ||
                           clusterState.wafV2ARN?.length === 0) && (
-                          <ErrorInLine>
-                            <i className="material-icons">error</i>
-                            {"Required if WafV2 is enabled"}
-                          </ErrorInLine>
-                        )}
+                            <ErrorInLine>
+                              <i className="material-icons">error</i>
+                              {"Required if WafV2 is enabled"}
+                            </ErrorInLine>
+                          )}
                       </>
                     )}
                     <Spacer y={1} />
@@ -1142,7 +1142,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     setShowHelpMessage(false);
     try {
       await preflightChecks();
-    } catch (err) {}
+    } catch (err) { }
   };
 
   const renderForm = (): JSX.Element => {
@@ -1153,7 +1153,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
           currentStep={step}
           steps={[
             <>
-              <Text size={16}>Select an AWS region</Text>
+              <Text size={16}>Set your cluster settings</Text>
               <Spacer y={0.5} />
               <Text color="helper">
                 Porter will automatically provision your infrastructure in the
@@ -1173,7 +1173,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
                 label="📍 AWS region"
               />
               <>
-                {(user?.isPorterUser || currentProject?.multi_cluster) &&
+                {(user?.isPorterUser || currentProject?.enable_reprovision) &&
                   renderAdvancedSettings()}
               </>
             </>,
@@ -1349,7 +1349,7 @@ const ExpandHeader = styled.div<{ isExpanded: boolean }>`
         margin - right: 7px;
       margin-left: -7px;
       transform: ${(props) =>
-        props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
+    props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
       transition: transform 0.1s ease;
   }
       `;

+ 51 - 0
dashboard/src/main/auth/context.tsx

@@ -0,0 +1,51 @@
+import React, { createContext, useContext } from "react";
+
+import Loading from "components/Loading";
+
+import { type ClusterType, type ProjectType } from "shared/types";
+
+type PorterUser = {
+  userId: number;
+  email: string;
+  isPorterUser: boolean;
+};
+
+type AuthContextType = {
+  currentProject: ProjectType;
+  currentCluster: ClusterType;
+  user: PorterUser;
+};
+
+export const AuthStateContext = createContext<AuthContextType | null>(null);
+
+export const useAuthState = (): AuthContextType => {
+  const context = useContext(AuthStateContext);
+  if (context === null) {
+    throw new Error("useAuthState must be used within a AuthStateProvider");
+  }
+  return context;
+};
+
+type AuthStateProviderProps = {
+  children: JSX.Element;
+  currentProject?: ProjectType;
+  currentCluster?: ClusterType;
+  user?: PorterUser;
+};
+
+export const AuthStateProvider: React.FC<AuthStateProviderProps> = ({
+  children,
+  currentProject,
+  currentCluster,
+  user,
+}) => {
+  if (!currentProject || !currentCluster || !user) {
+    return <Loading />;
+  }
+
+  return (
+    <AuthStateContext.Provider value={{ currentProject, currentCluster, user }}>
+      {children}
+    </AuthStateContext.Provider>
+  );
+};

+ 291 - 267
dashboard/src/main/home/Home.tsx

@@ -1,53 +1,62 @@
-import React, { useEffect, useState, useContext, useRef } from "react";
-import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
-import styled, { ThemeProvider } from "styled-components";
+import React, { useContext, useEffect, useRef, useState } from "react";
 import { createPortal } from "react-dom";
+import {
+  Route,
+  Switch,
+  withRouter,
+  type RouteComponentProps,
+} from "react-router";
+import styled, { ThemeProvider } from "styled-components";
+
+import ConfirmOverlay from "components/ConfirmOverlay";
+import Loading from "components/Loading";
+import NoClusterPlaceHolder from "components/NoClusterPlaceHolder";
+import Button from "components/porter/Button";
+import Modal from "components/porter/Modal";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+import { AuthStateProvider } from "main/auth/context";
 
 import api from "shared/api";
+import { withAuth, type WithAuthProps } from "shared/auth/AuthorizationHoc";
+import { fakeGuardedRoute } from "shared/auth/RouteGuard";
+import ClusterResourcesProvider from "shared/ClusterResourcesContext";
+import { Context } from "shared/Context";
+import DeploymentTargetProvider from "shared/DeploymentTargetContext";
+import { pushFiltered, pushQueryParams, type PorterUrl } from "shared/routing";
 import midnight from "shared/themes/midnight";
 import standard from "shared/themes/standard";
-import { Context } from "shared/Context";
-import { PorterUrl, pushFiltered, pushQueryParams } from "shared/routing";
-import { ClusterType, ProjectType, ProjectListType } from "shared/types";
+import {
+  type ClusterType,
+  type ProjectListType,
+  type ProjectType,
+} from "shared/types";
+import { overrideInfraTabEnabled } from "utils/infrastructure";
 
-import ConfirmOverlay from "components/ConfirmOverlay";
-import Loading from "components/Loading";
+import discordLogo from "../../assets/discord.svg";
+import AddOnDashboard from "./add-on-dashboard/AddOnDashboard";
+import NewAddOnFlow from "./add-on-dashboard/NewAddOnFlow";
+import AppView from "./app-dashboard/app-view/AppView";
+import AppDashboard from "./app-dashboard/AppDashboard";
+import Apps from "./app-dashboard/apps/Apps";
+import CreateApp from "./app-dashboard/create-app/CreateApp";
+import ExpandedApp from "./app-dashboard/expanded-app/ExpandedApp";
+import NewAppFlow from "./app-dashboard/new-app-flow/NewAppFlow";
 import DashboardRouter from "./cluster-dashboard/DashboardRouter";
+import PreviewEnvs from "./cluster-dashboard/preview-environments/v2/PreviewEnvs";
+import SetupApp from "./cluster-dashboard/preview-environments/v2/setup-app/SetupApp";
 import Dashboard from "./dashboard/Dashboard";
+import CreateDatabase from "./database-dashboard/CreateDatabase";
+import DatabaseDashboard from "./database-dashboard/DatabaseDashboard";
+import InfrastructureRouter from "./infrastructure/InfrastructureRouter";
 import Integrations from "./integrations/Integrations";
 import LaunchWrapper from "./launch/LaunchWrapper";
-
+import ModalHandler from "./ModalHandler";
 import Navbar from "./navbar/Navbar";
+import { NewProjectFC } from "./new-project/NewProject";
+import Onboarding from "./onboarding/Onboarding";
 import ProjectSettings from "./project-settings/ProjectSettings";
 import Sidebar from "./sidebar/Sidebar";
-import AppDashboard from "./app-dashboard/AppDashboard";
-import AddOnDashboard from "./add-on-dashboard/AddOnDashboard";
-import DatabaseDashboard from "./database-dashboard/DatabaseDashboard";
-import CreateDatabase from "./database-dashboard/CreateDatabase";
-
-import { fakeGuardedRoute } from "shared/auth/RouteGuard";
-import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
-import discordLogo from "../../assets/discord.svg";
-import Onboarding from "./onboarding/Onboarding";
-import ModalHandler from "./ModalHandler";
-import { NewProjectFC } from "./new-project/NewProject";
-import InfrastructureRouter from "./infrastructure/InfrastructureRouter";
-import { overrideInfraTabEnabled } from "utils/infrastructure";
-import NoClusterPlaceHolder from "components/NoClusterPlaceHolder";
-import NewAddOnFlow from "./add-on-dashboard/NewAddOnFlow";
-import Modal from "components/porter/Modal";
-import Text from "components/porter/Text";
-import Spacer from "components/porter/Spacer";
-import Button from "components/porter/Button";
-import NewAppFlow from "./app-dashboard/new-app-flow/NewAppFlow";
-import ExpandedApp from "./app-dashboard/expanded-app/ExpandedApp";
-import CreateApp from "./app-dashboard/create-app/CreateApp";
-import AppView from "./app-dashboard/app-view/AppView";
-import Apps from "./app-dashboard/apps/Apps";
-import DeploymentTargetProvider from "shared/DeploymentTargetContext";
-import PreviewEnvs from "./cluster-dashboard/preview-environments/v2/PreviewEnvs";
-import SetupApp from "./cluster-dashboard/preview-environments/v2/setup-app/SetupApp";
-import ClusterResourcesProvider from "shared/ClusterResourcesContext";
 
 // Guarded components
 const GuardedProjectSettings = fakeGuardedRoute("settings", "", [
@@ -123,10 +132,10 @@ const Home: React.FC<Props> = (props) => {
   };
 
   const getProjects = async (id?: number) => {
-    let { currentProject } = props;
-    let queryString = window.location.search;
-    let urlParams = new URLSearchParams(queryString);
-    let projectId = urlParams.get("project_id");
+    const { currentProject } = props;
+    const queryString = window.location.search;
+    const urlParams = new URLSearchParams(queryString);
+    const projectId = urlParams.get("project_id");
     if (!projectId && currentProject?.id) {
       pushQueryParams(props, { project_id: currentProject.id.toString() });
     }
@@ -154,7 +163,7 @@ const Home: React.FC<Props> = (props) => {
         }
 
         const project = await api
-          .getProject("<token>", {}, { id: id })
+          .getProject("<token>", {}, { id })
           .then((res) => res.data as ProjectType);
 
         setCurrentProject(project);
@@ -204,18 +213,18 @@ const Home: React.FC<Props> = (props) => {
   useEffect(() => {
     checkOnboarding();
     checkIfCanCreateProject();
-    let { match } = props;
+    const { match } = props;
 
     // Handle redirect from DO
-    let queryString = window.location.search;
-    let urlParams = new URLSearchParams(queryString);
+    const queryString = window.location.search;
+    const urlParams = new URLSearchParams(queryString);
 
-    let err = urlParams.get("error");
+    const err = urlParams.get("error");
     if (err) {
       setCurrentError(err);
     }
 
-    let defaultProjectId = parseInt(urlParams.get("project_id"));
+    const defaultProjectId = parseInt(urlParams.get("project_id"));
 
     setGhRedirect(urlParams.get("gh_oauth") !== null);
     urlParams.delete("gh_oauth");
@@ -288,9 +297,9 @@ const Home: React.FC<Props> = (props) => {
   }, [props.currentProject?.id]);
 
   useEffect(() => {
-    let queryString = window.location.search;
-    let urlParams = new URLSearchParams(queryString);
-    let err = urlParams.get("error");
+    const queryString = window.location.search;
+    const urlParams = new URLSearchParams(queryString);
+    const err = urlParams.get("error");
     if (
       !hasFinishedOnboarding &&
       props.history.location.pathname &&
@@ -326,7 +335,9 @@ const Home: React.FC<Props> = (props) => {
 
       setProjects(projectList);
       if (!projectList.length) {
-        setCurrentProject(null, () => redirectToNewProject());
+        setCurrentProject(null, () => {
+          redirectToNewProject();
+        });
       } else {
         const project = await api
           .getProject("<token>", {}, { id: projectList[0].id })
@@ -360,18 +371,18 @@ const Home: React.FC<Props> = (props) => {
 
     try {
       const res = await api.getClusters<
-        {
+        Array<{
           infra_id?: number;
           name: string;
-        }[]
+        }>
       >("<token>", {}, { id: currentProject?.id });
 
-      const destroyInfraPromises = res.data.map((cluster) => {
+      const destroyInfraPromises = res.data.map(async (cluster) => {
         if (!cluster.infra_id) {
           return undefined;
         }
 
-        return api.destroyInfra(
+        return await api.destroyInfra(
           "<token>",
           {},
           { project_id: currentProject.id, infra_id: cluster.infra_id }
@@ -388,233 +399,244 @@ const Home: React.FC<Props> = (props) => {
 
   const { cluster, baseRoute } = props.match.params as any;
   return (
-    <ThemeProvider
-      theme={currentProject?.simplified_view_enabled ? midnight : standard}
+    <AuthStateProvider
+    user={user}
+      currentCluster={currentCluster}
+      currentProject={currentProject}
     >
-      <ClusterResourcesProvider>
-        <DeploymentTargetProvider>
-          <StyledHome>
-            <ModalHandler setRefreshClusters={setForceRefreshClusters} />
-            {currentOverlay &&
-              createPortal(
-                <ConfirmOverlay
-                  show={true}
-                  message={currentOverlay.message}
-                  onYes={currentOverlay.onYes}
-                  onNo={currentOverlay.onNo}
-                />,
-                document.body
+      <ThemeProvider
+        theme={currentProject?.simplified_view_enabled ? midnight : standard}
+      >
+        <ClusterResourcesProvider>
+          <DeploymentTargetProvider>
+            <StyledHome>
+              <ModalHandler setRefreshClusters={setForceRefreshClusters} />
+              {currentOverlay &&
+                createPortal(
+                  <ConfirmOverlay
+                    show={true}
+                    message={currentOverlay.message}
+                    onYes={currentOverlay.onYes}
+                    onNo={currentOverlay.onNo}
+                  />,
+                  document.body
+                )}
+              {/* Render sidebar when there's at least one project */}
+              {projects?.length > 0 && baseRoute !== "new-project" ? (
+                <Sidebar
+                  key="sidebar"
+                  forceSidebar={forceSidebar}
+                  setWelcome={setShowWelcome}
+                  currentView={props.currentRoute}
+                  forceRefreshClusters={forceRefreshClusters}
+                  setRefreshClusters={setForceRefreshClusters}
+                />
+              ) : (
+                <DiscordButton
+                  href="https://discord.gg/34n7NN7FJ7"
+                  target="_blank"
+                >
+                  <Icon src={discordLogo} />
+                  Join Our Discord
+                </DiscordButton>
               )}
-            {/* Render sidebar when there's at least one project */}
-            {projects?.length > 0 && baseRoute !== "new-project" ? (
-              <Sidebar
-                key="sidebar"
-                forceSidebar={forceSidebar}
-                setWelcome={setShowWelcome}
-                currentView={props.currentRoute}
-                forceRefreshClusters={forceRefreshClusters}
-                setRefreshClusters={setForceRefreshClusters}
-              />
-            ) : (
-              <DiscordButton href="https://discord.gg/34n7NN7FJ7" target="_blank">
-                <Icon src={discordLogo} />
-                Join Our Discord
-              </DiscordButton>
-            )}
-            <ViewWrapper id="HomeViewWrapper">
-              <Navbar
-                logOut={props.logOut}
-                currentView={props.currentRoute} // For form feedback
-              />
-
-              <Switch>
-                <Route path="/apps/new/app">
-                  {currentProject?.validate_apply_v2 ? (
-                    <CreateApp />
-                  ) : (
-                    <NewAppFlow />
-                  )}
-                </Route>
-                <Route path="/apps/:appName/:tab">
-                  {currentProject?.validate_apply_v2 ? (
-                    <AppView />
-                  ) : (
-                    <ExpandedApp />
-                  )}
-                </Route>
-                <Route path="/apps/:appName">
-                  {currentProject?.validate_apply_v2 ? (
-                    <AppView />
-                  ) : (
-                    <ExpandedApp />
-                  )}
-                </Route>
-                <Route path="/apps">
-                  {currentProject?.validate_apply_v2 ? (
-                    <Apps />
-                  ) : (
-                    <AppDashboard />
-                  )}
-                </Route>
-
-                <Route path="/databases/new">
-                  <CreateDatabase />
-                </Route>
-                <Route path="/databases">
-                  <DatabaseDashboard />
-                </Route>
-
-                <Route path="/addons/new">
-                  <NewAddOnFlow />
-                </Route>
-                <Route path="/addons">
-                  <AddOnDashboard />
-                </Route>
-                <Route
-                  path="/new-project"
-                  render={() => {
-                    return <NewProjectFC />;
-                  }}
-                ></Route>
-                <Route
-                  path="/onboarding"
-                  render={() => {
-                    return <Onboarding />;
-                  }}
+              <ViewWrapper id="HomeViewWrapper">
+                <Navbar
+                  logOut={props.logOut}
+                  currentView={props.currentRoute} // For form feedback
                 />
-                {(user?.isPorterUser ||
-                  overrideInfraTabEnabled({
-                    projectID: currentProject?.id,
-                  })) && (
+
+                <Switch>
+                  <Route path="/apps/new/app">
+                    {currentProject?.validate_apply_v2 ? (
+                      <CreateApp />
+                    ) : (
+                      <NewAppFlow />
+                    )}
+                  </Route>
+                  <Route path="/apps/:appName/:tab">
+                    {currentProject?.validate_apply_v2 ? (
+                      <AppView />
+                    ) : (
+                      <ExpandedApp />
+                    )}
+                  </Route>
+                  <Route path="/apps/:appName">
+                    {currentProject?.validate_apply_v2 ? (
+                      <AppView />
+                    ) : (
+                      <ExpandedApp />
+                    )}
+                  </Route>
+                  <Route path="/apps">
+                    {currentProject?.validate_apply_v2 ? (
+                      <Apps />
+                    ) : (
+                      <AppDashboard />
+                    )}
+                  </Route>
+
+                  <Route path="/databases/new">
+                    <CreateDatabase />
+                  </Route>
+                  <Route path="/databases">
+                    <DatabaseDashboard />
+                  </Route>
+
+                  <Route path="/addons/new">
+                    <NewAddOnFlow />
+                  </Route>
+                  <Route path="/addons">
+                    <AddOnDashboard />
+                  </Route>
+                  <Route
+                    path="/new-project"
+                    render={() => {
+                      return <NewProjectFC />;
+                    }}
+                  ></Route>
                   <Route
-                    path="/infrastructure"
+                    path="/onboarding"
+                    render={() => {
+                      return <Onboarding />;
+                    }}
+                  />
+                  {(user?.isPorterUser ||
+                    overrideInfraTabEnabled({
+                      projectID: currentProject?.id,
+                    })) && (
+                    <Route
+                      path="/infrastructure"
+                      render={() => {
+                        return (
+                          <DashboardWrapper>
+                            <InfrastructureRouter />
+                          </DashboardWrapper>
+                        );
+                      }}
+                    />
+                  )}
+                  <Route
+                    path="/dashboard"
                     render={() => {
                       return (
                         <DashboardWrapper>
-                          <InfrastructureRouter />
+                          <Dashboard
+                            projectId={currentProject?.id}
+                            setRefreshClusters={setForceRefreshClusters}
+                          />
                         </DashboardWrapper>
                       );
                     }}
                   />
-                )}
-                <Route
-                  path="/dashboard"
-                  render={() => {
-                    return (
-                      <DashboardWrapper>
-                        <Dashboard
-                          projectId={currentProject?.id}
-                          setRefreshClusters={setForceRefreshClusters}
-                        />
-                      </DashboardWrapper>
-                    );
-                  }}
-                />
-                <Route
-                  path={[
-                    "/cluster-dashboard",
-                    "/applications",
-                    "/jobs",
-                    "/env-groups",
-                    "/databases",
-                    ...(!currentProject?.validate_apply_v2
-                      ? ["/preview-environments"]
-                      : []),
-                    "/stacks",
-                  ]}
-                  render={() => {
-                    if (currentCluster?.id === -1) {
-                      return <Loading />;
-                    } else if (!currentCluster || !currentCluster.name) {
+                  <Route
+                    path={[
+                      "/cluster-dashboard",
+                      "/applications",
+                      "/jobs",
+                      "/env-groups",
+                      "/databases",
+                      ...(!currentProject?.validate_apply_v2
+                        ? ["/preview-environments"]
+                        : []),
+                      "/stacks",
+                    ]}
+                    render={() => {
+                      if (currentCluster?.id === -1) {
+                        return <Loading />;
+                      } else if (!currentCluster?.name) {
+                        return (
+                          <DashboardWrapper>
+                            <NoClusterPlaceHolder></NoClusterPlaceHolder>
+                          </DashboardWrapper>
+                        );
+                      }
                       return (
                         <DashboardWrapper>
-                          <NoClusterPlaceHolder></NoClusterPlaceHolder>
+                          <DashboardRouter
+                            currentCluster={currentCluster}
+                            setSidebar={setForceSidebar}
+                            currentView={props.currentRoute}
+                          />
                         </DashboardWrapper>
                       );
-                    }
-                    return (
-                      <DashboardWrapper>
-                        <DashboardRouter
-                          currentCluster={currentCluster}
-                          setSidebar={setForceSidebar}
-                          currentView={props.currentRoute}
-                        />
-                      </DashboardWrapper>
-                    );
+                    }}
+                  />
+                  <Route
+                    path={"/integrations"}
+                    render={() => <GuardedIntegrations />}
+                  />
+                  <Route
+                    exact
+                    path={"/project-settings"}
+                    render={() => <GuardedProjectSettings />}
+                  />
+                  {currentProject?.validate_apply_v2 &&
+                  currentProject.preview_envs_enabled ? (
+                    <>
+                      <Route exact path="/preview-environments/configure">
+                        <SetupApp />
+                      </Route>
+                      <Route
+                        exact
+                        path={`/preview-environments/apps/:appName/:tab`}
+                      >
+                        <AppView />
+                      </Route>
+                      <Route exact path="/preview-environments/apps/:appName">
+                        <AppView />
+                      </Route>
+                      <Route exact path={`/preview-environments/apps`}>
+                        <Apps />
+                      </Route>
+                      <Route exact path={`/preview-environments`}>
+                        <PreviewEnvs />
+                      </Route>
+                    </>
+                  ) : null}
+                  <Route path={"*"} render={() => <LaunchWrapper />} />
+                </Switch>
+              </ViewWrapper>
+              {createPortal(
+                <ConfirmOverlay
+                  show={currentModal === "UpdateProjectModal"}
+                  message={
+                    currentProject
+                      ? `Are you sure you want to delete ${currentProject.name}?`
+                      : ""
+                  }
+                  onYes={handleDelete}
+                  onNo={() => {
+                    setCurrentModal(null, null);
                   }}
-                />
-                <Route
-                  path={"/integrations"}
-                  render={() => <GuardedIntegrations />}
-                />
-                <Route
-                  exact
-                  path={"/project-settings"}
-                  render={() => <GuardedProjectSettings />}
-                />
-                {currentProject?.validate_apply_v2 &&
-                currentProject.preview_envs_enabled ? (
-                  <>
-                    <Route exact path="/preview-environments/configure">
-                      <SetupApp />
-                    </Route>
-                    <Route
-                      exact
-                      path={`/preview-environments/apps/:appName/:tab`}
-                    >
-                      <AppView />
-                    </Route>
-                    <Route exact path="/preview-environments/apps/:appName">
-                      <AppView />
-                    </Route>
-                    <Route exact path={`/preview-environments/apps`}>
-                      <Apps />
-                    </Route>
-                    <Route exact path={`/preview-environments`}>
-                      <PreviewEnvs />
-                    </Route>
-                  </>
-                ) : null}
-                <Route path={"*"} render={() => <LaunchWrapper />} />
-              </Switch>
-            </ViewWrapper>
-            {createPortal(
-              <ConfirmOverlay
-                show={currentModal === "UpdateProjectModal"}
-                message={
-                  currentProject
-                    ? `Are you sure you want to delete ${currentProject.name}?`
-                    : ""
-                }
-                onYes={handleDelete}
-                onNo={() => setCurrentModal(null, null)}
-              />,
-              document.body
-            )}
-            {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.
-                </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.
-                </Text>
-                <Spacer y={1} />
-                <Button onClick={props.logOut}>Log out</Button>
-              </Modal>
-            )}
-          </StyledHome>
-        </DeploymentTargetProvider>
-      </ClusterResourcesProvider>
-    </ThemeProvider>
+                />,
+                document.body
+              )}
+              {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.
+                  </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.
+                  </Text>
+                  <Spacer y={1} />
+                  <Button onClick={props.logOut}>Log out</Button>
+                </Modal>
+              )}
+            </StyledHome>
+          </DeploymentTargetProvider>
+        </ClusterResourcesProvider>
+      </ThemeProvider>
+    </AuthStateProvider>
   );
 };
 
@@ -674,7 +696,9 @@ const DiscordButton = styled.a`
   border-radius: 3px;
   color: #ffffff44;
   height: 40px;
-  font-family: Work Sans, sans-serif;
+  font-family:
+    Work Sans,
+    sans-serif;
   font-size: 14px;
   font-weight: bold;
   cursor: pointer;

+ 8 - 16
dashboard/src/main/home/app-dashboard/app-view/LatestRevisionContext.tsx

@@ -16,6 +16,7 @@ import Container from "components/porter/Container";
 import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
+import { useAuthState } from "main/auth/context";
 import { usePorterYaml } from "lib/hooks/usePorterYaml";
 import { clientAppFromProto, type SourceOptions } from "lib/porter-apps";
 import {
@@ -25,7 +26,6 @@ import {
 import { appRevisionValidator, type AppRevision } from "lib/revisions/types";
 
 import api from "shared/api";
-import { Context } from "shared/Context";
 import {
   useDeploymentTarget,
   type DeploymentTarget,
@@ -79,17 +79,13 @@ export const LatestRevisionProvider: React.FC<LatestRevisionProviderProps> = ({
   const [previewRevision, setPreviewRevision] = useState<AppRevision | null>(
     null
   );
-  const { currentCluster, currentProject } = useContext(Context);
+  const { currentCluster, currentProject } = useAuthState();
   const { currentDeploymentTarget } = useDeploymentTarget();
 
-  const appParamsExist =
-    !!appName &&
-    !!currentCluster &&
-    !!currentProject &&
-    !!currentDeploymentTarget;
+  const appParamsExist = !!appName && !!currentDeploymentTarget;
 
   const { data: porterApp, status: porterAppStatus } = useQuery(
-    ["getPorterApp", currentCluster?.id, currentProject?.id, appName],
+    ["getPorterApp", currentCluster.id, currentProject.id, appName],
     async () => {
       if (!appParamsExist) {
         return;
@@ -116,8 +112,8 @@ export const LatestRevisionProvider: React.FC<LatestRevisionProviderProps> = ({
   const { data: latestRevision, status } = useQuery(
     [
       "getLatestRevision",
-      currentProject?.id,
-      currentCluster?.id,
+      currentProject.id,
+      currentCluster.id,
       currentDeploymentTarget,
       appName,
     ],
@@ -161,7 +157,7 @@ export const LatestRevisionProvider: React.FC<LatestRevisionProviderProps> = ({
       },
     ],
     async () => {
-      if (!currentCluster || !currentProject || !currentDeploymentTarget) {
+      if (!currentDeploymentTarget) {
         return;
       }
       const res = await api.getDeploymentTarget(
@@ -185,9 +181,6 @@ export const LatestRevisionProvider: React.FC<LatestRevisionProviderProps> = ({
         .parseAsync(res.data);
 
       return deploymentTarget;
-    },
-    {
-      enabled: !!currentCluster && !!currentProject,
     }
   );
 
@@ -231,8 +224,7 @@ export const LatestRevisionProvider: React.FC<LatestRevisionProviderProps> = ({
       };
     },
     {
-      enabled:
-        !!appName && !!revisionId && !!currentCluster && !!currentProject,
+      enabled: !!appName && !!revisionId,
     }
   );
 

+ 0 - 1
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx

@@ -1072,7 +1072,6 @@ const EnvGroupSettings = ({
                   label="New env group namespace"
                   placeholder="ex: default"
                 />
-              )}
               <FlexAlt>
                 <Button onClick={cloneEnvGroup}>Clone {envGroup.name}</Button>
                 {cloneSuccess && (

+ 12 - 12
dashboard/src/main/home/sidebar/ClusterList.tsx

@@ -13,7 +13,7 @@ import infra from "assets/cluster.svg";
 import ProvisionClusterModal from "./ProvisionClusterModal";
 import SidebarLink from "./SidebarLink";
 
-type Option = {
+type ClusterOptions = {
   label: string;
   value: string;
 };
@@ -29,7 +29,7 @@ const ClusterList: React.FC = (props) => {
     useState<boolean>(false);
   const wrapperRef = useRef<HTMLDivElement>(null);
   const [clusters, setClusters] = useState<ClusterType[]>([]);
-  const [options, setOptions] = useState<Option[]>([]);
+  const [options, setOptions] = useState<ClusterOptions[]>([]);
 
   useEffect(() => {
     const handleClickOutside = (e: MouseEvent): void => {
@@ -59,7 +59,7 @@ const ClusterList: React.FC = (props) => {
               (a: { id: number }, b: { id: number }) => a.id - b.id
             );
             if (clusters.length > 0) {
-              const options: Option[] = clusters.map(
+              const options: ClusterOptions[] = clusters.map(
                 (item: { vanity_name: string; name: string }) => ({
                   label: item.vanity_name ? item.vanity_name : item.name,
                   value: item.name,
@@ -78,13 +78,13 @@ const ClusterList: React.FC = (props) => {
         });
     }
   }, [currentProject, currentCluster]);
-  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
-  const truncate = (input: string) =>
+
+  const truncate = (input: string): string =>
     input.length > 27 ? `${input.substring(0, 27)}...` : input;
 
   const renderOptionList = (): JSX.Element[] =>
     options.map((option, i: number) => (
-      <OptionDis
+      <OptionDiv
         key={i}
         selected={option.value === currentCluster?.name}
         title={option.label}
@@ -97,7 +97,7 @@ const ClusterList: React.FC = (props) => {
       >
         <Icon src={infra} height={"14px"} />
         <ClusterLabel>{option.label}</ClusterLabel>
-      </OptionDis>
+      </OptionDiv>
     ));
 
   const renderDropdown = (): false | JSX.Element =>
@@ -106,7 +106,7 @@ const ClusterList: React.FC = (props) => {
         <Dropdown>
           {renderOptionList()}
           {currentProject?.enable_reprovision && (
-            <OptionDis
+            <OptionDiv
               selected={false}
               onClick={() => {
                 setClusterModalVisible(true);
@@ -114,7 +114,7 @@ const ClusterList: React.FC = (props) => {
               }}
             >
               <Plus>+</Plus> Deploy new cluster
-            </OptionDis>
+            </OptionDiv>
           )}
         </Dropdown>
       </>
@@ -202,7 +202,7 @@ const InitializeButton = styled.div`
   }
 `;
 
-const OptionDis = styled.div<{ selected: boolean }>`
+const OptionDiv = styled.div<{ selected: boolean }>`
   width: 100%;
   height: 45px;
   display: flex;
@@ -274,7 +274,7 @@ const MainSelector = styled.div`
     justify-content: center;
     border-radius: 20px;
     background: ${(props: { expanded: boolean }) =>
-      props.expanded ? "#ffffff22" : ""};
+    props.expanded ? "#ffffff22" : ""};
   }
 `;
 
@@ -302,7 +302,7 @@ const NavButton = styled(SidebarLink)`
 
   :hover {
     background: ${(props: NavButtonProps) =>
-      props.active ? "#ffffff11" : "#ffffff08"};
+    props.active ? "#ffffff11" : "#ffffff08"};
   }
 
   &.active {

+ 5 - 5
dashboard/tsconfig.json

@@ -1,8 +1,6 @@
 {
   "compilerOptions": {
-    "lib": [
-      "ESNext"
-    ],
+    "lib": ["ESNext"],
     "baseUrl": "src",
     "outDir": "./build/",
     "sourceMap": true,
@@ -15,5 +13,7 @@
     "removeComments": true,
     "moduleResolution": "node",
     "strict": true,
-  }
-}
+    "skipLibCheck": true
+  },
+  "exclude": ["node_modules", "build"]
+}