Merge pull request #4312 from Infisical/secret-sidebar-details-refactor

improvement(frontend): improve UX and design of secret sidebar/table row
This commit is contained in:
Scott Wilson
2025-08-07 17:53:30 -07:00
committed by GitHub
11 changed files with 861 additions and 893 deletions
@@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { z } from "zod"; import { z } from "zod";
import { SecretFoldersSchema, SecretImportsSchema, UsersSchema } from "@app/db/schemas"; import { SecretFoldersSchema, SecretImportsSchema, UsersSchema } from "@app/db/schemas";
import { RemindersSchema } from "@app/db/schemas/reminders";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema"; import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
@@ -628,7 +629,10 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secretValueHidden: z.boolean(), secretValueHidden: z.boolean(),
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(), secretMetadata: ResourceMetadataSchema.optional(),
tags: SanitizedTagSchema.array().optional() tags: SanitizedTagSchema.array().optional(),
reminder: RemindersSchema.extend({
recipients: z.string().array().optional()
}).nullish()
}) })
.array() .array()
.optional(), .optional(),
@@ -706,7 +710,11 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
let imports: Awaited<ReturnType<typeof server.services.secretImport.getImports>> | undefined; let imports: Awaited<ReturnType<typeof server.services.secretImport.getImports>> | undefined;
let folders: Awaited<ReturnType<typeof server.services.folder.getFolders>> | undefined; let folders: Awaited<ReturnType<typeof server.services.folder.getFolders>> | undefined;
let secrets: Awaited<ReturnType<typeof server.services.secret.getSecretsRaw>>["secrets"] | undefined; let secrets:
| (Awaited<ReturnType<typeof server.services.secret.getSecretsRaw>>["secrets"][number] & {
reminder: Awaited<ReturnType<typeof server.services.reminder.getRemindersForDashboard>>[string] | null;
})[]
| undefined;
let dynamicSecrets: Awaited<ReturnType<typeof server.services.dynamicSecret.listDynamicSecretsByEnv>> | undefined; let dynamicSecrets: Awaited<ReturnType<typeof server.services.dynamicSecret.listDynamicSecretsByEnv>> | undefined;
let secretRotations: let secretRotations:
| Awaited<ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>> | Awaited<ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>>
@@ -904,7 +912,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
}); });
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) { if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
secrets = ( const rawSecrets = (
await server.services.secret.getSecretsRaw({ await server.services.secret.getSecretsRaw({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
@@ -925,6 +933,15 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
includeMetadataInSearch: true includeMetadataInSearch: true
}) })
).secrets; ).secrets;
const reminders = await server.services.reminder.getRemindersForDashboard(
rawSecrets.map((secret) => secret.id)
);
secrets = rawSecrets.map((secret) => ({
...secret,
reminder: reminders[secret.id] ?? null
}));
} }
} }
} catch (error) { } catch (error) {
+26 -1
View File
@@ -124,10 +124,35 @@ export const reminderDALFactory = (db: TDbClient) => {
return reminders[0] || null; return reminders[0] || null;
}; };
const findSecretReminders = async (secretIds: string[], tx?: Knex) => {
const rawReminders = await (tx || db)(TableName.Reminder)
.whereIn(`${TableName.Reminder}.secretId`, secretIds)
.leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
.select(selectAllTableCols(TableName.Reminder))
.select(db.ref("userId").withSchema(TableName.ReminderRecipient));
const reminders = sqlNestRelationships({
data: rawReminders,
key: "id",
parentMapper: (el) => ({
_id: el.id,
...RemindersSchema.parse(el)
}),
childrenMapper: [
{
key: "userId",
label: "recipients" as const,
mapper: ({ userId }) => userId
}
]
});
return reminders;
};
return { return {
...reminderOrm, ...reminderOrm,
findSecretDailyReminders, findSecretDailyReminders,
findUpcomingReminders, findUpcomingReminders,
findSecretReminder findSecretReminder,
findSecretReminders
}; };
}; };
@@ -372,6 +372,21 @@ export const reminderServiceFactory = ({
}; };
}; };
const getRemindersForDashboard: TReminderServiceFactory["getRemindersForDashboard"] = async (secretIds) => {
// scott we don't need to check permissions/secret existence because these are the
// secrets from the dashboard that have already gone through these checks
const reminders = await reminderDAL.findSecretReminders(secretIds);
const reminderMap: Record<string, (typeof reminders)[number]> = {};
reminders.forEach((reminder) => {
if (reminder.secretId) reminderMap[reminder.secretId] = reminder;
});
return reminderMap;
};
return { return {
createReminder, createReminder,
getReminder, getReminder,
@@ -379,6 +394,7 @@ export const reminderServiceFactory = ({
deleteReminder, deleteReminder,
deleteReminderBySecretId, deleteReminderBySecretId,
batchCreateReminders, batchCreateReminders,
createReminderInternal createReminderInternal,
getRemindersForDashboard
}; };
}; };
@@ -103,4 +103,6 @@ export interface TReminderServiceFactory {
id: string; id: string;
created: boolean; created: boolean;
}>; }>;
getRemindersForDashboard: (secretIds: string[]) => Promise<Record<string, TReminder & { recipients: string[] }>>;
} }
@@ -125,7 +125,7 @@ export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Prop
if (isPending) { if (isPending) {
return ( return (
<div className="flex items-center justify-center py-4"> <div className="flex items-center justify-center py-4">
<Spinner size="xs" /> <Spinner className="text-mineshaft-400" />
</div> </div>
); );
} }
+19 -3
View File
@@ -13,6 +13,7 @@ export type DrawerContentProps = DialogPrimitive.DialogContentProps & {
subTitle?: ReactNode; subTitle?: ReactNode;
footerContent?: ReactNode; footerContent?: ReactNode;
onClose?: () => void; onClose?: () => void;
cardBodyClassName?: string;
} & VariantProps<typeof drawerContentVariation>; } & VariantProps<typeof drawerContentVariation>;
const drawerContentVariation = cva( const drawerContentVariation = cva(
@@ -32,7 +33,17 @@ const drawerContentVariation = cva(
export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>( export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
( (
{ children, title, subTitle, className, footerContent, direction = "right", onClose, ...props }, {
children,
title,
subTitle,
className,
footerContent,
direction = "right",
onClose,
cardBodyClassName,
...props
},
forwardedRef forwardedRef
) => ( ) => (
<DialogPrimitive.Portal> <DialogPrimitive.Portal>
@@ -47,11 +58,16 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
> >
<Card isRounded={false} className="dark h-full w-full"> <Card isRounded={false} className="dark h-full w-full">
{title && ( {title && (
<CardTitle subTitle={subTitle} className="px-4"> <CardTitle subTitle={subTitle} className="mb-0 px-4">
{title} {title}
</CardTitle> </CardTitle>
)} )}
<CardBody className="flex-grow overflow-y-auto overflow-x-hidden px-4 dark:[color-scheme:dark]"> <CardBody
className={twMerge(
"flex-grow overflow-y-auto overflow-x-hidden px-4 pt-4 dark:[color-scheme:dark]",
cardBodyClassName
)}
>
{children} {children}
</CardBody> </CardBody>
{footerContent && <CardFooter>{footerContent}</CardFooter>}{" "} {footerContent && <CardFooter>{footerContent}</CardFooter>}{" "}
+2 -1
View File
@@ -88,7 +88,8 @@ export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => {
path: el.secretPath, path: el.secretPath,
secretMetadata: el.secretMetadata, secretMetadata: el.secretMetadata,
isRotatedSecret: el.isRotatedSecret, isRotatedSecret: el.isRotatedSecret,
rotationId: el.rotationId rotationId: el.rotationId,
reminder: el.reminder
}; };
if (el.type === SecretType.Personal) { if (el.type === SecretType.Personal) {
+3
View File
@@ -1,4 +1,5 @@
import { ProjectPermissionActions } from "@app/context"; import { ProjectPermissionActions } from "@app/context";
import { Reminder } from "@app/hooks/api/reminders/types";
import { PendingAction } from "../secretFolders/types"; import { PendingAction } from "../secretFolders/types";
import type { WsTag } from "../tags/types"; import type { WsTag } from "../tags/types";
@@ -69,6 +70,7 @@ export type SecretV3RawSanitized = {
rotationId?: string; rotationId?: string;
isPending?: boolean; isPending?: boolean;
pendingAction?: PendingAction; pendingAction?: PendingAction;
reminder?: Reminder;
}; };
export type SecretV3Raw = { export type SecretV3Raw = {
@@ -94,6 +96,7 @@ export type SecretV3Raw = {
isRotatedSecret?: boolean; isRotatedSecret?: boolean;
rotationId?: string; rotationId?: string;
secretReminderRecipients?: SecretReminderRecipient[]; secretReminderRecipients?: SecretReminderRecipient[];
reminder?: Reminder;
}; };
export type SecretV3RawResponse = { export type SecretV3RawResponse = {
@@ -12,11 +12,9 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
FontAwesomeSymbol, FontAwesomeSymbol,
FormControl, FormControl,
GenericFieldLabel,
IconButton, IconButton,
Input, Input,
Modal,
ModalContent,
ModalTrigger,
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
@@ -41,16 +39,14 @@ import { AnimatePresence, motion } from "framer-motion";
import { memo, useCallback, useEffect, useRef } from "react"; import { memo, useCallback, useEffect, useRef } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Controller, useFieldArray, useForm } from "react-hook-form";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import {
hasSecretReference,
SecretReferenceTree
} from "@app/components/secrets/SecretReferenceDetails";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
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 { PendingAction } from "@app/hooks/api/secretFolders/types";
import { format } from "date-fns";
import { CreateReminderForm } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm";
import { import {
FontAwesomeSpriteName, FontAwesomeSpriteName,
formSchema, formSchema,
@@ -113,7 +109,8 @@ export const SecretItem = memo(
colWidth colWidth
}: Props) => { }: Props) => {
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
"editSecret" "editSecret",
"reminder"
] as const); ] as const);
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
@@ -186,7 +183,7 @@ export const SecretItem = memo(
name: "tags" name: "tags"
}); });
const isOverriden = const isOverridden =
overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified; overrideAction === SecretActionType.Created || overrideAction === SecretActionType.Modified;
const hasTagsApplied = Boolean(fields.length); const hasTagsApplied = Boolean(fields.length);
@@ -272,7 +269,7 @@ export const SecretItem = memo(
}, [isSecValueCopied]); }, [isSecValueCopied]);
const handleOverrideClick = () => { const handleOverrideClick = () => {
if (isOverriden) { if (isOverridden) {
// override need not be flagged delete if it was never saved in server // override need not be flagged delete if it was never saved in server
// meaning a new unsaved personal secret but user toggled back later // meaning a new unsaved personal secret but user toggled back later
const isUnsavedOverride = !secret.idOverride; const isUnsavedOverride = !secret.idOverride;
@@ -327,7 +324,7 @@ export const SecretItem = memo(
const copyTokenToClipboard = () => { const copyTokenToClipboard = () => {
const [overrideValue, value] = getValues(["value", "valueOverride"]); const [overrideValue, value] = getValues(["value", "valueOverride"]);
if (isOverriden) { if (isOverridden) {
navigator.clipboard.writeText(value as string); navigator.clipboard.writeText(value as string);
} else { } else {
navigator.clipboard.writeText(overrideValue as string); navigator.clipboard.writeText(overrideValue as string);
@@ -335,7 +332,7 @@ export const SecretItem = memo(
setIsSecValueCopied.on(); setIsSecValueCopied.on();
}; };
const isInAutoSaveMode = isDirty && !isSubmitting && !isOverriden; const isInAutoSaveMode = isDirty && !isSubmitting && !isOverridden;
return ( return (
<form onSubmit={handleSubmit(handleFormSubmit)}> <form onSubmit={handleSubmit(handleFormSubmit)}>
@@ -398,7 +395,7 @@ export const SecretItem = memo(
isReadOnly={isReadOnly || isRotatedSecret} isReadOnly={isReadOnly || isRotatedSecret}
autoCapitalization={currentWorkspace?.autoCapitalization} autoCapitalization={currentWorkspace?.autoCapitalization}
variant="plain" variant="plain"
isDisabled={isOverriden} isDisabled={isOverridden}
placeholder={error?.message} placeholder={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
onKeyUp={() => trigger("key")} onKeyUp={() => trigger("key")}
@@ -413,14 +410,14 @@ export const SecretItem = memo(
tabIndex={0} tabIndex={0}
role="button" role="button"
> >
{secretValueHidden && !isOverriden && !isPending && ( {secretValueHidden && !isOverridden && !isPending && (
<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" : "."}`}
> >
<FontAwesomeIcon className="pr-2" size="sm" icon={faEyeSlash} /> <FontAwesomeIcon className="pr-2" size="sm" icon={faEyeSlash} />
</Tooltip> </Tooltip>
)} )}
{isOverriden ? ( {isOverridden ? (
<Controller <Controller
name="valueOverride" name="valueOverride"
key="value-overriden" key="value-overriden"
@@ -445,7 +442,7 @@ export const SecretItem = memo(
isReadOnly={isReadOnlySecret} isReadOnly={isReadOnlySecret}
key="secret-value" key="secret-value"
isVisible={isVisible && (!secretValueHidden || isPending)} isVisible={isVisible && (!secretValueHidden || isPending)}
canEditButNotView={secretValueHidden && !isOverriden && !isPending} canEditButNotView={secretValueHidden && !isOverridden && !isPending}
environment={environment} environment={environment}
secretPath={secretPath} secretPath={secretPath}
{...field} {...field}
@@ -462,15 +459,15 @@ export const SecretItem = memo(
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"
> >
<Tooltip content="Copy secret"> <IconButton
<IconButton isDisabled={secret.secretValueHidden}
isDisabled={secret.secretValueHidden} ariaLabel="copy-value"
ariaLabel="copy-value" variant="plain"
variant="plain" size="sm"
size="sm" className="w-0 overflow-hidden p-0 group-hover:w-5"
className="w-0 overflow-hidden p-0 group-hover:w-5" onClick={copyTokenToClipboard}
onClick={copyTokenToClipboard} >
> <Tooltip content="Copy secret">
<FontAwesomeSymbol <FontAwesomeSymbol
className="h-3.5 w-3" className="h-3.5 w-3"
symbolName={ symbolName={
@@ -479,8 +476,8 @@ export const SecretItem = memo(
: FontAwesomeSpriteName.ClipboardCopy : FontAwesomeSpriteName.ClipboardCopy
} }
/> />
</IconButton> </Tooltip>
</Tooltip> </IconButton>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { a={subject(ProjectPermissionSub.Secrets, {
@@ -491,41 +488,47 @@ export const SecretItem = memo(
})} })}
> >
{(isAllowed) => ( {(isAllowed) => (
<Modal> <IconButton
<ModalTrigger asChild> className={twMerge(
<IconButton "w-0 overflow-hidden p-0 group-hover:w-5",
className="w-0 overflow-hidden p-0 group-hover:w-5" secret.reminder && "w-5 text-primary"
variant="plain" )}
size="md" onClick={() => handlePopUpOpen("reminder")}
ariaLabel="reference-tree" variant="plain"
isDisabled={!isAllowed || !hasSecretReference(secret?.value)} size="md"
> ariaLabel="Secret reminder"
<Tooltip isDisabled={!isAllowed || isOverridden}
content={ >
hasSecretReference(secret?.value) <Tooltip
? "Secret Reference Tree" className="max-w-2xl"
: "Secret does not contain references" content={
} isOverridden ? (
> "Unavailable with override"
<FontAwesomeSymbol ) : secret.reminder ? (
className="h-3.5 w-3.5" <div className="flex flex-col gap-y-1">
symbolName={FontAwesomeSpriteName.SecretReferenceTree} <GenericFieldLabel label="Reminder Date">
/> {secret.reminder.nextReminderDate
</Tooltip> ? format(
</IconButton> new Date(secret.reminder.nextReminderDate),
</ModalTrigger> "h:mm aa - MMM d yyyy"
<ModalContent )
title="Secret Reference Details" : undefined}
subTitle="Visual breakdown of secrets referenced by this secret." </GenericFieldLabel>
onOpenAutoFocus={(e) => e.preventDefault()} <GenericFieldLabel label="Message">
{secret.reminder.message}
</GenericFieldLabel>
</div>
) : (
"Set Secret Reminder"
)
}
> >
<SecretReferenceTree <FontAwesomeSymbol
secretPath={secretPath} className="h-3.5 w-3.5"
environment={environment} symbolName={FontAwesomeSpriteName.Reminder}
secretKey={secret?.key}
/> />
</ModalContent> </Tooltip>
</Modal> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<DropdownMenu> <DropdownMenu>
@@ -539,7 +542,7 @@ export const SecretItem = memo(
})} })}
> >
{(isAllowed) => ( {(isAllowed) => (
<DropdownMenuTrigger asChild disabled={!isAllowed}> <DropdownMenuTrigger asChild disabled={!isAllowed || isOverridden}>
<IconButton <IconButton
ariaLabel="tags" ariaLabel="tags"
variant="plain" variant="plain"
@@ -548,9 +551,9 @@ export const SecretItem = memo(
"w-0 overflow-hidden p-0 group-hover:w-5 data-[state=open]:w-5", "w-0 overflow-hidden p-0 group-hover:w-5 data-[state=open]:w-5",
hasTagsApplied && "w-5 text-primary" hasTagsApplied && "w-5 text-primary"
)} )}
isDisabled={!isAllowed} isDisabled={!isAllowed || isOverridden}
> >
<Tooltip content="Tags"> <Tooltip content={isOverridden ? "Unavailable with override" : "Tags"}>
<FontAwesomeSymbol <FontAwesomeSymbol
className="h-3.5 w-3.5" className="h-3.5 w-3.5"
symbolName={FontAwesomeSpriteName.Tags} symbolName={FontAwesomeSpriteName.Tags}
@@ -617,8 +620,6 @@ export const SecretItem = memo(
secretName, secretName,
secretTags: selectedTagSlugs secretTags: selectedTagSlugs
})} })}
renderTooltip
allowedLabel="Override"
> >
{(isAllowed) => ( {(isAllowed) => (
<IconButton <IconButton
@@ -629,13 +630,15 @@ export const SecretItem = memo(
onClick={handleOverrideClick} onClick={handleOverrideClick}
className={twMerge( className={twMerge(
"w-0 overflow-hidden p-0 group-hover:w-5", "w-0 overflow-hidden p-0 group-hover:w-5",
isOverriden && "w-5 text-primary" isOverridden && "w-5 text-primary"
)} )}
> >
<FontAwesomeSymbol <Tooltip content={`${isOverridden ? "Remove" : "Add"} Override`}>
symbolName={FontAwesomeSpriteName.Override} <FontAwesomeSymbol
className="h-3.5 w-3.5" symbolName={FontAwesomeSpriteName.Override}
/> className="h-3.5 w-3.5"
/>
</Tooltip>
</IconButton> </IconButton>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
@@ -650,7 +653,7 @@ export const SecretItem = memo(
})} })}
> >
{(isAllowed) => ( {(isAllowed) => (
<PopoverTrigger asChild disabled={!isAllowed}> <PopoverTrigger asChild disabled={!isAllowed || isOverridden}>
<IconButton <IconButton
className={twMerge( className={twMerge(
"w-0 overflow-hidden p-0 group-hover:w-5", "w-0 overflow-hidden p-0 group-hover:w-5",
@@ -659,9 +662,11 @@ export const SecretItem = memo(
variant="plain" variant="plain"
size="md" size="md"
ariaLabel="add-comment" ariaLabel="add-comment"
isDisabled={!isAllowed} isDisabled={!isAllowed || isOverridden}
> >
<Tooltip content="Comment"> <Tooltip
content={isOverridden ? "Unavailable with override" : "Comment"}
>
<FontAwesomeSymbol <FontAwesomeSymbol
className="h-3.5 w-3.5" className="h-3.5 w-3.5"
symbolName={FontAwesomeSpriteName.Comment} symbolName={FontAwesomeSpriteName.Comment}
@@ -851,12 +856,13 @@ 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="More"> <Tooltip content={isOverridden ? "Unavailable with override" : "More"}>
<IconButton <IconButton
ariaLabel="more" ariaLabel="more"
variant="plain" variant="plain"
size="md" size="md"
className="h-5 w-4 p-0 opacity-0 group-hover:opacity-100" className="h-5 w-4 p-0 opacity-0 group-hover:opacity-100"
isDisabled={isOverridden}
onClick={() => onDetailViewSecret(secret)} onClick={() => onDetailViewSecret(secret)}
> >
<FontAwesomeSymbol <FontAwesomeSymbol
@@ -874,7 +880,13 @@ export const SecretItem = memo(
secretTags: selectedTagSlugs secretTags: selectedTagSlugs
})} })}
renderTooltip renderTooltip
allowedLabel={isRotatedSecret ? "Cannot Delete Rotated Secret" : "Delete"} allowedLabel={
isOverridden
? "Unavailable with override"
: isRotatedSecret
? "Cannot Delete Rotated Secret"
: "Delete"
}
> >
{(isAllowed) => ( {(isAllowed) => (
<IconButton <IconButton
@@ -884,7 +896,7 @@ export const SecretItem = memo(
size="md" size="md"
className="p-0 opacity-0 group-hover:opacity-100" className="p-0 opacity-0 group-hover:opacity-100"
onClick={() => onDeleteSecret(secret)} onClick={() => onDeleteSecret(secret)}
isDisabled={!isAllowed || isRotatedSecret} isDisabled={!isAllowed || isRotatedSecret || isOverridden}
> >
<FontAwesomeSymbol <FontAwesomeSymbol
symbolName={FontAwesomeSpriteName.Trash} symbolName={FontAwesomeSpriteName.Trash}
@@ -959,6 +971,15 @@ export const SecretItem = memo(
</AnimatePresence> </AnimatePresence>
</div> </div>
</div> </div>
<CreateReminderForm
isOpen={popUp.reminder.isOpen}
onOpenChange={() => handlePopUpToggle("reminder")}
workspaceId={currentWorkspace.id}
environment={environment}
secretPath={secretPath}
secretId={secret?.id}
reminder={secret.reminder}
/>
<DeleteActionModal <DeleteActionModal
isOpen={popUp.editSecret.isOpen} isOpen={popUp.editSecret.isOpen}
deleteKey="confirm" deleteKey="confirm"
@@ -1,6 +1,7 @@
/* eslint-disable no-nested-ternary */ /* eslint-disable no-nested-ternary */
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
import { import {
faBell,
faCheck, faCheck,
faClock, faClock,
faClone, faClone,
@@ -11,7 +12,6 @@ import {
faEllipsis, faEllipsis,
faKey, faKey,
faLock, faLock,
faProjectDiagram,
faShare, faShare,
faTags, faTags,
faTrash faTrash
@@ -83,7 +83,7 @@ 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", Reminder = "secret-reminder",
Trash = "trash" Trash = "trash"
} }
@@ -103,6 +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: faBell, symbol: FontAwesomeSpriteName.Reminder },
{ icon: faTrash, symbol: FontAwesomeSpriteName.Trash } { icon: faTrash, symbol: FontAwesomeSpriteName.Trash }
]; ];