Merge remote-tracking branch 'origin' into stv3-org-roles

This commit is contained in:
Tuan Dang
2023-12-04 16:14:31 +07:00
40 changed files with 2029 additions and 331 deletions
@@ -139,6 +139,8 @@ export const useUpdateSecretV3 = ({
latestFileKey,
tags,
secretComment,
secretReminderRepeatDays,
secretReminderNote,
newSecretName,
skipMultilineEncoding
}) => {
@@ -157,6 +159,8 @@ export const useUpdateSecretV3 = ({
workspaceId,
environment,
type,
secretReminderNote,
secretReminderRepeatDays,
secretPath,
secretId,
...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment),
@@ -69,6 +69,8 @@ export const decryptSecrets = (
value: secretValue,
tags: encSecret.tags,
comment: secretComment,
reminderRepeatDays: encSecret.secretReminderRepeatDays,
reminderNote: encSecret.secretReminderNote,
createdAt: encSecret.createdAt,
updatedAt: encSecret.updatedAt,
version: encSecret.version,
+6
View File
@@ -20,6 +20,8 @@ export type EncryptedSecret = {
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null;
tags: WsTag[];
};
@@ -29,6 +31,8 @@ export type DecryptedSecret = {
key: string;
value: string;
comment: string;
reminderRepeatDays?: number | null;
reminderNote?: string | null;
tags: WsTag[];
createdAt: string;
updatedAt: string;
@@ -112,6 +116,8 @@ export type TUpdateSecretsV3DTO = {
secretId?: string;
secretValue: string;
secretComment?: string;
secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null;
tags?: string[];
};
@@ -0,0 +1,129 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { faClock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { twMerge } from "tailwind-merge";
import { z } from "zod";
import { Button, FormControl, Input, Modal, ModalContent, TextArea } from "@app/components/v2";
const ReminderFormSchema = z.object({
note: z.string().optional(),
days: z
.number()
.min(1, { message: "Must be at least 1 day" })
.max(365, { message: "Must be less than 365 days" })
});
export type TReminderFormSchema = z.infer<typeof ReminderFormSchema>;
interface ReminderFormProps {
isOpen: boolean;
onOpenChange: (isOpen: boolean, data?: TReminderFormSchema) => void;
}
export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps) => {
const {
register,
control,
reset,
setValue,
handleSubmit,
formState: { isSubmitting }
} = useForm<TReminderFormSchema>({
resolver: zodResolver(ReminderFormSchema)
});
const handleFormSubmit = async (data: TReminderFormSchema) => {
console.log(data);
onOpenChange(false, data);
};
useEffect(() => {
if (isOpen) {
reset();
}
}, [isOpen]);
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Create secret reminder"
// ? QUESTION: Should this specifically say its for secret rotation?
// ? Or should we be call it something more generic?
subTitle={
<div>
Set up a reminder for when this secret should be rotated. Everyone with access to this
project will be notified when the reminder is triggered.
</div>
}
>
<form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="space-y-2">
<div>
<Controller
control={control}
name="days"
render={({ field, fieldState }) => (
<>
<FormControl
className="mb-0"
label="How many days between"
isError={Boolean(fieldState.error)}
errorText={fieldState.error?.message || ""}
>
<Input
onChange={(el) => setValue("days", parseInt(el.target.value, 10))}
type="number"
placeholder="31"
/>
</FormControl>
<div
className={twMerge(
"mt-2 ml-1 text-xs",
field.value ? "opacity-60" : "opacity-0"
)}
>
Every {field.value > 1 ? `${field.value} days` : "day"}
</div>
</>
)}
/>
</div>
<FormControl label="Note" className="mb-0">
<TextArea
placeholder="Remember to rotate the AWS secret every month."
className="border border-mineshaft-600 text-sm"
rows={8}
reSize="none"
cols={30}
{...register("note")}
/>
</FormControl>
</div>
<div className="mt-7 flex items-center">
<Button
isDisabled={isSubmitting}
isLoading={isSubmitting}
key="layout-create-project-submit"
className="mr-4"
leftIcon={<FontAwesomeIcon icon={faClock} />}
type="submit"
>
Create reminder
</Button>
<Button
key="layout-cancel-create-project"
onClick={() => onOpenChange(false)}
variant="plain"
colorSchema="secondary"
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -1,9 +1,11 @@
/* eslint-disable simple-import-sort/imports */
import { memo, useEffect } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { subject } from "@casl/ability";
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
import {
faCheck,
faClock,
faClose,
faCodeBranch,
faComment,
@@ -17,7 +19,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
@@ -49,6 +50,7 @@ import { DecryptedSecret } from "@app/hooks/api/secrets/types";
import { WsTag } from "@app/hooks/api/types";
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
import { CreateReminderForm } from "./CreateReminderForm";
type Props = {
secret: DecryptedSecret;
@@ -111,6 +113,7 @@ export const SecretItem = memo(
const overrideAction = watch("overrideAction");
const hasComment = Boolean(watch("comment"));
const hasReminder = Boolean(watch("reminderRepeatDays"));
const selectedTags = watch("tags", []);
const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>(
@@ -123,6 +126,7 @@ export const SecretItem = memo(
});
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
useEffect(() => {
let timer: NodeJS.Timeout;
if (isSecValueCopied) {
@@ -148,6 +152,10 @@ export const SecretItem = memo(
}
);
setValue("valueOverride", secret?.valueOverride, { shouldDirty: !isUnsavedOverride });
setValue("reminderRepeatDays", secret?.reminderRepeatDays, {
shouldDirty: !isUnsavedOverride
});
setValue("reminderNote", secret?.reminderNote, { shouldDirty: !isUnsavedOverride });
} else {
reset();
setValue("overrideAction", SecretActionType.Modified, { shouldDirty: true });
@@ -181,308 +189,349 @@ export const SecretItem = memo(
};
return (
<form onSubmit={handleSubmit(handleFormSubmit)}>
<div
className={twMerge(
"shadow-none border-b border-mineshaft-600 bg-mineshaft-800 hover:bg-mineshaft-700",
isDirty && "border-primary-400/50"
)}
>
<div className="flex group">
<div
className={twMerge(
"flex items-center justify-center w-11 px-4 py-3 h-11",
isDirty && "text-primary"
)}
>
<Checkbox
id={`checkbox-${secret._id}`}
isChecked={isSelected}
onCheckedChange={() => onToggleSecretSelect(secret._id)}
className={twMerge("group-hover:flex hidden ml-3", isSelected && "flex")}
/>
<FontAwesomeIcon
icon={faKey}
className={twMerge("group-hover:hidden block ml-3", isSelected && "hidden")}
/>
</div>
<div className="w-80 h-11 flex items-center px-4 py-2 flex-shrink-0">
<Controller
name="key"
control={control}
render={({ field }) => (
<Input
autoComplete="off"
isReadOnly={isReadOnly}
autoCapitalization={currentWorkspace?.autoCapitalization}
variant="plain"
isDisabled={isOverriden}
{...field}
className="w-full focus:text-bunker-100 focus:ring-transparent px-0"
<>
<CreateReminderForm
isOpen={createReminderFormOpen}
onOpenChange={(_, data) => {
setCreateReminderFormOpen.toggle();
if (data) {
setValue("reminderRepeatDays", data.days, { shouldDirty: true });
setValue("reminderNote", data.note, { shouldDirty: true });
}
}}
/>
<form onSubmit={handleSubmit(handleFormSubmit)}>
<div
className={twMerge(
"border-b border-mineshaft-600 bg-mineshaft-800 shadow-none hover:bg-mineshaft-700",
isDirty && "border-primary-400/50"
)}
>
<div className="group flex">
<div
className={twMerge(
"flex h-11 w-11 items-center justify-center px-4 py-3",
isDirty && "text-primary"
)}
>
<Checkbox
id={`checkbox-${secret._id}`}
isChecked={isSelected}
onCheckedChange={() => onToggleSecretSelect(secret._id)}
className={twMerge("ml-3 hidden group-hover:flex", isSelected && "flex")}
/>
<FontAwesomeIcon
icon={faKey}
className={twMerge("ml-3 block group-hover:hidden", isSelected && "hidden")}
/>
</div>
<div className="flex h-11 w-80 flex-shrink-0 items-center px-4 py-2">
<Controller
name="key"
control={control}
render={({ field }) => (
<Input
autoComplete="off"
isReadOnly={isReadOnly}
autoCapitalization={currentWorkspace?.autoCapitalization}
variant="plain"
isDisabled={isOverriden}
{...field}
className="w-full px-0 focus:text-bunker-100 focus:ring-transparent"
/>
)}
/>
</div>
<div
className="flex flex-grow items-center border-x border-mineshaft-600 py-1 pl-4 pr-2"
tabIndex={0}
role="button"
>
{isOverriden ? (
<Controller
name="valueOverride"
key="value-overriden"
control={control}
render={({ field }) => (
<SecretInput
key="value-overriden"
isVisible={isVisible}
isReadOnly={isReadOnly}
{...field}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
/>
)}
/>
) : (
<Controller
name="value"
key="secret-value"
control={control}
render={({ field }) => (
<SecretInput
isReadOnly={isReadOnly}
key="secret-value"
isVisible={isVisible}
{...field}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
/>
)}
/>
)}
/>
</div>
<div
className="flex-grow flex items-center border-x border-mineshaft-600 pl-4 pr-2 py-1"
tabIndex={0}
role="button"
>
{isOverriden ? (
<Controller
name="valueOverride"
key="value-overriden"
control={control}
render={({ field }) => (
<SecretInput
key="value-overriden"
isVisible={isVisible}
isReadOnly={isReadOnly}
{...field}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
/>
)}
/>
) : (
<Controller
name="value"
key="secret-value"
control={control}
render={({ field }) => (
<SecretInput
isReadOnly={isReadOnly}
key="secret-value"
isVisible={isVisible}
{...field}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
/>
)}
/>
)}
<div key="actions" className="h-8 flex self-start flex-shrink-0 transition-all">
<Tooltip content="Copy secret">
<IconButton
ariaLabel="copy-value"
variant="plain"
size="sm"
className="w-0 group-hover:w-5 group-hover:mr-2 overflow-hidden p-0"
onClick={copyTokenToClipboard}
>
<FontAwesomeIcon icon={isSecValueCopied ? faCheck : faCopy} />
</IconButton>
</Tooltip>
<DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
>
{(isAllowed) => (
<DropdownMenuTrigger asChild disabled={!isAllowed}>
<IconButton
ariaLabel="tags"
variant="plain"
size="sm"
className={twMerge(
"w-0 group-hover:w-5 group-hover:mr-2 overflow-hidden p-0 data-[state=open]:w-5",
hasTagsApplied && "w-5 text-primary"
)}
isDisabled={!isAllowed}
>
<Tooltip content="Tags">
<FontAwesomeIcon icon={faTags} />
</Tooltip>
</IconButton>
</DropdownMenuTrigger>
)}
</ProjectPermissionCan>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Apply tags to this secrets</DropdownMenuLabel>
{tags.map((tag) => {
const { _id: tagId, name, tagColor } = tag;
const isTagSelected = selectedTagsGroupById?.[tagId];
return (
<DropdownMenuItem
onClick={() => handleTagSelect(tag)}
key={tagId}
icon={isTagSelected && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center">
<div
className="w-2 h-2 rounded-full mr-2"
style={{ background: tagColor || "#bec2c8" }}
/>
{name}
</div>
</DropdownMenuItem>
);
})}
<DropdownMenuItem className="px-1.5">
<Button
size="xs"
className="w-full"
colorSchema="primary"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faTag} />}
onClick={onCreateTag}
>
Create a tag
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
renderTooltip
allowedLabel="Override"
>
{(isAllowed) => (
<div key="actions" className="flex h-8 flex-shrink-0 self-start transition-all">
<Tooltip content="Copy secret">
<IconButton
ariaLabel="override-value"
isDisabled={!isAllowed}
ariaLabel="copy-value"
variant="plain"
size="sm"
onClick={handleOverrideClick}
className={twMerge(
"w-0 group-hover:w-5 group-hover:mr-2 overflow-hidden p-0",
isOverriden && "w-5 text-primary"
)}
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5"
onClick={copyTokenToClipboard}
>
<FontAwesomeIcon icon={faCodeBranch} />
<FontAwesomeIcon icon={isSecValueCopied ? faCheck : faCopy} />
</IconButton>
)}
</ProjectPermissionCan>
<Popover>
</Tooltip>
<DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
>
{(isAllowed) => (
<DropdownMenuTrigger asChild disabled={!isAllowed}>
<IconButton
ariaLabel="tags"
variant="plain"
size="sm"
className={twMerge(
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-5",
hasTagsApplied && "w-5 text-primary"
)}
isDisabled={!isAllowed}
>
<Tooltip content="Tags">
<FontAwesomeIcon icon={faTags} />
</Tooltip>
</IconButton>
</DropdownMenuTrigger>
)}
</ProjectPermissionCan>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Apply tags to this secrets</DropdownMenuLabel>
{tags.map((tag) => {
const { _id: tagId, name, tagColor } = tag;
const isTagSelected = selectedTagsGroupById?.[tagId];
return (
<DropdownMenuItem
onClick={() => handleTagSelect(tag)}
key={tagId}
icon={isTagSelected && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right"
>
<div className="flex items-center">
<div
className="mr-2 h-2 w-2 rounded-full"
style={{ background: tagColor || "#bec2c8" }}
/>
{name}
</div>
</DropdownMenuItem>
);
})}
<DropdownMenuItem className="px-1.5">
<Button
size="xs"
className="w-full"
colorSchema="primary"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faTag} />}
onClick={onCreateTag}
>
Create a tag
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
>
{(isAllowed) => (
<PopoverTrigger asChild disabled={!isAllowed}>
<IconButton
className={twMerge(
"overflow-hidden w-0 p-0 group-hover:w-5 group-hover:mr-2 data-[state=open]:w-6",
hasComment && "w-5 text-primary"
)}
variant="plain"
size="md"
ariaLabel="add-comment"
isDisabled={!isAllowed}
>
<Tooltip content="Comment">
<FontAwesomeIcon icon={faComment} />
</Tooltip>
</IconButton>
</PopoverTrigger>
)}
</ProjectPermissionCan>
<PopoverContent
className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl"
sticky="always"
>
<FormControl label="Comment" className="mb-0">
<TextArea
className="border border-mineshaft-600 text-sm"
rows={8}
cols={30}
{...register("comment")}
/>
</FormControl>
</PopoverContent>
</Popover>
</div>
</div>
<AnimatePresence exitBeforeEnter>
{!isDirty ? (
<motion.div
key="options"
className="h-10 flex items-center space-x-4 flex-shrink-0 px-3"
initial={{ x: 0, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 10, opacity: 0 }}
>
<Tooltip content="More">
<IconButton
ariaLabel="more"
variant="plain"
size="md"
className="group-hover:opacity-100 opacity-0 p-0"
onClick={() => onDetailViewSecret(secret)}
>
<FontAwesomeIcon icon={faEllipsis} size="lg" />
</IconButton>
</Tooltip>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"
allowedLabel="Override"
>
{(isAllowed) => (
<IconButton
ariaLabel="delete-value"
variant="plain"
colorSchema="danger"
size="md"
className="group-hover:opacity-100 opacity-0 p-0"
onClick={() => onDeleteSecret(secret)}
ariaLabel="override-value"
isDisabled={!isAllowed}
variant="plain"
size="sm"
onClick={handleOverrideClick}
className={twMerge(
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5",
isOverriden && "w-5 text-primary"
)}
>
<FontAwesomeIcon icon={faClose} size="lg" />
<FontAwesomeIcon icon={faCodeBranch} />
</IconButton>
)}
</ProjectPermissionCan>
</motion.div>
) : (
<motion.div
key="options-save"
className="h-10 flex items-center space-x-4 flex-shrink-0 px-3"
initial={{ x: -10, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -10, opacity: 0 }}
>
<Tooltip content="Save">
{!isOverriden && (
<IconButton
ariaLabel="more"
variant="plain"
type="submit"
size="md"
className={twMerge(
"group-hover:opacity-100 opacity-0 p-0 text-primary",
isDirty && "opacity-100"
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6",
hasReminder && "w-5 text-primary"
)}
isDisabled={isSubmitting}
>
{isSubmitting ? (
<Spinner className="w-4 h-4 p-0 m-0" />
) : (
<FontAwesomeIcon icon={faCheck} size="lg" className="text-primary" />
)}
</IconButton>
</Tooltip>
<Tooltip content="Cancel">
<IconButton
ariaLabel="more"
variant="plain"
size="md"
className={twMerge(
"group-hover:opacity-100 opacity-0 p-0",
isDirty && "opacity-100"
)}
onClick={() => reset()}
isDisabled={isSubmitting}
ariaLabel="add-reminder"
>
<FontAwesomeIcon icon={faClose} size="lg" />
<Tooltip content="Reminder">
<FontAwesomeIcon
onClick={() => {
if (!hasReminder) {
setCreateReminderFormOpen.on();
} else {
setValue("reminderRepeatDays", null, { shouldDirty: true });
setValue("reminderNote", null, { shouldDirty: true });
}
}}
icon={faClock}
/>
</Tooltip>
</IconButton>
</Tooltip>
</motion.div>
)}
</AnimatePresence>
)}
<Popover>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
>
{(isAllowed) => (
<PopoverTrigger asChild disabled={!isAllowed}>
<IconButton
className={twMerge(
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6",
hasComment && "w-5 text-primary"
)}
variant="plain"
size="md"
ariaLabel="add-comment"
isDisabled={!isAllowed}
>
<Tooltip content="Comment">
<FontAwesomeIcon icon={faComment} />
</Tooltip>
</IconButton>
</PopoverTrigger>
)}
</ProjectPermissionCan>
<PopoverContent
className="w-auto border border-mineshaft-600 bg-mineshaft-800 p-2 drop-shadow-2xl"
sticky="always"
>
<FormControl label="Comment" className="mb-0">
<TextArea
className="border border-mineshaft-600 text-sm"
rows={8}
cols={30}
{...register("comment")}
/>
</FormControl>
</PopoverContent>
</Popover>
</div>
</div>
<AnimatePresence exitBeforeEnter>
{!isDirty ? (
<motion.div
key="options"
className="flex h-10 flex-shrink-0 items-center space-x-4 px-3"
initial={{ x: 0, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 10, opacity: 0 }}
>
<Tooltip content="More">
<IconButton
ariaLabel="more"
variant="plain"
size="md"
className="p-0 opacity-0 group-hover:opacity-100"
onClick={() => onDetailViewSecret(secret)}
>
<FontAwesomeIcon icon={faEllipsis} size="lg" />
</IconButton>
</Tooltip>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"
>
{(isAllowed) => (
<IconButton
ariaLabel="delete-value"
variant="plain"
colorSchema="danger"
size="md"
className="p-0 opacity-0 group-hover:opacity-100"
onClick={() => onDeleteSecret(secret)}
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faClose} size="lg" />
</IconButton>
)}
</ProjectPermissionCan>
</motion.div>
) : (
<motion.div
key="options-save"
className="flex h-10 flex-shrink-0 items-center space-x-4 px-3"
initial={{ x: -10, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -10, opacity: 0 }}
>
<Tooltip content="Save">
<IconButton
ariaLabel="more"
variant="plain"
type="submit"
size="md"
className={twMerge(
"p-0 text-primary opacity-0 group-hover:opacity-100",
isDirty && "opacity-100"
)}
isDisabled={isSubmitting}
>
{isSubmitting ? (
<Spinner className="m-0 h-4 w-4 p-0" />
) : (
<FontAwesomeIcon icon={faCheck} size="lg" className="text-primary" />
)}
</IconButton>
</Tooltip>
<Tooltip content="Cancel">
<IconButton
ariaLabel="more"
variant="plain"
size="md"
className={twMerge(
"p-0 opacity-0 group-hover:opacity-100",
isDirty && "opacity-100"
)}
onClick={() => reset()}
isDisabled={isSubmitting}
>
<FontAwesomeIcon icon={faClose} size="lg" />
</IconButton>
</Tooltip>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
</form>
</form>
</>
);
}
);
@@ -122,6 +122,8 @@ export const SecretListView = ({
{
value,
comment,
reminderRepeatDays,
reminderNote,
tags,
skipMultilineEncoding,
newKey,
@@ -129,6 +131,8 @@ export const SecretListView = ({
}: Partial<{
value: string;
comment: string;
reminderRepeatDays: number | null;
reminderNote: string | null;
tags: string[];
skipMultilineEncoding: boolean;
newKey: string;
@@ -159,6 +163,8 @@ export const SecretListView = ({
latestFileKey: decryptFileKey,
tags,
secretComment: comment,
secretReminderRepeatDays: reminderRepeatDays,
secretReminderNote: reminderNote,
skipMultilineEncoding,
newSecretName: newKey
});
@@ -188,16 +194,33 @@ export const SecretListView = ({
cb?: () => void
) => {
const { key: oldKey } = orgSecret;
const { key, value, overrideAction, idOverride, valueOverride, tags, comment } = modSecret;
const {
key,
value,
overrideAction,
idOverride,
valueOverride,
tags,
comment,
reminderRepeatDays,
reminderNote
} = modSecret;
const hasKeyChanged = oldKey !== key;
const tagIds = tags.map(({ _id }) => _id);
const oldTagIds = orgSecret.tags.map(({ _id }) => _id);
const isSameTags = JSON.stringify(tagIds) === JSON.stringify(oldTagIds);
const isSharedSecUnchanged =
(["key", "value", "comment", "skipMultilineEncoding"] as const).every(
(el) => orgSecret[el] === modSecret[el]
) && isSameTags;
(
[
"key",
"value",
"comment",
"skipMultilineEncoding",
"reminderRepeatDays",
"reminderNote"
] as const
).every((el) => orgSecret[el] === modSecret[el]) && isSameTags;
try {
// personal secret change
@@ -222,13 +245,14 @@ export const SecretListView = ({
value,
tags: tagIds,
comment,
reminderRepeatDays,
reminderNote,
secretId: orgSecret._id,
newKey: hasKeyChanged ? key : undefined,
skipMultilineEncoding: modSecret.skipMultilineEncoding
});
if (cb) cb();
}
queryClient.invalidateQueries(
secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
);
@@ -306,7 +330,7 @@ export const SecretListView = ({
<div className="flex flex-col" key={`${namespace}-${groupedSecrets.length}`}>
<div
className={twMerge(
"bg-bunker-600 capitalize text-md h-0 transition-all",
"text-md h-0 bg-bunker-600 capitalize transition-all",
Boolean(namespace) && Boolean(filteredSecrets.length) && "h-11 py-3 pl-4 "
)}
key={namespace}
@@ -20,6 +20,15 @@ export const formSchema = z.object({
overrideAction: z.string().trim().optional(),
comment: z.string().trim().optional(),
skipMultilineEncoding: z.boolean().optional(),
reminderRepeatDays: z
.number()
.min(1, { message: "Days must be between 1 and 365" })
.max(365, { message: "Days must be between 1 and 365" })
.nullable()
.optional(),
reminderNote: z.string().trim().nullable().optional(),
tags: z
.object({
_id: z.string(),