Addressed PR suggestions

This commit is contained in:
Carlos Monastyrski
2025-07-10 16:15:15 -03:00
parent 706447d5c6
commit 6bb634f5ed
20 changed files with 216 additions and 170 deletions
+11 -5
View File
@@ -432,11 +432,17 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
} }
], ],
body: z.object({ body: z.object({
projectSlug: z.string().trim().optional(), projectId: z.string().trim(),
workspaceId: z.string().trim().optional(),
environment: z.string().trim(), environment: z.string().trim(),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash), secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
message: z.string().trim(), message: z
.string()
.trim()
.min(1)
.max(255)
.refine((message) => message.trim() !== "", {
message: "Commit message cannot be empty"
}),
changes: z.object({ changes: z.object({
secrets: z.object({ secrets: z.object({
create: z create: z
@@ -500,7 +506,7 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
.refine((name) => isValidFolderName(name), { .refine((name) => isValidFolderName(name), {
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed." message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
}), }),
description: z.string().optional(), description: z.string().nullable().optional(),
id: z.string() id: z.string()
}) })
) )
@@ -534,7 +540,7 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
projectId: req.body.workspaceId, projectId: req.body.projectId,
environment: req.body.environment, environment: req.body.environment,
secretPath: req.body.secretPath, secretPath: req.body.secretPath,
message: req.body.message, message: req.body.message,
+4 -17
View File
@@ -8,7 +8,7 @@ import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TFolderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal"; import { TFolderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
import { import {
ResourceType, ResourceType,
TCreateCommitChangeDTO, TCommitResourceChangeDTO,
TFolderCommitServiceFactory TFolderCommitServiceFactory
} from "@app/services/folder-commit/folder-commit-service"; } from "@app/services/folder-commit/folder-commit-service";
import { import {
@@ -500,8 +500,7 @@ export const pitServiceFactory = ({
const processNewCommitRaw = async ({ const processNewCommitRaw = async ({
actorId, actorId,
projectSlug, projectId,
projectId: optionalProjectId,
environment, environment,
actor, actor,
actorOrgId, actorOrgId,
@@ -522,8 +521,7 @@ export const pitServiceFactory = ({
} }
}: { }: {
actorId: string; actorId: string;
projectSlug?: string; projectId: string;
projectId?: string;
environment: string; environment: string;
actor: ActorType; actor: ActorType;
actorOrgId: string; actorOrgId: string;
@@ -532,16 +530,6 @@ export const pitServiceFactory = ({
message: string; message: string;
changes: TProcessNewCommitRawDTO; changes: TProcessNewCommitRawDTO;
}) => { }) => {
if (!projectSlug && !optionalProjectId)
throw new BadRequestError({ message: "Must provide either project slug or projectId" });
let projectId = optionalProjectId as string;
if (!optionalProjectId && projectSlug) {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
projectId = project.id;
}
const policy = const policy =
actor === ActorType.USER actor === ActorType.USER
? await secretApprovalPolicyService.getSecretApprovalPolicy(projectId, environment, secretPath) ? await secretApprovalPolicyService.getSecretApprovalPolicy(projectId, environment, secretPath)
@@ -581,7 +569,7 @@ export const pitServiceFactory = ({
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`, message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`,
name: "CreateManySecret" name: "CreateManySecret"
}); });
const commitChanges: TCreateCommitChangeDTO[] = []; const commitChanges: TCommitResourceChangeDTO[] = [];
if ((changes.folders?.create?.length ?? 0) > 0) { if ((changes.folders?.create?.length ?? 0) > 0) {
await folderService.createManyFolders({ await folderService.createManyFolders({
@@ -605,7 +593,6 @@ export const pitServiceFactory = ({
if ((changes.folders?.update?.length ?? 0) > 0) { if ((changes.folders?.update?.length ?? 0) > 0) {
await folderService.updateManyFolders({ await folderService.updateManyFolders({
projectId, projectId,
projectSlug: projectSlug || "",
actor, actor,
actorId, actorId,
actorOrgId, actorOrgId,
@@ -1,5 +1,6 @@
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { Knex } from "knex";
import { import {
ProjectMembershipRole, ProjectMembershipRole,
@@ -1260,8 +1261,9 @@ export const secretApprovalRequestServiceFactory = ({
policy, policy,
projectId, projectId,
secretPath, secretPath,
environment environment,
}: TGenerateSecretApprovalRequestV2BridgeDTO) => { trx: providedTx
}: TGenerateSecretApprovalRequestV2BridgeDTO & { trx?: Knex }) => {
if (actor === ActorType.SERVICE || actor === ActorType.Machine) if (actor === ActorType.SERVICE || actor === ActorType.Machine)
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" }); throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
@@ -1487,7 +1489,7 @@ export const secretApprovalRequestServiceFactory = ({
); );
}); });
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => { const executeApprovalRequestCreation = async (tx: Knex) => {
const doc = await secretApprovalRequestDAL.create( const doc = await secretApprovalRequestDAL.create(
{ {
folderId, folderId,
@@ -1549,7 +1551,11 @@ export const secretApprovalRequestServiceFactory = ({
} }
return { ...doc, commits: approvalCommits }; return { ...doc, commits: approvalCommits };
}); };
const secretApprovalRequest = providedTx
? await executeApprovalRequestCreation(providedTx)
: await secretApprovalRequestDAL.transaction(executeApprovalRequestCreation);
const user = await userDAL.findById(secretApprovalRequest.committerUserId); const user = await userDAL.findById(secretApprovalRequest.committerUserId);
const env = await projectEnvDAL.findOne({ id: policy.envId }); const env = await projectEnvDAL.findOne({ id: policy.envId });
@@ -47,7 +47,7 @@ export enum ResourceType {
FOLDER = "folder" FOLDER = "folder"
} }
export type TCreateCommitChangeDTO = { export type TCommitResourceChangeDTO = {
type: string; type: string;
secretVersionId?: string; secretVersionId?: string;
folderVersionId?: string; folderVersionId?: string;
@@ -65,7 +65,7 @@ type TCreateCommitDTO = {
}; };
message?: string; message?: string;
folderId: string; folderId: string;
changes: TCreateCommitChangeDTO[]; changes: TCommitResourceChangeDTO[];
omitIgnoreFilter?: boolean; omitIgnoreFilter?: boolean;
}; };
@@ -897,16 +897,16 @@ export const folderCommitServiceFactory = ({
} }
} }
const newCommit = await folderCommitDAL.create( const newCommit = await folderCommitDAL.create(
{ {
actorMetadata: metadata, actorMetadata: metadata,
actorType: data.actor.type, actorType: data.actor.type,
message: data.message, message: data.message,
folderId: data.folderId, folderId: data.folderId,
envId: folder.envId envId: folder.envId
}, },
tx tx
); );
const batchSize = 500; const batchSize = 500;
const chunks = chunkArray(changes, batchSize); const chunks = chunkArray(changes, batchSize);
@@ -17,7 +17,7 @@ import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns";
import { import {
ChangeType, ChangeType,
CommitType, CommitType,
TCreateCommitChangeDTO, TCommitResourceChangeDTO,
TFolderCommitServiceFactory TFolderCommitServiceFactory
} from "../folder-commit/folder-commit-service"; } from "../folder-commit/folder-commit-service";
import { TProjectDALFactory } from "../project/project-dal"; import { TProjectDALFactory } from "../project/project-dal";
@@ -251,15 +251,18 @@ export const secretFolderServiceFactory = ({
folders, folders,
tx: providedTx, tx: providedTx,
commitChanges commitChanges
}: TUpdateManyFoldersDTO & { tx?: Knex; commitChanges?: TCreateCommitChangeDTO[]; projectId?: string }) => { }: TUpdateManyFoldersDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[]; projectId?: string }) => {
let projectId = providedProjectId; let projectId = providedProjectId;
if (!projectId) { if (!projectId && projectSlug) {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) { if (!project) {
throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
} }
projectId = project.id; projectId = project.id;
} }
if (!projectId) {
throw new BadRequestError({ message: "Must provide either project slug or projectId" });
}
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
@@ -1010,7 +1013,7 @@ export const secretFolderServiceFactory = ({
folders, folders,
tx: providedTx, tx: providedTx,
commitChanges commitChanges
}: TCreateManyFoldersDTO & { tx?: Knex; commitChanges?: TCreateCommitChangeDTO[] }) => { }: TCreateManyFoldersDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -1221,7 +1224,7 @@ export const secretFolderServiceFactory = ({
folders, folders,
tx: providedTx, tx: providedTx,
commitChanges commitChanges
}: TDeleteManyFoldersDTO & { tx?: Knex; commitChanges?: TCreateCommitChangeDTO[] }) => { }: TDeleteManyFoldersDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -23,7 +23,7 @@ export type TUpdateFolderDTO = {
} & TProjectPermission; } & TProjectPermission;
export type TUpdateManyFoldersDTO = { export type TUpdateManyFoldersDTO = {
projectSlug: string; projectSlug?: string;
folders: { folders: {
environment: string; environment: string;
path: string; path: string;
@@ -75,7 +75,7 @@ export type TCreateManyFoldersDTO = {
name: string; name: string;
environment: string; environment: string;
path: string; path: string;
description?: string; description?: string | null;
}>; }>;
}; };
@@ -28,7 +28,7 @@ import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
import { TCreateCommitChangeDTO, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service"; import { TCommitResourceChangeDTO, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types"; import { KmsDataKey } from "../kms/kms-types";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
@@ -1477,7 +1477,7 @@ export const secretV2BridgeServiceFactory = ({
secrets: inputSecrets, secrets: inputSecrets,
tx: providedTx, tx: providedTx,
commitChanges commitChanges
}: TCreateManySecretDTO & { tx?: Knex; commitChanges?: TCreateCommitChangeDTO[] }) => { }: TCreateManySecretDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -1652,7 +1652,7 @@ export const secretV2BridgeServiceFactory = ({
mode: updateMode, mode: updateMode,
tx: providedTx, tx: providedTx,
commitChanges commitChanges
}: TUpdateManySecretDTO & { tx?: Knex; commitChanges?: TCreateCommitChangeDTO[] }) => { }: TUpdateManySecretDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -2013,7 +2013,7 @@ export const secretV2BridgeServiceFactory = ({
actorOrgId, actorOrgId,
tx: providedTx, tx: providedTx,
commitChanges commitChanges
}: TDeleteManySecretDTO & { tx?: Knex; commitChanges?: TCreateCommitChangeDTO[] }) => { }: TDeleteManySecretDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -8,7 +8,7 @@ import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { TCreateCommitChangeDTO, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service"; import { TCommitResourceChangeDTO, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal"; import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema"; import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
@@ -167,7 +167,7 @@ export type TFnSecretBulkInsert = {
folderId: string; folderId: string;
orgId: string; orgId: string;
tx?: Knex; tx?: Knex;
commitChanges?: TCreateCommitChangeDTO[]; commitChanges?: TCommitResourceChangeDTO[];
inputSecrets: Array< inputSecrets: Array<
Omit<TSecretsV2Insert, "folderId"> & { Omit<TSecretsV2Insert, "folderId"> & {
tagIds?: string[]; tagIds?: string[];
@@ -215,7 +215,7 @@ export type TFnSecretBulkUpdate = {
actorId?: string; actorId?: string;
}; };
tx?: Knex; tx?: Knex;
commitChanges?: TCreateCommitChangeDTO[]; commitChanges?: TCommitResourceChangeDTO[];
}; };
export type TFnSecretBulkDelete = { export type TFnSecretBulkDelete = {
@@ -225,7 +225,7 @@ export type TFnSecretBulkDelete = {
actorId: string; actorId: string;
actorType?: string; actorType?: string;
tx?: Knex; tx?: Knex;
commitChanges?: TCreateCommitChangeDTO[]; commitChanges?: TCommitResourceChangeDTO[];
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">; secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
secretQueueService: { secretQueueService: {
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>; removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
+1 -1
View File
@@ -570,7 +570,7 @@ export type TProcessNewCommitRawDTO = {
}; };
folders: { folders: {
create?: { folderName: string; description?: string }[]; create?: { folderName: string; description?: string }[];
update?: { folderName: string; description?: string; id: string }[]; update?: { folderName: string; description?: string | null; id: string }[];
delete?: { folderName: string; id: string }[]; delete?: { folderName: string; id: string }[];
}; };
}; };
@@ -4,13 +4,19 @@ export enum ReservedFolders {
SecretReplication = "__reserve_replication_" SecretReplication = "__reserve_replication_"
} }
export enum PendingAction {
Create = "create",
Update = "update",
Delete = "delete"
}
export type TSecretFolder = { export type TSecretFolder = {
id: string; id: string;
name: string; name: string;
description?: string; description?: string;
parentId?: string | null; parentId?: string | null;
isPending?: boolean; isPending?: boolean;
pendingAction?: string; pendingAction?: PendingAction;
}; };
export type TSecretFolderWithPath = TSecretFolder & { path: string }; export type TSecretFolderWithPath = TSecretFolder & { path: string };
+30 -9
View File
@@ -9,6 +9,7 @@ import {
import { commitKeys } from "../folderCommits/queries"; import { commitKeys } from "../folderCommits/queries";
import { secretApprovalRequestKeys } from "../secretApprovalRequest/queries"; import { secretApprovalRequestKeys } from "../secretApprovalRequest/queries";
import { PendingAction } from "../secretFolders/types";
import { secretSnapshotKeys } from "../secretSnapshots/queries"; import { secretSnapshotKeys } from "../secretSnapshots/queries";
import { secretKeys } from "./queries"; import { secretKeys } from "./queries";
import { import {
@@ -440,9 +441,20 @@ export const useCreateCommit = () => {
>({ >({
mutationFn: async ({ workspaceId, environment, secretPath, pendingChanges, message }) => { mutationFn: async ({ workspaceId, environment, secretPath, pendingChanges, message }) => {
const transformedSecretUpdates = pendingChanges.secrets const transformedSecretUpdates = pendingChanges.secrets
.filter((change) => change.type === "update") .filter((change) => change.type === PendingAction.Update)
.map((change: PendingSecretUpdate) => { .map((change: PendingSecretUpdate) => {
const updatePayload: any = { const updatePayload: {
secretKey: string;
newSecretName?: string;
secretValue?: string;
secretComment?: string;
skipMultilineEncoding?: boolean;
tagIds?: string[];
secretMetadata?: {
key: string;
value: string;
}[];
} = {
secretKey: change.secretKey secretKey: change.secretKey
}; };
@@ -469,14 +481,14 @@ export const useCreateCommit = () => {
return updatePayload; return updatePayload;
}); });
const { data } = await apiRequest.post("/api/v1/pit/batch/commit", { const { data } = await apiRequest.post("/api/v1/pit/batch/commit", {
workspaceId, projectId: workspaceId,
environment, environment,
secretPath, secretPath,
changes: { changes: {
secrets: { secrets: {
create: create:
pendingChanges.secrets pendingChanges.secrets
.filter((change) => change.type === "create") .filter((change) => change.type === PendingAction.Create)
.map((change) => ({ .map((change) => ({
secretKey: change.secretKey, secretKey: change.secretKey,
secretValue: change.secretValue, secretValue: change.secretValue,
@@ -485,13 +497,22 @@ export const useCreateCommit = () => {
tagIds: change.tags?.map((tag) => tag.id), tagIds: change.tags?.map((tag) => tag.id),
secretMetadata: change.secretMetadata secretMetadata: change.secretMetadata
})) || [], })) || [],
update: transformedSecretUpdates, update: transformedSecretUpdates || [],
delete: pendingChanges.secrets.filter((change) => change.type === "delete") || [] delete:
pendingChanges.secrets.filter((change) => change.type === PendingAction.Delete) || []
}, },
folders: { folders: {
create: pendingChanges.folders.filter((change) => change.type === "create") || [], create:
update: pendingChanges.folders.filter((change) => change.type === "update") || [], pendingChanges.folders.filter((change) => change.type === PendingAction.Create) || [],
delete: pendingChanges.folders.filter((change) => change.type === "delete") || [] update:
pendingChanges.folders
.filter((change) => change.type === PendingAction.Update)
.map((change) => ({
...change,
description: change.description || null
})) || [],
delete:
pendingChanges.folders.filter((change) => change.type === PendingAction.Delete) || []
} }
}, },
message message
+2 -1
View File
@@ -1,5 +1,6 @@
import { ProjectPermissionActions } from "@app/context"; import { ProjectPermissionActions } from "@app/context";
import { PendingAction } from "../secretFolders/types";
import type { WsTag } from "../tags/types"; import type { WsTag } from "../tags/types";
export enum SecretType { export enum SecretType {
@@ -67,7 +68,7 @@ export type SecretV3RawSanitized = {
secretReminderRecipients?: SecretReminderRecipient[]; secretReminderRecipients?: SecretReminderRecipient[];
rotationId?: string; rotationId?: string;
isPending?: boolean; isPending?: boolean;
pendingAction?: "create" | "update" | "delete"; pendingAction?: PendingAction;
}; };
export type SecretV3Raw = { export type SecretV3Raw = {
@@ -47,7 +47,9 @@ import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard";
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types"; import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
import { useGetFolderCommitsCount } from "@app/hooks/api/folderCommits"; import { useGetFolderCommitsCount } from "@app/hooks/api/folderCommits";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { useCreateCommit } from "@app/hooks/api/secrets/mutations"; import { useCreateCommit } from "@app/hooks/api/secrets/mutations";
import { SecretV3RawSanitized } from "@app/hooks/api/types";
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission"; import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
import { SecretRotationListView } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView"; import { SecretRotationListView } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView";
@@ -136,14 +138,22 @@ const Page = () => {
} }
}, [isBatchMode, workspaceId, environment, secretPath, loadPendingChanges]); }, [isBatchMode, workspaceId, environment, secretPath, loadPendingChanges]);
const handleCreateCommit = (changes: PendingChanges, message: string) => { const handleCreateCommit = async (changes: PendingChanges, message: string) => {
createCommit({ try {
workspaceId, await createCommit({
environment, workspaceId,
secretPath, environment,
pendingChanges: changes, secretPath,
message pendingChanges: changes,
}); message
});
} catch (error) {
createNotification({
text: "Failed to commit changes",
type: "error"
});
console.error(error);
}
}; };
const canReadSecret = hasSecretReadValueOrDescribePermission( const canReadSecret = hasSecretReadValueOrDescribePermission(
@@ -521,7 +531,7 @@ const Page = () => {
pendingChanges.secrets.forEach((change) => { pendingChanges.secrets.forEach((change) => {
switch (change.type) { switch (change.type) {
case "create": case PendingAction.Create:
mergedSecrets.unshift({ mergedSecrets.unshift({
id: change.id, id: change.id,
key: change.secretKey, key: change.secretKey,
@@ -534,11 +544,11 @@ const Page = () => {
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
version: 1, version: 1,
isPending: true, isPending: true,
pendingAction: "create" pendingAction: PendingAction.Create
} as any); } as unknown as SecretV3RawSanitized);
break; break;
case "update": case PendingAction.Update:
const updateIndex = mergedSecrets.findIndex((s) => s.key === change.secretKey); const updateIndex = mergedSecrets.findIndex((s) => s.key === change.secretKey);
if (updateIndex >= 0) { if (updateIndex >= 0) {
mergedSecrets[updateIndex] = { mergedSecrets[updateIndex] = {
@@ -558,18 +568,18 @@ const Page = () => {
: mergedSecrets[updateIndex].skipMultilineEncoding, : mergedSecrets[updateIndex].skipMultilineEncoding,
secretMetadata: change.secretMetadata || mergedSecrets[updateIndex].secretMetadata, secretMetadata: change.secretMetadata || mergedSecrets[updateIndex].secretMetadata,
isPending: true, isPending: true,
pendingAction: "update" pendingAction: PendingAction.Update
}; };
} }
break; break;
case "delete": case PendingAction.Delete:
const deleteIndex = mergedSecrets.findIndex((s) => s.key === change.secretKey); const deleteIndex = mergedSecrets.findIndex((s) => s.key === change.secretKey);
if (deleteIndex >= 0) { if (deleteIndex >= 0) {
mergedSecrets[deleteIndex] = { mergedSecrets[deleteIndex] = {
...mergedSecrets[deleteIndex], ...mergedSecrets[deleteIndex],
isPending: true, isPending: true,
pendingAction: "delete" pendingAction: PendingAction.Delete
}; };
} }
break; break;
@@ -591,18 +601,18 @@ const Page = () => {
pendingChanges.folders.forEach((change) => { pendingChanges.folders.forEach((change) => {
switch (change.type) { switch (change.type) {
case "create": case PendingAction.Create:
mergedFolders.unshift({ mergedFolders.unshift({
id: change.id, id: change.id,
name: change.folderName, name: change.folderName,
description: change.description, description: change.description,
parentId: null, parentId: null,
isPending: true, isPending: true,
pendingAction: "create" pendingAction: PendingAction.Create
} as any); } as any);
break; break;
case "update": case PendingAction.Update:
const updateIndex = mergedFolders.findIndex((f) => f.id === change.id); const updateIndex = mergedFolders.findIndex((f) => f.id === change.id);
if (updateIndex >= 0) { if (updateIndex >= 0) {
mergedFolders[updateIndex] = { mergedFolders[updateIndex] = {
@@ -613,18 +623,18 @@ const Page = () => {
? change.description ? change.description
: mergedFolders[updateIndex].description, : mergedFolders[updateIndex].description,
isPending: true, isPending: true,
pendingAction: "update" pendingAction: PendingAction.Update
}; };
} }
break; break;
case "delete": case PendingAction.Delete:
const deleteIndex = mergedFolders.findIndex((f) => f.id === change.id); const deleteIndex = mergedFolders.findIndex((f) => f.id === change.id);
if (deleteIndex >= 0) { if (deleteIndex >= 0) {
mergedFolders[deleteIndex] = { mergedFolders[deleteIndex] = {
...mergedFolders[deleteIndex], ...mergedFolders[deleteIndex],
isPending: true, isPending: true,
pendingAction: "delete" pendingAction: PendingAction.Delete
}; };
} }
break; break;
@@ -758,9 +768,7 @@ const Page = () => {
)} )}
{(pendingChanges.secrets.length > 0 || pendingChanges.folders.length > 0) && ( {(pendingChanges.secrets.length > 0 || pendingChanges.folders.length > 0) && (
<CommitForm <CommitForm
onCommit={async (changes, commitMessage) => { onCommit={handleCreateCommit}
await handleCreateCommit(changes, commitMessage);
}}
environment={environment} environment={environment}
workspaceId={workspaceId} workspaceId={workspaceId}
secretPath={secretPath} secretPath={secretPath}
@@ -4,6 +4,7 @@ import { useRouter } from "@tanstack/react-router";
import { createStore, StateCreator, StoreApi, useStore } from "zustand"; import { createStore, StateCreator, StoreApi, useStore } from "zustand";
import { useShallow } from "zustand/react/shallow"; import { useShallow } from "zustand/react/shallow";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
// akhilmhdh: Don't remove this file if ur thinking why use zustand just for selected selects state // akhilmhdh: Don't remove this file if ur thinking why use zustand just for selected selects state
@@ -19,7 +20,7 @@ export interface BasePendingChange {
// Secret-related change types // Secret-related change types
export interface PendingSecretCreate extends BasePendingChange { export interface PendingSecretCreate extends BasePendingChange {
resourceType: "secret"; resourceType: "secret";
type: "create"; type: PendingAction.Create;
secretKey: string; secretKey: string;
secretValue: string; secretValue: string;
secretComment?: string; secretComment?: string;
@@ -31,7 +32,7 @@ export interface PendingSecretCreate extends BasePendingChange {
export interface PendingSecretUpdate extends BasePendingChange { export interface PendingSecretUpdate extends BasePendingChange {
resourceType: "secret"; resourceType: "secret";
type: "update"; type: PendingAction.Update;
secretKey: string; secretKey: string;
newSecretName?: string; newSecretName?: string;
originalValue?: string; originalValue?: string;
@@ -48,7 +49,7 @@ export interface PendingSecretUpdate extends BasePendingChange {
export interface PendingSecretDelete extends BasePendingChange { export interface PendingSecretDelete extends BasePendingChange {
resourceType: "secret"; resourceType: "secret";
type: "delete"; type: PendingAction.Delete;
secretKey: string; secretKey: string;
secretValue: string; secretValue: string;
} }
@@ -56,7 +57,7 @@ export interface PendingSecretDelete extends BasePendingChange {
// Folder-related change types // Folder-related change types
export interface PendingFolderCreate extends BasePendingChange { export interface PendingFolderCreate extends BasePendingChange {
resourceType: "folder"; resourceType: "folder";
type: "create"; type: PendingAction.Create;
id: string; id: string;
folderName: string; folderName: string;
description?: string; description?: string;
@@ -65,7 +66,7 @@ export interface PendingFolderCreate extends BasePendingChange {
export interface PendingFolderUpdate extends BasePendingChange { export interface PendingFolderUpdate extends BasePendingChange {
resourceType: "folder"; resourceType: "folder";
type: "update"; type: PendingAction.Update;
originalFolderName: string; originalFolderName: string;
folderName: string; folderName: string;
id: string; id: string;
@@ -75,7 +76,7 @@ export interface PendingFolderUpdate extends BasePendingChange {
export interface PendingFolderDelete extends BasePendingChange { export interface PendingFolderDelete extends BasePendingChange {
resourceType: "folder"; resourceType: "folder";
type: "delete"; type: PendingAction.Delete;
id: string; id: string;
folderName: string; folderName: string;
folderPath: string; folderPath: string;
@@ -248,43 +249,45 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
const newChanges = { ...state.pendingChanges }; const newChanges = { ...state.pendingChanges };
if (change.resourceType === "folder") { if (change.resourceType === "folder") {
if ( const existingFolder =
change.type === "create" && state.existingFolderNames.has(change.folderName) ||
(state.existingFolderNames.has(change.folderName) || newChanges.folders.some((f) => f.folderName === change.folderName);
newChanges.folders.some((f) => f.folderName === change.folderName))
) { if (change.type === PendingAction.Create && existingFolder) {
return { pendingChanges: newChanges }; return { pendingChanges: newChanges };
} }
if ( if (
change.type === "update" && change.type === PendingAction.Update &&
change.folderName !== change.originalFolderName && change.folderName !== change.originalFolderName &&
(state.existingFolderNames.has(change.folderName) || existingFolder
newChanges.folders.some((f) => f.folderName === change.folderName))
) { ) {
return { pendingChanges: newChanges }; return { pendingChanges: newChanges };
} }
} }
if (change.resourceType === "secret") { if (change.resourceType === "secret") {
if ( const existingSecret =
change.type === "create" && state.existingSecretKeys.has(change.secretKey) ||
(state.existingSecretKeys.has(change.secretKey) || newChanges.secrets.some((s) => s.secretKey === change.secretKey);
newChanges.secrets.some((s) => s.secretKey === change.secretKey))
) { if (change.type === PendingAction.Create && existingSecret) {
return { pendingChanges: newChanges }; return { pendingChanges: newChanges };
} }
if (
change.type === "update" && const existingNewSecretName =
change.type === PendingAction.Update &&
change.newSecretName && change.newSecretName &&
change.newSecretName !== change.secretKey && change.newSecretName !== change.secretKey &&
(state.existingSecretKeys.has(change.newSecretName) || (state.existingSecretKeys.has(change.newSecretName) ||
newChanges.secrets.some( newChanges.secrets.some(
(s) => (s) =>
(s.secretKey === change.newSecretName || (s.secretKey === change.newSecretName ||
(s.type === "update" && s.newSecretName === change.newSecretName)) && (s.type === PendingAction.Update &&
s.newSecretName === change.newSecretName)) &&
s.id !== change.id s.id !== change.id
)) ));
) {
if (existingNewSecretName) {
return { pendingChanges: newChanges }; return { pendingChanges: newChanges };
} }
} }
@@ -292,10 +295,10 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
if (change.resourceType === "secret") { if (change.resourceType === "secret") {
const secretChanges = [...newChanges.secrets]; const secretChanges = [...newChanges.secrets];
if (change.type === "create") { if (change.type === PendingAction.Create) {
const existingCreateIndex = secretChanges.findIndex( const existingCreateIndex = secretChanges.findIndex(
(c) => (c) =>
c.type === "create" && c.type === PendingAction.Create &&
(c.secretKey === change.secretKey || c.secretKey === change.originalKey) (c.secretKey === change.secretKey || c.secretKey === change.originalKey)
); );
@@ -308,9 +311,9 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
} else { } else {
secretChanges.push(change); secretChanges.push(change);
} }
} else if (change.type === "update") { } else if (change.type === PendingAction.Update) {
const existingCreateIndex = secretChanges.findIndex( const existingCreateIndex = secretChanges.findIndex(
(c) => c.type === "create" && c.id === change.id (c) => c.type === PendingAction.Create && c.id === change.id
); );
if (existingCreateIndex >= 0) { if (existingCreateIndex >= 0) {
@@ -339,7 +342,7 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
}; };
} else { } else {
const existingUpdateIndex = secretChanges.findIndex( const existingUpdateIndex = secretChanges.findIndex(
(c) => c.type === "update" && c.id === change.id (c) => c.type === PendingAction.Update && c.id === change.id
); );
if (existingUpdateIndex >= 0) { if (existingUpdateIndex >= 0) {
@@ -394,9 +397,9 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
} else if (change.resourceType === "folder") { } else if (change.resourceType === "folder") {
const folderChanges = [...newChanges.folders]; const folderChanges = [...newChanges.folders];
if (change.type === "create") { if (change.type === PendingAction.Create) {
const existingCreateIndex = folderChanges.findIndex( const existingCreateIndex = folderChanges.findIndex(
(c) => c.type === "create" && c.folderName === change.folderName (c) => c.type === PendingAction.Create && c.folderName === change.folderName
); );
if (existingCreateIndex >= 0) { if (existingCreateIndex >= 0) {
@@ -408,9 +411,9 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
} else { } else {
folderChanges.push(change); folderChanges.push(change);
} }
} else if (change.type === "update") { } else if (change.type === PendingAction.Update) {
const existingCreateIndex = folderChanges.findIndex( const existingCreateIndex = folderChanges.findIndex(
(c) => c.type === "create" && c.folderName === change.originalFolderName (c) => c.type === PendingAction.Create && c.folderName === change.originalFolderName
); );
if (existingCreateIndex >= 0) { if (existingCreateIndex >= 0) {
@@ -426,7 +429,7 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
}; };
} else { } else {
const existingUpdateIndex = folderChanges.findIndex( const existingUpdateIndex = folderChanges.findIndex(
(c) => c.type === "update" && c.id === change.id (c) => c.type === PendingAction.Update && c.id === change.id
); );
if (existingUpdateIndex >= 0) { if (existingUpdateIndex >= 0) {
@@ -74,6 +74,7 @@ import {
} from "@app/hooks/api/dashboard/queries"; } from "@app/hooks/api/dashboard/queries";
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types"; import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { fetchProjectSecrets, secretKeys } from "@app/hooks/api/secrets/queries"; import { fetchProjectSecrets, secretKeys } from "@app/hooks/api/secrets/queries";
import { ApiErrorTypes, SecretType, TApiErrors, WsTag } from "@app/hooks/api/types"; import { ApiErrorTypes, SecretType, TApiErrors, WsTag } from "@app/hooks/api/types";
import { SecretSearchInput } from "@app/pages/secret-manager/OverviewPage/components/SecretSearchInput"; import { SecretSearchInput } from "@app/pages/secret-manager/OverviewPage/components/SecretSearchInput";
@@ -193,7 +194,7 @@ export const ActionBar = ({
const pendingFolderCreate: PendingFolderCreate = { const pendingFolderCreate: PendingFolderCreate = {
id: folderId, id: folderId,
resourceType: "folder", resourceType: "folder",
type: "create", type: PendingAction.Create,
folderName, folderName,
description: description || undefined, description: description || undefined,
parentPath: secretPath, parentPath: secretPath,
@@ -12,6 +12,7 @@ import {
Modal, Modal,
ModalContent ModalContent
} from "@app/components/v2"; } from "@app/components/v2";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { import {
PendingChange, PendingChange,
@@ -106,11 +107,11 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
}) => { }) => {
const getChangeBadge = (type: PendingChange["type"]) => { const getChangeBadge = (type: PendingChange["type"]) => {
switch (type) { switch (type) {
case "create": case PendingAction.Create:
return <Badge variant="success">Created</Badge>; return <Badge variant="success">Created</Badge>;
case "update": case PendingAction.Update:
return <Badge variant="primary">Updated</Badge>; return <Badge variant="primary">Updated</Badge>;
case "delete": case PendingAction.Delete:
return <Badge variant="danger">Deleted</Badge>; return <Badge variant="danger">Deleted</Badge>;
default: default:
return null; return null;
@@ -120,7 +121,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
const renderSecretChanges = () => { const renderSecretChanges = () => {
if (change.resourceType !== "secret") return null; if (change.resourceType !== "secret") return null;
if (change.type === "create") { if (change.type === PendingAction.Create) {
return ( return (
<div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900"> <div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900">
<table className="w-full text-sm"> <table className="w-full text-sm">
@@ -171,7 +172,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
); );
} }
if (change.type === "update") { if (change.type === PendingAction.Update) {
const hasKeyChange = change.newSecretName && change.secretKey !== change.newSecretName; const hasKeyChange = change.newSecretName && change.secretKey !== change.newSecretName;
const hasValueChange = change.secretValue !== change.originalValue; const hasValueChange = change.secretValue !== change.originalValue;
const hasCommentChange = change.secretComment !== change.originalComment; const hasCommentChange = change.secretComment !== change.originalComment;
@@ -252,7 +253,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
); );
} }
if (change.type === "delete") { if (change.type === PendingAction.Delete) {
return ( return (
<div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900"> <div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900">
<table className="w-full text-sm"> <table className="w-full text-sm">
@@ -275,7 +276,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
const renderFolderChanges = () => { const renderFolderChanges = () => {
if (change.resourceType !== "folder") return null; if (change.resourceType !== "folder") return null;
if (change.type === "create") { if (change.type === PendingAction.Create) {
return ( return (
<div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900"> <div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900">
<table className="w-full text-sm"> <table className="w-full text-sm">
@@ -300,7 +301,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
); );
} }
if (change.type === "update") { if (change.type === PendingAction.Update) {
const hasNameChange = change.folderName !== change.originalFolderName; const hasNameChange = change.folderName !== change.originalFolderName;
const hasDescriptionChange = change.description !== change.originalDescription; const hasDescriptionChange = change.description !== change.originalDescription;
@@ -334,7 +335,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
); );
} }
if (change.type === "delete") { if (change.type === PendingAction.Delete) {
return ( return (
<div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900"> <div className="mt-3 overflow-hidden rounded-md border border-mineshaft-700 bg-mineshaft-900">
<table className="w-full text-sm"> <table className="w-full text-sm">
@@ -356,10 +357,12 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
const getChangeName = () => { const getChangeName = () => {
if (change.resourceType === "secret") { if (change.resourceType === "secret") {
return change.type === "update" ? change.newSecretName || change.secretKey : change.secretKey; return change.type === PendingAction.Update
? change.newSecretName || change.secretKey
: change.secretKey;
} }
if (change.resourceType === "folder") { if (change.resourceType === "folder") {
return change.type === "update" ? change.originalFolderName : change.folderName; return change.type === PendingAction.Update ? change.originalFolderName : change.folderName;
} }
return "Unknown"; return "Unknown";
}; };
@@ -417,19 +420,14 @@ export const CommitForm: React.FC<CommitFormProps> = ({
if (!commitMessage.trim()) { if (!commitMessage.trim()) {
return; return;
} }
await onCommit(pendingChanges, commitMessage);
try { clearAllPendingChanges({
await onCommit(pendingChanges, commitMessage); workspaceId,
clearAllPendingChanges({ environment,
workspaceId, secretPath
environment, });
secretPath setIsModalOpen(false);
}); setCommitMessage("");
setIsModalOpen(false);
setCommitMessage("");
} catch (error) {
console.error("Failed to commit changes:", error);
}
}; };
return ( return (
@@ -12,6 +12,7 @@ import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import { getKeyValue } from "@app/helpers/parseEnvVar"; import { getKeyValue } from "@app/helpers/parseEnvVar";
import { useCreateSecretV3, useCreateWsTag, useGetWsTags } from "@app/hooks/api"; import { useCreateSecretV3, useCreateWsTag, useGetWsTags } from "@app/hooks/api";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { SecretType } from "@app/hooks/api/types"; import { SecretType } from "@app/hooks/api/types";
import { import {
@@ -96,7 +97,7 @@ export const CreateSecretForm = ({
if (isBatchMode) { if (isBatchMode) {
const pendingSecretCreate: PendingSecretCreate = { const pendingSecretCreate: PendingSecretCreate = {
id: key, id: key,
type: "create", type: PendingAction.Create,
secretKey: key, secretKey: key,
secretValue: value || "", secretValue: value || "",
secretComment: "", secretComment: "",
@@ -12,7 +12,7 @@ import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteFolder, useUpdateFolder } from "@app/hooks/api"; import { useDeleteFolder, useUpdateFolder } from "@app/hooks/api";
import { TSecretFolder } from "@app/hooks/api/secretFolders/types"; import { PendingAction, TSecretFolder } from "@app/hooks/api/secretFolders/types";
import { import {
PendingFolderCreate, PendingFolderCreate,
@@ -60,15 +60,17 @@ export const FolderListView = ({
oldFolderDescription?: string oldFolderDescription?: string
) => { ) => {
try { try {
const { id: folderId, pendingAction, isPending } = popUp.updateFolder.data as TSecretFolder; const updateFolderData = popUp.updateFolder.data;
if (!updateFolderData) throw new Error("Update folder data is required");
const { id: folderId, pendingAction, isPending } = updateFolderData as TSecretFolder;
if (isBatchMode) { if (isBatchMode) {
const isEditingPendingCreation = isPending && pendingAction === "create"; const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create;
if (isEditingPendingCreation) { if (isEditingPendingCreation) {
const updatedCreate: PendingFolderCreate = { const updatedCreate: PendingFolderCreate = {
id: folderId, id: folderId,
type: "create", type: PendingAction.Create,
folderName: newFolderName, folderName: newFolderName,
description: newFolderDescription || undefined, description: newFolderDescription || undefined,
parentPath: secretPath, parentPath: secretPath,
@@ -84,7 +86,7 @@ export const FolderListView = ({
} else { } else {
const updateChange: PendingFolderUpdate = { const updateChange: PendingFolderUpdate = {
id: folderId, id: folderId,
type: "update", type: PendingAction.Update,
originalFolderName: oldFolderName || "", originalFolderName: oldFolderName || "",
folderName: newFolderName, folderName: newFolderName,
originalDescription: oldFolderDescription, originalDescription: oldFolderDescription,
@@ -144,7 +146,7 @@ export const FolderListView = ({
folderName: folderData.name, folderName: folderData.name,
folderPath: secretPath, folderPath: secretPath,
resourceType: "folder", resourceType: "folder",
type: "delete", type: PendingAction.Delete,
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -197,9 +199,9 @@ export const FolderListView = ({
className={twMerge( className={twMerge(
"group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700", "group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700",
isPending && "bg-mineshaft-700/60", isPending && "bg-mineshaft-700/60",
pendingAction === "delete" && "border-l-2 border-l-red-600/75", pendingAction === PendingAction.Delete && "border-l-2 border-l-red-600/75",
pendingAction === "update" && "border-l-2 border-l-yellow-600/75", pendingAction === PendingAction.Update && "border-l-2 border-l-yellow-600/75",
pendingAction === "create" && "border-l-2 border-l-green-600/75" pendingAction === PendingAction.Create && "border-l-2 border-l-green-600/75"
)} )}
> >
<div className="flex w-11 items-center px-5 py-3 text-yellow-700"> <div className="flex w-11 items-center px-5 py-3 text-yellow-700">
@@ -50,6 +50,7 @@ import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionCo
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission"; import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEyeSlash, faKey, faRotate } from "@fortawesome/free-solid-svg-icons"; import { faEyeSlash, faKey, faRotate } from "@fortawesome/free-solid-svg-icons";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { import {
FontAwesomeSpriteName, FontAwesomeSpriteName,
formSchema, formSchema,
@@ -88,7 +89,7 @@ type Props = {
}[]; }[];
}[]; }[];
isPending?: boolean; isPending?: boolean;
pendingAction?: "create" | "update" | "delete"; pendingAction?: PendingAction;
}; };
export const SecretItem = memo( export const SecretItem = memo(
@@ -201,6 +202,8 @@ export const SecretItem = memo(
secretTags: selectedTagSlugs secretTags: selectedTagSlugs
}) })
); );
const isReadOnlySecret =
isReadOnly || isRotatedSecret || (isPending && pendingAction !== PendingAction.Update);
const { secretValueHidden } = secret; const { secretValueHidden } = secret;
@@ -289,9 +292,9 @@ export const SecretItem = memo(
isDirty && "border-primary-400/50", isDirty && "border-primary-400/50",
isRotatedSecret && "bg-mineshaft-700/60", isRotatedSecret && "bg-mineshaft-700/60",
isPending && "bg-mineshaft-700/60", isPending && "bg-mineshaft-700/60",
pendingAction === "delete" && "border-l-2 border-l-red-600/75", pendingAction === PendingAction.Delete && "border-l-2 border-l-red-600/75",
pendingAction === "update" && "border-l-2 border-l-yellow-600/75", pendingAction === PendingAction.Update && "border-l-2 border-l-yellow-600/75",
pendingAction === "create" && "border-l-2 border-l-green-600/75" pendingAction === PendingAction.Create && "border-l-2 border-l-green-600/75"
)} )}
> >
<div className="group flex"> <div className="group flex">
@@ -383,9 +386,7 @@ export const SecretItem = memo(
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<InfisicalSecretInput <InfisicalSecretInput
isReadOnly={ isReadOnly={isReadOnlySecret}
isReadOnly || isRotatedSecret || (isPending && pendingAction !== "update")
}
key="secret-value" key="secret-value"
isVisible={isVisible && !secretValueHidden} isVisible={isVisible && !secretValueHidden}
canEditButNotView={secretValueHidden && !isOverriden} canEditButNotView={secretValueHidden && !isOverriden}
@@ -398,7 +399,8 @@ export const SecretItem = memo(
)} )}
/> />
)} )}
{pendingAction !== "create" && pendingAction !== "delete" && ( {/* Only allow to open the side panel if the secret is not in a pending create or delete state */}
{pendingAction !== PendingAction.Create && pendingAction !== PendingAction.Delete && (
<div <div
key="actions" key="actions"
className="flex h-full flex-shrink-0 self-start transition-all group-hover:gap-x-2" className="flex h-full flex-shrink-0 self-start transition-all group-hover:gap-x-2"
@@ -11,6 +11,7 @@ import { dashboardKeys } from "@app/hooks/api/dashboard/queries";
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types"; import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
import { commitKeys } from "@app/hooks/api/folderCommits/queries"; import { commitKeys } from "@app/hooks/api/folderCommits/queries";
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { secretKeys } from "@app/hooks/api/secrets/queries"; import { secretKeys } from "@app/hooks/api/secrets/queries";
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries"; import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
@@ -300,12 +301,12 @@ export const SecretListView = ({
// shared secret change // shared secret change
if (!isSharedSecUnchanged && !personalAction) { if (!isSharedSecUnchanged && !personalAction) {
if (isBatchMode) { if (isBatchMode) {
const isEditingPendingCreation = isPending && pendingAction === "create"; const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create;
if (isEditingPendingCreation) { if (isEditingPendingCreation) {
const updatedCreate: PendingSecretCreate = { const updatedCreate: PendingSecretCreate = {
id: orgSecret.id, id: orgSecret.id,
type: "create", type: PendingAction.Create,
secretKey: key, secretKey: key,
secretValue: value || "", secretValue: value || "",
secretComment: comment || "", secretComment: comment || "",
@@ -327,7 +328,7 @@ export const SecretListView = ({
const updateChange: PendingSecretUpdate = { const updateChange: PendingSecretUpdate = {
id: orgSecret.id, id: orgSecret.id,
type: "update", type: PendingAction.Update,
secretKey: trueOriginalSecret.key, secretKey: trueOriginalSecret.key,
...(key !== trueOriginalSecret.key && { newSecretName: key }), ...(key !== trueOriginalSecret.key && { newSecretName: key }),
...(value !== trueOriginalSecret.value && { ...(value !== trueOriginalSecret.value && {
@@ -457,7 +458,7 @@ export const SecretListView = ({
if (isBatchMode) { if (isBatchMode) {
const deleteChange: PendingSecretDelete = { const deleteChange: PendingSecretDelete = {
id: `${secretId}`, id: `${secretId}`,
type: "delete", type: PendingAction.Delete,
secretKey: key, secretKey: key,
secretValue: value || "", secretValue: value || "",
timestamp: Date.now(), timestamp: Date.now(),