feat: SSE vault transit UI (#94)

Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
唐小鸭
2026-04-07 19:22:37 +08:00
committed by GitHub
parent 2979a8f17c
commit 8dc3853fc1
5 changed files with 121 additions and 45 deletions
+67 -38
View File
@@ -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() {
</SelectTrigger>
<SelectContent>
<SelectItem value="local">{t("Local filesystem")}</SelectItem>
<SelectItem value="vault">{t("HashiCorp Vault Transit Engine")}</SelectItem>
<SelectItem value="vault-kv2">{t("HashiCorp Vault KV2")}</SelectItem>
<SelectItem value="vault-transit">{t("HashiCorp Vault Transit Engine")}</SelectItem>
</SelectContent>
</Select>
</FieldContent>
@@ -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"
/>
</FieldContent>
<FieldDescription>{t("Required: Vault authentication token")}</FieldDescription>
<FieldDescription>
{hasStoredVaultCredentials
? t("Leave blank to keep the stored Vault token.")
: t("Required: Vault authentication token")}
</FieldDescription>
</Field>
</FieldGroup>
@@ -845,27 +870,31 @@ export default function SSEPage() {
</FieldContent>
</Field>
<Field>
<FieldLabel>{t("KV Mount")}</FieldLabel>
<FieldContent>
<Input
value={formState.kvMount}
onChange={(event) => updateFormState("kvMount", event.target.value)}
placeholder="secret"
/>
</FieldContent>
</Field>
{formState.backendType === "vault-kv2" && (
<>
<Field>
<FieldLabel>{t("KV Mount")}</FieldLabel>
<FieldContent>
<Input
value={formState.kvMount}
onChange={(event) => updateFormState("kvMount", event.target.value)}
placeholder="secret"
/>
</FieldContent>
</Field>
<Field>
<FieldLabel>{t("Key Path Prefix")}</FieldLabel>
<FieldContent>
<Input
value={formState.keyPathPrefix}
onChange={(event) => updateFormState("keyPathPrefix", event.target.value)}
placeholder="rustfs/kms/keys"
/>
</FieldContent>
</Field>
<Field>
<FieldLabel>{t("Key Path Prefix")}</FieldLabel>
<FieldContent>
<Input
value={formState.keyPathPrefix}
onChange={(event) => updateFormState("keyPathPrefix", event.target.value)}
placeholder="rustfs/kms/keys"
/>
</FieldContent>
</Field>
</>
)}
</FieldGroup>
<div className="flex items-center gap-3 rounded-md border p-3">
+1 -1
View File
@@ -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 ?? "",
})),
)
+3
View File
@@ -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",
+3
View File
@@ -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": "重置为默认成功",
+47 -6
View File
@@ -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