diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 2799c8f..c19aad9 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -59,6 +59,9 @@ jobs: - name: Check a new service is not shipped without an upgrade path run: python3 scripts/check-service-additions.py + - name: Check the empty state can still explain itself + run: python3 scripts/check-empty-state.py + - name: Check the release version is stated consistently run: python3 scripts/check-release.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f68a40f..8822d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,44 @@ All notable changes to Service Dash are recorded here. This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.2] — 2026-08-30 + +**An empty dashboard now explains itself.** A blank grid used to say almost nothing: a small `OFFLINE` in the top bar, and a toast that cleared itself after 5.2 seconds. Installing for the first time, you could not tell "Uptime Kuma is not set up yet" from "this app is broken" — which is exactly what happened when IceWhale's maintainer reviewed the app for their store, checked that every container was healthy, and reasonably concluded it was broken. + +### Added + +- **An empty state that names which of three things happened, and what to do about it.** + + A blank grid has three quite different causes, and until now the reader could not tell them apart: + + | Cause | Top bar | The panel says | + | --- | --- | --- | + | Uptime Kuma is unreachable | `OFFLINE` | Waiting for Uptime Kuma | + | The status page has no monitors | `CONNECTED` | Connected, but that status page is empty | + | Search and filters hide every card | `CONNECTED` | Nothing matches | + + **The middle one is easy to miss.** `publicGroupList: []` is a *valid* empty array, so a status page with nothing on it is a successful fetch: the top bar reports `CONNECTED` and the grid is still blank. Treating that as the same failure as an unreachable Kuma sends people to check a port that was never the problem. + +- **Setup steps in both empty states, not just a diagnosis.** + + The offline state leads with the thing that misleads people: **Uptime Kuma is a separate application, and Service Dash does not bundle, install or start it.** It then walks through installing it on the host, creating the admin account, publishing a status page, and matching `KUMA_PORT` and `STATUS_SLUG`. The install command is copyable and pins `louislam/uptime-kuma:2`. + + The empty-status-page state walks through Uptime Kuma's editor. Its second step is the real gotcha: **add a group first.** Monitors are nested inside groups in Kuma's API, so a page with no group has nowhere to put them and stays blank. + +- **A diagnostic command, behind a fold**, whose three possible outputs pin the cause exactly: `HTTP/1.1 200 OK` (both fine), `HTTP/1.1 404 Not Found` (Kuma is up, wrong slug), or `can't connect to remote host` (nothing on that port). It runs from any folder with nothing installed but Docker, and names *this* install's port and slug rather than the defaults — which is why `entrypoint.sh` now writes `kumaPort` into `config.js`. + +### Fixed + +- **The panel no longer explains an emptiness that is not there.** With cards on screen, losing Uptime Kuma put "Waiting for Uptime Kuma" underneath a full grid, which reads as though the app had lost them. A dashboard that has been up for a week and briefly loses Kuma keeps its cards and lets the top bar's `OFFLINE` say why they stopped moving. Every branch is now guarded on the grid actually being empty. + +- **Contrast on the light theme.** The offline title takes `--pending`, a dark-ground token that measures **1.36:1** on the light panel — it looked perfectly fine in a screenshot. The light-mode override reaches **9.34:1**. + +### Notes + +Nothing else changed. If your dashboard already shows cards, this release is invisible to you. + +`scripts/check-empty-state.py` guards all of the above and runs in CI. It was mutation-tested seven ways, and its `entrypoint.sh` check passed vacuously on first writing — it looked for the string `kumaPort` anywhere in the file, and the *comment* explaining the emission satisfied it while the emission itself was deleted. + ## [1.5.1] — 2026-08-29 **Fixes how the AI reporters are deployed, and corrects the 1.5.0 upgrade instructions.** The reporters themselves are unchanged; what changed is that they are no longer hidden behind a Compose profile. @@ -685,6 +723,7 @@ Housekeeping for the first public release. No functional changes to the dashboar See the [release history](https://github.com/cvaghela/service-dash/releases). +[1.5.2]: https://github.com/cvaghela/service-dash/releases/tag/v1.5.2 [1.5.1]: https://github.com/cvaghela/service-dash/releases/tag/v1.5.1 [1.5.0]: https://github.com/cvaghela/service-dash/releases/tag/v1.5.0 [1.4.2]: https://github.com/cvaghela/service-dash/releases/tag/v1.4.2 diff --git a/CLAUDE.md b/CLAUDE.md index 7b038cd..f0d1d51 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -323,6 +323,45 @@ original fault, watch the guard fail *by name*, then restore. Two of these passed vacuously on first writing — the nginx one only inspected quoted regexes, so removing the quotes made it find nothing and succeed. +## The empty state + +A blank grid has three quite different causes and, until this existed, said +nothing about which: the only signals were the word **OFFLINE** in small type in +the topbar and a toast that cleared itself after 5.2 seconds. IceWhale's +maintainer hit exactly that while reviewing the app for their store, checked +that every container was healthy, and reasonably concluded Service Dash was +broken. It was not — Uptime Kuma was simply not there to read. + +`renderEmptyState()` names the cause instead: + +- **Kuma unreachable** — the fetch failed. Names the URL it tried, the port and + the slug, and offers a copyable command that prints a different, unmistakable + line for each cause. +- **Connected, page empty** — `publicGroupList: []` is a *valid* array, so this + shows **CONNECTED** with no cards. It is not the same failure as the one + above, and conflating them sends people to check the wrong thing. +- **Nothing matches** — cards exist but the filters hide them all. + +Three things about it are load-bearing: + +- **`#emptyState` is a sibling of `#groups`, not a child.** + `buildDomOnceIfNeeded()` clears `#groups` wholesale and would take the panel + with it, which reads as the empty state randomly not appearing. +- **It is called from three places.** The filter pass covers the healthy paths; + the failure branches of `loadKumaOrMock()` and `pollOnce()` are the ones that + matter, and `applyFiltersAndCounts()` is never reached on those. +- **The diagnostic command interpolates `KUMA_PORT` and `STATUS_SLUG`**, which + is the only reason `entrypoint.sh` puts `kumaPort` into `config.js` at all. A + command naming the wrong port sends the reader to prove something irrelevant. + +`--pending` is a dark-ground token: on the light panel it measures **1.36:1** +and needs the `[data-theme="light"]` override, which reaches 9.34:1. It looked +fine by eye — measure it. + +`scripts/check-empty-state.py` guards all of the above. Its entrypoint check +passed vacuously when first written, because it looked for the string +`kumaPort` anywhere in the file and the explanatory *comment* satisfied it. + ## Conventions - Match the surrounding code: no framework, no build step, plain DOM APIs. diff --git a/README.md b/README.md index 4335fa6..8720327 100644 --- a/README.md +++ b/README.md @@ -656,7 +656,7 @@ Then hard-refresh the browser. ## Updating -The current release is **1.5.1**; the Compose files in this repository reference the matching `1.5.1` images. +The current release is **1.5.2**; the Compose files in this repository reference the matching `1.5.2` images. Most releases are drop-in: diff --git a/appstore/Apps/ServiceDash/docker-compose.yml b/appstore/Apps/ServiceDash/docker-compose.yml index bdfb723..c549070 100644 --- a/appstore/Apps/ServiceDash/docker-compose.yml +++ b/appstore/Apps/ServiceDash/docker-compose.yml @@ -2,7 +2,7 @@ name: service-dash services: service-dash: - image: ghcr.io/cvaghela/service-dash:1.5.1 + image: ghcr.io/cvaghela/service-dash:1.5.2 container_name: service-dash ports: - target: 80 @@ -59,7 +59,7 @@ services: # Answers one question for nginx: is this browser really signed in to Uptime # Kuma? Without it, saving settings would have to trust the browser's word. kuma-auth: - image: ghcr.io/cvaghela/service-dash-kuma-auth:1.5.1 + image: ghcr.io/cvaghela/service-dash-kuma-auth:1.5.2 container_name: service-dash-kuma-auth environment: # Must point at the same Uptime Kuma as KUMA_PORT above. @@ -83,7 +83,7 @@ services: restart: unless-stopped network-info: - image: ghcr.io/cvaghela/service-dash-network-info:1.5.1 + image: ghcr.io/cvaghela/service-dash-network-info:1.5.2 container_name: service-dash-network-info network_mode: host read_only: true @@ -184,7 +184,7 @@ services: # # docker exec -it service-dash-claude-usage claude auth login claude-usage: - image: ghcr.io/cvaghela/service-dash-claude-usage:1.5.1 + image: ghcr.io/cvaghela/service-dash-claude-usage:1.5.2 container_name: service-dash-claude-usage environment: # Plan windows move slowly; polling harder gains nothing. @@ -211,7 +211,7 @@ services: # localhost and hangs. # docker exec -it service-dash-codex-usage codex login --device-auth codex-usage: - image: ghcr.io/cvaghela/service-dash-codex-usage:1.5.1 + image: ghcr.io/cvaghela/service-dash-codex-usage:1.5.2 container_name: service-dash-codex-usage environment: CODEX_USAGE_REFRESH_SECONDS: "300" @@ -243,8 +243,8 @@ x-casaos: architectures: - amd64 - arm64 - version: "1.5.1" - update_at: "2026-08-29" + version: "1.5.2" + update_at: "2026-08-30" author: Chintan Vaghela developer: Chintan Vaghela icon: https://cdn.jsdelivr.net/gh/cvaghela/service-dash@main/appstore/Apps/ServiceDash/icon.png @@ -1379,267 +1379,298 @@ x-casaos: 然后在 **8888** 端口打开 Service Dash。 release_notes: en_US: | - 1.5.1 makes the AI usage panel actually reachable on ZimaOS. 1.5.0 - shipped its two reporters behind a Compose profile, and CasaOS silently - drops any service that declares one -- so the panel could not be turned - on at all, by update or by fresh install. They are now ordinary services - that sit idle until you sign in, so enabling the feature is just: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Idle they cost about 12MB of RAM and no measurable CPU. The trade is - disk: both images carry a vendor CLI and are pulled whether or not you - use them. - - Also in this cycle, from 1.5.0: the AI usage panel itself -- how much of - your Claude and ChatGPT plan is left, as dials above the grid -- plus a - fix for iPhone Safari killing the tab after a few minutes of scrolling, - and dialogs no longer running under the notch. + 1.5.2 makes an empty dashboard explain itself. A blank grid used to say + almost nothing: a small OFFLINE in the top bar, and a toast that cleared + itself after five seconds. Installing for the first time, you could not + tell "Uptime Kuma is not set up yet" from "this app is broken". + + A blank grid now names which of three things happened, and what to do + about it: + + * Uptime Kuma is not answering. It is a separate application, and + Service Dash does not bundle or install it -- so the panel says that + plainly, then walks through installing it, publishing a status page, + and matching KUMA_PORT and STATUS_SLUG. + * It answered, but that status page has no monitors on it. Steps for + the Uptime Kuma editor, including adding a group first, which is + where most empty pages get stuck. + * Your cards are loaded, and the search and filters are hiding them. + + Nothing else changed. If your dashboard already shows cards, this release + is invisible to you. en_GB: | - 1.5.1 makes the AI usage panel actually reachable on ZimaOS. 1.5.0 - shipped its two reporters behind a Compose profile, and CasaOS silently - drops any service that declares one -- so the panel could not be turned - on at all, by update or by fresh install. They are now ordinary services - that sit idle until you sign in, so enabling the feature is just: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Idle they cost about 12MB of RAM and no measurable CPU. The trade is - disk: both images carry a vendor CLI and are pulled whether or not you - use them. - - Also in this cycle, from 1.5.0: the AI usage panel itself -- how much of - your Claude and ChatGPT plan is left, as dials above the grid -- plus a - fix for iPhone Safari killing the tab after a few minutes of scrolling, - and dialogues no longer running under the notch. + 1.5.2 makes an empty dashboard explain itself. A blank grid used to say + almost nothing: a small OFFLINE in the top bar, and a toast that cleared + itself after five seconds. Installing for the first time, you could not + tell "Uptime Kuma is not set up yet" from "this app is broken". + + A blank grid now names which of three things happened, and what to do + about it: + + * Uptime Kuma is not answering. It is a separate application, and + Service Dash does not bundle or install it -- so the panel says that + plainly, then walks through installing it, publishing a status page, + and matching KUMA_PORT and STATUS_SLUG. + * It answered, but that status page has no monitors on it. Steps for + the Uptime Kuma editor, including adding a group first, which is + where most empty pages get stuck. + * Your cards are loaded, and the search and filters are hiding them. + + Nothing else changed. If your dashboard already shows cards, this release + is invisible to you. de_DE: | - 1.5.1 macht das KI-Nutzungspanel auf ZimaOS endlich erreichbar. 1.5.0 - lieferte seine beiden Reporter hinter einem Compose-Profil aus, und CasaOS - verwirft stillschweigend jeden Dienst, der eines deklariert -- das Panel - ließ sich also überhaupt nicht einschalten, weder per Update noch per - Neuinstallation. Sie sind jetzt gewöhnliche Dienste, die untätig bleiben, - bis Sie sich anmelden. Zum Aktivieren genügt: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Im Leerlauf kosten sie rund 12 MB RAM und keine messbare CPU. Der Preis ist - Speicherplatz: Beide Images enthalten ein Hersteller-CLI und werden - geladen, ob Sie sie nutzen oder nicht. - - Ebenfalls in diesem Zyklus, aus 1.5.0: das KI-Nutzungspanel selbst -- wie - viel von Ihrem Claude- und ChatGPT-Kontingent übrig ist, als Anzeigen über - dem Raster -- dazu ein Fix dafür, dass iPhone-Safari den Tab nach ein paar - Minuten Scrollen beendete, und Dialoge laufen nicht mehr unter den Notch. + 1.5.2 lässt ein leeres Dashboard sich selbst erklären. Bisher sagte ein + leeres Raster fast nichts: ein kleines OFFLINE in der oberen Leiste und + eine Meldung, die nach fünf Sekunden verschwand. Bei der ersten + Installation war "Uptime Kuma ist noch nicht eingerichtet" nicht von + "diese App ist kaputt" zu unterscheiden. + + Ein leeres Raster nennt jetzt, welcher der drei Fälle vorliegt, und was + zu tun ist: + + * Uptime Kuma antwortet nicht. Es ist eine eigenständige Anwendung, die + Service Dash weder mitliefert noch installiert -- das sagt das Panel + jetzt deutlich und führt dann durch Installation, Statusseite und die + passenden Werte für KUMA_PORT und STATUS_SLUG. + * Es hat geantwortet, aber auf der Statusseite liegen keine Monitore. + Schritte für den Editor von Uptime Kuma, einschließlich: zuerst eine + Gruppe anlegen -- daran scheitern die meisten leeren Seiten. + * Ihre Karten sind geladen, und Suche und Filter blenden sie aus. + + Sonst hat sich nichts geändert. Zeigt Ihr Dashboard bereits Karten, ist + dieses Update für Sie unsichtbar. el_GR: | - Η έκδοση 1.5.1 κάνει το πάνελ χρήσης AI πράγματι προσβάσιμο στο ZimaOS. Η - 1.5.0 έστελνε τους δύο reporters πίσω από ένα Compose profile, και το - CasaOS απορρίπτει σιωπηλά κάθε υπηρεσία που δηλώνει ένα -- έτσι το πάνελ - δεν μπορούσε να ενεργοποιηθεί καθόλου, ούτε με ενημέρωση ούτε με καθαρή - εγκατάσταση. Τώρα είναι απλές υπηρεσίες που μένουν αδρανείς μέχρι να - συνδεθείτε, οπότε η ενεργοποίηση είναι απλώς: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Αδρανείς κοστίζουν περίπου 12MB RAM και μηδενική μετρήσιμη CPU. Το τίμημα - είναι ο χώρος στον δίσκο: και τα δύο images περιέχουν ένα CLI του - κατασκευαστή και κατεβαίνουν είτε τα χρησιμοποιείτε είτε όχι. - - Επίσης σε αυτόν τον κύκλο, από την 1.5.0: το ίδιο το πάνελ χρήσης AI -- - πόσο απομένει από το πλάνο σας σε Claude και ChatGPT, ως δείκτες πάνω από - το πλέγμα -- μαζί με μια διόρθωση για τον Safari του iPhone που τερμάτιζε - την καρτέλα μετά από λίγα λεπτά κύλισης, και διαλόγους που δεν περνούν - πλέον κάτω από το notch. + Η έκδοση 1.5.2 κάνει έναν άδειο πίνακα να εξηγεί τον εαυτό του. Μέχρι + τώρα ένα άδειο πλέγμα δεν έλεγε σχεδόν τίποτα: ένα μικρό OFFLINE στην + επάνω μπάρα και ένα μήνυμα που έσβηνε μετά από πέντε δευτερόλεπτα. Στην + πρώτη εγκατάσταση, το "το Uptime Kuma δεν έχει ρυθμιστεί ακόμη" δεν + ξεχώριζε από το "η εφαρμογή χάλασε". + + Τώρα το άδειο πλέγμα ονομάζει ποιο από τα τρία συνέβη, και τι να κάνετε: + + * Το Uptime Kuma δεν απαντά. Είναι ξεχωριστή εφαρμογή, την οποία το + Service Dash δεν περιλαμβάνει ούτε εγκαθιστά -- ο πίνακας το λέει + πλέον καθαρά και μετά σας οδηγεί στην εγκατάσταση, στη δημοσίευση + σελίδας κατάστασης και στην αντιστοίχιση KUMA_PORT και STATUS_SLUG. + * Απάντησε, αλλά η σελίδα κατάστασης δεν έχει monitors. Βήματα για τον + επεξεργαστή του Uptime Kuma, μαζί με το "πρώτα προσθέστε ομάδα", εκεί + όπου κολλάνε οι περισσότερες άδειες σελίδες. + * Οι κάρτες σας έχουν φορτώσει και τις κρύβουν η αναζήτηση και τα + φίλτρα. + + Τίποτε άλλο δεν άλλαξε. Αν ο πίνακάς σας δείχνει ήδη κάρτες, αυτή η + έκδοση είναι αόρατη για εσάς. fr_FR: | - La version 1.5.1 rend enfin le panneau d'utilisation IA accessible sur - ZimaOS. La 1.5.0 livrait ses deux rapporteurs derrière un profil Compose, - et CasaOS écarte silencieusement tout service qui en déclare un -- le - panneau ne pouvait donc pas être activé du tout, ni par mise à jour ni par - installation neuve. Ce sont désormais des services ordinaires qui restent - inactifs jusqu'à votre connexion ; activer la fonction se résume à : - - sudo docker exec -it service-dash-claude-usage claude auth login - - Au repos, ils coûtent environ 12 Mo de RAM et aucun CPU mesurable. La - contrepartie est le disque : les deux images embarquent un CLI éditeur et - sont téléchargées que vous vous en serviez ou non. - - Également dans ce cycle, depuis la 1.5.0 : le panneau d'utilisation IA - lui-même -- ce qu'il reste de vos forfaits Claude et ChatGPT, sous forme de - cadrans au-dessus de la grille -- ainsi qu'un correctif pour Safari sur - iPhone qui tuait l'onglet après quelques minutes de défilement, et des - boîtes de dialogue qui ne passent plus sous l'encoche. + La version 1.5.2 fait en sorte qu'un tableau de bord vide s'explique + lui-même. Jusqu'ici une grille vide ne disait presque rien : un petit + OFFLINE dans la barre du haut et une notification qui disparaissait au + bout de cinq secondes. À la première installation, impossible de + distinguer « Uptime Kuma n'est pas encore configuré » de « cette + application est cassée ». + + Une grille vide indique désormais lequel des trois cas s'applique, et + quoi faire : + + * Uptime Kuma ne répond pas. C'est une application distincte, que + Service Dash n'embarque ni n'installe -- le panneau le dit clairement, + puis détaille l'installation, la publication d'une page de statut et + la correspondance de KUMA_PORT et STATUS_SLUG. + * Il a répondu, mais cette page de statut ne contient aucun moniteur. + Les étapes dans l'éditeur d'Uptime Kuma, dont « créer d'abord un + groupe », là où bloquent la plupart des pages vides. + * Vos cartes sont chargées, et la recherche et les filtres les masquent. + + Rien d'autre n'a changé. Si votre tableau de bord affiche déjà des cartes, + cette version vous est invisible. hr_HR: | - Verzija 1.5.1 čini ploču s AI potrošnjom doista dostupnom na ZimaOS-u. - 1.5.0 je isporučila svoja dva reportera iza Compose profila, a CasaOS - tiho odbacuje svaku uslugu koja ga deklarira -- pa se ploča uopće nije - mogla uključiti, ni nadogradnjom ni čistom instalacijom. Sada su to obične - usluge koje miruju dok se ne prijavite, pa je uključivanje samo: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Dok miruju troše oko 12MB RAM-a i nemjerljivo malo procesora. Cijena je - prostor na disku: obje slike nose proizvođačev CLI i preuzimaju se bez - obzira koristite li ih. - - Također u ovom ciklusu, iz 1.5.0: sama ploča s AI potrošnjom -- koliko vam - je ostalo od Claude i ChatGPT plana, kao brojčanici iznad mreže -- uz - ispravak za Safari na iPhoneu koji je nakon nekoliko minuta pomicanja - gasio karticu, te dijaloge koji više ne završavaju ispod ureza. + Verzija 1.5.2 čini da se prazna nadzorna ploča sama objasni. Dosad prazna + mreža nije govorila gotovo ništa: malen natpis OFFLINE u gornjoj traci i + poruka koja bi nestala nakon pet sekundi. Pri prvoj instalaciji niste + mogli razlikovati "Uptime Kuma još nije postavljen" od "aplikacija je + pokvarena". + + Prazna mreža sada imenuje koje se od tri stvari dogodilo i što učiniti: + + * Uptime Kuma ne odgovara. To je zasebna aplikacija koju Service Dash ne + isporučuje niti instalira -- ploča to sada jasno kaže, a zatim vodi + kroz instalaciju, objavu stranice stanja i usklađivanje KUMA_PORT i + STATUS_SLUG. + * Odgovorio je, ali na toj stranici stanja nema monitora. Koraci za + uređivač Uptime Kume, uključujući "prvo dodajte grupu", na čemu zapne + većina praznih stranica. + * Vaše su kartice učitane, a pretraga i filtri ih skrivaju. + + Ništa se drugo nije promijenilo. Ako vaša ploča već prikazuje kartice, ovo + je izdanje za vas nevidljivo. it_IT: | - La 1.5.1 rende il pannello di utilizzo AI davvero raggiungibile su ZimaOS. - La 1.5.0 spediva i suoi due reporter dietro un profilo Compose, e CasaOS - scarta silenziosamente qualsiasi servizio che ne dichiari uno -- quindi il - pannello non si poteva attivare affatto, né per aggiornamento né per - installazione pulita. Ora sono servizi ordinari che restano inattivi finché - non accedete, perciò abilitare la funzione è solo: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Da inattivi costano circa 12MB di RAM e nessuna CPU misurabile. Il prezzo è - lo spazio su disco: entrambe le immagini portano una CLI del fornitore e - vengono scaricate che le usiate o no. - - Sempre in questo ciclo, dalla 1.5.0: il pannello di utilizzo AI stesso -- - quanto resta dei vostri piani Claude e ChatGPT, come quadranti sopra la - griglia -- più una correzione per Safari su iPhone che chiudeva la scheda - dopo qualche minuto di scorrimento, e le finestre di dialogo che non - finiscono più sotto il notch. + La 1.5.2 fa in modo che una dashboard vuota si spieghi da sola. Finora una + griglia vuota non diceva quasi nulla: un piccolo OFFLINE nella barra in + alto e un avviso che spariva dopo cinque secondi. Alla prima + installazione, "Uptime Kuma non è ancora configurato" era + indistinguibile da "questa app è rotta". + + Ora una griglia vuota dice quale dei tre casi è il vostro, e cosa fare: + + * Uptime Kuma non risponde. È un'applicazione separata, che Service Dash + non include né installa -- il pannello lo dice chiaramente e poi guida + all'installazione, alla pubblicazione di una pagina di stato e + all'allineamento di KUMA_PORT e STATUS_SLUG. + * Ha risposto, ma quella pagina di stato non ha monitor. I passaggi + nell'editor di Uptime Kuma, incluso "prima aggiungete un gruppo", dove + si bloccano quasi tutte le pagine vuote. + * Le vostre schede sono caricate, e ricerca e filtri le stanno + nascondendo. + + Nient'altro è cambiato. Se la vostra dashboard mostra già le schede, + questa versione è invisibile. ja_JP: | - 1.5.1 では、AI 使用状況パネルに ZimaOS からきちんとたどり着けるようになり - ました。1.5.0 では 2 つのレポーターを Compose のプロファイルの後ろに置いて - いましたが、CasaOS はプロファイルを宣言したサービスを黙って取り除きます。 - そのため、更新でも新規インストールでもパネルをまったく有効化できませんでした。 - 現在は通常のサービスとして常駐し、サインインするまで待機します。有効化は - 次の 1 行だけです。 - - sudo docker exec -it service-dash-claude-usage claude auth login - - 待機中の消費はメモリー約 12MB、CPU は計測できない程度です。代償はディスクで、 - どちらのイメージもベンダーの CLI を含み、使うかどうかに関わらず取得されます。 - - 同じ流れで 1.5.0 から入ったもの: AI 使用状況パネル本体 (Claude と ChatGPT の - プラン残量をグリッド上部のダイヤルで表示)、数分スクロールすると iPhone の - Safari がタブを終了してしまう問題の修正、そしてダイアログがノッチの下に - 入り込まなくなった点です。 + 1.5.2 では、空のダッシュボードが自分で理由を説明するようになりました。これまで + 空のグリッドが伝えていたのは、上部バーの小さな OFFLINE と、5 秒で消えてしまう + 通知だけでした。初めて導入した人には、「Uptime Kuma をまだ設定していない」のか + 「このアプリが壊れている」のか見分けがつきませんでした。 + + 空のグリッドは、次の 3 つのどれに当たるかと、その対処を示します。 + + * Uptime Kuma が応答していない。これは別のアプリケーションで、Service Dash + は同梱もインストールもしません。パネルはそれをはっきり伝えたうえで、 + インストール、ステータスページの公開、KUMA_PORT と STATUS_SLUG の + 合わせ方まで案内します。 + * 応答はあったが、そのステータスページに監視項目がない。Uptime Kuma の + エディターでの手順を示します。最初にグループを追加すること、が + 空のページで最もつまずく点です。 + * カードは読み込まれていて、検索とフィルターが隠している。 + + ほかに変更はありません。すでにカードが表示されているなら、この更新は + 目に見えないはずです。 ko_KR: | - 1.5.1은 AI 사용량 패널에 ZimaOS에서 실제로 도달할 수 있게 만듭니다. 1.5.0은 - 두 리포터를 Compose 프로필 뒤에 두고 배포했는데, CasaOS는 프로필을 선언한 - 서비스를 조용히 버립니다. 그래서 업데이트로든 새로 설치하든 패널을 아예 켤 수 - 없었습니다. 이제는 로그인할 때까지 대기하는 평범한 서비스이므로, 기능을 - 켜는 일은 다음 한 줄이 전부입니다. - - sudo docker exec -it service-dash-claude-usage claude auth login - - 대기 중에는 RAM 약 12MB를 쓰고 CPU는 측정되지 않는 수준입니다. 대신 치르는 - 비용은 디스크입니다. 두 이미지 모두 공급사 CLI를 담고 있어 쓰든 안 쓰든 - 내려받습니다. - - 같은 흐름으로 1.5.0에서 들어온 것들: AI 사용량 패널 자체(Claude와 ChatGPT - 요금제가 얼마나 남았는지 그리드 위 다이얼로 표시), 몇 분 스크롤하면 iPhone - Safari가 탭을 종료해 버리던 문제의 수정, 그리고 대화 상자가 더 이상 노치 - 아래로 들어가지 않는 점입니다. + 1.5.2는 비어 있는 대시보드가 스스로 이유를 설명하게 합니다. 지금까지 빈 그리드가 + 알려 주는 것은 상단 바의 작은 OFFLINE과 5초 만에 사라지는 알림뿐이었습니다. 처음 + 설치한 사람은 "Uptime Kuma를 아직 설정하지 않았다"와 "이 앱이 고장 났다"를 구분할 + 수 없었습니다. + + 이제 빈 그리드는 셋 중 어떤 상황인지와 무엇을 해야 하는지 알려 줍니다. + + * Uptime Kuma가 응답하지 않음. 이것은 별도의 애플리케이션이며 Service Dash가 + 함께 담거나 설치해 주지 않습니다. 패널이 그 점을 분명히 밝힌 뒤 설치, 상태 + 페이지 게시, KUMA_PORT와 STATUS_SLUG 맞추기까지 안내합니다. + * 응답은 왔지만 그 상태 페이지에 모니터가 없음. Uptime Kuma 편집기에서의 + 단계를 안내하며, 그중 "그룹을 먼저 추가"가 빈 페이지가 가장 많이 막히는 + 지점입니다. + * 카드는 불러왔고, 검색과 필터가 가리고 있음. + + 그 밖에 달라진 것은 없습니다. 이미 카드가 보이고 있다면 이번 업데이트는 눈에 + 띄지 않을 것입니다. nb_NO: | - 1.5.1 gjør KI-brukspanelet faktisk tilgjengelig på ZimaOS. 1.5.0 leverte - de to rapportørene bak en Compose-profil, og CasaOS forkaster stille enhver - tjeneste som erklærer en -- panelet kunne altså ikke slås på i det hele - tatt, verken ved oppdatering eller ved ny installasjon. Nå er de vanlige - tjenester som står i ro til du logger inn, så det å slå på funksjonen er - bare: - - sudo docker exec -it service-dash-claude-usage claude auth login - - I ro koster de rundt 12 MB RAM og ingen målbar CPU. Prisen er diskplass: - begge bildene bærer en leverandør-CLI og lastes ned enten du bruker dem - eller ikke. - - Også i denne runden, fra 1.5.0: selve KI-brukspanelet -- hvor mye som er - igjen av Claude- og ChatGPT-abonnementene dine, som målere over rutenettet - -- pluss en rettelse for at Safari på iPhone drepte fanen etter noen - minutters rulling, og dialoger som ikke lenger havner under utsparingen. + 1.5.2 får et tomt dashbord til å forklare seg selv. Til nå sa et tomt + rutenett nesten ingenting: et lite OFFLINE i topplinjen og en melding som + forsvant etter fem sekunder. Ved første installasjon kunne du ikke skille + "Uptime Kuma er ikke satt opp ennå" fra "denne appen er ødelagt". + + Et tomt rutenett sier nå hvilken av tre ting som skjedde, og hva du gjør: + + * Uptime Kuma svarer ikke. Det er en egen applikasjon, og Service Dash + hverken leverer eller installerer den -- panelet sier det rett ut, og + går deretter gjennom installasjon, publisering av en statusside og + hvordan KUMA_PORT og STATUS_SLUG skal stemme. + * Det svarte, men statussiden har ingen overvåkere. Stegene i Uptime + Kumas redigering, inkludert "legg til en gruppe først", der de fleste + tomme sider står fast. + * Kortene dine er lastet, og søket og filtrene skjuler dem. + + Ingenting annet er endret. Viser dashbordet ditt allerede kort, er denne + utgivelsen usynlig for deg. pt_PT: | - A 1.5.1 torna o painel de utilização de IA realmente acessível no ZimaOS. A - 1.5.0 entregava os seus dois repórteres atrás de um perfil do Compose, e o - CasaOS descarta silenciosamente qualquer serviço que declare um -- por isso - o painel não podia ser ativado de todo, nem por atualização nem por - instalação de raiz. Agora são serviços normais que ficam inativos até - iniciar sessão, pelo que ativar a funcionalidade é apenas: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Inativos custam cerca de 12MB de RAM e nenhum CPU mensurável. A troca é o - disco: ambas as imagens transportam uma CLI do fornecedor e são - descarregadas quer as use quer não. - - Também neste ciclo, vindo da 1.5.0: o próprio painel de utilização de IA -- - quanto resta dos seus planos Claude e ChatGPT, em mostradores por cima da - grelha -- mais uma correção para o Safari do iPhone que fechava o separador - ao fim de alguns minutos a deslizar, e caixas de diálogo que já não passam - por baixo do entalhe. + A 1.5.2 faz com que um painel vazio se explique a si próprio. Até agora + uma grelha vazia não dizia quase nada: um pequeno OFFLINE na barra + superior e um aviso que desaparecia ao fim de cinco segundos. Numa + primeira instalação, não era possível distinguir "o Uptime Kuma ainda não + está configurado" de "esta aplicação está avariada". + + Uma grelha vazia indica agora qual dos três casos se aplica, e o que + fazer: + + * O Uptime Kuma não responde. É uma aplicação separada, que o Service + Dash não inclui nem instala -- o painel di-lo claramente e depois + orienta a instalação, a publicação de uma página de estado e a + correspondência de KUMA_PORT e STATUS_SLUG. + * Respondeu, mas essa página de estado não tem monitores. Os passos no + editor do Uptime Kuma, incluindo "adicione primeiro um grupo", onde + encalham quase todas as páginas vazias. + * Os seus cartões estão carregados e a pesquisa e os filtros escondem-nos. + + Mais nada mudou. Se o seu painel já mostra cartões, esta versão é + invisível para si. ru_RU: | - Версия 1.5.1 делает панель расхода ИИ действительно доступной в ZimaOS. В - 1.5.0 два её репортёра поставлялись за Compose-профилем, а CasaOS молча - отбрасывает любой сервис, который объявляет профиль, — поэтому включить - панель было нельзя вовсе, ни обновлением, ни чистой установкой. Теперь это - обычные сервисы, которые простаивают до вашего входа, так что включение - сводится к одной строке: - - sudo docker exec -it service-dash-claude-usage claude auth login - - В простое они занимают около 12 МБ ОЗУ и не дают измеримой нагрузки на - процессор. Плата — место на диске: оба образа несут в себе CLI поставщика и - загружаются независимо от того, пользуетесь вы ими или нет. - - Также в этом цикле, из 1.5.0: сама панель расхода ИИ — сколько осталось от - тарифов Claude и ChatGPT, в виде шкал над сеткой, — плюс исправление того, - что Safari на iPhone закрывал вкладку после нескольких минут прокрутки, и - диалоги больше не уходят под вырез экрана. + Версия 1.5.2 учит пустую панель объяснять саму себя. Раньше пустая сетка + сообщала почти ничего: маленькое OFFLINE в верхней строке и уведомление, + исчезавшее через пять секунд. При первой установке нельзя было отличить + «Uptime Kuma ещё не настроен» от «приложение сломано». + + Теперь пустая сетка называет, что именно из трёх произошло, и что делать: + + * Uptime Kuma не отвечает. Это отдельное приложение, которое Service + Dash не поставляет и не устанавливает, — панель прямо об этом + говорит, а затем проводит через установку, публикацию страницы + состояния и согласование KUMA_PORT и STATUS_SLUG. + * Ответ получен, но на этой странице состояния нет мониторов. Шаги в + редакторе Uptime Kuma, в том числе «сначала добавьте группу» — именно + здесь застревает большинство пустых страниц. + * Карточки загружены, а поиск и фильтры их скрывают. + + Больше ничего не изменилось. Если ваша панель уже показывает карточки, + этот выпуск для вас незаметен. sv_SE: | - 1.5.1 gör AI-användningspanelen faktiskt nåbar på ZimaOS. 1.5.0 levererade - sina två rapportörer bakom en Compose-profil, och CasaOS förkastar tyst - varje tjänst som deklarerar en -- panelen gick alltså inte att slå på alls, - varken via uppdatering eller ny installation. Nu är de vanliga tjänster som - ligger overksamma tills du loggar in, så att aktivera funktionen är bara: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Overksamma kostar de omkring 12 MB RAM och ingen mätbar processortid. - Priset är diskutrymme: båda avbildningarna bär med sig en leverantörs-CLI - och hämtas vare sig du använder dem eller inte. - - Även i den här omgången, från 1.5.0: själva AI-användningspanelen -- hur - mycket som är kvar av dina Claude- och ChatGPT-abonnemang, som mätare ovanför - rutnätet -- plus en rättning för att Safari på iPhone dödade fliken efter - några minuters rullning, och dialoger som inte längre hamnar under urklippet. + 1.5.2 får en tom instrumentpanel att förklara sig själv. Hittills sa ett + tomt rutnät nästan ingenting: ett litet OFFLINE i topplisten och en + avisering som försvann efter fem sekunder. Vid en första installation gick + det inte att skilja "Uptime Kuma är inte uppsatt än" från "den här appen + är trasig". + + Ett tomt rutnät säger nu vilket av tre fall det rör sig om, och vad du gör: + + * Uptime Kuma svarar inte. Det är en separat applikation som Service + Dash varken levererar eller installerar -- panelen säger det rakt ut + och går sedan igenom installation, publicering av en statussida och + hur KUMA_PORT och STATUS_SLUG ska stämma. + * Det svarade, men statussidan har inga övervakare. Stegen i Uptime + Kumas redigerare, inklusive "lägg till en grupp först", där de flesta + tomma sidor kör fast. + * Dina kort är laddade, och sökningen och filtren döljer dem. + + Inget annat har ändrats. Visar din panel redan kort är den här versionen + osynlig för dig. tr_TR: | - 1.5.1, yapay zekâ kullanım panosuna ZimaOS üzerinde gerçekten - erişilebilmesini sağlıyor. 1.5.0, iki raporlayıcısını bir Compose profilinin - arkasında dağıtıyordu; CasaOS ise profil tanımlayan her servisi sessizce - düşürüyor. Dolayısıyla pano ne güncellemeyle ne de temiz kurulumla hiçbir - şekilde açılamıyordu. Artık siz giriş yapana dek boşta bekleyen sıradan - servisler; özelliği açmak yalnızca şu satırdan ibaret: - - sudo docker exec -it service-dash-claude-usage claude auth login - - Boştayken yaklaşık 12MB bellek harcarlar ve ölçülebilir bir işlemci yükleri - yoktur. Bedeli disk: her iki imaj da üreticinin komut satırı aracını taşır - ve siz kullanın ya da kullanmayın indirilir. - - Yine bu turda, 1.5.0'dan gelenler: yapay zekâ kullanım panosunun kendisi -- - Claude ve ChatGPT planlarınızdan ne kaldığı, ızgaranın üzerinde kadranlar - hâlinde -- ayrıca iPhone Safari'nin birkaç dakika kaydırdıktan sonra sekmeyi - kapatmasına yönelik bir düzeltme ve artık çentiğin altında kalmayan iletişim - kutuları. + 1.5.2, boş bir panonun kendini açıklamasını sağlıyor. Şimdiye kadar boş + bir ızgara neredeyse hiçbir şey söylemiyordu: üst çubukta küçük bir + OFFLINE ve beş saniyede kaybolan bir bildirim. İlk kurulumda "Uptime Kuma + henüz kurulmadı" ile "bu uygulama bozuk" birbirinden ayırt edilemiyordu. + + Boş ızgara artık üç durumdan hangisinin geçerli olduğunu ve ne yapılacağını + söylüyor: + + * Uptime Kuma yanıt vermiyor. Ayrı bir uygulamadır ve Service Dash onu ne + içerir ne de kurar -- pano bunu açıkça söylüyor, ardından kurulumu, + bir durum sayfası yayımlamayı ve KUMA_PORT ile STATUS_SLUG değerlerini + eşleştirmeyi anlatıyor. + * Yanıt verdi, ama o durum sayfasında hiç izleyici yok. Uptime Kuma + düzenleyicisindeki adımlar, "önce bir grup ekleyin" dahil; boş + sayfaların çoğu tam burada takılıyor. + * Kartlarınız yüklendi, arama ve filtreler onları gizliyor. + + Başka bir şey değişmedi. Panonuz zaten kart gösteriyorsa bu sürüm sizin + için görünmezdir. zh_CN: | - 1.5.1 让 AI 用量面板在 ZimaOS 上真正可用。1.5.0 把两个上报服务放在了 Compose - profile 后面,而 CasaOS 会静默丢弃任何声明了 profile 的服务——于是无论是更新 - 还是全新安装,这个面板都根本没法开启。现在它们是普通服务,在你登录之前一直 - 空转,所以启用这项功能只需要一条命令: + 1.5.2 让空白的仪表盘自己说明原因。过去空白的网格几乎什么都不说:顶栏里一个很小的 + OFFLINE,加上一条五秒后就消失的提示。第一次安装的人根本分不清是「Uptime Kuma + 还没配置好」还是「这个应用坏了」。 - sudo docker exec -it service-dash-claude-usage claude auth login + 现在空白的网格会指出是以下三种情况中的哪一种,以及该怎么做: - 空转时它们大约占用 12MB 内存,CPU 占用低到测不出来。代价是磁盘:两个镜像都 - 自带厂商的命令行工具,无论你用不用都会被拉取。 + * Uptime Kuma 没有响应。它是一个独立的应用,Service Dash 既不打包也不安装 + 它——面板会把这一点讲清楚,然后一步步说明如何安装、如何发布状态页,以及如何 + 让 KUMA_PORT 和 STATUS_SLUG 对得上。 + * 有响应,但那个状态页上没有监控项。给出 Uptime Kuma 编辑器里的步骤,其中 + 「先添加一个分组」正是大多数空状态页卡住的地方。 + * 卡片已经加载,是搜索和筛选把它们藏起来了。 - 同一轮里还有来自 1.5.0 的内容:AI 用量面板本身——把 Claude 和 ChatGPT 套餐 - 还剩多少显示为网格上方的刻度盘——以及修复了 iPhone Safari 滚动几分钟后会杀掉 - 标签页的问题,还有对话框不再跑到刘海底下。 + 其他没有变化。如果你的仪表盘已经能看到卡片,这次更新对你来说是无感的。 diff --git a/assets/css/styles.css b/assets/css/styles.css index 4b2a940..c93666d 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -4523,3 +4523,178 @@ body.mobile-topbar-open .mobile-menu-icon span:nth-child(3) { .aiSetupSignOut[hidden] { display: none; } + +/* ========================= + EMPTY STATE + ========================= + A blank grid is the first screen of every new install, and it used to explain + nothing: the only signals were the word OFFLINE in small type in the topbar + and a toast that cleared itself after 5.2 seconds. Three quite different + causes produce the same blank grid, so this panel names which one it is. */ +.emptyState { + margin-top: 14px; + padding: 20px; + border: 1px solid var(--stroke); + border-radius: var(--radius-lg); + background: var(--panel-2); + /* No backdrop-filter, deliberately -- see the note by .topbar. This is a + large surface, and blurring one that sits on a smooth gradient returns + the same smooth gradient for the cost of a full-size GPU layer. */ +} +/* The panel is only ever shown by clearing `hidden`, and .emptyInner supplies + the layout, so the attribute is enough on its own. Stated anyway: a display + value on .emptyState would silently beat it, which is a bug this codebase has + already shipped once. */ +.emptyState[hidden] { + display: none; +} +.emptyInner { + display: grid; + gap: 12px; + justify-items: start; + /* Running text stays near 65 characters however wide the grid gets. */ + max-width: 64ch; +} +.emptyTitle { + margin: 0; + font-family: var(--font-head); + font-size: 17px; + font-weight: 700; + color: var(--text); +} +.emptyLead, +.emptyChecks { + margin: 0; + color: var(--muted); + line-height: 1.6; +} +.emptyChecks, +.emptySteps, +.emptyOutcomes { + display: grid; + gap: 6px; + padding-left: 20px; +} +/* The steps carry a command block inside step 1, so they need room to breathe + in a way a plain list does not. */ +.emptySteps { + gap: 10px; + color: var(--muted); + line-height: 1.6; + margin: 0; +} +.emptySteps b { + color: var(--text); +} +.emptySteps .emptyCmd { + margin-top: 8px; +} +/* States the thing the reader has got wrong before the instructions that + depend on it, so it is not read as another step. */ +.emptyNote { + margin: 0; + justify-self: stretch; + padding: 10px 12px; + border-radius: 12px; + border: 1px solid var(--stroke); + background: rgb(255, 255, 255, 0.05); + color: var(--muted); + line-height: 1.55; +} +.emptyNote b { + color: var(--text); +} +.emptyOutcomes { + margin: 10px 0 0; + list-style: none; + padding-left: 0; + color: var(--muted); + font-size: 12px; + line-height: 1.5; +} +.emptyState code { + font-size: 12px; + color: var(--text); + overflow-wrap: anywhere; +} +/* Mirrors .aiSetupCmd: the hint sits ON the command, because "where do I run + this" and "do I need to install anything" are the two questions people stall + on, and a footnote elsewhere does not reach them. */ +.emptyCmd { + display: grid; + gap: 6px 8px; + justify-items: end; + justify-self: stretch; + min-width: 0; + padding: 10px 12px; + border: 1px solid var(--stroke); + border-radius: 12px; + background: rgb(0, 0, 0, 0.22); +} +.emptyCmdHint { + justify-self: stretch; + font-family: var(--font-head); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--muted); +} +.emptyCmd code { + justify-self: stretch; + min-width: 0; + /* Wraps rather than scrolls, for the same reason as the AI setup commands: + a sideways-scrolling bar hides both the end of the command and the Copy + button, and the copied text is one line either way. */ + white-space: pre-wrap; + line-height: 1.55; +} +.emptyCopy, +.emptyClear { + flex: 0 0 auto; + padding: 5px 13px; + font-size: 12px; +} +.emptyDetail { + justify-self: stretch; + color: var(--muted); + font-size: 12px; +} +.emptyDetail summary { + cursor: pointer; + font-family: var(--font-head); + font-weight: 600; +} +.emptyDetail code { + display: block; + margin-top: 8px; + white-space: pre-wrap; + line-height: 1.5; +} +/* Same measurement as .aiSetupCmd: the dark fill is right on a dark ground and + merely legible on a light one, where a lighter fill matches the panel instead + of punching a hole in it. */ +[data-theme="light"] .emptyCmd { + background: rgb(0, 0, 0, 0.06); +} +/* A 5% white fill is invisible on the light panel, which is itself 50% white. + The callout has to read as a distinct surface or it is just an indented + paragraph. */ +[data-theme="light"] .emptyNote { + background: rgb(0, 0, 0, 0.04); +} +[data-theme="light"] .emptyCmdHint { + color: rgb(18, 18, 30, 0.72); +} +/* Ties the panel to the word it is explaining: the topbar says OFFLINE in + --pending, and this is the panel that says why. The other two causes are not + faults and stay in body colour. */ +.emptyState[data-kind="offline"] .emptyTitle { + color: var(--pending); +} +/* --pending is a dark-ground token: measured on this panel in light mode it is + 1.36:1, which is not a heading so much as a rumour of one. The same dark + amber the sign-out control uses reaches 8.9:1 here. */ +[data-theme="light"] .emptyState[data-kind="offline"] .emptyTitle { + color: #5c3d00; +} diff --git a/assets/js/app.js b/assets/js/app.js index 75c1b55..0fa29bd 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -41,6 +41,9 @@ const APP_VERSION = (() => { const RUNTIME_CONFIG = window.__DASHBOARD_CONFIG__ || {}; const KUMA_BASE = "/kuma"; const STATUS_SLUG = String(RUNTIME_CONFIG.statusSlug || "homelab"); +// Only ever used to build the empty state's diagnostic command. A command +// naming the wrong port is worse than offering no command at all. +const KUMA_PORT = String(RUNTIME_CONFIG.kumaPort || "3001"); const STORAGE_MOUNT = String(RUNTIME_CONFIG.storageMount || "auto").trim(); const ICON_STORAGE_KEY = "serviceIcons"; let BROWSER_ICON_OVERRIDES = loadBrowserIconOverrides(); @@ -253,6 +256,8 @@ const state = { pageData: null, heartbeat: null, + // How many cards survive the current filters. Drives the empty state. + visibleCount: 0, kumaConnected: false, lastSync: null, @@ -296,6 +301,7 @@ const els = { btnTheme: document.getElementById("btnTheme"), btnLinkMode: document.getElementById("btnLinkMode"), linkModeToggle: document.getElementById("linkModeToggle"), + emptyState: document.getElementById("emptyState"), secAiUsage: document.getElementById("secAiUsage"), secAiDetail: document.getElementById("secAiDetail"), aiRows: document.getElementById("aiRows"), @@ -4901,21 +4907,248 @@ function updateStatusesInPlace() { } } +/* ========================= + EMPTY STATE + ========================= */ +// A blank grid is the screen every new install starts on, and until this existed +// it explained nothing. The only signals were the word OFFLINE in small type in +// the topbar and a toast that cleared itself after 5.2 seconds. IceWhale's +// maintainer hit exactly that while reviewing the app for their store, checked +// that every container was healthy, and reasonably concluded Service Dash was +// broken. It was not: Uptime Kuma simply was not there to read. An empty grid +// has three quite different causes and the reader cannot tell them apart, so +// the panel names which one it is and what to do about it. + +// Runs from any folder with nothing installed on the host but Docker, and +// prints a different, unmistakable first line for each cause: "HTTP/1.1 200 OK" +// (both fine), "HTTP/1.1 404 Not Found" (Kuma up, wrong slug), or "can't connect +// to remote host" (nothing on that port). `service-dash` is a fixed +// container_name in all three Compose files, so this needs neither a Compose +// file nor a particular working directory. The sudo is not decoration -- on +// ZimaOS the user is not in the `docker` group by default. +// +// Both values are interpolated into a single-quoted shell string, and both are +// validated by entrypoint.sh before they reach the browser (STATUS_SLUG is +// [A-Za-z0-9_-] only, KUMA_PORT is 1-65535), so neither can close the quote. +function kumaDiagnosticCommand() { + return `sudo docker exec service-dash sh -c 'wget -S -O/dev/null --timeout=5 "http://host.docker.internal:${KUMA_PORT}/api/status-page/${STATUS_SLUG}" 2>&1 | head -3'`; +} + +// The floating major tag, so an install started from here does not pin itself +// to whatever was current the day this shipped. Verified to exist rather than +// assumed -- `louislam/uptime-kuma:1` is the tag most guides still quote, and +// following it would put a new install two majors behind. +function kumaInstallCommand() { + return `docker run -d --restart=always --name uptime-kuma -p ${KUMA_PORT}:3001 -v uptime-kuma:/app/data louislam/uptime-kuma:2`; +} + +// The stored error can be nginx's own 502 page: five hundred characters of +// markup wrapped around four useful words. Strip the tags, collapse the +// whitespace and cap it -- the panel summarises, it is not a log viewer. +function tidyKumaError(raw) { + const text = safeStr(raw) + .replace(/<[^>]*>/g, " ") + .replace(/\s+/g, " ") + .trim(); + return text.length > 180 ? `${text.slice(0, 180)}…` : text; +} + +function emptyStateModel() { + // Every branch below is guarded on there being nothing on screen, because + // this panel explains an EMPTY grid and nothing else. A dashboard that has + // been up for a week and briefly loses Kuma still has its cards: leave them + // alone and let the topbar's OFFLINE say why they have stopped moving. + // Putting "Waiting for Uptime Kuma" under a screen full of services reads + // as though the app has lost them. + if (!state.kumaConnected && !state.services.length) { + return { + kind: "offline", + title: "Waiting for Uptime Kuma", + lead: `Service Dash builds its cards from an Uptime Kuma status page on this host. Nothing usable answered at ${escapeHtml(EP_STATUS)}.`, + // The misconception that cost IceWhale's maintainer an afternoon: + // every container was healthy, so the app looked broken. It is + // stated before the steps because it is the thing that reframes + // everything after it. + note: "Uptime Kuma is a separate application. Service Dash reads from it, but does not bundle, install or start it for you.", + steps: [ + `Install Uptime Kuma on this host. On ZimaOS or CasaOS it is in the app store. Anywhere else:${commandBlock(kumaInstallCommand(), "Run on the host · any folder")}`, + `Open it on port ${escapeHtml(KUMA_PORT)} and create the admin account.`, + `Add your services as monitors, then create a status page, put the monitors in a group on it, and publish it.`, + `If Kuma ended up on another port, or the page's slug is not ${escapeHtml(STATUS_SLUG)}, change KUMA_PORT or STATUS_SLUG in the Compose file to match.`, + ], + diagnostic: kumaDiagnosticCommand(), + detail: tidyKumaError(window.__kuma?.error || ""), + }; + } + + if (state.kumaConnected && !state.services.length) { + return { + kind: "no-monitors", + title: "Connected, but that status page is empty", + lead: `Uptime Kuma answered at ${escapeHtml(EP_STATUS)}, so the connection is fine. The ${escapeHtml(STATUS_SLUG)} status page just has no monitors on it.`, + note: "", + steps: [ + `In Uptime Kuma, open the ${escapeHtml(STATUS_SLUG)} status page and click Edit.`, + // The actual reason most empty status pages are empty: monitors + // are nested inside groups in the API response, and the editor + // will not accept one until a group exists to hold it. + `Add a group first. Monitors live inside groups — a page with no group has nowhere to put them and stays blank.`, + `Drag the monitors you want into that group, then Save.`, + `Check the page is published. An unpublished page answers, but with nothing in it.`, + `For one card carrying both a public and a LAN address, name two monitors Plex and Plex local.`, + ], + diagnostic: "", + detail: "", + }; + } + + if (state.services.length && state.visibleCount === 0) { + return { + kind: "no-matches", + title: "Nothing matches", + lead: `${state.services.length} service${state.services.length === 1 ? "" : "s"} loaded, but none match the current search and filters.`, + note: "", + steps: [], + diagnostic: "", + detail: "", + clear: true, + }; + } + + return null; +} + +// One command block, used for the install command inside a step and for the +// diagnostic behind its fold. The hint sits ON the command because "where do I +// run this" and "do I need to install anything" are the two questions people +// stall on, and a footnote elsewhere does not reach them. +function commandBlock(command, hint) { + return `
+ ${hint} + ${escapeHtml(command)} + +
`; +} + +function renderEmptyState() { + const host = els.emptyState; + if (!host) return; + + const model = emptyStateModel(); + if (!model) { + // Compare before writing: this runs on every poll, and clearing an + // already-empty node is a layout write for nothing. + if (!host.hidden) { + host.hidden = true; + host.removeAttribute("data-kind"); + host.removeAttribute("data-signature"); + host.innerHTML = ""; + } + return; + } + + // The signature is what makes this idempotent. Without it the panel would + // be torn down and rebuilt under the reader every fifteen seconds, which + // loses text selection, closes any open fold and restarts the transition. + const signature = [model.kind, model.detail, model.lead].join("|"); + if (host.dataset.signature === signature) { + host.hidden = false; + return; + } + + const note = model.note ? `

${model.note}

` : ""; + + const steps = model.steps.length + ? `
    ${model.steps.map((s) => `
  1. ${s}
  2. `).join("")}
` + : ""; + + // Folded, because it is the second question. Somebody who has not installed + // Uptime Kuma at all does not need to prove that nothing is answering. + const diagnostic = model.diagnostic + ? `
+ Already installed it? Check what is answering + ${commandBlock(model.diagnostic, "Run on the host · any folder · nothing to install")} + +
` + : ""; + + // The raw fetch error, folded away. It is the fastest route to an answer + // for anyone who reads it, and noise for everyone else. + const detail = model.detail + ? `
What the browser reported${escapeHtml(model.detail)}
` + : ""; + + const clear = model.clear + ? `` + : ""; + + host.dataset.signature = signature; + host.dataset.kind = model.kind; + host.innerHTML = ` +
+

${escapeHtml(model.title)}

+

${model.lead}

+ ${note} + ${steps} + ${clear} + ${diagnostic} + ${detail} +
`; + host.hidden = false; +} + +// One delegated listener for the panel's whole life, attached once. The panel's +// innerHTML is rewritten whenever its cause changes, so per-render listeners +// would leak and per-button lookups would go stale. +if (els.emptyState) { + els.emptyState.addEventListener("click", async (event) => { + const copy = event.target.closest(".emptyCopy"); + if (copy) { + const command = copy.closest(".emptyCmd")?.querySelector("code")?.textContent || ""; + if (!command) return; + if (await writeClipboard(command)) toast("📋 Command copied. Run it on the host.", 2000); + else toast("⚠️ Could not copy the command.", 2200); + return; + } + + if (event.target.closest(".emptyClear")) { + // Reset through the same state the chips and the search box write, + // so the controls agree with the grid afterwards. + state.query = ""; + state.statusFilter = "all"; + state.categoryFilter = "all"; + if (els.q) els.q.value = ""; + savePrefs(); + applyFiltersAndCounts(); + updateChipActiveStates(); + } + }); +} + + function applyFiltersAndCounts() { const qLower = safeStr(state.query).trim().toLowerCase(); const statusFilter = state.statusFilter; const categoryFilter = state.categoryFilter; + let visible = 0; for (const s of state.services) { const card = state.cardElById.get(String(s.id)); if (!card) continue; const matches = serviceMatchesFilters(s, qLower, statusFilter, categoryFilter); + if (matches) visible += 1; card.classList.toggle("is-filtered-out", !matches); } + state.visibleCount = visible; renderChipsWithCounts(); + renderEmptyState(); } /* ========================= @@ -5505,6 +5738,9 @@ async function loadKumaOrMock() { error: String(e?.message || e), }; toast(`⚠️ Uptime Kuma offline. Could not reach ${escapeHtml(EP_STATUS)}. Retrying automatically.`, 5200); + // The toast clears itself after five seconds; the panel is what is still + // on screen when somebody comes back to the tab and wonders what broke. + renderEmptyState(); } await pollOnce(true); @@ -5556,6 +5792,7 @@ async function pollOnce(showToast) { if (showToast) { toast(`⚠️ Sync failed. ${escapeHtml(String(e.message || e))}`, 5200); } + renderEmptyState(); } finally { setTimeout(() => { els.pollDot.style.animationDuration = "1.6s"; diff --git a/docker-compose.casaos.yml b/docker-compose.casaos.yml index 6f6f780..a9217c7 100644 --- a/docker-compose.casaos.yml +++ b/docker-compose.casaos.yml @@ -2,7 +2,7 @@ name: service-dash services: service-dash: - image: ghcr.io/cvaghela/service-dash:1.5.1 + image: ghcr.io/cvaghela/service-dash:1.5.2 container_name: service-dash ports: - "8888:80" @@ -41,7 +41,7 @@ services: # Answers one question for nginx: is this browser really signed in to Uptime # Kuma? Without it, saving settings would have to trust the browser's word. kuma-auth: - image: ghcr.io/cvaghela/service-dash-kuma-auth:1.5.1 + image: ghcr.io/cvaghela/service-dash-kuma-auth:1.5.2 container_name: service-dash-kuma-auth environment: # Must point at the same Uptime Kuma as KUMA_PORT above. @@ -65,7 +65,7 @@ services: restart: unless-stopped network-info: - image: ghcr.io/cvaghela/service-dash-network-info:1.5.1 + image: ghcr.io/cvaghela/service-dash-network-info:1.5.2 container_name: service-dash-network-info network_mode: host read_only: true @@ -166,7 +166,7 @@ services: # # docker exec -it service-dash-claude-usage claude auth login claude-usage: - image: ghcr.io/cvaghela/service-dash-claude-usage:1.5.1 + image: ghcr.io/cvaghela/service-dash-claude-usage:1.5.2 container_name: service-dash-claude-usage environment: # Plan windows move slowly; polling harder gains nothing. @@ -193,7 +193,7 @@ services: # localhost and hangs. # docker exec -it service-dash-codex-usage codex login --device-auth codex-usage: - image: ghcr.io/cvaghela/service-dash-codex-usage:1.5.1 + image: ghcr.io/cvaghela/service-dash-codex-usage:1.5.2 container_name: service-dash-codex-usage environment: CODEX_USAGE_REFRESH_SECONDS: "300" diff --git a/docker-compose.yml b/docker-compose.yml index f68689d..2105680 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ name: service-dash services: service-dash: - image: ghcr.io/cvaghela/service-dash:1.5.1 + image: ghcr.io/cvaghela/service-dash:1.5.2 container_name: service-dash ports: # Change 8888 only when a different host port is required. @@ -37,7 +37,7 @@ services: # Answers one question for nginx: is this browser really signed in to Uptime # Kuma? Without it, saving settings would have to trust the browser's word. kuma-auth: - image: ghcr.io/cvaghela/service-dash-kuma-auth:1.5.1 + image: ghcr.io/cvaghela/service-dash-kuma-auth:1.5.2 container_name: service-dash-kuma-auth environment: # Must point at the same Uptime Kuma as KUMA_PORT above — if you change @@ -57,7 +57,7 @@ services: restart: unless-stopped network-info: - image: ghcr.io/cvaghela/service-dash-network-info:1.5.1 + image: ghcr.io/cvaghela/service-dash-network-info:1.5.2 container_name: service-dash-network-info network_mode: host read_only: true @@ -139,7 +139,7 @@ services: # # docker exec -it service-dash-claude-usage claude auth login claude-usage: - image: ghcr.io/cvaghela/service-dash-claude-usage:1.5.1 + image: ghcr.io/cvaghela/service-dash-claude-usage:1.5.2 container_name: service-dash-claude-usage environment: # Plan windows move slowly; polling harder gains nothing. @@ -160,7 +160,7 @@ services: # localhost and hangs. # docker exec -it service-dash-codex-usage codex login --device-auth codex-usage: - image: ghcr.io/cvaghela/service-dash-codex-usage:1.5.1 + image: ghcr.io/cvaghela/service-dash-codex-usage:1.5.2 container_name: service-dash-codex-usage environment: CODEX_USAGE_REFRESH_SECONDS: "300" diff --git a/entrypoint.sh b/entrypoint.sh index c3ea243..8408758 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -50,8 +50,12 @@ mkdir -p "$SHARED_SETTINGS_ROOT/settings" "$SHARED_SETTINGS_ROOT/tmp" # nginx workers drop to this user, so the volume has to belong to it. chown -R nginx:nginx "$SHARED_SETTINGS_ROOT" -printf 'window.__DASHBOARD_CONFIG__ = { statusSlug: "%s", storageMount: "%s" };\n' \ - "$STATUS_SLUG" "$STORAGE_MOUNT" \ +# kumaPort is here for the empty state alone: when no status page can be +# reached, the panel prints a diagnostic command the reader can paste, and a +# command naming the wrong port is worse than no command. Both values are +# already validated above, so neither can inject into the JS literal. +printf 'window.__DASHBOARD_CONFIG__ = { statusSlug: "%s", storageMount: "%s", kumaPort: "%s" };\n' \ + "$STATUS_SLUG" "$STORAGE_MOUNT" "$KUMA_PORT" \ > /usr/share/nginx/html/config.js export KUMA_UPSTREAM diff --git a/index.html b/index.html index d3a16de..5b6f097 100644 --- a/index.html +++ b/index.html @@ -38,7 +38,7 @@ rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,500,0,0" /> - +

AI Usage

+ + @@ -580,7 +584,7 @@

Card settings

- - + + diff --git a/scripts/check-empty-state.py b/scripts/check-empty-state.py new file mode 100755 index 0000000..21fde1b --- /dev/null +++ b/scripts/check-empty-state.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Guard the empty state: the panel that explains a blank grid. + +Why this exists. IceWhale's maintainer installed Service Dash on ZimaOS while +reviewing it for their store, got a blank grid, checked that every container was +healthy, and reasonably concluded the app was broken. It was not -- Uptime Kuma +simply was not there to read. The only things the page said were the word +OFFLINE in small type in the topbar and a toast that cleared itself after 5.2 +seconds, and neither survived long enough to answer the question. + +A blank grid has three quite different causes and the reader cannot tell them +apart, so the panel names which one it is. Each check below is one way that +would silently stop being true. + + python3 scripts/check-empty-state.py +""" +import pathlib +import re +import sys + +REPO = pathlib.Path(__file__).resolve().parent.parent +APP = REPO / "assets" / "js" / "app.js" +INDEX = REPO / "index.html" +STYLES = REPO / "assets" / "css" / "styles.css" +ENTRYPOINT = REPO / "entrypoint.sh" + + +def main() -> int: + problems: list[str] = [] + + app = APP.read_text() + index = INDEX.read_text() + styles = STYLES.read_text() + entrypoint = ENTRYPOINT.read_text() + + # The container has to exist, and has to sit OUTSIDE #groups: + # buildDomOnceIfNeeded() clears #groups wholesale and would take the panel + # with it, which reads as "the empty state randomly stopped appearing". + if 'id="emptyState"' not in index: + problems.append('index.html: no element with id="emptyState"') + else: + groups = re.search(r'
(.*?)
', index, re.S) + if groups and "emptyState" in groups.group(1): + problems.append( + "index.html: #emptyState is inside #groups, which buildDomOnceIfNeeded() " + "empties on every rebuild -- it must be a sibling" + ) + + if "function renderEmptyState(" not in app: + problems.append("app.js: renderEmptyState() is gone") + + # Three call sites, and each is load-bearing. The filter pass covers the + # healthy paths; the other two are the branches where Kuma failed, and + # applyFiltersAndCounts() is never reached on those. + calls = len(re.findall(r"^\s*renderEmptyState\(\);", app, re.M)) + if calls < 3: + problems.append( + f"app.js: renderEmptyState() is called from {calls} place(s), expected at least 3 " + "(the filter pass, plus the failure branches of loadKumaOrMock and pollOnce) -- " + "a missing call is a blank grid with no explanation on exactly the path that needs one" + ) + + # Every branch of emptyStateModel() must be guarded on there being nothing + # on screen. Without it, a dashboard that has been up for a week and briefly + # loses Kuma puts "Waiting for Uptime Kuma" under a screen full of its own + # cards, which reads as though the app has lost them. Caught in the sandbox, + # not in review. + model = re.search(r"function emptyStateModel\(\) \{(.*?)\n\}", app, re.S) + if not model: + problems.append("app.js: emptyStateModel() is gone") + else: + body = model.group(1) + for label, needle in ( + ("the offline branch", "!state.kumaConnected && !state.services.length"), + ("the empty-page branch", "state.kumaConnected && !state.services.length"), + ("the no-matches branch", "state.services.length && state.visibleCount === 0"), + ): + if needle not in body: + problems.append( + f"app.js: {label} of emptyStateModel() is no longer guarded on an empty " + f"grid (expected `{needle}`) -- the panel would explain an emptiness that " + "is not there, under cards the reader can see" + ) + + # The command the panel offers must describe THIS install. It was hardcoded + # to 3001/homelab once by accident; a command naming the wrong port sends + # the reader to prove something irrelevant. + command = re.search(r"function kumaDiagnosticCommand\(\) \{(.*?)\n\}", app, re.S) + if not command: + problems.append("app.js: kumaDiagnosticCommand() is gone") + else: + body = command.group(1) + for token in ("${KUMA_PORT}", "${STATUS_SLUG}"): + if token not in body: + problems.append( + f"app.js: the diagnostic command no longer interpolates {token} -- " + "it would print a command describing somebody else's install" + ) + + # KUMA_PORT only reaches the browser because entrypoint.sh puts it there. + # Both halves are checked, not just the name: the first version of this + # check looked for "kumaPort" anywhere in the file and was satisfied by the + # COMMENT above the printf, so deleting the emission itself passed clean. + emit = re.search(r"printf 'window\.__DASHBOARD_CONFIG__[^\n]*\n(?:[^\n]*\n)?", entrypoint) + if not emit: + problems.append("entrypoint.sh: the config.js emission is gone") + else: + block = emit.group(0) + if 'kumaPort: "%s"' not in block: + problems.append( + "entrypoint.sh: config.js no longer declares kumaPort -- the panel falls back " + "to 3001 and silently prints the wrong port for anyone who changed it" + ) + if "$KUMA_PORT" not in block: + problems.append( + "entrypoint.sh: kumaPort is declared but $KUMA_PORT is not passed to printf -- " + "the value would be empty or, worse, shifted from the next argument" + ) + if "RUNTIME_CONFIG.kumaPort" not in app: + problems.append("app.js: kumaPort is no longer read from the runtime config") + + # --pending is a dark-ground token: on the light panel it measures 1.36:1, + # which is not a heading so much as a rumour of one. The override is what + # takes it to 9.34:1. + if '.emptyState[data-kind="offline"] .emptyTitle' not in styles: + problems.append("styles.css: the offline title no longer takes the warning colour") + elif '[data-theme="light"] .emptyState[data-kind="offline"] .emptyTitle' not in styles: + problems.append( + 'styles.css: no light-mode override for the offline title -- --pending measures ' + "1.36:1 on that panel in light mode, which fails AA badly" + ) + + # A stray NUL turns the whole file binary to grep, ripgrep and every editor + # search. One got in here from a separator character and cost real time + # before anyone noticed that grep had quietly stopped matching anything. + for path in (APP, INDEX, STYLES, ENTRYPOINT): + count = path.read_bytes().count(b"\x00") + if count: + problems.append( + f"{path.relative_to(REPO)}: contains {count} NUL byte(s) -- grep and every " + "editor search treat the file as binary and silently find nothing in it" + ) + + for problem in problems: + print(f"FAIL: {problem}", file=sys.stderr) + if not problems: + print("ok: the empty state is wired up and can still explain itself") + return 1 if problems else 0 + + +if __name__ == "__main__": + sys.exit(main())