Jelajahi Sumber

Porter v2 Env Group Support (#3590)

Co-authored-by: Ian Edwards <ianedwards559@gmail.com>
sdess09 2 tahun lalu
induk
melakukan
bd92e9124a

+ 6 - 7
dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx

@@ -246,14 +246,14 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
             env_group_version: z.coerce.bigint(),
           })
           .parseAsync(envGroupResponse.data);
+
         const envGroups = [
-          ...app.envGroups,
+          ...app.envGroups.filter(group => group.name !== addedEnvGroup.env_group_name),
           {
             name: addedEnvGroup.env_group_name,
-            version: addedEnvGroup.env_group_version,
-          },
+            version: addedEnvGroup.env_group_version
+          }
         ];
-
         const appWithSeededEnv = new PorterApp({
           ...app,
           envGroups,
@@ -548,9 +548,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
                             }
                           >
                             {detectedServices.count > 0
-                              ? `Detected ${detectedServices.count} service${
-                                  detectedServices.count > 1 ? "s" : ""
-                                } from porter.yaml.`
+                              ? `Detected ${detectedServices.count} service${detectedServices.count > 1 ? "s" : ""
+                              } from porter.yaml.`
                               : `Could not detect any services from porter.yaml. Make sure it exists in the root of your repo.`}
                           </Text>
                         </AppearingDiv>

+ 177 - 12
dashboard/src/main/home/app-dashboard/validate-apply/app-settings/EnvVariables.tsx

@@ -1,29 +1,194 @@
-import React, { useCallback, useEffect } from "react";
+import React, { useCallback, useContext, useEffect, useState } from "react";
 import { Controller, useFormContext } from "react-hook-form";
 
 import { PorterAppFormData } from "lib/porter-apps";
 import EnvGroupArrayV2 from "main/home/cluster-dashboard/env-groups/EnvGroupArrayV2";
 import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArrayV2";
+import styled from "styled-components";
+import Spacer from "components/porter/Spacer";
+import EnvGroupModal from "../../expanded-app/env-vars/EnvGroupModal";
+import ExpandableEnvGroup from "../../expanded-app/env-vars/ExpandableEnvGroup";
+import { NewPopulatedEnvGroup } from "components/porter-form/types";
+import sliders from "assets/sliders.svg";
+import Text from "components/porter/Text";
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { z } from "zod";
+import { EnvGroup } from "@porter-dev/api-contracts";
+
+
 const EnvVariables: React.FC = () => {
   const { control } = useFormContext<PorterAppFormData>();
+  const [hovered, setHovered] = useState(false);
+  const [syncedEnvGroups, setSyncedEnvGroups] = useState<NewPopulatedEnvGroup[]>([]);
+  const [showEnvModal, setShowEnvModal] = useState(false);
+
+  const [deletedEnvGroups, setDeletedEnvGroups] = useState<NewPopulatedEnvGroup[]>([])
+
+  const maxEnvGroupsReached = syncedEnvGroups.length >= 4;
 
+  const convertSynced = (envGroups: NewPopulatedEnvGroup[]): {}[] => {
+    return envGroups?.map(group => (
+      new EnvGroup(
+        {
+          name: group.name,
+          version: BigInt(group.latest_version),
+        },
+      )
+    )
+    )
+  }
+
+  const removeSynced = (envGroups: [], envGroup: NewPopulatedEnvGroup): {}[] => {
+    return envGroups?.filter(group => (
+      group?.name !== envGroup.name
+    )
+    )
+  }
+  const deleteEnvGroup = (envGroup: NewPopulatedEnvGroup) => {
+
+    setDeletedEnvGroups([...deletedEnvGroups, envGroup]);
+    setSyncedEnvGroups(syncedEnvGroups?.filter(
+      (env) => env.name !== envGroup.name
+    ))
+  }
   return (
-    <Controller
+    <><Controller
       name={`app.env`}
       control={control}
       render={({ field: { value, onChange } }) => (
-        <EnvGroupArrayV2
-          values={value ? value : []}
-          setValues={(x: KeyValueType[]) => {
-            onChange(x);
-          }}
-          fileUpload={true}
-          syncedEnvGroups={[]}
-        />
-      )}
-    />
+        <>
+          <EnvGroupArrayV2
+            values={value ? value : []}
+            setValues={(x: KeyValueType[]) => {
+              onChange(x);
+            }}
+            fileUpload={true}
+            syncedEnvGroups={syncedEnvGroups} />
+        </>
+      )} />
+      <Controller
+        name={`app.envGroups`}
+        control={control}
+        render={({ field: { value, onChange } }) => (
+          <>
+            <TooltipWrapper
+              onMouseOver={() => setHovered(true)}
+              onMouseOut={() => setHovered(false)}>
+              <LoadButton
+                disabled={maxEnvGroupsReached}
+                onClick={() => !maxEnvGroupsReached && setShowEnvModal(true)}
+              >
+                <img src={sliders} /> Load from Env Group
+              </LoadButton>
+              <TooltipText visible={maxEnvGroupsReached && hovered}>Max 4 Env Groups allowed</TooltipText>
+            </TooltipWrapper>
+
+            {showEnvModal && <EnvGroupModal
+              setValues={(x: KeyValueType[]) => {
+                onChange(x);
+              }}
+              values={value}
+              closeModal={() => setShowEnvModal(false)}
+              syncedEnvGroups={syncedEnvGroups}
+              setSyncedEnvGroups={(x: NewPopulatedEnvGroup[]) => {
+                setSyncedEnvGroups(x);
+                onChange(convertSynced(x));
+              }}
+              namespace={"default"}
+              newApp={true} />}
+            {!!syncedEnvGroups?.length && (
+              <>
+                <Spacer y={0.5} />
+                <Text size={16}>Synced environment groups</Text>
+                {syncedEnvGroups?.map((envGroup: any) => {
+                  return (
+                    <ExpandableEnvGroup
+                      key={envGroup?.name}
+                      envGroup={envGroup}
+                      onDelete={() => {
+                        deleteEnvGroup(envGroup);
+                        onChange(removeSynced(value, envGroup));
+                      }} />
+                  );
+                })}
+              </>
+            )}
+          </>)} />
+
+    </>
+
   );
 };
 
 
 export default EnvVariables;
+
+const AddRowButton = styled.div`
+  display: flex;
+  align-items: center;
+  width: 270px;
+  font-size: 13px;
+  color: #aaaabb;
+  height: 32px;
+  border-radius: 3px;
+  cursor: pointer;
+  background: #ffffff11;
+  :hover {
+    background: #ffffff22;
+  }
+
+  > i {
+    color: #ffffff44;
+    font-size: 16px;
+    margin-left: 8px;
+    margin-right: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+`;
+const LoadButton = styled(AddRowButton) <{ disabled?: boolean }>`
+  background: ${(props) => (props.disabled ? "#aaaaaa55" : "none")};
+  border: 1px solid ${(props) => (props.disabled ? "#aaaaaa55" : "#ffffff55")};
+  cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
+
+  > i {
+    color: ${(props) => (props.disabled ? "#aaaaaa44" : "#ffffff44")};
+    font-size: 16px;
+    margin-left: 8px;
+    margin-right: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  > img {
+    width: 14px;
+    margin-left: 10px;
+    margin-right: 12px;
+    opacity: ${(props) => (props.disabled ? "0.5" : "1")};
+  }
+`;
+
+const TooltipWrapper = styled.div`
+  position: relative;
+  display: inline-block;
+`;
+
+const TooltipText = styled.span`
+  visibility: ${(props) => (props.visible ? 'visible' : 'hidden')};
+  width: 240px;
+  color: #fff;
+  text-align: center;
+  padding: 5px 0;
+  border-radius: 6px;
+  position: absolute;
+  z-index: 1;
+  bottom: 100%;
+  left: 50%;
+  margin-left: -120px;
+  opacity: ${(props) => (props.visible ? '1' : '0')};
+  transition: opacity 0.3s;
+  font-size: 12px;
+`;
+