jusrhee 2 лет назад
Родитель
Сommit
6b8135bee1

Разница между файлами не показана из-за своего большого размера
+ 191 - 0
dashboard/src/assets/placeholder.svg


+ 10 - 1
dashboard/src/components/porter/DashboardPlaceholder.tsx

@@ -1,7 +1,7 @@
 import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 
-import placeholder from "assets/placeholder.png";
+import placeholder from "assets/placeholder.svg";
 
 type Props = {
   children: React.ReactNode;
@@ -42,4 +42,13 @@ const StyledDashboardPlaceholder = styled.div<{
   border-radius: 10px;
   position: relative;
   overflow: hidden;
+  animation: fadeIn 0.5s;
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
 `;

+ 1 - 0
dashboard/src/components/porter/Toggle.tsx

@@ -53,6 +53,7 @@ const Item = styled.div<{ active: boolean; activeColor?: string; inactiveColor?:
   cursor: pointer;
   justify-content: center;
   padding: 10px;
+  opacity: ${(props) => props.active ? "1" : "0.4"};
   background: ${(props) =>
     props.active ? props.activeColor ?? "#ffffff11" : props.inactiveColor ?? "transparent"};
 `;

+ 0 - 4
dashboard/src/main/home/app-dashboard/apps/Apps.tsx

@@ -273,8 +273,6 @@ const Apps: React.FC = () => {
                 setSort("letter");
               }
             }}
-            inactiveColor={"#ffffff11"}
-            activeColor={"transparent"}
           />
           <Spacer inline x={1} />
           <Toggle
@@ -290,8 +288,6 @@ const Apps: React.FC = () => {
                 setView("list");
               }
             }}
-            inactiveColor={"#ffffff11"}
-            activeColor={"transparent"}
           />
           <Spacer inline x={2} />
           {currentDeploymentTarget?.is_preview ? (

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

@@ -157,8 +157,7 @@ const GithubActionModal: React.FC<Props> = ({
           <Select
             options={[
               {
-                label:
-                  "I authorize Porter to open a PR on my behalf (recommended)",
+                label: "I authorize Porter to open a PR on my behalf (recommended)",
                 value: "open_pr",
               },
               {
@@ -166,6 +165,7 @@ const GithubActionModal: React.FC<Props> = ({
                 value: "copy",
               },
             ]}
+            value={choice}
             setValue={(x: string) => {
               setChoice(x as Choice);
             }}

+ 208 - 136
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupDashboard.tsx

@@ -1,10 +1,12 @@
-import React, { Component, useContext, useEffect, useState } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
 import sliders from "assets/env-groups.svg";
 
 import { Context } from "shared/Context";
-import { ClusterType } from "shared/types";
+import api from "shared/api";
+
+import { type ClusterType } from "shared/types";
 
 import DashboardHeader from "../DashboardHeader";
 import { NamespaceSelector } from "../NamespaceSelector";
@@ -12,63 +14,41 @@ import SortSelector from "../SortSelector";
 import EnvGroupList from "./EnvGroupList";
 import CreateEnvGroup from "./CreateEnvGroup";
 import ExpandedEnvGroup from "./ExpandedEnvGroup";
-import { RouteComponentProps, withRouter } from "react-router";
-import { getQueryParam, pushQueryParams, pushFiltered } from "shared/routing";
-import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
+import { withRouter, type RouteComponentProps } from "react-router";
+import { getQueryParam, pushQueryParams } from "shared/routing";
+import { withAuth, type WithAuthProps } from "shared/auth/AuthorizationHoc";
 import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
 import Spacer from "components/porter/Spacer";
+import Loading from "components/Loading";
+import Placeholder from "components/Placeholder";
+import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
+import Text from "components/porter/Text";
+import Link from "components/porter/Link";
+import Button from "components/porter/Button";
 
-type PropsType = RouteComponentProps &
-  WithAuthProps & {
-    currentCluster: ClusterType;
-  };
+type Props = RouteComponentProps & WithAuthProps;
 
-type StateType = {
-  expand: boolean;
-  update: any[];
-  sortType: string;
-  expandedEnvGroup: any;
-  namespace: string;
-  createEnvMode: boolean;
-};
-
-const EnvGroupDashboard = (props: PropsType) => {
-  const [state, setState] = useState<StateType>({
-    expand: false,
-    update: [] as any[],
-    namespace: null as string,
-    expandedEnvGroup: null as any,
-    createEnvMode: false,
-    sortType: localStorage.getItem("SortType")
-      ? localStorage.getItem("SortType")
-      : "Newest",
-  });
-
-  const { currentProject } = useContext(Context);
-
-  const setNamespace = (namespace: string) => {
-    setState((state) => ({ ...state, namespace }));
+const EnvGroupDashboard: React.FC<Props> = (props) => {
+  const { currentProject, currentCluster } = useContext(Context);
+  const [expand, setExpand] = useState<boolean>(false);
+  const [update, setUpdate] = useState<any[]>([]);
+  const [namespace, setNamespace] = useState<string>("");
+  const [expandedEnvGroup, setExpandedEnvGroup] = useState<any>(null);
+  const [createEnvMode, setCreateEnvMode] = useState<boolean>(false);
+  const [sortType, setSortType] = useState<string>(
+    localStorage.getItem("SortType") || "Newest"
+  );
+  const [envGroups, setEnvGroups] = useState<any[]>([]);
+  const [isLoading, setIsLoading] = useState<boolean>(true);
+  const [hasError, setHasError] = useState<boolean>(false);
+  
+  useEffect(() => {
     pushQueryParams(props, {
-      namespace: currentProject.simplified_view_enabled ? ("porter-env-group") : (namespace ?? "ALL"),
-    });
-  };
-
-  const setSortType = (sortType: string) => {
-    setState((state) => ({ ...state, sortType }));
-  };
-
-  const toggleCreateEnvMode = () => {
-    setState((state) => ({
-      ...state,
-      createEnvMode: !state.createEnvMode,
-    }));
-  };
+      namespace: currentProject?.simplified_view_enabled ? ("porter-env-group") : (namespace ?? "ALL"),
+    })
+  }, [namespace]);
 
-  const setExpandedEnvGroup = (envGroup: any | null) => {
-    setState((state) => ({ ...state, expandedEnvGroup: envGroup }));
-  };
-
-  const closeExpanded = () => {
+  const closeExpanded = (): void => {
     pushQueryParams(props, {}, ["selected_env_group"]);
     const redirectUrlOnClose = getQueryParam(props, "redirect_url");
     if (redirectUrlOnClose) {
@@ -78,17 +58,101 @@ const EnvGroupDashboard = (props: PropsType) => {
     setExpandedEnvGroup(null);
   };
 
-  const renderBody = () => {
-    if (props.currentCluster.status === "UPDATING_UNAVAILABLE") {
+  const updateEnvGroups = async () => {
+    try {
+      let envGroups: any[] = []
+      if (currentProject?.simplified_view_enabled) {
+        envGroups = await api
+          .getAllEnvGroups(
+            "<token>",
+            {},
+            {
+              id: currentProject.id,
+              cluster_id: currentCluster?.id || -1,
+            }
+          )
+          .then((res: any) => {
+            return res.data?.environment_groups;
+          });
+      } else {
+        envGroups = await api.listEnvGroups(
+          "<token>",
+          {},
+          {
+            id: currentProject?.id || -1,
+            namespace,
+            cluster_id: currentCluster?.id || -1,
+          }
+        )
+        .then((res) => {
+          return res.data;
+        });
+      }
+      const sortedGroups = envGroups;
+      if (sortedGroups) {
+        switch (sortType) {
+          case "Oldest":
+            sortedGroups.sort((a: any, b: any) =>
+              Date.parse(a.created_at) > Date.parse(b.created_at) ? 1 : -1
+            );
+            break;
+          case "Alphabetical":
+            sortedGroups.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
+            break;
+          default:
+            sortedGroups.sort((a: any, b: any) =>
+              Date.parse(a.created_at) > Date.parse(b.created_at) ? -1 : 1
+            );
+        }
+      }
+      return sortedGroups;
+    } catch (error) {
+      setIsLoading(false);
+      setHasError(true);
+    }
+  };
+
+  useEffect(() => {
+    // Prevents reload when opening ClusterConfigModal
+    (namespace || namespace === "") &&
+      updateEnvGroups().then((envGroups) => {
+        const selectedEnvGroup = getQueryParam(props, "selected_env_group");
+
+        setEnvGroups(envGroups);
+        if (envGroups && envGroups.length > 0) {
+          setHasError(false);
+        }
+        setIsLoading(false);
+
+        if (selectedEnvGroup) {
+          // find env group by selectedEnvGroup
+          const envGroup = envGroups.find(
+            (envGroup: any) => envGroup.name === selectedEnvGroup
+          );
+          if (envGroup) {
+            setExpandedEnvGroup(envGroup);
+          } else {
+            pushQueryParams(props, {}, ["selected_env_group"]);
+          }
+        }
+      });
+  }, [currentCluster, namespace, sortType, createEnvMode]);
+
+  const renderBody = (): React.ReactNode => {
+    if (currentCluster?.status === "UPDATING_UNAVAILABLE") {
       return <ClusterProvisioningPlaceholder />
     }
 
-    const goBack = () =>
-      setState((state) => ({ ...state, createEnvMode: false }));
+    const goBack = (): void => {
+      setCreateEnvMode(false);
+    };
 
-    if (state.createEnvMode) {
+    if (createEnvMode) {
       return (
-        <CreateEnvGroup goBack={goBack} currentCluster={props.currentCluster} />
+        <CreateEnvGroup 
+          goBack={goBack}
+          currentCluster={currentCluster as ClusterType}
+        />
       );
     } else {
       const isAuthorizedToAdd = props.isAuthorized("env_group", "", [
@@ -98,48 +162,85 @@ const EnvGroupDashboard = (props: PropsType) => {
 
       return (
         <>
-          <ControlRow hasMultipleChilds={isAuthorizedToAdd}>
-            <SortFilterWrapper>
-              <SortSelector
-                currentView="env-groups"
-                setSortType={setSortType}
-                sortType={state.sortType}
-              />
-              <Spacer inline width="10px" />
-              {!currentProject.simplified_view_enabled && <NamespaceSelector
-                setNamespace={setNamespace}
-                namespace={currentProject.simplified_view_enabled ? "porter-env-group" : state.namespace}
-              />}
-            </SortFilterWrapper>
-            <Flex>
-              {isAuthorizedToAdd && (
-                <Button onClick={toggleCreateEnvMode}>
-                  <i className="material-icons">add</i> Create env group
-                </Button>
-              )}
-            </Flex>
-          </ControlRow>
-
-          <EnvGroupList
-            currentCluster={props.currentCluster}
-            namespace={currentProject?.simplified_view_enabled ? "porter-env-group" : state.namespace}
-            sortType={state.sortType}
-            setExpandedEnvGroup={setExpandedEnvGroup}
-          />
+          {isLoading || (!namespace && namespace !== "") ? (
+            <LoadingWrapper>
+              <Loading />
+            </LoadingWrapper>
+          ) : (
+            hasError ? (
+              <Placeholder height="370px">
+                <i className="material-icons">error</i> Error connecting to cluster.
+              </Placeholder>
+            ) : (
+              !envGroups || envGroups.length === 0 ? (
+                <DashboardPlaceholder>
+                  <Text size={16}>No environment groups found</Text>
+                  <Spacer y={0.5} />
+                  <Text color={"helper"}>Get started by creating an environment group.</Text>
+                  <Spacer y={1} />
+                  <Button
+                    height="35px"
+                    alt
+                    onClick={() => {
+                      setCreateEnvMode(!createEnvMode);
+                    }}
+                  >
+                    Create environment group <Spacer inline x={1} />{" "}
+                    <i className="material-icons" style={{ fontSize: "18px" }}>
+                      east
+                    </i>
+                  </Button>
+                </DashboardPlaceholder>
+              ) : (
+                <>
+                  <ControlRow hasMultipleChilds={isAuthorizedToAdd}>
+                    <SortFilterWrapper>
+                      <SortSelector
+                        currentView="env-groups"
+                        setSortType={setSortType}
+                        sortType={sortType}
+                      />
+                      <Spacer inline width="10px" />
+                      {!currentProject?.simplified_view_enabled && <NamespaceSelector
+                        setNamespace={setNamespace}
+                        namespace={currentProject?.simplified_view_enabled ? "porter-env-group" : namespace}
+                      />}
+                    </SortFilterWrapper>
+                    <Flex>
+                      {isAuthorizedToAdd && (
+                        <Button onClick={() => {
+                          setCreateEnvMode(!createEnvMode);
+                        }}>
+                          <I className="material-icons">add</I> Create env group
+                        </Button>
+                      )}
+                    </Flex>
+                  </ControlRow>
+                  <EnvGroupList
+                    envGroups={envGroups}
+                    currentCluster={currentCluster as ClusterType}
+                    namespace={currentProject?.simplified_view_enabled ? "porter-env-group" : namespace}
+                    sortType={sortType}
+                    setExpandedEnvGroup={setExpandedEnvGroup}
+                  />
+                </>
+              )
+            )
+          )}
         </>
       );
     }
   };
 
-  const renderContents = () => {
-    if (state.expandedEnvGroup) {
+  const renderContents = (): React.ReactNode => {
+    if (expandedEnvGroup) {
       return (
         <ExpandedEnvGroup
           isAuthorized={props.isAuthorized}
-          namespace={currentProject?.simplified_view_enabled ? "porter-env-group" : (state.expandedEnvGroup?.namespace || state.namespace)}
-          currentCluster={props.currentCluster}
-          envGroup={state.expandedEnvGroup}
-          closeExpanded={() => closeExpanded()}
+          namespace={currentProject?.simplified_view_enabled ? "porter-env-group" : (expandedEnvGroup?.namespace || namespace)}
+          currentCluster={currentCluster as ClusterType}
+          envGroup={expandedEnvGroup}
+          closeExpanded={closeExpanded}
         />
       );
     } else {
@@ -163,6 +264,19 @@ const EnvGroupDashboard = (props: PropsType) => {
 
 export default withRouter(withAuth(EnvGroupDashboard));
 
+const I = styled.i`
+  color: white;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  margin-right: 5px;
+  justify-content: center;
+`;
+
+const LoadingWrapper = styled.div`
+  padding-top: 100px;
+`;
+
 const Flex = styled.div`
   display: flex;
   align-items: center;
@@ -187,46 +301,4 @@ const ControlRow = styled.div`
   }};
   align-items: center;
   flex-wrap: wrap;
-`;
-
-const Button = styled.div`
-  display: flex;
-  margin-left: 10px;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 13px;
-  cursor: pointer;
-  font-family: "Work Sans", sans-serif;
-  border-radius: 5px;
-  color: white;
-  height: 30px;
-  padding: 0 8px;
-  min-width: 155px;
-  padding-right: 13px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  cursor: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "not-allowed" : "pointer"};
-
-  background: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "#aaaabbee" : "#616FEEcc"};
-  :hover {
-    background: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "" : "#505edddd"};
-  }
-
-  > i {
-    color: white;
-    width: 18px;
-    height: 18px;
-    font-weight: 600;
-    font-size: 12px;
-    border-radius: 20px;
-    display: flex;
-    align-items: center;
-    margin-right: 5px;
-    justify-content: center;
-  }
-`;
+`;

+ 16 - 149
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroupList.tsx

@@ -1,163 +1,30 @@
-import React, { useContext, useEffect, useMemo, useState } from "react";
+import React from "react";
 import styled from "styled-components";
 
-import { Context } from "shared/Context";
-import api from "shared/api";
-import { ClusterType } from "shared/types";
-
 import EnvGroup from "./EnvGroup";
-import Loading from "components/Loading";
-import { getQueryParam, pushQueryParams } from "shared/routing";
-import { RouteComponentProps, withRouter } from "react-router";
-
-import Placeholder from "components/Placeholder";
+import { type RouteComponentProps, withRouter } from "react-router";
 
 type Props = RouteComponentProps & {
-  currentCluster: ClusterType;
-  namespace: string;
-  sortType: string;
-  setExpandedEnvGroup: (envGroup: any) => void;
-};
-
-type State = {
   envGroups: any[];
-  loading: boolean;
-  error: boolean;
 };
 
-const EnvGroupList: React.FunctionComponent<Props> = (props) => {
-  const context = useContext(Context);
-
-  const { currentCluster, namespace, sortType, setExpandedEnvGroup } = props;
-
-  const [envGroups, setEnvGroups] = useState<any[]>([]);
-  const [isLoading, setIsLoading] = useState<boolean>(true);
-  const [hasError, setHasError] = useState<boolean>(false);
-
-  const updateEnvGroups = async () => {
-    let { currentProject, currentCluster } = context;
-    try {
-      let envGroups: any[] = []
-      if (currentProject?.simplified_view_enabled) {
-        envGroups = await api
-          .getAllEnvGroups(
-            "<token>",
-            {},
-            {
-              id: currentProject.id,
-              cluster_id: currentCluster.id,
-            }
-          )
-          .then((res) => {
-            return res.data?.environment_groups;
-          });
-      } else {
-        envGroups = await api
-          .listEnvGroups(
-            "<token>",
-            {},
-            {
-              id: currentProject.id,
-              namespace: namespace,
-              cluster_id: currentCluster.id,
-            }
-          )
-          .then((res) => {
-            return res.data;
-          });
-      }
-      let sortedGroups = envGroups;
-      if (sortedGroups) {
-        switch (sortType) {
-          case "Oldest":
-            sortedGroups.sort((a: any, b: any) =>
-              Date.parse(a.created_at) > Date.parse(b.created_at) ? 1 : -1
-            );
-            break;
-          case "Alphabetical":
-            sortedGroups.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
-            break;
-          default:
-            sortedGroups.sort((a: any, b: any) =>
-              Date.parse(a.created_at) > Date.parse(b.created_at) ? -1 : 1
-            );
-        }
-      }
-      return sortedGroups;
-    } catch (error) {
-      console.log(error)
-      setIsLoading(false);
-      setHasError(true);
-    }
-  };
-
-  useEffect(() => {
-    // Prevents reload when opening ClusterConfigModal
-    (namespace || namespace === "") &&
-      updateEnvGroups().then((envGroups) => {
-        const selectedEnvGroup = getQueryParam(props, "selected_env_group");
-
-        setEnvGroups(envGroups);
-        if (envGroups && envGroups.length > 0) {
-          setHasError(false);
-        }
-        setIsLoading(false);
-
-        if (selectedEnvGroup) {
-          // find env group by selectedEnvGroup
-          const envGroup = envGroups.find(
-            (envGroup: any) => envGroup.name === selectedEnvGroup
-          );
-          if (envGroup) {
-            setExpandedEnvGroup(envGroup);
-          } else {
-            pushQueryParams(props, {}, ["selected_env_group"]);
-          }
-        }
-      });
-  }, [currentCluster, namespace, sortType]);
-
-  const renderEnvGroupList = () => {
-    if (isLoading || (!namespace && namespace !== "")) {
-      return (
-        <LoadingWrapper>
-          <Loading />
-        </LoadingWrapper>
-      );
-    } else if (hasError) {
-      return (
-        <Placeholder height="370px">
-          <i className="material-icons">error</i> Error connecting to cluster.
-        </Placeholder>
-      );
-    } else if (!envGroups || envGroups.length === 0) {
-      return (
-        <Placeholder height="370px">
-          <i className="material-icons">category</i>
-          No environment groups found with the given filters.
-        </Placeholder>
-      );
-    }
-
-    return envGroups.map((envGroup: any, i: number) => {
-      return (
-        <EnvGroup
-          key={i}
-          envGroup={envGroup}
-        />
-      );
-    });
-  };
-
-  return <StyledEnvGroupList>{renderEnvGroupList()}</StyledEnvGroupList>;
+const EnvGroupList: React.FunctionComponent<Props> = ({ envGroups }) => {
+  return (
+    <StyledEnvGroupList>
+      {envGroups.map((envGroup: any, i: number) => {
+        return (
+          <EnvGroup
+            key={i}
+            envGroup={envGroup}
+          />
+        );
+      })}
+    </StyledEnvGroupList>
+  );
 };
 
 export default withRouter(EnvGroupList);
 
-const LoadingWrapper = styled.div`
-  padding-top: 100px;
-`;
-
 const StyledEnvGroupList = styled.div`
   padding-bottom: 85px;
-`;
+`;

Некоторые файлы не были показаны из-за большого количества измененных файлов