English | 日本語
Status page for https://status.unchained.co.jp.
Built with Astro + Cloudflare Pages Functions + Cloudflare Worker Cron.
Monitoring source is updown.io.
This project is not only a static status page UI. It is a small status platform that:
- fetches check states from updown.io
- enriches data into a 7-day service timeline
- stores normalized snapshots/events in Cloudflare KV
- serves same-origin status APIs from Pages Functions
- delivers browser notifications through Declarative Web Push (no Service Worker registration)
In short:
- Backend source of truth: updown.io
- Edge cache + event memory: Cloudflare KV (
STATUS_EVENTS) - API/UI delivery: Cloudflare Pages Functions + Astro frontend
- Background refresh + push fan-out: Cloudflare Worker Cron
- overall status summary (
Operational / Maintenance / Disruption) - per-service rows with:
- live state
- 7-day uptime timeline
- event-aware timeline popovers
- freshness indicator (
Synced/Delayed) - push opt-in UI (
Enable Push Notifications/Push Notifications Enabled) - browser notifications on status transitions
workers/status-events-cron.ts runs every minute (*/1 * * * *):
- Refresh
status.snapshot.v1.json- source: updown.io
/api/checks - enrichment:
/metrics+/downtimes - output: checks + period uptime + 7-day history
- source: updown.io
- Refresh
events.json- source: updown.io
/api/checks - computes state transitions (
operational/maintenance/disruption) - keeps only recent events window
- source: updown.io
- If a new latest event exists, dispatch Declarative Web Push to subscribers
status.snapshot.v1.json{ generated_at, checks[] }- optimized for fast UI list rendering
events.json{ generated_at, events[], latest{} }- optimized for transition/event timeline behavior
push:sub:<sha256(endpoint)>- stored browser push subscriptions
push:last-event-id- de-dup guard for push fan-out
Frontend calls same-origin APIs:
GET /api/checks- returns KV snapshot first
- falls back to direct updown.io if snapshot missing
GET /api/events.json- ensures freshness (
ensureFreshEventsDoc) - returns
checked_atper response for UI freshness display
- ensures freshness (
GET /api/checks/{token}/downtimesGET /api/checks/{token}/metrics?from=...&to=...- direct updown pass-through helpers (fallback/debug)
Subscription APIs:
GET /api/push/configPOST /api/push/subscribePOST /api/push/unsubscribe
Delivery:
- uses VAPID JWT + aes128gcm payload encryption in
functions/_lib/web-push.ts - sends
web_push: 8030Declarative payload - stale subscriptions (
404/410) are auto-removed
| Method | Path | Purpose |
|---|---|---|
| GET | /api/checks |
Snapshot-first checks list |
| GET | /api/events.json |
Events doc + checked_at |
| GET | /api/checks/{token}/downtimes |
Per-check downtime passthrough |
| GET | /api/checks/{token}/metrics |
Per-check metrics passthrough |
| GET | /api/push/config |
Push capability + VAPID public key |
| POST | /api/push/subscribe |
Store push subscription |
| POST | /api/push/unsubscribe |
Remove push subscription |
- Astro
^7.0.9 - TypeScript
^5.9.3 - Cloudflare Pages Functions
- Cloudflare Workers (Cron)
- Cloudflare KV (
STATUS_EVENTS) - updown.io API
- Node.js
>=22.12.0
UPDOWN_API_KEY(Pages + Worker)PUSH_VAPID_PRIVATE_KEY(secret)PUSH_VAPID_PUBLIC_KEY(plaintext var)- KV binding:
STATUS_EVENTS
wrangler.events-cron.toml:
name = "status-events-cron"
main = "workers/status-events-cron.ts"
[triggers]
crons = ["*/1 * * * *"]
[[kv_namespaces]]
binding = "STATUS_EVENTS"
id = "<namespace-id>"
[vars]
PUSH_VAPID_PUBLIC_KEY = "<public-vapid-key>"Set secrets:
npx wrangler secret put UPDOWN_API_KEY -c wrangler.events-cron.toml
npx wrangler secret put PUSH_VAPID_PRIVATE_KEY -c wrangler.events-cron.tomlInstall:
npm installRun commands:
npm run dev # Astro dev server
npm run dev:ui # UI dev server (http://localhost:4321)
npm run dev:cf # Pages Functions + KV proxy (http://localhost:8788)
npm run dev:cf:dist # Serve prebuilt dist via Pages dev
npm run check # Astro/TS checks
npm run build # Check + build
npm run preview # Preview buildRecommended workflow:
npm run dev:uifor fast UI iterationnpm run dev:cffor API/KV integrated behavior
Useful when local dev:cf data differs from production.
Export remote keys:
mkdir -p debug-data/kv-sync
npx wrangler kv key get events.json \
--namespace-id 6a495433dc4b40ecac1a328329474222 \
--remote --text > debug-data/kv-sync/events.remote.json
npx wrangler kv key get status.snapshot.v1.json \
--namespace-id 6a495433dc4b40ecac1a328329474222 \
--remote --text > debug-data/kv-sync/status.snapshot.v1.remote.jsonImport into local binding:
npx wrangler kv key put events.json \
--path debug-data/kv-sync/events.remote.json \
--namespace-id STATUS_EVENTS \
--local --persist-to .wrangler/state
npx wrangler kv key put status.snapshot.v1.json \
--path debug-data/kv-sync/status.snapshot.v1.remote.json \
--namespace-id STATUS_EVENTS \
--local --persist-to .wrangler/stateAfter deploy or cache-sensitive changes:
- hard reload the page
- verify timeline/date localization and popover interactions
- verify freshness badge updates (
Synced/Delayed) - verify push flow:
- subscribe from toggle
- trigger a real status transition
- confirm notification delivery
- for iOS/iPadOS push, verify from Home Screen app (not Safari tab)
src/
pages/
index.astro
public/
global.css
functions/
api/
checks.ts
events.json.ts
checks/[token]/
downtimes.ts
metrics.ts
push/
config.ts
subscribe.ts
unsubscribe.ts
_lib/
updown.ts
status-events.ts
status-snapshot.ts
push-subscriptions.ts
web-push.ts
workers/
status-events-cron.ts
wrangler.events-cron.toml- Push uses Declarative Web Push payload (
web_push: 8030) - No Service Worker registration is required for delivery in this implementation
- Push de-dup is managed by
push:last-event-idin KV - To test one-time resend, remove
push:last-event-idin remote KV

