29 Commits
Author SHA1 Message Date
mrhid6 9df89e2db4 fix: surface vuln_scanning across licence, staff and pricing UI
Chart Release / chart (push) Successful in 11s
Server Deploy / deploy (push) Canceled after 59s
The catalogue row alone was not enough; the feature was invisible in
three places and mislabelled in a fourth.

PlanConfigurator rendered every key that was not "console" as "Single
sign-on", so the staff checkbox granting vulnerability scanning was
labelled single sign-on. Feature wording was duplicated between the staff
configurator and the purchase form and the copies had drifted, so it now
lives in adminsite/lib/features.ts and both read from it.

The customer licence panel showed raw keys; it now labels them.

Pricing gains a comparison row. The add-on block with a monthly price is
deliberately NOT added: that is a pricing decision, and the Paddle price
IDs for the new catalogue rows have to be pasted in before it can be sold
anyway.
2026-08-06 15:10:26 +01:00
mrhid6 ed4c39650c feat: Removed hq signup page
Server Deploy / deploy (push) Successful in 1m44s
2026-07-29 10:42:51 +01:00
mrhid6 7b8fa4a8a0 feat: Updated hq login page
Server Deploy / deploy (push) Successful in 40s
2026-07-29 10:35:07 +01:00
mrhid6 f10e61cde4 feat: Updated marketing site 2026-07-28 12:46:28 +01:00
mrhid6 2869e63d0a feat: Move manage billing button to billing page 2026-07-27 21:47:50 +01:00
mrhid6 792b7eb211 feat: Updated instance record and license blob box
Server Deploy / deploy (push) Successful in 1m13s
2026-07-27 16:48:37 +01:00
mrhid6 8fd5eacec6 feat: Updated self hosted free plans
Server Deploy / deploy (push) Successful in 40s
2026-07-27 16:26:20 +01:00
mrhid6 66140aaf58 fixes
Server Deploy / deploy (push) Successful in 2m24s
2026-07-27 15:59:45 +01:00
mrhid6 fdfe8e8e46 fix: Fixed oidc purchase description
Server Deploy / deploy (push) Successful in 37s
2026-07-27 15:26:34 +01:00
mrhid6 4ad68e3ac4 feat: Updated purchase page
Server Deploy / deploy (push) Successful in 4m40s
2026-07-27 14:59:43 +01:00
mrhid6andClaude Opus 5 8bbecd2035 feat(adminsite): self-hosted purchase, checkout overlay, billing portal, client line-item builder
Purchase flow: name -> placeholder -> configure via the shipped PlanConfigurator
-> Paddle overlay with custom_data -> paste install UUID to link and issue.
lineItemsFor mirrors the Go catalogue.LineItems/billable exactly (base included
in exactly one place). ManageBillingButton opens the hosted portal. Paddle token
and env are baked into the build, never fetched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 10:53:21 +01:00
mrhid6andClaude Opus 5 c4e6ad5485 feat(license): metered licensing — catalogue, entitlements, and enforcement
Server Deploy / deploy (push) Successful in 5m22s
Implements spec 7 tasks 2-10 on top of the six-plan payload from task 1.

Admin: plans re-keyed on (deployment, tier); new catalogue collection holds
every Paddle price ID (one row per priceable component); new entitlements
collection holds desired beside granted. admin/internal/catalogue owns both
folds — entitlement to licence limits, and entitlement to Paddle line items —
so the base allowance is subtracted in exactly one place. licensing.Issue now
snapshots the instance's granted entitlement, never desired. Free is enforced
per account AND deployment. Staff endpoints for plans, catalogue and
entitlements; Free self-hosted can be claimed and renewed on its annual term;
the reaper stays cloud-only.

Server: enforces the monitor cap, audit-log retention (daily sweep, skips
Unlimited and lapsed instances), and gates the OIDC callback. Unset limits are
filled from the seed plan at the single decode site so old blobs never read as
zero.

Frontends: adminsite gains a catalogue price-ID editor, six-plan allowance
screen, and a catalogue-driven PlanConfigurator mounted on the staff instance
page. web shows monitors, audit retention and support level on the licence page.

Docs: CLAUDE.md, spec index and plan 5 preamble updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 09:37:39 +01:00
mrhid6andClaude Opus 5 34a0373eca feat(adminsite): one masthead, a shared page frame, collapsible instance records
Server Deploy / deploy (push) Successful in 53s
The console had components but no shell: a brand bar and a nav strip stacked
into 100px carrying eight words, no sign-out, no account identity, and an
Overview link hardcoded to text-accent so it read as the current page on
every screen. Nine pages each hand-rolled their own header.

AppBar replaces both bars and derives its active state from usePathname.
Settings moves into AccountMenu — it is your password, not a destination —
taking appearance with it, which finally sets the data-theme attribute the
token blocks have supported in both directions since they were written.
That leaves three customer destinations: Overview, People, Billing.

PageFrame adds a support rail so a page has a floor, and InstanceRecord
replaces InstanceCard with one component that opens and closes: an account
with a single instance used to render a third of a row of summary with its
substance a click away. It defaults open when the instance is the only one
or needs attention.

No plan card in the rail: tier, limits and expiry belong to a licence and a
licence belongs to one instance, so an account holding a Free cloud instance
and a Professional self-hosted one has no single plan. The rail carries only
what is account-wide.

Tokens and globals.css are untouched — they stay verbatim shared with site/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 17:45:53 +01:00
mrhid6andClaude Opus 5 2d12669f9b feat(adminsite): people, instance members and one password
The members panel is absent for self-hosted instances rather than disabled:
the backend refuses those, and a panel rendering controls the server will
reject is a panel that lies.

/auth/me now reports the caller's account role, so the UI hides what the
backend would refuse rather than discovering it in an error toast.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 16:38:21 +01:00
mrhid6 310b3ab03f fix: Fixed plans on admin site erroring
Server Deploy / deploy (push) Successful in 3m39s
2026-07-26 16:08:15 +01:00
mrhid6andClaude Opus 5 cb90ed12fb feat(adminsite): create and renew a free instance
Adds the create form with a live slug preview, a renew action inside the
seven-day window, and a deletion countdown that renders only when the
backend has actually promised a date.

The progress bar denominator now follows the tier; a 30-day Free licence
was rendering as an 8% sliver against the hardcoded 365.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 13:30:42 +01:00
mrhid6andClaude Opus 5 6a3e0a29a4 chore: verify the admin site end to end
Server Deploy / deploy (push) Successful in 1m54s
Everything checkable without a browser, against scratch databases:

- All five Go modules and the adminsite production build compile clean.
- Scoping holds: own instance 200, another account's 404, nonexistent 404 --
  indistinguishable, so no existence oracle -- and a customer session on the
  staff API gets 401 where staff gets 200.
- max_relinks comes from the API (3), not a constant mirrored in TypeScript.
- The inline blob and the download endpoint return byte-identical content, so
  the fallback is faithful rather than approximate.
- ADMIN_API_URL really is baked at build time: 9999 in the deliberately
  broken image, 8083 in the good one.
- The served stylesheet carries site/'s tokens with matching values, plus
  prefers-color-scheme and both data-theme overrides.
- With admin stopped the control plane still reports valid and mutations
  still succeed -- instances never call admin.
- Admin touched only the three licence fields on instances; every other
  control-plane collection is as the control plane left it.

Caught one stale-image bug doing this: the running admin predated the
customer blob change, so the licence response had no blob field at all.
Rebuilt and re-verified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:30:43 +01:00
mrhid6andClaude Opus 5 85f6d47024 feat(adminsite): image, compose service, image build and docs
Publishes adminsite on 3004 -- 3000 is web, 3003 is the marketing site since
the port shuffle -- and adds the sixth CI image.

CLAUDE.md gains both new services. admin/ was never documented there at all:
the backend plan's wiring task covered compose and CI but not the file every
session actually loads. So this records admin's whole REST surface, its
404-not-403 rule, the three visual identities and, most importantly, that
adminsite/ and site/ share one token set with nothing enforcing the match --
the same hazard shape as sitesvc's mirrored slug rules.

Also notes that admin's REDIS_ADDR reaches admin only, because the base
compose hardcodes redis:6379 for server.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:24:37 +01:00
mrhid6andClaude Opus 5 4175608772 feat(adminsite): staff licence history, audit and plan editing
Licences and audit are both filterable client-side: the endpoints cap at 500
rows and staff are narrowing a list already in front of them.

Plans carry both guard rails spec 4 asks for. The confirmation names each
field that changes and states how many licences are already issued and
unaffected -- existing licences snapshotted their plan at issue time, and
saying so is what stops a well-meaning edit being followed by a panicked
reissue. Deployment is displayed and never editable, because moving a tier
between cloud and self-hosted would break the cloud-only rule spec 1 leans
on; that is a code review, not a form field.

The two edit buttons are the concrete changes staff need on day one. A
general-purpose limits editor waits until somebody asks for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:20:54 +01:00
mrhid6andClaude Opus 5 24060b2c5a chore(adminsite): remove the frontend test suite
Removes vitest, React Testing Library, the config, the setup file and all
eleven test files, plus the test scripts and dev dependencies. Done at the
user's direction; it matches the rest of the repo, which has no automated
tests in any language.

All eleven were observed passing before removal, and their assertions are
kept in the plan as acceptance criteria to check by hand rather than deleted
outright -- they are the clearest statement of what each component has to do.

Consequence worth stating: Task 16's manual pass is now the only verification
that exists for spec 4. Four behaviours it must cover carefully, because each
is easy to break invisibly: 404-not-403 scoping, the expired card naming what
still works, relink disabling at zero, and the blob fallback when a download
fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:19:34 +01:00
mrhid6andClaude Opus 5 cefbac625c feat(adminsite): the licence ledger and staff instance actions
The screen that answers "why did this stop working on the 14th". Read top to
bottom it is one instance's whole history: what was issued, why, by whom, and
what replaced it.

Superseded entries stay visible and overprinted rather than disappearing,
because licences are append-only and hiding them would destroy the only
record that answers the question. Each links to its successor.

Injection state is shown live for cloud instances and omitted for
self-hosted, where the customer holds the blob and there is nothing for us to
have written. Staff relinks carry no cap, with the reason stated inline: the
customer cap exists to put a human in the loop, and this is that human.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:15:41 +01:00
mrhid6andClaude Opus 5 73efb206ac feat(adminsite): staff accounts, search by UUID and account detail
Search covers name, email, Paddle customer ID and instance UUID. The UUID
case is the one that matters: a support email often contains a UUID and
nothing else, and the empty state says so rather than just reporting nothing
found.

Account detail gathers everything about one customer on one screen --
instances, subscriptions, people, audit -- and says plainly when an account
has no people because it is a cloud account whose owner signs in with
control-plane credentials.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:12:48 +01:00
mrhid6andClaude Opus 5 7fea321376 feat(adminsite): staff operations dashboard
Four counts, each one work somebody has to do today: failed injections,
licences expiring inside 14 days, past-due subscriptions, and purchases
unlinked for more than 48 hours. No totals and no revenue -- nothing that
cannot be acted on. Every row links straight to the thing that needs doing.

An empty queue says "nothing to do here" rather than rendering a bare zero,
so a quiet dashboard reads as quiet rather than broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:10:48 +01:00
mrhid6andClaude Opus 5 b583e9803f feat(adminsite): the self-hosted link flow and billing view
The link screen carries the whole burden of the five-minute bar: it names
where to find the instance ID, validates the format before asking the server
so a typo is instant rather than a round trip, surfaces the backend's own
message when a UUID is already linked, and on success lands the customer
directly on the download rather than back on a list.

Billing is deliberately thin and says plainly that billing changes go
through support, rather than linking to a Paddle portal that does not exist
until spec 5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:08:46 +01:00
mrhid6andClaude Opus 5 92ac1eeb62 feat(adminsite): licence delivery, paste instructions and relink
The blob is shown inline as well as offered as a file, because a licence is
signed public data bound to one instance -- useless anywhere else -- and a
blocked download must never leave a paying customer stuck. Admin now returns
it to its owner for the same reason.

Relink shows the remaining allowance from the backend's max_relinks rather
than a hardcoded 3, and at zero it disables and says to contact support
instead of failing at the API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:06:21 +01:00
mrhid6andClaude Opus 5 242a587340 feat(adminsite): instance cards and the customer overview
State reads three ways on every card -- a stripe, a shaped-and-labelled
pill, and the copy -- so it survives a colourblind reader and a glance at
arm's length. Colour alone would fail on the one screen where getting it
wrong costs money.

The expired card leads with what still works, because that is the first
thing a worried customer wants to know and the backend really does keep
servers, monitors and agent keys running. The awaiting-link card is
deliberately loud: a customer who has paid and not linked has paid for
nothing yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:03:37 +01:00
mrhid6andClaude Opus 5 7a8e683d99 feat(adminsite): session guards and the auth screens
Route-group layouts do the guarding. A customer session on /staff/* is
redirected to its own home rather than shown a refusal -- there is nothing to
tell them about. This is UX only: admin enforces the same boundary with
RequireStaff/RequireCustomer and answers 404 rather than 403 for another
account's data, which is the layer that actually matters.

Signup carries the honeypot the backend expects and reports "check your
email" rather than claiming an account exists, matching a backend that
creates nothing until the link is opened.

Buttons match site/'s .btn--solid and .btn--line, neutral border included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 21:00:54 +01:00
mrhid6andClaude Opus 5 3e447fd024 feat(adminsite): test harness, typed client and the not-connected state
The repo's first frontend test setup: Vitest, React Testing Library, jsdom.
Scoped to the flows that lose money or leak data when broken, per spec 4.

lib/api.ts collapses every failure into three the UI can act on:
NotConnected (unreachable, or no URL baked in), ApiError 401 (redirect), and
ApiError with the backend's own message, which is customer-facing and shown
verbatim rather than replaced with something vaguer.

The not-connected panel names the variable, the value baked in, and both
reasons it fails -- unreachable from the browser, or missing from admin's
ADMIN_ORIGIN. Proven by test before it existed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 20:57:51 +01:00
mrhid6andClaude Opus 5 6953f5e972 feat(adminsite): scaffold and site/'s token system
A fifth Next.js app, built like web/ and site/: App Router, React 18,
Tailwind 3, TanStack Query, standalone output.

app/globals.css carries site/app/globals.css's token blocks copied verbatim
rather than retyped, so the two cannot drift by transcription. Tailwind holds
var() references only -- no component or config entry may contain a hex
value. The semantic three are aliased: site/'s --up/--pend/--down become
valid/warn/expired, so each app names the colours for what it shows.

Unlike web/, there is no rewrite proxy: the browser calls admin directly, so
NEXT_PUBLIC_ADMIN_API_URL must be browser-reachable and listed in admin's
ADMIN_ORIGIN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 20:55:25 +01:00