feat(settings): implement Registrations tab

Replaces stub RegistrationsTab with a full read-only view grouped by client.
Handles all 4 states: loading skeleton, fetch error, host_path_unavailable,
empty list, and populated data with per-registration detail rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 06:50:12 +00:00
parent 7e3c912899
commit 89ce6c79d7

View File

@@ -1 +1,134 @@
export function RegistrationsTab() { return <div>Registrations tab coming soon</div>; }
"use client";
import { Plug, AlertCircle } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Badge } from "@/components/ui/badge";
import { useMcpRegistrations } from "@/lib/api/settings";
export function RegistrationsTab() {
const { data, isPending, error } = useMcpRegistrations();
if (isPending) return <Skeleton className="h-64 w-full" />;
if (error) {
return (
<Card className="bg-surface border-danger/40">
<CardContent className="p-6 flex items-center gap-3 text-danger">
<AlertCircle className="w-5 h-5" />
<span>שגיאה: {error.message}</span>
</CardContent>
</Card>
);
}
if (!data) return null;
if (data.error === "host_path_unavailable") {
return (
<Card className="bg-surface border-warn/40">
<CardContent className="p-6">
<div className="flex items-center gap-3 text-warn mb-2">
<AlertCircle className="w-5 h-5" />
<span className="font-medium">תיקיית /host לא זמינה בקונטיינר</span>
</div>
<p className="text-sm text-ink-muted mb-2">
כדי להציג רישומי MCP, יש להוסיף volume mounts ב-Coolify.
ראה runbook ב-
<code dir="ltr" className="mx-1">
docs/runbooks/coolify-mcp-settings-volumes.md
</code>
</p>
{data.message && (
<p className="text-sm text-ink-muted">{data.message}</p>
)}
</CardContent>
</Card>
);
}
if (!data.registrations.length) {
return (
<Card className="bg-surface border-rule">
<CardContent className="p-6 text-ink-muted text-sm">
לא נמצאו רישומי MCP.
</CardContent>
</Card>
);
}
// Group by client
const groups = new Map<string, typeof data.registrations>();
for (const r of data.registrations) {
const arr = groups.get(r.client) ?? [];
arr.push(r);
groups.set(r.client, arr);
}
return (
<div className="space-y-4">
<div className="flex items-center gap-2 text-sm text-ink-muted">
<Plug className="w-4 h-4" />
סה&quot;כ {data.registrations.length} רישומים
</div>
{[...groups.entries()].map(([client, regs]) => (
<Card key={client} className="bg-surface border-rule">
<CardContent className="px-6 py-5">
<h2 className="text-navy text-lg mb-4 flex items-center gap-2">
{client}
<Badge variant="outline" className="text-[0.7rem]">
{regs.length}
</Badge>
</h2>
<div className="space-y-3">
{regs.map((r, i) => (
<div
key={`${r.server_name}-${i}`}
className="rounded-md border border-rule bg-rule-soft/20 p-4 space-y-2 text-sm"
>
<div className="flex items-center gap-2 mb-1">
<code dir="ltr" className="font-mono font-medium text-navy">
{r.server_name}
</code>
<Badge variant="outline" className="text-[0.7rem]" dir="ltr">
{r.transport}
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-[100px_1fr] gap-x-3 gap-y-1.5 text-[0.82rem]">
<span className="text-ink-muted">command:</span>
<code dir="ltr" className="font-mono text-ink break-all">
{r.command || "—"}
</code>
<span className="text-ink-muted">args:</span>
<code dir="ltr" className="font-mono text-ink break-all">
{r.args.length ? JSON.stringify(r.args) : "[]"}
</code>
<span className="text-ink-muted">cwd:</span>
<code dir="ltr" className="font-mono text-ink break-all">
{r.cwd || "—"}
</code>
<span className="text-ink-muted">env keys:</span>
<div className="flex flex-wrap gap-1">
{r.env_keys.length === 0 ? (
<span className="text-ink-muted"></span>
) : (
r.env_keys.map((k) => (
<Badge
key={k}
variant="outline"
className="text-[0.7rem] font-mono"
dir="ltr"
>
{k}
</Badge>
))
)}
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
))}
</div>
);
}