'use client' import { useState, useEffect, useCallback } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog' import { useToast } from '@/components/ui/use-toast' import { Loader2, Plus, Trash2, Globe } from 'lucide-react' interface OAuthClient { id: string client_name: string redirect_uri: string created_at: string revoked_at: string | null } export function OAuthClientsPanel() { const { toast } = useToast() const { dialogProps: revokeDialogProps, confirm: confirmRevoke } = useDestructiveConfirm() const [clients, setClients] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isCreating, setIsCreating] = useState(false) const [showCreateDialog, setShowCreateDialog] = useState(false) const [clientName, setClientName] = useState('') const [redirectUri, setRedirectUri] = useState('') const fetchClients = useCallback(async () => { try { const res = await fetch('/api/settings/oauth-clients') const json = await res.json() if (json.data) { setClients(json.data.filter((c: OAuthClient) => !c.revoked_at)) } } catch { toast({ title: 'Kunde inte hämta OAuth-klienter', variant: 'destructive' }) } finally { setIsLoading(false) } }, [toast]) useEffect(() => { fetchClients() }, [fetchClients]) async function handleCreate() { setIsCreating(true) try { const res = await fetch('/api/settings/oauth-clients', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_name: clientName.trim() || 'OAuth-klient', redirect_uri: redirectUri.trim(), }), }) const json = await res.json() if (!res.ok) { toast({ title: json.error ?? 'Kunde inte registrera redirect URI', variant: 'destructive' }) return } setShowCreateDialog(false) setClientName('') setRedirectUri('') fetchClients() } catch { toast({ title: 'Kunde inte registrera redirect URI', variant: 'destructive' }) } finally { setIsCreating(false) } } async function handleRevoke(id: string, name: string) { const ok = await confirmRevoke({ title: 'Återkalla OAuth-klient', description: `"${name}" tas bort från allowlist. Pågående auth-flöden slutar fungera direkt; redan utfärdade API-nycklar fortsätter att gälla tills de återkallas separat.`, confirmLabel: 'Återkalla', }) if (!ok) return try { const res = await fetch(`/api/settings/oauth-clients/${id}`, { method: 'DELETE' }) if (!res.ok) { // Surface the server error rather than optimistically pretending the // revocation succeeded — a silent fail leaves the row in the // allowlist while the UI says it's gone, which is the opposite of // what the user expected. const body = await res.json().catch(() => ({})) toast({ title: body?.error || 'Kunde inte återkalla klient', variant: 'destructive', }) return } setClients((prev) => prev.filter((c) => c.id !== id)) toast({ title: 'Klient återkallad' }) } catch { toast({ title: 'Kunde inte återkalla klient', variant: 'destructive' }) } } function formatDate(iso: string) { return new Date(iso).toLocaleDateString('sv-SE', { year: 'numeric', month: 'short', day: 'numeric', }) } return (
OAuth-klienter Registrera redirect-URI:er för egenutvecklade MCP-klienter. Claude.ai och localhost är redan godkända som standard — registrera bara här om du bygger en egen app.
{isLoading ? (
) : clients.length === 0 ? (

Inga egna OAuth-klienter registrerade.

Bygger du en agent som ska ansluta via OAuth? Registrera dess callback-URI här.

) : (
{clients.map((c) => (

{c.client_name}

{c.redirect_uri} Registrerad {formatDate(c.created_at)}
))}
)}
Registrera redirect URI Bara för egenbyggda MCP-klienter med en publik HTTPS-callback. Lägg{' '} inte till{' '} localhost{' '} eller{' '} claude.ai{' '} — de fungerar redan utan registrering. URI:n jämförs ord-för-ord mot redirect_uri-parametern i OAuth-flödet.
setClientName(e.target.value)} />
setRedirectUri(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && redirectUri && handleCreate()} />
) }