8 Commits (main)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
398028a57d |
Rebuild Add host as a page with one job, and let downloads through the gate
The page was a wall. Six paragraphs of procedure stacked above a form, so the thing the page actually asks for — four fields — sat underneath an essay about what would happen afterwards. It answered everything and showed nothing. It is now built the way the settings page is built, because that page was rebuilt for the same reason and there is no case for a second idiom: an eyebrow, a title, a sticky rail on the left and panels of rows on the right. The rail carries the six steps as two-word labels rather than paragraphs. That is what the question at this moment actually is — where am I, how much is left — and it fits in 232 pixels. The numbers carry the state; a tick beside them would be a second sign for one statement, and a number can be counted. What a step MEANS now appears in the row where it is due, not six times in advance. The two provider steps get a panel of their own above the form, because they have to be done before you save: the one-time code starts expiring the moment you do. Everything after the form waits until there is something to say. After saving the command becomes the page. Full width, its own framed block with the warning in the header strip rather than floating above, and the two remaining steps below it as ordinary rows. Two token bugs went with it. `bg-canvas` does not exist — it was a class that compiled to nothing, which is part of why the block looked wrong. And `text-accent` on white is 2.9:1; the config says in as many words to use accent-text for anything read, so the current step number does. Also fixed, and it would have broken every takeover in production: PublicSiteGate is appended to the whole web group, so while the site is hidden a server in a rescue system fetching the archive would have received the 503 placeholder and piped it into tar. The operator would have seen an unpack error on a machine only reachable through the provider's console, with nothing pointing at a switch in the admin area — and they are by definition neither on a management network nor signed in, since leaving that state is the whole point. Exempted by ROUTE NAME, not by hostname: the docblock rightly warns that exempting by host means trusting a header the caller picks, but that warning is about the entire portal. Behind these two routes are documents that are public anyway and an archive that 404s without a valid one-time code. 2026 tests pass, assets build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
2194ab8079 |
Put the instructions where somebody reads them before they start
The previous commit shipped the guide AFTER the host was created. Its first two steps happen at the provider — order the machine, boot the rescue system — and whoever reads them there has the one-time code already in the clipboard with its 24-hour window running. The instructions were correct and in the wrong place, which is its own kind of wrong. Six steps now, not three, and the two provider ones are named rather than assumed. They are marked "first" so the list does not read as something you can start at the top of and work down: step 2 is half an hour of waiting, and doing it inside a running code is exactly the mistake the page should prevent. All six are visible the whole time, including the done ones and the ones still to come. A guide that shows only the current step cannot answer "how much is left", which is the question somebody has while a server they are paying for sits in a rescue system. It is on the hosts list too, collapsed. Somebody standing there may not have ordered the machine yet — and that is step 1. Requiring them to click "add host" to find out what the procedure is puts the answer behind the action it describes. One component, two places, so the archive address in the instructions is the same string the command line will carry. Two copies of that would drift, and the difference would surface on a server that has already been paid for. The step-4 command box moved into the guide rather than sitting beside it, so after creating a host the whole procedure stays on screen. Somebody stuck at step 5 should not be looking at a page that has become a single box. 1991 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
903ebdd2b2 |
Give the operator one line to copy and three steps around it
The console could describe a takeover it had no way to start. This is the vertical slice that closes that: a one-time code, the archive the rescue system fetches, and the page that says what to do with both. The command carries EVERYTHING the script needs before the tunnel exists, because there is nothing to fetch — that is the whole point of spec §5. Which means CluPilot generates the WireGuard keypair and admits the peer at the hub before the machine has ever booted, and hands the private half over in the line. It is worthless within minutes: task 9 of the script replaces it with one generated on the machine. Shown exactly once. The database holds only the code's hash and never the private key, so leaving the page does not bring it back — it mints a new code, which invalidates the old one. That is deliberate: a glance at somebody's screen should be worth nothing an hour later. Which is also why save() no longer redirects. Sending the operator to the host detail page sends them away from the only value they need, and an existing test asserted that redirect — it now asserts the opposite, with the reason written next to it. SHA-256 rather than bcrypt for the code, and the reason is not speed. Both endpoints have to FIND the host by the code; with bcrypt that means trying every row. The code is 32 characters of CSPRNG output, so it has the entropy that stretching exists to manufacture. resolve() and claim() are separate because progress reports arrive BEFORE registration. If reporting consumed the code, a host could never register after its first message. The archive URL is always the public hostname. The console runs under admin.…, but this line executes on a machine that must not reach the admin area — it is locked down for exactly that reason — so route() from the console would emit a hostname that 404s on a server only reachable through the provider's console. The page warns about missing tunnel settings BEFORE the host is created, not after. An empty hub key produces a line that looks clean, copies fine, runs, and ends in a tunnel that never handshakes — discovered on the machine, after somebody has already paid for it. The three steps lead with the rescue system, because that is the one nobody knows by heart, and it says enabling is not the same as booting into it — the script refuses a running production machine, which is what a half-done switch looks like from the inside. 1986 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
d65ab71029 |
Let a datacenter code be corrected while nothing depends on it, and say which building
The code was immutable, which made a typo permanent. It cannot be freely mutable either, and not only because hosts.datacenter references it: each host's DNS name was minted from it and registered at the provider, the machine's own hostname was set from it as it was built, the counter handing out those numbers is keyed by it, and every past order stores it as plain text with no foreign key to follow. Renaming a code with hosts in it is not a rename — it is a datacenter called hel full of machines called fsn-01. So it is editable exactly while nothing references it, which is the case that matters: a typo noticed shortly after creating one. Once a host or an order depends on it the field is disabled and says what is holding it, with the numbers. The lock is recomputed from the database on every save, never read back from the property the browser returns. Alongside it, an optional free-text field for the specific datacenter — fsn-dc-15. A provider runs several within one location, on separate power and separate uplinks, and two entries both reading "Falkenstein (fsn)" are not a choice anybody can make. Nothing keys off it; the code remains the identifier. Also renames the country loop variables in the edit modal. They were $code and $name — the component's own properties — and shadowed them for the rest of the file. Harmless while nothing below the loop read them, which stopped being true the moment the code became an editable field above it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
435a202fdd |
Match the panel to the approved template, measured rather than assumed
tests / pest (push) Successful in 8m49s
Details
tests / assets (push) Successful in 21s
Details
tests / release (push) Has been skipped
Details
The panel had been built by reading the template and believing the result. Every round of review found the same class of defect, so this round the template was rendered in a browser and measured with getComputedStyle, and the implementation measured the same way — two number columns instead of an opinion. That immediately settled a disagreement: a review of the template's SOURCE said the metric grid used a 20px gap. Rendered, it is 14px. The measurement wins. Brought to the template's figures: label 11.5px (it was 11px in three separate places, which is how it drifted — it is now one .lbl rule), value tracking -0.02em, unit weight 450, metric row 6px above and 14px between, page head centred with a 14px gap, grid gap 14px, columns switching at 1101/561px and the h1 at 901px to match the template's own breakpoints, ring 62px, bar 5px. The shared button now takes its height from min-height (40px) rather than vertical padding, at 14px/600 with 0 18px padding and a 10px radius — a button keeps its height whatever sits inside it, icon, spinner or bare text. Also here, from the same round of review: - The chart's blue border was Tailwind's own `ring` utility colliding with a component class of the same name. Renamed to `.metric-ring`. This was dismissed once as a screenshot artefact; it was real. - Page titles lost the `sm:text-3xl` (40px) an earlier bulk edit had appended to 23 of them. The template's h1 is 30px. - The users table has its actions back — edit, suspend, lock and delete — for every seat that is not the owner, with the owner refused in the action itself and not merely hidden in the markup. DemoCustomerSeeder writes one complete customer as real rows: instance, subscription, six seats across four roles, a backup, current-period traffic and thirty days of samples with a deliberate wobble and one day at 286/288 checks. Nothing in the panel is drawn from a fixture any more, so anything missing shows up as missing. Removing the demo is one deletion. Verified: 607 tests, and a Codex comparison of the two measurement sets — "a person would call them the same design", the only difference a 1px gap. 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> |
|
|
|
bc278c8fa1 |
feat(admin): datacenters management (create + select)
datacenters table/model + admin CRUD (/admin/datacenters, nav). HostCreate picks from active datacenters (validated); hosts show the datacenter code. Seeded fsn/hel. 5 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
|
|
|
8d12a40a42 |
feat(admin): host onboarding UI (add / live stepper / retry / remove)
Real hosts list, add-host form (StartHostOnboarding), host detail with live progress stepper (Reverb + wire:poll fallback), retry failed run, remove via wire-elements/modal (deregister only). DE+EN. 8 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |