Преглед изворни кода

Implemented component to change source config

jnfrati пре 3 година
родитељ
комит
3b25b6b86f

+ 40 - 6
dashboard/src/main/home/cluster-dashboard/stacks/ExpandedStack/_SourceConfig.tsx

@@ -1,10 +1,29 @@
 import { Tooltip } from "@material-ui/core";
 import { Tooltip } from "@material-ui/core";
 import ImageSelector from "components/image-selector/ImageSelector";
 import ImageSelector from "components/image-selector/ImageSelector";
-import React from "react";
+import SaveButton from "components/SaveButton";
+import React, { useMemo, useState } from "react";
 import styled from "styled-components";
 import styled from "styled-components";
 import { AppResource, FullStackRevision, SourceConfig } from "../types";
 import { AppResource, FullStackRevision, SourceConfig } from "../types";
+import SourceEditorDocker from "./components/SourceEditorDocker";
 
 
 const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
 const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
+  const [sourceConfigArrayCopy, setSourceConfigArrayCopy] = useState<
+    SourceConfig[]
+  >(() => revision.source_configs);
+
+  const handleChange = (sourceConfig: SourceConfig) => {
+    const newSourceConfigArray = [...sourceConfigArrayCopy];
+    const index = newSourceConfigArray.findIndex(
+      (sc) => sc.id === sourceConfig.id
+    );
+    newSourceConfigArray[index] = sourceConfig;
+    setSourceConfigArrayCopy(newSourceConfigArray);
+  };
+
+  const handleSave = () => {
+    console.log("save");
+  };
+
   return (
   return (
     <SourceConfigStyles.Wrapper>
     <SourceConfigStyles.Wrapper>
       {revision.source_configs.map((sourceConfig) => {
       {revision.source_configs.map((sourceConfig) => {
@@ -36,15 +55,21 @@ const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
                 Used by {appList.value}
                 Used by {appList.value}
               </SourceConfigStyles.ItemTitle>
               </SourceConfigStyles.ItemTitle>
             )}
             )}
-            <ImageSelector
-              selectedImageUrl={sourceConfig.image_repo_uri}
-              selectedTag={sourceConfig.image_tag}
-              forceExpanded
-              readOnly
+            <SourceEditorDocker
+              sourceConfig={sourceConfig}
+              onChange={handleChange}
             />
             />
           </SourceConfigStyles.ItemContainer>
           </SourceConfigStyles.ItemContainer>
         );
         );
       })}
       })}
+      <SourceConfigStyles.SaveButtonRow>
+        <SourceConfigStyles.SaveButton
+          onClick={handleSave}
+          text="Save"
+          clearPosition={true}
+          makeFlush={true}
+        />
+      </SourceConfigStyles.SaveButtonRow>
     </SourceConfigStyles.Wrapper>
     </SourceConfigStyles.Wrapper>
   );
   );
 };
 };
@@ -89,6 +114,7 @@ const formatAppList = (apps: AppResource[], limit: number = 3) => {
 const SourceConfigStyles = {
 const SourceConfigStyles = {
   Wrapper: styled.div`
   Wrapper: styled.div`
     margin-top: 30px;
     margin-top: 30px;
+    position: relative;
   `,
   `,
   ItemContainer: styled.div`
   ItemContainer: styled.div`
     background: #ffffff11;
     background: #ffffff11;
@@ -102,4 +128,12 @@ const SourceConfigStyles = {
   TooltipItem: styled.div`
   TooltipItem: styled.div`
     font-size: 14px;
     font-size: 14px;
   `,
   `,
+  SaveButtonRow: styled.div`
+    margin-top: 15px;
+    display: flex;
+    justify-content: flex-end;
+  `,
+  SaveButton: styled(SaveButton)`
+    z-index: unset;
+  `,
 };
 };

+ 265 - 0
dashboard/src/main/home/cluster-dashboard/stacks/ExpandedStack/components/SourceEditorDocker.tsx

@@ -0,0 +1,265 @@
+import SelectRow from "components/form-components/SelectRow";
+import SearchSelector from "components/SearchSelector";
+import Selector from "components/Selector";
+import React, { useContext, useEffect, useMemo, useState } from "react";
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { proxy, useSnapshot } from "valtio";
+import { SourceConfig } from "../../types";
+
+const SourceEditorDocker = ({
+  sourceConfig,
+  onChange,
+}: {
+  sourceConfig: SourceConfig;
+  onChange: (sourceConfig: SourceConfig) => void;
+}) => {
+  const [registry, setRegistry] = useState<DockerRegistry | null>(null);
+  const [image, setImage] = useState<string | null>(
+    () => sourceConfig.image_repo_uri
+  );
+  const [tag, setTag] = useState<string | null>(() => sourceConfig.image_tag);
+
+  const imageName = useMemo(() => {
+    if (!registry) {
+      return "";
+    }
+
+    if (!image) {
+      return "";
+    }
+
+    return image.replace(registry.url + "/", "");
+  }, [image, registry]);
+
+  useEffect(() => {
+    const newSourceConfig: SourceConfig = {
+      ...sourceConfig,
+      image_repo_uri: image,
+      image_tag: tag,
+    };
+
+    onChange(newSourceConfig);
+  }, [image, tag]);
+
+  return (
+    <>
+      <_DockerRepositorySelector
+        currentImageUrl={sourceConfig.image_repo_uri}
+        value={registry}
+        onChange={setRegistry}
+      />
+      {registry && (
+        <_ImageSelector registry={registry} value={image} onChange={setImage} />
+      )}
+      {registry && imageName && (
+        <_TagSelector
+          registry={registry}
+          imageName={imageName}
+          value={tag}
+          onChange={setTag}
+        />
+      )}
+    </>
+  );
+};
+
+type DockerRegistry = {
+  id: number;
+  project_id: number;
+  name: string;
+  url: string;
+  service: string;
+  infra_id: number;
+  aws_integration_id: number;
+};
+
+const _DockerRepositorySelector = ({
+  currentImageUrl,
+  value,
+  onChange,
+}: {
+  currentImageUrl: string;
+  value: DockerRegistry;
+  onChange: (newRegistry: DockerRegistry) => void;
+}) => {
+  const { currentProject } = useContext(Context);
+
+  const [registries, setRegistries] = useState<DockerRegistry[]>([]);
+
+  useEffect(() => {
+    api
+      .getProjectRegistries<DockerRegistry[]>(
+        "<token>",
+        {},
+        {
+          id: currentProject.id,
+        }
+      )
+      .then(({ data }) => {
+        setRegistries(data);
+        if (!value) {
+          const currentRegistry = data.find((r) =>
+            currentImageUrl.includes(r.url)
+          );
+          onChange(currentRegistry);
+        }
+      });
+  }, []);
+
+  const registryOptions = registries.map((registry) => {
+    return {
+      value: registry.url,
+      label: registry.name,
+    };
+  });
+
+  const handleChange = (registryUrl: string) => {
+    const registry = registries.find(
+      (registry) => registry.url === registryUrl
+    );
+
+    onChange(registry);
+  };
+
+  return (
+    <SelectRow
+      value={value?.url}
+      options={registryOptions}
+      setActiveValue={handleChange}
+      width="100%"
+      label="Docker Registry"
+    />
+  );
+};
+
+type ImageRepo = {
+  name: string;
+  created_at: string;
+  uri: string;
+};
+
+const _ImageSelector = ({
+  registry,
+  value,
+  onChange,
+}: {
+  registry: DockerRegistry;
+  value: string;
+  onChange: (newValue: string) => void;
+}) => {
+  const { currentProject } = useContext(Context);
+
+  const [images, setImages] = useState<ImageRepo[]>([]);
+
+  useEffect(() => {
+    api
+      .getImageRepos<ImageRepo[]>(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          registry_id: registry.id,
+        }
+      )
+      .then(({ data }) => {
+        setImages(data);
+
+        if (!value) {
+          onChange(data[0].uri);
+        }
+      });
+  }, []);
+
+  const imageOptions = images.map((image) => {
+    return {
+      value: image.uri,
+      label: image.name,
+    };
+  });
+
+  const handleChange = (image: string) => {
+    onChange(image);
+  };
+
+  return (
+    <SelectRow
+      label="Image"
+      value={value}
+      options={imageOptions}
+      setActiveValue={handleChange}
+      width="100%"
+    />
+  );
+};
+
+type DockerImageTag = {
+  digest: string;
+  tag: string;
+  manifest: string;
+  repository_name: string;
+  pushed_at: string;
+};
+
+const _TagSelector = ({
+  registry,
+  imageName,
+  value,
+  onChange,
+}: {
+  registry: DockerRegistry;
+  imageName: string;
+  value: string;
+  onChange: (newTag: string) => void;
+}) => {
+  const { currentProject } = useContext(Context);
+  const [imageTags, setImageTags] = useState<string[]>([]);
+
+  useEffect(() => {
+    api
+      .getImageTags<DockerImageTag[]>(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          registry_id: registry?.id,
+          repo_name: imageName,
+        }
+      )
+      .then(({ data }) => {
+        const sortedTags = data.sort((a, b) => {
+          const aDate = new Date(a.pushed_at);
+          const bDate = new Date(b.pushed_at);
+          return bDate.getTime() - aDate.getTime();
+        });
+        setImageTags(sortedTags.map((tag) => tag.tag));
+
+        if (!value) {
+          onChange(sortedTags[0].tag);
+        }
+      });
+  }, [registry, imageName]);
+
+  const tagOptions = imageTags.map((tag) => {
+    return {
+      value: tag,
+      label: tag,
+    };
+  });
+
+  const handleChange = (tag: string) => {
+    onChange(tag);
+  };
+
+  return (
+    <SelectRow
+      label="Tag"
+      value={value}
+      options={tagOptions}
+      setActiveValue={handleChange}
+      width="100%"
+    />
+  );
+};
+
+export default SourceEditorDocker;