Admin screens
Ready-made screens for roles, assignments, group mappings, the change log and Explain, built on @zynactra/ui.
@zynactra/access-react/admin gives a product its access settings in one component. The screens below are the real ones, against an in-memory API, signed in as an access admin.
Add them to a settings page
import { AccessAdmin } from "@zynactra/access-react/admin"
// Inside AccessProvider, with @zynactra/ui's <Toaster> mounted once at the app root.
export function AccessSettingsPage({ people, units }: Props) {
return (
<AccessAdmin
users={people.map((p) => ({ id: p.id, label: p.name, description: p.email }))}
searchUsers={(q, signal) => api.searchPeople(q, { signal })}
scopeOptions={{ org_unit: units.map((u) => ({ id: u.id, label: u.name, parentId: u.parentId })) }}
/>
)
}The screens need @zynactra/ui 0.3 or later and lucide-react, and <Toaster> from @zynactra/ui/toast mounted once: every change is confirmed with a toast, and creating a role, an assignment or a group mapping offers Undo there. They follow zynactra/ui's rules (one brand action per panel, tokens only) and work in every theme, light and dark.
What you pass
| Prop | |
|---|---|
users | People as { id, label, description? }. Tables, the change log and Explain show names instead of IDs; pickers search them. Unknown IDs (service accounts, people not in the list) show as the raw ID, never blank. |
searchUsers(query, signal) | A server search for the pickers, for directories too big to load. Debounced and aborted as people type, so pass signal to fetch. Tables still name people from users. |
scopeOptions | IDs for set scopes, by scope type. With parentId, IDs are picked from a tree (choosing a unit covers its sub-units, as the policy does); without, from a searchable list. Without scopeOptions, admins type IDs. |
The tabs
Each tab shows only to people who hold its permission.
| Tab | Needs | Does |
|---|---|---|
| Roles | access.role:read (or assignment:manage, group_mapping:manage) | Lists system and custom roles with how many assignments and mappings use each. The editor opens in a side sheet with permission search and select-all per resource. A role in use can't be deleted; deleting an unused one asks for its key. |
| Assignments | access.assignment:read | Server search and "Load more", "Show revoked". The assign dialog previews the assignment as the form changes: what it grants, whether a reason and an end date are required, the longest allowed duration, and self-assignment or escalation problems at the field, before anyone submits. Revoking asks for an optional reason. |
| Group mappings | access.group_mapping:manage | Map an IdP group to a role at a scope. The group field offers groups mapped before. |
| Change log | access.change_log:read | A day-grouped activity log with filters (kind, high severity) applied on the server. An entry shows who, when, the reason and a diff of before and after, with links to that target's history and that person's other changes. |
| Explain | access.decision:explain | "Why can't they see this?": pick a person, a permission and optionally a record ID. Every assignment row has "Explain their access". |
Validation problems with a field show at that input; others show above the form. The server enforces every rule (escalation, reasons, break-glass limits); the screens only show them early.
Use the panels on their own
The panels are exported for products that lay them out themselves: RolesPanel, AssignmentsPanel, GroupMappingsPanel, ChangeLogPanel, ExplainPanel, and the pickers UserPicker, ScopeIdsPicker, PersonCell.
One person's roles, on a profile page:
Assignments
Each assignment gives one person a role at a scope, optionally for a limited time.
<AssignmentsPanel userId={person.id} users={people} onExplain={(userId) => openExplain(userId)} />Explain, started from a person and a permission (try record IDs lr_ok and lr_board):
Explain a decision
See why someone can or can't do something, and which grants are involved.
Explain is the first stop for "why can't they see this?" support questions: it names the grants, the scope that matched, or the condition or org rule that denied it.