12 Commits (37e191424674ed63f151e795ac0ac3b55ebddf4d)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
b3ddc44256 |
Zweite Fix-Welle: kein „beendet" ueber einem laufenden Aufbau
tests / pest (push) Waiting to run
Details
tests / assets (push) Waiting to run
Details
tests / release (push) Blocked by required conditions
Details
Zwei Nachwirkungen der ersten Fix-Welle (
|
|
|
|
c9fef59983 |
Fix-Welle: die Kuendigung sagt jedem nur das, was fuer ihn stimmt
Vier Befunde aus dem Gesamt-Review, und alle vier hatten dieselbe Wurzel:
`export_wish` fuehrt drei Zustaende, und jede Stelle, die den Kunden
ansprach, kannte nur zwei.
K1 — Der Streifen im Dashboard trug einen Schalter, und `(bool) null` ist
`false`. Wer vor dieser Ausrollung gekuendigt hat, las unter dem Streifen
„Kein Export gewuenscht" — als waere das seine eigene Antwort. Jetzt stehen
dort dieselben zwei Auswahlfelder wie im Kuendigungsdialog: gleiche Frage,
gleiche Form, und ein unbeantworteter Zustand markiert schlicht keines von
beiden. Der Satz daneben fragt dann, statt zu behaupten, und sagt, was
passiert, wenn die Frage offen bleibt.
K2 — Die Vertragsseite versprach jedem den Export, auch dem, den der Dialog
eine Sekunde vorher mit einem bewussten „Nein" genau dorthin umgeleitet
hatte. Drei Fassungen statt einer, an `export_wish` gebunden. Die Ja-Fassung
verspricht dabei nicht mehr den Export selbst, sondern dass der Wunsch
vermerkt ist und sich jemand meldet — den Export gibt es nicht, und ein
gebundenes, aber weiterhin unhaltbares Versprechen haette den Fehler nur
verschoben.
Dazu der Zustand danach: eine `ended`-Instanz holt Dashboard::render() nicht
mehr, und der Kunde fiel in denselben Zweig wie jemand, der noch nie etwas
bestellt hat — „Ihre Cloud wird eingerichtet." samt „Paket buchen", am Tag,
an dem ihm die Adresse eingezogen wurde. Der Fall hat jetzt seinen eigenen
Kasten, mit dem Datum, an dem das Paket endete.
W1 — Die Erinnerungsmail behauptete im Praesens, wir bereiteten bereits einen
Export vor. Der Satz sagt jetzt, was stimmt. Und die Antwort hatte in der
ganzen Konsole keinen einzigen Leser: ein „Ja" landete in einer Spalte, die
niemand je zu Gesicht bekam. Ueber der Instanzliste steht deshalb ein
Abschnitt „Datenexport bestellt" — wer, und bis wann. Nicht als Plakette in
der Zeile, weil die Liste geblaettert ist und ein alter Eintrag auf Seite acht
saesse; nicht auf der Uebersicht, weil ein Hinweis, den nichts je wieder
abraeumen kann, Moebel waere.
W3 — Die einzige Pruefung zur Anzeige der Antwort konnte nicht fehlschlagen:
`x-ui.switch` rendert beide Woerter und ueberlaesst dem CSS die Auswahl, also
war `assertSee('Kein Export gewuenscht')` bei true, bei false UND bei null
gruen. Nachgewiesen mit einer Wegwerf-Pruefung gegen den alten Streifen:
dreimal derselbe Satz, dreimal gruen. Jetzt drei Pruefungen, je eine pro
Zustand, am `checked`-Attribut der Auswahlfelder.
Zu jedem der vier Punkte wurde der Fix kurz zurueckgedreht und die neue
Zusicherung rot gesehen; die Ergebnisse stehen im Bericht.
Ganze Suite: 2819 gruen, 2 rot — beide fremd. ReadinessPageTest scheitert an
`server.private_key` aus der parallel laufenden Terminal-Arbeit im selben
Baum; HostStepTest ist auf main vorbestehend rot (install-agent.sh traegt
CONTRACT=3, update.sh HOST_STEP_NEEDS=2, beide unveraendert).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
|
|
cfd1481797 |
Ein Streifen im Dashboard sagt, wie lange der Zugang noch steht
Eine Mail sieben Tage vorher kann im Postfach untergehen; die eigene Uebersicht oeffnet der Kunde ohnehin. Der Streifen nennt das Datum, die Restzeit und was danach geschieht — dass der Zugang zur Cloud endet, nicht dass irgendetwas geloescht wird. Das ist die Frist, die ihn betrifft. Und er traegt die Antwort zum Export: wer liest, dass die Zeit laeuft, will es sich im selben Atemzug anders ueberlegen koennen (Aufgabe 4) — bis zum Laufzeitende, danach nicht mehr, geprueft direkt an der Methode und nicht nur am Schalter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
ea643b5e73 |
Prove a custom domain before serving it, and keep proving it
tests / pest (push) Failing after 8m17s
Details
tests / assets (push) Successful in 23s
Details
tests / release (push) Has been skipped
Details
Two things. ── The update screen, still opening twice ─────────────────────────────────── Reported again on 1.3.9, and the cause was not the one fixed in 1.3.8. The agent consumes the request file BEFORE it resolves the release — deliberately, because update.sh may kill the shell and a request left in place would loop — and writes `state: running` only once it has decided to go ahead. In between, the request is gone and the status does not say running yet, so the endpoint honestly answers "nothing is running". The watcher read that as "the run has finished" and reloaded the page: overlay on the click, gone a poll later, 503 after it. The overlay now closes only once the server has BOTH confirmed a run and then stopped reporting it. Before the confirmation, silence means the agent has not got there yet. Bounded at twenty polls so a request the agent refuses does not leave the console covered forever. ── Custom domains, proven and re-proven ───────────────────────────────────── `custom_domain` was a free-text field and everything downstream believed it: the proxy served it, the certificate was issued for it, Nextcloud trusted it. Anyone who pointed any hostname at the platform got somebody else's files under their own name. The proof is a TXT record at _clupilot-challenge.<domain> holding a token only this instance has. Nothing is served until it has been read. Every reader now goes through Instance::address(), which is the one place the decision is made — `custom_domain ?: subdomain` was the hole, written out four times. It is re-read every night at 03:40, because a token checked once can be taken straight back out and a domain that later lapses keeps resolving here. Three consecutive misses before a live domain is withdrawn: one failed lookup is a nameserver having a bad minute, and withdrawing takes a working Nextcloud off its own address. The domain and its token stay on the row so the customer can put the record back rather than start over. Changing the domain mints a NEW token. Reusing it would let somebody who once verified example.com claim any other domain later without touching its DNS — the old record is still sitting there and only the value is compared. The domain is changeable at any time, and removable. Fixing it once set was considered and rejected: adding or moving an address is a proxy entry, a certificate and one line in trusted_domains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
75b4a47768 |
Stop mailing the initial admin password, hold it in the panel until noted
The password no longer travels through third-party mail servers or sits forever in an inbox: it moves onto the instance (encrypted, same as nc_admin_ref) and shows on the dashboard until the customer clicks "Notiert", which deletes it for good. |
|
|
|
85fcc154b8 |
Measure availability, and let a customer move down again
Availability is now counted from our own monitoring answers. Uptime Kuma knows the figure, but the REST bridge in front of it only answers "healthy, yes or no", and waiting for the bridge to grow an endpoint would have left the panel without the number indefinitely. The sync already asks on a schedule; counting the answers gives a percentage that is ours and explainable. An unanswered check is not a failed one — the counter only moves when a real answer arrived, so our monitoring going down does not become the customer's outage on the one figure they are asked to trust. An instance nobody has ever checked shows no availability rather than 100 %. The downgrade path existed nowhere. Going down is not the mirror of going up: it can ask an instance to hold more than the target plan allows. Smaller plans are now listed even when they cannot be taken, with the obstacle in the customer's own numbers — "you have 31 users, this package allows 10" — because a greyed-out button that does not name what is in the way is the thing people ring about. Storage is checked against the last real reading rather than the contractual allowance, or anyone who once bought a large plan could never leave it. The limit is re-checked in the action: a rule enforced only in markup is not enforced. And the smaller corrections from this round: - The cloud tab carried a hardcoded "B" as the instance's initial, and the PHP and MariaDB versions, which tell a tenant nothing they can act on. - "EU — Serverstandort im Angebot festgehalten" under a label already reading "Serverstandort" was two sentences saying nothing. It is the country. - Support promised a reply "binnen 4 Std." with nothing behind it. What is true everywhere else is an answer on the same working day. - The actions column appeared even when no row in the table had an action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
ee5e92c0ed |
Measure what the template draws
The panel could not show the storage ring or the transfer trend because nothing measured them. instance_traffic keeps one row per billing period — right for an allowance, useless as a series — and storage consumption was never sampled at all, so the card could only ever state what was sold rather than what is used. instance_metrics holds one row per instance per day, written by the traffic collector on the visit it already makes. A second scheduler entry against the same Proxmox API would have doubled the load and the failure modes for no gain. Disk usage comes from `df` inside the guest rather than from Proxmox's own disk figure, which reports the allocated image: that would show every customer a full 500 GB from their first day. The rule the whole thing is built on: a reading that could not be taken is not a reading of zero. The disk columns are nullable and left untouched when the guest agent does not answer, so yesterday's figure stands instead of the ring dropping to empty — which would tell a customer their data had vanished. Missing days stay missing in the series rather than being filled with zeroes, which would draw an outage that never happened. Both are covered by tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
21d8dc310a |
One definition of the navigation, and the country instead of the building
The breadcrumb said Übersicht on every page. The sidebar and the breadcrumb each built their own copy of the structure, so the layout had no way to know which entry was current and fell back to the first one. Navigation::portal() and ::console() are now the single definition, and currentLabel() matches on the route name — which matters for the console, whose PATH changes between host-bound and fallback mode while its route names do not. The datacentre name is out of everything a customer sees, for the third time and now at the source rather than in a template. Falkenstein is how an operator places an instance; a customer's processing record names the jurisdiction, and putting the building in customer copy means editing that copy every time the estate grows. Also swept the views onto the new scale: --text-faint is 2.8:1 and a decoration token, but it was carrying table headers, hints and timestamps — text people have to read. All of it moved to --muted, which is 5.0:1 and passes AA. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
6376f5e9fa |
One design system for every surface
The console was a second product. admin-tokens.css held a complete dark set — its own surfaces, its own orange, its own status triad — so every shared component had to work in two worlds, and the two halves of the application drifted apart in ways nobody could see from inside either one. That file now carries density and nothing else: smaller type, tighter rows, tighter corners. Colour comes from one place. The tokens themselves are rebuilt around what the redesign settled on: - Radii scaled to object size (9/11/16/22) instead of one value everywhere, which is what made the interface read as a design-system specification rather than as a product. - Warm, broad, low shadows. A neutral grey drop shadow belongs to a different design language and reads as cold on this ground. - --accent-press, which several rules already referenced and nothing defined. An undefined var() does not make a browser skip the declaration; it computes to , and for background-color that is transparent — the accent button lost its fill on hover and left white text on white. - No serif. A serif headline is what made every page read as a document, which is the impression the whole redesign exists to remove. --font-serif now points at the sans so nothing breaks while call sites are cleaned up. The two shells are one shell with two configurations, and both finally have a mobile navigation: the sidebar used to simply disappear below 900px, leaving no way to move around the application at all on a phone. The customer dashboard is bound to real records — instance, seats, traffic, backups, contract — instead of the fixtures it shipped with. Where something is not measured, storage consumption, it says what was contractually agreed rather than inventing a figure. This is the sheet a customer forwards to their auditor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
c1e81808a7 |
feat(traffic): meter the monthly allowance, show it, throttle instead of blocking
Customers can now see what they have used and what is left, and the service slows down rather than stopping when the allowance is gone — a slow Nextcloud gets a top-up, a dead one gets a cancellation. - instance_traffic keeps one row per instance per month. Proxmox counters are cumulative since the VM last started, so usage is the difference between two samples, and a counter that went backwards means a restart, not a refund. - Outbound is what counts: inbound is free at our providers and egress is what Hetzner's 20 TB per server applies to. - CollectInstanceTraffic samples every 15 minutes, warns once per threshold (80/95 %) rather than on every run, and at 100 % limits the VM's NIC via Proxmox — released again as soon as the customer tops up or the month rolls over. - The dashboard gets a full-width band (it throttles the service, so it is not a tile among tiles) with the top-up offer right next to the warning. - Bytes are formatted in SI units now: allowances are computed in SI, so dividing by 1024 made a 1000 GB plan read as "931 GB". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
|
|
|
499adaff00 |
feat(dashboard): rich overview with Chart.js (customer-portal template)
- Chart.js as an Alpine island (x-ui.chart): configs are PHP arrays; colours use token: strings resolved from CSS design tokens at runtime (R3). Respects reduced-motion; IBM Plex chart defaults. - Tokens: soft --radius-xl (20px), --success-bright for dots/rings/charts. Staggered 'rise' reveal keyframe. Global toast in the app shell. - Overview rebuilt to the customer template: storage doughnut ring, availability sparkline, KPI tiles, upsell, cloud card, interactive onboarding checklist (Alpine), backups, modules (Lucide icons — no emoji, R9), activity feed. - Locale-aware fixture display: Carbon isoFormat dates + Number::format sizes so the EN dashboard is not mixed-language (R16). - 14 Pest tests green; R12 browser: /dashboard 0 console errors with Chart.js; R7 no overflow at 375/768/1280. Reviewed with Codex (R15) — clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
|
|
|
254a7d46a0 |
feat(portal): Fortify auth + Login/2FA/Dashboard + component kit
Backend (Fortify): - laravel/fortify with TOTP two-factor + recovery codes; User uses TwoFactorAuthenticatable; 2FA credentials hidden from serialization. - Views off — pages are full-page class-based Livewire components (R1/R2); Fortify handles POST actions. Home redirect -> /dashboard. v1 scope: login + 2FA only (no public register/reset/passkeys). Seeder gated to local/testing. Component kit (Blade, token-based, a11y): - button, input, checkbox, alert, card, badge, stat-tile, otp-input (Alpine, auto-advance/paste, -safe submit), progress-stepper, nav-item, icon (Lucide), plus layouts/portal-app app-shell (sidebar drawer + topbar + menu). Screens (localized DE/EN, R16): - Login (form -> login.store), Two-factor challenge (OTP + recovery fallback), Dashboard (KPI stat tiles, instance card, provisioning stepper fixtures, activity). Routes English (R13). Tests + verification: - Pest: 14 green (login ok/invalid/throttle, dashboard guard, component render). - R12 browser (Puppeteer, prod assets): /, /login, /two-factor-challenge and the authenticated /dashboard all HTTP 200 with ZERO console errors; login flow verified end-to-end. - Test isolation fixed (force test env over injected .env). - Reviewed with Codex (R15): 4 rounds, all findings fixed, final pass clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |