15 Commits
Author SHA1 Message Date
DenozordecandClaude Opus 5.5 ba3cdd08d2 UI polish across sections: one server selector, skeletons, inline errors
CI / check (push) Successful in 2m39s
Hub / check (push) Successful in 50s
CI / image (push) Successful in 1m30s
Hub / image (push) Successful in 1m37s
- Every section puts the hub's server selector in the chrome slot, the
  same place and gap as the Dashboard.
- Data regions load with a Skeleton and fail inline with Retry; KPIs show
  the no-data dash instead of 0 when a server did not answer. Settings
  keeps its header and selector while loading or failing.
- Cache, Apps and Logs headers group secondary or hub-refused actions in
  menus; app descriptions clamp to two lines; Unknown cells say why.
- DNS Client: Protocol label, Resolve and Import together. DHCP and Query
  Logs empty states with actions. View Logs list capped with scroll.
- Blocking: Lists KPI tiles like the Overview, app list table fits its
  panel with one-line URLs. Status re-reads on tab return at most once
  a minute.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 21:42:28 +07:00
DenozordecandClaude Opus 5.5 172e06f1ce Blocking: count Advanced Blocking, Dashboard layout and KPI cards
CI / check (push) Successful in 3m7s
Hub / check (push) Successful in 53s
CI / image (push) Successful in 1m29s
Hub / image (push) Successful in 1m19s
- Status, sidebar mark and header treat a server that blocks with the
  Advanced Blocking app as blocking, per server behind the hub.
- Overview panel summarising the app's groups; Lists and Rules show and
  edit the app's list URLs and rules (read, change one field, write back).
- Block List Domains adds the app's list domains, counted by the hub
  (hub/blocking/app-lists/count, public hosts only unless
  HUB_LISTS_ALLOW_PRIVATE=1) or by the browser where CORS allows.
- Header and KPI cards follow the Dashboard: selector in the chrome slot,
  Compare servers in the header actions, period under the title, StatCard
  tiles without sparklines.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 18:34:17 +07:00
DenozordecandClaude Opus 5.5 9742f0fe69 Hub stage 8: settings drift, webhook notices, profiles
CI / check (push) Successful in 4m27s
Hub / check (push) Successful in 1m17s
CI / image (push) Successful in 1m54s
Hub / image (push) Successful in 1m56s
The drift check now also covers the settings baseline and the Blocked
and Allowed lists baseline; the drift dialog offers Open compare,
Accept as baseline and Revert to baseline for them. Old drift.json
files still load.

HUB_WEBHOOK_URL (and an optional HUB_WEBHOOK_SECRET for an HMAC
signature) receives drift.found, drift.cleared and write.failed
notices, only on change, without values, never blocking a request and
never logging the address beyond its host. Admins see the webhook
status and can send a test from About and from the drift dialog.

Profiles bundle the hub's app, settings and lists baselines. Applying
one to a server runs install, app configs, settings and lists in order
after a preview; a failed step stops that server's later steps and
Retry failed resumes from it. Profiles export and import as a file.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 02:11:19 +07:00
DenozordecandClaude Opus 5.5 392b644faa Hub stage 7: compare and align zones, settings and blocking
Zones gets "All servers" in hub mode: one table of every zone on every
server with its type and serial, marking zones that are on one server
only or whose serials or types differ. A zone can be copied to another
server (export, create, import; Overwrite must be ticked for a zone
that is already there) or made a Secondary there (the source's zone
transfer and notify lists gain the target, then the target zone is
created).

Settings and Blocking can be compared across servers against a baseline
kept in the hub. Host fields stay local, secrets are compared inside
the hub only and never shown or logged, and an apply sends only the
changed shared fields after saving each server's settings to history.
Blocked and Allowed lists are compared as sets; extras are removed only
when asked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 01:20:05 +07:00
DenozordecandClaude Opus 5.5 34de910e04 Hub stage 6: blocking and cache actions on several servers
One endpoint, hub/servers/run (operate), runs a fixed set of actions on
the chosen servers in parallel with each server's admin token: add or
remove a domain in Blocked or Allowed, disable blocking for a time or
enable it, update block lists, replace the block list URLs, flush the
cache or delete a domain from it. Each server gets its own result and
the console offers Retry failed.

In "All servers", Blocking, Cache and Query Logs rows open this flow
instead of being disabled. The hub validates domains, minutes and the
server's list grammar (allow lists with !, switched-off lines with #)
and logs no URL list. Operators can now use the blocking switch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 22:50:09 +07:00
DenozordecandClaude Opus 5.5 8bb1e01936 Hub stage 5: manage each server from one console
Zones, Cache, Blocking, DNS Client, Settings, DHCP and Logs can now be
changed on one chosen server through the hub, with that server's admin
token. Operators manage cache and blocking; admins also zones, settings
and DHCP. Under "All servers", or on a server without an admin token,
the controls are disabled with a hint.

The hub refuses settings/set changes to a server's web service fields
and anything about users, SSO or LDAP; backup, restore, log and stats
deletion and admin/* stay refused. Non-admins get settings with secrets
blanked. Write params travel in the POST body, downloads pass through
with a single-use token, and the audit log names no values.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 22:06:37 +07:00
DenozordecandClaude Opus 5.5 57e492cceb Hub stage 4: config history, rollback, drift check, baseline export and import
Every saved config, and every earlier baseline, is listed per app. You
can view it, diff it against a server's current config, and roll it back
through preview and apply. Earlier baselines can be restored.

A drift check (HUB_DRIFT_MINUTES, default 15) compares each server with
its effective config. Drift shows in the header and the matrix, with
Accept as baseline and Revert to baseline. All baselines export to one
file. Importing that file shows a dry run before it writes.

Rolling back preselects, and diffs against, the server the version came
from. The line-diff op "del" is renamed to "remove" for the language lint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 19:57:33 +07:00
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 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