mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 15:41:27 +00:00
feat(folders): implemented ui for folders in dashboard
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { useCreateFolder, useDeleteFolder, useGetProjectFolders, useUpdateFolder } from './queries';
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ export type TWorkspaceSecretSnapshot = {
|
||||
|
||||
export type TSnapshotSecret = Omit<TWorkspaceSecretSnapshot, 'secretVersions'> & {
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user