Skip to content

Commit 8943178

Browse files
os-zhuangclaude
andauthored
feat(setup): the datasource list shows the real connect verdict, with the operator-facing reason (framework#3827) (#2926)
The framework's datasource-admin list now reports a four-state `status` from the retained connect verdict — ok | error | blocked | unvalidated — plus an operator-facing `statusReason` (framework#3827/#3869), and since framework#3869 the primary `default` datasource appears in the list too. The page rendered `status` as an undifferentiated text fragment ("· error"), so a dead datasource read exactly like an untested one and the reason never surfaced. - Status chip per verdict: connected (emerald) / connect failed (destructive) / blocked by policy (amber) / not connected (muted). Unknown values fall back to a muted chip with the raw string, so a future framework state degrades readably instead of disappearing. - `statusReason` shows under the row for error/blocked (truncated, full text via the native title tooltip — the file's existing idiom). This surface is admin-gated; end users never see these strings (framework#3828). Claude-Session: https://claude.ai/code/session_01TQVM3A9Yd6N2eZS8ZcdnMk Co-authored-by: Claude <noreply@anthropic.com>
1 parent 53642d4 commit 8943178

1 file changed

Lines changed: 49 additions & 1 deletion

File tree

packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.tsx

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,53 @@ interface DatasourceRow {
7070
name: string;
7171
label?: string;
7272
driver?: string;
73+
/**
74+
* Last connect verdict, from the framework's retained connection state
75+
* (framework#3827): `ok` (live driver registered) | `error` (connect
76+
* attempted and failed — see statusReason) | `blocked` (the host's connect
77+
* policy refused it; a decision, not a fault) | `unvalidated` (no connect
78+
* attempted — e.g. a managed datasource the auto-connect gate leaves
79+
* metadata-only).
80+
*/
7381
status?: string;
82+
/**
83+
* Operator-facing detail behind `error` / `blocked` — the raw connect error
84+
* or the policy's reason. This surface is admin-gated, so showing it here is
85+
* intended; end users never see it (framework#3828).
86+
*/
87+
statusReason?: string;
7488
origin?: string;
7589
active?: boolean;
7690
}
91+
92+
/**
93+
* Status chip per verdict. `unvalidated` stays visually quiet — it means
94+
* "nothing is known", not "something is wrong" — while `error`/`blocked` carry
95+
* the operator-facing reason as a native tooltip (the file's existing idiom).
96+
*/
97+
function StatusChip({ status, reason }: { status?: string; reason?: string }) {
98+
if (!status) return null;
99+
const chip = (cls: string, label: string) => (
100+
<span
101+
className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 font-medium ${cls}`}
102+
title={reason || undefined}
103+
>
104+
{label}
105+
</span>
106+
);
107+
switch (status) {
108+
case 'ok':
109+
return chip('bg-emerald-500/10 text-emerald-600 dark:text-emerald-400', 'connected');
110+
case 'error':
111+
return chip('bg-destructive/10 text-destructive', 'connect failed');
112+
case 'blocked':
113+
return chip('bg-amber-500/10 text-amber-600 dark:text-amber-400', 'blocked by policy');
114+
case 'unvalidated':
115+
return chip('bg-muted text-muted-foreground', 'not connected');
116+
default:
117+
return chip('bg-muted text-muted-foreground', status);
118+
}
119+
}
77120
interface RemoteTable { name: string; schema?: string; columnCount?: number }
78121

79122
interface JsonProp {
@@ -456,9 +499,14 @@ export function DatasourceResourcePage(_props: { type?: string }): React.ReactEl
456499
</div>
457500
<div className="mt-0.5 flex items-center gap-2 text-[11px] text-muted-foreground">
458501
<span className="rounded bg-muted px-1.5 py-0.5 font-mono">{ds.driver}</span>
459-
{ds.status && <span>· {ds.status}</span>}
502+
<StatusChip status={ds.status} reason={ds.statusReason} />
460503
{ds.origin && <span>· {ds.origin}</span>}
461504
</div>
505+
{ds.statusReason && (ds.status === 'error' || ds.status === 'blocked') && (
506+
<div className="mt-0.5 truncate text-[11px] text-muted-foreground" title={ds.statusReason}>
507+
{ds.statusReason}
508+
</div>
509+
)}
462510
</div>
463511
<Button variant="ghost" size="sm" disabled={busy === `test:${ds.name}`} onClick={() => void test(ds.name)}>
464512
{busy === `test:${ds.name}` ? <Loader2 className="mr-1.5 h-4 w-4 animate-spin" /> : <Plug className="mr-1.5 h-4 w-4" />} Test

0 commit comments

Comments
 (0)