// Duenne fetch()-Wrapper fuer die Phase-1-REST-API (siehe API.md). Reine Daten-Ebene, kein // DOM-Zugriff - office-render.js ruft diese Funktionen auf und reicht die Ergebnisse an die // Layoutlogik (office-scene.js) weiter. Basis-URL ist per window.MONITOR_API_BASE // ueberschreibbar - gesetzt wird sie zur Laufzeit von /config.js, das der Webserver aus der // Umgebungsvariable MONITOR_API_BASE erzeugt (siehe .env.example und // docker/nginx/default.conf.template). Noetig, weil Frontend und API auf unterschiedlichen // Origins laufen koennen (Dev: web :8090, api :8091); die API liefert dafuer CORS-Header, // siehe api/src/Http/Response.php. const DEFAULT_API_BASE = "/api/v1"; function apiBase() { const configured = typeof window !== "undefined" ? window.MONITOR_API_BASE : null; // Leer = gleicher Origin. Der startsWith-Check faengt einen nicht ersetzten Platzhalter // ab (z.B. wenn config.js von einem anderen Webserver ohne Substitution ausgeliefert // wird) - der waere sonst eine truthy Muell-URL. if (!configured || configured.startsWith("${")) { return DEFAULT_API_BASE; } return configured; } async function getJson(path) { const response = await fetch(`${apiBase()}${path}`, { headers: { Accept: "application/json" } }); if (!response.ok) { throw new Error(`GET ${path} fehlgeschlagen: HTTP ${response.status}`); } return response.json(); } export async function fetchTickets() { const { tickets } = await getJson("/tickets"); return tickets; } export async function fetchEvents(limit = 200) { const { events } = await getJson(`/events?limit=${encodeURIComponent(limit)}`); return events; } export async function fetchSystems() { const { systems } = await getJson("/systems"); return systems; } export async function fetchKpis() { const { kpis } = await getJson("/kpis"); return kpis; } export async function fetchAgents() { const { agents } = await getJson("/agents"); return agents; } /** * Hooks-KPI (Issue #38): Anzahl der seit dem letzten Start des api-Containers empfangenen * Webhooks, siehe API.md "GET /api/v1/webhooks/stats". Anders als die anderen fetch*()- * Funktionen kein eigener Datenbereich der Buero-Szene, sondern nur die Datenquelle einer * einzelnen KPI-Monitor-Kachel (buildHooksKpi() in office-scene.js). */ export async function fetchWebhookStats() { return getJson("/webhooks/stats"); } /** * Baut die Stream-URL fuer den SSE-Push-Endpoint (F13, Phase 4). Wird von office-realtime.js * an EventSource() uebergeben. */ export function streamUrl() { return `${apiBase()}/stream`; } // --------------------------------------------------------------------------------------- // Projekte des Codeberg-Sync (Issue #27: Klick auf den PO oeffnet ein Modal, um Projekte // hinzuzufuegen/zu bearbeiten/zu loeschen). Schreibende Endpunkte verlangen X-Admin-Key // (siehe API.md "Authentifizierung") - anders als der Agenten-/Ticket-API-Key gibt es dafuer // keine Registrierung im Browser, der Admin-Key wird daher einmalig manuell eingegeben und // im lokalen Speicher gemerkt (siehe office-render.js openRepoConfigModal()). const ADMIN_KEY_STORAGE_KEY = "monitor.adminKey"; export function getStoredAdminKey() { try { return window.localStorage.getItem(ADMIN_KEY_STORAGE_KEY) || ""; } catch { // localStorage kann im Privat-/Inkognito-Modus blockiert sein - dann muss der Admin-Key // bei jeder Aenderung erneut eingegeben werden, kein Hard-Fail. return ""; } } export function setStoredAdminKey(key) { try { if (key) { window.localStorage.setItem(ADMIN_KEY_STORAGE_KEY, key); } else { window.localStorage.removeItem(ADMIN_KEY_STORAGE_KEY); } } catch { // s.o. } } async function adminJson(method, path, body, adminKey) { const response = await fetch(`${apiBase()}${path}`, { method, headers: { Accept: "application/json", "Content-Type": "application/json", "X-Admin-Key": adminKey, }, body: body === undefined ? undefined : JSON.stringify(body), }); if (response.status === 401) { const error = new Error("Admin-Key ungueltig oder fehlend."); error.isAuthError = true; throw error; } if (!response.ok) { const payload = await response.json().catch(() => null); throw new Error(payload?.error || `${method} ${path} fehlgeschlagen: HTTP ${response.status}`); } return response.status === 204 ? null : response.json(); } export async function fetchRepoConfigs() { const { repos } = await getJson("/repo-configs"); return repos; } export async function createRepoConfig(data, adminKey) { const { repo } = await adminJson("POST", "/repo-configs", data, adminKey); return repo; } export async function updateRepoConfig(id, data, adminKey) { const { repo } = await adminJson("PATCH", `/repo-configs/${encodeURIComponent(id)}`, data, adminKey); return repo; } export async function deleteRepoConfig(id, adminKey) { await adminJson("DELETE", `/repo-configs/${encodeURIComponent(id)}`, undefined, adminKey); } /** * Issue #53: Prueft nach dem Anlegen/Bearbeiten eines Projekts, ob fds2610-bot dort * Schreibrechte hat und ob der Webhook korrekt eingerichtet ist. * * @returns {Promise<{write_access: {ok: boolean, message: string}, webhook: {ok: boolean, message: string}}>} */ export async function checkRepoConfig(id, adminKey) { const { checks } = await adminJson("POST", `/repo-configs/${encodeURIComponent(id)}/check`, undefined, adminKey); return checks; } /** * Issue #51: "Board neu einlesen"-Knopf am Kanban-Board - loescht serverseitig alle Tickets * und liest alle konfigurierten Codeberg-Projekte synchron neu ein. Braucht wie die * Projekte-Verwaltung den Admin-Key, weil ein Voll-Reset des Boards potenziell teuer * (mehrere Codeberg-Requests) und kein normaler Agenten-Vorgang ist. * * @returns {Promise>} der neue Board-Zustand. */ export async function resyncTickets(adminKey) { const { tickets } = await adminJson("POST", "/tickets/resync", undefined, adminKey); return tickets; } // --------------------------------------------------------------------------------------- // Hosts der Ping-Ueberwachung (Issue #70: Zahnrad am Wandmonitor "Systeme" oeffnet ein Modal, // um die hosts.conf des Ping-Rechners ueber die GUI zu pflegen). Schreibend hinter demselben // Admin-Key wie die Projekte-Verwaltung; der Ping-Check selbst holt seine Liste dagegen mit // seinem Agenten-Key ab (GET /hosts/conf, siehe check_fds.sh) - das macht der Browser nicht. // --------------------------------------------------------------------------------------- export async function fetchHostConfigs() { const { hosts } = await getJson("/hosts"); return hosts; } export async function createHostConfig(data, adminKey) { const { host } = await adminJson("POST", "/hosts", data, adminKey); return host; } export async function updateHostConfig(id, data, adminKey) { const { host } = await adminJson("PATCH", `/hosts/${encodeURIComponent(id)}`, data, adminKey); return host; } export async function deleteHostConfig(id, adminKey) { await adminJson("DELETE", `/hosts/${encodeURIComponent(id)}`, undefined, adminKey); } /** * Entfernt eine Kachel aus dem System-Monitor. Fuer Systeme, die zu keinem hier gepflegten * Host gehoeren - etwa weil sie sich frueher selbst per REST gemeldet haben und das nicht * mehr tun. Ein Host aus der Ping-Ueberwachung nimmt seine Kachel dagegen beim Loeschen * selbst mit (serverseitig, siehe HostConfigController::delete). */ export async function deleteSystem(id, adminKey) { await adminJson("DELETE", `/systems/${encodeURIComponent(id)}`, undefined, adminKey); }