Add a read-only configuration view to the web UI #68

Merged
danny merged 1 commit from issue22_webui_config into main 2026-07-06 13:40:42 +01:00
danny commented 2026-07-06 09:44:09 +01:00 (Migrated from gitlab.keyop.co.uk)

Closes #22 (https://gitlab.keyop.co.uk/keyop/go/nfq_forwarder/-/work_items/22).

Scope

Per the ticket's own hedge ("ideally... but that may need to be split out to a separate
ticket"), this covers viewing the running configuration only. Editing + restarting the
service is split into #25 - it's meaningfully bigger and more sensitive (no live-reload path
exists today, so "restart" means a real process restart against a process holding real
capabilities, triggered from an authenticated web request; any edit also needs the same
validation config.LoadFromFile already does before it's ever written to disk).

Changes

  • webUIServer previously only ever received config.WebUI (used once, to build the session
    store, and never stored) - nothing in the request path had access to the full running
    configuration. newWebUIServer/runWebUI now take the whole *config.Configuration instead.
  • New GET /config (page) and GET /api/config (JSON), following the exact pattern the
    existing tunnels dashboard already uses (requireSessionPage/requireSessionAPI - no CSRF
    needed, since there's nothing state-changing here).
  • Displays global settings, the web UI's own settings, the defaults section (including
    by_network rules), and a table of connections - via a purpose-built DTO (webUIConfig,
    matching the existing webUITunnel pattern) rather than marshaling config.Configuration
    directly, keeping internal/config free of web/JSON concerns. Since config.LoadFromFile
    already resolves defaults into each Connection at load time, the connections table shows
    each connection's effective settings for free.
  • Split the shared logout handler + table-cell() DOM helper out of app.js into a new
    common.js, so config.js doesn't duplicate them or accidentally pull in app.js's
    tunnel-specific assumptions (it unconditionally polls #tunnels, which doesn't exist on the
    config page).
  • Small nav link added between the two pages so it reads as one coherent site.

Is anything here sensitive?

No - confirmed from the config struct: there's no password/token/API-key field anywhere in it,
SSH auth is key-file-based (a path, not embedded key material), and the web UI's own auth
delegates to PAM. Displaying it also doesn't widen the trust boundary: reaching this page
already requires passing PAM auth as the same OS account that can read the real config file
directly from disk.

Testing

  • buildConfigDTO is unit tested directly against a fully-populated config.Configuration
    (all fields, including by_network and multiple connections), independent of any HTTP
    machinery.

  • handleConfigPage/handleAPIConfig get the same authenticated-200 / unauthenticated-401ish
    treatment as the existing handleIndex/handleAPITunnels tests.

  • TestWebUIStaticFilesServed extended to cover the two new static JS files.

  • Manually inspected the real rendered HTML shell, the JSON payload, and cross-checked every
    field config.js reads against the DTO's actual JSON tags (no browser available in this
    environment to click through it, but the full request/response contract was exercised for
    real via httptest and printed for review).

  • go build ./..., go vet ./...

  • make lint (golangci-lint + yamllint + gitleaks)

  • make test (go test -race -cover ./...)

Docs

Updated README.md, docs/README.md, and CLAUDE.md (Design notes + Testing sections).

Closes #22 (https://gitlab.keyop.co.uk/keyop/go/nfq_forwarder/-/work_items/22). ## Scope Per the ticket's own hedge ("ideally... but that may need to be split out to a separate ticket"), this covers **viewing** the running configuration only. Editing + restarting the service is split into #25 - it's meaningfully bigger and more sensitive (no live-reload path exists today, so "restart" means a real process restart against a process holding real capabilities, triggered from an authenticated web request; any edit also needs the same validation `config.LoadFromFile` already does before it's ever written to disk). ## Changes - `webUIServer` previously only ever received `config.WebUI` (used once, to build the session store, and never stored) - nothing in the request path had access to the full running configuration. `newWebUIServer`/`runWebUI` now take the whole `*config.Configuration` instead. - New `GET /config` (page) and `GET /api/config` (JSON), following the exact pattern the existing tunnels dashboard already uses (`requireSessionPage`/`requireSessionAPI` - no CSRF needed, since there's nothing state-changing here). - Displays global settings, the web UI's own settings, the `defaults` section (including `by_network` rules), and a table of connections - via a purpose-built DTO (`webUIConfig`, matching the existing `webUITunnel` pattern) rather than marshaling `config.Configuration` directly, keeping `internal/config` free of web/JSON concerns. Since `config.LoadFromFile` already resolves defaults into each `Connection` at load time, the connections table shows each connection's *effective* settings for free. - Split the shared logout handler + table-`cell()` DOM helper out of `app.js` into a new `common.js`, so `config.js` doesn't duplicate them or accidentally pull in `app.js`'s tunnel-specific assumptions (it unconditionally polls `#tunnels`, which doesn't exist on the config page). - Small nav link added between the two pages so it reads as one coherent site. ## Is anything here sensitive? No - confirmed from the config struct: there's no password/token/API-key field anywhere in it, SSH auth is key-*file*-based (a path, not embedded key material), and the web UI's own auth delegates to PAM. Displaying it also doesn't widen the trust boundary: reaching this page already requires passing PAM auth as the same OS account that can read the real config file directly from disk. ## Testing - `buildConfigDTO` is unit tested directly against a fully-populated `config.Configuration` (all fields, including `by_network` and multiple connections), independent of any HTTP machinery. - `handleConfigPage`/`handleAPIConfig` get the same authenticated-200 / unauthenticated-401ish treatment as the existing `handleIndex`/`handleAPITunnels` tests. - `TestWebUIStaticFilesServed` extended to cover the two new static JS files. - Manually inspected the real rendered HTML shell, the JSON payload, and cross-checked every field `config.js` reads against the DTO's actual JSON tags (no browser available in this environment to click through it, but the full request/response contract was exercised for real via `httptest` and printed for review). - [x] `go build ./...`, `go vet ./...` - [x] `make lint` (golangci-lint + yamllint + gitleaks) - [x] `make test` (`go test -race -cover ./...`) ## Docs Updated `README.md`, `docs/README.md`, and `CLAUDE.md` (Design notes + Testing sections).
danny (Migrated from gitlab.keyop.co.uk) approved these changes 2026-07-06 09:44:09 +01:00
danny commented 2026-07-06 13:37:50 +01:00 (Migrated from gitlab.keyop.co.uk)

assigned to @danny

assigned to @danny
danny commented 2026-07-06 13:37:51 +01:00 (Migrated from gitlab.keyop.co.uk)

requested review from @danny

requested review from @danny
danny commented 2026-07-06 13:40:32 +01:00 (Migrated from gitlab.keyop.co.uk)

LGTM and seems to work as intended in live testing.

LGTM and seems to work as intended in live testing.
danny commented 2026-07-06 13:40:32 +01:00 (Migrated from gitlab.keyop.co.uk)

approved this merge request

approved this merge request
danny commented 2026-07-06 13:40:43 +01:00 (Migrated from gitlab.keyop.co.uk)

mentioned in commit 646530d3cc

mentioned in commit 646530d3ccba7c24fa9a55d2e1f619ab615c466f
danny (Migrated from gitlab.keyop.co.uk) merged commit 646530d3cc into main 2026-07-06 13:40:43 +01:00
danny commented 2026-07-06 13:42:41 +01:00 (Migrated from gitlab.keyop.co.uk)

mentioned in issue #22

mentioned in issue #22
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
keyop-go/nfq_forwarder!68
No description provided.