All projects Pulse · Write-up

A status page that only readsEine Statusseite, die nur liest

Pulse checks a hand-picked list of apps once a minute and shows 90 days of uptime for each. Most of the work is deciding what a check result means, and what a public page may show.Pulse prüft eine handverlesene Liste von Apps einmal pro Minute und zeigt für jede 90 Tage Verfügbarkeit. Die meiste Arbeit steckt darin, was ein Prüfergebnis bedeutet und was eine öffentliche Seite zeigen darf.

pulse.ewolution.cloud Source

Type
Web appWeb-App
Stack
React · Express · SSE
Year
2026
Code
Public on GitHubÖffentlich auf GitHub

What a check is

Pulse checks only what’s listed in one file, services.json: a name, a URL, and optionally a health path and the exact status code that counts as up. Every 60 seconds the server sends each of them a plain GET with a 10-second timeout. Any 2xx or 3xx answer passes, unless a specific status was asked for. Nothing is discovered: a status page that finds services by itself also shows things nobody decided to publish.

The file is read again on every round instead of being watched, so adding a service is an edit and nothing else. There’s no restart, and no file watcher to lose track when an editor saves by replacing the file.

Four states from five checks

Up or down isn’t enough. A service that failed a minute ago and answers now isn’t quite fine, and neither is one that takes four seconds to answer. So each service gets one of four states from its most recent checks: down if the latest one failed, degraded if it passed but something in the last five didn’t, or if it was slow, operational otherwise, and unknown before the first check.

Five strips of eight checks, newest on the right, with the last five framed. A failed latest check is down; a failure inside the frame, or a latest check slower than 3 s, is degraded; a failure outside the frame no longer counts. ← olderlast five checks all cleanlatest failedfailed 3 checks agofailed 6 checks agolatest took 3.4 s operationaldowndegradedoperationaldegraded
Fig. 1Only the frame counts. A failed latest check is down; a failure anywhere in the last five, or a latest check slower than 3 s, is degraded. Six checks back, a failure has stopped mattering.

The page’s headline is the worst state of any service. The rules are one small function with its own tests, and CI runs them before every deploy: it’s the one part a status page can’t get wrong.

Sums, not averages

History is kept per day, and each day stores sums: how many checks ran, how many passed, the total response time, and how many checks that total covers. The average is only worked out when the page asks for it. A stored average would go wrong at the first failed check: a check that got no answer has no response time, and counting it as zero would make a bad day look fast.

Ninety days are kept, plus the last 200 individual checks per service for the state rules. It’s a JSON file in a mounted folder; there is no database.

Ninety days that line up

Every service shows its last 90 days as a row of ticks. A service added last week only has seven days of data, and drawn as it is, its bar would be shorter or stretched, with its ticks under different dates than the row above. Instead the page puts every service into the same fixed window of 90 calendar days, empty where there’s no data. A column is the same day in every row, so an outage that hit two services at once shows up as one vertical line.

60 sBetween two rounds of checks. Every service is checked at the same time.
3 sThe response time above which a passing check still makes a service degraded.
90 daysOf daily sums per service, plus its last 200 checks for the state rules.

A public page for a private network

The page is meant to be opened on a phone on mobile data, which is exactly when a home network can’t be reached. So the checks run on the server, and the page never embeds or calls the apps it watches. The browser only talks to Pulse, over one stream of server-sent events.

That also decides what the page may show. The URL a check goes to can be an internal address, so it never leaves the server; a service only gets a link if one is set explicitly. Errors are reduced to a code such as ECONNREFUSED, since the full message can contain a hostname. Installed to a home screen, Pulse opens without a connection too, with the last status it received, labelled with its time and never shown as current.

Harbor’s sibling

Harbor, further down this list, finds every dev server on one machine by itself and can start and stop them. Pulse does neither. It reads, over HTTP, from wherever it runs, and only what it was told about. The two share an idea, a live board of running things, and split the rest on purpose: a public page with a stop button would be the wrong kind of convenience.

Was eine Prüfung ist

Pulse prüft nur, was in einer Datei steht, services.json: ein Name, eine URL und optional ein Health-Pfad und der genaue Statuscode, der als erreichbar gilt. Alle 60 Sekunden schickt der Server jedem Eintrag ein schlichtes GET mit 10 Sekunden Timeout. Jede Antwort mit 2xx oder 3xx besteht, außer ein bestimmter Status ist verlangt. Gesucht wird nichts: Eine Statusseite, die Dienste selbst findet, zeigt auch Dinge, die niemand veröffentlichen wollte.

Die Datei wird in jeder Runde neu gelesen statt überwacht. Einen Dienst hinzuzufügen ist also nur eine Änderung an der Datei: kein Neustart und kein Watcher, der den Faden verliert, wenn ein Editor beim Speichern die Datei ersetzt.

Vier Zustände aus fünf Prüfungen

Erreichbar oder nicht reicht nicht. Ein Dienst, der vor einer Minute ausgefallen ist und jetzt antwortet, ist nicht ganz in Ordnung, und einer, der vier Sekunden für eine Antwort braucht, auch nicht. Deshalb bekommt jeder Dienst aus seinen letzten Prüfungen einen von vier Zuständen: ausgefallen, wenn die letzte fehlschlug, eingeschränkt, wenn sie bestand, aber in den letzten fünf etwas nicht, oder wenn sie langsam war, sonst in Betrieb, und unbekannt vor der ersten Prüfung.

Fünf Reihen aus acht Prüfungen, die neueste rechts, die letzten fünf eingerahmt. Ist die letzte fehlgeschlagen, ist der Dienst ausgefallen; ein Fehler im Rahmen oder eine letzte Prüfung über 3 s macht ihn eingeschränkt; ein Fehler außerhalb des Rahmens zählt nicht mehr. ← älterletzte fünf Prüfungen alles sauberletzte fehlgeschlagenFehler vor 3 PrüfungenFehler vor 6 Prüfungenletzte dauerte 3,4 s in Betriebausgefalleneingeschränktin Betriebeingeschränkt
Abb. 1Nur der Rahmen zählt. Ist die letzte Prüfung fehlgeschlagen, ist der Dienst ausgefallen; ein Fehler irgendwo in den letzten fünf oder eine letzte Prüfung über 3 s macht ihn eingeschränkt. Sechs Prüfungen zurück spielt ein Fehler keine Rolle mehr.

Die Überschrift der Seite ist der schlechteste Zustand aller Dienste. Die Regeln sind eine kleine Funktion mit eigenen Tests, und CI führt sie vor jedem Deploy aus: Es ist der eine Teil, bei dem eine Statusseite nicht falschliegen darf.

Summen statt Durchschnitte

Der Verlauf wird pro Tag gespeichert, und jeder Tag hält Summen: wie viele Prüfungen liefen, wie viele bestanden, die gesamte Antwortzeit und wie viele Prüfungen diese Summe abdeckt. Der Durchschnitt entsteht erst, wenn die Seite ihn abfragt. Ein gespeicherter Durchschnitt ginge bei der ersten fehlgeschlagenen Prüfung schief: Ohne Antwort gibt es keine Antwortzeit, und sie als null zu zählen, ließe einen schlechten Tag schnell aussehen.

Behalten werden 90 Tage, dazu die letzten 200 einzelnen Prüfungen pro Dienst für die Zustandsregeln. Das Ganze ist eine JSON-Datei in einem gemounteten Ordner; eine Datenbank gibt es nicht.

Neunzig Tage, die übereinanderliegen

Jeder Dienst zeigt seine letzten 90 Tage als Reihe von Strichen. Ein Dienst, der letzte Woche dazukam, hat erst sieben Tage Daten. Unverändert gezeichnet wäre sein Balken kürzer oder gestreckt, und seine Striche stünden unter anderen Tagen als die der Zeile darüber. Stattdessen setzt die Seite jeden Dienst in dasselbe feste Fenster aus 90 Kalendertagen, leer, wo Daten fehlen. Eine Spalte ist in jeder Zeile derselbe Tag, und ein Ausfall, der zwei Dienste zugleich traf, erscheint als eine senkrechte Linie.

60 sZwischen zwei Prüfrunden. Alle Dienste werden gleichzeitig geprüft.
3 sDie Antwortzeit, ab der eine bestandene Prüfung einen Dienst trotzdem eingeschränkt macht.
90 TageTagessummen pro Dienst, dazu seine letzten 200 Prüfungen für die Zustandsregeln.

Eine öffentliche Seite für ein privates Netz

Die Seite ist dafür da, am Handy im Mobilfunknetz geöffnet zu werden, also genau dann, wenn das Heimnetz nicht erreichbar ist. Deshalb laufen die Prüfungen auf dem Server, und die Seite bettet die beobachteten Apps nie ein und ruft sie nie auf. Der Browser spricht nur mit Pulse, über einen einzigen Stream von Server-Sent Events.

Das entscheidet auch, was die Seite zeigen darf. Die geprüfte URL kann eine interne Adresse sein, also verlässt sie den Server nie; einen Link bekommt ein Dienst nur, wenn einer ausdrücklich eingetragen ist. Fehler werden auf einen Code wie ECONNREFUSED reduziert, weil die volle Meldung einen Hostnamen enthalten kann. Auf dem Homescreen installiert, öffnet sich Pulse auch ohne Verbindung, mit dem zuletzt empfangenen Status, versehen mit seiner Uhrzeit und nie als aktuell ausgegeben.

Harbors Geschwister

Harbor, weiter unten in dieser Liste, findet jeden Dev-Server auf einem Rechner von selbst und kann ihn starten und stoppen. Pulse tut beides nicht. Es liest, per HTTP, von dort, wo es läuft, und nur, was man ihm genannt hat. Die beiden teilen eine Idee, eine Live-Übersicht laufender Dinge, und trennen den Rest mit Absicht: Eine öffentliche Seite mit Stopp-Knopf wäre die falsche Art Bequemlichkeit.