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.
- 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.
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.
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.
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.
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.