mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
feat: added handling of folder/secret deletion
This commit is contained in:
@@ -94,7 +94,21 @@ export const useGetFoldersByEnv = ({
|
|||||||
[(folders || []).map((folder) => folder.data)]
|
[(folders || []).map((folder) => folder.data)]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { folders, folderNames, isFolderPresentInEnv };
|
const getFolderByNameAndEnv = useCallback(
|
||||||
|
(name: string, env: string) => {
|
||||||
|
const selectedEnvIndex = environments.indexOf(env);
|
||||||
|
if (selectedEnvIndex !== -1) {
|
||||||
|
return folders?.[selectedEnvIndex]?.data?.find(
|
||||||
|
({ name: folderName }) => folderName === name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
[(folders || []).map((folder) => folder.data)]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { folders, folderNames, isFolderPresentInEnv, getFolderByNameAndEnv };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCreateFolder = () => {
|
export const useCreateFolder = () => {
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ export const SecretOverviewPage = () => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
decryptFileKey: latestFileKey!
|
decryptFileKey: latestFileKey!
|
||||||
});
|
});
|
||||||
const { folders, folderNames, isFolderPresentInEnv } = useGetFoldersByEnv({
|
|
||||||
|
const { folders, folderNames, isFolderPresentInEnv, getFolderByNameAndEnv } = useGetFoldersByEnv({
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
environments: userAvailableEnvs.map(({ slug }) => slug)
|
environments: userAvailableEnvs.map(({ slug }) => slug)
|
||||||
@@ -548,7 +549,11 @@ export const SecretOverviewPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SelectionPanel secretPath={secretPath} />
|
<SelectionPanel
|
||||||
|
secretPath={secretPath}
|
||||||
|
getSecretByKey={getSecretByKey}
|
||||||
|
getFolderByNameAndEnv={getFolderByNameAndEnv}
|
||||||
|
/>
|
||||||
<div className="thin-scrollbar mt-4" ref={parentTableRef}>
|
<div className="thin-scrollbar mt-4" ref={parentTableRef}>
|
||||||
<TableContainer className="max-h-[calc(100vh-250px)] overflow-y-auto">
|
<TableContainer className="max-h-[calc(100vh-250px)] overflow-y-auto">
|
||||||
<Table>
|
<Table>
|
||||||
|
|||||||
@@ -3,17 +3,27 @@ import { faMinusSquare, faTrash } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, DeleteActionModal, IconButton, Tooltip } from "@app/components/v2";
|
import { Button, DeleteActionModal, IconButton, Tooltip } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProjectPermission,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api";
|
||||||
|
import { DecryptedSecret, TDeleteSecretBatchDTO, TSecretFolder } from "@app/hooks/api/types";
|
||||||
|
|
||||||
import { useSelectedEntries, useSelectedEntryActions } from "../../SecretOverviewPage.store";
|
import { useSelectedEntries, useSelectedEntryActions } from "../../SecretOverviewPage.store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
|
getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined;
|
||||||
|
getFolderByNameAndEnv: (name: string, env: string) => TSecretFolder | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectionPanel = ({ secretPath }: Props) => {
|
export const SelectionPanel = ({ getFolderByNameAndEnv, getSecretByKey, secretPath }: Props) => {
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
|
|
||||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||||
@@ -25,6 +35,12 @@ export const SelectionPanel = ({ secretPath }: Props) => {
|
|||||||
const selectedCount =
|
const selectedCount =
|
||||||
Object.keys(selectedEntries.folder).length + Object.keys(selectedEntries.secret).length;
|
Object.keys(selectedEntries.folder).length + Object.keys(selectedEntries.secret).length;
|
||||||
|
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
|
const userAvailableEnvs = currentWorkspace?.environments || [];
|
||||||
|
const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch();
|
||||||
|
const { mutateAsync: deleteFolder } = useDeleteFolder();
|
||||||
|
|
||||||
const isMultiSelectActive = selectedCount > 0;
|
const isMultiSelectActive = selectedCount > 0;
|
||||||
|
|
||||||
// TODO: REVISIT RBAC
|
// TODO: REVISIT RBAC
|
||||||
@@ -34,7 +50,63 @@ export const SelectionPanel = ({ secretPath }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleBulkDelete = async () => {
|
const handleBulkDelete = async () => {
|
||||||
|
const promises = userAvailableEnvs.map(async (env) => {
|
||||||
|
await Promise.all(
|
||||||
|
Object.keys(selectedEntries.folder).map(async (folderName) => {
|
||||||
|
const folder = getFolderByNameAndEnv(folderName, env.slug);
|
||||||
|
if (folder) {
|
||||||
|
await deleteFolder({
|
||||||
|
folderId: folder?.id,
|
||||||
|
path: secretPath,
|
||||||
|
environment: env.slug,
|
||||||
|
projectId: workspaceId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretsToDelete = Object.keys(selectedEntries.secret).reduce(
|
||||||
|
(accum: TDeleteSecretBatchDTO["secrets"], secretName) => {
|
||||||
|
const entry = getSecretByKey(env.slug, secretName);
|
||||||
|
if (entry) {
|
||||||
|
return [
|
||||||
|
...accum,
|
||||||
|
{
|
||||||
|
secretName: entry.key,
|
||||||
|
type: "shared" as "shared"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return accum;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (secretsToDelete.length > 0) {
|
||||||
|
await deleteBatchSecretV3({
|
||||||
|
secretPath,
|
||||||
|
workspaceId,
|
||||||
|
environment: env.slug,
|
||||||
|
secrets: secretsToDelete
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(promises);
|
||||||
|
const areEntriesDeleted = results.some((result) => result.status === "fulfilled");
|
||||||
|
if (areEntriesDeleted) {
|
||||||
handlePopUpClose("bulkDeleteEntries");
|
handlePopUpClose("bulkDeleteEntries");
|
||||||
|
resetSelectedEntries();
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully deleted selected secrets and folders"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to delete selected secrets and folders"
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user