zynactradocs

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
usersPeople 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.
scopeOptionsIDs 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.

TabNeedsDoes
Rolesaccess.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.
Assignmentsaccess.assignment:readServer 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 mappingsaccess.group_mapping:manageMap an IdP group to a role at a scope. The group field offers groups mapped before.
Change logaccess.change_log:readA 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.
Explainaccess.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.

Leave empty to explain your own access.

Optional: decide on one record.

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.

Edit on GitHub

On this page