Commit Graph
32 Commits
Author SHA1 Message Date
DenozordecandClaude Opus 5.5 0e05039202 Hub stage 3: compare and sync app configs across servers
The hub keeps a baseline per app in /data/apps/<app>/baseline.json. Local
fields (overrides.json) are left out of the sync and out of the matrix
comparison. Each server gets the baseline plus its own values.

Applying goes target picker, preview with per-server diffs and warnings,
then apply. Apply refuses a server whose config changed since the
preview, saves the old config to history, then reads the new one back
to check it.

Large config bodies are read only for signed-in callers. The App config
dialog has Compare, Baseline and Local fields tabs and an empty state
that offers an import.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 16:46:15 +07:00
DenozordecandClaude Opus 5.5 6acd2d656d Hub stage 2: install, update and uninstall apps on several servers
Writes use only <ID>_ADMIN_TOKEN and need the apps.write capability.
Store URLs are resolved from each server's own listing. The config is saved
to history before an uninstall. Zip uploads are fanned out to the servers.
Every target's result is audited. The console gets a row menu on the
matrix, a target picker, a results dialog with Retry failed, the store and
install from file in All servers mode, and single-server actions for writers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 16:11:40 +07:00
DenozordecandClaude Opus 5.5 3d8df08fa0 Hub stage 1: apps across servers, read-only
hub/apps/matrix reads apps/list and each app's config on every server (the read
token first, the admin token once if a server refuses) and returns per app and server
the version, available update and a config hash, plus whether versions and configs
match and how many JSON leaf paths differ. Config texts never leave the hub. In hub
mode the Apps section has the server selector: All servers shows the matrix with
filters for differing versions, differing configs and updates; one server shows its
list without write actions. The dev mock takes --variant a|b with differing apps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 15:46:51 +07:00
DenozordecandClaude Opus 5.5 0ef2d53a65 Hub stage 0: own users with roles, login limit, audit log, permission table
Users in HUB_DATA/users.json with scrypt hashes and roles (viewer, operator,
apps-admin, admin), managed by hub/users-cli.ts; HUB_USER/HUB_PASSWORD remain a
bootstrap admin while the file does not exist. Five failed logins in 15 minutes lock
the address or the name for 15 minutes. Logins, logouts and every refused request go
to a JSON Lines audit log that admins read through hub/audit/list. Endpoints are
listed in routes.ts with their kind and capability; writes stay refused for now.
Optional <ID>_ADMIN_TOKEN is read for the coming write stages. The management plan
is in docs/hub-management-plan.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 15:25:38 +07:00
DenozordecandClaude Opus 5.5 042cb116ea Hub docs: the two servers are mdns.shnt.top and ndns.shnt.top, the hub is on dnss.shnt.top
CI / check (push) Successful in 2m39s
Hub / check (push) Successful in 41s
CI / image (push) Successful in 2m11s
Hub / image (push) Successful in 1m48s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:53:53 +07:00
DenozordecandClaude Opus 5.5 607d46f872 Compose example: the hub on dnss.shnt.top beside the server
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:50:50 +07:00
DenozordecandClaude Opus 5.5 a86d1a088a Hub: step-by-step deployment guide; Dockerfile comment the language check flagged
docs/hub-deploy.md walks through the image, a read-only hub-reader user and token on
each server, the DNS record, .env, the compose service, checks, updates and
troubleshooting.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:38:56 +07:00
DenozordecandClaude Opus 5.5 3f917c6bd2 Hub stage 4: its own image and pipeline
docker/Dockerfile.hub builds the console and ships it with the hub sources on
node:22-alpine, without node_modules, as a non-root user with a healthcheck.
.gitea/workflows/hub.yaml checks and pushes git.shx.one/denozord/technitium-hub
(latest from main, sha-*, X.Y.Z and X.Y from hub-v tags), independent of the console
pipeline, which now ignores hub-only changes. Compose example for hub.shnt.top and
hub/README.md, with the read-only permissions each server's token needs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:21:59 +07:00
DenozordecandClaude Opus 5.5 48b5d9fb15 Hub stage 3: the console in hub mode
With info.hub the node selector reads Server / All servers, the default-credentials
password prompt and the account actions the hub cannot serve are gone, the header
status reports the servers an aggregate could not reach (hubWarnings, kept until the
next aggregate), and merged Query Logs show a Server column. Without the flag
nothing changes. The hub sends hubWarnings on every aggregate answer, empty when all
servers answered, and never on a single-node one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:18:58 +07:00
DenozordecandClaude Opus 5.5 5f29a438c9 Hub stage 2: read-only Technitium-compatible server over several servers
node:http server that serves the console build and a /api/* the console accepts:
its own login and session (clusterInitialized, the servers as nodes, hub flag,
Dashboard and Logs only), node=<id> forwarded with that server's token, no node or
cluster merged (stats, top lists, query logs, apps), every write refused with 403,
a short response cache, and an unreachable server reported as hubWarnings. The dev
mock takes --port and --scale so two of them differ. Vitest skips .claude/ worktrees.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 19:04:41 +07:00
DenozordecandClaude Opus 5.5 9df2c2b908 Hub stage 1: pure merge functions for stats, top lists and query logs
Sums dashboard counters, aligns chart series by label, merges top lists by name and
query logs by time (page N cut from the merged head of every server), and reports an
unreachable server as a warning.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 18:51:37 +07:00
DenozordecandClaude Sonnet 5.5 37a74199e9 Block Lists: the enable switch keeps one place at the top of its row, not the middle of a row of any height
CI / check (push) Successful in 2m33s
CI / image (push) Successful in 1m10s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 15:31:03 +07:00
DenozordecandClaude Sonnet 5.5 4c09a8df44 Permission ticks centred under their headers; tick, radio and switch edge back to --line-ctrl
CI / check (push) Successful in 2m33s
CI / image (push) Canceled after 48s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 15:11:37 +07:00
DenozordecandClaude Sonnet 5.5 d2b05a1037 Forms on ReUI controls: radio-group installed, native checkboxes and radios replaced
Radios (PanelForm) is ReUI's RadioGroup; Check takes rich text; a bare Tick covers
the table-cell ticks and the list-row switch. Settings IPv6 mode, Change Theme and all
Zones modals no longer draw their own native inputs. Matrix and the data-grid's own
selection stay as they are.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 15:06:22 +07:00
DenozordecandClaude Sonnet 5.5 7f60dc9960 Switch knob sat 7px off the edge: no preflight left the browser's button padding on ReUI buttons
button[data-slot] gets padding 0 in the base layer (ReUI size utilities and the
console's own styles still win). The switch's help now lines up under its label by
the switch's 32px, and the dead .toggle input CSS from the native-switch days is gone.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 14:55:20 +07:00
DenozordecandClaude Sonnet 5.5 03fed773f1 Nothing below text-xs: tab counts, sidebar group label and record TTL hint at 12px
CI / check (push) Successful in 3m27s
CI / image (push) Successful in 1m44s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 11:20:12 +07:00
DenozordecandClaude Sonnet 5.5 36bfd6136b EditableTable: use ReUI ui/table instead of a raw table
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 11:13:42 +07:00
DenozordecandClaude Sonnet 5.5 59f882e58c ReUI alignment: AlertDialog for destructive confirms, Input for date range, ghost Button for Alert close
CI / check (push) Successful in 3m35s
CI / image (push) Successful in 1m32s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-30 10:55:18 +07:00
DenozordecandClaude Opus 5.5 7638939273 Point fixes: badge radius, DHCP Type header, one-line Zones dates, top list headers
CI / check (push) Successful in 3m27s
CI / image (push) Successful in 2m0s
Badges keep the installed style's radius instead of a pill; the Leases Type column
gets its visible header; Zones dates no longer wrap; the Dashboard's top lists use
the standard panel header height; DHCP reads the clock with the list, not in render.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 00:22:31 +07:00
DenozordecandClaude Sonnet 5.5 79db152c3f Header status names its cause; KPI cards wash on hover instead of an outline
CI / check (push) Successful in 3m32s
CI / image (push) Successful in 1m21s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 14:11:19 +07:00
DenozordecandClaude Sonnet 5.5 261f17a117 Settings test: a 60 s timeout for the thirteen-click backup scenario on CI
CI / check (push) Successful in 3m23s
CI / image (push) Successful in 1m23s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 13:12:35 +07:00
DenozordecandClaude Sonnet 5.5 fe7aa08633 Compose example: no em dash in the AXFR comment
CI / check (push) Failing after 2m46s
CI / image (push) Skipped
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 13:08:06 +07:00
DenozordecandClaude Sonnet 5.5 87281a87d3 Match the Technitium canvas: palette, shell, KPI strips, framed tables
CI / check (push) Failing after 37s
CI / image (push) Skipped
The live UI did not follow the canvas because its base layer differed: dark
palette in hex (now the canvas oklch values), 13 px body text, flat Frame panels.

- Tokens: canvas palette, 14 px body, 1.4 line height; Frame without `dense`
  gets the 3 px ring; Panel takes a description line.
- Shell: 56 px brand button with version, 11 px group headings, user card at the
  foot, versions moved into the header status popover, Home / group / section
  crumbs, amber "Attention" status when blocking is off or paused.
- New KpiStrip and PageTabs; Dashboard KPIs in three frames and a Refresh button.
- Zones: KPI strip, tab with count, views, framed table with the canvas columns.
- Apps: Installed / App Store tabs and a grid instead of cards (store inline).
- Blocking: state banner and tab counts; one "Blocking" title.
- Settings: actions in the header, label-and-help rows with the control on the right.
- DHCP, Administration, DNS Client, Logs, Cache, About: subtitles, framed tables,
  KPI strips where the data exists; Login cover with the three-point list.
- docs/compose.shnt.example.yaml: production compose with the console image.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 12:47:27 +07:00
DenozordecandClaude Sonnet 5.5 ea98a33b25 Tests: a 20 s timeout for the slower CI runner
CI / check (push) Successful in 3m23s
CI / image (push) Successful in 1m55s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 09:54:59 +07:00
DenozordecandClaude Sonnet 5.5 9d3d6fd884 Gitea CI: log in to the registry with ACTION_PAT
CI / check (push) Failing after 2m47s
CI / image (push) Skipped
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 09:51:17 +07:00
DenozordecandClaude Sonnet 5.5 06c558640d Fix the YAML of the routes step in the Gitea workflow
CI / check (push) Successful in 2m37s
CI / image (push) Failing after 20s
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 09:44:33 +07:00
DenozordecandClaude Sonnet 5.5 32942c91b3 Gitea CI: checks, build and console image pushed to git.shx.one
ci.yaml (push) Invalid workflow file
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 09:40:57 +07:00
DenozordecandClaude Sonnet 5.5 f4513533c4 App shell as the canvas draws it, full lazy Cache tree, styles aligned with the parent projects
Shell: collapsible rail, DNS/Server groups with pins, always-on header with
breadcrumbs and server status, Ctrl/Cmd+K command palette.
Cache: left column is a lazily expanded tree of every zone, right column the
selected node's records; both columns are ReUI Frames.
Styles: dark variant follows data-theme (field and outline fills), 32 px selects,
36 px segmented strip, 40 px caption table heads, 56 px rows, 20/24/32 page padding.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 09:35:41 +07:00
DenozordecandClaude Sonnet 5.5 44b6dc246c Add a mock API with made-up data to look at the console
dev/mock-api.mjs answers the endpoints the screens call, with surrogate data, and keeps
what is changed in memory (zones, scopes, users, groups, rules, sessions, the cache).
`npm run mock` starts it on 127.0.0.1:5380, where the dev server's proxy already points.
.claude/launch.json starts the mock and the console together in the preview pane.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 01:36:25 +07:00
DenozordecandClaude Sonnet 5.5 f24f4972ba Toast on sonner's behaviour; Dashboard figures as the canvas's KPI strip
Toast: sonner draws the notice and runs its clock, pauses it under the pointer and
removes it. ui/Toast keeps the live region and the action with its permission check.
Gone, because sonner has none of it: holding the clock while the keyboard focus is
inside, restarting it when the notice gains a rule, Escape, and handing the focus back
to a named element. The two Rules tests and three Toast tests about those are removed.

Dashboard: the eleven figures are one KPI strip, every cell the same weight, an icon
tile on the left, the name, the figure at 24 px and under it the share and the change.
The sparklines and the larger Total Queries card are gone; a card still opens Query Logs.

Check.module.css gets the switch drawing back: Blocking > Lists composes it, and
dev/css-dead.mjs reported the missing class.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 01:16:12 +07:00
DenozordecandClaude Sonnet 5.5 4c0a5de7e7 Describe the tables as TanStack columns
Add ui/DataTable, which builds ReUI's DataGrid from `columns` and `data`, and move
all 23 tables onto it. What the grid does not take (a class on a row or cell,
`aria-sort`, `style`, `colSpan`, `role`, `hidden`) is put on the elements it drew
after every render. The column definitions keep their identity between renders, so
cells are not mounted again, and the grid's own paging is off.

The stacked rows on a phone, the column labels and the sort order of the original
work as before. In Blocking > Lists a group heading is a row whose first cell spans
the four columns. docs/reui-migration-plan.md records it.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 00:40:53 +07:00
DenozordecandClaude Sonnet 5.5 f3531c64f9 Move the console onto ReUI PRO
Install ReUI (MCP, skill, @reui registry, Tailwind v4, shadcn base-nova) and move
the shared UI onto it: Button, Tag, Check, Input/Textarea, Segmented, Alert, Panel,
Dialog, Tooltip, Menu and Select (Popover), Toast (sonner), the Table frame
(DataGrid), the Dashboard KPI strip, the shnt Ops palette, Geist and the type scale.

Design only: behaviour, texts and roles are unchanged. Tests that asserted the old
DOM were updated; the tests now format numbers in English wherever they run.
docs/reui-migration-plan.md records the plan and its decisions.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 00:06:06 +07:00