@jini-ai/user-management 0.6.15-rc.2 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/admin/adapters/http.js +15 -5
  3. package/dist/admin/adapters/memory.js +103 -38
  4. package/dist/admin/conformance/index.js +48 -30
  5. package/dist/admin/controllers/controller-store.js +18 -6
  6. package/dist/admin/controllers/members.controller.js +48 -21
  7. package/dist/admin/controllers/roles.controller.js +98 -49
  8. package/dist/admin/controllers/users.controller.js +22 -11
  9. package/dist/admin/error-copy.js +3 -1
  10. package/dist/admin/ports.d.ts +11 -10
  11. package/dist/admin/react/components/PermissionPanel.js +3 -2
  12. package/dist/admin/react/components/PolicyRow.js +3 -2
  13. package/dist/admin/react/components/RoleRow.js +3 -2
  14. package/dist/admin/react/components/UsersConfirmation.js +1 -2
  15. package/dist/admin/react/hooks/AuthScope.hooks.js +2 -1
  16. package/dist/admin/react/hooks/MembersPage.hooks.js +23 -10
  17. package/dist/admin/react/hooks/MembersScope.hooks.js +2 -1
  18. package/dist/admin/react/hooks/UsersPage.hooks.js +13 -5
  19. package/dist/admin/react/hooks/UsersScope.hooks.js +2 -1
  20. package/dist/admin/react/index.d.ts +6 -6
  21. package/dist/admin/rules.js +15 -7
  22. package/dist/core/ports.d.ts +6 -0
  23. package/dist/react/errors.js +3 -1
  24. package/dist/react/users/Users.js +2 -1
  25. package/dist/react/users/rules.js +9 -5
  26. package/dist/server/admin-crud-service.js +17 -13
  27. package/dist/server/agent-tool-registrations.js +18 -15
  28. package/dist/server/agent-tools.js +38 -31
  29. package/dist/server/auth-challenge.js +4 -3
  30. package/dist/server/hasher.js +2 -1
  31. package/dist/server/repo.memory-transactions.js +13 -9
  32. package/dist/server/repo.memory.d.ts +3 -0
  33. package/dist/server/repo.memory.js +8 -0
  34. package/dist/server/seed.d.ts +17 -1
  35. package/dist/server/seed.js +25 -2
  36. package/package.json +41 -32
@@ -17,17 +17,20 @@ export function createRolesController({ api }, { permissions = [] } = {}) {
17
17
  const abort = new AbortController();
18
18
  const call = { signal: abort.signal };
19
19
  const set = (patch) => {
20
- if (disposed)
20
+ if (disposed) {
21
21
  return;
22
+ }
22
23
  const immutablePatch = patch.permissionRows ? { ...patch, permissionRows: Object.freeze([...patch.permissionRows]) } : patch;
23
24
  state = Object.freeze({ ...state, ...immutablePatch });
24
- for (const listener of listeners)
25
+ for (const listener of listeners) {
25
26
  listener();
27
+ }
26
28
  };
27
29
  const report = (error, fallback) => describeRolesError({ error, fallback });
28
30
  const gate = () => {
29
- if (disposed || state.confirming)
31
+ if (disposed || state.confirming) {
30
32
  return false;
33
+ }
31
34
  if (!canManageRoles({ permissions: grants })) {
32
35
  set({ rowError: m.forbidden });
33
36
  return false;
@@ -68,147 +71,188 @@ export function createRolesController({ api }, { permissions = [] } = {}) {
68
71
  set({ rowError: report(error, fallback) });
69
72
  }
70
73
  finally {
71
- if (state.rowSavingId === id)
74
+ if (state.rowSavingId === id) {
72
75
  set({ rowSavingId: null });
76
+ }
73
77
  }
74
78
  return saved;
75
79
  }
76
80
  async function load(_required, _optional = {}) {
77
- if (disposed)
81
+ if (disposed) {
78
82
  return;
79
- const generation = ++listGeneration;
83
+ }
84
+ ++listGeneration;
85
+ const generation = listGeneration;
80
86
  set({ error: null });
81
87
  try {
82
88
  const [r, p] = await Promise.all([api.listRoles({}, call), api.listPolicies({}, call)]);
83
- if (generation === listGeneration)
89
+ if (generation === listGeneration) {
84
90
  set({ roles: rows(r.roles), policies: rows(p.policies) });
91
+ }
85
92
  }
86
93
  catch (error) {
87
- if (generation === listGeneration)
94
+ if (generation === listGeneration) {
88
95
  set({ error: report(error, 'Failed to load roles/policies.') });
96
+ }
89
97
  }
90
98
  }
91
99
  async function loadPermissions({ policyId }, _optional = {}) {
92
- if (disposed || state.permissionPolicyId !== policyId)
100
+ if (disposed || state.permissionPolicyId !== policyId) {
93
101
  return;
102
+ }
94
103
  // Start order, rather than completion order, owns rows AND failures. Closing also invalidates.
95
- const generation = ++permissionGeneration;
104
+ ++permissionGeneration;
105
+ const generation = permissionGeneration;
96
106
  set({ permissionsLoading: true });
97
107
  try {
98
108
  const result = await api.listPolicyPermissions({ policyId }, call);
99
- if (generation === permissionGeneration && state.permissionPolicyId === policyId)
109
+ if (generation === permissionGeneration && state.permissionPolicyId === policyId) {
100
110
  set({ permissionRows: rows(result.policyPermissions) });
111
+ }
101
112
  }
102
113
  catch (error) {
103
- if (generation === permissionGeneration && state.permissionPolicyId === policyId)
114
+ if (generation === permissionGeneration && state.permissionPolicyId === policyId) {
104
115
  set({ permissionRows: [], rowError: report(error, 'Failed to load permissions.') });
116
+ }
105
117
  }
106
118
  finally {
107
- if (generation === permissionGeneration)
119
+ if (generation === permissionGeneration) {
108
120
  set({ permissionsLoading: false });
121
+ }
109
122
  }
110
123
  }
111
124
  async function togglePermissions({ policyId }, _optional = {}) {
112
- if (disposed || !policy(policyId))
125
+ if (disposed || !policy(policyId)) {
113
126
  return;
127
+ }
114
128
  const next = state.permissionPolicyId === policyId ? null : policyId;
115
129
  ++panelGeneration;
116
130
  ++permissionGeneration;
117
131
  // Clear first so reopening cannot flash the previous policy's permissions.
118
132
  set({ permissionPolicyId: next, permissionRows: [], permissionsLoading: false, permissionInput: '', resourceTypeInput: '', rowError: null });
119
- if (next)
133
+ if (next) {
120
134
  await loadPermissions({ policyId: next });
135
+ }
121
136
  }
122
137
  async function createRole(_required, _optional = {}) {
123
- if (!gate() || state.roleSaving)
138
+ if (!gate() || state.roleSaving) {
124
139
  return false;
140
+ }
125
141
  const name = state.roleName;
126
- return formWrite({ saving: 'roleSaving', error: 'roleError', fallback: 'Failed to create role.', run: async () => {
142
+ return formWrite({
143
+ saving: 'roleSaving', error: 'roleError', fallback: 'Failed to create role.', run: async () => {
127
144
  await api.createRole({ name }, call);
128
- if (state.roleName === name)
145
+ if (state.roleName === name) {
129
146
  set({ roleName: '' });
130
- } });
147
+ }
148
+ }
149
+ });
131
150
  }
132
151
  async function createPolicy(_required, _optional = {}) {
133
- if (!gate() || state.policySaving)
152
+ if (!gate() || state.policySaving) {
134
153
  return false;
154
+ }
135
155
  const name = state.policyName, description = state.policyDescription;
136
- return formWrite({ saving: 'policySaving', error: 'policyError', fallback: 'Failed to create policy.', run: async () => {
156
+ return formWrite({
157
+ saving: 'policySaving', error: 'policyError', fallback: 'Failed to create policy.', run: async () => {
137
158
  await api.createPolicy({ name }, { ...call, ...(description ? { description } : {}) });
138
- if (state.policyName === name && state.policyDescription === description)
159
+ if (state.policyName === name && state.policyDescription === description) {
139
160
  set({ policyName: '', policyDescription: '' });
140
- } });
161
+ }
162
+ }
163
+ });
141
164
  }
142
165
  function editRole({ role: r }, _optional = {}) {
143
- if (roleGate(role(r.id)))
166
+ if (roleGate(role(r.id))) {
144
167
  set({ editingRoleId: r.id, editingRoleName: r.name, rowError: null });
168
+ }
145
169
  }
146
170
  function editPolicy({ policy: p }, _optional = {}) {
147
- if (policyGate(policy(p.id)))
171
+ if (policyGate(policy(p.id))) {
148
172
  set({ editingPolicyId: p.id, editingPolicyName: p.name, editingPolicyDescription: p.description ?? '', rowError: null });
173
+ }
149
174
  }
150
175
  async function saveRole(_required, _optional = {}) {
151
176
  const id = state.editingRoleId, name = state.editingRoleName;
152
- if (!id || !roleGate(role(id)) || state.rowSavingId)
177
+ if (!id || !roleGate(role(id)) || state.rowSavingId) {
153
178
  return false;
154
- return rowWrite({ id, fallback: 'Failed to rename role.', run: async () => {
179
+ }
180
+ return rowWrite({
181
+ id, fallback: 'Failed to rename role.', run: async () => {
155
182
  await api.updateRole({ roleId: id, name }, call);
156
183
  // A late save must not close another row's newer edit.
157
- if (state.editingRoleId === id && state.editingRoleName === name)
184
+ if (state.editingRoleId === id && state.editingRoleName === name) {
158
185
  set({ editingRoleId: null });
186
+ }
159
187
  await load({});
160
- } });
188
+ }
189
+ });
161
190
  }
162
191
  async function savePolicy(_required, _optional = {}) {
163
192
  const id = state.editingPolicyId, name = state.editingPolicyName, description = state.editingPolicyDescription;
164
- if (!id || !policyGate(policy(id)) || state.rowSavingId)
193
+ if (!id || !policyGate(policy(id)) || state.rowSavingId) {
165
194
  return false;
166
- return rowWrite({ id, fallback: 'Failed to update policy.', run: async () => {
195
+ }
196
+ return rowWrite({
197
+ id, fallback: 'Failed to update policy.', run: async () => {
167
198
  await api.updatePolicy({ policyId: id }, { ...call, name, description });
168
- if (state.editingPolicyId === id && state.editingPolicyName === name && state.editingPolicyDescription === description)
199
+ if (state.editingPolicyId === id && state.editingPolicyName === name && state.editingPolicyDescription === description) {
169
200
  set({ editingPolicyId: null });
201
+ }
170
202
  await load({});
171
- } });
203
+ }
204
+ });
172
205
  }
173
206
  async function writePermission(_required, _optional = {}) {
174
207
  const id = state.permissionPolicyId, permission = state.permissionInput, resourceType = state.resourceTypeInput;
175
- if (!id || !permission || !policyGate(policy(id)) || state.rowSavingId)
208
+ if (!id || !permission || !policyGate(policy(id)) || state.rowSavingId) {
176
209
  return false;
210
+ }
177
211
  const generation = panelGeneration;
178
- return rowWrite({ id, fallback: 'Failed to add permission.', run: async () => {
212
+ return rowWrite({
213
+ id, fallback: 'Failed to add permission.', run: async () => {
179
214
  await api.writePolicyPermission({ policyId: id, permission }, { ...call, ...(resourceType ? { resourceType } : {}) });
180
215
  // Ordering alone cannot protect a trailing refresh: it always mints the newest generation.
181
216
  // Check the CURRENT panel identity, and preserve a newer draft even on the same panel.
182
- if (generation === panelGeneration && state.permissionInput === permission && state.resourceTypeInput === resourceType)
217
+ if (generation === panelGeneration && state.permissionInput === permission && state.resourceTypeInput === resourceType) {
183
218
  set({ permissionInput: '', resourceTypeInput: '' });
184
- if (state.permissionPolicyId === id)
219
+ }
220
+ if (state.permissionPolicyId === id) {
185
221
  await loadPermissions({ policyId: id });
186
- } });
222
+ }
223
+ }
224
+ });
187
225
  }
188
226
  function targetGate(target) {
189
- if (target.kind === 'role')
227
+ if (target.kind === 'role') {
190
228
  return roleGate(role(target.role.id));
229
+ }
191
230
  return policyGate(policy(target.kind === 'policy' ? target.policy.id : target.policyId));
192
231
  }
193
232
  function capture(target) {
194
- if (target.kind === 'role')
233
+ if (target.kind === 'role') {
195
234
  return Object.freeze({ kind: 'role', role: Object.freeze({ ...target.role }) });
196
- if (target.kind === 'policy')
235
+ }
236
+ if (target.kind === 'policy') {
197
237
  return Object.freeze({ kind: 'policy', policy: Object.freeze({ ...target.policy }) });
238
+ }
198
239
  return Object.freeze({ kind: 'permission', policyId: target.policyId, row: Object.freeze({ ...target.row }) });
199
240
  }
200
241
  function requestDestructive({ target }, _optional = {}) {
201
- if (state.confirming || !targetGate(target))
242
+ if (state.confirming || !targetGate(target)) {
202
243
  return;
203
- if (target.kind === 'permission' && (state.permissionPolicyId !== target.policyId || !state.permissionRows.some(r => r.id === target.row.id)))
244
+ }
245
+ if (target.kind === 'permission' && (state.permissionPolicyId !== target.policyId || !state.permissionRows.some(r => r.id === target.row.id))) {
204
246
  return;
247
+ }
205
248
  // The menu closes after one click; shell confirmation survives that closure. Revoke also
206
249
  // needs confirmation: removing from an attached policy immediately changes live authority.
207
250
  set({ pending: capture(target), rowError: null });
208
251
  }
209
252
  function cancelDestructive(_required, _optional = {}) {
210
- if (!state.confirming)
253
+ if (!state.confirming) {
211
254
  set({ pending: null });
255
+ }
212
256
  }
213
257
  async function destroy(target) {
214
258
  if (target.kind === 'role') {
@@ -227,14 +271,16 @@ export function createRolesController({ api }, { permissions = [] } = {}) {
227
271
  else {
228
272
  set({ removingPermissionId: target.row.id });
229
273
  await api.removePolicyPermission({ policyId: target.policyId, policyPermissionId: target.row.id }, call);
230
- if (state.permissionPolicyId === target.policyId)
274
+ if (state.permissionPolicyId === target.policyId) {
231
275
  await loadPermissions({ policyId: target.policyId });
276
+ }
232
277
  }
233
278
  }
234
279
  async function confirmDestructive(_required, _optional = {}) {
235
280
  const target = state.pending;
236
- if (!target || state.confirming || state.rowSavingId || !targetGate(target))
281
+ if (!target || state.confirming || state.rowSavingId || !targetGate(target)) {
237
282
  return false;
283
+ }
238
284
  set({ confirming: true, rowError: null });
239
285
  let destroyed = false;
240
286
  try {
@@ -245,8 +291,9 @@ export function createRolesController({ api }, { permissions = [] } = {}) {
245
291
  set({ rowError: report(error, target.kind === 'permission' ? 'Failed to remove permission.' : `Failed to delete ${target.kind}.`) });
246
292
  }
247
293
  finally {
248
- if (state.pending === target)
294
+ if (state.pending === target) {
249
295
  set({ pending: null });
296
+ }
250
297
  set({ confirming: false, removingPermissionId: null });
251
298
  }
252
299
  return destroyed;
@@ -254,13 +301,15 @@ export function createRolesController({ api }, { permissions = [] } = {}) {
254
301
  return {
255
302
  getSnapshot(_required = {}, _optional = {}) { return state; },
256
303
  subscribe({ listener }, _optional = {}) {
257
- if (!disposed)
304
+ if (!disposed) {
258
305
  listeners.add(listener);
306
+ }
259
307
  return () => { listeners.delete(listener); };
260
308
  },
261
309
  dispose(_required = {}, _optional = {}) {
262
- if (disposed)
310
+ if (disposed) {
263
311
  return;
312
+ }
264
313
  disposed = true;
265
314
  abort.abort();
266
315
  ++listGeneration;
@@ -37,8 +37,10 @@ export function createUsersController({ api, safety }, { permissions = [], openO
37
37
  if (read({}).rowSavingId || !gate(principalId, kind))
38
38
  return;
39
39
  const user = row(principalId);
40
- set({ pending: Object.freeze({ kind, user: snapshotUser(user) }), rowError: null, passwordError: null,
41
- newPassword: '', confirmPassword: '', showNewPassword: false, showConfirmPassword: false });
40
+ set({
41
+ pending: Object.freeze({ kind, user: snapshotUser(user) }), rowError: null, passwordError: null,
42
+ newPassword: '', confirmPassword: '', showNewPassword: false, showConfirmPassword: false
43
+ });
42
44
  }
43
45
  function maybeDeepLink() {
44
46
  const s = read({});
@@ -63,7 +65,8 @@ export function createUsersController({ api, safety }, { permissions = [], openO
63
65
  async function load(_required, _optional = {}) {
64
66
  if (!store.active({}))
65
67
  return;
66
- const generation = ++listGeneration;
68
+ ++listGeneration;
69
+ const generation = listGeneration;
67
70
  set({ error: null });
68
71
  try {
69
72
  const [u, r, p] = await Promise.all([api.listUsers({}, call), api.listRoles({}, call), api.listPolicies({}, call)]);
@@ -86,8 +89,10 @@ export function createUsersController({ api, safety }, { permissions = [], openO
86
89
  const caller = await api.me({}, call);
87
90
  if (!store.active({}) || !caller.user || typeof caller.user.id !== 'string' || !caller.user.id)
88
91
  return;
89
- const safeCaller = Object.freeze({ user: Object.freeze({ ...caller.user }), canManageUserTrash: caller.canManageUserTrash === true,
90
- ...(caller.effectivePermissions ? { effectivePermissions: Object.freeze([...caller.effectivePermissions]) } : {}) });
92
+ const safeCaller = Object.freeze({
93
+ user: Object.freeze({ ...caller.user }), canManageUserTrash: caller.canManageUserTrash === true,
94
+ ...(caller.effectivePermissions ? { effectivePermissions: Object.freeze([...caller.effectivePermissions]) } : {})
95
+ });
91
96
  set({ caller: safeCaller });
92
97
  maybeDeepLink();
93
98
  }
@@ -127,12 +132,18 @@ export function createUsersController({ api, safety }, { permissions = [], openO
127
132
  /** Per-kind panel writes: the authority checked, the selection that must be present, the call,
128
133
  * and the selection cleared on success (email keeps its field, but still notifies). */
129
134
  const panelWrites = {
130
- email: { action: 'email', fallback: 'failed to update email', cleared: {}, missing: () => false,
131
- write: (principalId, s) => api.updateUser({ principalId }, { ...call, email: s.editEmail }) },
132
- role: { action: 'grants', fallback: 'failed to assign role', cleared: { pendingRoleId: '' }, missing: s => !s.pendingRoleId,
133
- write: (principalId, s) => api.assignRole({ principalId, roleId: s.pendingRoleId }, call) },
134
- policy: { action: 'grants', fallback: 'failed to attach policy', cleared: { pendingPolicyId: '' }, missing: s => !s.pendingPolicyId,
135
- write: (principalId, s) => api.attachPolicy({ principalId, policyId: s.pendingPolicyId }, call) },
135
+ email: {
136
+ action: 'email', fallback: 'failed to update email', cleared: {}, missing: () => false,
137
+ write: (principalId, s) => api.updateUser({ principalId }, { ...call, email: s.editEmail })
138
+ },
139
+ role: {
140
+ action: 'grants', fallback: 'failed to assign role', cleared: { pendingRoleId: '' }, missing: s => !s.pendingRoleId,
141
+ write: (principalId, s) => api.assignRole({ principalId, roleId: s.pendingRoleId }, call)
142
+ },
143
+ policy: {
144
+ action: 'grants', fallback: 'failed to attach policy', cleared: { pendingPolicyId: '' }, missing: s => !s.pendingPolicyId,
145
+ write: (principalId, s) => api.attachPolicy({ principalId, policyId: s.pendingPolicyId }, call)
146
+ },
136
147
  };
137
148
  /** Busy, expanded-row and authority checks run before the selection check, so only a refusal of authority reports. */
138
149
  function panelSavable(s, principalId, panel) {
@@ -1,7 +1,9 @@
1
1
  /** Shared pieces of the People and Roles error-copy mappers (`describePeopleError`, `describeRolesError`). */
2
2
  /** The string `code` of a structured error, or undefined when there is none. */
3
3
  export function errorCode({ error }, _optional = {}) {
4
- return error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
4
+ if (!error || typeof error !== 'object' || !('code' in error))
5
+ return undefined;
6
+ return typeof error.code === 'string' ? error.code : undefined;
5
7
  }
6
8
  /** A real Error's non-empty message, else the given fallback copy. */
7
9
  export function errorMessageOr({ error, fallback }, _optional = {}) {
@@ -1,3 +1,4 @@
1
+ import type { AdminOperator, CallerInfo, AdminMember, AdminAccount } from './models.js';
1
2
  import type { RoleRecord, PolicyRecord, PolicyPermissionRecord } from './models.js';
2
3
  export interface RolesCallOptions {
3
4
  readonly signal?: AbortSignal;
@@ -76,7 +77,7 @@ export declare class RolesApiError extends Error {
76
77
  }
77
78
  export interface UsersApiPort {
78
79
  listUsers(required: Record<string, never>, optional?: RolesCallOptions): Promise<{
79
- users: import('./models.js').AdminOperator[];
80
+ users: AdminOperator[];
80
81
  }>;
81
82
  listRoles(required: Record<string, never>, optional?: RolesCallOptions): Promise<{
82
83
  roles: RoleRecord[];
@@ -84,31 +85,31 @@ export interface UsersApiPort {
84
85
  listPolicies(required: Record<string, never>, optional?: RolesCallOptions): Promise<{
85
86
  policies: PolicyRecord[];
86
87
  }>;
87
- me(required: Record<string, never>, optional?: RolesCallOptions): Promise<import('./models.js').CallerInfo>;
88
+ me(required: Record<string, never>, optional?: RolesCallOptions): Promise<CallerInfo>;
88
89
  createUser(required: {
89
90
  username: string;
90
91
  password: string;
91
92
  }, optional?: RolesCallOptions & {
92
93
  email?: string;
93
94
  }): Promise<{
94
- user: import('./models.js').AdminOperator;
95
+ user: AdminOperator;
95
96
  }>;
96
97
  updateUser(required: {
97
98
  principalId: string;
98
99
  }, optional?: RolesCallOptions & {
99
100
  email?: string;
100
101
  }): Promise<{
101
- user: import('./models.js').AdminOperator;
102
+ user: AdminOperator;
102
103
  }>;
103
104
  disableUser(required: {
104
105
  principalId: string;
105
106
  }, optional?: RolesCallOptions): Promise<{
106
- user: import('./models.js').AdminOperator;
107
+ user: AdminOperator;
107
108
  }>;
108
109
  enableUser(required: {
109
110
  principalId: string;
110
111
  }, optional?: RolesCallOptions): Promise<{
111
- user: import('./models.js').AdminOperator;
112
+ user: AdminOperator;
112
113
  }>;
113
114
  resetUserPassword(required: {
114
115
  principalId: string;
@@ -143,17 +144,17 @@ export interface MembersApiPort {
143
144
  afterId?: string;
144
145
  limit?: number;
145
146
  }): Promise<{
146
- members: import('./models.js').AdminMember[];
147
+ members: AdminMember[];
147
148
  }>;
148
149
  getMember(required: {
149
150
  id: string;
150
151
  }, optional?: RolesCallOptions): Promise<{
151
- member: import('./models.js').AdminMember;
152
+ member: AdminMember;
152
153
  }>;
153
154
  disableMember(required: {
154
155
  id: string;
155
156
  }, optional?: RolesCallOptions): Promise<{
156
- member: import('./models.js').AdminMember;
157
+ member: AdminMember;
157
158
  }>;
158
159
  requestMemberMagicLink(required: {
159
160
  email: string;
@@ -174,7 +175,7 @@ export interface AuthApiPort {
174
175
  username: string;
175
176
  password: string;
176
177
  }, optional?: RolesCallOptions): Promise<{
177
- user: import('./models.js').AdminAccount;
178
+ user: AdminAccount;
178
179
  }>;
179
180
  }
180
181
  export declare class PeopleApiError extends Error {
@@ -3,7 +3,8 @@ import { Button, TextField, Spinner } from '@jini-ai/ui-kit/react';
3
3
  import { usePermissionPanel } from '../hooks/PermissionPanel.hooks.js';
4
4
  export function PermissionPanel(props, _optional = {}) {
5
5
  const v = usePermissionPanel(props);
6
- return _jsxs("div", { children: [_jsx("h3", { children: "Current permissions" }), v.loading ? _jsx(Spinner, { label: "Loading permissions\u2026" }) : v.empty ? _jsx("p", { children: "No permissions yet." }) :
7
- _jsx("ul", { children: v.rows.map(row => _jsxs("li", { children: [_jsx("code", { children: row.permission }), row.resourceType ? _jsxs("span", { children: [" (", row.resourceType, ")"] }) : null, v.editable ? _jsx(Button, { onPress: row.requestRemove, pending: row.pending, attrs: { 'aria-label': row.removeLabel }, children: "Remove" }) : null] }, row.id)) }), v.editable ? _jsxs("form", { onSubmit: v.submit, children: [_jsx(TextField, { label: "Permission", value: v.permission, onValueChange: v.changePermission, placeholder: "e.g. content.write" }), _jsx(TextField, { label: "Resource type (optional)", value: v.resourceType, onValueChange: v.changeResourceType }), _jsx(Button, { type: "submit", pending: v.pending, disabled: v.addDisabled, children: "Add" })] }) : null] });
6
+ const permissions = v.empty ? _jsx("p", { children: "No permissions yet." }) :
7
+ _jsx("ul", { children: v.rows.map(row => _jsxs("li", { children: [_jsx("code", { children: row.permission }), row.resourceType ? _jsxs("span", { children: [" (", row.resourceType, ")"] }) : null, v.editable ? _jsx(Button, { onPress: row.requestRemove, pending: row.pending, attrs: { 'aria-label': row.removeLabel }, children: "Remove" }) : null] }, row.id)) });
8
+ return _jsxs("div", { children: [_jsx("h3", { children: "Current permissions" }), v.loading ? _jsx(Spinner, { label: "Loading permissions\u2026" }) : permissions, v.editable ? _jsxs("form", { onSubmit: v.submit, children: [_jsx(TextField, { label: "Permission", value: v.permission, onValueChange: v.changePermission, placeholder: "e.g. content.write" }), _jsx(TextField, { label: "Resource type (optional)", value: v.resourceType, onValueChange: v.changeResourceType }), _jsx(Button, { type: "submit", pending: v.pending, disabled: v.addDisabled, children: "Add" })] }) : null] });
8
9
  }
9
10
  //# sourceMappingURL=PermissionPanel.js.map
@@ -4,8 +4,9 @@ import { usePolicyRow } from '../hooks/PoliciesTab.hooks.js';
4
4
  import { PermissionPanel } from './PermissionPanel.js';
5
5
  export function PolicyRow(props, _optional = {}) {
6
6
  const v = usePolicyRow(props);
7
+ const editActions = v.editing ? _jsxs(_Fragment, { children: [_jsx(Button, { onPress: v.save, pending: v.pending, disabled: v.saveDisabled, children: "Save" }), _jsx(Button, { onPress: v.cancel, children: "Cancel" })] }) :
8
+ _jsx(Menu, { label: v.menuLabel, items: v.menu });
7
9
  return _jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: v.editing ? _jsx(TextField, { label: "Policy new name", value: v.draft, onValueChange: v.changeName }) : v.name }), _jsx("td", { children: v.editing ? _jsx(TextField, { label: "Policy new description", value: v.draftDescription, onValueChange: v.changeDescription }) : v.description }), _jsx("td", { children: _jsx(Badge, { children: v.type }) }), _jsx("td", { children: v.editable ?
8
- v.editing ? _jsxs(_Fragment, { children: [_jsx(Button, { onPress: v.save, pending: v.pending, disabled: v.saveDisabled, children: "Save" }), _jsx(Button, { onPress: v.cancel, children: "Cancel" })] }) :
9
- _jsx(Menu, { label: v.menuLabel, items: v.menu }) : _jsx(Button, { onPress: v.viewPermissions, children: v.readLabel }) })] }), v.open ? _jsx("tr", { children: _jsx("td", { colSpan: 4, children: _jsx(PermissionPanel, { policy: props.policy }) }) }) : null] });
10
+ editActions : _jsx(Button, { onPress: v.viewPermissions, children: v.readLabel }) })] }), v.open ? _jsx("tr", { children: _jsx("td", { colSpan: 4, children: _jsx(PermissionPanel, { policy: props.policy }) }) }) : null] });
10
11
  }
11
12
  //# sourceMappingURL=PolicyRow.js.map
@@ -3,8 +3,9 @@ import { Button, TextField, Menu, Badge } from '@jini-ai/ui-kit/react';
3
3
  import { useRoleRow } from '../hooks/RolesTab.hooks.js';
4
4
  export function RoleRow(props, _optional = {}) {
5
5
  const v = useRoleRow(props);
6
+ const editActions = v.editing ? _jsxs(_Fragment, { children: [_jsx(Button, { onPress: v.save, pending: v.pending, disabled: v.saveDisabled, children: "Save" }), _jsx(Button, { onPress: v.cancel, children: "Cancel" })] }) :
7
+ _jsx(Menu, { label: v.menuLabel, items: v.menu });
6
8
  return _jsxs("tr", { children: [_jsx("td", { children: v.editing ? _jsx(TextField, { label: "Role new name", value: v.draft, onValueChange: v.changeName }) : v.name }), _jsx("td", { children: _jsx(Badge, { children: v.type }) }), _jsx("td", { children: v.editable ?
7
- v.editing ? _jsxs(_Fragment, { children: [_jsx(Button, { onPress: v.save, pending: v.pending, disabled: v.saveDisabled, children: "Save" }), _jsx(Button, { onPress: v.cancel, children: "Cancel" })] }) :
8
- _jsx(Menu, { label: v.menuLabel, items: v.menu }) : _jsx("span", { children: "\u2014" }) })] });
9
+ editActions : _jsx("span", { children: "\u2014" }) })] });
9
10
  }
10
11
  //# sourceMappingURL=RoleRow.js.map
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ConfirmDialog, Notice } from '@jini-ai/ui-kit/react';
3
- import { SecretInput } from '@jini-ai/ui';
2
+ import { ConfirmDialog, Notice, SecretInput } from '@jini-ai/ui-kit/react';
4
3
  import { useId } from 'react';
5
4
  import { useUsersConfirmation, newPasswordAttrs } from '../hooks/UsersTab.hooks.js';
6
5
  /** Kept mounted independently of row menus. Confirmation and cancellation share ui-kit's guards. */
@@ -3,8 +3,9 @@ import { createContext, useContext } from 'react';
3
3
  export const ScopeContext = createContext(null);
4
4
  export function useAuthScope(_required = {}, _optional = {}) {
5
5
  const scope = useContext(ScopeContext);
6
- if (!scope)
6
+ if (!scope) {
7
7
  throw new Error('Auth admin scope is unavailable.');
8
+ }
8
9
  return scope;
9
10
  }
10
11
  //# sourceMappingURL=AuthScope.hooks.js.map
@@ -4,35 +4,48 @@ import { hasAdminGrant } from '../../people.rules.js';
4
4
  import { useMembersScope } from './MembersScope.hooks.js';
5
5
  import { usePeopleController } from './PeopleController.hooks.js';
6
6
  export const MembersViewContext = createContext(null);
7
- export function useMembersView(_required = {}, _optional = {}) { const v = useContext(MembersViewContext); if (!v)
8
- throw new Error('Members controller unavailable.'); return v; }
7
+ export function useMembersView(_required = {}, _optional = {}) {
8
+ const v = useContext(MembersViewContext);
9
+ if (!v) {
10
+ throw new Error('Members controller unavailable.');
11
+ }
12
+ return v;
13
+ }
9
14
  export function useMembersPage(props, _optional = {}) {
10
15
  const { api, permissions } = useMembersScope({});
11
16
  const scope = useMemo(() => ({ api, refresh: props.refresh }), [api, props.refresh]);
12
17
  const key = JSON.stringify(permissions);
13
- const { controller, state } = usePeopleController({ scope, key,
18
+ const { controller, state } = usePeopleController({
19
+ scope, key,
14
20
  create: () => createMembersController({ api }, { permissions, ...(props.refresh ? { refresh: props.refresh } : {}) }), start: c => { void c.load({}); },
15
21
  });
16
22
  const canManage = hasAdminGrant({ permissions, permission: 'member.manage' });
17
- return { content: props.tabs.members ? createElement(props.tabs.members, {}) : null, state, controller, canManage, visible: props.description.visible && props.description.tabs.some(t => t.visible),
18
- view: state && controller ? { state, controller, canManage } : null, retry: () => { void controller?.load({}); } };
23
+ return {
24
+ content: props.tabs.members ? createElement(props.tabs.members, {}) : null, state, controller, canManage, visible: props.description.visible && props.description.tabs.some(t => t.visible),
25
+ view: state && controller ? { state, controller, canManage } : null, retry: () => { void controller?.load({}); }
26
+ };
19
27
  }
20
28
  export function useMemberRow({ member }, _optional = {}) {
21
29
  const { state: s, controller: c, canManage } = useMembersView({}), id = member.id;
22
30
  const rs = c.stateFor({ id }), items = [];
23
31
  if (canManage) {
24
32
  items.push({ id: 'resend', label: 'Resend sign-in link', disabled: rs.resending, onPress: () => { void c.resendSignInLink({ id }); } });
25
- if (member.status !== 'disabled')
33
+ if (member.status !== 'disabled') {
26
34
  items.push({ id: 'disable', label: 'Disable', disabled: rs.disabling, onPress: () => c.requestDestructive({ id }) });
35
+ }
27
36
  }
28
- return { s, rs, items, menuLabel: `Actions for member "${member.email}"`, expanded: s.expandedId === id,
37
+ return {
38
+ s, rs, items, menuLabel: `Actions for member "${member.email}"`, expanded: s.expandedId === id,
29
39
  detail: s.detailById[id], loading: s.detailLoadingId === id, toggle: () => { void c.toggleDetail({ id }); },
30
- verified: s.detailById[id]?.emailVerifiedAt ?? 'not verified', created: member.createdAt, };
40
+ verified: s.detailById[id]?.emailVerifiedAt ?? 'not verified', created: member.createdAt,
41
+ };
31
42
  }
32
43
  export function useMembersConfirmation(_required = {}, _optional = {}) {
33
44
  const { state: s, controller: c } = useMembersView({});
34
- return { open: s.pending !== null, title: 'Disable this member?', body: s.pending ? `Disable "${s.pending.email}"? They will no longer be able to sign in.` : '',
45
+ return {
46
+ open: s.pending !== null, title: 'Disable this member?', body: s.pending ? `Disable "${s.pending.email}"? They will no longer be able to sign in.` : '',
35
47
  confirmLabel: 'Disable', tone: 'warning', consequence: 'revokes sign-in access', pending: s.confirming, agentMayConfirm: false,
36
- onConfirm: async () => { await c.confirmDestructive({}); }, onCancel: () => c.cancelDestructive({}) };
48
+ onConfirm: async () => { await c.confirmDestructive({}); }, onCancel: () => c.cancelDestructive({})
49
+ };
37
50
  }
38
51
  //# sourceMappingURL=MembersPage.hooks.js.map
@@ -3,8 +3,9 @@ import { createContext, useContext } from 'react';
3
3
  export const ScopeContext = createContext(null);
4
4
  export function useMembersScope(_required = {}, _optional = {}) {
5
5
  const scope = useContext(ScopeContext);
6
- if (!scope)
6
+ if (!scope) {
7
7
  throw new Error('Members admin scope is unavailable.');
8
+ }
8
9
  return scope;
9
10
  }
10
11
  //# sourceMappingURL=MembersScope.hooks.js.map
@@ -4,19 +4,27 @@ import { hasAdminGrant } from '../../people.rules.js';
4
4
  import { useUsersScope } from './UsersScope.hooks.js';
5
5
  import { usePeopleController } from './PeopleController.hooks.js';
6
6
  export const UsersViewContext = createContext(null);
7
- export function useUsersView(_required = {}, _optional = {}) { const v = useContext(UsersViewContext); if (!v)
8
- throw new Error('Users controller unavailable.'); return v; }
7
+ export function useUsersView(_required = {}, _optional = {}) {
8
+ const v = useContext(UsersViewContext);
9
+ if (!v) {
10
+ throw new Error('Users controller unavailable.');
11
+ }
12
+ return v;
13
+ }
9
14
  export function useUsersPage(props, _optional = {}) {
10
15
  const { api, safety, permissions } = useUsersScope({});
11
16
  const scope = useMemo(() => ({ api, safety, closed: props.onOwnPasswordResetClosed }), [api, safety, props.onOwnPasswordResetClosed]);
12
17
  const key = JSON.stringify([permissions, props.openOwnPasswordReset]);
13
- const { controller, state } = usePeopleController({ scope, key,
18
+ const { controller, state } = usePeopleController({
19
+ scope, key,
14
20
  create: () => createUsersController({ api, safety }, { permissions, ...(props.openOwnPasswordReset !== undefined ? { openOwnPasswordReset: props.openOwnPasswordReset } : {}), ...(props.onOwnPasswordResetClosed ? { onOwnPasswordResetClosed: props.onOwnPasswordResetClosed } : {}) }),
15
21
  start: c => { void c.load({}); void c.loadCaller({}); },
16
22
  });
17
23
  const visible = props.description.visible && props.description.tabs.some(t => t.visible);
18
- return { content: props.tabs.users ? createElement(props.tabs.users, {}) : null, controller, state, visible, view: controller && state ? { controller, state, permissions } : null,
24
+ return {
25
+ content: props.tabs.users ? createElement(props.tabs.users, {}) : null, controller, state, visible, view: controller && state ? { controller, state, permissions } : null,
19
26
  readOnly: !hasAdminGrant({ permissions, permission: 'user.manage' }) && !hasAdminGrant({ permissions, permission: 'role.manage' }),
20
- retry: () => { void controller?.load({}); } };
27
+ retry: () => { void controller?.load({}); }
28
+ };
21
29
  }
22
30
  //# sourceMappingURL=UsersPage.hooks.js.map
@@ -3,8 +3,9 @@ import { createContext, useContext } from 'react';
3
3
  export const ScopeContext = createContext(null);
4
4
  export function useUsersScope(_required = {}, _optional = {}) {
5
5
  const scope = useContext(ScopeContext);
6
- if (!scope)
6
+ if (!scope) {
7
7
  throw new Error('Users admin scope is unavailable.');
8
+ }
8
9
  return scope;
9
10
  }
10
11
  //# sourceMappingURL=UsersScope.hooks.js.map