Skip to content

feat(admin): add a Configuration page to view and edit central-manager settings - #1988

Open
Devin-Holland wants to merge 7 commits into
stagefrom
admin-configuration-page
Open

Devin-Holland wants to merge 7 commits into
stagefrom
admin-configuration-page

Conversation

@Devin-Holland

@Devin-Holland Devin-Holland commented Oct 20, 2026 •

Copy link
Copy Markdown
Member
A barefoot dungeon hero turns a glowing settings dial while his tiara-wearing cat points at the padlocked levers

Backend is live. HarperFast/central-manager#982 is in prod since 2026-10-26. HarperFast/central-manager#1025, merged and on stage, audits these edits without changing the API this page uses.

⊙ Problem

Staff cannot see or change central-manager's Configuration table from Studio; it takes the core super user and a direct API call.

🤔 Is a JSON editor safe enough for staff?

Yes, because central-manager decides: HarperFast/central-manager#982 lets fabric_admin edit an allowlist of operational keys and refuses the rest. This page only hides the edits that would fail.

💡 Solution

⚖️ Alternatives

  • Typed form per key — every new key would need a Studio change; the table's values are already JSON.
  • A server-side precondition on PATCH — closes the overwrite race fully, but Harper has no compare-and-set. The re-read before saving leaves only the gap between two requests.

🤔 Should read-only staff get the page too?

Not in this PR. Every staff role holds config:read, but the page exists to edit, so it is gated on config:write. Gating on config:read instead is a one-line change if read-only staff should see values.

🔧 Changes

✅ Verification

  • pnpm test 6302 passing after merging stage (the Hosts, Monitoring and API Tokens pages sit beside this one, and the custom-region types beside the row type). Page tests cover:

    • the lock and Edit rules for fabric_admin and the super user;
    • invalid JSON, the type warning, numbers JSON cannot round-trip, and deep nesting;
    • the saved value, a refused save reported through the app's real error handler, and a stale save stopped before PATCH;
    • the editor locked mid-save, then closing and refreshing, and a failure still reported after the page unmounts.

    Shell visibility and route tests include Configuration.

  • Screenshots below: this branch (ede76d2) in Chrome with stubbed API responses and placeholder values.

  • Not run against a deployed central-manager. The endpoint side was smoke-tested on released Harper 5.3.1 in HarperFast/central-manager#982.

Light theme, as fabric_admin: Super user keys have no Edit button.

Configuration page as fabric_admin, light theme

Dark theme.

Configuration page as fabric_admin, dark theme

Changing a number to a string warns; Save stays enabled for valid JSON.

Edit dialog warning on a number to string change, light theme

🤖 Generated by Claude Code (Claude Opus 5.5); posted via @Devin-Holland.

Related PRs: #1975 overlaps (merged into stage, included here), #1780 overlaps (merged into stage, included here)
Complexity: medium

Deferred findings

  • e3cf9431bf9907fc nit maintainability — src/features/admin/configuration/index.tsx — local imports omit file extensions — NOT FILED
  • 9662b6ba91556850 nit data-integrity — src/features/admin/configuration/mutations/useUpdateConfiguration.ts — whole-value PATCH from stale snapshot loses concurrent edits — NOT FILED
  • c061a4cfe6c1e8e6 nit performance — src/features/admin/configuration/index.tsx — per-keystroke stringify/lowercase/closed-dialog parse over all rows — NOT FILED
  • 64d604298c694ca2 nit maintainability — src/features/admin/configuration/queries/getConfiguration.ts — double cast instead of patched endpoint types — NOT FILED
  • 7244681086f2b812 nit maintainability — src/features/admin/configuration/index.tsx — exports with no consumer outside their own file — NOT FILED

Review-Coverage: authored=claude; ran=gemini,codex,cursor-composer; pending=cursor-grok; late=cursor-grok:pending; adjudicated=domain; declined=cursor-kimi,cursor-muse,muse; rounds=4; full=1 @ ede76d2

Review-Attention: read ~8m (decisions: gate-page-on-config-write, global-error-reporting, client-side-precondition, raw-value-display, strict-integer-rejection, do-less-alternative; raised: degraded review) @ ede76d2

Devin-Holland and others added 4 commits October 20, 2026 10:41
…r settings

A staff-only /admin/configuration page listing every Configuration key with
its description and current value. Accounts holding config:write
(fabric_admin, and the core super user) can edit a value as JSON: the editor
rejects JSON that doesn't parse, warns when an edit changes the value's type,
and shows a refused save inline. Keys central-manager marks superUserOnly
(security controls such as the SSO session cap and host SSH keys) show a lock
and can only be edited by the core super user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…SON can't round-trip

The editor no longer closes while a save is in flight, so a dismissed dialog
can't drop the result; the list refresh and success toast now live on the
mutation itself. Values containing a non-finite number (1e400) or an integer
past 2^53 are refused instead of being saved as null or rounded, and a
refused-save message clears once the value is edited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… overwrite a newer value

A save now re-reads the key and stops if it changed since the editor opened,
since PATCH replaces the whole value. Failures go through the global toast, so
a refused or unanswered save is still reported after navigating away. The
editor locks while saving, deep nesting no longer throws during render, and a
row with no value edits as null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lue as null

Drops the stack-dependent depth case to 1,000 (100,000 still catches both
regressions), shows a row with no value as null in the table as the editor
does, and trims comments that restated the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new Configuration section to the admin panel, allowing authorized staff and super users to view and edit central-manager settings with JSON validation and concurrency checks. It adds the /admin/configuration route, updates navigation visibility, and introduces new config:read and config:write permissions. The review feedback suggests improving the layout of the edit dialog by wrapping the title and description in a DialogHeader component, and adjusting the size and spacing of the LockIcon inside the "Super user" badge for better visual alignment.

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 82.06% 16706 / 20357
🔵 Statements 81.95% 17735 / 21641
🔵 Functions 77.45% 4544 / 5867
🔵 Branches 76.93% 13312 / 17302
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/admin/routes.ts 66.66% 100% 52.38% 66.66% 33-34, 40-41, 47-48, 54-55, 61-62
src/features/admin/components/AdminShell.tsx 33.33% 61.9% 60% 42.85% 53-75
src/features/admin/configuration/index.tsx 91.8% 85.33% 88.88% 94.23% 81, 84, 131-132, 153
src/features/admin/configuration/mutations/useUpdateConfiguration.ts 100% 66.66% 100% 100%
src/features/admin/configuration/queries/getConfiguration.ts 100% 100% 100% 100%
Generated in workflow #2477 for commit ede76d2 by the Vitest Coverage Report Action

Devin-Holland and others added 2 commits October 28, 2026 09:17
…page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…logs

Wrap the title and description in DialogHeader, as most Studio dialogs do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Devin-Holland
Devin-Holland marked this pull request as ready for review October 28, 2026 16:55
@Devin-Holland
Devin-Holland requested a review from a team as a code owner October 28, 2026 16:55

@DavidCockerill DavidCockerill left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Reviewed with Muse

const path = `/Admin/Configuration/${encodeURIComponent(name)}` as '/Admin/Configuration/{name}';
// PATCH replaces the whole value with no precondition. Re-reading first catches an edit made while the
// editor was open; one landing between these two requests is still overwritten.
const { data } = await apiClient.get(path);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] A second admin's write between GET and PATCH is silently overwritten; enforce an atomic expected-value precondition in central-manager

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, agreed that only the server can close this fully. This PR leaves it open on purpose, though. It is the "client-side-precondition" decision under ⚖️ Alternatives: PATCH /Admin/Configuration/{name} takes no expected value, so Studio can only re-read before it writes. That catches any edit made while the dialog was open. The only gap left is the round trip between the GET and the PATCH.

Why it's not a blocker here:

  • A handful of staff edit this table, and the gap is one request long.
  • central-manager#1025 audits each edit with its before and after values (Configuration.js patch), so a lost write can be found and restored.

The real fix is a central-manager change: accept an optional expected value on PATCH and compare it in the same transaction as the write. Harper's transactions and record locks can support that, so the PR body's "no compare-and-set" means there is no built-in primitive, not that it can't be built. Once that lands, Studio only needs to pass expected through. I've noted this as a follow-up for central-manager instead of growing this PR.

— devain (Claude Opus 5.5)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants