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

handle invalid routes / page not found

jusrhee 5 лет назад
Родитель
Сommit
4a8eea98a7

+ 13 - 60
dashboard/src/main/home/Home.tsx

@@ -23,6 +23,7 @@ import Navbar from "./navbar/Navbar";
 import NewProject from "./new-project/NewProject";
 import ProjectSettings from "./project-settings/ProjectSettings";
 import Sidebar from "./sidebar/Sidebar";
+import PageNotFound from "./PageNotFound";
 
 type PropsType = RouteComponentProps & {
   logOut: () => void;
@@ -75,9 +76,9 @@ class Home extends Component<PropsType, StateType> {
           creating = res.data[i].status === "creating";
         }
         if (creating) {
-          pushFiltered(this.props, "/dashboard", [
-            "project_id",
-          ], { tab: "provisioner" });
+          pushFiltered(this.props, "/dashboard", ["project_id"], {
+            tab: "provisioner",
+          });
         } else if (this.state.ghRedirect) {
           pushFiltered(this.props, "/integrations", ["project_id"]);
           this.setState({ ghRedirect: false });
@@ -182,9 +183,9 @@ class Home extends Component<PropsType, StateType> {
         project_id: this.props.currentProject.id,
       }
     );
-    return pushFiltered(this.props, "/dashboard", [
-      "project_id",
-    ], { tab: "provisioner" });
+    return pushFiltered(this.props, "/dashboard", ["project_id"], {
+      tab: "provisioner",
+    });
   };
 
   checkDO = () => {
@@ -214,9 +215,9 @@ class Home extends Component<PropsType, StateType> {
             });
           } else if (infras[0] === "docr") {
             this.provisionDOCR(tgtIntegration.id, tier, () => {
-              pushFiltered(this.props, "/dashboard", [
-                "project_id",
-              ], { tab: "provisioner" });
+              pushFiltered(this.props, "/dashboard", ["project_id"], {
+                tab: "provisioner",
+              });
             });
           } else {
             this.provisionDOKS(tgtIntegration.id, region, clusterName);
@@ -281,43 +282,16 @@ class Home extends Component<PropsType, StateType> {
 
   // TODO: move into ClusterDashboard
   renderDashboard = () => {
-    let { currentCluster, setCurrentModal } = this.context;
-    if (currentCluster && !currentCluster.name) {
+    let { currentCluster } = this.context;
+    if (!currentCluster || !currentCluster.name) {
       return (
         <DashboardWrapper>
-          <Placeholder>
-            <Bold>Porter - Getting</Bold>
-            <br />
-            <br />
-            1. Navigate to{" "}
-            <A onClick={() => setCurrentModal("ClusterConfigModal")}>
-              + Add a Cluster
-            </A>{" "}
-            and provide a kubeconfig. *<br />
-            <br />
-            2. Choose which contexts you would like to use from the{" "}
-            <A
-              onClick={() => {
-                setCurrentModal("ClusterConfigModal", { currentTab: "select" });
-              }}
-            >
-              Select Clusters
-            </A>{" "}
-            tab.
-            <br />
-            <br />
-            3. For additional information, please refer to our <A>docs</A>.
-            <br />
-            <br />
-            <br />* Make sure all fields are explicitly declared (e.g., certs
-            and keys).
-          </Placeholder>
+          <PageNotFound />
         </DashboardWrapper>
       );
     } else if (!currentCluster) {
       return <Loading />;
     }
-    console.log("current route", this.props.currentRoute);
     return (
       <DashboardWrapper>
         <ClusterDashboard
@@ -567,27 +541,6 @@ const DashboardWrapper = styled.div`
   padding-bottom: 120px;
 `;
 
-const A = styled.a`
-  color: #ffffff;
-  text-decoration: underline;
-  cursor: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "not-allowed" : "pointer"};
-`;
-
-const Placeholder = styled.div`
-  font-family: "Work Sans", sans-serif;
-  color: #6f6f6f;
-  font-size: 16px;
-  margin-left: 20px;
-  margin-top: 24vh;
-  user-select: none;
-`;
-
-const Bold = styled.div`
-  font-weight: bold;
-  font-size: 20px;
-`;
-
 const StyledHome = styled.div`
   width: 100vw;
   height: 100vh;

+ 98 - 0
dashboard/src/main/home/NoClusterPlaceholder.tsx

@@ -0,0 +1,98 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+import { RouteComponentProps, withRouter } from "react-router";
+
+import { pushFiltered } from "shared/routing";
+
+import { Context } from "shared/Context";
+
+type PropsType = RouteComponentProps & {};
+
+type StateType = {};
+
+class NoClusterPlaceholder extends Component<PropsType, StateType> {
+  state = {};
+
+  render() {
+    let { setCurrentModal, currentProject } = this.context;
+
+    return (
+      <StyledNoClusterPlaceholder>
+        <Bold>
+          <i className="material-icons">tips_and_updates</i>
+          Porter - Getting Started
+        </Bold>
+        <br />
+        <br />
+        1. If you're deploying from a repo{" "}
+        <A
+          onClick={() =>
+            window.open(`/api/oauth/projects/${currentProject.id}/github`)
+          }
+        >
+          link your GitHub account
+        </A>
+        <br />
+        <br />
+        2.{" "}
+        <A
+          onClick={() =>
+            pushFiltered(this.props, "/dashboard", ["project_id"], {
+              tab: "create-cluster",
+            })
+          }
+        >
+          Create a new cluster
+        </A>{" "}
+        or{" "}
+        <A onClick={() => setCurrentModal("ClusterInstructionsModal")}>
+          add an existing cluster
+        </A>{" "}
+        *
+        <br />
+        <br />
+        3. To receive community updates{" "}
+        <A onClick={() => window.open("https://discord.gg/34n7NN7FJ7")}>
+          join our official Discord
+        </A>
+        <br />
+        <br />
+        <br />* Required. For more information{" "}
+        <A onClick={() => window.open("https://docs.getporter.dev/docs")}>
+          refer to our docs
+        </A>
+      </StyledNoClusterPlaceholder>
+    );
+  }
+}
+
+NoClusterPlaceholder.contextType = Context;
+
+export default withRouter(NoClusterPlaceholder);
+
+const A = styled.a`
+  color: #ffffff;
+  text-decoration: underline;
+  cursor: ${(props: { disabled?: boolean }) =>
+    props.disabled ? "not-allowed" : "pointer"};
+`;
+
+const StyledNoClusterPlaceholder = styled.div`
+  font-family: "Work Sans", sans-serif;
+  color: #6f6f6f;
+  font-size: 16px;
+  margin-top: 12px;
+  user-select: none;
+`;
+
+const Bold = styled.div`
+  font-weight: bold;
+  font-size: 20px;
+  display: flex;
+  align-items: center;
+
+  > i {
+    font-size: 23px;
+    margin-right: 12px;
+  }
+`;

+ 134 - 0
dashboard/src/main/home/PageNotFound.tsx

@@ -0,0 +1,134 @@
+import React, { Component } from "react";
+import styled from "styled-components";
+import { RouteComponentProps, withRouter } from "react-router";
+
+import { pushFiltered } from "shared/routing";
+
+type PropsType = RouteComponentProps & {};
+
+type StateType = {};
+
+class PageNotFound extends Component<PropsType, StateType> {
+  state = {};
+
+  render() {
+    let { pathname } = this.props.location;
+    return (
+      <StyledPageNotFound>
+        <Mega>
+          404
+          <Inside>Page Not Found</Inside>
+        </Mega>
+        <Flex>
+          <BackButton
+            width="145px"
+            onClick={() =>
+              pushFiltered(this.props, "/dashboard", ["project_id"])
+            }
+          >
+            <i className="material-icons">home</i>
+            Return Home
+          </BackButton>
+          {pathname && (
+            <>
+              <Splitter>|</Splitter>
+              <Helper>Could not find "{pathname}"</Helper>
+            </>
+          )}
+        </Flex>
+      </StyledPageNotFound>
+    );
+  }
+}
+
+export default withRouter(PageNotFound);
+
+const Splitter = styled.div`
+  margin: 0 20px;
+  font-size: 27px;
+  font-weight: 200;
+  color: #ffffff15;
+`;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const Helper = styled.div`
+  font-size: 15px;
+`;
+
+const BackButton = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  cursor: pointer;
+  font-size: 13px;
+  height: 35px;
+  padding: 5px 16px;
+  padding-right: 15px;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  width: ${(props: { width: string }) => props.width};
+  color: white;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+  }
+
+  > i {
+    color: white;
+    font-size: 16px;
+    margin-right: 6px;
+    margin-left: -2px;
+  }
+`;
+
+const StyledPageNotFound = styled.div`
+  font-family: "Work Sans", sans-serif;
+  color: #6f6f6f;
+  font-size: 16px;
+  user-select: none;
+  padding-bottom: 20px;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+`;
+
+const Mega = styled.div`
+  font-size: 200px;
+  color: #ffffff06;
+  position: relative;
+  font-weight: bold;
+  text-align: center;
+
+  > i {
+    font-size: 23px;
+    margin-right: 12px;
+  }
+`;
+
+const Inside = styled.div`
+  position: absolute;
+  color: #6f6f6f;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 400;
+  font-size: 20px;
+
+  > i {
+    font-size: 23px;
+    margin-right: 12px;
+  }
+`;

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

@@ -61,7 +61,6 @@ export default class EnvGroupList extends Component<PropsType, StateType> {
             sortedGroups.sort((a: any, b: any) =>
               a.metadata.name > b.metadata.name ? 1 : -1
             );
-            console.log(sortedGroups);
             break;
           default:
             sortedGroups.sort((a: any, b: any) =>

+ 0 - 4
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -215,11 +215,9 @@ export default class ExpandedJobChart extends Component<PropsType, StateType> {
           releaseName == relNameAnn &&
           releaseNamespace == relNamespaceAnn
         ) {
-          console.log("belonged to chart");
           let newestImage =
             event.Object?.spec?.jobTemplate?.spec?.template?.spec?.containers[0]
               ?.image;
-          console.log("newest image", newestImage);
           if (
             newestImage &&
             newestImage !== "porterdev/hello-porter-job" &&
@@ -227,7 +225,6 @@ export default class ExpandedJobChart extends Component<PropsType, StateType> {
             newestImage !== "public.ecr.aws/o1j4x7p4/hello-porter-job" &&
             newestImage !== "public.ecr.aws/o1j4x7p4/hello-porter-job:latest"
           ) {
-            console.log("setting image placeholder to false");
             this.setState({ newestImage, imageIsPlaceholder: false });
           }
         }
@@ -368,7 +365,6 @@ export default class ExpandedJobChart extends Component<PropsType, StateType> {
       newestImage !== "public.ecr.aws/o1j4x7p4/hello-porter-job" &&
       newestImage !== "public.ecr.aws/o1j4x7p4/hello-porter-job:latest"
     ) {
-      console.log("set to false on sorting");
       this.setState({ jobs, newestImage, imageIsPlaceholder: false });
     } else {
       this.setState({ jobs });

+ 0 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -168,7 +168,6 @@ export default class Logs extends Component<PropsType, StateType> {
   componentDidMount() {
     let { selectedPod } = this.props;
     let status = this.getPodStatus(selectedPod?.status);
-    console.log("STATUS", selectedPod?.status, status);
     if (status == "running" || status == "succeeded") {
       this.setupWebsocket();
       this.scrollToBottom(false);

+ 3 - 1
dashboard/src/main/home/dashboard/ClusterList.tsx

@@ -63,7 +63,9 @@ class Templates extends Component<PropsType, StateType> {
         <TemplateBlock
           onClick={() => {
             this.context.setCurrentCluster(cluster);
-            pushFiltered(this.props, "/applications", ["project_id"], { cluster: cluster.name });
+            pushFiltered(this.props, "/applications", ["project_id"], {
+              cluster: cluster.name,
+            });
           }}
           key={i}
         >

+ 3 - 13
dashboard/src/main/home/dashboard/ClusterPlaceholder.tsx

@@ -6,6 +6,7 @@ import { ClusterType } from "shared/types";
 
 import ClusterList from "./ClusterList";
 import Loading from "components/Loading";
+import NoClusterPlaceholder from "../NoClusterPlaceholder";
 
 type PropsType = {
   currentCluster: ClusterType;
@@ -36,25 +37,14 @@ export default class ClusterPlaceholder extends Component<
   }
 
   render() {
-    if (this.state.loading) {
+    if (this.state.loading || this.props.currentCluster?.id === -1) {
       return (
         <LoadingWrapper>
           <Loading />
         </LoadingWrapper>
       );
     } else if (!this.props.currentCluster) {
-      return (
-        <StyledStatusPlaceholder>
-          You need to connect a cluster to use Porter.
-          <Highlight
-            onClick={() => {
-              this.context.setCurrentModal("ClusterInstructionsModal", {});
-            }}
-          >
-            + Connect an existing cluster
-          </Highlight>
-        </StyledStatusPlaceholder>
-      );
+      return <NoClusterPlaceholder />;
     } else {
       return <ClusterList currentCluster={this.props.currentCluster} />;
     }

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

@@ -54,7 +54,6 @@ class Dashboard extends Component<PropsType, StateType> {
   };
 
   componentDidMount() {
-    console.log("href is", window.location.href);
     this.refreshInfras();
     document.addEventListener("keydown", this.handleKeyDown);
     document.addEventListener("keyup", this.handleKeyUp);

+ 35 - 36
dashboard/src/main/home/launch/Launch.tsx

@@ -9,6 +9,7 @@ import TabSelector from "components/TabSelector";
 import ExpandedTemplate from "./expanded-template/ExpandedTemplate";
 import Loading from "components/Loading";
 import LaunchFlow from "./launch-flow/LaunchFlow";
+import NoClusterPlaceholder from "../NoClusterPlaceholder";
 
 import hardcodedNames from "./hardcodedNameDict";
 import semver from "semver";
@@ -197,6 +198,38 @@ export default class Templates extends Component<PropsType, StateType> {
     }
   };
 
+  renderContents = () => {
+    if (this.context.currentCluster) {
+      return (
+        <>
+          <TabSelector
+            options={tabOptions}
+            currentTab={this.state.currentTab}
+            setCurrentTab={(value: string) =>
+              this.setState({
+                currentTab: value,
+                currentTemplate: null,
+              })
+            }
+          />
+          {this.renderTabContents()}
+        </>
+      );
+    } else if (this.context.currentCluster?.id === -1) {
+      return <Loading />;
+    } else if (!this.context.currentCluster) {
+      return (
+        <>
+          <Banner>
+            <i className="material-icons">error_outline</i>
+            No cluster connected to this project.
+          </Banner>
+          <NoClusterPlaceholder />
+        </>
+      );
+    }
+  };
+
   render() {
     if (!this.state.isOnLaunchFlow || !this.state.currentTemplate) {
       return (
@@ -210,41 +243,7 @@ export default class Templates extends Component<PropsType, StateType> {
               <i className="material-icons">help_outline</i>
             </a>
           </TitleSection>
-          {this.context.currentCluster ? (
-            <>
-              <TabSelector
-                options={tabOptions}
-                currentTab={this.state.currentTab}
-                setCurrentTab={(value: string) =>
-                  this.setState({
-                    currentTab: value,
-                    currentTemplate: null,
-                  })
-                }
-              />
-              {this.renderTabContents()}
-            </>
-          ) : (
-            <>
-              <Banner>
-                <i className="material-icons">error_outline</i>
-                No cluster connected to this project.
-              </Banner>
-              <StyledStatusPlaceholder>
-                You need to connect a cluster to use Porter.
-                <Highlight
-                  onClick={() => {
-                    this.context.setCurrentModal(
-                      "ClusterInstructionsModal",
-                      {}
-                    );
-                  }}
-                >
-                  + Connect an existing cluster
-                </Highlight>
-              </StyledStatusPlaceholder>
-            </>
-          )}
+          {this.renderContents()}
         </TemplatesWrapper>
       );
     } else {
@@ -280,7 +279,7 @@ const Placeholder = styled.div`
 const Banner = styled.div`
   height: 40px;
   width: 100%;
-  margin: 30px 0 30px;
+  margin: 30px 0 38px;
   font-size: 13px;
   display: flex;
   border-radius: 5px;

+ 9 - 3
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -165,9 +165,13 @@ class LaunchTemplate extends Component<PropsType, StateType> {
         this.setState({ saveValuesStatus: "successful" }, () => {
           // TODO: redirect to appropriate cluster if not current context
           let dst =
-            this.props.currentTemplate.name === "job" ? "/jobs" : "/applications";
+            this.props.currentTemplate.name === "job"
+              ? "/jobs"
+              : "/applications";
           setTimeout(() => {
-            pushFiltered(this.props, dst, ["project_id"], { cluster: currentCluster.name });
+            pushFiltered(this.props, dst, ["project_id"], {
+              cluster: currentCluster.name,
+            });
           }, 500);
           window.analytics.track("Deployed Add-on", {
             name: this.props.currentTemplate.name,
@@ -304,7 +308,9 @@ class LaunchTemplate extends Component<PropsType, StateType> {
               this.props.currentTemplate.name === "job"
                 ? "/jobs"
                 : "/applications";
-            pushFiltered(this.props, dst, ["project_id"], { cluster: currentCluster.name });
+            pushFiltered(this.props, dst, ["project_id"], {
+              cluster: currentCluster.name,
+            });
           }, 1000);
         });
       })

+ 9 - 3
dashboard/src/main/home/launch/launch-flow/LaunchFlow.tsx

@@ -162,9 +162,13 @@ class LaunchFlow extends Component<PropsType, StateType> {
         this.setState({ saveValuesStatus: "successful" }, () => {
           // redirect to dashboard
           let dst =
-            this.props.currentTemplate.name === "job" ? "/jobs" : "/applications";
+            this.props.currentTemplate.name === "job"
+              ? "/jobs"
+              : "/applications";
           setTimeout(() => {
-            pushFiltered(this.props, dst, ["project_id"], { cluster: currentCluster.name });
+            pushFiltered(this.props, dst, ["project_id"], {
+              cluster: currentCluster.name,
+            });
           }, 500);
           window.analytics.track("Deployed Add-on", {
             name: this.props.currentTemplate.name,
@@ -318,7 +322,9 @@ class LaunchFlow extends Component<PropsType, StateType> {
               this.props.currentTemplate.name === "job"
                 ? "/jobs"
                 : "/applications";
-            pushFiltered(this.props, dst, ["project_id"], { cluster: currentCluster.name });
+            pushFiltered(this.props, dst, ["project_id"], {
+              cluster: currentCluster.name,
+            });
           }, 1000);
         });
       })

+ 0 - 1
dashboard/src/main/home/modals/LoadEnvGroupModal.tsx

@@ -55,7 +55,6 @@ export default class LoadEnvGroupModal extends Component<PropsType, StateType> {
           envGroups: res?.data?.items as any[],
           loading: false,
         });
-        console.log(res.data.items);
       })
       .catch((err) => {
         this.setState({ loading: false, error: true });

+ 3 - 1
dashboard/src/main/home/modals/UpdateClusterModal.tsx

@@ -52,7 +52,9 @@ class UpdateClusterModal extends Component<PropsType, StateType> {
           this.props.setRefreshClusters(true);
           this.setState({ status: "successful", showDeleteOverlay: false });
           this.context.setCurrentModal(null, null);
-          pushFiltered(this.props, "/dashboard", ["project_id"], { tab: "overview" });
+          pushFiltered(this.props, "/dashboard", ["project_id"], {
+            tab: "overview",
+          });
           return;
         }
 

+ 9 - 5
dashboard/src/main/home/provisioner/AWSFormSection.tsx

@@ -262,7 +262,9 @@ class AWSFormSection extends Component<PropsType, StateType> {
         )
       )
       .then(() =>
-        pushFiltered(this.props, "/dashboard", ["project_id"], { tab: "provisioner" })
+        pushFiltered(this.props, "/dashboard", ["project_id"], {
+          tab: "provisioner",
+        })
       )
       .catch(this.catchError);
   };
@@ -280,7 +282,9 @@ class AWSFormSection extends Component<PropsType, StateType> {
       } else if (selectedInfras[0].value === "ecr") {
         // Case: project exists, only provision ECR
         this.provisionECR().then(() =>
-          pushFiltered(this.props, "/dashboard", ["project_id"], { tab: "provisioner" })
+          pushFiltered(this.props, "/dashboard", ["project_id"], {
+            tab: "provisioner",
+          })
         );
       } else {
         // Case: project exists, only provision EKS
@@ -294,9 +298,9 @@ class AWSFormSection extends Component<PropsType, StateType> {
         // Case: project DNE, only provision ECR
         this.createProject(() =>
           this.provisionECR().then(() =>
-            pushFiltered(this.props, "/dashboard", [
-              "project_id",
-            ], { tab: "provisioner" })
+            pushFiltered(this.props, "/dashboard", ["project_id"], {
+              tab: "provisioner",
+            })
           )
         );
       } else {

+ 3 - 1
dashboard/src/main/home/provisioner/ExistingClusterSection.tsx

@@ -47,7 +47,9 @@ class ExistingClusterSection extends Component<PropsType, StateType> {
               return el.name === projectName;
             });
             setCurrentProject(proj);
-            pushFiltered(this.props, "/dashboard", ["project_id"], { tab: "overview" });
+            pushFiltered(this.props, "/dashboard", ["project_id"], {
+              tab: "overview",
+            });
           }
         }
       })

+ 6 - 6
dashboard/src/main/home/provisioner/GCPFormSection.tsx

@@ -223,7 +223,9 @@ class GCPFormSection extends Component<PropsType, StateType> {
         { project_id: currentProject.id }
       )
       .then((res) =>
-        pushFiltered(this.props, "/dashboard", ["project_id"], { tab: "provisioner" })
+        pushFiltered(this.props, "/dashboard", ["project_id"], {
+          tab: "provisioner",
+        })
       )
       .catch(this.catchError);
   };
@@ -243,7 +245,6 @@ class GCPFormSection extends Component<PropsType, StateType> {
       )
       .then((res) => {
         if (res?.data) {
-          console.log("gcp provisioned with response: ", res.data);
           let { id } = res.data;
 
           if (selectedInfras.length === 2) {
@@ -252,9 +253,9 @@ class GCPFormSection extends Component<PropsType, StateType> {
           } else if (selectedInfras[0].value === "gcr") {
             // Case: project exists, only provision GCR
             this.provisionGCR(id).then(() =>
-              pushFiltered(this.props, "/dashboard", [
-                "project_id",
-              ], { tab: "provisioner" })
+              pushFiltered(this.props, "/dashboard", ["project_id"], {
+                tab: "provisioner",
+              })
             );
           } else {
             // Case: project exists, only provision GKE
@@ -321,7 +322,6 @@ class GCPFormSection extends Component<PropsType, StateType> {
   render() {
     let { setSelectedProvisioner } = this.props;
     let { gcpRegion, gcpProjectId, gcpKeyData, selectedInfras } = this.state;
-    console.log("gcpkeydata", gcpKeyData);
     return (
       <StyledGCPFormSection>
         <FormSection>

+ 7 - 3
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -38,7 +38,12 @@ class ClusterSection extends Component<PropsType, StateType> {
   };
 
   updateClusters = () => {
-    let { user, currentProject, setCurrentCluster, currentCluster } = this.context;
+    let {
+      user,
+      currentProject,
+      setCurrentCluster,
+      currentCluster,
+    } = this.context;
 
     // TODO: query with selected filter once implemented
     api
@@ -55,7 +60,6 @@ class ClusterSection extends Component<PropsType, StateType> {
           let clusters = res.data;
           clusters.sort((a: any, b: any) => a.id - b.id);
           if (clusters.length > 0) {
-
             // Set cluster from URL if specified
             let queryString = window.location.search;
             let urlParams = new URLSearchParams(queryString);
@@ -66,7 +70,7 @@ class ClusterSection extends Component<PropsType, StateType> {
                 if (cluster.name === clusterName) {
                   defaultCluster = cluster;
                 }
-              })
+              });
             }
 
             this.setState({ clusters });

+ 3 - 1
dashboard/src/main/home/sidebar/Drawer.tsx

@@ -35,7 +35,9 @@ class Drawer extends Component<PropsType, StateType> {
             active={cluster.name === currentCluster.name}
             onClick={() => {
               setCurrentCluster(cluster, () => {
-                pushFiltered(this.props, "/applications", ["project_id"], { cluster: cluster.name });
+                pushFiltered(this.props, "/applications", ["project_id"], {
+                  cluster: cluster.name,
+                });
               });
             }}
           >

+ 11 - 3
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -107,7 +107,9 @@ class Sidebar extends Component<PropsType, StateType> {
           <NavButton
             selected={currentView === "applications"}
             onClick={() =>
-              pushFiltered(this.props, "/applications", ["project_id"], { cluster: currentCluster.name })
+              pushFiltered(this.props, "/applications", ["project_id"], {
+                cluster: currentCluster.name,
+              })
             }
           >
             <Img src={monoweb} />
@@ -115,7 +117,11 @@ class Sidebar extends Component<PropsType, StateType> {
           </NavButton>
           <NavButton
             selected={currentView === "jobs"}
-            onClick={() => pushFiltered(this.props, "/jobs", ["project_id"], { cluster: currentCluster.name })}
+            onClick={() =>
+              pushFiltered(this.props, "/jobs", ["project_id"], {
+                cluster: currentCluster.name,
+              })
+            }
           >
             <Img src={monojob} />
             Jobs
@@ -123,7 +129,9 @@ class Sidebar extends Component<PropsType, StateType> {
           <NavButton
             selected={currentView === "env-groups"}
             onClick={() =>
-              pushFiltered(this.props, "/env-groups", ["project_id"], { cluster: currentCluster.name })
+              pushFiltered(this.props, "/env-groups", ["project_id"], {
+                cluster: currentCluster.name,
+              })
             }
           >
             <Img src={sliders} />

+ 8 - 1
dashboard/src/shared/Context.tsx

@@ -37,7 +37,14 @@ class ContextProvider extends Component<PropsType, StateType> {
     setCurrentError: (currentError: string) => {
       this.setState({ currentError });
     },
-    currentCluster: null as ClusterType | null,
+    currentCluster: {
+      id: -1,
+      name: "",
+      server: "",
+      service_account_id: -1,
+      infra_id: -1,
+      service: "",
+    },
     setCurrentCluster: (currentCluster: ClusterType, callback?: any) => {
       localStorage.setItem(
         this.state.currentProject.id + "-cluster",

+ 5 - 4
dashboard/src/shared/routing.tsx

@@ -40,7 +40,7 @@ export const pushFiltered = (
   props: any, // Props for retrieving history and location
   pathname: string, // Path to redirect to
   keys: string[], // Query params to preserve during redirect
-  params?: any,
+  params?: any
 ) => {
   let { location, history } = props;
   let urlParams = new URLSearchParams(location.search);
@@ -49,9 +49,10 @@ export const pushFiltered = (
     let value = urlParams.get(key);
     value && newUrlParams.set(key, value);
   });
-  params && Object.keys(params)?.forEach((key: string) => {
-    params[key] && newUrlParams.set(key, params[key]);
-  });
+  params &&
+    Object.keys(params)?.forEach((key: string) => {
+      params[key] && newUrlParams.set(key, params[key]);
+    });
   history.push({
     pathname,
     search: newUrlParams.toString(),