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:
@@ -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" />
|
||||||
|
סה"כ {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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user