Switch the gate from `currentUser.is_admin` to `access.canSeeOrgAdmin`
so Org owners get the same all-keys view (Creator column, creator
filter, `user_id: '*'` default) that platform admins have. Mirrors
the BE's "platform admin OR current-Org owner" gate on listing every
key in scope.
The user picker now fetches `/user-directory` instead of the
admin-only `/users` endpoint, which would 403 Org owners. Rename the
filter placeholder from the misnamed `models.table.filterByName` to
`common.filter.byCreator`.
The per-row user actions menu was hard-wired to a fixed list and any
plugin-contributed control had to sit as a separate trigger beside
the dropdown. Move the seam: plugins now contribute dropdown items
directly via `users.rowActions`, and we dispatch the click to the
plugin entry by key while the host's `handleSelect` keeps owning
the built-in keys.
Each entry is `{ key, labelId, icon?, danger?, show?(user), onClick(user) }`.
For UI state the onClick might need (drawers, modals), plugins can
mount a single `components.UsersPageGlobal` slot that the page now
renders once outside the table — typical pattern is a jotai atom
the rowAction writes to and the global slot reads from.
Drop the side-mounted `PluginExtraFields name="UserRowActions"` and
its surrounding `<Space>`; the action column is back to a single
DropdownButtons with all entries in one menu.
The form populated its candidate list via the admin-only
`GET /v2/users`, so opening Route Access Settings as a non-admin
caller (e.g. an org owner managing their own route in the
enterprise plugin) returned 403. Add a `queryUserDirectory` client
wrapper around the slim `/v2/user-directory` endpoint (gated to
platform admin OR org owner) and switch `getUserList` over. Response
shape is the same so the rest of the form is unchanged.
Four purely-additive seams that let build-time tooling extend host
behaviour without forking files:
* `src/access.extensions.ts` — identity `applyAccessExtensions`
(mirrors `config/routes.extensions.ts`). `src/access.ts` runs the
resolved predicate object through it. Adds two new predicates,
`canSeeOrgAdmin` and `canManageCurrentOrg`, alongside the existing
`canSeeAdmin`. Resources, Models children, Cluster Management, and
Resources/Backends are retagged from `canSeeAdmin` to
`canSeeOrgAdmin`. Users and Dashboard stay strict.
* `src/request.extensions.ts` — identity-empty
`extraRequestInterceptors`. `src/request-config.tsx` spreads it
into the existing `requestInterceptors` list so extensions can
inject context-aware headers without forking the request config.
* Users page action column — renders
`getGPUStackPlugin()?.components?.UserRowActions` next to the
existing DropdownButtons inside a Space when a plugin component
is registered. If absent, the cell renders exactly as before.
* `src/components/plugin-extra-fields.tsx` — generic component-slot
helper. Renders `pluginManager.components.<name>` if registered,
forwarding a `context` payload. Used by create/edit forms to let
plugins inject extra `Form.Item` fields. Mounted in the relevant
create forms — API Keys, Cloud Credentials, Clusters, Model
Routes, Model Providers, and Inference Backends — under the slot
name `CreateOrgScopeField`. Resources whose org is implicit from a
parent (Models / Workers / Benchmarks / Worker Pools / Model Files
inherit from the chosen Cluster) deliberately don't mount the slot.
- Add is_active field to user model
- Create SealCheckbox component with clickable container
- Add activation control to user modal
- Display user status in users table
- Add localization strings for activation features