You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Surface the moderation actions and make the admin-action audit log reviewable (self-oversight; a gap PAS still has).
Scope
Reusable confirm-dialog moderation controls (suspend/unsuspend user, grant/revoke role, unpublish/delete agent, cancel instance, revoke key) — embedded in the User/Agent/Instance detail pages, all calling the Moderation API.
Audit-log viewer page: read admin_audit_log (needs a GET /v1/admin/audit endpoint — add here if not already) — actor, action, target, detail, time; filter by actor/action/target.
Acceptance criteria
Every moderation action requires explicit confirm and shows success/failure.
Audit-log viewer lists actions with filters; each moderation action appears in it.
Non-admin cannot reach any of it (server 403 + hidden UI).
What already existed:Audit.tsx — an unfiltered list of the last 200 rows. GET /v1/admin/audit already existed too (#28), so no new endpoint was needed. Nothing called the moderation API.
Built: a reusable DangerAction control + an AuditTrail component (store/admin/src/lib/moderation.tsx), wired into User, Agent and Instance detail — suspend/unsuspend, roles, key revoke, unpublish, delete, cancel.
How the destructive actions are guarded. The API's constraints are expressed, not papered over:
Nothing fires on one click. Opening a control is separate from confirming it. Where the API demands an echo, so does the UI: agent delete requires typing the slug, key revoke the provider name, suspend the login. A mis-click or the wrong row destroys nothing.
A 409 is information, not a failure. Delete against an agent with live subscribers surfaces as "N live subscriber instance(s) are still attached — forcing cancels them as part of the delete", and forcing is a second, differently-labelled button (Cancel N subscriber(s) & delete). force is never sent on the first attempt, so nobody force-deletes without having read the number they are stranding.
Key revoke is one button per stored provider. There is no "revoke all" — an omitted field must never be able to mean "wipe every key".
Self-targeting is refused up front./v1/auth/me gives the operator their own id, so "suspend" is disabled with a reason on your own account and the admin checkbox is locked rather than offered and then rejected. The server still refuses; the UI just stops offering a button that can only error.
Roles is a checkbox set (matching the API's set-not-patch semantics), showing the before/after list in the confirm copy, and re-keyed on the persisted roles so a save cannot leave stale checkboxes on screen.
The audit log is not a separate silo. Every detail page carries the trail for its own target and refreshes it the moment an action lands — "what has already been done to this account" is a question you have while acting, not later. Rows expand to the recorded before-state. The Audit page itself gains actor/action/target filters (actions listed explicitly, so "has anyone ever deleted an agent" can be asked and answered empty), a row limit, expandable detail, and deep links back to the target — suppressed after a .delete, where the bare id is the honest answer.
All three ACs met; the non-admin gate (server requireAdmin + the /v1/admin/me app gate) was already in place from #28/#35.
Left out:store/admin has no test infrastructure (no vitest/testing-library) and CI typechecks the console but not the admin app — the admin build only runs in deploy-host.yml. Verified here with pnpm typecheck, biome check, a production vite build, and a post-deploy check that the new strings are live in the served bundle.
Activity
feat(admin): the operator can now act, and be audited for it (#38, #41)
serge-ivo commentedon Aug 6, 2026
Shipped in 66fcc23 (CI + Deploy Host green; live at https://proagentstore.online/admin/).
What already existed:
Audit.tsx— an unfiltered list of the last 200 rows.GET /v1/admin/auditalready existed too (#28), so no new endpoint was needed. Nothing called the moderation API.Built: a reusable
DangerActioncontrol + anAuditTrailcomponent (store/admin/src/lib/moderation.tsx), wired into User, Agent and Instance detail — suspend/unsuspend, roles, key revoke, unpublish, delete, cancel.How the destructive actions are guarded. The API's constraints are expressed, not papered over:
Cancel N subscriber(s) & delete).forceis never sent on the first attempt, so nobody force-deletes without having read the number they are stranding./v1/auth/megives the operator their own id, so "suspend" is disabled with a reason on your own account and the admin checkbox is locked rather than offered and then rejected. The server still refuses; the UI just stops offering a button that can only error.The audit log is not a separate silo. Every detail page carries the trail for its own target and refreshes it the moment an action lands — "what has already been done to this account" is a question you have while acting, not later. Rows expand to the recorded before-state. The Audit page itself gains actor/action/target filters (actions listed explicitly, so "has anyone ever deleted an agent" can be asked and answered empty), a row limit, expandable detail, and deep links back to the target — suppressed after a
.delete, where the bare id is the honest answer.All three ACs met; the non-admin gate (server
requireAdmin+ the/v1/admin/meapp gate) was already in place from #28/#35.Left out:
store/adminhas no test infrastructure (no vitest/testing-library) and CI typechecks the console but not the admin app — the admin build only runs indeploy-host.yml. Verified here withpnpm typecheck,biome check, a production vite build, and a post-deploy check that the new strings are live in the served bundle.ci: typecheck the admin app