Skip to content

Add super admin console at /-/admin/#9083

Open
ericokuma wants to merge 65 commits intomainfrom
eokuma/admin-console
Open

Add super admin console at /-/admin/#9083
ericokuma wants to merge 65 commits intomainfrom
eokuma/admin-console

Conversation

@ericokuma
Copy link
Copy Markdown
Contributor

@ericokuma ericokuma commented Mar 19, 2026

Internal superuser console at /-/superuser/, giving CS and account managers a GUI for operations currently only available via rill sudo CLI commands.

  • Route group /-/superuser/ with auth guard (checks GetCurrentUserResponse.superuser)
  • Uses ContentContainer + LeftNav layout pattern (matches Settings pages)
  • 6 pages: Users (search/assume/delete), Superusers (list/add/remove), Billing (setup link/extend trial/issues), Quotas (org quotas), Organizations (lookup/members/projects), Projects (search/slots/hibernate/redeploy)
  • Destructive actions use GuardedDeleteDialog (type-to-confirm) or ConfirmActionDialog
  • "Superuser Console" link in avatar menu (above "View as"), visible only to superusers
  • Self-removal from superuser list is prevented

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I am proud of this work!

Developed in collaboration with Claude Code

ericokuma and others added 23 commits March 19, 2026 08:30
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…adge, ActionResultBanner, SearchInput)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d delete

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, repair, and issue management

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…te, and redeploy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…agement

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor Author

@ericokuma ericokuma left a comment

Choose a reason for hiding this comment

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

Re-review: Issues Found & Fixed

After re-reviewing the current state of the PR, I identified and fixed the following issues:

Fixed in this pass

  1. Auth guard control flow clarity (+layout.ts): Restructured the catch block so redirectToLogin() is only called inside the 401 branch, with non-401 errors always throwing a redirect. Same behavior, clearer intent.

  2. Unreachable code (+page.svelte Users): Removed notifySuccess() call after assumedUser.assume() — since assume() sets window.location.href, the notification never displays.

  3. Unfiltered invalidateQueries() (superusers/+page.svelte): Both add and remove handlers called queryClient.invalidateQueries() with no filter, invalidating every query in the app. Scoped to /v1/superuser queries.

  4. Unfiltered invalidateQueries() (billing/+page.svelte): handleDeleteIssue invalidated all queries. Scoped to /v1/organizations and /v1/superuser/billing.

  5. ConfirmDialog closes on error (ConfirmDialog.svelte): If onConfirm threw, the dialog would close via the finally block, hiding the error. Added a catch block so the dialog stays open on failure, letting users retry or cancel.

Not applicable

  • Annotations page, whitelist page, and OrgHeader component do not exist in the current diff — these were false positives from the earlier review.
  • superuserForceAccess on getOrgProjects: the AdminServiceListProjectsForOrganizationParams type does not include this field, so no change needed.

Developed in collaboration with Claude Code

…eview issues

- Fix mutateAsync param names: organization → org for hibernate/redeploy
- Fix billing issue type to use V1BillingIssueType enum instead of string
- Fix quota fields: convert API numbers to strings for form binding
- Remove invalid superuserForceAccess from deleteUser call
- Scope invalidateQueries in superusers and billing pages instead of invalidating all
- Fix ConfirmDialog to keep dialog open on error for retry
- Clean up auth guard control flow in admin layout
- Remove unreachable notifySuccess after assume navigation
- Fix a11y: change label to span for non-input billing text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ericokuma
Copy link
Copy Markdown
Contributor Author

ericokuma and others added 2 commits March 20, 2026 23:44
Add new shared components (OrgSearchInput, UserSearchInput, notify,
RepresentingBanner, assume-state) that were missing from previous
commits. Remove obsolete pages (annotations, runtime, users,
virtual-files, whitelist) and unused components (ActionResultBanner,
StatusBadge, whitelist selectors) that were deleted locally but
not staged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove Dashboard, Whitelist, and Advanced nav sections from sidebar
- Point Users link to root admin page (/-/admin)
- Fix users/selectors: use emailPattern with wildcards, threshold >= 3,
  remove unused assume/unassume mutations
- Fix projects/selectors: add wildcard wrapping, threshold >= 3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ericokuma and others added 5 commits March 31, 2026 11:07
- Move "Type at least 3 characters" text directly under search inputs
  (Users, Projects, Organizations) for tighter visual coupling
- Reorder org detail fields: Name first, ID last
- Move Delete Organization button below field grid with border separator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The direct link to /{org}/{project} produced a blank screen because
the superuser isn't necessarily a member of that org. Now View fetches
org members, picks an admin, and assumes as them with a redirect to
the project page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Organizations page: project name link now assumes as an org admin
  and redirects to the project (same pattern as Projects page View)
- Billing page: remove hardcoded border-primary-200/bg-primary-50
  from Billing Setup section to match other sections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ericokuma ericokuma requested a review from ericpgreen2 March 31, 2026 19:29
Copy link
Copy Markdown
Contributor

@royendo royendo left a comment

Choose a reason for hiding this comment

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

Would be nice to have a header in each page; naving from Organizations to Projects is hard to tell if i swapped pages

id remove the ability to remove yourself from superuser by accident
Screenshot 2026-04-06 at 13 20 31

ericokuma and others added 3 commits April 10, 2026 11:37
# Conflicts:
#	proto/gen/rill/admin/v1/openapi.yaml
#	proto/gen/rill/admin/v1/public.openapi.yaml
#	web-admin/src/features/authentication/AvatarButton.svelte
- Add page-level headings to all superuser pages (royendo feedback)
- Prevent self-removal from superusers list (royendo feedback)
- Replace last raw <button> with Button component in organizations page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ericokuma ericokuma requested a review from royendo April 10, 2026 19:27
The merge conflict resolution accidentally dropped the `superuser` field
from `v1GetCurrentUserResponse` in both openapi.yaml files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@royendo royendo left a comment

Choose a reason for hiding this comment

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

addressed my review

# Conflicts:
#	proto/gen/rill/admin/v1/api.pb.go
Copy link
Copy Markdown
Contributor

@ericpgreen2 ericpgreen2 left a comment

Choose a reason for hiding this comment

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

Three issues worth addressing before merge, one structural recommendation for after.

Assumed sessions affect all browser tabs, but the banner is per-tab only.

RepresentingBanner reads from sessionStorage, which is scoped to a single tab. Auth cookies are not — they apply to the entire browser session. If a superuser assumes a user in Tab A and then switches to or opens Tab B, Tab B is authenticated as the customer with no banner. Any action taken there happens on behalf of the customer without any visible indication. This is the highest-risk behavior in the PR.

Redeploy should require type-to-confirm, not a single click.

Delete organization correctly uses GuardedDeleteDialog. Redeploy uses ConfirmActionDialog — one button click, no friction. Redeploy is disruptive to active users; it warrants the same safeguard.

getOrgProjects is missing superuserForceAccess: true.

In organizations/selectors.ts, getOrgProjects passes {} as params while getOrganization and getOrgMembers both pass { superuserForceAccess: true }. Without it, listing projects for an org the superuser isn't a member of returns a 403. The flag is a per-request opt-in required by the server (forceAccess = claims.Superuser(ctx) && req.SuperuserForceAccess) — normal membership checks apply otherwise. Given that every selector in this surface needs it, each selector file should make it impossible to forget, whether through a local constant, a comment at the top, or a wrapping helper.

Structural: hibernate and redeploy belong only on the Projects page.

These actions are duplicated across organizations/+page.svelte and projects/+page.svelte with separate handler functions and dialog state in each. When the API changes, both pages need updating. The Organizations page is for inspection; project-level operations (hibernate, redeploy, slots) have a natural home in Projects. Removing them from Organizations would eliminate the duplication and give each page a clearer scope.


Developed in collaboration with Claude Code

ericokuma and others added 9 commits April 13, 2026 11:39
Prettier has no parser for .npmrc files. After the husky removal in
#9216 dropped .husky/ from .prettierignore, .npmrc started being
picked up by the prettier check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch assume-state from sessionStorage to localStorage so the
  "Browsing as" banner syncs across all browser tabs (auth cookies
  are browser-wide; the banner must match)
- Add cross-tab storage event listener in RepresentingBanner
- Add missing superuserForceAccess to getOrgProjects selector
- Use GuardedDeleteDialog (type-to-confirm) for redeploy on Projects page
- Remove hibernate/redeploy from Organizations page to eliminate
  duplication; Projects page is the canonical home for these actions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`ListProjectsForOrganization` doesn't support `superuserForceAccess`,
so superusers who aren't org members get an empty list. Switch to the
superuser-only `SearchProjectNames` endpoint with an `orgName/%` pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These are local untracked directories that cause prettier CI failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI uses Node 22 which adds `peer` fields to the lockfile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove .claude/skills/, .claude/launch.json, and docs/superpowers/
that were accidentally committed to this branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These files exist on main but weren't on the PR's merge base — they
appeared as new files when restored. Removing so the PR stays clean;
they'll come in via merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ericokuma ericokuma requested a review from ericpgreen2 April 13, 2026 20:28
…owers

Prevents these local-only files from being accidentally committed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@ericpgreen2 ericpgreen2 left a comment

Choose a reason for hiding this comment

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

It looks like this PR now has a bunch of unintentionally committed code.

Image

I'm certain the above is a mistake, but, related, I've noticed that the base PR is itself very large. Going forward, let's please try to scope PRs to be less than 1000 lines-of-code changed.

…ories

Reverts `.gitignore` and `.prettierignore` to main, removing unrelated
changes that were part of local workspace setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ericokuma ericokuma requested a review from ericpgreen2 April 14, 2026 23:23
@ericokuma
Copy link
Copy Markdown
Contributor Author

It looks like this PR now has a bunch of unintentionally committed code.

Image I'm certain the above is a mistake, but, related, I've noticed that the base PR is itself very large. Going forward, let's please try to scope PRs to be less than 1000 lines-of-code changed.

For sure, I addressed to remove unintentionally committed code

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.

3 participants