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

Implemented update source configs

jnfrati 3 лет назад
Родитель
Сommit
bfad276389

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

@@ -174,7 +174,12 @@ const ExpandedStack = () => {
             value: "source_config",
             component: (
               <>
-                <SourceConfig revision={currentRevision}></SourceConfig>
+                <SourceConfig
+                  namespace={namespace}
+                  revision={currentRevision}
+                  readOnly={stack.latest_revision.id !== currentRevision.id}
+                  onSourceConfigUpdate={() => getStack()}
+                ></SourceConfig>
               </>
             ),
           },

+ 43 - 5
dashboard/src/main/home/cluster-dashboard/stacks/ExpandedStack/_SourceConfig.tsx

@@ -1,15 +1,31 @@
 import { Tooltip } from "@material-ui/core";
 import ImageSelector from "components/image-selector/ImageSelector";
 import SaveButton from "components/SaveButton";
-import React, { useMemo, useState } from "react";
+import React, { useContext, useMemo, useState } from "react";
+import api from "shared/api";
+import { Context } from "shared/Context";
 import styled from "styled-components";
-import { AppResource, FullStackRevision, SourceConfig } from "../types";
+import { AppResource, FullStackRevision, SourceConfig, Stack } from "../types";
 import SourceEditorDocker from "./components/SourceEditorDocker";
 
-const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
+const _SourceConfig = ({
+  namespace,
+  revision,
+  readOnly,
+  onSourceConfigUpdate,
+}: {
+  namespace: string;
+  revision: FullStackRevision;
+  readOnly: boolean;
+  onSourceConfigUpdate: () => void;
+}) => {
+  const { currentProject, currentCluster, setCurrentError } = useContext(
+    Context
+  );
   const [sourceConfigArrayCopy, setSourceConfigArrayCopy] = useState<
     SourceConfig[]
   >(() => revision.source_configs);
+  const [isSaving, setIsSaving] = useState(false);
 
   const handleChange = (sourceConfig: SourceConfig) => {
     const newSourceConfigArray = [...sourceConfigArrayCopy];
@@ -21,7 +37,27 @@ const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
   };
 
   const handleSave = () => {
-    console.log("save");
+    setIsSaving(true);
+    api
+      .updateStackSourceConfig(
+        "<token>",
+        {
+          source_configs: sourceConfigArrayCopy,
+        },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          namespace: namespace,
+          stack_id: revision.stack_id,
+        }
+      )
+      .then(() => {
+        setIsSaving(false);
+        onSourceConfigUpdate();
+      })
+      .catch((err) => {
+        setCurrentError(err);
+      });
   };
 
   return (
@@ -30,7 +66,6 @@ const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
         const apps = getAppsFromSourceConfig(revision.resources, sourceConfig);
 
         const appList = formatAppList(apps, 2);
-        console.log({ appList });
         return (
           <SourceConfigStyles.ItemContainer>
             {appList.hiddenApps?.length ? (
@@ -58,6 +93,7 @@ const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
             <SourceEditorDocker
               sourceConfig={sourceConfig}
               onChange={handleChange}
+              readOnly={readOnly || isSaving}
             />
           </SourceConfigStyles.ItemContainer>
         );
@@ -68,6 +104,8 @@ const _SourceConfig = ({ revision }: { revision: FullStackRevision }) => {
           text="Save"
           clearPosition={true}
           makeFlush={true}
+          status={isSaving ? "loading" : ""}
+          statusPosition="left"
         />
       </SourceConfigStyles.SaveButtonRow>
     </SourceConfigStyles.Wrapper>

+ 66 - 37
dashboard/src/main/home/cluster-dashboard/stacks/ExpandedStack/components/Select.tsx

@@ -58,7 +58,7 @@ const Select = <T extends unknown>({
 
   if (isLoading) {
     return (
-      <>
+      <div>
         {getLabel()}
         <SelectStyles.Wrapper>
           <SelectStyles.Selector
@@ -66,17 +66,17 @@ const Select = <T extends unknown>({
             expanded={false}
             readOnly={readOnly}
           >
-            <div>
+            <SelectStyles.Loading>
               <Loading />
-            </div>
+            </SelectStyles.Loading>
           </SelectStyles.Selector>
         </SelectStyles.Wrapper>
-      </>
+      </div>
     );
   }
 
   return (
-    <>
+    <div>
       {getLabel()}
       <SelectStyles.Wrapper ref={wrapperRef}>
         <SelectStyles.Selector
@@ -85,9 +85,9 @@ const Select = <T extends unknown>({
           expanded={expanded}
           readOnly={readOnly}
         >
-          <div>
+          <SelectStyles.CurrentValue>
             <span>{value ? accessor(value) : placeholder}</span>
-          </div>
+          </SelectStyles.CurrentValue>
           {readOnly ? null : <i className="material-icons">arrow_drop_down</i>}
         </SelectStyles.Selector>
         {expanded && !readOnly ? (
@@ -95,28 +95,38 @@ const Select = <T extends unknown>({
             width={dropdown?.width}
             maxH={dropdown?.maxH}
           >
-            <SelectStyles.Dropdown.Label>
-              {dropdown?.label}
-            </SelectStyles.Dropdown.Label>
-            {options.map((option, i) => (
-              <SelectStyles.Dropdown.Option
-                key={i}
-                onClick={() => !readOnly && handleOptionClick(option)}
-                lastItem={i === options.length - 1}
-                selected={
-                  isOptionEqualToValue
-                    ? isOptionEqualToValue(option, value)
-                    : option === value
-                }
-                height={dropdown?.option?.height}
-              >
-                {accessor(option)}
-              </SelectStyles.Dropdown.Option>
-            ))}
+            {dropdown?.label && (
+              <SelectStyles.Dropdown.Label>
+                {dropdown?.label}
+              </SelectStyles.Dropdown.Label>
+            )}
+            {options.length > 0 ? (
+              <>
+                {options.map((option, i) => (
+                  <SelectStyles.Dropdown.Option
+                    key={i}
+                    onClick={() => !readOnly && handleOptionClick(option)}
+                    lastItem={i === options.length - 1}
+                    selected={
+                      isOptionEqualToValue
+                        ? isOptionEqualToValue(option, value)
+                        : option === value
+                    }
+                    height={dropdown?.option?.height}
+                  >
+                    {accessor(option)}
+                  </SelectStyles.Dropdown.Option>
+                ))}
+              </>
+            ) : (
+              <SelectStyles.Dropdown.NoOptions>
+                No options available
+              </SelectStyles.Dropdown.NoOptions>
+            )}
           </SelectStyles.Dropdown.Wrapper>
         ) : null}
       </SelectStyles.Wrapper>
-    </>
+    </div>
   );
 };
 
@@ -129,6 +139,7 @@ export const SelectStyles = {
   Label: styled.div`
     color: #ffffff;
     margin-bottom: 10px;
+    margin-top: 20px;
     font-size: 13px;
   `,
 
@@ -171,18 +182,22 @@ export const SelectStyles = {
       font-size: 20px;
       transform: ${(props) => (props.expanded ? "rotate(180deg)" : "")};
     }
+  `,
 
-    > div {
-      display: flex;
-      align-items: center;
-      width: 85%;
+  Loading: styled.div`
+    width: 100%;
+  `,
 
-      > span {
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        z-index: 0;
-      }
+  CurrentValue: styled.div`
+    display: flex;
+    align-items: center;
+    width: 85%;
+
+    > span {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      z-index: 0;
     }
   `,
 
@@ -224,6 +239,20 @@ export const SelectStyles = {
         background: #ffffff22;
       }
     `,
-    Label: styled.div``,
+    Label: styled.div`
+      font-size: 13px;
+      color: #ffffff44;
+      font-weight: 500;
+      margin: 10px 13px;
+    `,
+    NoOptions: styled.div`
+      font-size: 13px;
+      color: #ffffff44;
+      font-weight: 500;
+      margin: 10px 13px;
+      :not(:first-child) {
+        border-top: 1px solid #ffffff15;
+      }
+    `,
   },
 };

+ 58 - 20
dashboard/src/main/home/cluster-dashboard/stacks/ExpandedStack/components/SourceEditorDocker.tsx

@@ -5,6 +5,7 @@ import React, { useContext, useEffect, useMemo, useState } from "react";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import { useOutsideAlerter } from "shared/hooks/useOutsideAlerter";
+import styled from "styled-components";
 import { proxy, useSnapshot } from "valtio";
 import { SourceConfig } from "../../types";
 import Select from "./Select";
@@ -48,28 +49,33 @@ const SourceEditorDocker = ({
 
   return (
     <>
-      <_DockerRepositorySelector
-        currentImageUrl={sourceConfig.image_repo_uri}
-        value={registry}
-        onChange={setRegistry}
-        readOnly={readOnly}
-      />
-      {registry && (
-        <_ImageSelector
-          registry={registry}
-          value={image}
-          onChange={setImage}
-          readOnly={readOnly}
-        />
-      )}
-      {registry && imageName && (
-        <_TagSelector
-          registry={registry}
-          imageName={imageName}
-          value={tag}
-          onChange={setTag}
+      <SourceEditorDockerStlyes.RegistryWrapper>
+        <_DockerRepositorySelector
+          currentImageUrl={sourceConfig.image_repo_uri}
+          value={registry}
+          onChange={setRegistry}
           readOnly={readOnly}
         />
+      </SourceEditorDockerStlyes.RegistryWrapper>
+      {registry && (
+        <SourceEditorDockerStlyes.ImageAndTagWrapper>
+          <_ImageSelector
+            registry={registry}
+            value={image}
+            onChange={setImage}
+            readOnly={readOnly}
+          />
+
+          {registry && imageName && (
+            <_TagSelector
+              registry={registry}
+              imageName={imageName}
+              value={tag}
+              onChange={setTag}
+              readOnly={readOnly}
+            />
+          )}
+        </SourceEditorDockerStlyes.ImageAndTagWrapper>
       )}
     </>
   );
@@ -138,6 +144,9 @@ const _DockerRepositorySelector = ({
         isOptionEqualToValue={(a, b) => a.url === b.url}
         readOnly={readOnly}
         isLoading={isLoading}
+        dropdown={{
+          maxH: "200px",
+        }}
       />
     </>
   );
@@ -209,6 +218,9 @@ const _ImageSelector = ({
       isOptionEqualToValue={(a, b) => a === b}
       readOnly={readOnly}
       isLoading={isLoading}
+      dropdown={{
+        maxH: "200px",
+      }}
     />
   );
 };
@@ -251,6 +263,13 @@ const _TagSelector = ({
         }
       )
       .then(({ data }) => {
+        if (!data?.length) {
+          setImageTags([]);
+          onChange("");
+          setIsLoading(false);
+          return;
+        }
+
         const sortedTags = data.sort((a, b) => {
           const aDate = new Date(a.pushed_at);
           const bDate = new Date(b.pushed_at);
@@ -258,6 +277,12 @@ const _TagSelector = ({
         });
         setImageTags(sortedTags.map((tag) => tag.tag));
 
+        if (sortedTags.map((tag) => tag.tag).includes(value)) {
+          onChange(value);
+        } else {
+          onChange(sortedTags[0].tag);
+        }
+
         setIsLoading(false);
       });
   }, [registry, imageName]);
@@ -276,8 +301,21 @@ const _TagSelector = ({
       onChange={handleChange}
       readOnly={readOnly}
       isLoading={isLoading}
+      dropdown={{
+        maxH: "200px",
+      }}
     />
   );
 };
 
 export default SourceEditorDocker;
+
+const SourceEditorDockerStlyes = {
+  RegistryWrapper: styled.div``,
+  ImageAndTagWrapper: styled.div`
+    display: grid;
+    grid-template-columns: 3fr 1fr;
+    grid-gap: 10px;
+    align-items: center;
+  `,
+};