DocsStatus pages and badges for people outside Yarrix

Status pages and badges

View source on GitHubdocs/status-pages.md

On this page

A status page is a read-only page that answers "is it working?" for people who do not use Yarrix: a team, customers, a wall display. A badge is a small image or JSON that says the same thing in one line, for a README or a dashboard.

Both are off by default. With nothing configured, /status/... and /badge/... answer 404 not found, as if they did not exist. Changing either field needs an admin signed in to the web app: an API token is refused with exec_field_requires_admin_session, because these fields decide what a visitor with no sign-in can see.

A status page#

In yarrix.yaml:

status_pages:
  - id: office
    title: Office systems
    access: public          # public: no sign-in. viewer (the default): any signed-in user.
    show_reasons: false     # default false
    show_availability: true # default true: the 30-day figure
    items:
      - { ref: "check:web", label: Website }
      - { ref: "host:nas-1", label: File server }
      - { ref: "backup:tm", label: Backups }
      - { tag: customer-facing, label: Customer-facing apps }

The page is at /status/office and its JSON at /status/office.json. Both are outside /api, need no session when access: public, and use no script.

What a visitor sees, for each item you list: the label you wrote, a state (operational, degraded, down, paused, maintenance or unknown) and, if show_availability is on and Yarrix has enough history, the 30-day availability. Nothing else. The page never shows a host name, address, check id or reason, and an object you did not list does not appear, whatever its state.

  • ref points at one object: host:<id>, check:<id> (an app or custom check by its id, such as check:nas-1:disk, or a heartbeat), backup:<id> or heartbeat:<id>. An id that does not exist reads unknown, never operational.
  • tag is one row for every app and heartbeat carrying that tag. Its state is the worst among them, and its figure the lowest. A paused or in-maintenance member does not make the rest look unwell.
  • A page with access: viewer sends a visitor with no session to the sign-in page. An API token is not a sign-in.
  • show_reasons: true adds the hub's reason line under a problem (connection refused to 10.1.2.3:5432). Reasons can name hosts, addresses, paths and commands, so Yarrix warns when a public page turns this on. Group rows never show one.

The page is plain HTML with a small inline stylesheet, set apart in apps/hub/src/status/style.ts. It refreshes itself every minute and carries noindex.

A badge#

badges:
  - { id: backups, label: backups, tag: nightly }
  - { id: web, items: ["check:web", "host:web-1"] }
  - { id: pinned, tag: nightly, key: "${env:YARRIX_BADGE_KEY}" }   # optional: 16 to 64 letters and digits

Set exactly one of tag and items. The badge says up, 2 down, 1 degraded or unknown, from the worst state among its objects.

The URL holds a key the hub made (22 letters and digits, kept in its database), not the badge id, so nobody can guess one. Find it in the API (an admin):

GET /api/v1/badges
  → [{ "id": "backups", "key": "…", "svg_path": "/badge/<key>.svg", "json_path": "/badge/<key>.json" }]

.svg is the image. .json is the shields.io endpoint format (schemaVersion, label, message, color) plus status.

To revoke a URL, POST /api/v1/badges/<id>/rotate (signed-in admin): the old one stops answering at once. Taking a badge out of config does the same, and adding it back makes a new key. A pinned key is changed in config; a pin that is missing, too short or already used leaves the badge with no URL.

Load and limits#

Everyone who looks at a page or badge shares one reading of the fleet, taken at most every 15 seconds, so a change can take that long to show and a crowd costs the hub no more than one visitor. Each address (a whole IPv6 /64 counts as one) gets 120 page reads and 300 badge reads a minute, and 30 wrong badge keys a minute; past that the answer is 429 with Retry-After: 60. Behind a reverse proxy, set trusted_proxies so the limit is per visitor and not per proxy.

Pages are served with Content-Security-Policy: default-src 'none'; style-src <hash of the page's stylesheet> and no framing.