From fcf3f2837e32169f613dbb478116b1fd230838a2 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Fri, 22 Sep 2023 21:50:06 +0530 Subject: [PATCH] feat(dashboard-v3): updated ui components and hooks for new migrated apis and v3 apis --- .../src/components/navigation/NavHeader.tsx | 52 ++- .../tags/CreateTagModal/CreateTagModal.tsx | 266 ++++++++++++++ .../components/tags/CreateTagModal/index.tsx | 1 + .../src/components/v2/Checkbox/Checkbox.tsx | 3 +- .../v2/ContentLoader/ContentLoader.tsx | 46 +++ .../src/components/v2/ContentLoader/index.tsx | 1 + .../src/components/v2/Dropdown/Dropdown.tsx | 70 +++- frontend/src/components/v2/Dropdown/index.tsx | 10 +- frontend/src/components/v2/Menu/Menu.tsx | 69 ++-- .../components/v2/SecretInput/SecretInput.tsx | 18 +- .../src/components/v2/Spinner/Spinner.tsx | 2 +- frontend/src/components/v2/Tag/Tag.tsx | 20 +- frontend/src/components/v2/index.tsx | 1 + frontend/src/helpers/project.ts | 176 +++++---- .../src/hooks/api/secretFolders/index.tsx | 1 - .../src/hooks/api/secretFolders/queries.tsx | 167 ++++----- frontend/src/hooks/api/secretFolders/types.ts | 29 +- .../src/hooks/api/secretImports/mutation.tsx | 26 +- .../src/hooks/api/secretImports/queries.tsx | 90 +++-- frontend/src/hooks/api/secretImports/types.ts | 18 +- .../src/hooks/api/secretSnapshots/index.tsx | 2 +- .../src/hooks/api/secretSnapshots/queries.tsx | 88 ++--- .../src/hooks/api/secretSnapshots/types.ts | 12 +- frontend/src/hooks/api/secrets/index.ts | 14 +- frontend/src/hooks/api/secrets/mutations.tsx | 234 ++++++++++-- frontend/src/hooks/api/secrets/queries.tsx | 335 ++++++------------ frontend/src/hooks/api/secrets/types.ts | 100 +++--- frontend/src/hooks/api/types.ts | 3 + frontend/tailwind.config.js | 2 +- 29 files changed, 1195 insertions(+), 661 deletions(-) create mode 100644 frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx create mode 100644 frontend/src/components/tags/CreateTagModal/index.tsx create mode 100644 frontend/src/components/v2/ContentLoader/ContentLoader.tsx create mode 100644 frontend/src/components/v2/ContentLoader/index.tsx diff --git a/frontend/src/components/navigation/NavHeader.tsx b/frontend/src/components/navigation/NavHeader.tsx index d4a5c4352b..f783cdb864 100644 --- a/frontend/src/components/navigation/NavHeader.tsx +++ b/frontend/src/components/navigation/NavHeader.tsx @@ -1,4 +1,3 @@ -import { useMemo } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import { faAngleRight } from "@fortawesome/free-solid-svg-icons"; @@ -15,7 +14,7 @@ type Props = { currentEnv?: string; userAvailableEnvs?: any[]; onEnvChange?: (slug: string) => void; - folders?: Array<{ id: string; name: string }>; + secretPath?: string; isFolderMode?: boolean; }; @@ -42,19 +41,14 @@ export default function NavHeader({ currentEnv, userAvailableEnvs = [], onEnvChange, - folders = [], - isFolderMode + isFolderMode, + secretPath = "/" }: Props): JSX.Element { const { currentWorkspace } = useWorkspace(); const { currentOrg } = useOrganization(); const router = useRouter(); - const isInRootFolder = isFolderMode && folders.length <= 1; - - const selectedEnv = useMemo( - () => userAvailableEnvs?.find((uae) => uae.name === currentEnv), - [userAvailableEnvs, currentEnv] - ); + const secretPathSegments = secretPath.split("/").filter(Boolean); return (
@@ -90,13 +84,13 @@ export default function NavHeader({ ) : (
{pageName}
)} - {currentEnv && isInRootFolder && ( + {currentEnv && secretPath === "/" && ( <>
+ + )} + /> +
+
+ Tag Color +
+
+
+
+
+
+ {!showHexInput ? ( +
+ {secretTagsColors.map(($tagColor: TagColor) => { + return ( +
+ +
setValue("color", $tagColor.hex)} + tabIndex={0} + role="button" + onKeyDown={() => {}} + > + {$tagColor.hex === selectedTagColor && ( + + )} +
+
+
+ ); + })} +
+ ) : ( +
+
+ {isValidHexColor(selectedTagColor) && ( +
+ +
+ )} + {!isValidHexColor(selectedTagColor) && ( +
+ )} +
+
+ ) => + setValue("color", e.target.value) + } + /> +
+
+ )} +
+
+
setShowHexInput((prev) => !prev)} + style={{ border: "1px solid rgba(220, 216, 254, 0.376)" }} + tabIndex={0} + role="button" + onKeyDown={() => {}} + > + {!showHexInput && #} +
+
+
+
+
+ +
+ + + + +
+ + + + ); +}; diff --git a/frontend/src/components/tags/CreateTagModal/index.tsx b/frontend/src/components/tags/CreateTagModal/index.tsx new file mode 100644 index 0000000000..e9d283b3c2 --- /dev/null +++ b/frontend/src/components/tags/CreateTagModal/index.tsx @@ -0,0 +1 @@ +export { CreateTagModal } from "./CreateTagModal"; diff --git a/frontend/src/components/v2/Checkbox/Checkbox.tsx b/frontend/src/components/v2/Checkbox/Checkbox.tsx index 8c79a5ff22..87e2781190 100644 --- a/frontend/src/components/v2/Checkbox/Checkbox.tsx +++ b/frontend/src/components/v2/Checkbox/Checkbox.tsx @@ -30,9 +30,10 @@ export const Checkbox = ({
{ + const [pos, setPos] = useState(0); + const isTextArray = Array.isArray(text); + useEffect(() => { + let interval: NodeJS.Timer; + if (isTextArray) { + interval = setInterval(() => { + setPos((state) => (state + 1) % text.length); + }, frequency); + } + return () => clearInterval(interval); + }, []); + + return ( +
+
+ loading animation +
+ {text && isTextArray && ( + + + {text[pos]} + + + )} + {text && !isTextArray &&
{text}
} +
+ ); +}; diff --git a/frontend/src/components/v2/ContentLoader/index.tsx b/frontend/src/components/v2/ContentLoader/index.tsx new file mode 100644 index 0000000000..78b373523c --- /dev/null +++ b/frontend/src/components/v2/ContentLoader/index.tsx @@ -0,0 +1 @@ +export { ContentLoader } from "./ContentLoader"; diff --git a/frontend/src/components/v2/Dropdown/Dropdown.tsx b/frontend/src/components/v2/Dropdown/Dropdown.tsx index 8d2427c1c4..057a7b4a61 100644 --- a/frontend/src/components/v2/Dropdown/Dropdown.tsx +++ b/frontend/src/components/v2/Dropdown/Dropdown.tsx @@ -6,6 +6,9 @@ import { twMerge } from "tailwind-merge"; export type DropdownMenuProps = DropdownMenuPrimitive.DropdownMenuProps; export const DropdownMenu = DropdownMenuPrimitive.Root; +export type DropdownSubMenuProps = DropdownMenuPrimitive.DropdownMenuSubProps; +export const DropdownSubMenu = DropdownMenuPrimitive.Sub; + // trigger export type DropdownMenuTriggerProps = DropdownMenuPrimitive.DropdownMenuTriggerProps; export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; @@ -34,6 +37,30 @@ export const DropdownMenuContent = forwardRef( + ({ children, className, ...props }, forwardedRef) => { + return ( + + + {children} + + + ); + } +); + +DropdownSubMenuContent.displayName = "DropdownMenuContent"; + // item label component export type DropdownLabelProps = DropdownMenuPrimitive.MenuLabelProps; export const DropdownMenuLabel = ({ className, ...props }: DropdownLabelProps) => ( @@ -76,11 +103,50 @@ export const DropdownMenuItem = ({ ); +// trigger +export type DropdownSubMenuTriggerProps = + DropdownMenuPrimitive.DropdownMenuSubTriggerProps & { + icon?: ReactNode; + as?: T; + inputRef?: Ref; + iconPos?: "left" | "right"; + }; + +export const DropdownSubMenuTrigger = ({ + children, + inputRef, + className, + icon, + as: Item = "button", + iconPos = "left", + ...props +}: DropdownMenuItemProps & ComponentPropsWithRef) => ( + + + {icon && iconPos === "left" && {icon}} + {children} + {icon && iconPos === "right" && {icon}} + + +); + // grouping items into 1 export type DropdownMenuGroupProps = DropdownMenuPrimitive.DropdownMenuGroupProps; export const DropdownMenuGroup = forwardRef( - ({ ...props }, ref) => + ({ ...props }, ref) => ( + + ) ); DropdownMenuGroup.displayName = "DropdownMenuGroup"; @@ -98,3 +164,5 @@ export const DropdownMenuSeparator = forwardRef< )); DropdownMenuSeparator.displayName = "DropdownMenuSeperator"; + +DropdownMenuSeparator.displayName = "DropdownMenuSeperator"; diff --git a/frontend/src/components/v2/Dropdown/index.tsx b/frontend/src/components/v2/Dropdown/index.tsx index 8ecbebd094..bde01449e8 100644 --- a/frontend/src/components/v2/Dropdown/index.tsx +++ b/frontend/src/components/v2/Dropdown/index.tsx @@ -4,7 +4,10 @@ export type { DropdownMenuGroupProps, DropdownMenuItemProps, DropdownMenuProps, - DropdownMenuTriggerProps + DropdownMenuTriggerProps, + DropdownSubMenuContentProps, + DropdownSubMenuProps, + DropdownSubMenuTriggerProps } from "./Dropdown"; export { DropdownMenu, @@ -13,5 +16,8 @@ export { DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, - DropdownMenuTrigger + DropdownMenuTrigger, + DropdownSubMenu, + DropdownSubMenuContent, + DropdownSubMenuTrigger } from "./Dropdown"; diff --git a/frontend/src/components/v2/Menu/Menu.tsx b/frontend/src/components/v2/Menu/Menu.tsx index d56d16fecf..e4771e5515 100644 --- a/frontend/src/components/v2/Menu/Menu.tsx +++ b/frontend/src/components/v2/Menu/Menu.tsx @@ -2,8 +2,8 @@ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable global-require */ import { ComponentPropsWithRef, ElementType, ReactNode, Ref, useRef } from "react"; -import { motion } from "framer-motion" -import Lottie from "lottie-react" +import { motion } from "framer-motion"; +import Lottie from "lottie-react"; import { twMerge } from "tailwind-merge"; export type MenuProps = { @@ -39,13 +39,10 @@ export const MenuItem = ({ inputRef, ...props }: MenuItemProps & ComponentPropsWithRef): JSX.Element => { - const iconRef = useRef() + const iconRef = useRef(); - return( - iconRef.current?.play()} - onMouseLeave={() => iconRef.current?.stop()} - > + return ( + iconRef.current?.play()} onMouseLeave={() => iconRef.current?.stop()}>
  • ({ )} > - -
    - {/* {icon && {icon}} */} - +
    + {/* {icon && {icon}} */} + {icon && ( + + )} {children} {description && {description}}
  • - ) + ); }; export const SubMenuItem = ({ @@ -88,13 +97,10 @@ export const SubMenuItem = ({ inputRef, ...props }: MenuItemProps & ComponentPropsWithRef): JSX.Element => { - const iconRef = useRef() + const iconRef = useRef(); - return( - iconRef.current?.play()} - onMouseLeave={() => iconRef.current?.stop()} - > + return ( + iconRef.current?.play()} onMouseLeave={() => iconRef.current?.stop()}>
  • ({ )} > - + ({
  • - ) + ); }; - MenuItem.displayName = "MenuItem"; export type MenuGroupProps = { diff --git a/frontend/src/components/v2/SecretInput/SecretInput.tsx b/frontend/src/components/v2/SecretInput/SecretInput.tsx index d7476ec576..c09061c4f8 100644 --- a/frontend/src/components/v2/SecretInput/SecretInput.tsx +++ b/frontend/src/components/v2/SecretInput/SecretInput.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-danger */ -import { forwardRef, HTMLAttributes } from "react"; +import { forwardRef, TextareaHTMLAttributes } from "react"; import sanitizeHtml, { DisallowedTagsModes } from "sanitize-html"; +import { twMerge } from "tailwind-merge"; import { useToggle } from "@app/hooks"; @@ -39,20 +40,28 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => { return `${newContent}
    `; }; -type Props = HTMLAttributes & { +type Props = TextareaHTMLAttributes & { value?: string | null; isVisible?: boolean; + isReadOnly?: boolean; isDisabled?: boolean; + containerClassName?: string; }; const commonClassName = "font-mono text-sm caret-white border-none outline-none w-full break-all"; export const SecretInput = forwardRef( - ({ value, isVisible, onBlur, isDisabled, onFocus, ...props }, ref) => { + ( + { value, isVisible, containerClassName, onBlur, isDisabled, isReadOnly, onFocus, ...props }, + ref + ) => { const [isSecretFocused, setIsSecretFocused] = useToggle(); return ( -
    +
                 
    @@ -78,6 +87,7 @@ export const SecretInput = forwardRef(
                 }}
                 value={value || ""}
                 {...props}
    +            readOnly={isReadOnly}
               />
             
    diff --git a/frontend/src/components/v2/Spinner/Spinner.tsx b/frontend/src/components/v2/Spinner/Spinner.tsx index ce877cff29..4f0cb15aa9 100644 --- a/frontend/src/components/v2/Spinner/Spinner.tsx +++ b/frontend/src/components/v2/Spinner/Spinner.tsx @@ -20,7 +20,7 @@ export const Spinner = ({ className, size = "md" }: Props): JSX.Element => {
    +export const Tag = ({ children, className, colorSchema = "gray", size = "sm", onClose }: Props) => ( +
    {children} + {onClose && ( + + )}
    ); diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index eb49f0d88f..89030931de 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -2,6 +2,7 @@ export * from "./Accordion"; export * from "./Button"; export * from "./Card"; export * from "./Checkbox"; +export * from "./ContentLoader"; export * from "./DatePicker"; export * from "./DeleteActionModal"; export * from "./Drawer"; diff --git a/frontend/src/helpers/project.ts b/frontend/src/helpers/project.ts index 6a1efc135b..13352c586f 100644 --- a/frontend/src/helpers/project.ts +++ b/frontend/src/helpers/project.ts @@ -3,77 +3,75 @@ import crypto from "crypto"; import { encryptAssymmetric } from "@app/components/utilities/cryptography/crypto"; import encryptSecrets from "@app/components/utilities/secrets/encryptSecrets"; import { uploadWsKey } from "@app/hooks/api/keys/queries"; -import { createSecret } from "@app/hooks/api/secrets/queries"; +import { createSecret } from "@app/hooks/api/secrets/mutations"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; import { createWorkspace } from "@app/hooks/api/workspace/queries"; const secretsToBeAdded = [ - { - pos: 0, - key: "DATABASE_URL", - // eslint-disable-next-line no-template-curly-in-string - value: "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net", - valueOverride: undefined, - comment: "Secret referencing example", - id: "", - tags: [] - }, - { - pos: 1, - key: "DB_USERNAME", - value: "OVERRIDE_THIS", - valueOverride: undefined, - comment: - "Override secrets with personal value", - id: "", - tags: [] - }, - { - pos: 2, - key: "DB_PASSWORD", - value: "OVERRIDE_THIS", - valueOverride: undefined, - comment: - "Another secret override", - id: "", - tags: [] - }, - { - pos: 3, - key: "DB_USERNAME", - value: "user1234", - valueOverride: "user1234", - comment: "", - id: "", - tags: [] - }, - { - pos: 4, - key: "DB_PASSWORD", - value: "example_password", - valueOverride: "example_password", - comment: "", - id: "", - tags: [] - }, - { - pos: 5, - key: "TWILIO_AUTH_TOKEN", - value: "example_twillio_token", - valueOverride: undefined, - comment: "", - id: "", - tags: [] - }, - { - pos: 6, - key: "WEBSITE_URL", - value: "http://localhost:3000", - valueOverride: undefined, - comment: "", - id: "", - tags: [] - } + { + pos: 0, + key: "DATABASE_URL", + // eslint-disable-next-line no-template-curly-in-string + value: "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net", + valueOverride: undefined, + comment: "Secret referencing example", + id: "", + tags: [] + }, + { + pos: 1, + key: "DB_USERNAME", + value: "OVERRIDE_THIS", + valueOverride: undefined, + comment: "Override secrets with personal value", + id: "", + tags: [] + }, + { + pos: 2, + key: "DB_PASSWORD", + value: "OVERRIDE_THIS", + valueOverride: undefined, + comment: "Another secret override", + id: "", + tags: [] + }, + { + pos: 3, + key: "DB_USERNAME", + value: "user1234", + valueOverride: "user1234", + comment: "", + id: "", + tags: [] + }, + { + pos: 4, + key: "DB_PASSWORD", + value: "example_password", + valueOverride: "example_password", + comment: "", + id: "", + tags: [] + }, + { + pos: 5, + key: "TWILIO_AUTH_TOKEN", + value: "example_twillio_token", + valueOverride: undefined, + comment: "", + id: "", + tags: [] + }, + { + pos: 6, + key: "WEBSITE_URL", + value: "http://localhost:3000", + valueOverride: undefined, + comment: "", + id: "", + tags: [] + } ]; /** @@ -85,30 +83,32 @@ const secretsToBeAdded = [ * @returns {Project} project - new project */ const initProjectHelper = async ({ - organizationId, - projectName + organizationId, + projectName }: { - organizationId: string; - projectName: string; + organizationId: string; + projectName: string; }) => { // create new project - const { data: { workspace } } = await createWorkspace({ - workspaceName: projectName, - organizationId + const { + data: { workspace } + } = await createWorkspace({ + workspaceName: projectName, + organizationId }); - + // create and upload new (encrypted) project key const randomBytes = crypto.randomBytes(16).toString("hex"); const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - + if (!PRIVATE_KEY) throw new Error("Failed to find private key"); const user = await fetchUserDetails(); const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: randomBytes, - publicKey: user.publicKey, - privateKey: PRIVATE_KEY + plaintext: randomBytes, + publicKey: user.publicKey, + privateKey: PRIVATE_KEY }); await uploadWsKey({ @@ -120,11 +120,11 @@ const initProjectHelper = async ({ // encrypt and upload secrets to new project const secrets = await encryptSecrets({ - secretsToEncrypt: secretsToBeAdded, - workspaceId: workspace._id, - env: "dev" + secretsToEncrypt: secretsToBeAdded, + workspaceId: workspace._id, + env: "dev" }); - + secrets?.forEach((secret) => { createSecret({ workspaceId: workspace._id, @@ -146,10 +146,8 @@ const initProjectHelper = async ({ } }); }); - - return workspace; -} -export { - initProjectHelper -} \ No newline at end of file + return workspace; +}; + +export { initProjectHelper }; diff --git a/frontend/src/hooks/api/secretFolders/index.tsx b/frontend/src/hooks/api/secretFolders/index.tsx index 3c0692eb66..0676f5d149 100644 --- a/frontend/src/hooks/api/secretFolders/index.tsx +++ b/frontend/src/hooks/api/secretFolders/index.tsx @@ -3,6 +3,5 @@ export { useDeleteFolder, useGetFoldersByEnv, useGetProjectFolders, - useGetProjectFoldersBatch, useUpdateFolder } from "./queries"; diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx index 36d013b72d..e562e01050 100644 --- a/frontend/src/hooks/api/secretFolders/queries.tsx +++ b/frontend/src/hooks/api/secretFolders/queries.tsx @@ -1,86 +1,80 @@ import { useCallback, useMemo } from "react"; -import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + useMutation, + useQueries, + useQuery, + useQueryClient, + UseQueryOptions +} from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { secretSnapshotKeys } from "../secretSnapshots/queries"; import { - CreateFolderDTO, - DeleteFolderDTO, - GetProjectFoldersBatchDTO, - GetProjectFoldersDTO, + TCreateFolderDTO, + TDeleteFolderDTO, TGetFoldersByEnvDTO, + TGetProjectFoldersDTO, TSecretFolder, - UpdateFolderDTO + TUpdateFolderDTO } from "./types"; const queryKeys = { - getSecretFolders: (workspaceId: string, environment: string, parentFolderId?: string) => - ["secret-folders", { workspaceId, environment, parentFolderId }] as const + getSecretFolders: ({ workspaceId, environment, directory }: TGetProjectFoldersDTO) => + ["secret-folders", { workspaceId, environment, directory }] as const }; -const fetchProjectFolders = async ( - workspaceId: string, - environment: string, - parentFolderId?: string, - parentFolderPath?: string -) => { - const { data } = await apiRequest.get<{ folders: TSecretFolder[]; dir: TSecretFolder[] }>( - "/api/v1/folders", - { - params: { - workspaceId, - environment, - parentFolderId, - parentFolderPath - } +const fetchProjectFolders = async (workspaceId: string, environment: string, directory = "/") => { + const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", { + params: { + workspaceId, + environment, + directory } - ); - return data; + }); + return data.folders; }; export const useGetProjectFolders = ({ workspaceId, - parentFolderId, environment, - isPaused, - sortDir -}: GetProjectFoldersDTO) => + directory = "/", + options = {} +}: TGetProjectFoldersDTO & { + options?: Omit< + UseQueryOptions< + TSecretFolder[], + unknown, + TSecretFolder[], + ReturnType + >, + "queryKey" | "queryFn" + >; +}) => useQuery({ - queryKey: queryKeys.getSecretFolders(workspaceId, environment, parentFolderId), - enabled: Boolean(workspaceId) && Boolean(environment) && !isPaused, - queryFn: async () => fetchProjectFolders(workspaceId, environment, parentFolderId), - select: useCallback( - ({ folders, dir }: { folders: TSecretFolder[]; dir: TSecretFolder[] }) => ({ - dir, - folders: folders.sort((a, b) => - sortDir === "asc" - ? a?.name?.localeCompare(b?.name || "") - : b?.name?.localeCompare(a?.name || "") - ) - }), - [sortDir] - ) + ...options, + queryKey: queryKeys.getSecretFolders({ workspaceId, environment, directory }), + enabled: Boolean(workspaceId) && Boolean(environment) && (options?.enabled ?? true), + queryFn: async () => fetchProjectFolders(workspaceId, environment, directory) }); export const useGetFoldersByEnv = ({ - parentFolderPath, + directory = "/", workspaceId, - environments, - parentFolderId + environments }: TGetFoldersByEnvDTO) => { const folders = useQueries({ - queries: environments.map((env) => ({ - queryKey: queryKeys.getSecretFolders(workspaceId, env, parentFolderPath || parentFolderId), - queryFn: async () => fetchProjectFolders(workspaceId, env, parentFolderId, parentFolderPath), - enabled: Boolean(workspaceId) && Boolean(env) + queries: environments.map((environment) => ({ + queryKey: queryKeys.getSecretFolders({ workspaceId, environment, directory }), + queryFn: async () => fetchProjectFolders(workspaceId, environment, directory), + enabled: Boolean(workspaceId) && Boolean(environment) })) }); const folderNames = useMemo(() => { const names = new Set(); folders?.forEach(({ data }) => { - data?.folders.forEach(({ name }) => { + data?.forEach(({ name }) => { names.add(name); }); }); @@ -92,9 +86,7 @@ export const useGetFoldersByEnv = ({ const selectedEnvIndex = environments.indexOf(env); if (selectedEnvIndex !== -1) { return Boolean( - folders?.[selectedEnvIndex]?.data?.folders?.find( - ({ name: folderName }) => folderName === name - ) + folders?.[selectedEnvIndex]?.data?.find(({ name: folderName }) => folderName === name) ); } return false; @@ -105,95 +97,78 @@ export const useGetFoldersByEnv = ({ return { folders, folderNames, isFolderPresentInEnv }; }; -export const useGetProjectFoldersBatch = ({ - folders = [], - isPaused, - parentFolderPath -}: GetProjectFoldersBatchDTO) => - useQueries({ - queries: folders.map(({ workspaceId, environment, parentFolderId }) => ({ - queryKey: queryKeys.getSecretFolders(workspaceId, environment, parentFolderPath), - queryFn: async () => - fetchProjectFolders(workspaceId, environment, parentFolderId, parentFolderPath), - enabled: Boolean(workspaceId) && Boolean(environment) && !isPaused, - select: (data: { folders: TSecretFolder[]; dir: TSecretFolder[] }) => ({ - environment, - folders: data.folders, - dir: data.dir - }) - })) - }); - export const useCreateFolder = () => { const queryClient = useQueryClient(); - return useMutation<{}, {}, CreateFolderDTO>({ + return useMutation<{}, {}, TCreateFolderDTO>({ mutationFn: async (dto) => { const { data } = await apiRequest.post("/api/v1/folders", dto); return data; }, - onSuccess: (_, { workspaceId, environment, parentFolderId }) => { + onSuccess: (_, { workspaceId, environment, directory }) => { queryClient.invalidateQueries( - queryKeys.getSecretFolders(workspaceId, environment, parentFolderId) + queryKeys.getSecretFolders({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretSnapshotKeys.count(workspaceId, environment, parentFolderId) + secretSnapshotKeys.list({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretSnapshotKeys.list(workspaceId, environment, parentFolderId) + secretSnapshotKeys.count({ workspaceId, environment, directory }) ); } }); }; -export const useUpdateFolder = (parentFolderId: string) => { +export const useUpdateFolder = () => { const queryClient = useQueryClient(); - return useMutation<{}, {}, UpdateFolderDTO>({ - mutationFn: async ({ folderId, name, environment, workspaceId }) => { - const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, { + return useMutation<{}, {}, TUpdateFolderDTO>({ + mutationFn: async ({ directory = "/", folderName, name, environment, workspaceId }) => { + const { data } = await apiRequest.patch(`/api/v1/folders/${folderName}`, { name, environment, - workspaceId + workspaceId, + directory }); return data; }, - onSuccess: (_, { workspaceId, environment }) => { + onSuccess: (_, { workspaceId, environment, directory }) => { queryClient.invalidateQueries( - queryKeys.getSecretFolders(workspaceId, environment, parentFolderId) + queryKeys.getSecretFolders({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretSnapshotKeys.count(workspaceId, environment, parentFolderId) + secretSnapshotKeys.list({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretSnapshotKeys.list(workspaceId, environment, parentFolderId) + secretSnapshotKeys.count({ workspaceId, environment, directory }) ); } }); }; -export const useDeleteFolder = (parentFolderId: string) => { +export const useDeleteFolder = () => { const queryClient = useQueryClient(); - return useMutation<{}, {}, DeleteFolderDTO>({ - mutationFn: async ({ folderId, environment, workspaceId }) => { - const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, { + return useMutation<{}, {}, TDeleteFolderDTO>({ + mutationFn: async ({ directory = "/", folderName, environment, workspaceId }) => { + const { data } = await apiRequest.delete(`/api/v1/folders/${folderName}`, { data: { environment, - workspaceId + workspaceId, + directory } }); return data; }, - onSuccess: (_, { workspaceId, environment }) => { + onSuccess: (_, { directory = "/", workspaceId, environment }) => { queryClient.invalidateQueries( - queryKeys.getSecretFolders(workspaceId, environment, parentFolderId) + queryKeys.getSecretFolders({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretSnapshotKeys.count(workspaceId, environment, parentFolderId) + secretSnapshotKeys.list({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretSnapshotKeys.list(workspaceId, environment, parentFolderId) + secretSnapshotKeys.count({ workspaceId, environment, directory }) ); } }); diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts index c9d7076209..c051173e9a 100644 --- a/frontend/src/hooks/api/secretFolders/types.ts +++ b/frontend/src/hooks/api/secretFolders/types.ts @@ -3,43 +3,36 @@ export type TSecretFolder = { name: string; }; -export type GetProjectFoldersDTO = { +export type TGetProjectFoldersDTO = { workspaceId: string; environment: string; - parentFolderId?: string; - isPaused?: boolean; - sortDir?: "asc" | "desc"; -}; - -export type GetProjectFoldersBatchDTO = { - folders: Omit[]; - isPaused?: boolean; - parentFolderPath?: string; + directory?: string; }; export type TGetFoldersByEnvDTO = { environments: string[]; workspaceId: string; - parentFolderPath?: string; - parentFolderId?: string; + directory?: string; }; -export type CreateFolderDTO = { +export type TCreateFolderDTO = { workspaceId: string; environment: string; folderName: string; - parentFolderId?: string; + directory?: string; }; -export type UpdateFolderDTO = { +export type TUpdateFolderDTO = { workspaceId: string; environment: string; name: string; - folderId: string; + folderName: string; + directory?: string; }; -export type DeleteFolderDTO = { +export type TDeleteFolderDTO = { workspaceId: string; environment: string; - folderId: string; + folderName: string; + directory?: string; }; diff --git a/frontend/src/hooks/api/secretImports/mutation.tsx b/frontend/src/hooks/api/secretImports/mutation.tsx index bb01528ea3..184c8dd63e 100644 --- a/frontend/src/hooks/api/secretImports/mutation.tsx +++ b/frontend/src/hooks/api/secretImports/mutation.tsx @@ -9,21 +9,21 @@ export const useCreateSecretImport = () => { const queryClient = useQueryClient(); return useMutation<{}, {}, TCreateSecretImportDTO>({ - mutationFn: async ({ secretImport, environment, workspaceId, folderId }) => { + mutationFn: async ({ secretImport, environment, workspaceId, directory }) => { const { data } = await apiRequest.post("/api/v1/secret-imports", { secretImport, environment, workspaceId, - folderId + directory }); return data; }, - onSuccess: (_, { workspaceId, environment, folderId }) => { + onSuccess: (_, { workspaceId, environment, directory }) => { queryClient.invalidateQueries( - secretImportKeys.getProjectSecretImports(workspaceId, environment, folderId) + secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId) + secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory }) ); } }); @@ -33,21 +33,21 @@ export const useUpdateSecretImport = () => { const queryClient = useQueryClient(); return useMutation<{}, {}, TUpdateSecretImportDTO>({ - mutationFn: async ({ environment, workspaceId, folderId, secretImports, id }) => { + mutationFn: async ({ environment, workspaceId, directory, secretImports, id }) => { const { data } = await apiRequest.put(`/api/v1/secret-imports/${id}`, { secretImports, environment, workspaceId, - folderId + directory }); return data; }, - onSuccess: (_, { workspaceId, environment, folderId }) => { + onSuccess: (_, { workspaceId, environment, directory }) => { queryClient.invalidateQueries( - secretImportKeys.getProjectSecretImports(workspaceId, environment, folderId) + secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId) + secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory }) ); } }); @@ -66,12 +66,12 @@ export const useDeleteSecretImport = () => { }); return data; }, - onSuccess: (_, { workspaceId, environment, folderId }) => { + onSuccess: (_, { workspaceId, environment, directory }) => { queryClient.invalidateQueries( - secretImportKeys.getProjectSecretImports(workspaceId, environment, folderId) + secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory }) ); queryClient.invalidateQueries( - secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId) + secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory }) ); } }); diff --git a/frontend/src/hooks/api/secretImports/queries.tsx b/frontend/src/hooks/api/secretImports/queries.tsx index 7bb07488cf..75c1820cc5 100644 --- a/frontend/src/hooks/api/secretImports/queries.tsx +++ b/frontend/src/hooks/api/secretImports/queries.tsx @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { decryptAssymmetric, @@ -7,52 +7,68 @@ import { } from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; -import { TGetImportedSecrets, TImportedSecrets, TSecretImports } from "./types"; +import { TGetImportedSecrets, TGetSecretImports, TImportedSecrets, TSecretImports } from "./types"; export const secretImportKeys = { - getProjectSecretImports: (workspaceId: string, env: string | string[], folderId?: string) => [ - { workspaceId, env, folderId }, - "secrets-imports" - ], - getSecretImportSecrets: (workspaceId: string, env: string | string[], folderId?: string) => [ - { workspaceId, env, folderId }, - "secrets-import-sec" - ] + getProjectSecretImports: ({ environment, workspaceId, directory }: TGetSecretImports) => + [{ workspaceId, directory, environment }, "secrets-imports"] as const, + getSecretImportSecrets: ({ + workspaceId, + environment, + directory + }: Omit) => + [{ workspaceId, environment, directory }, "secrets-import-sec"] as const }; -const fetchSecretImport = async (workspaceId: string, environment: string, folderId?: string) => { +const fetchSecretImport = async ({ workspaceId, environment, directory }: TGetSecretImports) => { const { data } = await apiRequest.get<{ secretImport: TSecretImports }>( "/api/v1/secret-imports", { params: { workspaceId, environment, - folderId + directory } } ); return data.secretImport; }; -export const useGetSecretImports = (workspaceId: string, env: string, folderId?: string) => +export const useGetSecretImports = ({ + workspaceId, + environment, + directory = "/", + options = {} +}: TGetSecretImports & { + options?: Omit< + UseQueryOptions< + TSecretImports, + unknown, + TSecretImports, + ReturnType + >, + "queryKey" | "queryFn" + >; +}) => useQuery({ - enabled: Boolean(workspaceId) && Boolean(env), - queryKey: secretImportKeys.getProjectSecretImports(workspaceId, env, folderId), - queryFn: () => fetchSecretImport(workspaceId, env, folderId) + ...options, + queryKey: secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory }), + enabled: Boolean(workspaceId) && Boolean(environment) && (options?.enabled ?? true), + queryFn: () => fetchSecretImport({ workspaceId, environment, directory }) }); const fetchImportedSecrets = async ( workspaceId: string, environment: string, - folderId?: string + directory?: string ) => { - const { data } = await apiRequest.get<{ secrets: TImportedSecrets }>( + const { data } = await apiRequest.get<{ secrets: TImportedSecrets[] }>( "/api/v1/secret-imports/secrets", { params: { workspaceId, environment, - folderId + directory } } ); @@ -62,15 +78,34 @@ const fetchImportedSecrets = async ( export const useGetImportedSecrets = ({ workspaceId, environment, - folderId, - decryptFileKey -}: TGetImportedSecrets) => + decryptFileKey, + directory, + options = {} +}: TGetImportedSecrets & { + options?: Omit< + UseQueryOptions< + TImportedSecrets[], + unknown, + TImportedSecrets[], + ReturnType + >, + "queryKey" | "queryFn" + >; +}) => useQuery({ - enabled: Boolean(workspaceId) && Boolean(environment) && Boolean(decryptFileKey), - queryKey: secretImportKeys.getSecretImportSecrets(workspaceId, environment, folderId), - queryFn: () => fetchImportedSecrets(workspaceId, environment, folderId), + enabled: + Boolean(workspaceId) && + Boolean(environment) && + Boolean(decryptFileKey) && + (options?.enabled ?? true), + queryKey: secretImportKeys.getSecretImportSecrets({ + workspaceId, + environment, + directory + }), + queryFn: () => fetchImportedSecrets(workspaceId, environment, directory), select: useCallback( - (data: TImportedSecrets) => { + (data: TImportedSecrets[]) => { const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; const latestKey = decryptFileKey; const key = decryptAssymmetric({ @@ -114,7 +149,8 @@ export const useGetImportedSecrets = ({ tags: encSecret.tags, comment: secretComment, createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt + updatedAt: encSecret.updatedAt, + version: encSecret.version }; }) })); diff --git a/frontend/src/hooks/api/secretImports/types.ts b/frontend/src/hooks/api/secretImports/types.ts index 50884225f8..019401092f 100644 --- a/frontend/src/hooks/api/secretImports/types.ts +++ b/frontend/src/hooks/api/secretImports/types.ts @@ -1,5 +1,5 @@ +import { UserWsKeyPair } from "../keys/types"; import { EncryptedSecret } from "../secrets/types"; -import { UserWsKeyPair } from "../types"; export type TSecretImports = { _id: string; @@ -16,19 +16,25 @@ export type TImportedSecrets = { secretPath: string; folderId: string; secrets: EncryptedSecret[]; -}[]; +}; + +export type TGetSecretImports = { + workspaceId: string; + environment: string; + directory?: string; +}; export type TGetImportedSecrets = { workspaceId: string; environment: string; - folderId?: string; + directory?: string; decryptFileKey: UserWsKeyPair; }; export type TCreateSecretImportDTO = { workspaceId: string; environment: string; - folderId?: string; + directory?: string; secretImport: { environment: string; secretPath: string; @@ -39,7 +45,7 @@ export type TUpdateSecretImportDTO = { id: string; workspaceId: string; environment: string; - folderId?: string; + directory?: string; secretImports: Array<{ environment: string; secretPath: string; @@ -50,7 +56,7 @@ export type TDeleteSecretImportDTO = { id: string; workspaceId: string; environment: string; - folderId?: string; + directory?: string; secretImportPath: string; secretImportEnv: string; }; diff --git a/frontend/src/hooks/api/secretSnapshots/index.tsx b/frontend/src/hooks/api/secretSnapshots/index.tsx index 0e308b6f8d..e6d4ec9b04 100644 --- a/frontend/src/hooks/api/secretSnapshots/index.tsx +++ b/frontend/src/hooks/api/secretSnapshots/index.tsx @@ -1,6 +1,6 @@ export { useGetSnapshotSecrets, - useGetWorkspaceSecretSnapshots, + useGetWorkspaceSnapshotList, useGetWsSnapshotCount, usePerformSecretRollback } from "./queries"; diff --git a/frontend/src/hooks/api/secretSnapshots/queries.tsx b/frontend/src/hooks/api/secretSnapshots/queries.tsx index cf11039101..13db97aead 100644 --- a/frontend/src/hooks/api/secretSnapshots/queries.tsx +++ b/frontend/src/hooks/api/secretSnapshots/queries.tsx @@ -9,39 +9,39 @@ import { apiRequest } from "@app/config/request"; import { DecryptedSecret } from "../secrets/types"; import { - GetWorkspaceSecretSnapshotsDTO, + TGetSecretSnapshotsDTO, TSecretRollbackDTO, - TSnapshotSecret, - TSnapshotSecretProps, - TWorkspaceSecretSnapshot + TSecretSnapshot, + TSnapshotData, + TSnapshotDataProps } from "./types"; export const secretSnapshotKeys = { - list: (workspaceId: string, env: string, folderId?: string) => - [{ workspaceId, env, folderId }, "secret-snapshot"] as const, - snapshotSecrets: (snapshotId: string) => [{ snapshotId }, "secret-snapshot"] as const, - count: (workspaceId: string, env: string, folderId?: string) => [ - { workspaceId, env, folderId }, + list: ({ workspaceId, environment, directory }: Omit) => + [{ workspaceId, environment, directory }, "secret-snapshot"] as const, + snapshotData: (snapshotId: string) => [{ snapshotId }, "secret-snapshot"] as const, + count: ({ environment, workspaceId, directory }: Omit) => [ + { workspaceId, environment, directory }, "count", "secret-snapshot" ] }; -const fetchWorkspaceSecretSnaphots = async ( - workspaceId: string, - environment: string, - folderId?: string, +const fetchWorkspaceSnaphots = async ({ + workspaceId, + environment, + directory = "/", limit = 10, offset = 0 -) => { - const res = await apiRequest.get<{ secretSnapshots: TWorkspaceSecretSnapshot[] }>( +}: TGetSecretSnapshotsDTO & { offset: number }) => { + const res = await apiRequest.get<{ secretSnapshots: TSecretSnapshot[] }>( `/api/v1/workspace/${workspaceId}/secret-snapshots`, { params: { limit, offset, environment, - folderId + directory } } ); @@ -49,32 +49,25 @@ const fetchWorkspaceSecretSnaphots = async ( return res.data.secretSnapshots; }; -export const useGetWorkspaceSecretSnapshots = (dto: GetWorkspaceSecretSnapshotsDTO) => +export const useGetWorkspaceSnapshotList = (dto: TGetSecretSnapshotsDTO & { isPaused?: boolean }) => useInfiniteQuery({ - enabled: Boolean(dto.workspaceId && dto.environment), - queryKey: secretSnapshotKeys.list(dto.workspaceId, dto.environment, dto?.folder), - queryFn: ({ pageParam }) => - fetchWorkspaceSecretSnaphots( - dto.workspaceId, - dto.environment, - dto?.folder, - dto.limit, - pageParam - ), + enabled: Boolean(dto.workspaceId && dto.environment) && !dto.isPaused, + queryKey: secretSnapshotKeys.list({ ...dto }), + queryFn: ({ pageParam }) => fetchWorkspaceSnaphots({ ...dto, offset: pageParam }), getNextPageParam: (lastPage, pages) => lastPage.length !== 0 ? pages.length * dto.limit : undefined }); const fetchSnapshotEncSecrets = async (snapshotId: string) => { - const res = await apiRequest.get<{ secretSnapshot: TSnapshotSecret }>( + const res = await apiRequest.get<{ secretSnapshot: TSnapshotData }>( `/api/v1/secret-snapshot/${snapshotId}` ); return res.data.secretSnapshot; }; -export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnapshotSecretProps) => +export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnapshotDataProps) => useQuery({ - queryKey: secretSnapshotKeys.snapshotSecrets(snapshotId), + queryKey: secretSnapshotKeys.snapshotData(snapshotId), enabled: Boolean(snapshotId && decryptFileKey), queryFn: () => fetchSnapshotEncSecrets(snapshotId), select: (data) => { @@ -117,7 +110,8 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnap comment: secretComment, createdAt: encSecret.createdAt, updatedAt: encSecret.updatedAt, - type: "modified" + type: "modified", + version: encSecret.version }; if (encSecret.type === "personal") { @@ -147,25 +141,30 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnap const fetchWorkspaceSecretSnaphotCount = async ( workspaceId: string, environment: string, - folderId?: string + directory = "/" ) => { const res = await apiRequest.get<{ count: number }>( `/api/v1/workspace/${workspaceId}/secret-snapshots/count`, { params: { environment, - folderId + directory } } ); return res.data.count; }; -export const useGetWsSnapshotCount = (workspaceId: string, env: string, folderId?: string) => +export const useGetWsSnapshotCount = ({ + workspaceId, + environment, + directory, + isPaused +}: Omit & { isPaused?: boolean }) => useQuery({ - enabled: Boolean(workspaceId && env), - queryKey: secretSnapshotKeys.count(workspaceId, env, folderId), - queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId, env, folderId) + enabled: Boolean(workspaceId && environment) && !isPaused, + queryKey: secretSnapshotKeys.count({ workspaceId, environment, directory }), + queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId, environment, directory) }); export const usePerformSecretRollback = () => { @@ -179,10 +178,17 @@ export const usePerformSecretRollback = () => { ); return data; }, - onSuccess: (_, { workspaceId, environment, folderId }) => { - queryClient.invalidateQueries([{ workspaceId, environment }, "secrets"]); - queryClient.invalidateQueries(secretSnapshotKeys.list(workspaceId, environment, folderId)); - queryClient.invalidateQueries(secretSnapshotKeys.count(workspaceId, environment, folderId)); + onSuccess: (_, { workspaceId, environment, directory }) => { + queryClient.invalidateQueries([ + { workspaceId, environment, secretPath: directory }, + "secrets" + ]); + queryClient.invalidateQueries( + secretSnapshotKeys.list({ workspaceId, environment, directory }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ workspaceId, environment, directory }) + ); } }); }; diff --git a/frontend/src/hooks/api/secretSnapshots/types.ts b/frontend/src/hooks/api/secretSnapshots/types.ts index 98f7b5b247..8fa5f94517 100644 --- a/frontend/src/hooks/api/secretSnapshots/types.ts +++ b/frontend/src/hooks/api/secretSnapshots/types.ts @@ -1,7 +1,7 @@ import { UserWsKeyPair } from "../keys/types"; import { EncryptedSecretVersion } from "../secrets/types"; -export type TWorkspaceSecretSnapshot = { +export type TSecretSnapshot = { _id: string; workspace: string; version: number; @@ -11,27 +11,27 @@ export type TWorkspaceSecretSnapshot = { __v: number; }; -export type TSnapshotSecret = Omit & { +export type TSnapshotData = Omit & { secretVersions: EncryptedSecretVersion[]; folderVersion: Array<{ name: string; id: string }>; }; -export type TSnapshotSecretProps = { +export type TSnapshotDataProps = { snapshotId: string; env: string; decryptFileKey: UserWsKeyPair; }; -export type GetWorkspaceSecretSnapshotsDTO = { +export type TGetSecretSnapshotsDTO = { workspaceId: string; limit: number; environment: string; - folder?: string; + directory?: string; }; export type TSecretRollbackDTO = { workspaceId: string; version: number; environment: string; - folderId?: string; + directory?: string; }; diff --git a/frontend/src/hooks/api/secrets/index.ts b/frontend/src/hooks/api/secrets/index.ts index 2e31e4f0b5..b24b5ed192 100644 --- a/frontend/src/hooks/api/secrets/index.ts +++ b/frontend/src/hooks/api/secrets/index.ts @@ -1,7 +1,9 @@ -export { useCreateSecretV3, useDeleteSecretV3, useUpdateSecretV3 } from "./mutations"; export { - useBatchSecretsOp, - useGetProjectSecrets, - useGetProjectSecretsAllEnv, - useGetSecretVersion -} from "./queries"; + useCreateSecretBatch, + useCreateSecretV3, + useDeleteSecretBatch, + useDeleteSecretV3, + useUpdateSecretBatch, + useUpdateSecretV3 +} from "./mutations"; +export { useGetProjectSecrets, useGetProjectSecretsAllEnv, useGetSecretVersion } from "./queries"; diff --git a/frontend/src/hooks/api/secrets/mutations.tsx b/frontend/src/hooks/api/secrets/mutations.tsx index 6df00174c4..c1bf59c85f 100644 --- a/frontend/src/hooks/api/secrets/mutations.tsx +++ b/frontend/src/hooks/api/secrets/mutations.tsx @@ -1,6 +1,6 @@ import crypto from "crypto"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { MutationOptions, useMutation, useQueryClient } from "@tanstack/react-query"; import { decryptAssymmetric, @@ -8,8 +8,17 @@ import { } from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; +import { secretSnapshotKeys } from "../secretSnapshots/queries"; import { secretKeys } from "./queries"; -import { TCreateSecretsV3DTO, TDeleteSecretsV3DTO, TUpdateSecretsV3DTO } from "./types"; +import { + CreateSecretDTO, + TCreateSecretBatchDTO, + TCreateSecretsV3DTO, + TDeleteSecretBatchDTO, + TDeleteSecretsV3DTO, + TUpdateSecretBatchDTO, + TUpdateSecretsV3DTO +} from "./types"; const encryptSecret = (randomBytes: string, key: string, value?: string, comment?: string) => { // encrypt key @@ -55,7 +64,11 @@ const encryptSecret = (randomBytes: string, key: string, value?: string, comment }; }; -export const useCreateSecretV3 = () => { +export const useCreateSecretV3 = ({ + options +}: { + options?: Omit, "mutationFn">; +} = {}) => { const queryClient = useQueryClient(); return useMutation<{}, {}, TCreateSecretsV3DTO>({ mutationFn: async ({ @@ -66,7 +79,8 @@ export const useCreateSecretV3 = () => { secretName, secretValue, latestFileKey, - secretComment + secretComment, + skipMultilineEncoding }) => { const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; @@ -84,20 +98,32 @@ export const useCreateSecretV3 = () => { environment, type, secretPath, - ...encryptSecret(randomBytes, secretName, secretValue, secretComment) + ...encryptSecret(randomBytes, secretName, secretValue, secretComment), + skipMultilineEncoding }; const { data } = await apiRequest.post(`/api/v3/secrets/${secretName}`, reqBody); return data; }, onSuccess: (_, { workspaceId, environment, secretPath }) => { queryClient.invalidateQueries( - secretKeys.getProjectSecret(workspaceId, environment, secretPath) + secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) ); - } + queryClient.invalidateQueries( + secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + ); + }, + ...options }); }; -export const useUpdateSecretV3 = () => { +export const useUpdateSecretV3 = ({ + options +}: { + options?: Omit, "mutationFn">; +} = {}) => { const queryClient = useQueryClient(); return useMutation<{}, {}, TUpdateSecretsV3DTO>({ mutationFn: async ({ @@ -107,7 +133,11 @@ export const useUpdateSecretV3 = () => { workspaceId, secretName, secretValue, - latestFileKey + latestFileKey, + tags, + secretComment, + newSecretName, + skipMultilineEncoding }) => { const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; @@ -119,34 +149,40 @@ export const useUpdateSecretV3 = () => { privateKey: PRIVATE_KEY }) : crypto.randomBytes(16).toString("hex"); - const { secretValueIV, secretValueTag, secretValueCiphertext } = encryptSecret( - randomBytes, - secretName, - secretValue, - "" - ); const reqBody = { workspaceId, environment, type, secretPath, - secretValueIV, - secretValueTag, - secretValueCiphertext + ...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment), + tags, + skipMultilineEncoding, + secretName: newSecretName }; const { data } = await apiRequest.patch(`/api/v3/secrets/${secretName}`, reqBody); return data; }, onSuccess: (_, { workspaceId, environment, secretPath }) => { queryClient.invalidateQueries( - secretKeys.getProjectSecret(workspaceId, environment, secretPath) + secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) ); - } + queryClient.invalidateQueries( + secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + ); + }, + ...options }); }; -export const useDeleteSecretV3 = () => { +export const useDeleteSecretV3 = ({ + options +}: { + options?: Omit, "mutationFn">; +} = {}) => { const queryClient = useQueryClient(); return useMutation<{}, {}, TDeleteSecretsV3DTO>({ @@ -165,8 +201,160 @@ export const useDeleteSecretV3 = () => { }, onSuccess: (_, { workspaceId, environment, secretPath }) => { queryClient.invalidateQueries( - secretKeys.getProjectSecret(workspaceId, environment, secretPath) + secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) ); - } + queryClient.invalidateQueries( + secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + ); + }, + ...options }); }; + +export const useCreateSecretBatch = ({ + options +}: { + options?: Omit, "mutationFn">; +} = {}) => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, TCreateSecretBatchDTO>({ + mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets, latestFileKey }) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const randomBytes = latestFileKey + ? decryptAssymmetric({ + ciphertext: latestFileKey.encryptedKey, + nonce: latestFileKey.nonce, + publicKey: latestFileKey.sender.publicKey, + privateKey: PRIVATE_KEY + }) + : crypto.randomBytes(16).toString("hex"); + + const reqBody = { + workspaceId, + environment, + secretPath, + secrets: secrets.map( + ({ secretName, secretValue, secretComment, metadata, type, skipMultilineEncoding }) => ({ + secretName, + ...encryptSecret(randomBytes, secretName, secretValue, secretComment), + type, + metadata, + skipMultilineEncoding + }) + ) + }; + + const { data } = await apiRequest.post("/api/v3/secrets/batch", reqBody); + return data; + }, + onSuccess: (_, { workspaceId, environment, secretPath }) => { + queryClient.invalidateQueries( + secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + ); + }, + ...options + }); +}; + +export const useUpdateSecretBatch = ({ + options +}: { + options?: Omit, "mutationFn">; +} = {}) => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, TUpdateSecretBatchDTO>({ + mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets, latestFileKey }) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const randomBytes = latestFileKey + ? decryptAssymmetric({ + ciphertext: latestFileKey.encryptedKey, + nonce: latestFileKey.nonce, + publicKey: latestFileKey.sender.publicKey, + privateKey: PRIVATE_KEY + }) + : crypto.randomBytes(16).toString("hex"); + + const reqBody = { + workspaceId, + environment, + secretPath, + secrets: secrets.map( + ({ secretName, secretValue, secretComment, type, tags, skipMultilineEncoding }) => ({ + secretName, + ...encryptSecret(randomBytes, secretName, secretValue, secretComment), + type, + tags, + skipMultilineEncoding + }) + ) + }; + + const { data } = await apiRequest.patch("/api/v3/secrets/batch", reqBody); + return data; + }, + onSuccess: (_, { workspaceId, environment, secretPath }) => { + queryClient.invalidateQueries( + secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + ); + }, + ...options + }); +}; + +export const useDeleteSecretBatch = ({ + options +}: { + options?: Omit, "mutationFn">; +} = {}) => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, TDeleteSecretBatchDTO>({ + mutationFn: async ({ secretPath = "/", workspaceId, environment, secrets }) => { + const reqBody = { + workspaceId, + environment, + secretPath, + secrets + }; + + const { data } = await apiRequest.delete("/api/v3/secrets/batch", { + data: reqBody + }); + return data; + }, + onSuccess: (_, { workspaceId, environment, secretPath }) => { + queryClient.invalidateQueries( + secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.list({ environment, workspaceId, directory: secretPath }) + ); + queryClient.invalidateQueries( + secretSnapshotKeys.count({ environment, workspaceId, directory: secretPath }) + ); + }, + ...options + }); +}; + +export const createSecret = async (dto: CreateSecretDTO) => { + const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto); + return data; +}; diff --git a/frontend/src/hooks/api/secrets/queries.tsx b/frontend/src/hooks/api/secrets/queries.tsx index cd032e58b3..a9cbc3b297 100644 --- a/frontend/src/hooks/api/secrets/queries.tsx +++ b/frontend/src/hooks/api/secrets/queries.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ import { useCallback, useMemo } from "react"; -import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQueries, useQuery, UseQueryOptions } from "@tanstack/react-query"; import { decryptAssymmetric, @@ -8,218 +8,148 @@ import { } from "@app/components/utilities/cryptography/crypto"; import { apiRequest } from "@app/config/request"; -import { secretSnapshotKeys } from "../secretSnapshots/queries"; +import { UserWsKeyPair } from "../keys/types"; import { - BatchSecretDTO, - CreateSecretDTO, DecryptedSecret, EncryptedSecret, EncryptedSecretVersion, - GetProjectSecretsDTO, GetSecretVersionsDTO, - TGetProjectSecretsAllEnvDTO} from "./types"; + TGetProjectSecretsAllEnvDTO, + TGetProjectSecretsDTO, + TGetProjectSecretsKey +} from "./types"; export const secretKeys = { // this is also used in secretSnapshot part - getProjectSecret: (workspaceId: string, env: string | string[], folderId?: string) => [ - { workspaceId, env, folderId }, - "secrets" - ], - getProjectSecretImports: (workspaceId: string, env: string | string[], folderId?: string) => [ - { workspaceId, env, folderId }, - "secrets-imports" - ], - getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] + getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) => + [{ workspaceId, environment, secretPath }, "secrets"] as const, + getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const }; -const fetchProjectEncryptedSecrets = async ( - workspaceId: string, - env: string | string[], - folderId?: string, - secretPath?: string -) => { +const decryptSecrets = (encryptedSecrets: EncryptedSecret[], decryptFileKey: UserWsKeyPair) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const key = decryptAssymmetric({ + ciphertext: decryptFileKey.encryptedKey, + nonce: decryptFileKey.nonce, + publicKey: decryptFileKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); + + const personalSecrets: Record = {}; + const secrets: DecryptedSecret[] = []; + encryptedSecrets.forEach((encSecret) => { + const secretKey = decryptSymmetric({ + ciphertext: encSecret.secretKeyCiphertext, + iv: encSecret.secretKeyIV, + tag: encSecret.secretKeyTag, + key + }); + + const secretValue = decryptSymmetric({ + ciphertext: encSecret.secretValueCiphertext, + iv: encSecret.secretValueIV, + tag: encSecret.secretValueTag, + key + }); + + const secretComment = decryptSymmetric({ + ciphertext: encSecret.secretCommentCiphertext, + iv: encSecret.secretCommentIV, + tag: encSecret.secretCommentTag, + key + }); + + const decryptedSecret: DecryptedSecret = { + _id: encSecret._id, + env: encSecret.environment, + key: secretKey, + value: secretValue, + tags: encSecret.tags, + comment: secretComment, + createdAt: encSecret.createdAt, + updatedAt: encSecret.updatedAt, + version: encSecret.version, + skipMultilineEncoding: encSecret.skipMultilineEncoding + }; + + if (encSecret.type === "personal") { + personalSecrets[decryptedSecret.key] = { + id: encSecret._id, + value: secretValue + }; + } else { + secrets.push(decryptedSecret); + } + }); + + secrets.forEach((sec) => { + if (personalSecrets?.[sec.key]) { + sec.idOverride = personalSecrets[sec.key].id; + sec.valueOverride = personalSecrets[sec.key].value; + sec.overrideAction = "modified"; + } + }); + + return secrets; +}; + +const fetchProjectEncryptedSecrets = async ({ + workspaceId, + environment, + secretPath +}: TGetProjectSecretsKey) => { const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>("/api/v3/secrets", { params: { - environment: env, + environment, workspaceId, - folderId: folderId || undefined, secretPath } }); - + return data.secrets; }; - export const useGetProjectSecrets = ({ workspaceId, - env, + environment, decryptFileKey, - isPaused, - folderId, - secretPath -}: GetProjectSecretsDTO) => + secretPath, + options +}: TGetProjectSecretsDTO & { + options?: Omit< + UseQueryOptions< + EncryptedSecret[], + unknown, + DecryptedSecret[], + ReturnType + >, + "queryKey" | "queryFn" + >; +}) => useQuery({ + ...options, // wait for all values to be available - enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused, - queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId || secretPath), - queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId, secretPath), - select: useCallback( - (data: EncryptedSecret[]) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const latestKey = decryptFileKey; - const key = decryptAssymmetric({ - ciphertext: latestKey.encryptedKey, - nonce: latestKey.nonce, - publicKey: latestKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const sharedSecrets: DecryptedSecret[] = []; - const personalSecrets: Record = {}; - // this used for add-only mode in dashboard - // type won't be there thus only one key is shown - const duplicateSecretKey: Record = {}; - data.forEach((encSecret: EncryptedSecret) => { - const secretKey = decryptSymmetric({ - ciphertext: encSecret.secretKeyCiphertext, - iv: encSecret.secretKeyIV, - tag: encSecret.secretKeyTag, - key - }); - - const secretValue = decryptSymmetric({ - ciphertext: encSecret.secretValueCiphertext, - iv: encSecret.secretValueIV, - tag: encSecret.secretValueTag, - key - }); - - const secretComment = decryptSymmetric({ - ciphertext: encSecret.secretCommentCiphertext, - iv: encSecret.secretCommentIV, - tag: encSecret.secretCommentTag, - key - }); - - const decryptedSecret = { - _id: encSecret._id, - env: encSecret.environment, - key: secretKey, - value: secretValue, - tags: encSecret.tags, - comment: secretComment, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt - }; - - if (encSecret.type === "personal") { - personalSecrets[`${decryptedSecret.key}-${decryptedSecret.env}`] = { - id: encSecret._id, - value: secretValue - }; - } else { - if (!duplicateSecretKey?.[`${decryptedSecret.key}-${decryptedSecret.env}`]) { - sharedSecrets.push(decryptedSecret); - } - duplicateSecretKey[`${decryptedSecret.key}-${decryptedSecret.env}`] = true; - } - }); - sharedSecrets.forEach((val) => { - const dupKey = `${val.key}-${val.env}`; - if (personalSecrets?.[dupKey]) { - val.idOverride = personalSecrets[dupKey].id; - val.valueOverride = personalSecrets[dupKey].value; - val.overrideAction = "modified"; - } - }); - return { secrets: sharedSecrets }; - }, - [decryptFileKey] - ) + enabled: Boolean(decryptFileKey && workspaceId && environment) && (options?.enabled ?? true), + queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }), + queryFn: async () => fetchProjectEncryptedSecrets({ workspaceId, environment, secretPath }), + select: (secrets: EncryptedSecret[]) => decryptSecrets(secrets, decryptFileKey) }); export const useGetProjectSecretsAllEnv = ({ workspaceId, envs, decryptFileKey, - folderId, secretPath }: TGetProjectSecretsAllEnvDTO) => { const secrets = useQueries({ - queries: envs.map((env) => ({ - queryKey: secretKeys.getProjectSecret(workspaceId, env, secretPath || folderId), - enabled: Boolean(decryptFileKey && workspaceId && env), - queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId, secretPath), - select: (data: EncryptedSecret[]) => { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - const latestKey = decryptFileKey; - const key = decryptAssymmetric({ - ciphertext: latestKey.encryptedKey, - nonce: latestKey.nonce, - publicKey: latestKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const sharedSecrets: Record = {}; - const personalSecrets: Record = {}; - // this used for add-only mode in dashboard - // type won't be there thus only one key is shown - const duplicateSecretKey: Record = {}; - data.forEach((encSecret: EncryptedSecret) => { - const secretKey = decryptSymmetric({ - ciphertext: encSecret.secretKeyCiphertext, - iv: encSecret.secretKeyIV, - tag: encSecret.secretKeyTag, - key - }); - - const secretValue = decryptSymmetric({ - ciphertext: encSecret.secretValueCiphertext, - iv: encSecret.secretValueIV, - tag: encSecret.secretValueTag, - key - }); - - const secretComment = decryptSymmetric({ - ciphertext: encSecret.secretCommentCiphertext, - iv: encSecret.secretCommentIV, - tag: encSecret.secretCommentTag, - key - }); - - const decryptedSecret = { - _id: encSecret._id, - env: encSecret.environment, - key: secretKey, - value: secretValue, - tags: encSecret.tags, - comment: secretComment, - createdAt: encSecret.createdAt, - updatedAt: encSecret.updatedAt - }; - - if (encSecret.type === "personal") { - personalSecrets[decryptedSecret.key] = { - id: encSecret._id, - value: secretValue - }; - } else { - if (!duplicateSecretKey?.[decryptedSecret.key]) { - sharedSecrets[decryptedSecret.key] = decryptedSecret; - } - duplicateSecretKey[decryptedSecret.key] = true; - } - }); - - Object.keys(sharedSecrets).forEach((val) => { - if (personalSecrets?.[val]) { - sharedSecrets[val].idOverride = personalSecrets[val].id; - sharedSecrets[val].valueOverride = personalSecrets[val].value; - sharedSecrets[val].overrideAction = "modified"; - } - }); - return sharedSecrets; - } + queries: envs.map((environment) => ({ + queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }), + enabled: Boolean(decryptFileKey && workspaceId && environment), + queryFn: async () => fetchProjectEncryptedSecrets({ workspaceId, environment, secretPath }), + select: (secs: EncryptedSecret[]) => + decryptSecrets(secs, decryptFileKey).reduce>( + (prev, curr) => ({ ...prev, [curr.key]: curr }), + {} + ) })) }); @@ -303,46 +233,3 @@ export const useGetSecretVersion = (dto: GetSecretVersionsDTO) => [dto.decryptFileKey] ) }); - -export const useBatchSecretsOp = () => { - const queryClient = useQueryClient(); - - return useMutation<{}, {}, BatchSecretDTO>({ - mutationFn: async (dto) => { - const { data } = await apiRequest.post("/api/v2/secrets/batch", dto); - return data; - }, - onSuccess: (_, dto) => { - queryClient.invalidateQueries( - secretKeys.getProjectSecret(dto.workspaceId, dto.environment, dto.folderId) - ); - queryClient.invalidateQueries( - secretSnapshotKeys.list(dto.workspaceId, dto.environment, dto?.folderId) - ); - queryClient.invalidateQueries( - secretSnapshotKeys.count(dto.workspaceId, dto.environment, dto?.folderId) - ); - } - }); -}; - -export const createSecret = async (dto: CreateSecretDTO) => { - const { data } = await apiRequest.post(`/api/v3/secrets/${dto.secretKey}`, dto); - return data; -} - -export const useCreateSecret = () => { - const queryClient = useQueryClient(); - - return useMutation<{}, {}, CreateSecretDTO>({ - mutationFn: async (dto) => { - const data = createSecret(dto); - return data; - }, - onSuccess: (_, dto) => { - queryClient.invalidateQueries( - secretKeys.getProjectSecret(dto.workspaceId, dto.environment) - ); - } - }); -}; diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts index 2bf304f4eb..253ce77a6b 100644 --- a/frontend/src/hooks/api/secrets/types.ts +++ b/frontend/src/hooks/api/secrets/types.ts @@ -16,6 +16,7 @@ export type EncryptedSecret = { __v: number; createdAt: string; updatedAt: string; + skipMultilineEncoding?: boolean; secretCommentCiphertext: string; secretCommentIV: string; secretCommentTag: string; @@ -24,6 +25,7 @@ export type EncryptedSecret = { export type DecryptedSecret = { _id: string; + version: number; key: string; value: string; comment: string; @@ -35,6 +37,7 @@ export type DecryptedSecret = { idOverride?: string; overrideAction?: string; folderId?: string; + skipMultilineEncoding?: boolean; }; export type EncryptedSecretVersion = { @@ -53,55 +56,21 @@ export type EncryptedSecretVersion = { secretValueTag: string; tags: WsTag[]; __v: number; + skipMultilineEncoding?: boolean; createdAt: string; updatedAt: string; }; // dto -type SecretTagArg = { _id: string; name: string; slug: string }; - -export type UpdateSecretArg = { - _id: string; - folderId?: string; - type: "shared" | "personal"; - secretName: string; - secretKeyCiphertext: string; - secretKeyIV: string; - secretKeyTag: string; - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; - secretCommentCiphertext: string; - secretCommentIV: string; - secretCommentTag: string; - tags: SecretTagArg[]; -}; - -export type CreateSecretArg = Omit; - -export type DeleteSecretArg = { _id: string, secretName: string; }; - -export type BatchSecretDTO = { +export type TGetProjectSecretsKey = { workspaceId: string; - folderId: string; environment: string; - requests: Array< - | { method: "POST"; secret: CreateSecretArg } - | { method: "PATCH"; secret: UpdateSecretArg } - | { method: "DELETE"; secret: DeleteSecretArg } - >; + secretPath?: string; }; -export type GetProjectSecretsDTO = { - workspaceId: string; - env: string | string[]; +export type TGetProjectSecretsDTO = { decryptFileKey: UserWsKeyPair; - folderId?: string; - secretPath?: string; - isPaused?: boolean; - include_imports?: boolean; - onSuccess?: (data: DecryptedSecret[]) => void; -}; +} & TGetProjectSecretsKey; export type TGetProjectSecretsAllEnvDTO = { workspaceId: string; @@ -124,6 +93,7 @@ export type TCreateSecretsV3DTO = { secretName: string; secretValue: string; secretComment: string; + skipMultilineEncoding?: boolean; secretPath: string; workspaceId: string; environment: string; @@ -136,19 +106,63 @@ export type TUpdateSecretsV3DTO = { environment: string; type: string; secretPath: string; + skipMultilineEncoding?: boolean; + newSecretName?: string; secretName: string; secretValue: string; + secretComment?: string; + tags?: string[]; }; export type TDeleteSecretsV3DTO = { workspaceId: string; environment: string; - type: string; + type: "shared" | "personal"; secretPath: string; secretName: string; }; -// --- v3 +export type TCreateSecretBatchDTO = { + workspaceId: string; + environment: string; + secretPath: string; + latestFileKey: UserWsKeyPair; + secrets: Array<{ + secretName: string; + secretValue: string; + secretComment: string; + skipMultilineEncoding?: boolean; + type: "shared" | "personal"; + metadata?: { + source?: string; + }; + }>; +}; + +export type TUpdateSecretBatchDTO = { + workspaceId: string; + environment: string; + secretPath: string; + latestFileKey: UserWsKeyPair; + secrets: Array<{ + type: "shared" | "personal"; + secretName: string; + skipMultilineEncoding?: boolean; + secretValue: string; + secretComment: string; + tags?: string[]; + }>; +}; + +export type TDeleteSecretBatchDTO = { + workspaceId: string; + environment: string; + secretPath: string; + secrets: Array<{ + secretName: string; + type: "shared" | "personal"; + }>; +}; export type CreateSecretDTO = { workspaceId: string; @@ -167,5 +181,5 @@ export type CreateSecretDTO = { secretPath: string; metadata?: { source?: string; - } -} \ No newline at end of file + }; +}; diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts index c91fb551d5..b46b5bfab6 100644 --- a/frontend/src/hooks/api/types.ts +++ b/frontend/src/hooks/api/types.ts @@ -5,6 +5,9 @@ export type { TCloudIntegration, TIntegration } from "./integrations/types"; export type { UserWsKeyPair } from "./keys/types"; export type { Organization } from "./organization/types"; export type { TSecretApprovalPolicy } from "./secretApproval/types"; +export type { TSecretFolder } from "./secretFolders/types"; +export type { TImportedSecrets, TSecretImports } from "./secretImports/types"; +export * from "./secrets/types"; export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types"; export type { SubscriptionPlan } from "./subscriptions/types"; export type { WsTag } from "./tags/types"; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 39098bf5c2..0dd859ed46 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -315,7 +315,7 @@ module.exports = { // TODO:(akhilmhdh) remove all these unused and keep the config file as small as possible // Make the whole color pallelte into simpler bounce: "bounce 1000ms ease-in-out infinite", - spin: "spin 4000ms ease-in-out infinite", + spin: "spin 1500ms ease-in-out infinite", cursor: "cursor .6s linear infinite alternate", type: "type 2.7s ease-out .8s infinite alternate both", "type-reverse": "type 1.8s ease-out 0s infinite alternate-reverse both",