mirror of
https://github.com/rustfs/console.git
synced 2026-08-28 19:47:21 +08:00
feat: SSE vault transit UI (#94)
Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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 ?? "",
|
||||
})),
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user