De tweede indeling gebouwd, en er ging meer weg dan erbij kwam

Vijf widgets op de bovenste rij, de certificaatkeuze in een dialoog achter een
puntjesmenu, het activiteitenlog vol breed, en het instelkader dichtgeklapt met
twee kopieerknoppen. Uitgeleverd als 0.0.17. De zelfcontrole uit 0.0.16 heeft
daarmee eindelijk een plek om te verschijnen.

Wat opvalt is wat er verdween. Twee mechanismen bestonden alleen omdat er kaders
naast elkaar stonden: het meerekken van de tegels met de hoogte van hun buur, en
het samen groeien en krimpen van log en certificaatkader. Dat tweede was de hele
inhoud van 0.0.13. Vijf gelijke widgets en een vol breed log hebben geen buur,
dus beide zijn weg, samen met .col-5 en .col-7.

De harde eis staat in de code en niet alleen in het plan: de foutmelding "geen
certificaat in gebruik" heeft de knop die de dialoog opent. Een verse installatie
heeft geen certificaat en de pagina is de enige plek waar je er een kiest; dat
achter een menu verstoppen zou de klem van 0.0.3 in een andere vorm zijn.

Twee dingen onderweg bijgestuurd. Het menu werd drie punten in plaats van een
hamburger, op aanwijzing van de gebruiker, want dat is wat umbrelOS bij zijn
eigen apps toont. En het ontwerp beweerde dat de certificaatlijst een lijst was,
terwijl die op 20-08 al een dropdown geworden is; dat is rechtgezet in 4a2 en de
dropdown is ongewijzigd meeverhuisd.

Niet geverifieerd: niets hiervan is in een browser gezien, op geen enkele
schermbreedte. Dat staat als taak met de gebruiker als eigenaar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Harmen
2026-08-27 15:49:38 +02:00
co-authored by Claude Opus 5
parent c8d9765cb3
commit be91125cce
7 changed files with 642 additions and 259 deletions
+42
View File
@@ -8,6 +8,48 @@ Wat er nog moet gebeuren staat **niet** hier maar in de plannen; zie [CONTINUE_H
Een lijst met geplande features op twee plekken loopt uit elkaar, en dan is geen van beide meer te
vertrouwen.
## [0.0.17] - 2026-08-27
### Changed
- **De tweede indeling: vijf kleine widgets op de bovenste rij.** Electrum-server, blokhoogte,
reactietijd, certificaat en de zelfcontrole uit 0.0.16, die daarmee eindelijk ergens te zien is.
Opgegeven door de gebruiker; ontwerp in het plan **Webinterface**, `PLAN.md` §4a2.
Dit is minder mechaniek dan er stond, niet meer. Twee koppelingen vervallen die er alleen waren omdat
kaders naast elkaar stonden: de tegels hoefden niet meer mee te rekken met de hoogte van hun buur, en
het logblok en de certificaatlijst hoefden niet meer samen te groeien en te krimpen. Dat laatste was de
hele inhoud van 0.0.13.
Vijf past niet in het twaalfkolomsraster, dus die rij heeft een eigen raster van vijf. De stappen bij
het versmallen staan expliciet in de CSS en zijn niet aan `auto-fit` overgelaten: met vijf items levert
elke stap een ongelijke laatste rij op, en dan wil je zelf bepalen welke widget alleen komt te staan.
- **Kiezen, uploaden en de certificaatdetails naar een dialoog** achter een menu rechtsboven, samen met de
licht/donker-schakelaar. Het menu toont drie punten en geen hamburger, op aanwijzing van de gebruiker:
dat is wat umbrelOS bij zijn eigen apps doet.
**De eis die dat oproept staat in de code en niet alleen in het plan.** Een verse installatie heeft geen
certificaat, en de pagina is de enige plek waar je er een kiest; dat achter een menu verstoppen is de
klem van 0.0.3 in een andere vorm. De foutmelding bovenaan blijft daarom staan en heeft nu een knop die
de dialoog opent. Het menu is de weg terug voor later, die knop is de weg erheen nu.
- **Het activiteitenlog over de volle breedte**, en `Setting up your wallet` dichtgeklapt en compacter.
Twee kopieerknoppen in plaats van één per wallet: er zijn feitelijk maar twee vormen van hetzelfde
adres, dus tien knoppen waren negen keer dezelfde handeling. Dat draait een besluit van 19-08-2026
terug, met één voorwaarde die in het plan staat: de wallets blijven gegroepeerd onder de vorm die ze
vragen, want zonder die groepering staat er wel een adres maar niet meer waar het bij hoort.
- **De licht/donker-regel toont de huidige stand en niet de handeling.** Als losse knop stond er "Light"
terwijl de pagina donker was, en dát klopte: een knop belooft wat er gebeurt als je hem indrukt. Als
menuregel met een waarde erachter leest dezelfde tekst als een bewering over hoe het nu staat, en dan is
hij fout.
### Removed
- `.col-5` en `.col-7` uit de CSS. Er is geen rij meer die in tweeën valt, en een ongebruikte klasse
nodigt uit tot een indeling die er niet meer is.
## [0.0.16] - 2026-08-27
### Added
+1 -1
View File
@@ -23,7 +23,7 @@
| Plan | App | Volgende stap | Status |
|-|-|-|-|
| [Webinterface](Plannen/Actief/005-Webinterface/TAKEN.md) | Gate | **Fase 6, de tweede indeling** (opgegeven 27-08-2026): vijf kleine widgets op de bovenste rij, de certificaatkeuze naar een dialoog achter een menu, log over de volle breedte, en het instelkader dichtgeklapt en compacter. De zelfcontrole eronder is gebouwd in 0.0.16 (27-08-2026), dus wat er nu ligt is de verbouwing zelf | 🔶 |
| [Webinterface](Plannen/Actief/005-Webinterface/TAKEN.md) | Gate | **Fase 6 is gebouwd en uitgerold als 0.0.17 (27-08-2026), maar in geen enkele browser gezien.** Nakijken op een breed scherm én op een telefoon is nu de volgende stap: de bovenste rij gaat van vijf naar drie naar twee kolommen, en de certificaatdialoog is nieuw. Daarna resteert alleen nog het uploaden op het niet-gelukkige pad | 🔶 |
| [Umbrelapp](Plannen/Actief/008-Umbrelapp/TAKEN.md) | Relay | **Hij draait sinds 25-08-2026, maar kan in de kern niet werken.** Uit de broncode van Suite: bij een eigen relay-URL registreert de cliënt geen eigenaar, terwijl Trezor's relay iedereen zonder limietenrij weigert. Volgende stap is de kále Evolu-relay proberen (`docker.io/evoluhq/relay:latest`, één container, gepubliceerde image) en waarschijnlijk daarnaartoe verbouwen. Zie open punt 6 | 🔶 |
| [Proefopstelling](Plannen/Actief/007-Proefopstelling/TAKEN.md) | Relay | **Eerst beslissen of dit plan nog bestaansrecht heeft.** Fase 1 is af, maar fase 2 en 3 zijn ingehaald: het pakket draait al op de Umbrel, dus een lokale opstelling ernaast meet minder en kost meer. Wat overblijft zijn twee vragen die al als blokkade in **Umbrelapp** staan, en die horen op één plek | 🔶 |
+14 -5
View File
@@ -243,11 +243,20 @@ Twee dingen gaan tegen een eerder besluit in. Ze staan hier opgeschreven zodat e
**gegroepeerd onder de twee vormen** te staan, elk met zijn eigen kopieerknop, en niet als één platte
lijst wallets naast twee losse knoppen. Anders staat er wel een adres om te kopiëren maar niet meer bij
welke wallet welk adres hoort, en dat is precies wat dit kader moet vertellen.
2. **De certificaatlijst verhuist naar een dialoog.** Op 19-08-2026 is een dropdown afgewezen omdat je bron,
naam en resterende dagen naast elkaar moet kunnen vergelijken en een verlopen certificaat rood moet
kunnen zijn. **Die reden verbiedt een dialoog niet, hij verbiedt een dropdown**, en een dialoog geeft
meer ruimte dan het kader had. De lijst blijft dus een lijst, nu in de dialoog. Vervang hem daar niet
alsnog door een dropdown omdat het in een dialoog netjes zou staan.
2. **De certificaatkeuze verhuist naar een dialoog.** Op 19-08-2026 is een dropdown afgewezen omdat je
bron, naam en resterende dagen naast elkaar moet kunnen vergelijken en een verlopen certificaat rood
moet kunnen zijn. Die reden verbood een dropdown, geen dialoog, en een dialoog geeft meer ruimte dan
het kader had.
**Bijgesteld bij het bouwen op 27-08-2026: het ís al een dropdown.** Hier stond "de lijst blijft dus
een lijst", en dat klopte niet meer. Op 20-08-2026 bleken het veertien certificaten op de Umbrel van de
gebruiker, en toen sloeg het argument om: veertien rijen in een scrollend vak maken van de keuze het
drukste onderdeel van de pagina, terwijl je er precies één keer iets mee doet. Alles wat de kleur zou
zeggen staat sindsdien in de tekst van de optie zelf, inclusief `expired` en `in use`, en dat leest ook
voor wie de kleur niet ziet.
De dropdown is dus ongewijzigd meeverhuisd. Wat blijft gelden is de reden eronder: wie hem ooit terug
wil veranderen, moet de vergelijkbaarheid oplossen en niet alleen de ruimte.
#### De eis die deze indeling zelf oproept
@@ -1,5 +1,28 @@
# Voortgang - Webinterface
## 27-08-2026 - de tweede indeling gebouwd, en er ging meer weg dan erbij kwam
Fase 6 in één keer gebouwd, uitgeleverd als 0.0.17: vijf widgets op de bovenste rij, de certificaatkeuze
in een dialoog achter een puntjesmenu, het log vol breed, en het instelkader dichtgeklapt met twee
kopieerknoppen. De zelfcontrole uit 0.0.16 heeft daarmee eindelijk een plek om te verschijnen.
**Wat opvalt aan deze verbouwing is wat er verdween.** Twee mechanismen bestonden alleen omdat er kaders
naast elkaar stonden: het meerekken van de tegels met de hoogte van hun buur, en het samen groeien en
krimpen van log en certificaatkader. Dat tweede was de hele inhoud van 0.0.13. Vijf gelijke widgets en een
vol breed log hebben geen buur, dus beide zijn weg, net als `.col-5` en `.col-7`.
**Twee dingen zijn onderweg bijgestuurd.** Het menu werd drie punten in plaats van een hamburger, op
aanwijzing van de gebruiker: dat is wat umbrelOS bij zijn eigen apps toont. En het ontwerp beweerde dat de
certificaatlijst een lijst was; dat was op 20-08-2026 al een dropdown geworden. Die is ongewijzigd
meeverhuisd naar de dialoog.
De harde eis uit §4a2 staat in de code en niet alleen in het plan: de foutmelding "geen certificaat in
gebruik" heeft de knop die de dialoog opent, want een verse installatie moet die keuze kunnen vinden.
**Geraakt:** `index.html.template` (kop, meldingen, hele raster, dialoog, script), `umbrel-app.yml`
(0.0.17), dit plan, de changelog. **Tests:** 39, 32, 54 en 22 goed, 0 fout. **Niet geverifieerd:** niets
hiervan is in een browser gezien, op geen enkele schermbreedte.
## 27-08-2026 - de app kijkt eindelijk naar zijn eigen voordeur, en meet minder dan beloofd
Open punt 5 is goedgekeurd en gebouwd in 0.0.16. De agent verbindt met de eigen TLS-poort, maakt de
+28 -25
View File
@@ -52,9 +52,9 @@
alleen tegen tijdelijke bestanden getoetst, niet tegen een echte wallet. **Loopt sinds 27-08-2026 bij
de gebruiker; hij kijkt er na een etmaal naar. Eigenaar: gebruiker**
- [ ] **Fase 6, de tweede indeling.** Opgegeven door de gebruiker op 27-08-2026 en het grootste blok werk
dat er in dit plan nog ligt. Begint met de zelfcontrole, want dat is het enige echte nieuwe werk en
het levert de vijfde widget van de bovenste rij
- [x] **Fase 6, de tweede indeling, gebouwd in 0.0.17 (27-08-2026).** Alles uit de opdracht zit erin; wat
er nog staat is het nakijken in een browser, en dat is de helft van de verificatie bij een
verbouwing van deze omvang. Zie de fase zelf voor wat er per punt gedaan is
Daarna, en niet eerder:
@@ -245,28 +245,31 @@ de vijfde widget; de rest is de pagina verbouwen om wat er dan staat.
Getest in `tests/test_agent_zelfcontrole.py`, 22 toetsen, met de nadruk op de niet-gelukkige paden:
geen certificaat, dichte poort, iets dat luistert maar geen TLS spreekt, en een ander certificaat aan
de lijn. Alle vier de beslissende regels zijn mutatie-getest
- [ ] Rij 1 wordt vijf gelijke widgets: Electrum-server, blokhoogte, reactietijd, certificaat, zelfcontrole
- [ ] Een eigen raster van vijf kolommen voor die rij, en beslissen wat er tussen "vijf naast elkaar" en
"gestapeld op mobiel" gebeurt. Vijf valt niet netjes in twaalf kolommen; dit is nieuw werk
- [ ] De Electrum-serverwidget kleiner, met minder onderteksten
- [ ] De certificaatwidget toont het gekozen certificaat: naam en dagen tot vernieuwing. Niet de keuze
- [ ] Kiezen, uploaden en afhandelen naar een dialoog achter een hamburger- of puntjesmenu rechtsboven.
Met het `dialog`-element, want focus en Escape zitten erin. De lijst blijft een lijst, zie §4a2
- [ ] **De licht/donker-schakelaar het menu in** (27-08-2026). Staat nu als losse knop rechtsboven. Let op
de vorm: in een menu hoort de huidige stand te staan en niet de handeling, dus niet het label "Light"
overnemen terwijl de pagina licht is. Zie §4a2
- [ ] **De foutmelding "geen certificaat actief" blijft bovenaan staan en krijgt de knop die de dialoog
opent.** Dit is de harde eis van deze indeling: een verse installatie heeft geen certificaat, en de
pagina is de enige plek waar je er een kiest. Zie §4a2 en de klem van 0.0.3
- [ ] Het activiteitenlog over de volle breedte
- [ ] "Setting up your wallet" dichtgeklapt met `details`/`summary`, en compacter
- [ ] Twee kopieerknoppen in plaats van één per clientregel, **met de wallets gegroepeerd onder de vorm die
ze willen.** Niet één platte lijst naast twee losse knoppen: dan is het adres er nog wel, maar niet
meer bij welke wallet welk adres hoort. Bron blijft `Referenties/Clients.md` §4
- [ ] Opruimen wat vervalt: de statustegels hoeven niet meer mee te rekken met hun buur, en het logblok en
de certificaatlijst hoeven niet meer samen te groeien. Dat laatste was de hele inhoud van 0.0.13
- [ ] Na afloop op mobiel nakijken, net als bij 0.0.10. De leesvolgorde in de HTML blijft de bedoelde
volgorde, zonder `order` in de CSS
- [x] Rij 1 is vijf gelijke widgets: Electrum-server, blokhoogte, reactietijd, certificaat, zelfcontrole
- [x] **Een eigen raster van vijf kolommen, met expliciete stappen** (5 → 3 → 2 → 1). Niet aan `auto-fit`
overgelaten: met vijf items levert elke stap een ongelijke laatste rij op, en dan wil je zelf bepalen
wélke widget alleen komt te staan. Dat is steeds de laatste, de zelfcontrole, want die is als enige
te begrijpen zonder buur
- [x] De Electrum-serverwidget kleiner: de naam als waarde, het adres als onderregel. "Last checked" is
eruit, want dat stond al onder de blokhoogte als "as of ... ago"
- [x] De certificaatwidget toont de dagen tot vernieuwing, met de naam als onderregel. Bij een verlopen
certificaat staat er `expired` in het rood, en zonder keuze `none` en niet `unknown`: er is niets
onbekends aan
- [x] Kiezen, uploaden en de details naar een dialoog achter het menu, met het `dialog`-element
- [x] **De licht/donker-schakelaar het menu in, met de huidige stand als waarde.** Het menu toont drie
punten en geen hamburger, op aanwijzing van de gebruiker: dat is wat umbrelOS bij zijn eigen apps
doet. Als tekst en niet als SVG, zodat hij meeschaalt met de lettergrootte
- [x] **De foutmelding "geen certificaat actief" heeft de knop die de dialoog opent.** De harde eis van
deze indeling, en hij staat als zodanig in de code
- [x] Het activiteitenlog over de volle breedte
- [x] "Setting up your wallet" dichtgeklapt met `details`/`summary`, en compacter
- [x] Twee kopieerknoppen, met de wallets gegroepeerd onder de vorm die ze vragen
- [x] Opgeruimd wat vervalt: de hoogtekoppeling van log en certificaatkader, het meerekken van de tegels,
en `.col-5` en `.col-7` uit de CSS
- [ ] **Na afloop op mobiel nakijken, net als bij 0.0.10.** Niets van deze indeling is in een browser
gezien, en dat is bij een verbouwing van deze omvang de helft van de verificatie. Let vooral op de
stap van vijf naar drie naar twee kolommen, en op de dialoog op een smal scherm.
**Eigenaar: gebruiker**
## Geblokkeerd / wacht op
+485 -182
View File
@@ -107,7 +107,7 @@ body {
hebben 1,8rem ruimte binnenin en een radius van 28 pixels; met 1rem ertussen
is de ruimte tússen twee kaders kleiner dan de ruimte erbinnen, en dan gaan ze
aan elkaar plakken. De maat hoort eerder groter dan de binnenruimte te zijn
dan kleiner. Zelfde waarde in .stat-grid hieronder, anders valt de rij tegels
dan kleiner. Zelfde waarde in .row1 hieronder, anders valt de rij widgets
uit de toon met de rij eronder. */
gap: 1.5rem;
/* stretch en niet start: twee kaders naast elkaar horen even hoog te zijn, en
@@ -118,16 +118,14 @@ body {
}
.grid > * { min-width: 0; }
.grid > .card { display: flex; flex-direction: column; }
.col-5 { grid-column: span 5; }
.col-7 { grid-column: span 7; }
/* Hier stonden ook .col-5 en .col-7, met een mediaregel die ze onder 1100px vol
breed maakte. Sinds de tweede indeling (27-08-2026) is er geen rij meer die in
tweeën valt: de bovenste rij heeft zijn eigen raster van vijf, en het log en het
instelkader zijn allebei vol breed. Weggehaald in plaats van laten staan, want
ongebruikte klassen nodigen uit tot een indeling die er niet meer is. */
.col-full { grid-column: 1 / -1; }
.grid .card { margin-bottom: 0; }
/* Onder deze breedte is naast elkaar geen winst meer maar gedrang. */
@media (max-width: 1100px) {
.col-5, .col-7 { grid-column: 1 / -1; }
}
/* ── Typografie ─────────────────────────────────────────────────────────── */
.t-h1 { font-size: 2rem; font-weight: 700; letter-spacing: -0.025em; line-height: 1.15; }
.t-h3 { font-size: 1.1rem; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; }
@@ -183,23 +181,35 @@ body {
[data-theme="light"] .card { box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 6px 24px rgba(0,0,0,0.04); }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.1rem; }
/* Drie kolommen, en de reactietijd neemt er twee. Er stonden drie tegels van
gelijke breedte; de derde was de resterende geldigheid van het certificaat, en
die is er op 20-08-2026 uit omdat de kaart eronder hetzelfde zegt met een datum
erbij. De ruimte gaat naar de sparkline, want dat is het enige hier dat
daadwerkelijk breedte nodig heeft. */
.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
.stat-wide { grid-column: span 2; }
/* ── De bovenste rij ────────────────────────────────────────────────────────
Vijf gelijke widgets (27-08-2026). Hier stond een raster van drie tegels waarvan
de reactietijd er twee nam, naast een kader met lijstregels; dat vroeg twee
kunstjes die nu weg zijn, namelijk meerekken met de hoogte van de buur en een
dubbele kolom voor de sparkline.
/* De drie tegels staan naast een kader met drie lijstregels en zijn van zichzelf
lager, wat een gat rechtsboven oplevert. Ze zetten hun getal dus onderaan, zodat
de drie getallen onderling op één lijn staan én op de laatste regel van het kader
ernaast. In de gestapelde weergave doet dit niets, want daar is geen buur om je
aan te meten. */
@media (min-width: 1101px) {
.stat-grid .card { display: flex; flex-direction: column; }
.stat-grid .stat-body { margin-top: auto; }
Een eigen raster en geen deling van de twaalf kolommen hierboven, want vijf
past daar niet in. Dat is nieuw op deze pagina: elke andere rij viel in tweeën.
Wat er bij het versmallen gebeurt, is daarom expliciet en niet aan auto-fit
overgelaten. Met vijf items levert elke stap een ongelijke laatste rij op, en
dan wil je zelf bepalen wélke widget alleen komt te staan. Dat is steeds de
laatste, de zelfcontrole, en die is als enige begrijpelijk zonder buur. */
.row1 {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 1.5rem;
}
/* Onder deze breedte wordt een widget van een vijfde te smal voor "Wrong
certificate" of een blokhoogte met scheidingstekens. Drie plus twee. */
@media (max-width: 1400px) { .row1 { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 900px) { .row1 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .row1 { grid-template-columns: 1fr; } }
/* De widgets zetten hun waarde onderaan, zodat de vijf getallen op één lijn
staan ook als de ene titel over twee regels valt en de andere niet. */
.row1 .card { display: flex; flex-direction: column; }
.row1 .stat-body { margin-top: auto; }
/* De twee clientlijsten naast elkaar zodra er ruimte is; het zijn twee losse
lijsten en geen vervolg op elkaar, dus onder elkaar is verspilde breedte. */
@@ -211,6 +221,15 @@ body {
.clients { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); gap: 1.2rem 2.4rem; }
.stat-val { font-size: 1.9rem; font-weight: 700; letter-spacing: -0.025em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.stat-val.unknown { color: var(--text-ter); font-size: 1.5rem; }
/* Twee widgets tonen een woord in plaats van een getal, en op 1,9rem loopt
"Wrong certificate" over drie regels. Kleiner, en met een afbreekpunt, want
afkappen met puntjes zou hier de betekenis wegnemen. */
.stat-val.text { font-size: 1.3rem; line-height: 1.25; overflow-wrap: anywhere; }
/* Rood is voor het geval waar je iets aan moet doen. Bewust niet voor "off": geen
certificaat gekozen is de normale begintoestand van een verse installatie, en
daar staat de melding bovenaan al voor. */
.stat-val.bad { color: var(--neg); }
.stat-sub.mono { font-family: var(--font-mono); overflow-wrap: anywhere; }
/* Even hoog als de sparkline hieronder, en met dezelfde bovenmarge. Anders staat
het getal van "Backend response" hoger dan de andere twee, want daar hangt een
grafiekje onder in plaats van een regel tekst. */
@@ -317,24 +336,20 @@ body {
font-family: var(--font-mono);
font-size: 0.75rem;
line-height: 1.9;
/* flex-basis 0 en niet auto, en dát is de hele hoogtekwestie van 20-08-2026.
Met basis auto telt de inhoud van dit blok mee voor de natuurlijke hoogte van
zijn kader, en dan duwt een log met tien regels de rij hoger dan het
certificaatkader ernaast nodig heeft. Het gevolg zag je aan de andere kant:
leegte onder de knop "Use this certificate", die precies verdween als je het
uploadblok uitklapte.
/* Sinds 27-08-2026 neemt dit blok gewoon zijn eigen hoogte. Hier stond
flex: 1 1 0, en dat was de oplossing voor een probleem dat niet meer bestaat:
het log stond naast het certificaatkader en die twee moesten samen groeien en
krimpen, anders viel er leegte onder de knop "Use this certificate" zodra je
het uploadblok uitklapte. Dat was de hele inhoud van 0.0.13.
Met basis 0 draagt dit blok niets bij aan die natuurlijke hoogte. Het
certificaatkader bepaalt de rij, dit blok groeit ernaartoe, en uitklappen laat
dus beide kaders meegroeien. Wat er niet in past, scrolt hier, en dat is waar
een log voor gemaakt is.
Geen max-height meer: de rij is nu begrensd door het kader ernaast, dus een
eigen plafond zou alleen weer leegte onderaan opleveren. */
flex: 1 1 0;
/* De bodem voor het geval het certificaatkader ooit korter is dan dit, zoals
wanneer er geen enkel certificaat gevonden is. Er passen vier regels in. */
De certificaatkeuze zit nu in een dialoog en dit kader is vol breed, dus er is
geen buur meer om je aan te meten. Wat overblijft is een plafond, want een
etmaal aan regels zou anders één lange pagina worden; daarboven scrolt het
blok zelf, en dat is waar een log voor gemaakt is. */
flex: 0 0 auto;
/* Er passen vier regels in; dat is de bodem voor een leeg of net gestart log. */
min-height: 110px;
max-height: 60vh;
overflow: auto;
overscroll-behavior: contain;
scrollbar-width: thin;
@@ -385,6 +400,103 @@ body {
/* ── Grafieken ──────────────────────────────────────────────────────────── */
.spark { width: 100%; height: 34px; display: block; margin-top: 0.5rem; overflow: visible; }
/* ── Het menu rechtsboven ───────────────────────────────────────────────── */
.menu { position: relative; flex-shrink: 0; }
/* Het driehoekje van summary weg, in twee vormen: WebKit heeft zijn eigen
pseudo-element en de rest gaat via list-style. */
/* Alleen het driehoekje weg en de cursor; de maatvoering staat hieronder bij
.menu-button, want twee regels die allebei padding zetten laten de meest
specifieke winnen en dat is deze. */
.menu > summary { list-style: none; cursor: pointer; }
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::marker { content: ''; }
/* Flink, want drie punten op de gewone knopgrootte van 0,75rem zijn nauwelijks
te zien en al helemaal niet te raken. De letterafstand zet ze uit elkaar,
anders plakken ze in de meeste letterfamilies aan elkaar. De vaste hoogte en
line-height houden de knop even hoog als hij met tekst erin zou zijn. */
.menu-button {
font-size: 1.4rem;
line-height: 1;
letter-spacing: 0.08em;
padding: 0.3rem 0.7rem 0.5rem;
min-height: 2rem;
}
.menu-panel {
position: absolute; right: 0; top: calc(100% + 0.4rem);
z-index: 20; min-width: 15rem;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 0.35rem;
box-shadow: 0 8px 30px rgba(0,0,0,0.18);
}
.menu-item {
display: flex; align-items: center; justify-content: space-between; gap: 1.2rem;
width: 100%; text-align: left;
font-family: inherit; font-size: 0.82rem; color: var(--text-primary);
background: transparent; border: 0; border-radius: var(--radius-sm);
padding: 0.55rem 0.65rem; cursor: pointer;
}
.menu-item:hover { background: var(--neutral-bg); }
.menu-meta { font-size: 0.75rem; color: var(--text-sec); }
/* ── De dialoog ─────────────────────────────────────────────────────────── */
/* Breder dan het kader waar deze inhoud uit komt, en dat mag: een dialoog leent
geen hoogte van een buur, dus de keuzelijst en het uploadblok kunnen hier
gewoon staan zonder dat er elders leegte ontstaat. */
.sheet {
/* Alleen van belang in de terugvalroute zonder showModal; zie de HTML. */
grid-column: 1 / -1;
width: min(46rem, calc(100vw - 2rem));
max-height: min(44rem, calc(100vh - 4rem));
padding: 0;
color: var(--text-primary);
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
}
.sheet::backdrop { background: rgba(0,0,0,0.55); }
.sheet-head {
display: flex; align-items: center; justify-content: space-between; gap: 1rem;
padding: 1.3rem 1.5rem; border-bottom: 1px solid var(--border);
}
/* Alleen dit deel scrollt, zodat de sluitknop in beeld blijft bij twintig
certificaten. */
.sheet-body {
padding: 0.4rem 1.5rem 1.5rem;
overflow: auto;
max-height: calc(min(44rem, 100vh - 4rem) - 5rem);
scrollbar-width: thin;
scrollbar-color: var(--scrollbar) transparent;
}
/* De knop in de foutmelding "geen certificaat in gebruik". Op een eigen regel,
want in de melding staat een zin die je eerst leest. */
.alert-action { margin-top: 0.7rem; }
/* ── Het instelkader ────────────────────────────────────────────────────── */
.setup > summary { list-style: none; cursor: pointer; display: flex; align-items: baseline; gap: 0.8rem; flex-wrap: wrap; }
.setup > summary::-webkit-details-marker { display: none; }
.setup > summary::marker { content: ''; }
/* Een chevron die meedraait, want zonder het driehoekje van de browser is er
verder niets dat zegt dat dit open kan. */
.setup > summary::after {
content: ''; margin-left: auto; width: 0.5rem; height: 0.5rem;
border-right: 2px solid var(--text-sec); border-bottom: 2px solid var(--text-sec);
transform: rotate(45deg); transition: transform 0.2s var(--ease-out);
}
.setup[open] > summary::after { transform: rotate(-135deg); }
.setup-form { min-width: 0; }
.setup-value {
display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
margin: 0.45rem 0 0.55rem;
}
/* De wallets onder de vorm die ze vragen. Naam en vindplaats op één regel, want
het gaat om het herkennen van je eigen wallet en niet om een handleiding. */
.setup-wallets { font-size: 0.75rem; line-height: 1.7; color: var(--text-sec); }
.setup-wallets strong { color: var(--text-primary); font-weight: 600; }
.hidden { display: none !important; }
/* Onder deze breedte past een regel niet meer naast zijn label. Alles wat op een
@@ -413,14 +525,9 @@ body {
.log-event { width: auto; }
.log-detail { flex-basis: 100%; }
/* Op één kolom staat er geen kader meer naast om de hoogte van te lenen, dus
hier neemt het log wél zijn eigen hoogte. Met een plafond, want anders wordt
een etmaal aan regels één lange pagina; daarboven scrolt het blok zelf. */
.log { flex: 0 0 auto; min-height: 0; max-height: 60vh; }
/* De tegels op elkaar, en dan neemt de reactietijd geen twee kolommen meer. */
.stat-grid { grid-template-columns: 1fr; }
.stat-wide { grid-column: auto; }
/* Het plafond blijft, de bodem niet: op een telefoon is 110 pixels aan lege
loghoogte zonde van het scherm. */
.log { min-height: 0; }
}
@media (max-width: 560px) {
@@ -456,9 +563,41 @@ body {
tests/test_server_start_zonder_certificaat.py houdt ze gelijk. -->
<p class="t-body-sm">Your own node from anywhere, without waiting for Tor <span class="head-version">v${APP_VERSION}</span></p>
</div>
<button class="btn" id="theme-toggle" type="button" title="Switch between light and dark">
<span id="theme-label">Light</span>
<!-- Het menu rechtsboven. Hier stond een losse knop voor licht en donker; die
is er op verzoek van de gebruiker (27-08-2026) in gegaan, samen met het
certificaatbeheer. Wat hier staat is wat je zelden aanraakt, en de kop
houdt daardoor niets meer dan het merk en de tagline.
Met details/summary en niet met eigen JavaScript, want dan werkt openen
en sluiten met het toetsenbord zonder dat wij daar iets voor doen. Wat er
wél bij moet is sluiten op Escape en bij een klik ernaast; dat staat in
het script, want dat kent details van zichzelf niet. -->
<details class="menu" id="menu">
<!-- Drie punten en geen hamburger, op aanwijzing van de gebruiker
(27-08-2026): dat is wat umbrelOS bij zijn eigen apps toont, en een
pagina die in dat dashboard hangt hoort daar niet van af te wijken.
Als tekst en niet als SVG, want dan schaalt hij mee met de lettergrootte
van de gebruiker. Middelste punten (U+00B7) en geen gewone punten: die
staan op de basislijn en zakken dus naar de onderrand van de knop,
terwijl deze vanzelf op halve hoogte staan. Hetzelfde teken dat elders
op deze pagina de velden in een keuzeregel scheidt. -->
<summary class="btn menu-button" title="Settings" aria-label="Settings">···</summary>
<div class="menu-panel">
<button class="menu-item" id="menu-certs" type="button">
<span>Certificates</span>
<span class="menu-meta" id="menu-certs-meta"></span>
</button>
<!-- De regel toont de huidige stand en niet de handeling. De losse knop
hiervoor deed het andersom, met "Light" terwijl de pagina donker was.
Als knop klopte dat, want dat was waar je heen ging; als menuregel
leest het als een bewering over hoe het nú staat, en dan is het fout. -->
<button class="menu-item" id="menu-theme" type="button">
<span>Appearance</span>
<span class="menu-meta" id="theme-label">Dark</span>
</button>
</div>
</details>
</header>
<!-- Meldingen. Alle drie standaard verborgen; het script bepaalt welke past. -->
@@ -499,6 +638,13 @@ body {
<span>
<span class="alert-title">No certificate in use, so TLS is not running</span>
<span class="alert-body" id="alert-nocert-body"></span>
<!-- Deze knop is geen extraatje maar de eis die de nieuwe indeling zelf
oproept. De certificaatkeuze zit sinds 27-08-2026 achter het menu, en
dit is precies de toestand waarin een gebruiker die keuze móet kunnen
vinden: een verse installatie heeft er nog geen. Dat is de klem van
0.0.3 in een andere vorm, en die komt niet terug. Het menu is de weg
terug voor later, deze knop is de weg erheen nu. Zie PLAN.md 4a2. -->
<button class="btn alert-action" id="alert-nocert-open" type="button">Choose a certificate</button>
</span>
</div>
@@ -510,38 +656,34 @@ body {
schermen stapelt hij precies zo. -->
<div class="grid">
<section class="card col-5">
<!-- Vijf widgets, alle vijf hetzelfde soort ding: een titel, één waarde, en
hooguit één onderregel. Opgegeven door de gebruiker op 27-08-2026 en
uitgeschreven in PLAN.md 4a2.
Hier stonden een kader met drie lijstregels voor de Electrum-server en
daarnaast twee tegels. Dat vroeg twee kunstjes die nu vervallen: de tegels
moesten meerekken met de hoogte van het kader ernaast, en de reactietijd
nam twee kolommen om de sparkline ruimte te geven. Vijf gelijke widgets
hebben geen buur om zich aan te meten.
De volgorde is die van het lezen: eerst waar je verkeer heen gaat, dan of
dat werkt, dan het certificaat, en als laatste of onze eigen voordeur
antwoordt. -->
<div class="row1">
<div class="card">
<div class="card-head">
<h2 class="t-h3">Electrum server</h2>
</div>
<div class="list-item">
<div>
<div class="list-item-title">Address</div>
<div class="list-item-sub">Chosen in the umbrelOS settings, not in this app</div>
<div class="stat-body">
<div class="stat-val text unknown" id="backend-name">unknown</div>
<!-- Het adres als onderregel en niet als eigen lijstregel. De twee andere
regels die hier stonden zijn weg: "Chosen in the umbrelOS settings"
was uitleg bij een instelling die je hier toch niet wijzigt, en "Last
checked" staat al onder de blokhoogte als "as of ... ago". -->
<div class="stat-sub mono" id="backend-address">${APP_ELECTRS_NODE_IP}:${APP_ELECTRS_NODE_PORT}</div>
</div>
<div class="list-item-val mono" id="backend-address">${APP_ELECTRS_NODE_IP}:${APP_ELECTRS_NODE_PORT}</div>
</div>
<div class="list-item">
<div>
<div class="list-item-title">Implementation</div>
<div class="list-item-sub">Recognised by address; shown as unknown if it has moved</div>
</div>
<div class="list-item-val unknown" id="backend-name">unknown</div>
</div>
<div class="list-item">
<div>
<div class="list-item-title">Last checked</div>
<div class="list-item-sub">Written by the proxy itself, not measured by this page</div>
</div>
<div class="list-item-val unknown" id="backend-checked">unknown</div>
</div>
</section>
<!-- De drie getallen die zeggen of het goed gaat. Precies zo opgebouwd als elk
ander kader op deze pagina: een card-head met een titel erin, dan de inhoud.
Ze hadden eerst een eigen kleine grijze kop in kapitalen, en dat maakte ze
een apart soort ding zonder dat daar een reden voor was. -->
<div class="stat-grid col-7">
<div class="card">
<div class="card-head">
<h2 class="t-h3">Block height</h2>
@@ -551,13 +693,8 @@ body {
<div class="stat-sub" id="stat-height-sub"></div>
</div>
</div>
<!-- Twee kolommen breed. Hiernaast stond een derde tegel met het aantal dagen
dat het certificaat nog geldig is, en die is er op verzoek van de
gebruiker uit (20-08-2026): de kaart Certificate hieronder zegt hetzelfde
en noemt de datum erbij, en bij minder dan dertig dagen staat er een
melding bovenaan de pagina. De vrijgekomen ruimte gaat hierheen, want de
sparkline is het enige op deze rij dat er iets mee doet. -->
<div class="card stat-wide">
<div class="card">
<div class="card-head">
<h2 class="t-h3">Backend response</h2>
</div>
@@ -566,18 +703,66 @@ body {
<svg class="spark" id="spark" viewBox="0 0 100 34" preserveAspectRatio="none" aria-hidden="true"></svg>
</div>
</div>
</div>
<section class="card col-5">
<!-- Het certificaat als getal en niet als kaart. Kiezen, uploaden en de
details zitten sinds 27-08-2026 in de dialoog achter het menu; wat hier
staat is wat je in één oogopslag wilt weten. Het aantal dagen tot
vernieuwing stond hier eerder als tegel en is er op 20-08-2026 uit
gegaan omdat de kaart eronder hetzelfde zei. Die kaart is er nu niet
meer, dus het getal komt terug. -->
<div class="card">
<div class="card-head">
<h2 class="t-h3">Certificate</h2>
</div>
<div class="stat-body">
<div class="stat-val unknown" id="cert-days">unknown</div>
<div class="stat-sub mono" id="cert-domain">unknown</div>
</div>
</div>
<!-- De zelfcontrole, gebouwd in 0.0.16. Dit is de enige widget die over deze
app zelf gaat en niet over iets waar hij naar kijkt.
Let op het woord dat hier NIET staat: "reachable". De agent verbindt van
container naar container, dus een router die de poort niet meer
doorstuurt leest hier als in orde. Wat er gemeten is, is de voordeur zelf
en niet de weg ernaartoe; de onderregel zegt dat er ook bij. -->
<div class="card">
<div class="card-head">
<h2 class="t-h3">Gateway</h2>
</div>
<div class="stat-body">
<div class="stat-val text unknown" id="self-state">unknown</div>
<div class="stat-sub" id="self-sub"></div>
</div>
</div>
</div>
<!-- De dialoog met alles wat met certificaten te maken heeft: de huidige, de
keuze, en het uploaden. Te openen uit het menu rechtsboven en uit de
foutmelding bovenaan.
Met het dialog-element en niet met een eigen overlay: focus, Escape en het
afschermen van de pagina erachter zitten erin.
Hij staat binnen het raster en dat maakt in de praktijk niets uit: dicht is
een dialog display:none, en open via showModal() staat hij in de top layer,
dus in geen van beide gevallen is hij een rastercel. Alleen de terugvalroute
voor een browser zonder showModal zet hem gewoon in de stroom, en daarvoor
staat er in .sheet een grid-column op de volle breedte. -->
<dialog id="cert-dialog" class="sheet">
<form method="dialog" class="sheet-head">
<h2 class="t-h3">Certificates</h2>
<button class="btn" value="close" aria-label="Close">Close</button>
</form>
<div class="sheet-body">
<div class="list-item">
<div>
<div class="list-item-title">In use</div>
<div class="list-item-sub" id="cert-source">source unknown</div>
</div>
<div class="list-item-val mono unknown" id="cert-domain">unknown</div>
<div class="list-item-val mono unknown" id="cert-dialog-domain">unknown</div>
</div>
<div class="list-item">
<div><div class="list-item-title">Valid until</div></div>
@@ -597,11 +782,9 @@ body {
komt uit de hostnaam in het certificaat zelf, en dat is ook precies
waarom er geen padtrucs mogelijk zijn.
Hier en niet onder de keuzeknop, op verzoek van de gebruiker
(20-08-2026). Twee redenen: het spaart een horizontale lijn uit, want de
rij hierboven trekt er al een, en het is de logische volgorde. Wat je
uploadt komt in de keuzelijst hieronder terecht, dus eerst toevoegen en
dan kiezen. -->
Boven de keuzelijst en niet eronder, op verzoek van de gebruiker
(20-08-2026): wat je uploadt komt in die lijst terecht, dus eerst
toevoegen en dan kiezen. -->
<details class="upload">
<summary>Upload your own certificate</summary>
<p class="t-caption" style="margin: 0.5rem 0 0.8rem;">
@@ -629,11 +812,10 @@ body {
<span class="t-label">Choose a certificate</span>
<span class="t-caption" id="cert-count"></span>
</div>
<!-- Eén regel, om twee redenen. Het scheelt hoogte, en dit kader bepaalt de
hoogte van de rij. En de oude tekst noemde Nginx Proxy Manager als bron,
terwijl die mount nog uitgecommentarieerd staat in de compose omdat het
pad niet geverifieerd is; wat er stond kon dus niet waar zijn. Waar een
certificaat vandaan komt staat toch al in de optie zelf. -->
<!-- Eén regel. De oude tekst noemde Nginx Proxy Manager als bron, terwijl die
mount nog uitgecommentarieerd staat in de compose omdat het pad niet
geverifieerd is; wat er stond kon dus niet waar zijn. Waar een certificaat
vandaan komt staat toch al in de optie zelf. -->
<p class="t-caption" style="margin-bottom: 0.6rem;">
Pick the one matching the name your wallet connects to.
</p>
@@ -642,17 +824,20 @@ body {
<button class="btn btn-solid" id="cert-apply" type="button" disabled>Use this certificate</button>
<span class="t-caption" id="cert-apply-note"></span>
</div>
</section>
</div><!-- /sheet-body -->
</dialog>
<!-- Voorstel, nog niet besloten. De agent stelt de regels samen uit de
stream-access-log van nginx. Bewust zonder client-IP en zonder bronpoort:
dat is precies het soort gegeven dat deze app dichtzet, en het is niet
nodig om te zien of het werkt.
Staat rechts en is het brede kader van de twee: logregels zijn monospace en
hebben breedte nodig, en zo valt de kolomgrens samen met die van de rij
erboven in plaats van ertegenin. -->
<section class="card col-7">
Over de volle breedte sinds 27-08-2026. Het stond naast het
certificaatkader, en die twee moesten daarvoor samen groeien en krimpen;
dat was de hele inhoud van 0.0.13. Nu de certificaatkeuze in een dialoog
zit, is er geen buur meer en vervalt die koppeling. Logregels zijn
monospace en hadden de breedte toch al het hardst nodig. -->
<section class="card col-full">
<div class="card-head">
<h2 class="t-h3">Activity log</h2>
</div>
@@ -668,27 +853,48 @@ body {
kopieerknop zat eerst in een eigen kader erboven; dat was hetzelfde kader
twee keer, dus het is hierin opgegaan. -->
<section class="card col-full">
<div class="card-head">
<h2 class="t-h3">Setting up your wallet</h2>
</div>
<!-- Dichtgeklapt, op verzoek van de gebruiker (27-08-2026). Dit kader heb je
één keer nodig en daarna nooit meer, en open nam het het onderste derde
deel van de pagina.
<!-- Hier stond het verbindingsadres nog een keer groot, met een kopieerknop.
Weg op verzoek van de gebruiker (19-08-2026), en zonder verlies: datzelfde
adres staat hieronder bij elke client, in de vorm die díe client wil, en
daar zit de kopieerknop ook. -->
<p class="t-caption" style="margin: 0 0 1.4rem;">
Er staan nu twee kopieerknoppen in plaats van één per wallet. Dat draait
een besluit van 19-08-2026 terug, en dat is opgeschreven in PLAN.md 4a2
zodat het niet per ongeluk hersteld wordt: er zijn feitelijk maar twee
vormen van hetzelfde adres, dus tien knoppen waren negen keer dezelfde
handeling.
Wat daarbij niet verloren mocht gaan is wélke wallet welke vorm wil. De
wallets staan daarom onder de vorm die ze vragen en niet als één lijst
ernaast; zonder die groepering staat er wel een adres, maar niet meer
waar het bij hoort. -->
<details class="setup" id="setup">
<summary class="setup-summary">
<span class="t-h3">Setting up your wallet</span>
<span class="t-caption">The address to paste, per wallet</span>
</summary>
<p class="t-caption" style="margin: 1rem 0 1.2rem;">
Use <strong>SSL</strong> as the connection type. If you connect from outside your own network, your
router may forward a different external port to this one.
</p>
<div class="clients">
<div>
<div class="t-label" style="margin-bottom: 0.4rem;">Paste as one line</div>
<div id="clients-oneline"></div>
<div class="setup-form">
<div class="t-label">Paste as one line</div>
<div class="setup-value">
<span class="list-item-val mono unknown" id="oneline-value">unknown</span>
<button class="btn" id="oneline-copy" type="button">Copy</button>
</div>
<div>
<div class="t-label" style="margin-bottom: 0.4rem;">Host and port separately, with SSL turned on</div>
<div id="clients-split"></div>
<div class="setup-wallets" id="oneline-wallets"></div>
</div>
<div class="setup-form">
<div class="t-label">Host and port separately, with SSL turned on</div>
<div class="setup-value">
<span class="list-item-val mono unknown" id="split-value">unknown</span>
<span class="t-caption" id="split-port"></span>
<button class="btn" id="split-copy" type="button">Copy</button>
</div>
<div class="setup-wallets" id="split-wallets"></div>
</div>
</div>
@@ -697,6 +903,7 @@ body {
talks to Bitcoin Core directly. Hardware wallets connect through one of the apps above rather than
on their own.
</p>
</details>
</section>
</div><!-- /grid -->
@@ -749,15 +956,55 @@ body {
function applyTheme(theme) {
root.setAttribute('data-theme', theme);
document.getElementById('theme-label').textContent = theme === 'dark' ? 'Light' : 'Dark';
// De huidige stand en niet waar je heen gaat. Als losse knop stond hier het
// omgekeerde, en dáár klopte dat: een knop belooft wat er gebeurt als je hem
// indrukt. Een menuregel met een waarde erachter leest als een bewering over
// hoe het nu staat, en dan is dezelfde tekst ineens fout.
document.getElementById('theme-label').textContent = theme === 'dark' ? 'Dark' : 'Light';
}
document.getElementById('theme-toggle').addEventListener('click', function () {
document.getElementById('menu-theme').addEventListener('click', function () {
var next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
applyTheme(next);
try { localStorage.setItem('eg-theme', next); } catch (e) { /* privémodus; niet erg */ }
});
// ── Het menu ────────────────────────────────────────────────────────────
// details opent en sluit zichzelf, ook met het toetsenbord. Wat het niet doet
// is sluiten bij een klik ernaast of op Escape, en dat is bij een menu wel de
// verwachting. Vandaar deze twee, en niet meer dan deze twee.
var menu = el('menu');
function closeMenu() { menu.removeAttribute('open'); }
document.addEventListener('click', function (e) {
if (menu.hasAttribute('open') && !menu.contains(e.target)) { closeMenu(); }
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && menu.hasAttribute('open')) { closeMenu(); }
});
// ── De certificaatdialoog ───────────────────────────────────────────────
// Twee wegen erheen, en dat is met opzet. Het menu is de weg voor wie later
// iets wil wijzigen; de knop in de foutmelding is de weg voor wie nog nooit een
// certificaat gekozen heeft, en dat is de enige toestand waarin deze pagina
// écht iets van je vraagt. Zie PLAN.md 4a2.
var sheet = el('cert-dialog');
function openSheet() {
closeMenu();
if (typeof sheet.showModal === 'function') {
sheet.showModal();
} else {
// Zonder showModal geen overlay en geen focusval, maar wel een zichtbare
// dialoog. Beter dan een knop die niets doet.
sheet.setAttribute('open', '');
}
}
el('menu-certs').addEventListener('click', openSheet);
el('alert-nocert-open').addEventListener('click', openSheet);
// ── Hulpjes ─────────────────────────────────────────────────────────────
function el(id) { return document.getElementById(id); }
@@ -812,7 +1059,7 @@ body {
var backend = s.backend || {};
var cert = s.certificate || {};
drawClients(tls.domain, tls.port);
drawSetup(tls.domain, tls.port);
// Blokhoogte
if (typeof backend.height === 'number') {
@@ -826,14 +1073,29 @@ body {
}
drawSpark(s.history || []);
// Certificaat
// Certificaat. Het aantal dagen tot vernieuwing staat sinds 27-08-2026 weer
// als widget op de pagina; het stond er tot 20-08-2026 als tegel en ging er
// toen uit omdat de kaart eronder hetzelfde zei. Die kaart is nu een dialoog,
// dus de dubbeling is weg en het getal hoort weer op de pagina.
var daysLeft = null;
// daysLeft stuurt alleen nog de melding bovenaan; de tegel die dit getal
// toonde is eruit. De kaart hieronder noemt de datum, en dat is wat je wilt
// weten als je hier komt kijken.
var days = el('cert-days');
days.classList.remove('bad', 'text');
if (cert.not_after) {
daysLeft = Math.floor((cert.not_after - nowSec) / 86400);
put('cert-until', whenLocal(cert.not_after));
if (daysLeft < 0) {
put('cert-days', 'expired');
days.classList.add('bad', 'text');
} else {
put('cert-days', daysLeft + (daysLeft === 1 ? ' day' : ' days'));
}
} else if (!cert.selected) {
// Niet 'unknown': er is niets onbekends aan: er is er geen. De melding
// bovenaan zegt wat je eraan doet, dus hier volstaat het woord.
put('cert-days', 'none');
days.classList.add('text');
} else {
put('cert-days', null);
}
// Geen actief certificaat betekent geen TLS, en dat is het ergste wat deze
// app kan overkomen. Dus geen subtiele grijze tekst maar de foutmelding
@@ -848,15 +1110,23 @@ body {
el('cert-source').textContent = cert.source
? 'from ' + cert.source + (cert.reason ? ' · ' + cert.reason : '')
: (cert.reason || 'source unknown');
// Twee keer dezelfde naam: als onderregel van de widget en in de dialoog. Dat
// is geen dubbeling maar twee plekken die elkaar niet zien; wie de dialoog
// opent, hoort er niet voor terug te hoeven bladeren.
put('cert-domain', tls.domain);
put('cert-dialog-domain', tls.domain);
put('cert-reload', whenLocal(cert.last_reload));
el('menu-certs-meta').textContent = cert.selected ? (tls.domain || 'in use') : 'none in use';
renderCertAlert(daysLeft);
drawCertChoices(s.certificates, cert.selected, nowSec);
// Backend
// Backend. "Last checked" stond hier als derde lijstregel en is er op
// 27-08-2026 uit: hetzelfde staat onder de blokhoogte als "as of ... ago",
// en een widget met drie onderteksten is geen widget meer.
put('backend-name', backend.name);
if (backend.address) { put('backend-address', backend.address); }
put('backend-checked', age !== null ? agoText(age) : null);
drawSelfCheck(tls.self_check);
// Hier stond een badge met "answered 7 seconds ago". Die is eruit op verzoek
// van de gebruiker (20-08-2026), en met reden: de rij "Last checked" eronder
@@ -901,6 +1171,54 @@ body {
}
}
// ── De zelfcontrole ─────────────────────────────────────────────────────
// Gebouwd in de agent in 0.0.16, hier getoond sinds de tweede indeling.
//
// Let op het woord dat hier niet staat: "reachable". De agent verbindt van
// container naar container, dus dit zegt niets over de poortmapping naar de
// host of over de doorstuurregel in de router. "Answering" met daaronder "from
// inside the app" is wat er gemeten is; alles wat verder gaat, zou de tegel
// laten liegen op precies het punt waar iemand hem gelooft.
var SELF = {
ok: { label: 'Answering', bad: false },
failed: { label: 'Not answering', bad: true },
'wrong-certificate': { label: 'Wrong certificate', bad: true },
// Geen certificaat is de normale begintoestand van een verse installatie en
// dus niet rood; de melding bovenaan zegt al wat eraan te doen is.
off: { label: 'Off', bad: false },
pending: { label: 'Checking', bad: false }
};
function drawSelfCheck(self) {
var node = el('self-state');
var sub = el('self-sub');
var kind = self && self.state ? SELF[self.state] : null;
if (!kind) {
put('self-state', null);
node.classList.remove('bad');
// Dit is de toestand op een installatie met een oudere agent. Zwijgen zou
// hier als "in orde" lezen.
sub.textContent = self ? 'Reported as ' + self.state : 'The agent has not reported this yet.';
return;
}
put('self-state', kind.label);
node.classList.toggle('bad', kind.bad);
if (self.state === 'ok') {
sub.textContent = 'answered in ' + self.ms + ' ms, from inside the app';
} else if (self.state === 'off') {
sub.textContent = 'no certificate selected, so nothing is listening';
} else if (self.state === 'wrong-certificate') {
sub.textContent = 'not the certificate you selected; a reload may have failed';
} else if (self.state === 'pending') {
sub.textContent = 'measuring again after the certificate change';
} else {
sub.textContent = self.error ? String(self.error) : 'no reason given';
}
}
// ── Certificaatkeuze ────────────────────────────────────────────────────
// De lus schrijft in status.json wat hij in de drie gemounte mappen gevonden
// heeft. De keuze gaat er met een PUT naartoe; de lus leest dat bestand en
@@ -1082,74 +1400,56 @@ body {
});
});
// ── Clientregels ────────────────────────────────────────────────────────
// Zonder domein en poort krijgt elke rij hetzelfde onbekend-veld als de rest
// van de pagina. Een half ingevulde regel die iemand toch kopieert, is erger
// dan een lege.
function drawClients(domain, port) {
fill('clients-oneline', CLIENTS_ONELINE, function () {
return (domain && port) ? domain + ':' + port + ':s' : null;
});
fill('clients-split', CLIENTS_SPLIT, function () {
return domain ? domain : null;
}, port ? 'port ' + port : null);
// ── Het instelkader ─────────────────────────────────────────────────────
// Twee vormen van hetzelfde adres, dus twee kopieerknoppen (27-08-2026). Hier
// stond een kopieerknop per wallet, tien in totaal, en dat waren negen keer
// dezelfde handeling. Dat draait een besluit van 19-08-2026 terug; PLAN.md 4a2
// zegt waarom, zodat het niet per ongeluk hersteld wordt.
//
// De wallets blijven wél onder de vorm staan die ze vragen. Dat is de
// voorwaarde bij die vereenvoudiging: welke wallet welk formaat wil, is precies
// wat dit kader te vertellen heeft, en één lijst naast twee losse knoppen
// gooit dat weg.
//
// Zonder domein en poort blijft de waarde op 'unknown' staan, net als elders op
// de pagina. Een half ingevuld adres dat iemand toch kopieert is erger dan een
// leeg veld, dus de kopieerknop staat dan uit.
var oneLineValue = null;
var splitValue = null;
function drawSetup(domain, port) {
oneLineValue = (domain && port) ? domain + ':' + port + ':s' : null;
splitValue = domain || null;
put('oneline-value', oneLineValue);
put('split-value', splitValue);
el('split-port').textContent = port ? 'port ' + port : '';
el('oneline-copy').disabled = !oneLineValue;
el('split-copy').disabled = !splitValue;
}
function fill(containerId, clients, valueFor, extra) {
var box = el(containerId);
// Eén keer getekend, want deze lijst verandert nooit; hij komt uit
// Referenties/Clients.md. De vindplaats staat erbij en dat is bewust: het gaat
// erom je eigen wallet te herkennen, en dan is "waar zit die instelling" de
// volgende vraag.
function walletList(id, clients) {
var box = el(id);
box.innerHTML = '';
for (var i = 0; i < clients.length; i++) {
var value = valueFor();
var row = document.createElement('div');
row.className = 'list-item';
var left = document.createElement('div');
var title = document.createElement('div');
title.className = 'list-item-title';
title.textContent = clients[i].name;
var sub = document.createElement('div');
sub.className = 'list-item-sub';
sub.textContent = clients[i].where;
left.appendChild(title);
left.appendChild(sub);
var right = document.createElement('div');
right.style.display = 'flex';
right.style.alignItems = 'center';
right.style.gap = '0.6rem';
var val = document.createElement('span');
val.className = 'list-item-val mono' + (value ? '' : ' unknown');
val.textContent = value ? value : 'unknown';
right.appendChild(val);
if (extra) {
var note = document.createElement('span');
note.className = 'list-item-meta t-caption';
note.textContent = extra;
right.appendChild(note);
}
if (value) {
right.appendChild(copyButton(value));
}
row.appendChild(left);
row.appendChild(right);
var name = document.createElement('strong');
name.textContent = clients[i].name;
row.appendChild(name);
row.appendChild(document.createTextNode(' · ' + clients[i].where));
box.appendChild(row);
}
}
function copyButton(value) {
var b = document.createElement('button');
b.type = 'button';
b.className = 'btn';
b.textContent = 'Copy';
b.addEventListener('click', function () {
copy(value, b);
el('oneline-copy').addEventListener('click', function () {
if (oneLineValue) { copy(oneLineValue, el('oneline-copy')); }
});
el('split-copy').addEventListener('click', function () {
if (splitValue) { copy(splitValue, el('split-copy')); }
});
return b;
}
// ── Grafiekjes ──────────────────────────────────────────────────────────
function drawSpark(history) {
@@ -1327,7 +1627,10 @@ body {
});
}
drawClients(null, null);
walletList('oneline-wallets', CLIENTS_ONELINE);
walletList('split-wallets', CLIENTS_SPLIT);
drawSetup(null, null);
drawSelfCheck(null);
drawActivity(null);
drawCertChoices(null, null, Math.floor(Date.now() / 1000));
load();
+17 -14
View File
@@ -9,7 +9,7 @@ manifestVersion: 1
id: whatsnext-electrum-gate
category: bitcoin
name: Electrum Gate
version: "0.0.16"
version: "0.0.17"
tagline: Your own node from anywhere, without waiting for Tor
description: >-
The privacy win is already yours: you run the Electrum server. A public one gets asked for
@@ -56,28 +56,31 @@ description: >-
# staan. In 0.0.9 stond er daardoor drie keer "Earlier releases" en twee keer
# dezelfde 0.0.4-regel. De toets let er nu op.
releaseNotes: >-
The app now checks its own front door. Until this release it watched whether your Electrum server was
answering, but never whether the gateway itself was, which is the one thing it exists to do.
The dashboard has been rebuilt around five small tiles: your Electrum server, block height, response
time, your certificate, and whether the gateway itself is answering. That last one is the check added
in the previous release, which until now had nowhere to show.
Every few minutes it opens a connection to its own TLS port, completes the handshake, and compares the
certificate it gets back with the one you picked. That last part catches something nothing else did: a
certificate change that nginx never actually applied, which looks fine from the outside and fails at
your wallet.
Choosing and uploading certificates moved into a dialog behind the menu at the top right, together with
the light and dark setting. Nothing is harder to find: when no certificate is in use, the message at the
top of the page still takes you straight there, and that message is what a fresh install shows.
For now the result appears as a line in the activity log when it changes. A tile on the dashboard
follows in a later release. The check runs every few minutes rather than every minute, and its own
connections are kept out of the activity log, so the log stays about your wallets.
What it does not tell you: whether your gateway can be reached from outside. This check runs inside the
app, so a router that stopped forwarding the port still reads as fine here.
The activity log now runs the full width of the page. "Setting up your wallet" starts collapsed and is
much shorter. There are only two forms of the same address, so there are two copy buttons instead of one
per wallet, with the wallets listed under the form each of them asks for.
Earlier releases:
0.0.16 taught the app to check its own front door: it connects to its own TLS port, completes the
handshake, and compares the certificate it gets back with the one you picked. That catches a
certificate change that never actually took effect, which looks fine from the outside and fails at your
wallet. It does not tell you whether your gateway can be reached from outside; the check runs inside the
app.
0.0.15 moved the app store this app comes from to a new address, and pointed every link in this listing
there. If you added the store at its old address, remove it in umbrelOS and add the new one.