-
{title}
+export const EmptyState = ({
+ title,
+ className,
+ children,
+ icon = faCubesStacked,
+ iconSize = '2x'
+}: Props) => (
+
diff --git a/frontend/src/components/v2/Tooltip/Tooltip.tsx b/frontend/src/components/v2/Tooltip/Tooltip.tsx
index a3119d9c7..b0d009421 100644
--- a/frontend/src/components/v2/Tooltip/Tooltip.tsx
+++ b/frontend/src/components/v2/Tooltip/Tooltip.tsx
@@ -35,7 +35,7 @@ export const Tooltip = ({
sideOffset={5}
{...props}
className={twMerge(
- `z-20 select-none max-w-[15rem] rounded-md bg-mineshaft-800 border border-mineshaft-600 py-2 px-4 font-light text-sm text-bunker-200 shadow-md
+ `z-50 max-w-[15rem] select-none rounded-md border border-mineshaft-600 bg-mineshaft-800 py-2 px-4 text-sm font-light text-bunker-200 shadow-md
data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade
data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade
data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade
diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx
index 89d947068..9c98e3b7e 100644
--- a/frontend/src/hooks/api/index.tsx
+++ b/frontend/src/hooks/api/index.tsx
@@ -2,6 +2,7 @@ export * from './auth';
export * from './incidentContacts';
export * from './keys';
export * from './organization';
+export * from './secretFolders';
export * from './secrets';
export * from './secretSnapshots';
export * from './serviceAccounts';
diff --git a/frontend/src/hooks/api/secretFolders/index.tsx b/frontend/src/hooks/api/secretFolders/index.tsx
new file mode 100644
index 000000000..c1cc056d1
--- /dev/null
+++ b/frontend/src/hooks/api/secretFolders/index.tsx
@@ -0,0 +1 @@
+export { useCreateFolder, useDeleteFolder, useGetProjectFolders, useUpdateFolder } from './queries';
diff --git a/frontend/src/hooks/api/secretFolders/queries.tsx b/frontend/src/hooks/api/secretFolders/queries.tsx
new file mode 100644
index 000000000..008d6f26f
--- /dev/null
+++ b/frontend/src/hooks/api/secretFolders/queries.tsx
@@ -0,0 +1,129 @@
+import { useCallback } from 'react';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+
+import { apiRequest } from '@app/config/request';
+
+import { secretSnapshotKeys } from '../secretSnapshots/queries';
+import {
+ CreateFolderDTO,
+ DeleteFolderDTO,
+ GetProjectFoldersDTO,
+ TSecretFolder,
+ UpdateFolderDTO
+} from './types';
+
+const queryKeys = {
+ getSecretFolders: (workspaceId: string, environment: string, parentFolderId?: string) =>
+ ['secret-folders', { workspaceId, environment, parentFolderId }] as const
+};
+
+export const useGetProjectFolders = ({
+ workspaceId,
+ parentFolderId,
+ environment,
+ isPaused,
+ sortDir
+}: GetProjectFoldersDTO) =>
+ useQuery({
+ queryKey: queryKeys.getSecretFolders(workspaceId, environment, parentFolderId),
+ enabled: Boolean(workspaceId) && Boolean(environment) && !isPaused,
+ queryFn: async () => {
+ const { data } = await apiRequest.get<{ folders: TSecretFolder[]; dir: TSecretFolder[] }>(
+ '/api/v1/folders',
+ {
+ params: {
+ workspaceId,
+ environment,
+ parentFolderId
+ }
+ }
+ );
+ return data;
+ },
+ 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]
+ )
+ });
+
+export const useCreateFolder = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation<{}, {}, CreateFolderDTO>({
+ mutationFn: async (dto) => {
+ const { data } = await apiRequest.post('/api/v1/folders', dto);
+ return data;
+ },
+ onSuccess: (_, { workspaceId, environment, parentFolderId }) => {
+ queryClient.invalidateQueries(
+ queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
+ );
+ }
+ });
+};
+
+export const useUpdateFolder = (parentFolderId: string) => {
+ const queryClient = useQueryClient();
+
+ return useMutation<{}, {}, UpdateFolderDTO>({
+ mutationFn: async ({ folderId, name, environment, workspaceId }) => {
+ const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, {
+ name,
+ environment,
+ workspaceId
+ });
+ return data;
+ },
+ onSuccess: (_, { workspaceId, environment }) => {
+ queryClient.invalidateQueries(
+ queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
+ );
+ }
+ });
+};
+
+export const useDeleteFolder = (parentFolderId: string) => {
+ const queryClient = useQueryClient();
+
+ return useMutation<{}, {}, DeleteFolderDTO>({
+ mutationFn: async ({ folderId, environment, workspaceId }) => {
+ const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, {
+ data: {
+ environment,
+ workspaceId
+ }
+ });
+ return data;
+ },
+ onSuccess: (_, { workspaceId, environment }) => {
+ queryClient.invalidateQueries(
+ queryKeys.getSecretFolders(workspaceId, environment, parentFolderId)
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.count(workspaceId, environment, parentFolderId)
+ );
+ queryClient.invalidateQueries(
+ secretSnapshotKeys.list(workspaceId, environment, parentFolderId)
+ );
+ }
+ });
+};
diff --git a/frontend/src/hooks/api/secretFolders/types.ts b/frontend/src/hooks/api/secretFolders/types.ts
new file mode 100644
index 000000000..a9ca17641
--- /dev/null
+++ b/frontend/src/hooks/api/secretFolders/types.ts
@@ -0,0 +1,32 @@
+export type TSecretFolder = {
+ id: string;
+ name: string;
+};
+
+export type GetProjectFoldersDTO = {
+ workspaceId: string;
+ environment: string;
+ parentFolderId?: string;
+ isPaused?: boolean;
+ sortDir?: 'asc' | 'desc';
+};
+
+export type CreateFolderDTO = {
+ workspaceId: string;
+ environment: string;
+ folderName: string;
+ parentFolderId?: string;
+};
+
+export type UpdateFolderDTO = {
+ workspaceId: string;
+ environment: string;
+ name: string;
+ folderId: string;
+};
+
+export type DeleteFolderDTO = {
+ workspaceId: string;
+ environment: string;
+ folderId: string;
+};
diff --git a/frontend/src/hooks/api/secretSnapshots/queries.tsx b/frontend/src/hooks/api/secretSnapshots/queries.tsx
index 3aa9dfbe5..b5daf62fd 100644
--- a/frontend/src/hooks/api/secretSnapshots/queries.tsx
+++ b/frontend/src/hooks/api/secretSnapshots/queries.tsx
@@ -17,18 +17,31 @@ import {
} from './types';
export const secretSnapshotKeys = {
- list: (workspaceId: string) => [{ workspaceId }, 'secret-snapshot'] as const,
+ 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) => [{ workspaceId }, 'count', 'secret-snapshot']
+ count: (workspaceId: string, env: string, folderId?: string) => [
+ { workspaceId, env, folderId },
+ 'count',
+ 'secret-snapshot'
+ ]
};
-const fetchWorkspaceSecretSnaphots = async (workspaceId: string, limit = 10, offset = 0) => {
+const fetchWorkspaceSecretSnaphots = async (
+ workspaceId: string,
+ environment: string,
+ folderId?: string,
+ limit = 10,
+ offset = 0
+) => {
const res = await apiRequest.get<{ secretSnapshots: TWorkspaceSecretSnapshot[] }>(
`/api/v1/workspace/${workspaceId}/secret-snapshots`,
{
params: {
limit,
- offset
+ offset,
+ environment,
+ folderId
}
}
);
@@ -38,9 +51,16 @@ const fetchWorkspaceSecretSnaphots = async (workspaceId: string, limit = 10, off
export const useGetWorkspaceSecretSnapshots = (dto: GetWorkspaceSecretSnapshotsDTO) =>
useInfiniteQuery({
- enabled: Boolean(dto.workspaceId),
- queryKey: secretSnapshotKeys.list(dto.workspaceId),
- queryFn: ({ pageParam }) => fetchWorkspaceSecretSnaphots(dto.workspaceId, dto.limit, pageParam),
+ 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
+ ),
getNextPageParam: (lastPage, pages) =>
lastPage.length !== 0 ? pages.length * dto.limit : undefined
});
@@ -115,40 +135,53 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, env, snapshotId }: TSnap
}
});
- return { version: data.version, secrets: sharedSecrets, createdAt: data.createdAt };
+ return {
+ version: data.version,
+ secrets: sharedSecrets,
+ createdAt: data.createdAt,
+ folders: data.folderVersion
+ };
}
});
-const fetchWorkspaceSecretSnaphotCount = async (workspaceId: string) => {
+const fetchWorkspaceSecretSnaphotCount = async (
+ workspaceId: string,
+ environment: string,
+ folderId?: string
+) => {
const res = await apiRequest.get<{ count: number }>(
- `/api/v1/workspace/${workspaceId}/secret-snapshots/count`
+ `/api/v1/workspace/${workspaceId}/secret-snapshots/count`,
+ {
+ params: {
+ environment,
+ folderId
+ }
+ }
);
return res.data.count;
};
-export const useGetWsSnapshotCount = (workspaceId: string) =>
+export const useGetWsSnapshotCount = (workspaceId: string, env: string, folderId?: string) =>
useQuery({
- enabled: Boolean(workspaceId),
- queryKey: secretSnapshotKeys.count(workspaceId),
- queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId)
+ enabled: Boolean(workspaceId && env),
+ queryKey: secretSnapshotKeys.count(workspaceId, env, folderId),
+ queryFn: () => fetchWorkspaceSecretSnaphotCount(workspaceId, env, folderId)
});
export const usePerformSecretRollback = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TSecretRollbackDTO>({
- mutationFn: async (dto) => {
+ mutationFn: async ({ workspaceId, ...dto }) => {
const { data } = await apiRequest.post(
- `/api/v1/workspace/${dto.workspaceId}/secret-snapshots/rollback`,
- {
- version: dto.version
- }
+ `/api/v1/workspace/${workspaceId}/secret-snapshots/rollback`,
+ dto
);
return data;
},
- onSuccess: (_, dto) => {
- queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
- queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
+ onSuccess: (_, { workspaceId, environment, folderId }) => {
+ queryClient.invalidateQueries(secretSnapshotKeys.list(workspaceId, environment, folderId));
+ queryClient.invalidateQueries(secretSnapshotKeys.count(workspaceId, environment, folderId));
}
});
};
diff --git a/frontend/src/hooks/api/secretSnapshots/types.ts b/frontend/src/hooks/api/secretSnapshots/types.ts
index dbe8ccd17..cc06f2bc1 100644
--- a/frontend/src/hooks/api/secretSnapshots/types.ts
+++ b/frontend/src/hooks/api/secretSnapshots/types.ts
@@ -13,6 +13,7 @@ export type TWorkspaceSecretSnapshot = {
export type TSnapshotSecret = Omit
& {
secretVersions: EncryptedSecretVersion[];
+ folderVersion: Array<{ name: string; id: string }>;
};
export type TSnapshotSecretProps = {
@@ -24,9 +25,13 @@ export type TSnapshotSecretProps = {
export type GetWorkspaceSecretSnapshotsDTO = {
workspaceId: string;
limit: number;
+ environment: string;
+ folder?: string;
};
export type TSecretRollbackDTO = {
workspaceId: string;
version: number;
+ environment: string;
+ folderId?: string;
};
diff --git a/frontend/src/hooks/api/secrets/queries.tsx b/frontend/src/hooks/api/secrets/queries.tsx
index ecc26c63d..e4ad89dd6 100644
--- a/frontend/src/hooks/api/secrets/queries.tsx
+++ b/frontend/src/hooks/api/secrets/queries.tsx
@@ -19,19 +19,24 @@ import {
export const secretKeys = {
// this is also used in secretSnapshot part
- getProjectSecret: (workspaceId: string, env: string | string[]) => [
- { workspaceId, env },
+ getProjectSecret: (workspaceId: string, env: string | string[], folderId?: string) => [
+ { workspaceId, env, folderId },
'secrets'
],
getSecretVersion: (secretId: string) => [{ secretId }, 'secret-versions']
};
-const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | string[]) => {
+const fetchProjectEncryptedSecrets = async (
+ workspaceId: string,
+ env: string | string[],
+ folderId?: string
+) => {
if (typeof env === 'string') {
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
params: {
environment: env,
- workspaceId
+ workspaceId,
+ folderId: folderId || undefined
}
});
return data.secrets;
@@ -46,7 +51,8 @@ const fetchProjectEncryptedSecrets = async (workspaceId: string, env: string | s
const { data } = await apiRequest.get<{ secrets: EncryptedSecret[] }>('/api/v2/secrets', {
params: {
environment: envPoint,
- workspaceId
+ workspaceId,
+ folderId
}
});
allEnvData = allEnvData.concat(data.secrets);
@@ -63,13 +69,14 @@ export const useGetProjectSecrets = ({
workspaceId,
env,
decryptFileKey,
- isPaused
+ isPaused,
+ folderId
}: GetProjectSecretsDTO) =>
useQuery({
// wait for all values to be available
enabled: Boolean(decryptFileKey && workspaceId && env) && !isPaused,
- queryKey: secretKeys.getProjectSecret(workspaceId, env),
- queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env),
+ queryKey: secretKeys.getProjectSecret(workspaceId, env, folderId),
+ queryFn: () => fetchProjectEncryptedSecrets(workspaceId, env, folderId),
select: (data) => {
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
const latestKey = decryptFileKey;
@@ -283,9 +290,15 @@ export const useBatchSecretsOp = () => {
return data;
},
onSuccess: (_, dto) => {
- queryClient.invalidateQueries(secretKeys.getProjectSecret(dto.workspaceId, dto.environment));
- queryClient.invalidateQueries(secretSnapshotKeys.list(dto.workspaceId));
- queryClient.invalidateQueries(secretSnapshotKeys.count(dto.workspaceId));
+ 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)
+ );
}
});
};
diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts
index eb1157e91..2a5938c68 100644
--- a/frontend/src/hooks/api/secrets/types.ts
+++ b/frontend/src/hooks/api/secrets/types.ts
@@ -61,6 +61,7 @@ type SecretTagArg = { _id: string; name: string; slug: string };
export type UpdateSecretArg = {
_id: string;
+ folderId?: string;
type: 'shared' | 'personal';
secretName: string;
secretKeyCiphertext: string;
@@ -81,6 +82,7 @@ export type DeleteSecretArg = { _id: string };
export type BatchSecretDTO = {
workspaceId: string;
+ folderId: string;
environment: string;
requests: Array<
| { method: 'POST'; secret: CreateSecretArg }
@@ -93,6 +95,7 @@ export type GetProjectSecretsDTO = {
workspaceId: string;
env: string | string[];
decryptFileKey: UserWsKeyPair;
+ folderId?: string;
isPaused?: boolean;
onSuccess?: (data: DecryptedSecret[]) => void;
};
diff --git a/frontend/src/views/DashboardPage/DashboardPage.tsx b/frontend/src/views/DashboardPage/DashboardPage.tsx
index a8f9b1275..9479e50d7 100644
--- a/frontend/src/views/DashboardPage/DashboardPage.tsx
+++ b/frontend/src/views/DashboardPage/DashboardPage.tsx
@@ -10,6 +10,7 @@ import {
faDownload,
faEye,
faEyeSlash,
+ faFolderPlus,
faMagnifyingGlass,
faPlus
} from '@fortawesome/free-solid-svg-icons';
@@ -21,6 +22,7 @@ import { useNotificationContext } from '@app/components/context/Notifications/No
import NavHeader from '@app/components/navigation/NavHeader';
import {
Button,
+ DeleteActionModal,
IconButton,
Input,
Modal,
@@ -37,7 +39,10 @@ import { useWorkspace } from '@app/context';
import { useLeaveConfirm, usePopUp, useToggle } from '@app/hooks';
import {
useBatchSecretsOp,
+ useCreateFolder,
useCreateWsTag,
+ useDeleteFolder,
+ useGetProjectFolders,
useGetProjectSecrets,
useGetSecretVersion,
useGetSnapshotSecrets,
@@ -48,13 +53,20 @@ import {
useGetWsSnapshotCount,
useGetWsTags,
usePerformSecretRollback,
- useRegisterUserAction
+ useRegisterUserAction,
+ useUpdateFolder
} from '@app/hooks/api';
import { secretKeys } from '@app/hooks/api/secrets/queries';
import { WorkspaceEnv } from '@app/hooks/api/types';
import { CompareSecret } from './components/CompareSecret';
import { CreateTagModal } from './components/CreateTagModal';
+import {
+ FolderForm,
+ FolderSection,
+ TDeleteFolderForm,
+ TEditFolderForm
+} from './components/FolderSection';
import { PitDrawer } from './components/PitDrawer';
import { SecretDetailDrawer } from './components/SecretDetailDrawer';
import { SecretDropzone } from './components/SecretDropzone';
@@ -96,7 +108,9 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
'addTag',
'secretSnapshots',
'uploadedSecOpts',
- 'compareSecrets'
+ 'compareSecrets',
+ 'folderForm',
+ 'deleteFolder'
] as const);
const [isSecretValueHidden, setIsSecretValueHidden] = useToggle(true);
const [searchFilter, setSearchFilter] = useState('');
@@ -106,6 +120,9 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
const deletedSecretIds = useRef([]);
const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({ initialValue: false });
+ const folderId = router.query.folderId as string;
+ const isRollbackMode = Boolean(snapshotId);
+
const { currentWorkspace, isLoading } = useWorkspace();
const workspaceId = currentWorkspace?._id as string;
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
@@ -142,7 +159,16 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
workspaceId,
env: selectedEnv?.slug || '',
decryptFileKey: latestFileKey!,
- isPaused: Boolean(snapshotId)
+ isPaused: Boolean(snapshotId),
+ folderId
+ });
+
+ const { data: folderData, isLoading: isFoldersLoading } = useGetProjectFolders({
+ workspaceId: workspaceId || '',
+ environment: selectedEnv?.slug || '',
+ parentFolderId: folderId,
+ isPaused: isRollbackMode,
+ sortDir
});
const {
@@ -152,6 +178,8 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
isFetchingNextPage
} = useGetWorkspaceSecretSnapshots({
workspaceId,
+ environment: selectedEnv?.slug || '',
+ folder: folderId,
limit: 10
});
@@ -165,8 +193,11 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
decryptFileKey: latestFileKey!
});
- const { data: snapshotCount, isLoading: isLoadingSnapshotCount } =
- useGetWsSnapshotCount(workspaceId);
+ const { data: snapshotCount, isLoading: isLoadingSnapshotCount } = useGetWsSnapshotCount(
+ workspaceId,
+ selectedEnv?.slug || '',
+ folderId
+ );
const { data: wsTags } = useGetWsTags(workspaceId);
// mutation calls
@@ -174,6 +205,9 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
const { mutateAsync: performSecretRollback } = usePerformSecretRollback();
const { mutateAsync: registerUserAction } = useRegisterUserAction();
const { mutateAsync: createWsTag } = useCreateWsTag();
+ const { mutateAsync: createFolder } = useCreateFolder();
+ const { mutateAsync: updateFolder } = useUpdateFolder(folderId);
+ const { mutateAsync: deleteFolder } = useDeleteFolder(folderId);
const method = useForm({
// why any: well yup inferred ts expects other keys to defined as undefined
@@ -192,7 +226,6 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
reset
} = method;
const { fields, prepend, append, remove } = useFieldArray({ control, name: 'secrets' });
- const isRollbackMode = Boolean(snapshotId);
const isReadOnly = selectedEnv?.isWriteDenied;
const isAddOnly = selectedEnv?.isReadDenied && !selectedEnv?.isWriteDenied;
const canDoRollback = !isReadOnly && !isAddOnly;
@@ -281,7 +314,9 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
try {
await performSecretRollback({
workspaceId,
- version: snapshotSecret.version
+ version: snapshotSecret.version,
+ environment: selectedEnv?.slug || '',
+ folderId
});
setValue('isSnapshotMode', false);
setSnaphotId(null);
@@ -326,6 +361,7 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
await batchSecretOp({
requests: batchedSecret,
workspaceId,
+ folderId,
environment: selectedEnv?.slug
});
createNotification({
@@ -393,7 +429,99 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
}
};
- if (isSecretsLoading || isEnvListLoading) {
+ const handleFolderOpen = (id: string) => {
+ setSearchFilter('');
+ router.push({
+ pathname: router.pathname,
+ query: {
+ ...router.query,
+ folderId: id
+ }
+ });
+ };
+
+ const isEditFolder = Boolean(popUp?.folderForm?.data);
+
+ const handleFolderCreate = async (name: string) => {
+ try {
+ await createFolder({
+ workspaceId,
+ environment: selectedEnv?.slug || '',
+ folderName: name,
+ parentFolderId: folderId
+ });
+ createNotification({
+ type: 'success',
+ text: 'Successfully created folder'
+ });
+ handlePopUpClose('folderForm');
+ } catch (error) {
+ console.error(error);
+ createNotification({
+ text: 'Failed to create folder',
+ type: 'error'
+ });
+ }
+ };
+
+ const handleFolderUpdate = async (name: string) => {
+ const { id } = popUp?.folderForm?.data as TDeleteFolderForm;
+ try {
+ await updateFolder({
+ folderId: id,
+ workspaceId,
+ environment: selectedEnv?.slug || '',
+ name
+ });
+ createNotification({
+ type: 'success',
+ text: 'Successfully updated folder'
+ });
+ handlePopUpClose('folderForm');
+ } catch (error) {
+ console.error(error);
+ createNotification({
+ text: 'Failed to update folder',
+ type: 'error'
+ });
+ }
+ };
+
+ const handleFolderDelete = async () => {
+ const { id } = popUp?.deleteFolder?.data as TDeleteFolderForm;
+ try {
+ deleteFolder({
+ workspaceId,
+ environment: selectedEnv?.slug || '',
+ folderId: id
+ });
+ createNotification({
+ type: 'success',
+ text: 'Successfully removed folder'
+ });
+ handlePopUpClose('deleteFolder');
+ } catch (error) {
+ console.error(error);
+ createNotification({
+ text: 'Failed to remove folder',
+ type: 'error'
+ });
+ }
+ };
+
+ // when secrets is not loading and secrets list is empty
+ const isDashboardSecretEmpty = !isSecretsLoading && !fields?.length;
+
+ // folder list checks
+ const isFolderListLoading = isRollbackMode ? isSnapshotSecretsLoading : isFoldersLoading;
+ const folderList = isRollbackMode ? snapshotSecret?.folders : folderData?.folders;
+
+ // when using snapshot mode and snapshot is loading and snapshot list is empty
+ const isSnapshotSecretEmtpy =
+ isRollbackMode && !isSnapshotSecretsLoading && !snapshotSecret?.secrets?.length;
+ const isSecretEmpty = (!isRollbackMode && isDashboardSecretEmpty) || isSnapshotSecretEmtpy;
+
+ if (isSecretsLoading || isEnvListLoading || isFolderListLoading) {
return (

@@ -401,64 +529,42 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => {
);
}
- // when secrets is not loading and secrets list is empty
- const isDashboardSecretEmpty = !isSecretsLoading && false;
- // when using snapshot mode and snapshot is loading and snapshot list is empty
- const isSnapshotSecretEmtpy =
- isRollbackMode && !isSnapshotSecretsLoading && !snapshotSecret?.secrets?.length;
- const isSecretEmpty = (!isRollbackMode && isDashboardSecretEmpty) || isSnapshotSecretEmtpy;
-
const userAvailableEnvs = wsEnv?.filter(
({ isReadDenied, isWriteDenied }) => !isReadDenied || !isWriteDenied
);
return (
-