Manchmal werden bei Codeberg geloeschte oder anderweitig verschwundene Issues vom periodischen Sync nicht bereinigt (er upsertet nur, ruehrt fehlende Issues nie an). Neuer Knopf neben der "Kanban-Board"-Ueberschrift stoesst dafuer einen synchronen Voll-Resync an (POST /api/v1/tickets/resync, X-Admin-Key): jedes konfigurierte Projekt wird neu gelesen, Tickets zu inzwischen verschwundenen Issues werden je Projekt entfernt (auch wenn sie manuell auf eine nicht vom Sync verwaltete Spalte wie "fertig" bewegt wurden), Tickets zu weiterhin existierenden Issues bleiben unangetastet. Der PO geht dabei sichtbar ans Board und steht dort, bis die Anfrage abgeschlossen ist - anders als beim automatischen Board-Besuch (feste Animationsdauer je Stage) macht das die tatsaechliche Sync-Dauer sichtbar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
157 lines
5.5 KiB
JavaScript
157 lines
5.5 KiB
JavaScript
// 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 #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<Array<object>>} der neue Board-Zustand.
|
|
*/
|
|
export async function resyncTickets(adminKey) {
|
|
const { tickets } = await adminJson("POST", "/tickets/resync", undefined, adminKey);
|
|
return tickets;
|
|
}
|