mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 21:29:31 +00:00
feat(infisical-pg): completed root folder level secret, secret import and folder
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
TGetUserProjectPermissionDTO,
|
||||
TOrgRole,
|
||||
TPermission,
|
||||
TProjectPermission,
|
||||
TProjectRole
|
||||
} from "./types";
|
||||
|
||||
@@ -50,7 +51,7 @@ const getProjectRoles = async (projectId: string) => {
|
||||
}>(`/api/ee/v1/workspace/${projectId}/roles`);
|
||||
return data.data.roles.map(({ permissions, ...el }) => ({
|
||||
...el,
|
||||
permissions: unpackRules(permissions as PackRule<TPermission>[])
|
||||
permissions: unpackRules(permissions as PackRule<TProjectPermission>[])
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export type TProjectRole = {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
description?: string;
|
||||
permissions: TPermission[];
|
||||
permissions: TProjectPermission[];
|
||||
};
|
||||
|
||||
export type TOrgRole = {
|
||||
@@ -67,13 +67,13 @@ export type TCreateProjectRoleDTO = {
|
||||
name: string;
|
||||
description?: string;
|
||||
slug: string;
|
||||
permissions: TPermission[];
|
||||
permissions: TProjectPermission[];
|
||||
};
|
||||
|
||||
export type TUpdateProjectRoleDTO = {
|
||||
projectId: string;
|
||||
id: string;
|
||||
} & Partial<Omit<TCreateOrgRoleDTO, "orgId">>;
|
||||
} & Partial<Omit<TCreateProjectRoleDTO, "orgId">>;
|
||||
|
||||
export type TDeleteProjectRoleDTO = {
|
||||
projectId: string;
|
||||
|
||||
@@ -20,25 +20,25 @@ import {
|
||||
} from "./types";
|
||||
|
||||
const queryKeys = {
|
||||
getSecretFolders: ({ workspaceId, environment, directory }: TGetProjectFoldersDTO) =>
|
||||
["secret-folders", { workspaceId, environment, directory }] as const
|
||||
getSecretFolders: ({ projectId, environment, path }: TGetProjectFoldersDTO) =>
|
||||
["secret-folders", { projectId, environment, path }] as const
|
||||
};
|
||||
|
||||
const fetchProjectFolders = async (workspaceId: string, environment: string, directory = "/") => {
|
||||
const fetchProjectFolders = async (projectId: string, environment: string, path = "/") => {
|
||||
const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", {
|
||||
params: {
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory
|
||||
path
|
||||
}
|
||||
});
|
||||
return data.folders;
|
||||
};
|
||||
|
||||
export const useGetProjectFolders = ({
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory = "/",
|
||||
path = "/",
|
||||
options = {}
|
||||
}: TGetProjectFoldersDTO & {
|
||||
options?: Omit<
|
||||
@@ -53,21 +53,21 @@ export const useGetProjectFolders = ({
|
||||
}) =>
|
||||
useQuery({
|
||||
...options,
|
||||
queryKey: queryKeys.getSecretFolders({ workspaceId, environment, directory }),
|
||||
enabled: Boolean(workspaceId) && Boolean(environment) && (options?.enabled ?? true),
|
||||
queryFn: async () => fetchProjectFolders(workspaceId, environment, directory)
|
||||
queryKey: queryKeys.getSecretFolders({ projectId, environment, path }),
|
||||
enabled: Boolean(projectId) && Boolean(environment) && (options?.enabled ?? true),
|
||||
queryFn: async () => fetchProjectFolders(projectId, environment, path)
|
||||
});
|
||||
|
||||
export const useGetFoldersByEnv = ({
|
||||
directory = "/",
|
||||
workspaceId,
|
||||
path = "/",
|
||||
projectId,
|
||||
environments
|
||||
}: TGetFoldersByEnvDTO) => {
|
||||
const folders = useQueries({
|
||||
queries: environments.map((environment) => ({
|
||||
queryKey: queryKeys.getSecretFolders({ workspaceId, environment, directory }),
|
||||
queryFn: async () => fetchProjectFolders(workspaceId, environment, directory),
|
||||
enabled: Boolean(workspaceId) && Boolean(environment)
|
||||
queryKey: queryKeys.getSecretFolders({ projectId, environment, path }),
|
||||
queryFn: async () => fetchProjectFolders(projectId, environment, path),
|
||||
enabled: Boolean(projectId) && Boolean(environment)
|
||||
}))
|
||||
});
|
||||
|
||||
@@ -105,15 +105,13 @@ export const useCreateFolder = () => {
|
||||
const { data } = await apiRequest.post("/api/v1/folders", dto);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { workspaceId, environment, directory }) => {
|
||||
onSuccess: (_, { projectId, environment, path }) => {
|
||||
queryClient.invalidateQueries(queryKeys.getSecretFolders({ projectId, environment, path }));
|
||||
queryClient.invalidateQueries(
|
||||
queryKeys.getSecretFolders({ workspaceId, environment, directory })
|
||||
secretSnapshotKeys.list({ workspaceId: projectId, environment, directory: path })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretSnapshotKeys.list({ workspaceId, environment, directory })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretSnapshotKeys.count({ workspaceId, environment, directory })
|
||||
secretSnapshotKeys.count({ workspaceId: projectId, environment, directory: path })
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -123,24 +121,22 @@ export const useUpdateFolder = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TUpdateFolderDTO>({
|
||||
mutationFn: async ({ directory = "/", folderName, name, environment, workspaceId }) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/folders/${folderName}`, {
|
||||
mutationFn: async ({ path = "/", folderId, name, environment, projectId }) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, {
|
||||
name,
|
||||
environment,
|
||||
workspaceId,
|
||||
directory
|
||||
projectId,
|
||||
path
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { workspaceId, environment, directory }) => {
|
||||
onSuccess: (_, { projectId, environment, path }) => {
|
||||
queryClient.invalidateQueries(queryKeys.getSecretFolders({ projectId, environment, path }));
|
||||
queryClient.invalidateQueries(
|
||||
queryKeys.getSecretFolders({ workspaceId, environment, directory })
|
||||
secretSnapshotKeys.list({ workspaceId: projectId, environment, directory: path })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretSnapshotKeys.list({ workspaceId, environment, directory })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretSnapshotKeys.count({ workspaceId, environment, directory })
|
||||
secretSnapshotKeys.count({ workspaceId: projectId, environment, directory: path })
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -150,25 +146,23 @@ export const useDeleteFolder = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TDeleteFolderDTO>({
|
||||
mutationFn: async ({ directory = "/", folderName, environment, workspaceId }) => {
|
||||
const { data } = await apiRequest.delete(`/api/v1/folders/${folderName}`, {
|
||||
mutationFn: async ({ path = "/", folderId, environment, projectId }) => {
|
||||
const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, {
|
||||
data: {
|
||||
environment,
|
||||
workspaceId,
|
||||
directory
|
||||
projectId,
|
||||
path
|
||||
}
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { directory = "/", workspaceId, environment }) => {
|
||||
onSuccess: (_, { path = "/", projectId, environment }) => {
|
||||
queryClient.invalidateQueries(queryKeys.getSecretFolders({ projectId, environment, path }));
|
||||
queryClient.invalidateQueries(
|
||||
queryKeys.getSecretFolders({ workspaceId, environment, directory })
|
||||
secretSnapshotKeys.list({ workspaceId: projectId, environment, directory: path })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretSnapshotKeys.list({ workspaceId, environment, directory })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretSnapshotKeys.count({ workspaceId, environment, directory })
|
||||
secretSnapshotKeys.count({ workspaceId: projectId, environment, directory: path })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,35 +4,35 @@ export type TSecretFolder = {
|
||||
};
|
||||
|
||||
export type TGetProjectFoldersDTO = {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type TGetFoldersByEnvDTO = {
|
||||
environments: string[];
|
||||
workspaceId: string;
|
||||
directory?: string;
|
||||
projectId: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type TCreateFolderDTO = {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
folderName: string;
|
||||
directory?: string;
|
||||
name: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type TUpdateFolderDTO = {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
name: string;
|
||||
folderName: string;
|
||||
directory?: string;
|
||||
folderId: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type TDeleteFolderDTO = {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
folderName: string;
|
||||
directory?: string;
|
||||
folderId: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
@@ -9,21 +9,21 @@ export const useCreateSecretImport = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TCreateSecretImportDTO>({
|
||||
mutationFn: async ({ secretImport, environment, workspaceId, directory }) => {
|
||||
mutationFn: async ({ import: secretImport, environment, projectId, path }) => {
|
||||
const { data } = await apiRequest.post("/api/v1/secret-imports", {
|
||||
secretImport,
|
||||
import: secretImport,
|
||||
environment,
|
||||
workspaceId,
|
||||
directory
|
||||
projectId,
|
||||
path
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { workspaceId, environment, directory }) => {
|
||||
onSuccess: (_, { environment, projectId, path }) => {
|
||||
queryClient.invalidateQueries(
|
||||
secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory })
|
||||
secretImportKeys.getProjectSecretImports({ projectId, environment, path })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory })
|
||||
secretImportKeys.getSecretImportSecrets({ projectId, environment, path })
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -33,21 +33,21 @@ export const useUpdateSecretImport = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TUpdateSecretImportDTO>({
|
||||
mutationFn: async ({ environment, workspaceId, directory, secretImports, id }) => {
|
||||
const { data } = await apiRequest.put(`/api/v1/secret-imports/${id}`, {
|
||||
secretImports,
|
||||
mutationFn: async ({ environment, import: secretImports, projectId, path, id }) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/secret-imports/${id}`, {
|
||||
import: secretImports,
|
||||
environment,
|
||||
workspaceId,
|
||||
directory
|
||||
path,
|
||||
projectId
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { workspaceId, environment, directory }) => {
|
||||
onSuccess: (_, { environment, projectId, path }) => {
|
||||
queryClient.invalidateQueries(
|
||||
secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory })
|
||||
secretImportKeys.getProjectSecretImports({ projectId, path, environment })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory })
|
||||
secretImportKeys.getSecretImportSecrets({ environment, path, projectId })
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -57,21 +57,22 @@ export const useDeleteSecretImport = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{}, {}, TDeleteSecretImportDTO>({
|
||||
mutationFn: async ({ id, secretImportEnv, secretImportPath }) => {
|
||||
mutationFn: async ({ id, projectId, path, environment }) => {
|
||||
const { data } = await apiRequest.delete(`/api/v1/secret-imports/${id}`, {
|
||||
data: {
|
||||
secretImportPath,
|
||||
secretImportEnv
|
||||
projectId,
|
||||
path,
|
||||
environment
|
||||
}
|
||||
});
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { workspaceId, environment, directory }) => {
|
||||
onSuccess: (_, { projectId, environment, path }) => {
|
||||
queryClient.invalidateQueries(
|
||||
secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory })
|
||||
secretImportKeys.getProjectSecretImports({ projectId, environment, path })
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
secretImportKeys.getSecretImportSecrets({ workspaceId, environment, directory })
|
||||
secretImportKeys.getSecretImportSecrets({ projectId, environment, path })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,44 +7,44 @@ import {
|
||||
} from "@app/components/utilities/cryptography/crypto";
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TGetImportedSecrets, TGetSecretImports, TImportedSecrets, TSecretImports } from "./types";
|
||||
import { TGetImportedSecrets, TGetSecretImports, TImportedSecrets, TSecretImport } from "./types";
|
||||
|
||||
export const secretImportKeys = {
|
||||
getProjectSecretImports: ({ environment, workspaceId, directory }: TGetSecretImports) =>
|
||||
[{ workspaceId, directory, environment }, "secrets-imports"] as const,
|
||||
getProjectSecretImports: ({ environment, projectId, path }: TGetSecretImports) =>
|
||||
[{ projectId, path, environment }, "secrets-imports"] as const,
|
||||
getSecretImportSecrets: ({
|
||||
workspaceId,
|
||||
environment,
|
||||
directory
|
||||
projectId,
|
||||
path
|
||||
}: Omit<TGetImportedSecrets, "decryptFileKey">) =>
|
||||
[{ workspaceId, environment, directory }, "secrets-import-sec"] as const
|
||||
[{ environment, path, projectId }, "secrets-import-sec"] as const
|
||||
};
|
||||
|
||||
const fetchSecretImport = async ({ workspaceId, environment, directory }: TGetSecretImports) => {
|
||||
const { data } = await apiRequest.get<{ secretImport: TSecretImports }>(
|
||||
const fetchSecretImport = async ({ projectId, environment, path = "/" }: TGetSecretImports) => {
|
||||
const { data } = await apiRequest.get<{ secretImports: TSecretImport[] }>(
|
||||
"/api/v1/secret-imports",
|
||||
{
|
||||
params: {
|
||||
workspaceId,
|
||||
projectId,
|
||||
environment,
|
||||
directory
|
||||
path
|
||||
}
|
||||
}
|
||||
);
|
||||
return data.secretImport;
|
||||
return data.secretImports;
|
||||
};
|
||||
|
||||
export const useGetSecretImports = ({
|
||||
workspaceId,
|
||||
environment,
|
||||
directory = "/",
|
||||
path = "/",
|
||||
projectId,
|
||||
options = {}
|
||||
}: TGetSecretImports & {
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TSecretImports,
|
||||
TSecretImport[],
|
||||
unknown,
|
||||
TSecretImports,
|
||||
TSecretImport[],
|
||||
ReturnType<typeof secretImportKeys.getProjectSecretImports>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
@@ -52,9 +52,9 @@ export const useGetSecretImports = ({
|
||||
}) =>
|
||||
useQuery({
|
||||
...options,
|
||||
queryKey: secretImportKeys.getProjectSecretImports({ workspaceId, environment, directory }),
|
||||
enabled: Boolean(workspaceId) && Boolean(environment) && (options?.enabled ?? true),
|
||||
queryFn: () => fetchSecretImport({ workspaceId, environment, directory })
|
||||
queryKey: secretImportKeys.getProjectSecretImports({ environment, projectId, path }),
|
||||
enabled: Boolean(projectId) && Boolean(environment) && (options?.enabled ?? true),
|
||||
queryFn: () => fetchSecretImport({ path, projectId, environment })
|
||||
});
|
||||
|
||||
const fetchImportedSecrets = async (
|
||||
@@ -76,10 +76,10 @@ const fetchImportedSecrets = async (
|
||||
};
|
||||
|
||||
export const useGetImportedSecrets = ({
|
||||
workspaceId,
|
||||
environment,
|
||||
decryptFileKey,
|
||||
directory,
|
||||
path,
|
||||
projectId,
|
||||
options = {}
|
||||
}: TGetImportedSecrets & {
|
||||
options?: Omit<
|
||||
@@ -94,16 +94,16 @@ export const useGetImportedSecrets = ({
|
||||
}) =>
|
||||
useQuery({
|
||||
enabled:
|
||||
Boolean(workspaceId) &&
|
||||
Boolean(projectId) &&
|
||||
Boolean(environment) &&
|
||||
Boolean(decryptFileKey) &&
|
||||
(options?.enabled ?? true),
|
||||
queryKey: secretImportKeys.getSecretImportSecrets({
|
||||
workspaceId,
|
||||
environment,
|
||||
directory
|
||||
path,
|
||||
projectId
|
||||
}),
|
||||
queryFn: () => fetchImportedSecrets(workspaceId, environment, directory),
|
||||
queryFn: () => fetchImportedSecrets(projectId, environment, path),
|
||||
select: useCallback(
|
||||
(data: TImportedSecrets[]) => {
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { UserWsKeyPair } from "../keys/types";
|
||||
import { EncryptedSecret } from "../secrets/types";
|
||||
|
||||
export type TSecretImports = {
|
||||
export type TSecretImport = {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
folderId: string;
|
||||
imports: Array<{ environment: string; secretPath: string }>;
|
||||
importPath: string;
|
||||
importEnv: {
|
||||
name: string;
|
||||
slug: string;
|
||||
id: string;
|
||||
};
|
||||
position: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
@@ -19,44 +23,43 @@ export type TImportedSecrets = {
|
||||
};
|
||||
|
||||
export type TGetSecretImports = {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
export type TGetImportedSecrets = {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
path?: string;
|
||||
decryptFileKey: UserWsKeyPair;
|
||||
};
|
||||
|
||||
export type TCreateSecretImportDTO = {
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
secretImport: {
|
||||
path?: string;
|
||||
import: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
path: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TUpdateSecretImportDTO = {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
secretImports: Array<{
|
||||
path?: string;
|
||||
import: Partial<{
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
path: string;
|
||||
position: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TDeleteSecretImportDTO = {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
directory?: string;
|
||||
secretImportPath: string;
|
||||
secretImportEnv: string;
|
||||
path?: string;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export type {
|
||||
} from "./secretApprovalRequest/types";
|
||||
export { ApprovalStatus, CommitType } from "./secretApprovalRequest/types";
|
||||
export type { TSecretFolder } from "./secretFolders/types";
|
||||
export type { TImportedSecrets, TSecretImports } from "./secretImports/types";
|
||||
export type { TImportedSecrets, TSecretImport } from "./secretImports/types";
|
||||
export type {
|
||||
TGetSecretRotationProviders,
|
||||
TProviderTemplate,
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ export const ProjectRoleList = ({ onSelectRole }: Props) => {
|
||||
</THead>
|
||||
<TBody>
|
||||
{isRolesLoading && <TableSkeleton columns={4} innerKey="org-roles" />}
|
||||
{(roles as TProjectRole[])?.map((role) => {
|
||||
{roles?.map((role) => {
|
||||
const { id, name, slug } = role;
|
||||
const isNonMutatable = ["admin", "member", "viewer", "no-access"].includes(slug);
|
||||
|
||||
|
||||
+3
-3
@@ -118,7 +118,7 @@ const multiEnvForm2Api = (
|
||||
const isFullAccess = PERMISSION_ACTIONS.every((action) => formVal?.all?.[action]);
|
||||
// if any of them is set in all push it without any condition
|
||||
PERMISSION_ACTIONS.forEach((action) => {
|
||||
if (formVal?.all?.[action]) permissions.push({ action, subject });
|
||||
if (formVal?.all?.[action]) permissions.push({ action, subject: [subject] });
|
||||
});
|
||||
|
||||
if (!isFullAccess) {
|
||||
@@ -139,7 +139,7 @@ const multiEnvForm2Api = (
|
||||
if (formVal[slug]?.secretPath)
|
||||
conditions.secretPath = { $glob: formVal?.[slug]?.secretPath };
|
||||
|
||||
permissions.push({ action, subject, conditions });
|
||||
permissions.push({ action, subject: [subject], conditions });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -156,7 +156,7 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
|
||||
} else {
|
||||
Object.entries(actions).forEach(([action, isAllowed]) => {
|
||||
if (isAllowed) {
|
||||
permissions.push({ subject: rule, action });
|
||||
permissions.push({ subject: [rule], action });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,9 +88,9 @@ export const SecretMainPage = () => {
|
||||
});
|
||||
// fetch folders
|
||||
const { data: folders, isLoading: isFoldersLoading } = useGetProjectFolders({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
directory: secretPath
|
||||
path: secretPath
|
||||
});
|
||||
// fetch secret imports
|
||||
const {
|
||||
@@ -98,9 +98,9 @@ export const SecretMainPage = () => {
|
||||
isLoading: isSecretImportsLoading,
|
||||
isFetching: isSecretImportsFetching
|
||||
} = useGetSecretImports({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
directory: secretPath,
|
||||
path: secretPath,
|
||||
options: {
|
||||
enabled: canReadSecret
|
||||
}
|
||||
@@ -108,15 +108,15 @@ export const SecretMainPage = () => {
|
||||
|
||||
// fetch imported secrets to show user the overriden ones
|
||||
const { data: importedSecrets } = useGetImportedSecrets({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
decryptFileKey: decryptFileKey!,
|
||||
directory: secretPath,
|
||||
path: secretPath,
|
||||
options: {
|
||||
enabled: canReadSecret
|
||||
}
|
||||
});
|
||||
// fetch tags
|
||||
// fech tags
|
||||
const { data: tags } = useGetWsTags(canReadSecret ? workspaceId : "");
|
||||
|
||||
const { data: boardPolicy } = useGetSecretApprovalPolicyOfABoard({
|
||||
@@ -146,7 +146,7 @@ export const SecretMainPage = () => {
|
||||
isPaused: !canDoReadRollback
|
||||
});
|
||||
|
||||
const isNotEmtpy = Boolean(secrets?.length || folders?.length || secretImports?.imports?.length);
|
||||
const isNotEmtpy = Boolean(secrets?.length || folders?.length || secretImports?.length);
|
||||
|
||||
const handleSortToggle = () =>
|
||||
setSortDir((state) => (state === SortDir.ASC ? SortDir.DESC : SortDir.ASC));
|
||||
|
||||
@@ -112,10 +112,10 @@ export const ActionBar = ({
|
||||
const handleFolderCreate = async (folderName: string) => {
|
||||
try {
|
||||
await createFolder({
|
||||
folderName,
|
||||
directory: secretPath,
|
||||
name: folderName,
|
||||
path: secretPath,
|
||||
environment,
|
||||
workspaceId
|
||||
projectId: workspaceId
|
||||
});
|
||||
handlePopUpClose("addFolder");
|
||||
createNotification({
|
||||
|
||||
@@ -66,11 +66,11 @@ export const CreateSecretImportForm = ({
|
||||
try {
|
||||
await createSecretImport({
|
||||
environment,
|
||||
workspaceId,
|
||||
directory: secretPath,
|
||||
secretImport: {
|
||||
projectId: workspaceId,
|
||||
path: secretPath,
|
||||
import: {
|
||||
environment: importedEnv,
|
||||
secretPath: importedSecPath
|
||||
path: importedSecPath
|
||||
}
|
||||
});
|
||||
onClose();
|
||||
|
||||
@@ -42,12 +42,13 @@ export const FolderListView = ({
|
||||
|
||||
const handleFolderUpdate = async (newFolderName: string) => {
|
||||
try {
|
||||
const { id: folderId } = popUp.updateFolder.data as TSecretFolder;
|
||||
await updateFolder({
|
||||
folderName: popUp.updateFolder.data as string,
|
||||
folderId,
|
||||
name: newFolderName,
|
||||
directory: secretPath,
|
||||
path: secretPath,
|
||||
environment,
|
||||
workspaceId
|
||||
projectId: workspaceId
|
||||
});
|
||||
handlePopUpClose("updateFolder");
|
||||
createNotification({
|
||||
@@ -65,11 +66,12 @@ export const FolderListView = ({
|
||||
|
||||
const handleFolderDelete = async () => {
|
||||
try {
|
||||
const { id: folderId } = popUp.deleteFolder.data as TSecretFolder;
|
||||
await deleteFolder({
|
||||
folderName: popUp.deleteFolder.data as string,
|
||||
directory: secretPath,
|
||||
folderId,
|
||||
path: secretPath,
|
||||
environment,
|
||||
workspaceId
|
||||
projectId: workspaceId
|
||||
});
|
||||
handlePopUpClose("deleteFolder");
|
||||
createNotification({
|
||||
@@ -106,13 +108,13 @@ export const FolderListView = ({
|
||||
.map(({ name, id }) => (
|
||||
<div
|
||||
key={id}
|
||||
className="flex group border-b border-mineshaft-600 hover:bg-mineshaft-700 cursor-pointer"
|
||||
className="group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="w-11 px-5 py-3 text-yellow-700 flex items-center">
|
||||
<div className="flex w-11 items-center px-5 py-3 text-yellow-700">
|
||||
<FontAwesomeIcon icon={faFolder} />
|
||||
</div>
|
||||
<div
|
||||
className="flex-grow px-4 py-3 flex items-center"
|
||||
className="flex flex-grow items-center px-4 py-3"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(evt) => {
|
||||
@@ -122,7 +124,7 @@ export const FolderListView = ({
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
<div className="px-3 py-3 flex items-center space-x-4 border-l border-mineshaft-600">
|
||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
@@ -134,8 +136,8 @@ export const FolderListView = ({
|
||||
ariaLabel="edit-folder"
|
||||
variant="plain"
|
||||
size="sm"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
onClick={() => handlePopUpOpen("updateFolder", name)}
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={() => handlePopUpOpen("updateFolder", { id, name })}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencilSquare} size="lg" />
|
||||
@@ -153,8 +155,8 @@ export const FolderListView = ({
|
||||
ariaLabel="delete-folder"
|
||||
variant="plain"
|
||||
size="md"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
onClick={() => handlePopUpOpen("deleteFolder", name)}
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={() => handlePopUpOpen("deleteFolder", { id, name })}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} size="lg" />
|
||||
@@ -171,14 +173,14 @@ export const FolderListView = ({
|
||||
<ModalContent title="Edit Folder">
|
||||
<FolderForm
|
||||
isEdit
|
||||
defaultFolderName={popUp.updateFolder.data as string}
|
||||
defaultFolderName={(popUp.updateFolder?.data as TSecretFolder)?.name}
|
||||
onUpdateFolder={handleFolderUpdate}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteFolder.isOpen}
|
||||
deleteKey={popUp.deleteFolder?.data as string}
|
||||
deleteKey={(popUp.deleteFolder?.data as TSecretFolder)?.name}
|
||||
title="Do you want to delete this folder?"
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteFolder", isOpen)}
|
||||
onDeleteApproved={handleFolderDelete}
|
||||
|
||||
+19
-19
@@ -12,17 +12,18 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { EmptyState, IconButton, SecretInput, TableContainer } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
type Props = {
|
||||
onDelete: (environment: string, secretPath: string) => void;
|
||||
onDelete: () => void;
|
||||
environment: string;
|
||||
secretPath?: string;
|
||||
importedEnv: string;
|
||||
importedSecPath: string;
|
||||
importEnvName: string;
|
||||
importEnvPath: string;
|
||||
importedSecrets: { key: string; value: string; overriden: { env: string; secretPath: string } }[];
|
||||
searchTerm: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
// to show the environment and folder icon
|
||||
@@ -31,7 +32,7 @@ export const EnvFolderIcon = ({ env, secretPath }: { env: string; secretPath: st
|
||||
<div style={{ minWidth: "96px" }}>{env || "-"}</div>
|
||||
{secretPath && (
|
||||
<div className="inline-flex items-center space-x-2 border-l border-mineshaft-600 pl-2">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-green-700 text-md" />
|
||||
<FontAwesomeIcon icon={faFolder} className="text-md text-green-700" />
|
||||
<span>{secretPath}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -39,9 +40,10 @@ export const EnvFolderIcon = ({ env, secretPath }: { env: string; secretPath: st
|
||||
);
|
||||
|
||||
export const SecretImportItem = ({
|
||||
importedEnv,
|
||||
importedSecPath,
|
||||
onDelete,
|
||||
id,
|
||||
importEnvName,
|
||||
importEnvPath,
|
||||
importedSecrets = [],
|
||||
searchTerm = "",
|
||||
secretPath,
|
||||
@@ -49,10 +51,8 @@ export const SecretImportItem = ({
|
||||
}: Props) => {
|
||||
const [isExpanded, setIsExpanded] = useToggle();
|
||||
const { attributes, listeners, transform, transition, setNodeRef, isDragging } = useSortable({
|
||||
id: `${importedEnv}-${importedSecPath}`
|
||||
id
|
||||
});
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const rowEnv = currentWorkspace?.environments?.find(({ slug }) => slug === importedEnv);
|
||||
|
||||
useEffect(() => {
|
||||
const filteredSecrets = importedSecrets.filter((secret) =>
|
||||
@@ -80,7 +80,7 @@ export const SecretImportItem = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex group border-b border-mineshaft-600 hover:bg-mineshaft-700 cursor-pointer"
|
||||
className="group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
ref={setNodeRef}
|
||||
tabIndex={0}
|
||||
@@ -88,13 +88,13 @@ export const SecretImportItem = ({
|
||||
onClick={() => setIsExpanded.toggle()}
|
||||
onKeyDown={() => setIsExpanded.toggle()}
|
||||
>
|
||||
<div className="w-12 px-4 py-2 flex items-center text-green-700">
|
||||
<div className="flex w-12 items-center px-4 py-2 text-green-700">
|
||||
<FontAwesomeIcon icon={faFileImport} />
|
||||
</div>
|
||||
<div className="flex-grow px-4 py-2 flex items-center">
|
||||
<EnvFolderIcon env={rowEnv?.name || ""} secretPath={importedSecPath} />
|
||||
<div className="flex flex-grow items-center px-4 py-2">
|
||||
<EnvFolderIcon env={importEnvName || ""} secretPath={importEnvPath} />
|
||||
</div>
|
||||
<div className="px-4 py-2 flex items-center space-x-4 border-l border-mineshaft-600">
|
||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-4 py-2">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
@@ -107,7 +107,7 @@ export const SecretImportItem = ({
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="expand"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
isDisabled={!isAllowed}
|
||||
@@ -128,10 +128,10 @@ export const SecretImportItem = ({
|
||||
variant="plain"
|
||||
colorSchema="danger"
|
||||
ariaLabel="delete"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
onDelete(importedEnv, importedSecPath);
|
||||
onDelete();
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
@@ -167,7 +167,7 @@ export const SecretImportItem = ({
|
||||
{importedSecrets
|
||||
.filter((secret) => secret.key.toUpperCase().includes(searchTerm.toUpperCase()))
|
||||
.map(({ key, value, overriden }, index) => (
|
||||
<tr key={`${importedEnv}-${importedSecPath}-${key}-${index + 1}`}>
|
||||
<tr key={`${id}-${key}-${index + 1}`}>
|
||||
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
|
||||
{key}
|
||||
</td>
|
||||
|
||||
+47
-60
@@ -17,7 +17,7 @@ import { DeleteActionModal } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteSecretImport, useUpdateSecretImport } from "@app/hooks/api";
|
||||
import { TSecretImports } from "@app/hooks/api/secretImports/types";
|
||||
import { TSecretImport } from "@app/hooks/api/secretImports/types";
|
||||
import { DecryptedSecret } from "@app/hooks/api/types";
|
||||
|
||||
import { SecretImportItem } from "./SecretImportItem";
|
||||
@@ -76,17 +76,15 @@ type Props = {
|
||||
environment: string;
|
||||
workspaceId: string;
|
||||
secretPath?: string;
|
||||
secretImports?: TSecretImports;
|
||||
secretImports?: TSecretImport[];
|
||||
isFetching?: boolean;
|
||||
secrets?: DecryptedSecret[];
|
||||
importedSecrets?: TImportedSecrets;
|
||||
searchTerm: string;
|
||||
};
|
||||
|
||||
type TDeleteSecretImport = { environment: string; secretPath: string };
|
||||
|
||||
export const SecretImportListView = ({
|
||||
secretImports,
|
||||
secretImports = [],
|
||||
environment,
|
||||
workspaceId,
|
||||
secretPath,
|
||||
@@ -107,21 +105,11 @@ export const SecretImportListView = ({
|
||||
useSensor(KeyboardSensor, {})
|
||||
);
|
||||
|
||||
const [items, setItems] = useState(
|
||||
(secretImports?.imports || [])?.map((dto) => ({
|
||||
id: `${dto.environment}-${dto.secretPath}`,
|
||||
...dto
|
||||
}))
|
||||
);
|
||||
const [items, setItems] = useState(secretImports);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetching) {
|
||||
setItems(
|
||||
(secretImports?.imports || [])?.map((dto) => ({
|
||||
id: `${dto.environment}-${dto.secretPath}`,
|
||||
...dto
|
||||
}))
|
||||
);
|
||||
setItems(secretImports);
|
||||
}
|
||||
}, [isFetching]);
|
||||
|
||||
@@ -129,24 +117,19 @@ export const SecretImportListView = ({
|
||||
const { mutate: updateSecretImport } = useUpdateSecretImport();
|
||||
|
||||
const handleSecretImportDelete = async () => {
|
||||
const { environment: importEnv, secretPath: impSecPath } = popUp.deleteSecretImport
|
||||
?.data as TDeleteSecretImport;
|
||||
const { id: secretImportId } = popUp.deleteSecretImport?.data as { id: string };
|
||||
try {
|
||||
if (secretImports?.id) {
|
||||
await deleteSecretImport({
|
||||
workspaceId,
|
||||
environment,
|
||||
directory: secretPath,
|
||||
id: secretImports?.id,
|
||||
secretImportEnv: importEnv,
|
||||
secretImportPath: impSecPath
|
||||
});
|
||||
handlePopUpClose("deleteSecretImport");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully removed secret link"
|
||||
});
|
||||
}
|
||||
await deleteSecretImport({
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
path: secretPath,
|
||||
id: secretImportId
|
||||
});
|
||||
handlePopUpClose("deleteSecretImport");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully removed secret link"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
@@ -163,11 +146,13 @@ export const SecretImportListView = ({
|
||||
const newImportOrder = arrayMove(items, oldIndex, newIndex);
|
||||
setItems(newImportOrder);
|
||||
updateSecretImport({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
directory: secretPath,
|
||||
id: secretImports?.id || "",
|
||||
secretImports: newImportOrder
|
||||
path: secretPath,
|
||||
id: active.id as string,
|
||||
import: {
|
||||
position: newIndex + 1
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -181,26 +166,28 @@ export const SecretImportListView = ({
|
||||
modifiers={[restrictToVerticalAxis]}
|
||||
>
|
||||
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||
{items?.map(({ secretPath: importedSecPath, environment: importedEnv }) => (
|
||||
<SecretImportItem
|
||||
searchTerm={searchTerm}
|
||||
key={`${importedEnv}-${importedSecPath}`}
|
||||
importedEnv={importedEnv}
|
||||
importedSecPath={importedSecPath}
|
||||
importedSecrets={computeImportedSecretRows(
|
||||
importedEnv,
|
||||
importedSecPath,
|
||||
importedSecrets,
|
||||
secrets,
|
||||
environments
|
||||
)}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
onDelete={(env, secPath) =>
|
||||
handlePopUpOpen("deleteSecretImport", { environment: env, secretPath: secPath })
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{items?.map((item) => {
|
||||
const { importPath, importEnv, id } = item;
|
||||
return (
|
||||
<SecretImportItem
|
||||
searchTerm={searchTerm}
|
||||
key={`imported-env-${id}`}
|
||||
id={id}
|
||||
importEnvPath={importPath}
|
||||
importEnvName={importEnv.name}
|
||||
importedSecrets={computeImportedSecretRows(
|
||||
importEnv.slug,
|
||||
importPath,
|
||||
importedSecrets,
|
||||
secrets,
|
||||
environments
|
||||
)}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
onDelete={() => handlePopUpOpen("deleteSecretImport", item)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
<DeleteActionModal
|
||||
@@ -208,8 +195,8 @@ export const SecretImportListView = ({
|
||||
deleteKey="unlink"
|
||||
title="Do you want to remove this secret import?"
|
||||
subTitle={`This will unlink secrets from environment ${
|
||||
(popUp.deleteSecretImport?.data as TDeleteSecretImport)?.environment
|
||||
} of path ${(popUp.deleteSecretImport?.data as TDeleteSecretImport)?.secretPath}?`}
|
||||
(popUp.deleteSecretImport?.data as TSecretImport)?.importEnv
|
||||
} of path ${(popUp.deleteSecretImport?.data as TSecretImport)?.importPath}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteSecretImport", isOpen)}
|
||||
onDeleteApproved={handleSecretImportDelete}
|
||||
/>
|
||||
|
||||
@@ -208,8 +208,8 @@ export const SecretListView = ({
|
||||
} = modSecret;
|
||||
const hasKeyChanged = oldKey !== key;
|
||||
|
||||
const tagIds = tags.map(({ id }) => id);
|
||||
const oldTagIds = orgSecret.tags.map(({ id }) => id);
|
||||
const tagIds = tags?.map(({ id }) => id);
|
||||
const oldTagIds = (orgSecret?.tags || []).map(({ id }) => id);
|
||||
const isSameTags = JSON.stringify(tagIds) === JSON.stringify(oldTagIds);
|
||||
const isSharedSecUnchanged =
|
||||
(
|
||||
|
||||
@@ -99,9 +99,9 @@ export const SecretOverviewPage = () => {
|
||||
decryptFileKey: latestFileKey!
|
||||
});
|
||||
const { folders, folderNames, isFolderPresentInEnv } = useGetFoldersByEnv({
|
||||
workspaceId,
|
||||
environments: userAvailableEnvs.map(({ slug }) => slug),
|
||||
directory: secretPath
|
||||
projectId: workspaceId,
|
||||
path: secretPath,
|
||||
environments: userAvailableEnvs.map(({ slug }) => slug)
|
||||
});
|
||||
|
||||
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
||||
@@ -119,10 +119,10 @@ export const SecretOverviewPage = () => {
|
||||
const folderName = pathSegment.at(-1);
|
||||
if (folderName && parentPath) {
|
||||
await createFolder({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
path: secretPath,
|
||||
environment: env,
|
||||
directory: parentPath,
|
||||
folderName
|
||||
name: folderName
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -216,10 +216,10 @@ export const SecretOverviewPage = () => {
|
||||
const folderName = path.at(-1);
|
||||
if (folderName && directory) {
|
||||
await createFolder({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment: slug,
|
||||
directory,
|
||||
folderName
|
||||
path: secretPath,
|
||||
name: folderName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user