Skip to content

Latest commit

 

History

History
212 lines (168 loc) · 7.25 KB

API.md

File metadata and controls

212 lines (168 loc) · 7.25 KB

Statusmonitor API

JSON API served by the same Dart process as the static Vue page. All routes, including static assets, send a 30-second cache policy intended for Cloudflare.

Cache headers

Every response includes:

Cache-Control: public, max-age=30, s-maxage=30, stale-while-revalidate=30
CDN-Cache-Control: max-age=30
Cloudflare-CDN-Cache-Control: max-age=30

Cloudflare does not cache extensionless JSON by default. Add a Cache Rule:

  • If URI Path starts with / (or at least /api/)
  • Then: Eligible for cache, Edge TTL 30 seconds, respect origin Cache-Control

That keeps origin hits close to two per unique URL per minute (plus one per POP on refresh).

Official status ingestion

The first detail tab displays operator notices from https://status.schulportal.hessen.de/. The backend fetches that stable URL at startup and every five minutes, following up to five redirects. No versioned WordPress URL is configured or stored. Redirects must use HTTPS on schulportal.hessen.de or one of its subdomains, without credentials or custom ports. A move outside this domain requires a deliberate code change.

Fetches have a 25-second overall deadline and a 2 MiB decoded-body limit. Only a complete parse of all six service sections replaces the SQLite snapshot; network errors or layout changes retain the last successful snapshot across restarts. API requests never trigger upstream requests. The frontend polls the cached API every minute, independently of login-monitoring requests.

The parser produces plain text and fixed status enums only. Scripts, styles, embedded content and other active subtrees are discarded. No upstream URLs or HTML attributes are forwarded. The frontend uses Vue text interpolation only, and the original-page link is a fixed HTTPS URL. Encoded markup is displayed as literal text. Responses also send nosniff and a CSP prohibiting inline scripts, objects, frames embedding this page, and foreign script/connect sources. Inline CSS remains allowed for the existing charts; upstream CSS is discarded.

test/fixtures/official.html records the official article on 2026-10-25 for offline parser and browser tests. Fixture mode does not contact the official site.

A further 50 Wayback snapshots of the original and -3 pages (2023–2026) are covered by offline regressions; see the historic parser audit for scope, findings, source URLs and reproduction commands.

Endpoints

GET /api/official

Last successfully parsed operator notices (independent of our login probes):

{
  "sourceUrl": "https://status.schulportal.hessen.de/",
  "fetchedAt": 1792926000,
  "stale": false,
  "services": [
    {
      "id": "PaedOrg",
      "name": "Pädagogische Organisation (PaedOrg)",
      "status": "restricted",
      "maintenance": true,
      "notice": "Operator status notice and previous restriction details",
      "update": "Scheduled maintenance and previous update details"
    }
  ]
}

status is operational, restricted, or unknown, based on the operator's summary symbols; it is never inferred from our probes. maintenance is separate because maintenance and restrictions can coexist. fetchedAt is the retrieval time, not the operator's publication time. stale is true after a failed fetch, when the snapshot is at least ten minutes old, or when no snapshot exists. Before the first successful fetch, fetchedAt is null and services is empty. The endpoint returns 200 in this case so the frontend can show an unavailable state without affecting the monitoring tabs.

GET /api/status

Secondary check: current online status only.

{
  "online": true,
  "status": "operational",
  "checkedAt": 1784116800,
  "responseMs": 240,
  "code": 0,
  "inHolidays": true,
  "holidayLabel": "Sommerferien"
}

status is one of operational, down, misconfigured. online is true only when status is operational.

code is the last probe result:

code meaning
0 ok
1 timeout
2 HTTP / network / Lanis down
3 authentication (monitor credentials)
4 unexpected error

Persistent code=3 is treated as misconfigured, not as an SPH outage.

GET /api/summary

Current status plus uptime for fixed windows.

{
  "current": { "online": true, "status": "operational" },
  "windows": {
    "24h": { "uptime": 97.917, "avgMs": 250, "checks": 1441, "failures": 30 },
    "7d": { "uptime": 99.053, "avgMs": 260, "checks": 3169, "failures": 30 },
    "30d": { "uptime": 99.2, "avgMs": 255, "checks": 800, "failures": 4 },
    "90d": { "uptime": 99.4, "avgMs": 252, "checks": 1200, "failures": 6 },
    "180d": { "uptime": 99.6, "avgMs": 250, "checks": 2000, "failures": 7 },
    "1y": { "uptime": 99.7, "avgMs": 249, "checks": 3000, "failures": 8 },
    "2y": { "uptime": 99.8, "avgMs": 248, "checks": 4000, "failures": 8 }
  }
}

Uptime is 100 * (checks - failures) / checks over raw samples in that window.

GET /api/incidents

Measured failure spans for the last 30 days, derived from individual probe results (not chart buckets). Each span runs from the first failed check until the next successful check. Isolated single-probe failures are omitted; a span is listed only when at least two consecutive checks failed.

{
  "from": 1781524800,
  "to": 1784116800,
  "incidents": [
    { "start": 1784111400, "end": 1784113200 },
    { "start": 1782900000, "end": 1782910800 }
  ]
}

GET /api/history/{24h|7d|30d|90d|180d|1y|2y}

Fixed paths so Cloudflare cache keys stay stable. Unknown windows return 404.

window bucket
24h 1 minute
7d 5 minutes
30d 1 hour
90d 2 hours
180d 4 hours
1y 6 hours
2y 6 hours
{
  "window": "24h",
  "bucketSeconds": 60,
  "from": 1784030400,
  "to": 1784116800,
  "points": [[1784030400, 220, 1], [1784030460, null, 0]]
}

Each point is [ts, ms|null, ok|null]. ok is 0 if any sample in the bucket failed, 1 if all succeeded, or null if the bucket has no samples. ms is the average of successful samples, or null when unavailable.

GET /api/holidays

Hesse Schulferien covering the two-year history window. Served only from the local SQLite snapshot. The origin fetches https://schulferien-api.de/api/v1/{year}/HE at most once every 7 days. Client requests never reach that API.

{
  "state": "HE",
  "updatedAt": 1784116800,
  "periods": [
    {
      "start": 1782691200,
      "end": 1786147140,
      "name": "sommerferien",
      "label": "Sommerferien"
    }
  ]
}

Environment

variable default notes
LANIS_SCHOOL_ID — required in live mode
LANIS_USERNAME — required in live mode
LANIS_PASSWORD — required in live mode
PORT 8080 listen port
DATABASE_PATH data/status.db use :memory: in fixture mode
STATUSMONITOR_MODE live fixture skips probes and holiday refresh
STATUSMONITOR_NOW wall clock pinned unix seconds for fixtures
STATUSMONITOR_STATIC_DIR frontend/dist Vue build output
STATUSMONITOR_FIXTURE_DIR test/fixtures JSON seed files

liblanis is GPL-3.0. A distributed binary that links it should be treated as GPL-3.0 even though this repository’s LICENSE file is MIT.