mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
feat: integrated secret metadata to ui
This commit is contained in:
@@ -14,6 +14,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
|
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
|
||||||
|
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
|
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
|
||||||
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
|
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
|
||||||
@@ -154,8 +155,10 @@ export const secretReplicationServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const $getReplicatedSecretsV2 = (
|
const $getReplicatedSecretsV2 = (
|
||||||
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[],
|
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[],
|
||||||
importedSecrets: { secrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[] }[]
|
importedSecrets: {
|
||||||
|
secrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[];
|
||||||
|
}[]
|
||||||
) => {
|
) => {
|
||||||
const deDupe = new Set<string>();
|
const deDupe = new Set<string>();
|
||||||
const secrets = [...localSecrets];
|
const secrets = [...localSecrets];
|
||||||
@@ -272,6 +275,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
|
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
|
||||||
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
|
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
|
||||||
|
|
||||||
@@ -426,6 +430,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue,
|
encryptedValue: doc.encryptedValue,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
|
secretMetadata: doc.secretMetadata,
|
||||||
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -454,6 +459,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue as Buffer,
|
encryptedValue: doc.encryptedValue as Buffer,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
|
secretMetadata: doc.secretMetadata,
|
||||||
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -222,7 +222,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPath: z.string(),
|
secretPath: z.string(),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
folderId: z.string().optional(),
|
folderId: z.string().optional(),
|
||||||
secrets: secretRawSchema.omit({ createdAt: true, updatedAt: true }).array()
|
secrets: secretRawSchema
|
||||||
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
|
.extend({
|
||||||
|
secretMetadata: ResourceMetadataSchema.optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import z from "zod";
|
|||||||
export const ResourceMetadataSchema = z
|
export const ResourceMetadataSchema = z
|
||||||
.object({
|
.object({
|
||||||
key: z.string().trim().min(1),
|
key: z.string().trim().min(1),
|
||||||
value: z.string().trim().min(1)
|
value: z.string().trim().default("")
|
||||||
})
|
})
|
||||||
.array();
|
.array();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { SecretType, TSecretImports, TSecrets, TSecretsV2 } from "@app/db/schemas";
|
import { SecretType, TSecretImports, TSecrets, TSecretsV2 } from "@app/db/schemas";
|
||||||
import { groupBy, unique } from "@app/lib/fn";
|
import { groupBy, unique } from "@app/lib/fn";
|
||||||
|
|
||||||
|
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||||
import { TSecretDALFactory } from "../secret/secret-dal";
|
import { TSecretDALFactory } from "../secret/secret-dal";
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
@@ -39,6 +40,7 @@ type TSecretImportSecretsV2 = {
|
|||||||
// But for somereason ts consider ? and undefined explicit as different just ts things
|
// But for somereason ts consider ? and undefined explicit as different just ts things
|
||||||
secretValue: string;
|
secretValue: string;
|
||||||
secretComment: string;
|
secretComment: string;
|
||||||
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
})[];
|
})[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||||
`${TableName.SecretTag}.id`
|
`${TableName.SecretTag}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretV2))
|
.select(selectAllTableCols(TableName.SecretV2))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
@@ -103,6 +109,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
slug,
|
slug,
|
||||||
name: slug
|
name: slug
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "secretMetadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
field: "key",
|
field: `${TableName.SecretV2}.key` as "key",
|
||||||
value: el.secretKey
|
value: el.secretKey
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1127,7 +1127,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
field: "key",
|
field: `${TableName.SecretV2}.key` as "key",
|
||||||
value: el.secretKey
|
value: el.secretKey
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1199,6 +1199,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
key: el.secretKey,
|
key: el.secretKey,
|
||||||
tagIds: el.tagIds,
|
tagIds: el.tagIds,
|
||||||
references,
|
references,
|
||||||
|
secretMetadata: el.secretMetadata,
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -1271,7 +1272,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
field: "key",
|
field: `${TableName.SecretV2}.key` as "key",
|
||||||
value: el.secretKey
|
value: el.secretKey
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1336,7 +1337,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
field: "key",
|
field: `${TableName.SecretV2}.key` as "key",
|
||||||
value: el.secretKey
|
value: el.secretKey
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1482,7 +1483,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
field: "key",
|
field: `${TableName.SecretV2}.key` as "key",
|
||||||
value: el.secretKey
|
value: el.secretKey
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1854,6 +1855,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
reminderNote: doc.reminderNote,
|
reminderNote: doc.reminderNote,
|
||||||
reminderRepeatDays: doc.reminderRepeatDays,
|
reminderRepeatDays: doc.reminderRepeatDays,
|
||||||
|
secretMetadata: doc.secretMetadata,
|
||||||
references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : []
|
references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : []
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -1881,6 +1883,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
reminderNote: doc.reminderNote,
|
reminderNote: doc.reminderNote,
|
||||||
|
secretMetadata: doc.secretMetadata,
|
||||||
reminderRepeatDays: doc.reminderRepeatDays,
|
reminderRepeatDays: doc.reminderRepeatDays,
|
||||||
...(doc.encryptedValue
|
...(doc.encryptedValue
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export type TCreateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
|
|||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
source?: string;
|
source?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ export const useUpdateSecretV3 = ({
|
|||||||
secretReminderRepeatDays,
|
secretReminderRepeatDays,
|
||||||
secretReminderNote,
|
secretReminderNote,
|
||||||
newSecretName,
|
newSecretName,
|
||||||
skipMultilineEncoding
|
skipMultilineEncoding,
|
||||||
|
secretMetadata
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.patch(`/api/v3/secrets/raw/${secretKey}`, {
|
const { data } = await apiRequest.patch(`/api/v3/secrets/raw/${secretKey}`, {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -97,7 +98,8 @@ export const useUpdateSecretV3 = ({
|
|||||||
newSecretName,
|
newSecretName,
|
||||||
secretComment,
|
secretComment,
|
||||||
tagIds,
|
tagIds,
|
||||||
secretValue
|
secretValue,
|
||||||
|
secretMetadata
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => {
|
|||||||
updatedAt: el.updatedAt,
|
updatedAt: el.updatedAt,
|
||||||
version: el.version,
|
version: el.version,
|
||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||||
path: el.secretPath
|
path: el.secretPath,
|
||||||
|
secretMetadata: el.secretMetadata
|
||||||
};
|
};
|
||||||
|
|
||||||
if (el.type === SecretType.Personal) {
|
if (el.type === SecretType.Personal) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export type SecretV3RawSanitized = {
|
|||||||
overrideAction?: string;
|
overrideAction?: string;
|
||||||
folderId?: string;
|
folderId?: string;
|
||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
|
secretMetadata?: { key: string; value: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretV3Raw = {
|
export type SecretV3Raw = {
|
||||||
@@ -63,6 +64,7 @@ export type SecretV3Raw = {
|
|||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
secretReminderNote?: string;
|
secretReminderNote?: string;
|
||||||
secretReminderRepeatDays?: number;
|
secretReminderRepeatDays?: number;
|
||||||
|
secretMetadata?: { key: string; value: string }[];
|
||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
metadata?: Record<string, string>;
|
metadata?: Record<string, string>;
|
||||||
tags?: WsTag[];
|
tags?: WsTag[];
|
||||||
@@ -148,6 +150,7 @@ export type TUpdateSecretsV3DTO = {
|
|||||||
secretReminderRepeatDays?: number | null;
|
secretReminderRepeatDays?: number | null;
|
||||||
secretReminderNote?: string | null;
|
secretReminderNote?: string | null;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
|
secretMetadata?: { key: string; value: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteSecretsV3DTO = {
|
export type TDeleteSecretsV3DTO = {
|
||||||
|
|||||||
+76
-5
@@ -8,7 +8,8 @@ import {
|
|||||||
faClock,
|
faClock,
|
||||||
faPlus,
|
faPlus,
|
||||||
faShare,
|
faShare,
|
||||||
faTag
|
faTag,
|
||||||
|
faTrash
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -26,6 +27,7 @@ import {
|
|||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
Switch,
|
Switch,
|
||||||
@@ -87,11 +89,16 @@ export const SecretDetailSidebar = ({
|
|||||||
|
|
||||||
const { permission } = useProjectPermission();
|
const { permission } = useProjectPermission();
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
const tagFields = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: "tags"
|
name: "tags"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const metadataFormFields = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: "secretMetadata"
|
||||||
|
});
|
||||||
|
|
||||||
const secretKey = secret?.key || "";
|
const secretKey = secret?.key || "";
|
||||||
const selectedTags = watch("tags", []) || [];
|
const selectedTags = watch("tags", []) || [];
|
||||||
const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>(
|
const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>(
|
||||||
@@ -153,10 +160,10 @@ export const SecretDetailSidebar = ({
|
|||||||
if (selectedTagsGroupById?.[tag.id]) {
|
if (selectedTagsGroupById?.[tag.id]) {
|
||||||
const tagPos = selectedTags.findIndex(({ id }) => id === tag.id);
|
const tagPos = selectedTags.findIndex(({ id }) => id === tag.id);
|
||||||
if (tagPos !== -1) {
|
if (tagPos !== -1) {
|
||||||
remove(tagPos);
|
tagFields.remove(tagPos);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
append(tag);
|
tagFields.append(tag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -277,9 +284,73 @@ export const SecretDetailSidebar = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<FormControl label="Metadata">
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
|
||||||
|
<div key={metadataFieldId} className="flex items-end space-x-2">
|
||||||
|
<div className="flex-grow">
|
||||||
|
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`secretMetadata.${i}.key`}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
<Input {...field} className="max-h-8" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow">
|
||||||
|
{i === 0 && (
|
||||||
|
<FormLabel
|
||||||
|
label="Value"
|
||||||
|
className="text-xs text-mineshaft-400"
|
||||||
|
isOptional
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`secretMetadata.${i}.value`}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
<Input {...field} className="max-h-8" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="delete key"
|
||||||
|
className="bottom-0.5 max-h-8"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => metadataFormFields.remove(i)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="mt-2">
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => metadataFormFields.append({ key: "", value: "" })}
|
||||||
|
>
|
||||||
|
Add Key
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
<FormControl label="Tags" className="">
|
<FormControl label="Tags" className="">
|
||||||
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
|
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
|
||||||
{fields.map(({ tagColor, id: formId, slug, id }) => (
|
{tagFields.fields.map(({ tagColor, id: formId, slug, id }) => (
|
||||||
<Tag
|
<Tag
|
||||||
className="flex w-min items-center space-x-2"
|
className="flex w-min items-center space-x-2"
|
||||||
key={formId}
|
key={formId}
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ export const SecretListView = ({
|
|||||||
tags,
|
tags,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
newKey,
|
newKey,
|
||||||
secretId
|
secretId,
|
||||||
|
secretMetadata
|
||||||
}: Partial<{
|
}: Partial<{
|
||||||
value: string;
|
value: string;
|
||||||
comment: string;
|
comment: string;
|
||||||
@@ -88,6 +89,7 @@ export const SecretListView = ({
|
|||||||
skipMultilineEncoding: boolean;
|
skipMultilineEncoding: boolean;
|
||||||
newKey: string;
|
newKey: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
|
secretMetadata?: { key: string; value: string }[];
|
||||||
}> = {}
|
}> = {}
|
||||||
) => {
|
) => {
|
||||||
if (operation === "delete") {
|
if (operation === "delete") {
|
||||||
@@ -115,7 +117,8 @@ export const SecretListView = ({
|
|||||||
secretReminderRepeatDays: reminderRepeatDays,
|
secretReminderRepeatDays: reminderRepeatDays,
|
||||||
secretReminderNote: reminderNote,
|
secretReminderNote: reminderNote,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
newSecretName: newKey
|
newSecretName: newKey,
|
||||||
|
secretMetadata
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -138,7 +141,10 @@ export const SecretListView = ({
|
|||||||
const handleSaveSecret = useCallback(
|
const handleSaveSecret = useCallback(
|
||||||
async (
|
async (
|
||||||
orgSecret: SecretV3RawSanitized,
|
orgSecret: SecretV3RawSanitized,
|
||||||
modSecret: Omit<SecretV3RawSanitized, "tags"> & { tags?: { id: string }[] },
|
modSecret: Omit<SecretV3RawSanitized, "tags"> & {
|
||||||
|
tags?: { id: string }[];
|
||||||
|
secretMetadata?: { key: string; value: string }[];
|
||||||
|
},
|
||||||
cb?: () => void
|
cb?: () => void
|
||||||
) => {
|
) => {
|
||||||
const { key: oldKey } = orgSecret;
|
const { key: oldKey } = orgSecret;
|
||||||
@@ -151,7 +157,8 @@ export const SecretListView = ({
|
|||||||
tags,
|
tags,
|
||||||
comment,
|
comment,
|
||||||
reminderRepeatDays,
|
reminderRepeatDays,
|
||||||
reminderNote
|
reminderNote,
|
||||||
|
secretMetadata
|
||||||
} = modSecret;
|
} = modSecret;
|
||||||
const hasKeyChanged = oldKey !== key && key;
|
const hasKeyChanged = oldKey !== key && key;
|
||||||
|
|
||||||
@@ -166,7 +173,8 @@ export const SecretListView = ({
|
|||||||
"comment",
|
"comment",
|
||||||
"skipMultilineEncoding",
|
"skipMultilineEncoding",
|
||||||
"reminderRepeatDays",
|
"reminderRepeatDays",
|
||||||
"reminderNote"
|
"reminderNote",
|
||||||
|
"secretMetadata"
|
||||||
] as const
|
] as const
|
||||||
).every((el) => orgSecret[el] === modSecret[el]) && isSameTags;
|
).every((el) => orgSecret[el] === modSecret[el]) && isSameTags;
|
||||||
|
|
||||||
@@ -199,7 +207,8 @@ export const SecretListView = ({
|
|||||||
reminderNote,
|
reminderNote,
|
||||||
secretId: orgSecret.id,
|
secretId: orgSecret.id,
|
||||||
newKey: hasKeyChanged ? key : undefined,
|
newKey: hasKeyChanged ? key : undefined,
|
||||||
skipMultilineEncoding: modSecret.skipMultilineEncoding
|
skipMultilineEncoding: modSecret.skipMultilineEncoding,
|
||||||
|
secretMetadata
|
||||||
});
|
});
|
||||||
if (cb) cb();
|
if (cb) cb();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,13 @@ export const formSchema = z.object({
|
|||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
reminderNote: z.string().trim().nullable().optional(),
|
reminderNote: z.string().trim().nullable().optional(),
|
||||||
|
secretMetadata: z
|
||||||
|
.object({
|
||||||
|
key: z.string().trim().min(1),
|
||||||
|
value: z.string().trim().default("")
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
tags: z
|
tags: z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user