// Read the CSRF token from the auth-bootstrap JSON if present. Returns '' // when the bootstrap tag isn't on the page (e.g. the public page) — that's // fine because the public page only ever makes GET requests, which don't // need a CSRF token anyway. function _csrfToken() { try { const el = document.getElementById('auth-bootstrap'); if (!el) return ''; return (JSON.parse(el.textContent) || {}).csrf || ''; } catch (_) { return ''; } } async function apiFetch(path, opts = {}) { const init = { ...opts }; const method = (opts.method || 'GET').toUpperCase(); const headers = { ...(opts.headers || {}) }; if (opts.body !== undefined) { headers['Content-Type'] = headers['Content-Type'] || 'application/json'; if (typeof init.body !== 'string') init.body = JSON.stringify(init.body); } // CSRF only on mutating requests. GET stays anonymous-friendly so the // public page's /api/status.php and /api/me calls don't depend on the // bootstrap being present. if (method !== 'GET' && method !== 'HEAD') { const tok = _csrfToken(); if (tok) headers['X-CSRF-Token'] = tok; } init.headers = headers; const res = await fetch(path, init); if (!res.ok) { let detail = res.statusText; try { const j = await res.json(); if (j && j.error) detail = j.error; } catch (_) {} throw new Error(`HTTP ${res.status}: ${detail}`); } if (res.status === 204) return null; return res.json(); } const API = { monitors: { list: () => apiFetch('/api/monitors.php'), get: (id) => apiFetch('/api/monitors.php?id=' + id), detail: (id, limit) => apiFetch('/api/monitor_detail.php?id=' + id + (limit ? '&limit=' + limit : '')), create: (body) => apiFetch('/api/monitors.php', { method: 'POST', body }), update: (id, b) => apiFetch('/api/monitors.php?id=' + id, { method: 'PUT', body: b }), remove: (id) => apiFetch('/api/monitors.php?id=' + id, { method: 'DELETE' }), reorder: (ids) => apiFetch('/api/monitors.php?action=reorder', { method: 'POST', body: { order: ids } }), }, incidents: { list: () => apiFetch('/api/incidents.php'), get: (id) => apiFetch('/api/incidents.php?id=' + id), create: (body) => apiFetch('/api/incidents.php', { method: 'POST', body }), update: (id, b) => apiFetch('/api/incidents.php?id=' + id, { method: 'PUT', body: b }), remove: (id) => apiFetch('/api/incidents.php?id=' + id, { method: 'DELETE' }), addUpdate: (id, label, text) => apiFetch('/api/incidents.php?id=' + id + '&action=update', { method: 'POST', body: { label, text } }), editUpdate: (id, updateId, b) => apiFetch('/api/incidents.php?id=' + id + '&action=update&updateId=' + updateId, { method: 'PUT', body: b }), removeUpdate: (id, updateId) => apiFetch('/api/incidents.php?id=' + id + '&action=update&updateId=' + updateId, { method: 'DELETE' }), }, maintenance: { list: () => apiFetch('/api/maintenance.php'), get: (id) => apiFetch('/api/maintenance.php?id=' + id), create: (body) => apiFetch('/api/maintenance.php', { method: 'POST', body }), update: (id, b) => apiFetch('/api/maintenance.php?id=' + id, { method: 'PUT', body: b }), remove: (id) => apiFetch('/api/maintenance.php?id=' + id, { method: 'DELETE' }), addUpdate: (id, label, text) => apiFetch('/api/maintenance.php?id=' + id + '&action=update', { method: 'POST', body: { label, text } }), editUpdate: (id, updateId, b) => apiFetch('/api/maintenance.php?id=' + id + '&action=update&updateId=' + updateId, { method: 'PUT', body: b }), removeUpdate: (id, updateId) => apiFetch('/api/maintenance.php?id=' + id + '&action=update&updateId=' + updateId, { method: 'DELETE' }), }, monitorChecks: { update: (id, b) => apiFetch('/api/monitor_checks.php?id=' + id, { method: 'PUT', body: b }), }, dashboard: { get: (range) => apiFetch('/api/dashboard.php' + (range ? '?range=' + encodeURIComponent(range) : '')), }, }; Object.assign(window, { API, apiFetch });