Wer einen Server aus der Ping-Ueberwachung nehmen wollte, musste sich bisher auf den Rechner mit check_fds.sh einloggen und die hosts.conf editieren - und die Kachel blieb danach trotzdem im System-Monitor stehen, weil ein einmal gemeldetes System nirgends wieder geloescht werden konnte. Die Liste steht jetzt in der Datenbank und ist ueber ein Zahnrad neben der Ueberschrift "Systeme" pflegbar, analog zum Zahnrad am PO-Platz (Issue #27). Jeder Host gehoert dem OPs-Agenten (Issue #66), der ihn anpingt: API und Ping-Check laufen nicht zwangslaeufig auf demselben Rechner, die API kann die hosts.conf also nicht selbst schreiben. check_fds.sh holt sie stattdessen bei jedem Lauf mit seinem Agenten-Key ab und schreibt daraus die lokale Datei; ist die API nicht erreichbar, pingt es unveraendert nach der zuletzt geschriebenen Liste weiter. Beim ersten Lauf laedt der Check seine bestehende hosts.conf hoch, damit die bereits ueberwachten Server nicht von Hand nachgetragen werden muessen. Dass die API das nur einmal annimmt (Marker in host_config_imports) und "noch nie konfiguriert" per 204 von "im Frontend geleert" per 200 unterscheidet, ist noetig, damit ein Ping-Rechner mit alter lokaler Datei die dort geloeschten Hosts nicht wieder anlegt. Loeschen raeumt mit auf: Ein entfernter Host nimmt seine Kachel mit - beide Ids, denn der status.txt-Import benennt ihn nach dem Hostnamen, die REST-Meldung nach dessen Slug. Kacheln ohne zugehoerigen Host (Selbstmelder oder Reste laengst entfernter Hosts) stehen im selben Dialog unter "Weitere Kacheln im System-Monitor" und sind dort einzeln loeschbar - dafuer gibt es neu DELETE /api/v1/systems/{id}. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vNjbfr8M3HSfWMee92GTE
204 lines
7.5 KiB
JavaScript
204 lines
7.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 #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<Array<object>>} 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);
|
|
}
|