mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 00:17:46 +00:00
feat(secrets-ui): Improve SecretDashboard table to support multiple changes on a single commit
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -46,12 +47,14 @@ import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard";
|
||||
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
|
||||
import { useGetFolderCommitsCount } from "@app/hooks/api/folderCommits";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { useCreateCommit } from "@app/hooks/api/secrets/mutations";
|
||||
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
||||
import { SecretRotationListView } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView";
|
||||
|
||||
import { SecretTableResourceCount } from "../OverviewPage/components/SecretTableResourceCount";
|
||||
import { SecretV2MigrationSection } from "../OverviewPage/components/SecretV2MigrationSection";
|
||||
import { ActionBar } from "./components/ActionBar";
|
||||
import { CommitForm } from "./components/CommitForm";
|
||||
import { CreateSecretForm } from "./components/CreateSecretForm";
|
||||
import { DynamicSecretListView } from "./components/DynamicSecretListView";
|
||||
import { FolderListView } from "./components/FolderListView";
|
||||
@@ -61,8 +64,11 @@ import { SecretImportListView } from "./components/SecretImportListView";
|
||||
import { SecretListView, SecretNoAccessListView } from "./components/SecretListView";
|
||||
import { SnapshotView } from "./components/SnapshotView";
|
||||
import {
|
||||
PendingChanges,
|
||||
PopUpNames,
|
||||
StoreProvider,
|
||||
useBatchMode,
|
||||
useBatchModeActions,
|
||||
usePopUpAction,
|
||||
usePopUpState,
|
||||
useSelectedSecretActions,
|
||||
@@ -90,8 +96,11 @@ const Page = () => {
|
||||
});
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
const { mutateAsync: createCommit } = useCreateCommit();
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const { isBatchMode, pendingChanges } = useBatchMode();
|
||||
const { loadPendingChanges, setExistingKeys } = useBatchModeActions();
|
||||
|
||||
const {
|
||||
offset,
|
||||
@@ -121,6 +130,22 @@ const Page = () => {
|
||||
const projectSlug = currentWorkspace?.slug || "";
|
||||
const secretPath = (routerQueryParams.secretPath as string) || "/";
|
||||
|
||||
useEffect(() => {
|
||||
if (isBatchMode && workspaceId && environment && secretPath) {
|
||||
loadPendingChanges({ workspaceId, environment, secretPath });
|
||||
}
|
||||
}, [isBatchMode, workspaceId, environment, secretPath, loadPendingChanges]);
|
||||
|
||||
const handleCreateCommit = (changes: PendingChanges, message: string) => {
|
||||
createCommit({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
pendingChanges: changes,
|
||||
message
|
||||
});
|
||||
};
|
||||
|
||||
const canReadSecret = hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.DescribeSecret,
|
||||
@@ -350,6 +375,16 @@ const Page = () => {
|
||||
noAccessSecretCount
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data && isBatchMode) {
|
||||
const existingSecretKeys = [...(secrets?.map((s) => s.key) || [])];
|
||||
|
||||
const existingFolderNames = folders?.map((f) => f.name) || [];
|
||||
|
||||
setExistingKeys(existingSecretKeys, existingFolderNames);
|
||||
}
|
||||
}, [data, isBatchMode, setExistingKeys, secrets, importedSecrets, folders]);
|
||||
|
||||
const handleSortToggle = () =>
|
||||
setOrderDirection((state) =>
|
||||
state === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC
|
||||
@@ -476,6 +511,134 @@ const Page = () => {
|
||||
setFilter(defaultFilterState);
|
||||
setDebouncedSearchFilter("");
|
||||
};
|
||||
|
||||
const getMergedSecretsWithPending = () => {
|
||||
if (!isBatchMode || pendingChanges.secrets.length === 0) {
|
||||
return secrets;
|
||||
}
|
||||
|
||||
const mergedSecrets = [...(secrets || [])];
|
||||
|
||||
pendingChanges.secrets.forEach((change) => {
|
||||
switch (change.type) {
|
||||
case "create":
|
||||
mergedSecrets.unshift({
|
||||
id: change.id,
|
||||
key: change.secretKey,
|
||||
value: change.secretValue,
|
||||
comment: change.secretComment || "",
|
||||
skipMultilineEncoding: change.skipMultilineEncoding || false,
|
||||
tags: change.tags?.map((tag) => ({ id: tag.id, slug: tag.slug })) || [],
|
||||
secretMetadata: change.secretMetadata || [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
version: 1,
|
||||
isPending: true,
|
||||
pendingAction: "create"
|
||||
} as any);
|
||||
break;
|
||||
|
||||
case "update":
|
||||
const updateIndex = mergedSecrets.findIndex((s) => s.key === change.secretKey);
|
||||
if (updateIndex >= 0) {
|
||||
mergedSecrets[updateIndex] = {
|
||||
...mergedSecrets[updateIndex],
|
||||
key: change.newSecretName || change.secretKey,
|
||||
value:
|
||||
change.secretValue !== undefined
|
||||
? change.secretValue
|
||||
: mergedSecrets[updateIndex].value,
|
||||
comment:
|
||||
change.secretComment !== undefined
|
||||
? change.secretComment
|
||||
: mergedSecrets[updateIndex].comment,
|
||||
skipMultilineEncoding:
|
||||
change.skipMultilineEncoding !== undefined
|
||||
? change.skipMultilineEncoding
|
||||
: mergedSecrets[updateIndex].skipMultilineEncoding,
|
||||
secretMetadata: change.secretMetadata || mergedSecrets[updateIndex].secretMetadata,
|
||||
isPending: true,
|
||||
pendingAction: "update"
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case "delete":
|
||||
const deleteIndex = mergedSecrets.findIndex((s) => s.key === change.secretKey);
|
||||
if (deleteIndex >= 0) {
|
||||
mergedSecrets[deleteIndex] = {
|
||||
...mergedSecrets[deleteIndex],
|
||||
isPending: true,
|
||||
pendingAction: "delete"
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return mergedSecrets;
|
||||
};
|
||||
|
||||
const getMergedFoldersWithPending = () => {
|
||||
if (!isBatchMode || pendingChanges.folders.length === 0) {
|
||||
return folders;
|
||||
}
|
||||
|
||||
const mergedFolders = [...(folders || [])];
|
||||
|
||||
pendingChanges.folders.forEach((change) => {
|
||||
switch (change.type) {
|
||||
case "create":
|
||||
mergedFolders.unshift({
|
||||
id: change.id,
|
||||
name: change.folderName,
|
||||
description: change.description,
|
||||
parentId: null,
|
||||
isPending: true,
|
||||
pendingAction: "create"
|
||||
} as any);
|
||||
break;
|
||||
|
||||
case "update":
|
||||
const updateIndex = mergedFolders.findIndex((f) => f.id === change.id);
|
||||
if (updateIndex >= 0) {
|
||||
mergedFolders[updateIndex] = {
|
||||
...mergedFolders[updateIndex],
|
||||
name: change.folderName,
|
||||
description:
|
||||
change.description !== undefined
|
||||
? change.description
|
||||
: mergedFolders[updateIndex].description,
|
||||
isPending: true,
|
||||
pendingAction: "update"
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case "delete":
|
||||
const deleteIndex = mergedFolders.findIndex((f) => f.id === change.id);
|
||||
if (deleteIndex >= 0) {
|
||||
mergedFolders[deleteIndex] = {
|
||||
...mergedFolders[deleteIndex],
|
||||
isPending: true,
|
||||
pendingAction: "delete"
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return mergedFolders;
|
||||
};
|
||||
|
||||
const mergedSecrets = getMergedSecretsWithPending();
|
||||
const mergedFolders = getMergedFoldersWithPending();
|
||||
return (
|
||||
<div className="container mx-auto flex max-w-7xl flex-col text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
<SecretV2MigrationSection />
|
||||
@@ -487,6 +650,7 @@ const Page = () => {
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
isVisible={isVisible}
|
||||
isBatchMode={isBatchMode}
|
||||
filter={filter}
|
||||
tags={tags}
|
||||
onVisibilityToggle={handleToggleVisibility}
|
||||
@@ -559,9 +723,9 @@ const Page = () => {
|
||||
importedSecrets={importedSecrets}
|
||||
/>
|
||||
)}
|
||||
{Boolean(folders?.length) && (
|
||||
{Boolean(mergedFolders?.length) && (
|
||||
<FolderListView
|
||||
folders={folders}
|
||||
folders={mergedFolders}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
@@ -579,9 +743,9 @@ const Page = () => {
|
||||
{canReadSecretRotations && Boolean(secretRotations?.length) && (
|
||||
<SecretRotationListView secretRotations={secretRotations} />
|
||||
)}
|
||||
{canReadSecret && Boolean(secrets?.length) && (
|
||||
{canReadSecret && Boolean(mergedSecrets?.length) && (
|
||||
<SecretListView
|
||||
secrets={secrets}
|
||||
secrets={mergedSecrets}
|
||||
tags={tags}
|
||||
isVisible={isVisible}
|
||||
environment={environment}
|
||||
@@ -592,6 +756,17 @@ const Page = () => {
|
||||
usedBySecretSyncs={usedBySecretSyncs}
|
||||
/>
|
||||
)}
|
||||
{(pendingChanges.secrets.length > 0 || pendingChanges.folders.length > 0) && (
|
||||
<CommitForm
|
||||
onCommit={async (changes, commitMessage) => {
|
||||
await handleCreateCommit(changes, commitMessage);
|
||||
}}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
isCommitting={false}
|
||||
/>
|
||||
)}
|
||||
{noAccessSecretCount > 0 && <SecretNoAccessListView count={noAccessSecretCount} />}
|
||||
{!canReadSecret &&
|
||||
!canReadDynamicSecret &&
|
||||
@@ -633,6 +808,7 @@ const Page = () => {
|
||||
secretPath={secretPath}
|
||||
autoCapitalize={currentWorkspace?.autoCapitalization}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
isBatchMode={isBatchMode}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user