mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 08:27:53 +00:00
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:
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>}{" "}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
+104
-237
@@ -1,15 +1,16 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect } from "react";
|
||||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { subject } from "@casl/ability";
|
import { subject } from "@casl/ability";
|
||||||
import { faCircleQuestion, faEye } from "@fortawesome/free-regular-svg-icons";
|
import { faCircleQuestion, faEye } from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
faArrowRotateRight,
|
faArrowRotateRight,
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faClock,
|
|
||||||
faCopy,
|
faCopy,
|
||||||
faDesktop,
|
faDesktop,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faPlus,
|
faPlus,
|
||||||
|
faProjectDiagram,
|
||||||
|
faSearch,
|
||||||
faServer,
|
faServer,
|
||||||
faShare,
|
faShare,
|
||||||
faTag,
|
faTag,
|
||||||
@@ -26,6 +27,7 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { hasSecretReference, SecretReferenceTree } from "@app/components/secrets/SecretReferenceDetails";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Drawer,
|
Drawer,
|
||||||
@@ -39,30 +41,26 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
Switch,
|
Switch,
|
||||||
Tag,
|
Tag,
|
||||||
TextArea,
|
TextArea,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission, useWorkspace } from "@app/context";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSub,
|
|
||||||
useProjectPermission,
|
|
||||||
useWorkspace
|
|
||||||
} from "@app/context";
|
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
import { getProjectBaseURL } from "@app/helpers/project";
|
import { getProjectBaseURL } from "@app/helpers/project";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useGetSecretVersion } from "@app/hooks/api";
|
import { useGetSecretVersion } from "@app/hooks/api";
|
||||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { useGetReminder } from "@app/hooks/api/reminders";
|
|
||||||
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
||||||
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||||
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
||||||
import { camelCaseToSpaces } from "@app/lib/fn/string";
|
import { camelCaseToSpaces } from "@app/lib/fn/string";
|
||||||
|
|
||||||
import { CreateReminderForm } from "./CreateReminderForm";
|
|
||||||
import { HIDDEN_SECRET_VALUE } from "./SecretItem";
|
import { HIDDEN_SECRET_VALUE } from "./SecretItem";
|
||||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||||
|
|
||||||
@@ -102,7 +100,7 @@ export const SecretDetailSidebar = ({
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
formState: { isDirty, isSubmitting }
|
formState: { isDirty }
|
||||||
} = useForm<TFormSchema>({
|
} = useForm<TFormSchema>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
values: secret,
|
values: secret,
|
||||||
@@ -110,12 +108,12 @@ export const SecretDetailSidebar = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
|
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
|
||||||
"secretAccessUpgradePlan"
|
"secretAccessUpgradePlan",
|
||||||
|
"secretReferenceTree"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data: reminderData } = useGetReminder(secret?.id);
|
|
||||||
|
|
||||||
const tagFields = useFieldArray({
|
const tagFields = useFieldArray({
|
||||||
control,
|
control,
|
||||||
@@ -188,25 +186,6 @@ export const SecretDetailSidebar = ({
|
|||||||
secretKey
|
secretKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOverrideClick = () => {
|
|
||||||
if (isOverridden) {
|
|
||||||
// override need not be flagged delete if it was never saved in server
|
|
||||||
// meaning a new unsaved personal secret but user toggled back later
|
|
||||||
const isUnsavedOverride = !secret.idOverride;
|
|
||||||
setValue(
|
|
||||||
"overrideAction",
|
|
||||||
isUnsavedOverride ? secret?.overrideAction : SecretActionType.Deleted,
|
|
||||||
{
|
|
||||||
shouldDirty: !isUnsavedOverride
|
|
||||||
}
|
|
||||||
);
|
|
||||||
setValue("valueOverride", secret?.valueOverride, { shouldDirty: !isUnsavedOverride });
|
|
||||||
} else {
|
|
||||||
setValue("overrideAction", SecretActionType.Modified, { shouldDirty: true });
|
|
||||||
setValue("valueOverride", "", { shouldDirty: true });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTagSelect = (tag: WsTag) => {
|
const handleTagSelect = (tag: WsTag) => {
|
||||||
if (selectedTagsGroupById?.[tag.id]) {
|
if (selectedTagsGroupById?.[tag.id]) {
|
||||||
const tagPos = selectedTags.findIndex(({ id }) => id === tag.id);
|
const tagPos = selectedTags.findIndex(({ id }) => id === tag.id);
|
||||||
@@ -222,8 +201,6 @@ export const SecretDetailSidebar = ({
|
|||||||
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
||||||
};
|
};
|
||||||
|
|
||||||
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(
|
setValue(
|
||||||
"reminderRecipients",
|
"reminderRecipients",
|
||||||
@@ -289,34 +266,8 @@ export const SecretDetailSidebar = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDaysUntilReminder = useMemo(() => {
|
|
||||||
return (): string => {
|
|
||||||
const now = new Date();
|
|
||||||
now.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const target = new Date(reminderData?.nextReminderDate || "");
|
|
||||||
target.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const diffTime = target.getTime() - now.getTime();
|
|
||||||
const daysRemaining = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
||||||
|
|
||||||
return `Days until next reminder: ${daysRemaining}`;
|
|
||||||
};
|
|
||||||
}, [reminderData]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateReminderForm
|
|
||||||
isOpen={createReminderFormOpen}
|
|
||||||
onOpenChange={() => {
|
|
||||||
setCreateReminderFormOpen.toggle();
|
|
||||||
}}
|
|
||||||
workspaceId={currentWorkspace.id}
|
|
||||||
environment={environment}
|
|
||||||
secretPath={secretPath}
|
|
||||||
secretId={secret?.id}
|
|
||||||
reminder={reminderData}
|
|
||||||
/>
|
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.secretAccessUpgradePlan.isOpen}
|
isOpen={popUp.secretAccessUpgradePlan.isOpen}
|
||||||
onOpenChange={(isUpgradeModalOpen) =>
|
onOpenChange={(isUpgradeModalOpen) =>
|
||||||
@@ -324,28 +275,45 @@ export const SecretDetailSidebar = ({
|
|||||||
}
|
}
|
||||||
text="Secret access analysis is only available on Infisical's Pro plan and above."
|
text="Secret access analysis is only available on Infisical's Pro plan and above."
|
||||||
/>
|
/>
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp.secretReferenceTree.isOpen}
|
||||||
|
onOpenChange={(isSecretRefOpen) =>
|
||||||
|
handlePopUpToggle("secretReferenceTree", isSecretRefOpen)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title="Secret Reference Details"
|
||||||
|
subTitle="Visual breakdown of secrets referenced by this secret."
|
||||||
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<SecretReferenceTree
|
||||||
|
secretPath={secretPath}
|
||||||
|
environment={environment}
|
||||||
|
secretKey={popUp.secretReferenceTree.data}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
<Drawer
|
<Drawer
|
||||||
onOpenChange={(state) => {
|
onOpenChange={async (state) => {
|
||||||
if (isOpen && isDirty) {
|
if (isOpen && isDirty) {
|
||||||
if (
|
await handleSubmit(handleFormSubmit)();
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
window.confirm(
|
|
||||||
"You have edited the secret. Are you sure you want to reset the change?"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
onToggle(false);
|
|
||||||
reset();
|
|
||||||
} else return;
|
|
||||||
}
|
}
|
||||||
onToggle(state);
|
onToggle(state);
|
||||||
}}
|
}}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
>
|
>
|
||||||
<DrawerContent title={`Secret – ${secret?.key}`} className="thin-scrollbar">
|
<DrawerContent
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)} className="h-full">
|
title={`Secret – ${secret?.key}`}
|
||||||
<div className="flex h-full flex-col">
|
className="thin-scrollbar h-full"
|
||||||
<div className="flex flex-row">
|
cardBodyClassName="pb-0"
|
||||||
<div className="w-full">
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(handleFormSubmit)}
|
||||||
|
className="flex h-full flex-1 flex-col gap-y-4"
|
||||||
|
>
|
||||||
|
<span className="text-sm text-bunker-300">
|
||||||
|
Changes will automatically be applied for commit
|
||||||
|
</span>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionActions.Edit}
|
||||||
a={subject(ProjectPermissionSub.Secrets, {
|
a={subject(ProjectPermissionSub.Secrets, {
|
||||||
@@ -361,9 +329,8 @@ export const SecretDetailSidebar = ({
|
|||||||
key="secret-value"
|
key="secret-value"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<FormControl
|
<FormControl
|
||||||
className="flex-1"
|
className="mb-0"
|
||||||
helperText={
|
helperText={
|
||||||
cannotReadSecretValue ? (
|
cannotReadSecretValue ? (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
@@ -372,22 +339,22 @@ export const SecretDetailSidebar = ({
|
|||||||
className="mt-0.5 text-yellow-400"
|
className="mt-0.5 text-yellow-400"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
The value of this secret is hidden because you do not have the
|
The value of this secret is hidden because you do not have the read
|
||||||
read secret value permission.
|
secret value permission.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
label="Value"
|
label="Value"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-start gap-x-2">
|
||||||
<InfisicalSecretInput
|
<InfisicalSecretInput
|
||||||
isReadOnly={isReadOnly || !isAllowed || secret?.isRotatedSecret}
|
isReadOnly={isReadOnly || !isAllowed || secret?.isRotatedSecret}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
key="secret-value"
|
key="secret-value"
|
||||||
isDisabled={isOverridden}
|
isDisabled={isOverridden}
|
||||||
containerClassName="text-bunker-300 w-full hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
containerClassName="text-bunker-300 w-full hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||||
{...field}
|
{...field}
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
/>
|
/>
|
||||||
@@ -397,9 +364,7 @@ export const SecretDetailSidebar = ({
|
|||||||
? "This project does not allow secret sharing."
|
? "This project does not allow secret sharing."
|
||||||
: "You don't have permission to view the secret value."
|
: "You don't have permission to view the secret value."
|
||||||
}
|
}
|
||||||
isDisabled={
|
isDisabled={!secret?.secretValueHidden && currentWorkspace.secretSharing}
|
||||||
!secret?.secretValueHidden && currentWorkspace.secretSharing
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
isDisabled={
|
isDisabled={
|
||||||
@@ -420,15 +385,11 @@ export const SecretDetailSidebar = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</div>
|
<div className="rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
</div>
|
|
||||||
<div className="mb-2 rounded border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
|
|
||||||
<div className="mb-4 px-4">
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="skipMultilineEncoding"
|
name="skipMultilineEncoding"
|
||||||
@@ -467,58 +428,7 @@ export const SecretDetailSidebar = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="flex flex-col rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
|
||||||
className={`mb-4 w-full border-t border-mineshaft-600 ${isOverridden ? "block" : "hidden"}`}
|
|
||||||
/>
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionActions.Edit}
|
|
||||||
a={subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secretName: secretKey,
|
|
||||||
secretTags: selectTagSlugs
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<div className="flex items-center justify-between px-4 pb-4">
|
|
||||||
<span className="w-max text-sm text-mineshaft-300">
|
|
||||||
Override with a personal value
|
|
||||||
<Tooltip
|
|
||||||
content="Override the secret value with a personal value that does not get shared with other users and machines."
|
|
||||||
className="z-[100]"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faCircleQuestion} className="ml-2" />
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
<Switch
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
id="personal-override"
|
|
||||||
onCheckedChange={handleOverrideClick}
|
|
||||||
isChecked={isOverridden}
|
|
||||||
className="justify-start"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ProjectPermissionCan>
|
|
||||||
{isOverridden && (
|
|
||||||
<Controller
|
|
||||||
name="valueOverride"
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl label="Override Value" className="px-4">
|
|
||||||
<InfisicalSecretInput
|
|
||||||
isReadOnly={isReadOnly}
|
|
||||||
environment={environment}
|
|
||||||
secretPath={secretPath}
|
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="mb-4 mt-2 flex flex-col rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 px-0 pb-0">
|
|
||||||
<div
|
<div
|
||||||
className={`flex justify-between px-4 text-mineshaft-100 ${tagFields.fields.length > 0 ? "flex-col" : "flex-row"}`}
|
className={`flex justify-between px-4 text-mineshaft-100 ${tagFields.fields.length > 0 ? "flex-col" : "flex-row"}`}
|
||||||
>
|
>
|
||||||
@@ -530,7 +440,7 @@ export const SecretDetailSidebar = ({
|
|||||||
<div>
|
<div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div
|
<div
|
||||||
className={`grid auto-cols-min grid-flow-col gap-2 overflow-hidden ${tagFields.fields.length > 0 ? "pt-2" : ""}`}
|
className={`flex flex-wrap gap-2 overflow-hidden ${tagFields.fields.length > 0 ? "pt-2" : ""}`}
|
||||||
>
|
>
|
||||||
{tagFields.fields.map(({ tagColor, id: formId, slug }) => (
|
{tagFields.fields.map(({ tagColor, id: formId, slug }) => (
|
||||||
<Tag
|
<Tag
|
||||||
@@ -631,7 +541,6 @@ export const SecretDetailSidebar = ({
|
|||||||
<div
|
<div
|
||||||
className={`mb-4 w-full border-t border-mineshaft-600 ${tagFields.fields.length > 0 || metadataFormFields.fields.length > 0 ? "block" : "hidden"}`}
|
className={`mb-4 w-full border-t border-mineshaft-600 ${tagFields.fields.length > 0 || metadataFormFields.fields.length > 0 ? "block" : "hidden"}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`flex justify-between px-4 text-mineshaft-100 ${metadataFormFields.fields.length > 0 ? "flex-col" : "flex-row"}`}
|
className={`flex justify-between px-4 text-mineshaft-100 ${metadataFormFields.fields.length > 0 ? "flex-col" : "flex-row"}`}
|
||||||
>
|
>
|
||||||
@@ -715,51 +624,21 @@ export const SecretDetailSidebar = ({
|
|||||||
label="Comments & Notes"
|
label="Comments & Notes"
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
className="mb-0"
|
className="mb-0 h-[125px] pb-0"
|
||||||
>
|
>
|
||||||
<TextArea
|
<TextArea
|
||||||
className="border border-mineshaft-600 bg-bunker-800 text-sm"
|
className="mb-0 !resize-none border border-mineshaft-600 bg-mineshaft-900 text-sm"
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
rows={5}
|
placeholder="add a comment or note to this secret..."
|
||||||
|
rows={4}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormControl>
|
<div className="dark flex max-h-[24rem] flex-1 cursor-default flex-col text-sm text-bunker-300">
|
||||||
{reminderData && reminderData.nextReminderDate ? (
|
<div className="mb-0.5 text-mineshaft-400">Version History</div>
|
||||||
<div className="flex items-center justify-between px-2">
|
<div className="thin-scrollbar flex flex-1 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<FontAwesomeIcon className="text-primary-500" icon={faClock} />
|
|
||||||
<span className="text-sm text-bunker-300">{getDaysUntilReminder()}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
className="px-2 py-1"
|
|
||||||
variant="outline_bg"
|
|
||||||
onClick={() => setCreateReminderFormOpen.on()}
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="ml-1 flex items-center space-x-2">
|
|
||||||
<Button
|
|
||||||
className="w-full px-2 py-2 font-normal"
|
|
||||||
variant="outline_bg"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faClock} />}
|
|
||||||
onClick={() => setCreateReminderFormOpen.on()}
|
|
||||||
isDisabled={cannotEditSecret}
|
|
||||||
>
|
|
||||||
Create Reminder
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
<div className="mb-4flex-grow dark cursor-default text-sm text-bunker-300">
|
|
||||||
<div className="mb-2 pl-1">Version History</div>
|
|
||||||
<div className="thin-scrollbar flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
|
||||||
{secretVersion?.map(
|
{secretVersion?.map(
|
||||||
({ createdAt, secretValue, secretValueHidden, version, id, actor }) => (
|
({ createdAt, secretValue, secretValueHidden, version, id, actor }) => (
|
||||||
<div className="flex flex-row" key={id}>
|
<div className="flex flex-row" key={id}>
|
||||||
@@ -781,9 +660,7 @@ export const SecretDetailSidebar = ({
|
|||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<div className="flex w-fit flex-row text-sm">
|
<div className="flex w-fit flex-row text-sm">
|
||||||
Modified by:
|
Modified by:
|
||||||
<Tooltip
|
<Tooltip content={getModifiedByName(actor.actorType, actor.name)}>
|
||||||
content={getModifiedByName(actor.actorType, actor.name)}
|
|
||||||
>
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
<div
|
<div
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -925,9 +802,7 @@ export const SecretDetailSidebar = ({
|
|||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 w-8 rounded-md"
|
className="h-8 w-8 rounded-md"
|
||||||
onClick={() =>
|
onClick={() => setValue("value", secretValue, { shouldDirty: true })}
|
||||||
setValue("value", secretValue, { shouldDirty: true })
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faArrowRotateRight} />
|
<FontAwesomeIcon icon={faArrowRotateRight} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -939,8 +814,8 @@ export const SecretDetailSidebar = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
<div className="dark flex flex-col text-sm text-bunker-300">
|
||||||
<div className="mb-2 mt-4">
|
<div className="mb-0.5 text-mineshaft-400">
|
||||||
Access List
|
Access List
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content="Lists all users, machine identities, and groups that have been granted any permission level (read, create, edit, or delete) for this secret."
|
content="Lists all users, machine identities, and groups that have been granted any permission level (read, create, edit, or delete) for this secret."
|
||||||
@@ -949,24 +824,8 @@ export const SecretDetailSidebar = ({
|
|||||||
<FontAwesomeIcon icon={faCircleQuestion} className="ml-2" />
|
<FontAwesomeIcon icon={faCircleQuestion} className="ml-2" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{isPending && (
|
{secretAccessList ? (
|
||||||
<Button className="w-full px-2 py-1" variant="outline_bg" isDisabled>
|
<div className="flex flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
||||||
Analyze Access
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!isPending && secretAccessList === undefined && (
|
|
||||||
<Button
|
|
||||||
className="w-full px-2 py-1"
|
|
||||||
variant="outline_bg"
|
|
||||||
onClick={() => {
|
|
||||||
handlePopUpOpen("secretAccessUpgradePlan");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Analyze Access
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!isPending && secretAccessList && (
|
|
||||||
<div className="mb-4 flex max-h-72 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4 dark:[color-scheme:dark]">
|
|
||||||
{secretAccessList.users.length > 0 && (
|
{secretAccessList.users.length > 0 && (
|
||||||
<div className="pb-3">
|
<div className="pb-3">
|
||||||
<div className="mb-2 font-bold">Users</div>
|
<div className="mb-2 font-bold">Users</div>
|
||||||
@@ -974,6 +833,7 @@ export const SecretDetailSidebar = ({
|
|||||||
{secretAccessList.users.map((user) => (
|
{secretAccessList.users.map((user) => (
|
||||||
<div className="rounded-md bg-bunker-500">
|
<div className="rounded-md bg-bunker-500">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
side="left"
|
||||||
content={user.allowedActions
|
content={user.allowedActions
|
||||||
.map((action) => camelCaseToSpaces(action))
|
.map((action) => camelCaseToSpaces(action))
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
@@ -987,7 +847,7 @@ export const SecretDetailSidebar = ({
|
|||||||
projectId: currentWorkspace.id,
|
projectId: currentWorkspace.id,
|
||||||
membershipId: user.membershipId
|
membershipId: user.membershipId
|
||||||
}}
|
}}
|
||||||
className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-primary"
|
className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
{user.name}
|
{user.name}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -1004,6 +864,7 @@ export const SecretDetailSidebar = ({
|
|||||||
{secretAccessList.identities.map((identity) => (
|
{secretAccessList.identities.map((identity) => (
|
||||||
<div className="rounded-md bg-bunker-500">
|
<div className="rounded-md bg-bunker-500">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
side="left"
|
||||||
content={identity.allowedActions
|
content={identity.allowedActions
|
||||||
.map(
|
.map(
|
||||||
(action) =>
|
(action) =>
|
||||||
@@ -1020,7 +881,7 @@ export const SecretDetailSidebar = ({
|
|||||||
projectId: currentWorkspace.id,
|
projectId: currentWorkspace.id,
|
||||||
identityId: identity.id
|
identityId: identity.id
|
||||||
}}
|
}}
|
||||||
className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-primary"
|
className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
{identity.name}
|
{identity.name}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -1037,6 +898,7 @@ export const SecretDetailSidebar = ({
|
|||||||
{secretAccessList.groups.map((group) => (
|
{secretAccessList.groups.map((group) => (
|
||||||
<div className="rounded-md bg-bunker-500">
|
<div className="rounded-md bg-bunker-500">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
side="left"
|
||||||
content={group.allowedActions
|
content={group.allowedActions
|
||||||
.map(
|
.map(
|
||||||
(action) =>
|
(action) =>
|
||||||
@@ -1050,7 +912,7 @@ export const SecretDetailSidebar = ({
|
|||||||
params={{
|
params={{
|
||||||
groupId: group.id
|
groupId: group.id
|
||||||
}}
|
}}
|
||||||
className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-primary"
|
className="text-secondary/80 rounded-md border border-mineshaft-600 bg-mineshaft-700 px-1 py-0.5 text-sm hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
{group.name}
|
{group.name}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -1061,33 +923,41 @@ export const SecretDetailSidebar = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : (
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-4">
|
|
||||||
<div className="mb-4 flex items-center space-x-4">
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionActions.Edit}
|
|
||||||
a={subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secretName: secretKey,
|
|
||||||
secretTags: selectTagSlugs
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Button
|
<Button
|
||||||
isFullWidth
|
className="w-full"
|
||||||
type="submit"
|
leftIcon={<FontAwesomeIcon icon={faSearch} />}
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
isDisabled={isSubmitting || !isDirty || !isAllowed}
|
isDisabled={isPending}
|
||||||
isLoading={isSubmitting}
|
isLoading={isPending}
|
||||||
|
onClick={() => handlePopUpOpen("secretAccessUpgradePlan")}
|
||||||
>
|
>
|
||||||
Apply Changes
|
Analyze Access
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="mt-auto flex items-center space-x-2 pb-4">
|
||||||
<Tooltip content="Copy Secret ID">
|
<Tooltip
|
||||||
|
content={
|
||||||
|
hasSecretReference(secret?.value)
|
||||||
|
? undefined
|
||||||
|
: "Secret does not contain any references."
|
||||||
|
}
|
||||||
|
className="z-[100] text-center"
|
||||||
|
>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
variant="outline_bg"
|
||||||
|
isDisabled={cannotReadSecretValue || !hasSecretReference(secret?.value)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faProjectDiagram} />}
|
||||||
|
onClick={() => handlePopUpOpen("secretReferenceTree", secretKey)}
|
||||||
|
>
|
||||||
|
Secret Reference Tree
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Copy Secret ID" className="z-[100]">
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
ariaLabel="Copy Secret ID"
|
ariaLabel="Copy Secret ID"
|
||||||
@@ -1114,12 +984,12 @@ export const SecretDetailSidebar = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Tooltip content="Delete Secret">
|
<Tooltip content="Delete Secret" align="end" className="z-[100]">
|
||||||
<IconButton
|
<IconButton
|
||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
ariaLabel="Delete Secret"
|
ariaLabel="Delete Secret"
|
||||||
className="border border-mineshaft-600 bg-mineshaft-700 hover:border-red-500/70 hover:bg-red-600/20"
|
className="h-min border border-mineshaft-600 bg-mineshaft-700 hover:border-red-500/70 hover:bg-red-600/20"
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
onClick={onDeleteSecret}
|
onClick={onDeleteSecret}
|
||||||
>
|
>
|
||||||
@@ -1129,9 +999,6 @@ export const SecretDetailSidebar = ({
|
|||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
+73
-52
@@ -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,7 +459,6 @@ 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"
|
||||||
@@ -471,6 +467,7 @@ export const SecretItem = memo(
|
|||||||
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>
|
|
||||||
<ModalTrigger asChild>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
className="w-0 overflow-hidden p-0 group-hover:w-5"
|
className={twMerge(
|
||||||
|
"w-0 overflow-hidden p-0 group-hover:w-5",
|
||||||
|
secret.reminder && "w-5 text-primary"
|
||||||
|
)}
|
||||||
|
onClick={() => handlePopUpOpen("reminder")}
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="md"
|
size="md"
|
||||||
ariaLabel="reference-tree"
|
ariaLabel="Secret reminder"
|
||||||
isDisabled={!isAllowed || !hasSecretReference(secret?.value)}
|
isDisabled={!isAllowed || isOverridden}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
className="max-w-2xl"
|
||||||
content={
|
content={
|
||||||
hasSecretReference(secret?.value)
|
isOverridden ? (
|
||||||
? "Secret Reference Tree"
|
"Unavailable with override"
|
||||||
: "Secret does not contain references"
|
) : secret.reminder ? (
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<GenericFieldLabel label="Reminder Date">
|
||||||
|
{secret.reminder.nextReminderDate
|
||||||
|
? format(
|
||||||
|
new Date(secret.reminder.nextReminderDate),
|
||||||
|
"h:mm aa - MMM d yyyy"
|
||||||
|
)
|
||||||
|
: undefined}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Message">
|
||||||
|
{secret.reminder.message}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
"Set Secret Reminder"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FontAwesomeSymbol
|
<FontAwesomeSymbol
|
||||||
className="h-3.5 w-3.5"
|
className="h-3.5 w-3.5"
|
||||||
symbolName={FontAwesomeSpriteName.SecretReferenceTree}
|
symbolName={FontAwesomeSpriteName.Reminder}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</ModalTrigger>
|
|
||||||
<ModalContent
|
|
||||||
title="Secret Reference Details"
|
|
||||||
subTitle="Visual breakdown of secrets referenced by this secret."
|
|
||||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<SecretReferenceTree
|
|
||||||
secretPath={secretPath}
|
|
||||||
environment={environment}
|
|
||||||
secretKey={secret?.key}
|
|
||||||
/>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)}
|
)}
|
||||||
</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"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<Tooltip content={`${isOverridden ? "Remove" : "Add"} Override`}>
|
||||||
<FontAwesomeSymbol
|
<FontAwesomeSymbol
|
||||||
symbolName={FontAwesomeSpriteName.Override}
|
symbolName={FontAwesomeSpriteName.Override}
|
||||||
className="h-3.5 w-3.5"
|
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={isOverridden ? "Unavailable with override" : "Comment"}
|
||||||
>
|
>
|
||||||
<Tooltip content="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"
|
||||||
|
|||||||
+3
-3
@@ -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 }
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user