// status.moleculesai.app — read-only status page for Molecules AI services. // // Pulls the probe-list config + per-site history JSONL from the // molecule-ai-status repo on Gitea, renders a one-row-per-service // dashboard with current state + a 24h-history sparkline. // // Why no framework: this page is plain DOM + fetch. Zero build step, // zero dependencies, zero supply-chain surface. The thing it MUST do // well is "load fast, show correct status, never lie." React/Vue // would be cargo-culting at this scale. // // Data source: same-origin /data/* paths, Vercel-rewritten to // git.moleculesai.app raw URLs. The rewrite avoids cross-origin // browser fetches (Gitea doesn't send Access-Control-Allow-Origin // on raw file responses). vercel.json owns the rewrite map. const HISTORY_URL = (slug) => `/data/history/${slug}.jsonl`; const CONFIG_URL = `/data/.upptimerc.yml`; const REPO_BROWSE = "https://git.moleculesai.app/molecule-ai/molecule-ai-status"; // Window of history we render in the sparkline (24h of probes at one // per 5 minutes ≈ 288). Cap to keep the DOM bounded if a site has // been probing for years. const SPARKLINE_LIMIT = 288; // Slugify must match the probe binary's slugify() in cmd/probe/main.go // — the page reads files the probe writes, so the slugging rule is // load-bearing. Mirror in tests if/when this gets a follow-up. function slugify(s) { let out = ""; let last = "-"; for (const c of s.toLowerCase()) { const isAlnum = (c >= "a" && c <= "z") || (c >= "0" && c <= "9"); if (isAlnum) { out += c; last = c; } else if (last !== "-") { out += "-"; last = "-"; } } return out.replace(/^-+|-+$/g, ""); } // Minimal YAML parser for the subset of .upptimerc.yml we read: // only the `sites:` list of `{name, url}`. Anything more elaborate // (anchors, multiline strings, etc.) is overkill — the upstream // upptime config schema is intentionally simple. function parseSites(yamlText) { const sites = []; let inSites = false; let current = null; for (const rawLine of yamlText.split("\n")) { const line = rawLine.replace(/\r$/, ""); if (line.startsWith("#")) continue; if (/^\s*$/.test(line)) continue; if (/^sites:\s*$/.test(line)) { inSites = true; continue; } if (inSites && /^[a-zA-Z]/.test(line)) { // hit a top-level key after sites: — bail inSites = false; } if (!inSites) continue; const itemStart = line.match(/^\s*-\s+name:\s*(.+)$/); if (itemStart) { if (current) sites.push(current); current = { name: itemStart[1].trim().replace(/^["']|["']$/g, "") }; continue; } const urlMatch = line.match(/^\s+url:\s*(.+)$/); if (urlMatch && current) { current.url = urlMatch[1].trim().replace(/^["']|["']$/g, ""); } } if (current) sites.push(current); return sites.filter((s) => s.name && s.url); } // Parse a JSONL response into an array of Result objects. Tolerant of // trailing newlines + (rarely) blank lines from a partial-write race. function parseJSONL(text) { const out = []; for (const line of text.split("\n")) { if (!line.trim()) continue; try { out.push(JSON.parse(line)); } catch { // skip malformed line — better than the whole page erroring } } return out; } // Best-effort fetch — returns null on failure (no exceptions). async function fetchText(url) { try { const resp = await fetch(url, { cache: "no-cache" }); if (!resp.ok) return null; return await resp.text(); } catch { return null; } } // Render a row for one site given its latest results. function renderRow(site, results) { const last = results[results.length - 1]; const status = !last ? "unknown" : last.success ? "up" : "down"; const latency = last && last.success ? `${last.latency_ms} ms` : "—"; // Sparkline: last SPARKLINE_LIMIT entries, one bar per. Bar height // proportional to latency (clamped). Failing checks render red and // taller (so eye is drawn to outages). const recent = results.slice(-SPARKLINE_LIMIT); const succ = recent.filter((r) => r.success); const maxLat = Math.max(50, ...succ.map((r) => r.latency_ms)); const spark = recent .map((r) => { const cls = r.success ? "" : "fail"; const h = !r.success ? 20 : Math.max(2, Math.round((r.latency_ms / maxLat) * 18)); return ``; }) .join(""); return `
`; } function escape(s) { return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'", })[c]); } function renderSummary(rows) { const total = rows.length; const up = rows.filter((r) => r.status === "up").length; const down = rows.filter((r) => r.status === "down").length; const unknown = rows.filter((r) => r.status === "unknown").length; let dot, text, sub; if (total === 0) { dot = "var(--ink-soft)"; text = "No services configured"; sub = "Add `.upptimerc.yml` entries."; } else if (down === 0 && unknown === 0) { dot = "var(--green)"; text = "All systems operational"; sub = `${up} of ${total} services responding normally.`; } else if (down === 0) { dot = "var(--amber)"; text = "Status partially unknown"; sub = `${up} up · ${unknown} no recent data.`; } else if (up === 0) { dot = "var(--red)"; text = "Major outage"; sub = `${down} services failing.`; } else { dot = "var(--amber)"; text = "Partial outage"; sub = `${up} up · ${down} down · ${unknown} unknown.`; } return `${CONFIG_URL} is reachable (Vercel rewrites /data/* to ${REPO_BROWSE}/raw/branch/main/$1)..upptimerc.yml.