|
|
|
@@ -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();
|
|
|
|
|