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
When a non-admin user (or a user lacking a specific permission) lands on a page, the current UX is:
Frontend unconditionally fires every API call the page would normally make.
Backend returns 403 for the calls the user isn't allowed to make.
Frontend catches each 403 and shows a red toast: Failed to load X: admin access required.
The result is a page full of empty sections + a stack of red error toasts, which is both ugly and misleading (the user didn't fail at anything — they're just looking at a page that's not for them). Better UX: hide what the user can't access, don't surface "denied" toasts at all. A readonly user shouldn't see "Create Plan" buttons whose only outcome is a 403.
PR #364 fixes the symptom for read endpoints by widening their Auth level to AuthUser. This issue tracks the broader cleanup: pre-emptively suppress UI that triggers permission-denied responses, rather than firing the call and catching the denial.
Scope
Two layers — same canAccess(action, resource) helper drives both:
1. Page-level — hide entire sections / nav entries the user can't reach
2. Element-level — hide mutating CTAs on otherwise-readable pages
Once the page itself is reachable for readonly, the per-row Edit / Delete / Run buttons in tables (plans, planned-purchases, users on admin pages) should be hidden for users who can't actually invoke the underlying endpoint. Currently they're rendered unconditionally and the 403 only surfaces on click.
Proposed design
A single canAccess(action, resource) helper in frontend/src/auth.ts (or a new permissions.ts) that:
Reads state.currentUser.role (already populated post-login via /api/auth/me).
Maps role → default permission set, matching the backend's DefaultAdminPermissions / DefaultUserPermissions / DefaultReadOnlyPermissions. This logic already exists in frontend/src/users/userList.ts:155-163 (effectivePermissions) — extract it into a shared module and reuse there.
Optionally folds in group-grant permissions when the user has explicit groups[] entries (the backend's group-permissions stay authoritative; the frontend's job is only to decide whether to render an element).
API call sites can also short-circuit when the user lacks the permission — no network round-trip just to discover the answer is no:
if(!canAccess('view','plans'))return;// skip the list-plans fetch entirely
Acceptance criteria
canAccess helper extracted into frontend/src/permissions.ts with unit tests covering the three role defaults + the empty-state.
userList.ts:effectivePermissions rewritten to delegate to the helper (one source of truth for role → permissions).
Top-nav: Admin dropdown hidden for non-admins.
Plans page: Create/Edit/Delete buttons hidden when the user lacks the permission.
Planned Purchases page: pause / resume / run / delete row buttons hidden for users lacking execute:purchases.
RI Exchange page: action buttons hidden for non-admins.
Admin → Users page: entire page hidden for non-admins; route also bounces (/admin/users → /).
No Failed to … : admin access required toast fires for a logged-in readonly user navigating the entire app (golden-path manual check).
Out of scope
Backend RBAC widening — separate work. The frontend just decides what to render based on the role currently in state; the backend stays authoritative.
Per-account permission scoping (the user has access to some accounts but not others). Backend already filters response rows by allowed_accounts; the frontend just renders what it gets back.
Completed by #452 (merged 2026-05-14, commit d448d72). This issue stayed open only because #452 merged into the feat/multicloud-web-frontend feature branch rather than the default branch, so GitHub's auto-close didn't fire. The role-based hide-instead-of-toast gating is verified present in base (canAccess(...) in recommendations.ts/settings.ts, admin-only handling in auth.ts/base.css/index.html). Duplicate PR #534 closed as superseded.
Activity
fix(frontend/ux): hide admin-only sections instead of toasting "admin…
cristim commentedon May 20, 2026
Completed by #452 (merged 2026-05-14, commit d448d72). This issue stayed open only because #452 merged into the
feat/multicloud-web-frontendfeature branch rather than the default branch, so GitHub's auto-close didn't fire. The role-based hide-instead-of-toast gating is verified present in base (canAccess(...)in recommendations.ts/settings.ts,admin-onlyhandling in auth.ts/base.css/index.html). Duplicate PR #534 closed as superseded.fix(frontend/ux): hide admin-only sections instead of toasting "admin…