mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 17:27:40 +00:00
Removed session storage logic and added a few UI improvements
This commit is contained in:
@@ -440,46 +440,6 @@ export const useCreateCommit = () => {
|
|||||||
}
|
}
|
||||||
>({
|
>({
|
||||||
mutationFn: async ({ workspaceId, environment, secretPath, pendingChanges, message }) => {
|
mutationFn: async ({ workspaceId, environment, secretPath, pendingChanges, message }) => {
|
||||||
const transformedSecretUpdates = pendingChanges.secrets
|
|
||||||
.filter((change) => change.type === PendingAction.Update)
|
|
||||||
.map((change: PendingSecretUpdate) => {
|
|
||||||
const updatePayload: {
|
|
||||||
secretKey: string;
|
|
||||||
newSecretName?: string;
|
|
||||||
secretValue?: string;
|
|
||||||
secretComment?: string;
|
|
||||||
skipMultilineEncoding?: boolean;
|
|
||||||
tagIds?: string[];
|
|
||||||
secretMetadata?: {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
} = {
|
|
||||||
secretKey: change.secretKey
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only include fields that actually changed
|
|
||||||
if (change.newSecretName) {
|
|
||||||
updatePayload.newSecretName = change.newSecretName;
|
|
||||||
}
|
|
||||||
if (change.secretValue !== undefined) {
|
|
||||||
updatePayload.secretValue = change.secretValue;
|
|
||||||
}
|
|
||||||
if (change.secretComment !== undefined) {
|
|
||||||
updatePayload.secretComment = change.secretComment;
|
|
||||||
}
|
|
||||||
if (change.skipMultilineEncoding !== undefined) {
|
|
||||||
updatePayload.skipMultilineEncoding = change.skipMultilineEncoding;
|
|
||||||
}
|
|
||||||
if (change.tags) {
|
|
||||||
updatePayload.tagIds = change.tags.map((tag) => tag.id);
|
|
||||||
}
|
|
||||||
if (change.secretMetadata) {
|
|
||||||
updatePayload.secretMetadata = change.secretMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatePayload;
|
|
||||||
});
|
|
||||||
const { data } = await apiRequest.post("/api/v1/pit/batch/commit", {
|
const { data } = await apiRequest.post("/api/v1/pit/batch/commit", {
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -497,7 +457,23 @@ 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:
|
||||||
|
pendingChanges.secrets
|
||||||
|
.filter((change) => change.type === PendingAction.Update)
|
||||||
|
.map((change: PendingSecretUpdate) => ({
|
||||||
|
secretKey: change.secretKey,
|
||||||
|
newSecretName: change.newSecretName,
|
||||||
|
secretValue: change.secretValue || change.existingSecret.value,
|
||||||
|
secretComment: change.secretComment || change.existingSecret.comment,
|
||||||
|
skipMultilineEncoding:
|
||||||
|
change.skipMultilineEncoding !== undefined
|
||||||
|
? change.skipMultilineEncoding
|
||||||
|
: change.existingSecret.skipMultilineEncoding,
|
||||||
|
tagIds:
|
||||||
|
change.tags?.map((tag) => tag.id) ||
|
||||||
|
change.existingSecret.tags?.map((tag) => tag.id),
|
||||||
|
secretMetadata: change.secretMetadata || change.existingSecret.secretMetadata
|
||||||
|
})) || [],
|
||||||
delete:
|
delete:
|
||||||
pendingChanges.secrets.filter((change) => change.type === PendingAction.Delete) || []
|
pendingChanges.secrets.filter((change) => change.type === PendingAction.Delete) || []
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -417,7 +417,9 @@ const Page = () => {
|
|||||||
imports?.length ||
|
imports?.length ||
|
||||||
dynamicSecrets?.length ||
|
dynamicSecrets?.length ||
|
||||||
secretRotations?.length ||
|
secretRotations?.length ||
|
||||||
noAccessSecretCount
|
noAccessSecretCount ||
|
||||||
|
pendingChanges.secrets.length ||
|
||||||
|
pendingChanges.folders.length
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -604,15 +606,16 @@ const Page = () => {
|
|||||||
secretMetadata: change.secretMetadata || mergedSecrets[updateIndex].secretMetadata,
|
secretMetadata: change.secretMetadata || mergedSecrets[updateIndex].secretMetadata,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
pendingAction: PendingAction.Update,
|
pendingAction: PendingAction.Update,
|
||||||
tags:
|
tags: change.tags
|
||||||
change.tags?.map((tag) => ({
|
? change.tags?.map((tag) => ({
|
||||||
id: tag.id,
|
id: tag.id,
|
||||||
slug: tag.slug,
|
slug: tag.slug,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
__v: 0
|
__v: 0
|
||||||
})) || []
|
})) || []
|
||||||
|
: mergedSecrets[updateIndex].tags
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -935,25 +938,34 @@ const Page = () => {
|
|||||||
folders?.length === 0 && <PermissionDeniedBanner />}
|
folders?.length === 0 && <PermissionDeniedBanner />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isDetailsLoading && totalCount > 0 && (
|
{!isDetailsLoading &&
|
||||||
<Pagination
|
(totalCount > 0 ||
|
||||||
startAdornment={
|
pendingChanges.secrets.length > 0 ||
|
||||||
<SecretTableResourceCount
|
pendingChanges.folders.length > 0) && (
|
||||||
dynamicSecretCount={totalDynamicSecretCount}
|
<Pagination
|
||||||
importCount={totalImportCount}
|
startAdornment={
|
||||||
secretCount={totalSecretCount}
|
<SecretTableResourceCount
|
||||||
folderCount={totalFolderCount}
|
dynamicSecretCount={totalDynamicSecretCount}
|
||||||
secretRotationCount={totalSecretRotationCount}
|
importCount={totalImportCount}
|
||||||
/>
|
secretCount={
|
||||||
}
|
totalSecretCount +
|
||||||
className="rounded-b-md border-t border-solid border-t-mineshaft-600"
|
pendingChanges.secrets.filter((s) => s.type === PendingAction.Create).length
|
||||||
count={totalCount}
|
}
|
||||||
page={page}
|
folderCount={
|
||||||
perPage={perPage}
|
totalFolderCount +
|
||||||
onChangePage={(newPage) => setPage(newPage)}
|
pendingChanges.folders.filter((f) => f.type === PendingAction.Create).length
|
||||||
onChangePerPage={handlePerPageChange}
|
}
|
||||||
/>
|
secretRotationCount={totalSecretRotationCount}
|
||||||
)}
|
/>
|
||||||
|
}
|
||||||
|
className="rounded-b-md border-t border-solid border-t-mineshaft-600"
|
||||||
|
count={totalCount + pendingChanges.secrets.length + pendingChanges.folders.length}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={(newPage) => setPage(newPage)}
|
||||||
|
onChangePerPage={handlePerPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={createSecretPopUp.isOpen}
|
isOpen={createSecretPopUp.isOpen}
|
||||||
onOpenChange={(state) => togglePopUp(PopUpNames.CreateSecretForm, state)}
|
onOpenChange={(state) => togglePopUp(PopUpNames.CreateSecretForm, state)}
|
||||||
|
|||||||
@@ -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 { createNotification } from "@app/components/notifications";
|
||||||
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
import { PendingAction } from "@app/hooks/api/secretFolders/types";
|
||||||
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ export interface PendingSecretUpdate extends BasePendingChange {
|
|||||||
tags?: { id: string; slug: string }[];
|
tags?: { id: string; slug: string }[];
|
||||||
originalSecretMetadata?: { key: string; value: string }[];
|
originalSecretMetadata?: { key: string; value: string }[];
|
||||||
secretMetadata?: { key: string; value: string }[];
|
secretMetadata?: { key: string; value: string }[];
|
||||||
|
existingSecret: SecretV3RawSanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingSecretDelete extends BasePendingChange {
|
export interface PendingSecretDelete extends BasePendingChange {
|
||||||
@@ -100,68 +102,6 @@ export interface BatchContext {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STORAGE_KEY = "infisical_pending_changes";
|
|
||||||
|
|
||||||
const generateContextKey = (workspaceId: string, environment: string, secretPath: string) => {
|
|
||||||
return `${workspaceId}_${environment}_${secretPath}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const savePendingChangesToStorage = (
|
|
||||||
changes: PendingChanges,
|
|
||||||
workspaceId: string,
|
|
||||||
environment: string,
|
|
||||||
secretPath: string
|
|
||||||
) => {
|
|
||||||
const key = `${STORAGE_KEY}_${generateContextKey(workspaceId, environment, secretPath)}`;
|
|
||||||
try {
|
|
||||||
sessionStorage.setItem(key, JSON.stringify(changes));
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("Failed to save pending changes to sessionStorage:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadPendingChangesFromStorage = (
|
|
||||||
workspaceId: string,
|
|
||||||
environment: string,
|
|
||||||
secretPath: string
|
|
||||||
): PendingChanges => {
|
|
||||||
const key = `${STORAGE_KEY}_${generateContextKey(workspaceId, environment, secretPath)}`;
|
|
||||||
const stored = sessionStorage.getItem(key);
|
|
||||||
if (!stored) return { secrets: [], folders: [] };
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(stored);
|
|
||||||
if (Array.isArray(parsed)) {
|
|
||||||
return {
|
|
||||||
secrets: parsed.filter(
|
|
||||||
(change: any) => !change.resourceType || change.resourceType === "secret"
|
|
||||||
),
|
|
||||||
folders: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
secrets: parsed.secrets || [],
|
|
||||||
folders: parsed.folders || []
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("Failed to parse pending changes from sessionStorage:", error);
|
|
||||||
return { secrets: [], folders: [] };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearPendingChangesFromStorage = (
|
|
||||||
workspaceId: string,
|
|
||||||
environment: string,
|
|
||||||
secretPath: string
|
|
||||||
) => {
|
|
||||||
const key = `${STORAGE_KEY}_${generateContextKey(workspaceId, environment, secretPath)}`;
|
|
||||||
try {
|
|
||||||
sessionStorage.removeItem(key);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("Failed to clear pending changes from sessionStorage:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeValue = (value: any): string | boolean | undefined => {
|
const normalizeValue = (value: any): string | boolean | undefined => {
|
||||||
if (value === null || value === undefined || value === "") {
|
if (value === null || value === undefined || value === "") {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -207,7 +147,8 @@ const cleanupRevertedSecretFields = (update: PendingSecretUpdate): PendingSecret
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
cleaned.secretComment !== undefined &&
|
cleaned.secretComment !== undefined &&
|
||||||
!areValuesEqual(cleaned.secretComment, cleaned.originalComment)
|
(!areValuesEqual(cleaned.secretComment, cleaned.originalComment) ||
|
||||||
|
!areValuesEqual(cleaned.secretComment, cleaned.existingSecret.comment))
|
||||||
) {
|
) {
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -302,6 +243,7 @@ const createSelectedSecretStore: StateCreator<CombinedState, [], [], SelectedSec
|
|||||||
type BatchModeState = {
|
type BatchModeState = {
|
||||||
isBatchMode: boolean;
|
isBatchMode: boolean;
|
||||||
pendingChanges: PendingChanges;
|
pendingChanges: PendingChanges;
|
||||||
|
pendingChangesByContext: Map<string, PendingChanges>;
|
||||||
existingSecretKeys: Set<string>;
|
existingSecretKeys: Set<string>;
|
||||||
existingFolderNames: Set<string>;
|
existingFolderNames: Set<string>;
|
||||||
currentContext: BatchContext | null;
|
currentContext: BatchContext | null;
|
||||||
@@ -315,16 +257,32 @@ type BatchModeState = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateContextKey = (workspaceId: string, environment: string, secretPath: string) => {
|
||||||
|
return `${workspaceId}_${environment}_${secretPath}`;
|
||||||
|
};
|
||||||
|
|
||||||
const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState> = (set, get) => ({
|
const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState> = (set, get) => ({
|
||||||
isBatchMode: true, // Always enabled by default
|
isBatchMode: true, // Always enabled by default
|
||||||
pendingChanges: { secrets: [], folders: [] },
|
pendingChanges: { secrets: [], folders: [] },
|
||||||
|
pendingChangesByContext: new Map<string, PendingChanges>(),
|
||||||
currentContext: null,
|
currentContext: null,
|
||||||
existingSecretKeys: new Set<string>(),
|
existingSecretKeys: new Set<string>(),
|
||||||
existingFolderNames: new Set<string>(),
|
existingFolderNames: new Set<string>(),
|
||||||
batchActions: {
|
batchActions: {
|
||||||
addPendingChange: (change: PendingChange, context: BatchContext) =>
|
addPendingChange: (change: PendingChange, context: BatchContext) =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newChanges = { ...state.pendingChanges };
|
const contextKey = generateContextKey(
|
||||||
|
context.workspaceId,
|
||||||
|
context.environment,
|
||||||
|
context.secretPath
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get existing changes for this context or create new empty state
|
||||||
|
const existingChanges = state.pendingChangesByContext.get(contextKey) || {
|
||||||
|
secrets: [],
|
||||||
|
folders: []
|
||||||
|
};
|
||||||
|
const newChanges = { ...existingChanges };
|
||||||
|
|
||||||
if (change.resourceType === "folder") {
|
if (change.resourceType === "folder") {
|
||||||
const existingFolder =
|
const existingFolder =
|
||||||
@@ -332,6 +290,10 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
newChanges.folders.some((f) => f.folderName === change.folderName);
|
newChanges.folders.some((f) => f.folderName === change.folderName);
|
||||||
|
|
||||||
if (change.type === PendingAction.Create && existingFolder) {
|
if (change.type === PendingAction.Create && existingFolder) {
|
||||||
|
createNotification({
|
||||||
|
text: "Another folder with same name already exists",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
return { pendingChanges: newChanges };
|
return { pendingChanges: newChanges };
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -339,6 +301,10 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
change.folderName !== change.originalFolderName &&
|
change.folderName !== change.originalFolderName &&
|
||||||
existingFolder
|
existingFolder
|
||||||
) {
|
) {
|
||||||
|
createNotification({
|
||||||
|
text: "Another folder with same name already exists",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
return { pendingChanges: newChanges };
|
return { pendingChanges: newChanges };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,9 +312,19 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
if (change.resourceType === "secret") {
|
if (change.resourceType === "secret") {
|
||||||
const existingSecret =
|
const existingSecret =
|
||||||
state.existingSecretKeys.has(change.secretKey) ||
|
state.existingSecretKeys.has(change.secretKey) ||
|
||||||
newChanges.secrets.some((s) => (s.secretKey === change.secretKey && s.type !== PendingAction.Create) || (change.type === PendingAction.Create && change.originalKey !== change.secretKey && s.secretKey === change.secretKey));
|
newChanges.secrets.some(
|
||||||
|
(s) =>
|
||||||
|
(s.secretKey === change.secretKey && s.type !== PendingAction.Create) ||
|
||||||
|
(change.type === PendingAction.Create &&
|
||||||
|
change.originalKey !== change.secretKey &&
|
||||||
|
s.secretKey === change.secretKey)
|
||||||
|
);
|
||||||
|
|
||||||
if (change.type === PendingAction.Create && existingSecret) {
|
if (change.type === PendingAction.Create && existingSecret) {
|
||||||
|
createNotification({
|
||||||
|
text: "Another secret with same name already exists",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
return { pendingChanges: newChanges };
|
return { pendingChanges: newChanges };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,6 +342,10 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
));
|
));
|
||||||
|
|
||||||
if (existingNewSecretName) {
|
if (existingNewSecretName) {
|
||||||
|
createNotification({
|
||||||
|
text: "Another secret with same name already exists",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
return { pendingChanges: newChanges };
|
return { pendingChanges: newChanges };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,7 +405,6 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
|
|
||||||
if (existingUpdateIndex >= 0) {
|
if (existingUpdateIndex >= 0) {
|
||||||
const existingUpdate = secretChanges[existingUpdateIndex] as PendingSecretUpdate;
|
const existingUpdate = secretChanges[existingUpdateIndex] as PendingSecretUpdate;
|
||||||
|
|
||||||
const mergedUpdate: PendingSecretUpdate = {
|
const mergedUpdate: PendingSecretUpdate = {
|
||||||
...existingUpdate,
|
...existingUpdate,
|
||||||
secretKey: existingUpdate.secretKey,
|
secretKey: existingUpdate.secretKey,
|
||||||
@@ -456,7 +435,7 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
change.secretMetadata !== undefined
|
change.secretMetadata !== undefined
|
||||||
? change.secretMetadata
|
? change.secretMetadata
|
||||||
: existingUpdate.secretMetadata,
|
: existingUpdate.secretMetadata,
|
||||||
|
existingSecret: existingUpdate.existingSecret,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -566,18 +545,40 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
newChanges.folders = folderChanges;
|
newChanges.folders = folderChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
savePendingChangesToStorage(
|
const updatedContextMap = new Map(state.pendingChangesByContext);
|
||||||
newChanges,
|
updatedContextMap.set(contextKey, newChanges);
|
||||||
context.workspaceId,
|
|
||||||
context.environment,
|
const currentChanges =
|
||||||
context.secretPath
|
contextKey ===
|
||||||
);
|
generateContextKey(
|
||||||
return { pendingChanges: newChanges };
|
state.currentContext?.workspaceId || context.workspaceId,
|
||||||
|
state.currentContext?.environment || context.environment,
|
||||||
|
state.currentContext?.secretPath || context.secretPath
|
||||||
|
)
|
||||||
|
? newChanges
|
||||||
|
: state.pendingChanges;
|
||||||
|
|
||||||
|
return {
|
||||||
|
pendingChangesByContext: updatedContextMap,
|
||||||
|
pendingChanges: currentChanges,
|
||||||
|
currentContext: context
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
removePendingChange: (changeId: string, resourceType: string, context: BatchContext) =>
|
removePendingChange: (changeId: string, resourceType: string, context: BatchContext) =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newChanges = { ...state.pendingChanges };
|
const contextKey = generateContextKey(
|
||||||
|
context.workspaceId,
|
||||||
|
context.environment,
|
||||||
|
context.secretPath
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get existing changes for this context
|
||||||
|
const existingChanges = state.pendingChangesByContext.get(contextKey) || {
|
||||||
|
secrets: [],
|
||||||
|
folders: []
|
||||||
|
};
|
||||||
|
const newChanges = { ...existingChanges };
|
||||||
|
|
||||||
if (resourceType === "secret") {
|
if (resourceType === "secret") {
|
||||||
newChanges.secrets = newChanges.secrets.filter((c) => c.id !== changeId);
|
newChanges.secrets = newChanges.secrets.filter((c) => c.id !== changeId);
|
||||||
@@ -585,28 +586,70 @@ const createBatchModeStore: StateCreator<CombinedState, [], [], BatchModeState>
|
|||||||
newChanges.folders = newChanges.folders.filter((c) => c.id !== changeId);
|
newChanges.folders = newChanges.folders.filter((c) => c.id !== changeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
savePendingChangesToStorage(
|
const updatedContextMap = new Map(state.pendingChangesByContext);
|
||||||
newChanges,
|
updatedContextMap.set(contextKey, newChanges);
|
||||||
context.workspaceId,
|
|
||||||
context.environment,
|
const isCurrentContext =
|
||||||
context.secretPath
|
state.currentContext &&
|
||||||
);
|
contextKey ===
|
||||||
return { pendingChanges: newChanges };
|
generateContextKey(
|
||||||
|
state.currentContext.workspaceId,
|
||||||
|
state.currentContext.environment,
|
||||||
|
state.currentContext.secretPath
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pendingChangesByContext: updatedContextMap,
|
||||||
|
pendingChanges: isCurrentContext ? newChanges : state.pendingChanges
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
loadPendingChanges: (context) => {
|
loadPendingChanges: (context) => {
|
||||||
const changes = loadPendingChangesFromStorage(
|
const contextKey = generateContextKey(
|
||||||
context.workspaceId,
|
context.workspaceId,
|
||||||
context.environment,
|
context.environment,
|
||||||
context.secretPath
|
context.secretPath
|
||||||
);
|
);
|
||||||
set({ pendingChanges: changes });
|
|
||||||
|
set((state) => {
|
||||||
|
const contextChanges = state.pendingChangesByContext.get(contextKey) || {
|
||||||
|
secrets: [],
|
||||||
|
folders: []
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
pendingChanges: contextChanges,
|
||||||
|
currentContext: context
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
clearAllPendingChanges: (context) => {
|
clearAllPendingChanges: (context) => {
|
||||||
clearPendingChangesFromStorage(context.workspaceId, context.environment, context.secretPath);
|
const contextKey = generateContextKey(
|
||||||
set({
|
context.workspaceId,
|
||||||
pendingChanges: { secrets: [], folders: [] }
|
context.environment,
|
||||||
|
context.secretPath
|
||||||
|
);
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
|
// Clear changes for this specific context
|
||||||
|
const updatedContextMap = new Map(state.pendingChangesByContext);
|
||||||
|
updatedContextMap.delete(contextKey);
|
||||||
|
|
||||||
|
// If this is the current context, also clear the active pending changes
|
||||||
|
const isCurrentContext =
|
||||||
|
state.currentContext &&
|
||||||
|
contextKey ===
|
||||||
|
generateContextKey(
|
||||||
|
state.currentContext.workspaceId,
|
||||||
|
state.currentContext.environment,
|
||||||
|
state.currentContext.secretPath
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pendingChangesByContext: updatedContextMap,
|
||||||
|
pendingChanges: isCurrentContext ? { secrets: [], folders: [] } : state.pendingChanges
|
||||||
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+11
-7
@@ -191,7 +191,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
|
|||||||
{hasKeyChange && (
|
{hasKeyChange && (
|
||||||
<ComparisonTableRow
|
<ComparisonTableRow
|
||||||
label="Key"
|
label="Key"
|
||||||
previousValue={<span className="font-mono">{change.secretKey}</span>}
|
previousValue={<span className="font-mono">{change.existingSecret.key}</span>}
|
||||||
newValue={<span className="font-mono">{change.newSecretName}</span>}
|
newValue={<span className="font-mono">{change.newSecretName}</span>}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -200,7 +200,7 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
|
|||||||
label="Value"
|
label="Value"
|
||||||
previousValue={
|
previousValue={
|
||||||
<div className="max-w-md break-all rounded">
|
<div className="max-w-md break-all rounded">
|
||||||
{change.originalValue || <span className="italic">(empty)</span>}
|
{change.existingSecret.value || <span className="italic">(empty)</span>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
newValue={
|
newValue={
|
||||||
@@ -213,28 +213,32 @@ const ChangeTable: React.FC<ChangeTableProps> = ({
|
|||||||
{hasCommentChange && (
|
{hasCommentChange && (
|
||||||
<ComparisonTableRow
|
<ComparisonTableRow
|
||||||
label="Comment"
|
label="Comment"
|
||||||
previousValue={change.originalComment || <span className="italic">(empty)</span>}
|
previousValue={
|
||||||
|
change.existingSecret.comment || <span className="italic">(empty)</span>
|
||||||
|
}
|
||||||
newValue={change.secretComment || <span className="italic">(empty)</span>}
|
newValue={change.secretComment || <span className="italic">(empty)</span>}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasMultilineChange && (
|
{hasMultilineChange && (
|
||||||
<ComparisonTableRow
|
<ComparisonTableRow
|
||||||
label="Multiline"
|
label="Multi-line Encoding"
|
||||||
previousValue={change.originalSkipMultilineEncoding ? "Enabled" : "Disabled"}
|
previousValue={
|
||||||
|
change.existingSecret.skipMultilineEncoding ? "Enabled" : "Disabled"
|
||||||
|
}
|
||||||
newValue={change.skipMultilineEncoding ? "Enabled" : "Disabled"}
|
newValue={change.skipMultilineEncoding ? "Enabled" : "Disabled"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasTagsChange && (
|
{hasTagsChange && (
|
||||||
<ComparisonTableRow
|
<ComparisonTableRow
|
||||||
label="Tags"
|
label="Tags"
|
||||||
previousValue={<TagsList tags={change.originalTags} />}
|
previousValue={<TagsList tags={change.existingSecret.tags} />}
|
||||||
newValue={<TagsList tags={change.tags} />}
|
newValue={<TagsList tags={change.tags} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasMetadataChange && (
|
{hasMetadataChange && (
|
||||||
<ComparisonTableRow
|
<ComparisonTableRow
|
||||||
label="Metadata"
|
label="Metadata"
|
||||||
previousValue={<MetadataList metadata={change.originalSecretMetadata} />}
|
previousValue={<MetadataList metadata={change.existingSecret.secretMetadata} />}
|
||||||
newValue={<MetadataList metadata={change.secretMetadata} />}
|
newValue={<MetadataList metadata={change.secretMetadata} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+1
-1
@@ -220,7 +220,7 @@ export const SecretItem = memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDirty && !isSubmitting && !isAutoSavingRef.current) {
|
if (isDirty && !isSubmitting && !isAutoSavingRef.current) {
|
||||||
const debounceTime = isPending ? 500 : 1500;
|
const debounceTime = 600;
|
||||||
|
|
||||||
autoSaveTimeoutRef.current = setTimeout(() => {
|
autoSaveTimeoutRef.current = setTimeout(() => {
|
||||||
autoSaveChanges(formValues);
|
autoSaveChanges(formValues);
|
||||||
|
|||||||
+11
-11
@@ -86,17 +86,16 @@ export const SecretListView = ({
|
|||||||
const selectedSecrets = useSelectedSecrets();
|
const selectedSecrets = useSelectedSecrets();
|
||||||
const { toggle: toggleSelectedSecret } = useSelectedSecretActions();
|
const { toggle: toggleSelectedSecret } = useSelectedSecretActions();
|
||||||
const { isBatchMode, pendingChanges } = useBatchMode();
|
const { isBatchMode, pendingChanges } = useBatchMode();
|
||||||
useNavigationBlocker(
|
useNavigationBlocker({
|
||||||
{
|
shouldBlock: pendingChanges.secrets.length > 0 || pendingChanges.folders.length > 0,
|
||||||
shouldBlock: pendingChanges.secrets.length > 0 || pendingChanges.folders.length > 0,
|
message:
|
||||||
message: "You have unsaved changes. If you leave now, your work will be lost. Do you want to continue?",
|
"You have unsaved changes. If you leave now, your work will be lost. Do you want to continue?",
|
||||||
context: {
|
context: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
const { addPendingChange } = useBatchModeActions();
|
const { addPendingChange } = useBatchModeActions();
|
||||||
|
|
||||||
const handleSecretOperation = async (
|
const handleSecretOperation = async (
|
||||||
@@ -368,7 +367,8 @@ export const SecretListView = ({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
resourceType: "secret"
|
resourceType: "secret",
|
||||||
|
existingSecret: orgSecret
|
||||||
};
|
};
|
||||||
|
|
||||||
addPendingChange(updateChange, {
|
addPendingChange(updateChange, {
|
||||||
|
|||||||
Reference in New Issue
Block a user