"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { Check, Copy, Loader2, Radio, Trash2 } from "lucide-react"; import type { GitProvider } from "@prisma/client"; import { Modal } from "@/components/ui/Modal"; import { formatRelative } from "@/lib/utils"; /** * Einrichtung des Webhooks fuer ein Repository. * * Der Haken wird im Git-System von Hand eingetragen. Ihn ueber die API * anzulegen waere machbar, verlangte aber Administrationsrechte am * Repository – deutlich mehr, als Nebula sonst braucht, und es waere ein * Recht, das die meisten Token gar nicht haben. * * Das Geheimnis ist genau einmal zu sehen: danach liegt nur noch die * verschluesselte Fassung in der Datenbank, wie bei den Zugangstoken. */ /** Wo der Haken im jeweiligen System einzutragen ist. */ const ANLEITUNG: Record = { GITHUB: { pfad: "Settings → Webhooks → Add webhook", ereignisse: "Content type „application/json“, Events: Pushes und Issues", }, GITEA: { pfad: "Einstellungen → Webhooks → Gitea", ereignisse: "Auslöser: Push und Issues", }, GITLAB: { pfad: "Settings → Webhooks", ereignisse: "Trigger: Push events und Issues events", }, }; export function WebhookDialog({ open, onClose, repositoryId, repositoryTitle, provider, eingerichtet, zuletzt, }: { open: boolean; onClose: () => void; repositoryId: string; repositoryTitle: string; provider: GitProvider | null; eingerichtet: boolean; zuletzt: Date | string | null; }) { const router = useRouter(); const [busy, setBusy] = useState(false); const [fehler, setFehler] = useState(null); const [geheimnis, setGeheimnis] = useState(null); const [url, setUrl] = useState(null); const [kopiert, setKopiert] = useState(null); // Ohne Verbindung ist das System unbekannt. Dann lieber allgemein bleiben, // als eine Anleitung fuer das falsche zu zeigen. const anleitung = provider ? ANLEITUNG[provider] : null; async function erzeugen() { setBusy(true); setFehler(null); try { const antwort = await fetch(`/api/repositories/${repositoryId}/webhook`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}), }); const nutzlast = await antwort.json().catch(() => ({})); if (!antwort.ok) throw new Error(nutzlast.error ?? "Fehlgeschlagen."); setGeheimnis(nutzlast.secret); setUrl(nutzlast.url); router.refresh(); } catch (gefangen) { setFehler(gefangen instanceof Error ? gefangen.message : String(gefangen)); } finally { setBusy(false); } } async function zuruecknehmen() { if (!window.confirm("Webhook zuruecknehmen? Eingehende Aufrufe werden dann abgewiesen.")) { return; } setBusy(true); try { await fetch(`/api/repositories/${repositoryId}/webhook`, { method: "DELETE" }); setGeheimnis(null); setUrl(null); router.refresh(); onClose(); } finally { setBusy(false); } } async function kopieren(wert: string, was: string) { try { await navigator.clipboard.writeText(wert); setKopiert(was); setTimeout(() => setKopiert(null), 2000); } catch { // Ohne Zwischenablage bleibt der Text zum Markieren stehen. } } return (
{eingerichtet && !geheimnis ? (

Webhook ist eingerichtet.

{zuletzt ? `Zuletzt empfangen ${formatRelative(zuletzt)}.` : "Bisher ist noch kein Aufruf angekommen — der Haken im Git-System fehlt vermutlich noch."}

) : null} {geheimnis && url ? (
kopieren(url, "url")} /> kopieren(geheimnis, "secret")} />

Das Geheimnis ist jetzt zum einzigen Mal zu sehen. Danach liegt es nur noch verschluesselt in der Datenbank. Verloren? Dann hier ein neues erzeugen — das alte gilt dann nicht mehr.

) : null}

Im Git-System eintragen

{anleitung ? ( <>

{anleitung.pfad}

{anleitung.ereignisse}

) : (

In den Einstellungen des Repositories unter „Webhooks“, mit den Ausloesern Push und Issues. Ist eine Git-Verbindung hinterlegt, steht hier der genaue Pfad fuer das jeweilige System.

)}

Nebula wertet nur Push und Issues aus. Alles andere wird angenommen und liegen gelassen — ein Fehler wuerde das Git-System sonst zum Wiederholen bringen.

{fehler ? (

{fehler}

) : null}
{eingerichtet ? ( ) : ( )}
); } function Feld({ label, wert, kopiert, onCopy, }: { label: string; wert: string; kopiert: boolean; onCopy: () => void; }) { return (
{label}
{/* tabIndex: der Kasten scrollt seitlich und muss deshalb auch mit der Tastatur erreichbar sein – sonst kommt man an das Ende einer langen URL nur mit der Maus. */} {wert}
); }