Bladeren bron

do not allow users to name an app with same name as existing app (#3232)

Feroze Mohideen 2 jaren geleden
bovenliggende
commit
7845912faf

+ 22 - 23
dashboard/src/components/image-selector/ImageList.tsx

@@ -11,29 +11,29 @@ import TagList from "./TagList";
 
 type PropsType =
   | {
-      selectedImageUrl: string | null;
-      selectedTag: string | null;
-      clickedImage: ImageType | null;
-      registry?: any;
-      noTagSelection?: boolean;
-      setSelectedImageUrl: (x: string) => void;
-      setSelectedTag: (x: string) => void;
-      setClickedImage: (x: ImageType) => void;
-      disableImageSelect?: boolean;
-      readOnly?: boolean;
-    }
+    selectedImageUrl: string | null;
+    selectedTag: string | null;
+    clickedImage: ImageType | null;
+    registry?: any;
+    noTagSelection?: boolean;
+    setSelectedImageUrl: (x: string) => void;
+    setSelectedTag: (x: string) => void;
+    setClickedImage: (x: ImageType) => void;
+    disableImageSelect?: boolean;
+    readOnly?: boolean;
+  }
   | {
-      selectedImageUrl: string | null;
-      selectedTag: string | null;
-      clickedImage: ImageType | null;
-      registry?: any;
-      noTagSelection?: boolean;
-      setSelectedImageUrl?: (x: string) => void;
-      setSelectedTag?: (x: string) => void;
-      setClickedImage?: (x: ImageType) => void;
-      disableImageSelect?: boolean;
-      readOnly: true;
-    };
+    selectedImageUrl: string | null;
+    selectedTag: string | null;
+    clickedImage: ImageType | null;
+    registry?: any;
+    noTagSelection?: boolean;
+    setSelectedImageUrl?: (x: string) => void;
+    setSelectedTag?: (x: string) => void;
+    setClickedImage?: (x: ImageType) => void;
+    disableImageSelect?: boolean;
+    readOnly: true;
+  };
 
 type StateType = {
   loading: boolean;
@@ -128,7 +128,6 @@ export default class ImageList extends Component<PropsType, StateType> {
           });
         })
         .catch((err) => {
-          console.log(err);
           this.setState({ loading: false, error: true });
         });
     } else {

+ 2 - 6
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/ActivityFeed.tsx

@@ -94,11 +94,7 @@ const ActivityFeed: React.FC<Props> = ({ chart, stackName, appData, eventId }) =
       getEvents();
     }
 
-  }, [currentProject, currentCluster, hasPorterAgent, page]);
-
-  useEffect(() => {
-    getEvents();
-  }, [eventId]);
+  }, [currentProject, currentCluster, hasPorterAgent, page, eventId]);
 
   const installAgent = async () => {
     const project_id = currentProject?.id;
@@ -150,7 +146,7 @@ const ActivityFeed: React.FC<Props> = ({ chart, stackName, appData, eventId }) =
     />;
   }
 
-  if (!hasPorterAgent) {
+  if (!loading && !hasPorterAgent) {
     return (
       <Fieldset>
         <Text size={16}>

+ 2 - 0
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/events/cards/AppEventCard.tsx

@@ -15,6 +15,7 @@ import { readableDate } from "shared/string_utils";
 import dayjs from "dayjs";
 import Anser from "anser";
 import api from "shared/api";
+import { Direction } from "../../../logs/types";
 
 type Props = {
   event: PorterAppEvent;
@@ -36,6 +37,7 @@ const AppEventCard: React.FC<Props> = ({ event, appData }) => {
           end_range: dayjs(event.updated_at).add(1, 'minute').toISOString(),
           pod_selector: event.metadata.pod_name.endsWith(".*") ? event.metadata.pod_name : event.metadata.pod_name + ".*",
           limit: 1000,
+          direction: Direction.forward,
         },
         {
           project_id: appData.app.project_id,

+ 81 - 62
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -102,10 +102,73 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [detected, setDetected] = useState<Detected | undefined>(undefined);
   const [buildView, setBuildView] = useState<BuildMethod>("buildpacks");
 
+  const [existingApps, setExistingApps] = useState<string[]>([]);
+  const [appNameInputError, setAppNameInputError] = useState<string | undefined>(undefined);
+
   const [syncedEnvGroups, setSyncedEnvGroups] = useState<PopulatedEnvGroup[]>([]);
   const [showEnvModal, setShowEnvModal] = useState(false);
   const [deletedEnvGroups, setDeleteEnvGroups] = useState<PopulatedEnvGroup[]>([])
 
+  // this advances the step in the case that a user chooses a repo that doesn't have a porter.yaml
+  useEffect(() => {
+    if (porterApp.git_branch !== "") {
+      setCurrentStep(Math.max(currentStep, 2));
+    }
+  }, [porterApp.git_branch]);
+
+  useEffect(() => {
+    let isSubscribed = true;
+
+    if (currentProject == null) {
+      return;
+    }
+
+    api
+      .getGitProviders("<token>", {}, { project_id: currentProject?.id })
+      .then((res) => {
+        const data = res.data;
+        if (!isSubscribed) {
+          return;
+        }
+
+        if (!Array.isArray(data)) {
+          setHasProviders(false);
+          return;
+        }
+      })
+      .catch((err) => {
+        setHasProviders(false);
+      });
+
+    return () => {
+      isSubscribed = false;
+    };
+  }, [currentProject]);
+
+  useEffect(() => {
+    const getApps = async () => {
+      try {
+        const res = await api.getPorterApps(
+          "<token>",
+          {},
+          {
+            project_id: currentProject.id,
+            cluster_id: currentCluster.id,
+          }
+        );
+        if (res?.data != null) {
+          setExistingApps(res.data.map((app: PorterApp) => app.name));
+        }
+      } catch (err) {
+      }
+    };
+    getApps();
+  }, [])
+
+  useEffect(() => {
+    setFormState({ ...formState, serviceList: [] });
+  }, [porterApp.git_branch]);
+
   const handleSetAccessData = (data: GithubAppAccessData) => {
     setAccessData(data);
     setShowGithubConnectModal(
@@ -197,55 +260,16 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
     }
   };
 
-  // this advances the step in the case that a user chooses a repo that doesn't have a porter.yaml
-  useEffect(() => {
-    if (porterApp.git_branch !== "") {
-      setCurrentStep(Math.max(currentStep, 2));
-    }
-  }, [porterApp.git_branch]);
-
-  useEffect(() => {
-    let isSubscribed = true;
-
-    if (currentProject == null) {
-      return;
-    }
-
-    api
-      .getGitProviders("<token>", {}, { project_id: currentProject?.id })
-      .then((res) => {
-        const data = res.data;
-        if (!isSubscribed) {
-          return;
-        }
-
-        if (!Array.isArray(data)) {
-          setHasProviders(false);
-          return;
-        }
-      })
-      .catch((err) => {
-        setHasProviders(false);
-      });
-
-    return () => {
-      isSubscribed = false;
-    };
-  }, [currentProject]);
-
-  const isAppNameValid = (name: string) => {
-    const regex = /^[a-z0-9-]{1,61}$/;
-    return regex.test(name);
-  };
-
   const handleAppNameChange = (name: string) => {
     setPorterApp(PorterApp.setAttribute(porterApp, "name", name));
-    if (isAppNameValid(name) && Validators.applicationName(name)) {
+    const appNameInputError = getAppNameInputError(name);
+    if (appNameInputError == null) {
       setCurrentStep(Math.max(Math.max(currentStep, 1), existingStep));
     } else {
       setExistingStep(Math.max(currentStep, existingStep));
       setCurrentStep(0);
     }
+    setAppNameInputError(appNameInputError);
   };
 
   const handleDoNotConnect = () => {
@@ -253,13 +277,20 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
     localStorage.setItem("hasClickedDoNotConnect", "true");
   };
 
-  const shouldHighlightAppNameInput = () => {
-    return (
-      porterApp.name !== "" &&
-      (!isAppNameValid(porterApp.name) ||
-        porterApp.name.length > 61)
-    );
+  const getAppNameInputError = (name: string) => {
+    const regex = /^[a-z0-9-]{1,61}$/;
+    if (name === "") {
+      return undefined;
+    } else if (!regex.test(name)) {
+      return 'Lowercase letters, numbers, and "-" only.';
+    } else if (name.length > 30) {
+      return "Maximum 30 characters allowed.";
+    } else if (existingApps.includes(name)) {
+      return "An app with this name already exists.";
+    }
+    return undefined;
   };
+
   const deleteEnvGroup = (envGroup: PopulatedEnvGroup) => {
     setDeleteEnvGroups([...deletedEnvGroups, envGroup]);
     setSyncedEnvGroups(syncedEnvGroups?.filter(
@@ -418,10 +449,6 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
     }
   };
 
-  useEffect(() => {
-    setFormState({ ...formState, serviceList: [] });
-  }, [porterApp.git_branch]);
-
   return (
     <CenterWrapper>
       <Div>
@@ -460,17 +487,9 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                   placeholder="ex: academic-sophon"
                   value={porterApp.name}
                   width="300px"
-                  error={
-                    shouldHighlightAppNameInput() &&
-                    (porterApp.name.length > 30
-                      ? "Maximum 30 characters allowed."
-                      : 'Lowercase letters, numbers, and "-" only.')
-                  }
-                  setValue={(e) => {
-                    handleAppNameChange(e);
-                  }}
+                  error={appNameInputError}
+                  setValue={handleAppNameChange}
                 />
-                {shouldHighlightAppNameInput()}
               </>,
               <>
                 <Text size={16}>Deployment method</Text>