cfwithoutincident/src/index.ts
2026-08-18 10:23:04 +09:00

350 lines
28 KiB
TypeScript

import { Elysia } from "elysia";
import { cron } from "@elysiajs/cron";
const CLOUDFLARE_STATUS_URL = "https://www.cloudflarestatus.com";
const CLOUDFLARE_API_URL = `${CLOUDFLARE_STATUS_URL}/api/v2`;
const REFRESH_INTERVAL = "*/5 * * * *";
type PageIndicator = "none" | "minor" | "major" | "critical" | "unknown";
type ComponentStatus =
| "operational"
| "degraded_performance"
| "partial_outage"
| "major_outage"
| "unknown";
interface CloudflareComponent {
id: string;
name: string;
status: ComponentStatus;
position?: number;
description?: string | null;
group?: boolean;
}
interface CloudflareIncident {
id: string;
name: string;
status: string;
impact?: string;
shortlink?: string;
started_at?: string;
created_at?: string;
updated_at?: string;
resolved_at?: string | null;
}
interface CloudflareMaintenance {
id: string;
name: string;
status: string;
impact?: string;
shortlink?: string;
started_at?: string;
scheduled_for?: string;
scheduled_until?: string;
}
interface CloudflareSummaryResponse {
page?: { name?: string; updated_at?: string };
status?: { indicator?: string; description?: string };
components?: CloudflareComponent[];
incidents?: CloudflareIncident[];
scheduled_maintenances?: CloudflareMaintenance[];
}
interface CloudflareIncidentsResponse {
incidents?: CloudflareIncident[];
}
interface StatusSnapshot {
ok: boolean;
fetchedAt: string | null;
pageUpdatedAt: string | null;
status: { indicator: PageIndicator; description: string };
streak: { days: number | null; since: string | null; activeIncident: boolean };
lastIncident: {
id: string;
name: string;
startedAt: string | null;
resolvedAt: string | null;
impact: string;
shortlink: string | null;
} | null;
incidents: Array<{
id: string;
name: string;
status: string;
impact: string;
startedAt: string | null;
resolvedAt: string | null;
shortlink: string | null;
}>;
components: Array<{
id: string;
name: string;
status: ComponentStatus;
description: string | null;
}>;
componentCounts: { total: number; operational: number; affected: number };
maintenances: Array<{
id: string;
name: string;
status: string;
impact: string;
startsAt: string | null;
endsAt: string | null;
shortlink: string | null;
}>;
error: string | null;
}
const initialSnapshot: StatusSnapshot = {
ok: false,
fetchedAt: null,
pageUpdatedAt: null,
status: { indicator: "unknown", description: "Checking Cloudflare status" },
streak: { days: null, since: null, activeIncident: false },
lastIncident: null,
incidents: [],
components: [],
componentCounts: { total: 0, operational: 0, affected: 0 },
maintenances: [],
error: null,
};
let snapshot = initialSnapshot;
function asArray<T>(value: unknown): T[] {
return Array.isArray(value) ? (value as T[]) : [];
}
function isIncidentActive(incident: CloudflareIncident): boolean {
return !["resolved", "postmortem"].includes(incident.status);
}
function toTime(value?: string | null): number {
if (!value) return 0;
const time = Date.parse(value);
return Number.isNaN(time) ? 0 : time;
}
function daysSince(value: string | null, now = Date.now()): number | null {
if (!value) return null;
const since = toTime(value);
if (!since) return null;
return Math.max(0, Math.floor((now - since) / (1000 * 60 * 60 * 24)));
}
function indicator(value?: string): PageIndicator {
if (value === "none" || value === "minor" || value === "major" || value === "critical") return value;
return "unknown";
}
function componentStatus(value?: string): ComponentStatus {
if (
value === "operational" ||
value === "degraded_performance" ||
value === "partial_outage" ||
value === "major_outage"
) return value;
return "unknown";
}
function normalizeIncident(incident: CloudflareIncident) {
return {
id: incident.id,
name: incident.name,
status: incident.status,
impact: incident.impact ?? "none",
startedAt: incident.started_at ?? incident.created_at ?? null,
resolvedAt: incident.resolved_at ?? null,
shortlink: incident.shortlink ?? null,
};
}
function normalizeMaintenance(maintenance: CloudflareMaintenance) {
return {
id: maintenance.id,
name: maintenance.name,
status: maintenance.status,
impact: maintenance.impact ?? "none",
startsAt: maintenance.scheduled_for ?? maintenance.started_at ?? null,
endsAt: maintenance.scheduled_until ?? null,
shortlink: maintenance.shortlink ?? null,
};
}
async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(url, {
headers: { accept: "application/json", "user-agent": "cloudflare-status-dday/1.0" },
signal: AbortSignal.timeout(10_000),
});
if (!response.ok) throw new Error(`Cloudflare API returned ${response.status}`);
return (await response.json()) as T;
}
async function refreshStatus(source: "startup" | "cron") {
try {
const [summary, incidentsResponse] = await Promise.all([
fetchJson<CloudflareSummaryResponse>(`${CLOUDFLARE_API_URL}/summary.json`),
fetchJson<CloudflareIncidentsResponse>(`${CLOUDFLARE_API_URL}/incidents.json`),
]);
const incidentsById = new Map<string, CloudflareIncident>();
for (const incident of [
...asArray<CloudflareIncident>(summary.incidents),
...asArray<CloudflareIncident>(incidentsResponse.incidents),
]) {
if (incident.id) incidentsById.set(incident.id, incident);
}
const incidents = [...incidentsById.values()].sort(
(a, b) => toTime(b.started_at ?? b.created_at) - toTime(a.started_at ?? a.created_at),
);
const activeIncidents = incidents.filter(isIncidentActive);
const latestIncident = incidents[0];
const components = asArray<CloudflareComponent>(summary.components)
.filter((component) => !component.group)
.sort((a, b) => {
const aAffected = componentStatus(a.status) === "operational" ? 1 : 0;
const bAffected = componentStatus(b.status) === "operational" ? 1 : 0;
return aAffected - bAffected || (a.position ?? 0) - (b.position ?? 0);
})
.map((component) => ({
id: component.id,
name: component.name,
status: componentStatus(component.status),
description: component.description ?? null,
}));
const maintenances = asArray<CloudflareMaintenance>(summary.scheduled_maintenances)
.sort((a, b) => toTime(a.scheduled_for ?? a.started_at) - toTime(b.scheduled_for ?? b.started_at))
.map(normalizeMaintenance);
const now = new Date().toISOString();
snapshot = {
ok: true,
fetchedAt: now,
pageUpdatedAt: summary.page?.updated_at ?? null,
status: {
indicator: indicator(summary.status?.indicator),
description: summary.status?.description ?? "Status information is unavailable",
},
streak: {
days: daysSince(latestIncident?.started_at ?? latestIncident?.created_at ?? null),
since: latestIncident?.started_at ?? latestIncident?.created_at ?? null,
activeIncident: activeIncidents.length > 0,
},
lastIncident: latestIncident
? {
id: latestIncident.id,
name: latestIncident.name,
startedAt: latestIncident.started_at ?? latestIncident.created_at ?? null,
resolvedAt: latestIncident.resolved_at ?? null,
impact: latestIncident.impact ?? "none",
shortlink: latestIncident.shortlink ?? null,
}
: null,
incidents: incidents.slice(0, 6).map(normalizeIncident),
components,
componentCounts: {
total: components.length,
operational: components.filter((component) => component.status === "operational").length,
affected: components.filter((component) => component.status !== "operational").length,
},
maintenances: maintenances.slice(0, 5),
error: null,
};
console.log(`[${source}] Cloudflare status updated at ${now}`);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
snapshot = { ...snapshot, ok: false, error: message };
console.error(`[${source}] Failed to update Cloudflare status: ${message}`);
}
}
const INDEX_HTML = String.raw`<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#07111f" />
<meta name="description" content="A Cloudflare reliability dashboard tracking days without an incident" />
<title>Cloudflare · Days Without Incident</title>
<style>
:root { color-scheme: dark; --bg: #06101e; --card: rgba(13, 31, 52, .7); --line: rgba(141, 196, 255, .16); --text: #eef7ff; --muted: #8da8c0; --blue: #69b7ff; --cyan: #59e3f4; --green: #58e39a; --amber: #ffc76b; --red: #ff7d9b; }
* { box-sizing: border-box; }
html { min-width: 320px; background: var(--bg); }
body { margin: 0; min-height: 100vh; overflow-x: hidden; color: var(--text); font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: radial-gradient(circle at 50% -8%, rgba(70, 168, 255, .24), transparent 36rem), radial-gradient(circle at 10% 58%, rgba(20, 103, 170, .11), transparent 26rem), linear-gradient(150deg, #071527 0%, #06101e 48%, #030912 100%); }
body::before { position: fixed; inset: 0; z-index: -1; pointer-events: none; content: ""; opacity: .22; background-image: linear-gradient(rgba(127, 182, 235, .07) 1px, transparent 1px), linear-gradient(90deg, rgba(127, 182, 235, .07) 1px, transparent 1px); background-size: 72px 72px; mask-image: linear-gradient(to bottom, black, transparent 78%); }
a { color: inherit; } button { font: inherit; }
.shell { width: min(1120px, calc(100% - 40px)); margin: 0 auto; padding: 28px 0 64px; }
.topbar { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.brand { display: inline-flex; align-items: center; gap: 12px; text-decoration: none; }
.brand-mark { display: grid; width: 38px; height: 38px; place-items: center; border: 1px solid rgba(124, 203, 255, .48); border-radius: 12px; color: #c7efff; background: linear-gradient(145deg, rgba(65, 176, 255, .38), rgba(19, 72, 126, .45)); box-shadow: 0 0 28px rgba(45, 171, 255, .2); }
.brand-mark svg { width: 22px; height: 22px; } .brand-copy { display: grid; gap: 2px; } .brand-copy strong { font-size: 14px; letter-spacing: .04em; } .brand-copy span, .eyebrow { color: var(--muted); font-size: 11px; letter-spacing: .16em; text-transform: uppercase; }
.source-link { color: var(--muted); font-size: 12px; text-decoration: none; transition: color .2s ease; } .source-link:hover { color: var(--text); }
.hero { padding: 84px 0 68px; text-align: center; } .eyebrow { display: inline-flex; align-items: center; gap: 8px; color: #9dcbeb; } .eyebrow::before { width: 5px; height: 5px; border-radius: 50%; background: var(--cyan); box-shadow: 0 0 14px var(--cyan); content: ""; }
.hero h1 { max-width: 780px; margin: 18px auto 16px; font-size: clamp(35px, 7vw, 72px); line-height: .98; letter-spacing: -.065em; text-wrap: balance; } .hero h1 em { color: var(--blue); font-style: normal; text-shadow: 0 0 30px rgba(105, 183, 255, .28); }
.hero-subtitle { max-width: 560px; margin: 0 auto; color: var(--muted); font-size: 15px; line-height: 1.7; } .counter-wrap { position: relative; width: fit-content; margin: 42px auto 0; } .counter-wrap::before { position: absolute; inset: 12% -12%; z-index: -1; border-radius: 50%; background: rgba(49, 159, 255, .18); filter: blur(42px); content: ""; }
.counter { font-size: clamp(108px, 24vw, 246px); font-weight: 750; line-height: .8; letter-spacing: -.1em; color: #f5fbff; text-shadow: 0 8px 48px rgba(54, 165, 255, .2); } .counter-label { margin-top: 22px; color: #a9c7de; font-size: clamp(13px, 2vw, 16px); letter-spacing: .2em; text-transform: uppercase; } .hero-note { margin: 18px 0 0; color: #6f8ca6; font-size: 12px; }
.status-pill { display: inline-flex; align-items: center; gap: 8px; margin-top: 26px; padding: 9px 14px; border: 1px solid rgba(88, 227, 154, .25); border-radius: 999px; color: #b7f6d2; background: rgba(53, 162, 105, .11); font-size: 12px; } .status-pill::before { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 13px var(--green); content: ""; } .status-pill[data-indicator="minor"] { border-color: rgba(255, 199, 107, .32); color: #ffe3ad; background: rgba(193, 123, 30, .12); } .status-pill[data-indicator="minor"]::before { background: var(--amber); box-shadow: 0 0 13px var(--amber); } .status-pill[data-indicator="major"], .status-pill[data-indicator="critical"] { border-color: rgba(255, 125, 155, .32); color: #ffc0d0; background: rgba(173, 48, 83, .12); } .status-pill[data-indicator="major"]::before, .status-pill[data-indicator="critical"]::before { background: var(--red); box-shadow: 0 0 13px var(--red); } .status-pill[data-indicator="unknown"] { border-color: var(--line); color: var(--muted); background: rgba(120, 150, 180, .08); } .status-pill[data-indicator="unknown"]::before { background: var(--muted); box-shadow: none; }
.grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; } .card { min-width: 0; border: 1px solid var(--line); border-radius: 20px; background: linear-gradient(145deg, rgba(19, 48, 79, .72), rgba(8, 23, 41, .76)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .035), 0 18px 45px rgba(0, 0, 0, .14); } .card-pad { padding: 24px; }
.health-card { grid-column: span 7; } .components-card { grid-column: span 5; } .incidents-card, .maintenance-card { grid-column: span 6; } .card-heading { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 20px; } .card-title { margin: 0; font-size: 15px; letter-spacing: -.02em; } .card-kicker { margin: 5px 0 0; color: var(--muted); font-size: 12px; } .live-dot { display: inline-flex; align-items: center; gap: 6px; color: #8fdcb1; font-size: 11px; white-space: nowrap; } .live-dot::before { width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 10px var(--green); content: ""; }
.health-summary { display: flex; align-items: center; gap: 17px; padding: 17px; border: 1px solid rgba(111, 192, 255, .13); border-radius: 14px; background: rgba(2, 14, 28, .25); } .health-icon { display: grid; width: 42px; height: 42px; flex: 0 0 42px; place-items: center; border-radius: 12px; color: var(--cyan); background: rgba(64, 193, 224, .11); } .health-icon svg { width: 21px; height: 21px; } .health-summary strong { display: block; font-size: 15px; } .health-summary span { display: block; margin-top: 5px; color: var(--muted); font-size: 12px; }
.metric-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 14px; } .metric { padding: 14px; border-radius: 14px; background: rgba(4, 17, 31, .32); } .metric strong { display: block; font-size: 20px; letter-spacing: -.04em; } .metric span { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; } .component-count { color: var(--muted); font-size: 12px; }
.component-list { display: grid; gap: 8px; max-height: 265px; overflow-y: auto; padding-right: 3px; } .component-list::-webkit-scrollbar { width: 4px; } .component-list::-webkit-scrollbar-thumb { border-radius: 4px; background: rgba(141, 196, 255, .22); } .component { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 10px 11px; border-radius: 10px; background: rgba(4, 17, 31, .27); } .component .dot, .incident-status { width: 7px; height: 7px; flex: 0 0 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 9px rgba(88, 227, 154, .7); } .component[data-status="degraded_performance"] .dot { background: var(--amber); box-shadow: 0 0 9px rgba(255, 199, 107, .7); } .component[data-status="partial_outage"] .dot, .component[data-status="major_outage"] .dot { background: var(--red); box-shadow: 0 0 9px rgba(255, 125, 155, .7); } .component-name { overflow: hidden; color: #c7dced; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; } .component-state { margin-left: auto; color: var(--muted); font-size: 10px; white-space: nowrap; }
.incident-list, .maintenance-list { display: grid; gap: 8px; } .incident, .maintenance { display: flex; align-items: center; gap: 12px; min-width: 0; padding: 12px; border-radius: 12px; background: rgba(4, 17, 31, .3); text-decoration: none; transition: background .2s ease, transform .2s ease; } .incident:hover, .maintenance:hover { transform: translateY(-1px); background: rgba(26, 67, 106, .38); } .incident-status { background: var(--muted); box-shadow: none; } .incident[data-active="true"] .incident-status { background: var(--amber); box-shadow: 0 0 9px rgba(255, 199, 107, .7); } .incident-copy, .maintenance-copy { min-width: 0; flex: 1; } .incident-copy strong, .maintenance-copy strong { display: block; overflow: hidden; font-size: 12px; font-weight: 550; text-overflow: ellipsis; white-space: nowrap; } .incident-copy span, .maintenance-copy span { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; } .incident-impact { color: #a4bfd4; font-size: 10px; text-transform: capitalize; } .empty { padding: 22px 12px; color: var(--muted); font-size: 12px; text-align: center; }
.footer { display: flex; justify-content: space-between; gap: 20px; margin-top: 22px; color: #648099; font-size: 11px; } .footer a { color: #82b8df; text-decoration: none; } .error-banner { display: none; margin: 18px 0 0; padding: 11px 14px; border: 1px solid rgba(255, 125, 155, .22); border-radius: 10px; color: #ffc0d0; background: rgba(173, 48, 83, .1); font-size: 12px; } .error-banner[data-visible="true"] { display: block; }
@media (max-width: 760px) { .shell { width: min(100% - 28px, 600px); padding-top: 20px; } .source-link { display: none; } .hero { padding: 68px 0 52px; } .hero h1 { font-size: clamp(34px, 12vw, 62px); } .counter { font-size: clamp(104px, 34vw, 190px); } .health-card, .components-card, .incidents-card, .maintenance-card { grid-column: span 12; } .footer { flex-direction: column; gap: 8px; } }
@media (max-width: 430px) { .card-pad { padding: 18px; } .metric strong { font-size: 17px; } .component-state { display: none; } }
</style>
</head>
<body>
<main class="shell">
<nav class="topbar"><a class="brand" href="/"><span class="brand-mark" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3.5 19 6v5.4c0 4.3-2.9 7.9-7 9.1-4.1-1.2-7-4.8-7-9.1V6l7-2.5Z"/><path d="m8.7 12.2 2.1 2.1 4.6-4.6"/></svg></span><span class="brand-copy"><strong>CF STATUS D-DAY</strong><span>Cloudflare reliability</span></span></a><a class="source-link" href="https://www.cloudflarestatus.com" target="_blank" rel="noreferrer">cloudflarestatus.com ↗</a></nav>
<section class="hero"><span class="eyebrow">a quiet day on the edge</span><h1>Cloudflare has gone<br /><em id="hero-days">—</em> days without an incident</h1><p class="hero-subtitle">We check the official Cloudflare Status API on a regular schedule<br />to track time since the last incident.</p><div class="counter-wrap"><div id="days" class="counter" aria-live="polite">—</div><div class="counter-label">days without incident</div></div><div id="status-pill" class="status-pill" data-indicator="unknown">Checking status</div><p id="hero-note" class="hero-note">Loading Cloudflare status data.</p><div id="error-banner" class="error-banner" data-visible="false"></div></section>
<section class="grid" aria-label="Cloudflare status overview">
<article class="card health-card"><div class="card-pad"><div class="card-heading"><div><h2 class="card-title">Current overall status</h2><p class="card-kicker">Cloudflare's official page rollup</p></div><span class="live-dot">LIVE</span></div><div class="health-summary"><div class="health-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 12h3l2-6 3 12 2-6h6"/><path d="M4 19h16" opacity=".45"/></svg></div><div><strong id="status-description">Checking status</strong><span id="last-updated">Last synced: —</span></div></div><div class="metric-row"><div class="metric"><strong id="total-components">—</strong><span>Total services</span></div><div class="metric"><strong id="operational-components">—</strong><span>Operational</span></div><div class="metric"><strong id="affected-components">—</strong><span>Needs attention</span></div></div></div></article>
<article class="card components-card"><div class="card-pad"><div class="card-heading"><div><h2 class="card-title">Service components</h2><p class="card-kicker">Affected items are shown first</p></div><span id="component-count" class="component-count">—</span></div><div id="component-list" class="component-list"><div class="empty">Loading…</div></div></div></article>
<article class="card incidents-card"><div class="card-pad"><div class="card-heading"><div><h2 class="card-title">Recent incidents</h2><p class="card-kicker">Recent incidents recorded by Cloudflare Status</p></div></div><div id="incident-list" class="incident-list"><div class="empty">Loading…</div></div></div></article>
<article class="card maintenance-card"><div class="card-pad"><div class="card-heading"><div><h2 class="card-title">Scheduled maintenance</h2><p class="card-kicker">Scheduled or in-progress work</p></div></div><div id="maintenance-list" class="maintenance-list"><div class="empty">No scheduled maintenance.</div></div></div></article>
</section>
<footer class="footer"><span id="footer-status">Updates automatically every 5 minutes.</span><span>Data from <a href="https://www.cloudflarestatus.com/api" target="_blank" rel="noreferrer">Cloudflare Status API</a></span></footer>
</main>
<script>
const labels = { operational: "Operational", degraded_performance: "Degraded performance", partial_outage: "Partial outage", major_outage: "Major outage", unknown: "Checking" };
const statusLabels = { none: "All systems operational", minor: "Minor performance issues", major: "Partial system outage", critical: "Major system outage", unknown: "Checking status" };
const impactLabels = { none: "No impact", minor: "Minor impact", major: "Major impact", critical: "Critical impact" };
const incidentStatusLabels = { investigating: "Investigating", identified: "Identified", monitoring: "Monitoring", resolved: "Resolved", postmortem: "Postmortem" };
const maintenanceStatusLabels = { scheduled: "Scheduled", in_progress: "In progress", verifying: "Verifying", completed: "Completed" };
const dateFormatter = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
const fullDateFormatter = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit" });
const byId = (id) => document.getElementById(id);
const formatDate = (value, full) => { if (!value) return "—"; const date = new Date(value); return Number.isNaN(date.getTime()) ? "—" : (full ? fullDateFormatter : dateFormatter).format(date); };
const escapeHtml = (value) => String(value || "").replace(/[&<>\"']/g, (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#039;" }[character]));
const setText = (id, value) => { byId(id).textContent = value; };
function renderComponents(data) { const list = byId("component-list"); setText("component-count", data.componentCounts.total + " components"); if (!data.components.length) { list.innerHTML = '<div class="empty">No component data available.</div>'; return; } list.innerHTML = data.components.slice(0, 14).map((component) => '<div class="component" data-status="' + component.status + '"><span class="dot"></span><span class="component-name" title="' + escapeHtml(component.description || component.name) + '">' + escapeHtml(component.name) + '</span><span class="component-state">' + (labels[component.status] || labels.unknown) + '</span></div>').join(""); if (data.components.length > 14) list.insertAdjacentHTML("beforeend", '<div class="empty">' + (data.components.length - 14) + ' more components</div>'); }
function renderIncidents(data) { const list = byId("incident-list"); if (!data.incidents.length) { list.innerHTML = '<div class="empty">No recorded incidents.</div>'; return; } list.innerHTML = data.incidents.map((incident) => { const active = incident.status !== "resolved" && incident.status !== "postmortem"; const content = '<span class="incident-status"></span><span class="incident-copy"><strong>' + escapeHtml(incident.name) + '</strong><span>' + (incidentStatusLabels[incident.status] || incident.status) + ' · ' + formatDate(incident.startedAt) + '</span></span><span class="incident-impact">' + (impactLabels[incident.impact] || incident.impact) + '</span>'; return incident.shortlink ? '<a class="incident" data-active="' + active + '" href="' + escapeHtml(incident.shortlink) + '" target="_blank" rel="noreferrer">' + content + '</a>' : '<div class="incident" data-active="' + active + '">' + content + '</div>'; }).join(""); }
function renderMaintenances(data) { const list = byId("maintenance-list"); if (!data.maintenances.length) { list.innerHTML = '<div class="empty">No scheduled maintenance.</div>'; return; } list.innerHTML = data.maintenances.map((maintenance) => { const content = '<span class="incident-status"></span><span class="maintenance-copy"><strong>' + escapeHtml(maintenance.name) + '</strong><span>' + (maintenanceStatusLabels[maintenance.status] || maintenance.status) + ' · ' + formatDate(maintenance.startsAt) + '</span></span><span class="incident-impact">' + (impactLabels[maintenance.impact] || maintenance.impact) + '</span>'; return maintenance.shortlink ? '<a class="maintenance" href="' + escapeHtml(maintenance.shortlink) + '" target="_blank" rel="noreferrer">' + content + '</a>' : '<div class="maintenance">' + content + '</div>'; }).join(""); }
function render(data) { const indicator = data.status.indicator; const active = data.streak.activeIncident; byId("status-pill").dataset.indicator = indicator; setText("status-pill", statusLabels[indicator] || statusLabels.unknown); setText("status-description", data.status.description); setText("hero-days", data.streak.days === null ? "—" : data.streak.days.toLocaleString("en-US")); setText("days", data.streak.days === null ? "—" : data.streak.days.toLocaleString("en-US")); setText("hero-note", active ? "The counter is paused while an incident is in progress." : (data.lastIncident ? "Last incident: " + data.lastIncident.name + " · " + formatDate(data.lastIncident.startedAt, true) : "Waiting for recent incident data.")); setText("last-updated", "Last synced: " + formatDate(data.fetchedAt, true)); setText("total-components", data.componentCounts.total.toLocaleString("en-US")); setText("operational-components", data.componentCounts.operational.toLocaleString("en-US")); setText("affected-components", data.componentCounts.affected.toLocaleString("en-US")); setText("footer-status", data.error ? "Update failed after the last successful refresh." : "Updates automatically every 5 minutes."); byId("error-banner").dataset.visible = data.error ? "true" : "false"; setText("error-banner", data.error ? "Update error: " + data.error : ""); renderComponents(data); renderIncidents(data); renderMaintenances(data); }
async function load() { try { const response = await fetch("/api/status", { cache: "no-store" }); render(await response.json()); } catch (error) { setText("hero-note", "Unable to load status data. Retrying shortly."); } }
load(); setInterval(load, 60 * 1000);
</script>
</body>
</html>`;
const port = Number.parseInt(Bun.env.PORT ?? "3000", 10);
const hostname = Bun.env.HOST ?? "0.0.0.0";
const app = new Elysia()
.use(cron({ name: "cloudflare-status-refresh", pattern: REFRESH_INTERVAL, catch: true, run: () => refreshStatus("cron") }))
.get("/", () => new Response(INDEX_HTML, { headers: { "content-type": "text/html; charset=utf-8" } }))
.get("/api/status", () => new Response(JSON.stringify(snapshot), { headers: { "cache-control": "no-store", "content-type": "application/json; charset=utf-8" } }))
.get("/healthz", () => ({ ok: true, fetchedAt: snapshot.fetchedAt, cloudflareOk: snapshot.ok }))
.listen({ port, hostname });
await refreshStatus("startup");
console.log(`🦊 Cloudflare D-Day is running at ${app.server?.hostname}:${app.server?.port}`);