# RBAC CRUD Gaps — Design Specification ## Goal Add missing CRUD and assignment UI to the RBAC management page, fix date formatting, seed a built-in Admins group, and fix dashboard diagram ordering. ## References - Parent spec: `docs/superpowers/specs/2026-03-17-rbac-management-design.md` - Visual prototype: `examples/RBAC/rbac_management_ui.html` --- ## Changes ### 1. Users Tab — Delete + Assignments Users cannot be created manually (they arrive via login). The detail pane gains: - **Delete button** in the detail header area. Uses existing `ConfirmDeleteDialog` with the user's `displayName` as the confirmation string. Calls `useDeleteUser()`. **Guard:** the currently authenticated user (from `useAuthStore`) cannot delete themselves — button disabled with tooltip "Cannot delete your own account". - **Group membership section** — "+ Add" chip opens a **multi-select dropdown** listing all groups the user is NOT already a member of. Checkboxes for batch selection, "Apply" button to commit. Calls are batched via `Promise.allSettled()` — if any fail, show an inline error, invalidate queries regardless to refresh. Existing group chips gain an "x" remove button calling `useRemoveUserFromGroup()`. - **Direct roles section** — the existing "Effective roles" section renders both direct and inherited roles. The "+ Add" multi-select dropdown lists roles not yet directly assigned. Calls `useAssignRoleToUser()` (batched via `Promise.allSettled()`). Direct role chips gain an "x" button calling `useRemoveRoleFromUser()`. Inherited role chips (dashed border) do NOT get remove buttons — they can only be removed by changing group membership or group role assignments. - **Created field** — change from date-only to full date+time: `new Date(createdAt).toLocaleString()`. - **Mutation button states** — all action buttons (delete, remove chip "x") disable while their mutation is in-flight to prevent double-clicks. ### 2. Groups Tab — CRUD + Assignments - **"+ Add group" button** in the panel header (`.btnAdd` style exists). Opens an inline form below the search bar with: name text input, optional parent group dropdown, "Create" button. Calls `useCreateGroup()`. Form clears and closes on success. On error: shows error message inline. - **Delete button** in detail pane header. Uses `ConfirmDeleteDialog` with group name. Calls `useDeleteGroup()`. Resets selected group. **Guard:** the built-in Admins group (`SystemRole.ADMINS_GROUP_ID`) cannot be deleted — button disabled with tooltip "Built-in group cannot be deleted". - **Assigned roles section** — "+ Add" multi-select dropdown listing roles not yet assigned to this group. Batched via `Promise.allSettled()`. Calls `useAssignRoleToGroup()`. Role chips gain "x" for `useRemoveRoleFromGroup()`. - **Parent group** — shown as a dropdown in the detail header area, allowing re-parenting. Calls `useUpdateGroup()`. The dropdown excludes the group itself and its transitive descendants (cycle prevention — requires recursive traversal of `childGroups` on each `GroupDetail`). Setting to empty/none makes it top-level. ### 3. Roles Tab — CRUD - **"+ Add role" button** in panel header. Opens an inline form: name (required), description (optional), scope (optional, free-text, defaults to "custom"). Calls `useCreateRole()`. - **Delete button** in detail pane header. **Disabled for system roles** (lock icon + tooltip "System roles cannot be deleted"). Custom roles use `ConfirmDeleteDialog` with role name → `useDeleteRole()`. - No assignment UI on the roles tab — assignments are managed from the User and Group detail panes. ### 4. Multi-Select Dropdown Component A reusable component used across all assignment actions: ``` Props: items: { id: string; label: string }[] — available items to pick from onApply: (selectedIds: string[]) => void — called with all checked IDs placeholder?: string — search filter placeholder ``` Behavior: - Opens as a positioned dropdown below the "+ Add" chip - Search/filter input at top - Checkbox list of items (max-height with scroll) - "Apply" button at bottom (disabled when nothing selected) - Closes on Apply, Escape, or click-outside - Shows count badge on Apply button: "Apply (3)" Styling: background `var(--bg-raised)`, border `var(--border)`, border-radius `var(--radius-md)`, items with `var(--bg-hover)` on hover, checkboxes with `var(--amber)` accent. ### 5. Inline Create Form A reusable pattern for "Add group" and "Add role": - Appears below the search bar in the list pane, pushing content down - Input fields with labels - "Create" and "Cancel" buttons - On success: closes form, clears inputs, new entity appears in list - On error: shows error message inline - "Create" button disabled while mutation is in-flight ### 6. Built-in Admins Group Seed **Database migration** — new `V2__admin_group_seed.sql` (V1 is already deployed, V2-V10 were deleted in the migration consolidation so V2 is safe): ```sql -- Built-in Admins group INSERT INTO groups (id, name) VALUES ('00000000-0000-0000-0000-000000000010', 'Admins'); -- Assign ADMIN role to Admins group INSERT INTO group_roles (group_id, role_id) VALUES ('00000000-0000-0000-0000-000000000010', '00000000-0000-0000-0000-000000000004'); ``` **SystemRole.java** — add constants: ```java public static final UUID ADMINS_GROUP_ID = UUID.fromString("00000000-0000-0000-0000-000000000010"); ``` **UiAuthController.login()** — after upserting the user and assigning ADMIN role, also add to Admins group: ```java rbacService.addUserToGroup(subject, SystemRole.ADMINS_GROUP_ID); ``` **Frontend guard:** The Admins group UUID is hardcoded as a constant in the frontend to disable deletion. Alternatively, check if a group's ID matches a known system group ID. ### 7. Dashboard Diagram Ordering The inheritance diagram's three columns (Groups → Roles → Users) must show items in a consistent, matching order: - **Groups column**: alphabetical by name, children indented under parents - **Roles column**: iterate groups top-to-bottom, collect their direct roles, deduplicate preserving first-seen order. Roles not assigned to any group are omitted from the diagram. - **Users column**: alphabetical by display name Sort explicitly in `DashboardTab.tsx` before rendering. --- ## Files Changed ### Frontend — Modified | File | Change | |---|---| | `ui/src/pages/admin/rbac/UsersTab.tsx` | Delete button, group/role assignment dropdowns, date format fix, self-delete guard | | `ui/src/pages/admin/rbac/GroupsTab.tsx` | Add group form, delete button, role assignment dropdown, parent group dropdown, Admins guard | | `ui/src/pages/admin/rbac/RolesTab.tsx` | Add role form, delete button (disabled for system) | | `ui/src/pages/admin/rbac/DashboardTab.tsx` | Sort diagram columns consistently | | `ui/src/pages/admin/rbac/RbacPage.module.css` | Styles for multi-select dropdown, inline create form, delete button, action chips, remove buttons | ### Frontend — New | File | Responsibility | |---|---| | `ui/src/pages/admin/rbac/components/MultiSelectDropdown.tsx` | Reusable multi-select picker with search, checkboxes, batch apply | ### Backend — Modified | File | Change | |---|---| | `cameleer3-server-core/.../rbac/SystemRole.java` | Add `ADMINS_GROUP_ID` constant | | `cameleer3-server-app/.../security/UiAuthController.java` | Add admin user to Admins group on login | ### Backend — New Migration | File | Change | |---|---| | `cameleer3-server-app/src/main/resources/db/migration/V2__admin_group_seed.sql` | Seed Admins group + ADMIN role assignment | --- ## Out of Scope - Editing user profile fields (name, email) — users are managed by their identity provider - Drag-and-drop group hierarchy management - Role permission editing (custom roles have no effect on Spring Security yet)