fix(integrations): align vault-backed credential status

This commit is contained in:
Haitao Pan 2026-03-20 22:45:26 +08:00
parent be0424652c
commit ed5a628fa9
4 changed files with 104 additions and 13 deletions

View File

@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react";
import { CheckCircle2, Loader2, RefreshCw, ShieldCheck } from "lucide-react";
import Link from "next/link";
import { resolveCredentialReady } from "@/lib/integrations/credentialStatus";
import type { IntegrationDefaults } from "@/lib/openclaw/types";
import type { XWorkmateProfileResponse } from "@/lib/xworkmate/types";
import { toXWorkmateIntegrationDefaults } from "@/lib/xworkmate/types";
@ -147,10 +148,14 @@ export function XWorkmateProfileEditor({
key: "openclaw",
label: "OpenClaw",
configured: Boolean(openclawUrl.trim()),
tokenConfigured:
payload.tokenConfigured.openclaw ||
Boolean(vaultSecretPath.trim()) ||
Boolean(openclawToken.trim()),
tokenConfigured: resolveCredentialReady({
inlineToken: openclawToken,
storedTokenConfigured: payload.tokenConfigured.openclaw,
vaultUrl,
vaultSecretPath,
vaultAuthToken: vaultToken,
storedVaultAuthConfigured: payload.tokenConfigured.vault,
}),
},
{
key: "vault",
@ -163,8 +168,14 @@ export function XWorkmateProfileEditor({
key: "apisix",
label: "APISIX",
configured: Boolean(apisixUrl.trim()),
tokenConfigured:
payload.tokenConfigured.apisix || Boolean(apisixToken.trim()),
tokenConfigured: resolveCredentialReady({
inlineToken: apisixToken,
storedTokenConfigured: payload.tokenConfigured.apisix,
vaultUrl,
vaultSecretPath,
vaultAuthToken: vaultToken,
storedVaultAuthConfigured: payload.tokenConfigured.vault,
}),
},
],
[

View File

@ -0,0 +1,34 @@
import {
hasVaultBackedCredentialAccess,
resolveCredentialReady,
} from '@/lib/integrations/credentialStatus'
describe('credentialStatus', () => {
test('marks credentials ready when an inline token is present', () => {
expect(
resolveCredentialReady({
inlineToken: 'live-token',
}),
).toBe(true)
})
test('marks credentials ready when vault auth can resolve a locator-backed secret', () => {
expect(
resolveCredentialReady({
vaultUrl: 'https://vault.svc.plus',
vaultSecretPath: 'kv/team-a/openclaw',
vaultAuthToken: 'vault-token',
}),
).toBe(true)
})
test('keeps credentials pending when the vault locator is incomplete', () => {
expect(
hasVaultBackedCredentialAccess({
vaultUrl: 'https://vault.svc.plus',
vaultSecretPath: 'kv/team-a/openclaw',
storedVaultAuthConfigured: false,
}),
).toBe(false)
})
})

View File

@ -0,0 +1,33 @@
type CredentialStatusInput = {
inlineToken?: string
storedTokenConfigured?: boolean
vaultUrl?: string
vaultSecretPath?: string
vaultAuthToken?: string
storedVaultAuthConfigured?: boolean
}
function hasText(value?: string): boolean {
return Boolean(value?.trim())
}
export function hasVaultBackedCredentialAccess(
input: Pick<
CredentialStatusInput,
'vaultUrl' | 'vaultSecretPath' | 'vaultAuthToken' | 'storedVaultAuthConfigured'
>,
): boolean {
return (
hasText(input.vaultUrl) &&
hasText(input.vaultSecretPath) &&
(hasText(input.vaultAuthToken) || Boolean(input.storedVaultAuthConfigured))
)
}
export function resolveCredentialReady(input: CredentialStatusInput): boolean {
return (
Boolean(input.storedTokenConfigured) ||
hasText(input.inlineToken) ||
hasVaultBackedCredentialAccess(input)
)
}

View File

@ -11,6 +11,7 @@ import {
} from "lucide-react";
import { useRouter } from "next/navigation";
import { resolveCredentialReady } from "@/lib/integrations/credentialStatus";
import type { IntegrationDefaults } from "@/lib/openclaw/types";
import { useOpenClawConsoleStore } from "@/state/openclawConsoleStore";
@ -199,10 +200,14 @@ export function IntegrationsConsole({
key: "openclaw",
label: "OpenClaw Gateway",
configured: Boolean(openclawUrl.trim()),
tokenConfigured:
resolvedDefaults.openclawTokenConfigured ||
Boolean(vaultSecretPath.trim() && (vaultToken.trim() || resolvedDefaults.vaultTokenConfigured)) ||
Boolean(openclawToken.trim()),
tokenConfigured: resolveCredentialReady({
inlineToken: openclawToken,
storedTokenConfigured: resolvedDefaults.openclawTokenConfigured,
vaultUrl,
vaultSecretPath,
vaultAuthToken: vaultToken,
storedVaultAuthConfigured: resolvedDefaults.vaultTokenConfigured,
}),
},
{
key: "vault",
@ -215,8 +220,14 @@ export function IntegrationsConsole({
key: "apisix",
label: "APISIX AI Gateway",
configured: Boolean(apisixUrl.trim()),
tokenConfigured:
resolvedDefaults.apisixTokenConfigured || Boolean(apisixToken.trim()),
tokenConfigured: resolveCredentialReady({
inlineToken: apisixToken,
storedTokenConfigured: resolvedDefaults.apisixTokenConfigured,
vaultUrl,
vaultSecretPath,
vaultAuthToken: vaultToken,
storedVaultAuthConfigured: resolvedDefaults.vaultTokenConfigured,
}),
},
],
[
@ -332,7 +343,9 @@ export function IntegrationsConsole({
</p>
<p className="mt-1 text-xs text-[var(--color-text-subtle)]">
{item.configured ? "address ready" : "missing address"} ·{" "}
{item.tokenConfigured ? "token ready" : "token pending"}
{item.tokenConfigured
? "credential path ready"
: "credential pending"}
</p>
</div>
<StatusBadge