mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 19:26:19 +00:00
Addressed PR suggestions
This commit is contained in:
@@ -1,24 +1,34 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useBlocker } from "@tanstack/react-router";
|
import { useBlocker } from "@tanstack/react-router";
|
||||||
|
import { BatchContext, useBatchModeActions } from "@app/pages/secret-manager/SecretDashboardPage/SecretMainPage.store";
|
||||||
|
|
||||||
type TNavigationBlockerReturn = {
|
type TNavigationBlockerReturn = {
|
||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useNavigationBlocker = (
|
export const useNavigationBlocker = ({
|
||||||
shouldBlock: boolean,
|
shouldBlock = false,
|
||||||
message: string = "Are you sure you want to leave? You may have unsaved changes."
|
message = "Are you sure you want to leave? You may have unsaved changes.",
|
||||||
): TNavigationBlockerReturn => {
|
context
|
||||||
|
}: {
|
||||||
|
shouldBlock: boolean;
|
||||||
|
message: string;
|
||||||
|
context: BatchContext;
|
||||||
|
}): TNavigationBlockerReturn => {
|
||||||
|
const { clearAllPendingChanges } = useBatchModeActions();
|
||||||
const blockerFn = useCallback(() => {
|
const blockerFn = useCallback(() => {
|
||||||
if (!shouldBlock) return false;
|
if (!shouldBlock) return false;
|
||||||
|
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
const confirmed = window.confirm(message);
|
const confirmed = window.confirm(message);
|
||||||
|
if (confirmed) {
|
||||||
|
clearAllPendingChanges(context);
|
||||||
|
}
|
||||||
|
|
||||||
return !confirmed;
|
return !confirmed;
|
||||||
}, [shouldBlock, message]);
|
}, [shouldBlock, message, context]);
|
||||||
|
|
||||||
useBlocker(blockerFn);
|
useBlocker(blockerFn, shouldBlock);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isBlocked: shouldBlock
|
isBlocked: shouldBlock
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ 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);
|
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) {
|
||||||
return { pendingChanges: newChanges };
|
return { pendingChanges: newChanges };
|
||||||
|
|||||||
+109
-44
@@ -200,21 +200,9 @@ export const SecretItem = memo(
|
|||||||
|
|
||||||
isAutoSavingRef.current = true;
|
isAutoSavingRef.current = true;
|
||||||
try {
|
try {
|
||||||
const hasDirectReferences = importedBy?.some(({ folders }) =>
|
await onSaveSecret(secret, { ...secret, ...data }, () => {
|
||||||
folders?.some(({ secrets }) =>
|
reset();
|
||||||
secrets?.some(({ referencedSecretKey }) => referencedSecretKey === secret.key)
|
});
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasDirectReferences) {
|
|
||||||
await onSaveSecret(secret, { ...secret, ...data }, () => {
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await onSaveSecret(secret, { ...secret, ...data }, () => {
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Auto-save failed:", error);
|
console.error("Auto-save failed:", error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -268,7 +256,7 @@ export const SecretItem = memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isReadOnlySecret =
|
const isReadOnlySecret =
|
||||||
isReadOnly || isRotatedSecret || (isPending && pendingAction !== PendingAction.Update);
|
isReadOnly || isRotatedSecret || (isPending && pendingAction === PendingAction.Delete);
|
||||||
|
|
||||||
const { secretValueHidden } = secret;
|
const { secretValueHidden } = secret;
|
||||||
|
|
||||||
@@ -355,7 +343,6 @@ 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",
|
||||||
isInAutoSaveMode && "border-primary-400/75 bg-primary-900/20",
|
|
||||||
pendingAction === PendingAction.Delete && "border-l-2 border-l-red-600/75",
|
pendingAction === PendingAction.Delete && "border-l-2 border-l-red-600/75",
|
||||||
pendingAction === PendingAction.Update && "border-l-2 border-l-yellow-600/75",
|
pendingAction === PendingAction.Update && "border-l-2 border-l-yellow-600/75",
|
||||||
pendingAction === PendingAction.Create && "border-l-2 border-l-green-600/75"
|
pendingAction === PendingAction.Create && "border-l-2 border-l-green-600/75"
|
||||||
@@ -421,14 +408,6 @@ export const SecretItem = memo(
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
{isInAutoSaveMode && (
|
|
||||||
<Tooltip content="Auto-saving changes">
|
|
||||||
<div className="mr-2 flex items-center">
|
|
||||||
<Spinner className="h-3 w-3 text-primary" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{secretValueHidden && !isOverriden && (
|
{secretValueHidden && !isOverriden && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={`You do not have access to view the current value${canEditSecretValue && !isRotatedSecret ? ", but you can set a new one" : "."}`}
|
content={`You do not have access to view the current value${canEditSecretValue && !isRotatedSecret ? ", but you can set a new one" : "."}`}
|
||||||
@@ -716,6 +695,94 @@ export const SecretItem = memo(
|
|||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{pendingAction === PendingAction.Create && (
|
||||||
|
<div
|
||||||
|
key="actions"
|
||||||
|
className="flex h-full flex-shrink-0 self-start transition-all group-hover:gap-x-2"
|
||||||
|
>
|
||||||
|
<DropdownMenu>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName,
|
||||||
|
secretTags: selectedTagSlugs
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuTrigger asChild disabled={!isAllowed}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="tags"
|
||||||
|
variant="plain"
|
||||||
|
size="sm"
|
||||||
|
className={twMerge(
|
||||||
|
"w-0 overflow-hidden p-0 group-hover:w-5 data-[state=open]:w-5",
|
||||||
|
hasTagsApplied && "w-5 text-primary"
|
||||||
|
)}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
>
|
||||||
|
<Tooltip content="Tags">
|
||||||
|
<FontAwesomeSymbol
|
||||||
|
className="h-3.5 w-3.5"
|
||||||
|
symbolName={FontAwesomeSpriteName.Tags}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuLabel>Add tags to this secret</DropdownMenuLabel>
|
||||||
|
{tags.map((tag) => {
|
||||||
|
const { id: tagId, slug, color } = tag;
|
||||||
|
|
||||||
|
const isTagSelected = selectedTagsGroupById?.[tagId];
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => handleTagSelect(tag)}
|
||||||
|
key={`${secret.id}-${tagId}`}
|
||||||
|
icon={
|
||||||
|
isTagSelected && (
|
||||||
|
<FontAwesomeSymbol
|
||||||
|
symbolName={FontAwesomeSpriteName.CheckedCircle}
|
||||||
|
className="h-3 w-3"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconPos="right"
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div
|
||||||
|
className="mr-2 h-2 w-2 rounded-full"
|
||||||
|
style={{ background: color || "#bec2c8" }}
|
||||||
|
/>
|
||||||
|
{slug}
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<DropdownMenuItem className="px-1.5" asChild>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
className="w-full"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
leftIcon={
|
||||||
|
<FontAwesomeSymbol
|
||||||
|
symbolName={FontAwesomeSpriteName.Tags}
|
||||||
|
className="h-3 w-3"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={onCreateTag}
|
||||||
|
>
|
||||||
|
Create a tag
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{isInAutoSaveMode ? (
|
{isInAutoSaveMode ? (
|
||||||
@@ -726,11 +793,7 @@ export const SecretItem = memo(
|
|||||||
animate={{ x: 0, opacity: 1 }}
|
animate={{ x: 0, opacity: 1 }}
|
||||||
exit={{ x: -10, opacity: 0 }}
|
exit={{ x: -10, opacity: 0 }}
|
||||||
>
|
>
|
||||||
<Tooltip content="Auto-saving changes...">
|
<div className="h-12 w-12" />
|
||||||
<div className="flex items-center text-primary">
|
|
||||||
<Spinner className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : !isDirty ? (
|
) : !isDirty ? (
|
||||||
isPending ? (
|
isPending ? (
|
||||||
@@ -757,19 +820,21 @@ export const SecretItem = memo(
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<IconButton
|
<Tooltip content="Discard">
|
||||||
ariaLabel="delete-value"
|
<IconButton
|
||||||
variant="plain"
|
ariaLabel="delete-value"
|
||||||
colorSchema="danger"
|
variant="plain"
|
||||||
size="md"
|
colorSchema="danger"
|
||||||
className="p-0 opacity-0 group-hover:opacity-100"
|
size="md"
|
||||||
onClick={() => handleDeletePending(secret)}
|
className="p-0 opacity-0 group-hover:opacity-100"
|
||||||
>
|
onClick={() => handleDeletePending(secret)}
|
||||||
<FontAwesomeSymbol
|
>
|
||||||
symbolName={FontAwesomeSpriteName.Close}
|
<FontAwesomeSymbol
|
||||||
className="h-5 w-4"
|
symbolName={FontAwesomeSpriteName.Close}
|
||||||
/>
|
className="h-5 w-4"
|
||||||
</IconButton>
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -815,7 +880,7 @@ export const SecretItem = memo(
|
|||||||
isDisabled={!isAllowed || isRotatedSecret}
|
isDisabled={!isAllowed || isRotatedSecret}
|
||||||
>
|
>
|
||||||
<FontAwesomeSymbol
|
<FontAwesomeSymbol
|
||||||
symbolName={FontAwesomeSpriteName.Close}
|
symbolName={FontAwesomeSpriteName.Trash}
|
||||||
className="h-5 w-4"
|
className="h-5 w-4"
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
+10
-3
@@ -87,8 +87,15 @@ export const SecretListView = ({
|
|||||||
const { toggle: toggleSelectedSecret } = useSelectedSecretActions();
|
const { toggle: toggleSelectedSecret } = useSelectedSecretActions();
|
||||||
const { isBatchMode, pendingChanges } = useBatchMode();
|
const { isBatchMode, pendingChanges } = useBatchMode();
|
||||||
useNavigationBlocker(
|
useNavigationBlocker(
|
||||||
pendingChanges.secrets.length > 0 || pendingChanges.folders.length > 0,
|
{
|
||||||
"You have unsaved work. Are you sure you want to leave?"
|
shouldBlock: pendingChanges.secrets.length > 0 || pendingChanges.folders.length > 0,
|
||||||
|
message: "You have unsaved changes. If you leave now, your work will be lost. Do you want to continue?",
|
||||||
|
context: {
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const { addPendingChange } = useBatchModeActions();
|
const { addPendingChange } = useBatchModeActions();
|
||||||
|
|
||||||
@@ -316,7 +323,7 @@ export const SecretListView = ({
|
|||||||
secretValue: value || "",
|
secretValue: value || "",
|
||||||
secretComment: comment || "",
|
secretComment: comment || "",
|
||||||
skipMultilineEncoding: modSecret.skipMultilineEncoding || false,
|
skipMultilineEncoding: modSecret.skipMultilineEncoding || false,
|
||||||
tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || "" })) || [],
|
tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [],
|
||||||
secretMetadata: secretMetadata || [],
|
secretMetadata: secretMetadata || [],
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
resourceType: "secret",
|
resourceType: "secret",
|
||||||
|
|||||||
+6
-3
@@ -13,7 +13,8 @@ import {
|
|||||||
faLock,
|
faLock,
|
||||||
faProjectDiagram,
|
faProjectDiagram,
|
||||||
faShare,
|
faShare,
|
||||||
faTags
|
faTags,
|
||||||
|
faTrash
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -82,7 +83,8 @@ export enum FontAwesomeSpriteName {
|
|||||||
ReplicatedSecretKey = "secret-replicated",
|
ReplicatedSecretKey = "secret-replicated",
|
||||||
ShareSecret = "share-secret",
|
ShareSecret = "share-secret",
|
||||||
KeyLock = "key-lock",
|
KeyLock = "key-lock",
|
||||||
SecretReferenceTree = "secret-reference-tree"
|
SecretReferenceTree = "secret-reference-tree",
|
||||||
|
Trash = "trash"
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is an optimization technique
|
// this is an optimization technique
|
||||||
@@ -101,5 +103,6 @@ export const FontAwesomeSpriteSymbols = [
|
|||||||
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey },
|
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey },
|
||||||
{ icon: faShare, symbol: FontAwesomeSpriteName.ShareSecret },
|
{ icon: faShare, symbol: FontAwesomeSpriteName.ShareSecret },
|
||||||
{ icon: faLock, symbol: FontAwesomeSpriteName.KeyLock },
|
{ icon: faLock, symbol: FontAwesomeSpriteName.KeyLock },
|
||||||
{ icon: faProjectDiagram, symbol: FontAwesomeSpriteName.SecretReferenceTree }
|
{ icon: faProjectDiagram, symbol: FontAwesomeSpriteName.SecretReferenceTree },
|
||||||
|
{ icon: faTrash, symbol: FontAwesomeSpriteName.Trash }
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user