diff --git a/app/(dashboard)/sse/page.tsx b/app/(dashboard)/sse/page.tsx index e523dd5..c6c65f1 100644 --- a/app/(dashboard)/sse/page.tsx +++ b/app/(dashboard)/sse/page.tsx @@ -49,7 +49,7 @@ const KEY_LIST_LIMIT = 20 const DEFAULT_PENDING_DELETE_DAYS = 7 type ConfigFormState = { - backendType: "local" | "vault" + backendType: "local" | "vault-kv2" | "vault-transit" keyDir: string filePermissions: string defaultKeyId: string @@ -134,25 +134,36 @@ function isAbsolutePath(value: string) { return /^(?:[A-Za-z]:[\\/]|\\\\|\/)/.test(value.trim()) } +function normalizeBackendType(value?: string | null): ConfigFormState["backendType"] { + switch (value) { + case "Vault": + case "VaultKV2": + return "vault-kv2" + case "VaultTransit": + return "vault-transit" + default: + return "local" + } +} + function buildFormStateFromStatus(status: KmsServiceStatusResponse | null): ConfigFormState { if (!status) return INITIAL_FORM_STATE const summary = status.config_summary const backendSummary = summary?.backend_summary const cacheSummary = summary?.cache_summary + const backendType = normalizeBackendType(status.backend_type ?? summary?.backend_type) return { - backendType: status.backend_type === "Vault" ? "vault" : "local", + backendType, keyDir: backendSummary?.key_dir ?? "", filePermissions: String(backendSummary?.file_permissions ?? 384), defaultKeyId: summary?.default_key_id ?? "", - timeoutSeconds: String(backendSummary?.timeout_seconds ?? 30), - retryAttempts: String(backendSummary?.retry_attempts ?? 3), + timeoutSeconds: String(summary?.timeout_seconds ?? 30), + retryAttempts: String(summary?.retry_attempts ?? 3), enableCache: summary?.enable_cache ?? cacheSummary?.enabled ?? true, - maxCachedKeys: String(summary?.max_cached_keys ?? cacheSummary?.max_cached_keys ?? 1000), - cacheTtlSeconds: String( - summary?.cache_ttl_seconds ?? cacheSummary?.cache_ttl_seconds ?? cacheSummary?.ttl_seconds ?? 3600, - ), + maxCachedKeys: String(summary?.max_cached_keys ?? cacheSummary?.max_keys ?? 1000), + cacheTtlSeconds: String(summary?.cache_ttl_seconds ?? cacheSummary?.ttl_seconds ?? 3600), address: backendSummary?.address ?? "", vaultToken: "", namespace: backendSummary?.namespace ?? "", @@ -212,6 +223,7 @@ export default function SSEPage() { const statusKind = React.useMemo(() => getStatusKind(status), [status]) const isRunning = statusKind === "Running" const hasConfiguration = statusKind !== "NotConfigured" + const hasStoredVaultCredentials = status?.config_summary?.backend_summary?.has_stored_credentials === true const statusBadgeValue = statusKind === "Error" ? "Error" : typeof status?.status === "string" ? status.status : statusKind @@ -224,7 +236,7 @@ export default function SSEPage() { if (syncForm) { setFormState((current) => { const next = buildFormStateFromStatus(res) - if (current.vaultToken && next.backendType === "vault") { + if (current.vaultToken && next.backendType !== "local") { next.vaultToken = current.vaultToken } return next @@ -339,7 +351,7 @@ export default function SSEPage() { return { payload: { - backend_type: "local", + backend_type: "Local", key_dir: formState.keyDir.trim(), file_permissions: parseOptionalInteger(formState.filePermissions) ?? 384, default_key_id: defaultKeyId || undefined, @@ -355,7 +367,7 @@ export default function SSEPage() { if (!formState.address.trim()) { return { error: t("Please enter Vault server address") } } - if (!formState.vaultToken.trim()) { + if (!formState.vaultToken.trim() && !hasStoredVaultCredentials) { return { error: t("Please enter Vault token") } } if (!formState.mountPath.trim()) { @@ -364,7 +376,7 @@ export default function SSEPage() { return { payload: { - backend_type: "vault", + backend_type: formState.backendType === "vault-kv2" ? "VaultKV2" : "VaultTransit", address: formState.address.trim(), auth_method: { Token: { @@ -373,8 +385,12 @@ export default function SSEPage() { }, namespace: formState.namespace.trim() || null, mount_path: formState.mountPath.trim(), - kv_mount: formState.kvMount.trim() || null, - key_path_prefix: formState.keyPathPrefix.trim() || null, + ...(formState.backendType === "vault-kv2" + ? { + kv_mount: formState.kvMount.trim() || null, + key_path_prefix: formState.keyPathPrefix.trim() || null, + } + : {}), skip_tls_verify: formState.skipTlsVerify, default_key_id: defaultKeyId || undefined, timeout_seconds: timeoutSeconds ?? 30, @@ -385,7 +401,7 @@ export default function SSEPage() { }, } }, - [formState, t], + [formState, hasStoredVaultCredentials, t], ) const submitConfiguration = React.useCallback( @@ -736,7 +752,8 @@ export default function SSEPage() { {t("Local filesystem")} - {t("HashiCorp Vault Transit Engine")} + {t("HashiCorp Vault KV2")} + {t("HashiCorp Vault Transit Engine")} @@ -814,11 +831,19 @@ export default function SSEPage() { type="password" value={formState.vaultToken} onChange={(event) => updateFormState("vaultToken", event.target.value)} - placeholder={t("Enter your Vault authentication token")} + placeholder={ + hasStoredVaultCredentials + ? t("Stored token is hidden. Enter a new token only to replace it.") + : t("Enter your Vault authentication token") + } autoComplete="off" /> - {t("Required: Vault authentication token")} + + {hasStoredVaultCredentials + ? t("Leave blank to keep the stored Vault token.") + : t("Required: Vault authentication token")} + @@ -845,27 +870,31 @@ export default function SSEPage() { - - {t("KV Mount")} - - updateFormState("kvMount", event.target.value)} - placeholder="secret" - /> - - + {formState.backendType === "vault-kv2" && ( + <> + + {t("KV Mount")} + + updateFormState("kvMount", event.target.value)} + placeholder="secret" + /> + + - - {t("Key Path Prefix")} - - updateFormState("keyPathPrefix", event.target.value)} - placeholder="rustfs/kms/keys" - /> - - + + {t("Key Path Prefix")} + + updateFormState("keyPathPrefix", event.target.value)} + placeholder="rustfs/kms/keys" + /> + + + + )}
diff --git a/components/buckets/info.tsx b/components/buckets/info.tsx index 193149e..b2ca1ce 100644 --- a/components/buckets/info.tsx +++ b/components/buckets/info.tsx @@ -275,7 +275,7 @@ export function BucketInfo({ bucketName }: BucketInfoProps) { (keys as { keys?: Array<{ key_id?: string; tags?: { name?: string }; description?: string }> })?.keys ?? [] setKmsKeyOptions( list.map((k) => ({ - label: k.tags?.name ?? k.description ?? `Key-${(k.key_id ?? "").slice(0, 8)}`, + label: k.tags?.name ?? k.description ?? `Key-${(k.key_id ?? "").slice(0, 24)}`, value: k.key_id ?? "", })), ) diff --git a/i18n/locales/en-US.json b/i18n/locales/en-US.json index 18c8165..06e0fcc 100644 --- a/i18n/locales/en-US.json +++ b/i18n/locales/en-US.json @@ -249,6 +249,7 @@ "Enter AppRole Role ID": "Enter AppRole Role ID", "Enter AppRole Secret ID": "Enter AppRole Secret ID", "Enter your Vault authentication token": "Enter your Vault authentication token", + "Leave blank to keep the stored Vault token.": "Leave blank to keep the stored Vault token.", "Enterprise": "Enterprise", "Enterprise License": "Enterprise License", "Enterprise Service Level": "Enterprise Service Level", @@ -314,6 +315,7 @@ "Get Help": "Get Help", "Groups": "Groups", "HashiCorp Encryption": "HashiCorp Encryption", + "HashiCorp Vault KV2": "HashiCorp Vault KV2", "HashiCorp Vault Transit Engine": "HashiCorp Vault Transit Engine", "Health Check Interval (seconds)": "Health Check Interval (seconds)", "High Memory Usage Warning": "High Memory Usage Warning", @@ -619,6 +621,7 @@ "Replication": "Replication", "Request timeout in seconds, default: 30": "Request timeout in seconds, default: 30", "Required: Vault authentication token": "Required: Vault authentication token", + "Stored token is hidden. Enter a new token only to replace it.": "Stored token is hidden. Enter a new token only to replace it.", "Reset": "Reset", "Reset to Default": "Reset to Default", "Reset to default successfully": "Reset to default successfully", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 435682f..143c8ce 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -252,6 +252,7 @@ "Enter AppRole Role ID": "输入 AppRole 角色 ID", "Enter AppRole Secret ID": "输入AppRole Secret ID", "Enter your Vault authentication token": "输入您的Vault认证令牌", + "Leave blank to keep the stored Vault token.": "留空则继续使用已存储的 Vault 令牌。", "Enterprise": "企业版", "Enterprise License": "企业版许可证", "Enterprise Service Level": "企业服务级别", @@ -321,6 +322,7 @@ "Get Notification Config Failed": "获取通知配置失败", "Groups": "分组", "HashiCorp Encryption": "HashiCorp 加密", + "HashiCorp Vault KV2": "HashiCorp Vault KV2", "HashiCorp Vault Transit Engine": "HashiCorp Vault Transit 引擎", "Health Check Interval (seconds)": "健康检查间隔(秒)", "High Memory Usage Warning": "高内存使用警告", @@ -637,6 +639,7 @@ "Replicate Delete": "复制删除", "Request timeout in seconds, default: 30": "请求超时时间(秒),默认:30", "Required: Vault authentication token": "必需:Vault 认证令牌", + "Stored token is hidden. Enter a new token only to replace it.": "已存储的令牌不会显示;仅在需要替换时输入新令牌。", "Reset": "重置", "Reset to Default": "重置为默认", "Reset to default successfully": "重置为默认成功", diff --git a/types/kms.ts b/types/kms.ts index c6a8e2e..31b0519 100644 --- a/types/kms.ts +++ b/types/kms.ts @@ -1,9 +1,10 @@ export type KmsServiceStatus = "NotConfigured" | "Configured" | "Running" | { Error: string } -export type KmsBackendType = "Local" | "Vault" +export type KmsBackendType = "Local" | "Vault" | "VaultKV2" | "VaultTransit" export interface KmsCacheSummary { enabled?: boolean + max_keys?: number | null max_cached_keys?: number | null cache_ttl_seconds?: number | null ttl_seconds?: number | null @@ -12,18 +13,22 @@ export interface KmsCacheSummary { export interface KmsBackendSummary { key_dir?: string | null file_permissions?: number | null + has_master_key?: boolean | null address?: string | null + auth_method_type?: string | null + has_stored_credentials?: boolean | null namespace?: string | null mount_path?: string | null kv_mount?: string | null key_path_prefix?: string | null skip_tls_verify?: boolean | null - timeout_seconds?: number | null - retry_attempts?: number | null } export interface KmsConfigSummary { + backend_type?: KmsBackendType | null default_key_id?: string | null + timeout_seconds?: number | null + retry_attempts?: number | null enable_cache?: boolean | null max_cached_keys?: number | null cache_ttl_seconds?: number | null @@ -49,7 +54,7 @@ export interface KmsStartRequest { } export interface KmsLocalConfigPayload { - backend_type: "local" + backend_type: "Local" key_dir: string file_permissions?: number default_key_id?: string @@ -67,7 +72,7 @@ export interface KmsVaultTokenAuthMethod { } export interface KmsVaultConfigPayload { - backend_type: "vault" + backend_type: "Vault" address: string auth_method: KmsVaultTokenAuthMethod namespace?: string | null @@ -83,7 +88,43 @@ export interface KmsVaultConfigPayload { cache_ttl_seconds?: number } -export type KmsConfigPayload = KmsLocalConfigPayload | KmsVaultConfigPayload +export interface KmsVaultKV2ConfigPayload { + backend_type: "VaultKV2" + address: string + auth_method: KmsVaultTokenAuthMethod + namespace?: string | null + mount_path: string + kv_mount?: string | null + key_path_prefix?: string | null + skip_tls_verify?: boolean + default_key_id?: string + timeout_seconds?: number + retry_attempts?: number + enable_cache?: boolean + max_cached_keys?: number + cache_ttl_seconds?: number +} + +export interface KmsVaultTransitConfigPayload { + backend_type: "VaultTransit" + address: string + auth_method: KmsVaultTokenAuthMethod + namespace?: string | null + mount_path: string + skip_tls_verify?: boolean + default_key_id?: string + timeout_seconds?: number + retry_attempts?: number + enable_cache?: boolean + max_cached_keys?: number + cache_ttl_seconds?: number +} + +export type KmsConfigPayload = + | KmsLocalConfigPayload + | KmsVaultConfigPayload + | KmsVaultKV2ConfigPayload + | KmsVaultTransitConfigPayload export interface KmsKeyInfo { key_id: string