@jini-ai/user-management 0.3.3 → 0.6.10
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.
- package/CHANGELOG.md +15 -1
- package/dist/admin/adapters/memory.d.ts +3 -5
- package/dist/admin/adapters/memory.js +27 -28
- package/dist/admin/conformance/index.js +1 -0
- package/dist/admin/controllers/auth.controller.js +8 -6
- package/dist/admin/controllers/members.controller.js +9 -6
- package/dist/admin/controllers/roles.controller.js +99 -105
- package/dist/admin/controllers/users.controller.js +56 -36
- package/dist/admin/error-copy.d.ts +11 -0
- package/dist/admin/error-copy.js +10 -0
- package/dist/admin/people.rules.js +37 -31
- package/dist/admin/react/components/MemberRow.d.ts +1 -1
- package/dist/admin/react/components/MemberRow.js +12 -2
- package/dist/admin/react/hooks/AuthBinding.hooks.d.ts +6 -21
- package/dist/admin/react/hooks/AuthBinding.hooks.js +3 -8
- package/dist/admin/react/hooks/AuthScope.hooks.d.ts +22 -0
- package/dist/admin/react/hooks/AuthScope.hooks.js +10 -0
- package/dist/admin/react/hooks/LoginPage.hooks.d.ts +1 -1
- package/dist/admin/react/hooks/LoginPage.hooks.js +1 -1
- package/dist/admin/react/hooks/MembersBinding.hooks.d.ts +6 -17
- package/dist/admin/react/hooks/MembersBinding.hooks.js +3 -8
- package/dist/admin/react/hooks/MembersPage.hooks.d.ts +1 -1
- package/dist/admin/react/hooks/MembersPage.hooks.js +1 -1
- package/dist/admin/react/hooks/MembersScope.hooks.d.ts +17 -0
- package/dist/admin/react/hooks/MembersScope.hooks.js +10 -0
- package/dist/admin/react/hooks/RolesBinding.hooks.d.ts +7 -21
- package/dist/admin/react/hooks/RolesBinding.hooks.js +3 -8
- package/dist/admin/react/hooks/RolesController.hooks.js +1 -1
- package/dist/admin/react/hooks/RolesPage.hooks.d.ts +2 -2
- package/dist/admin/react/hooks/RolesPage.hooks.js +22 -8
- package/dist/admin/react/hooks/RolesScope.hooks.d.ts +21 -0
- package/dist/admin/react/hooks/RolesScope.hooks.js +10 -0
- package/dist/admin/react/hooks/UsersBinding.hooks.d.ts +6 -18
- package/dist/admin/react/hooks/UsersBinding.hooks.js +3 -8
- package/dist/admin/react/hooks/UsersPage.hooks.d.ts +1 -1
- package/dist/admin/react/hooks/UsersPage.hooks.js +1 -1
- package/dist/admin/react/hooks/UsersScope.hooks.d.ts +19 -0
- package/dist/admin/react/hooks/UsersScope.hooks.js +10 -0
- package/dist/admin/react/hooks/UsersTab.hooks.d.ts +6 -0
- package/dist/admin/react/hooks/UsersTab.hooks.js +34 -22
- package/dist/admin/react/index.d.ts +4 -4
- package/dist/admin/react/pages/LoginPage.d.ts +1 -1
- package/dist/admin/react/pages/MembersPage.d.ts +1 -1
- package/dist/admin/react/pages/RolesPage.d.ts +2 -2
- package/dist/admin/react/pages/RolesPage.js +6 -2
- package/dist/admin/react/pages/UsersPage.d.ts +2 -2
- package/dist/admin/react/pages/UsersPage.js +6 -2
- package/dist/admin/rules.js +14 -12
- package/dist/react/errors.js +16 -9
- package/dist/react/index.d.ts +1 -1
- package/dist/react/members/hooks/use-members.hooks.js +21 -12
- package/dist/react/testing.js +4 -6
- package/dist/react/users/UserRowParts.d.ts +53 -0
- package/dist/react/users/UserRowParts.js +39 -0
- package/dist/react/users/Users.d.ts +31 -2
- package/dist/react/users/Users.js +41 -36
- package/dist/react/users/hooks/use-users.hooks.js +57 -30
- package/dist/server/admin-crud-service.js +25 -13
- package/dist/server/agent-tool-input.js +62 -36
- package/dist/server/agent-tool-registrations.d.ts +46 -0
- package/dist/server/agent-tool-registrations.js +28 -20
- package/dist/server/auth-service.js +35 -20
- package/dist/server/authorize.js +7 -9
- package/dist/server/permission-migrations.js +35 -25
- package/dist/server/repo.memory-transactions.js +5 -6
- package/dist/server/repo.memory.js +35 -81
- package/dist/server/seed.js +100 -84
- package/package.json +12 -12
|
@@ -101,20 +101,21 @@ export function createUsersController({ api, safety }, { permissions = [], openO
|
|
|
101
101
|
return false;
|
|
102
102
|
const draft = draftGeneration;
|
|
103
103
|
set({ saving: true, formError: null });
|
|
104
|
+
let created = false;
|
|
104
105
|
try {
|
|
105
106
|
await api.createUser({ username: s.username, password: s.password }, { ...call, ...(s.email ? { email: s.email } : {}) });
|
|
106
107
|
if (draft === draftGeneration)
|
|
107
108
|
set({ username: '', email: '', password: '', formOpen: false });
|
|
108
109
|
await load({});
|
|
109
|
-
|
|
110
|
+
created = store.active({});
|
|
110
111
|
}
|
|
111
112
|
catch (error) {
|
|
112
113
|
set({ formError: report(error, 'failed to create user') });
|
|
113
|
-
return false;
|
|
114
114
|
}
|
|
115
115
|
finally {
|
|
116
116
|
set({ saving: false });
|
|
117
117
|
}
|
|
118
|
+
return created;
|
|
118
119
|
}
|
|
119
120
|
function toggleExpanded({ principalId }, _optional = {}) {
|
|
120
121
|
if (!store.active({}) || !row(principalId))
|
|
@@ -123,81 +124,99 @@ export function createUsersController({ api, safety }, { permissions = [], openO
|
|
|
123
124
|
// A stale grant cannot clear a newer panel's selection or busy flag. Reset on switching.
|
|
124
125
|
set({ expandedId: read({}).expandedId === principalId ? null : principalId, editEmail: row(principalId).email ?? '', pendingRoleId: '', pendingPolicyId: '', grantError: null, grantSaving: false });
|
|
125
126
|
}
|
|
127
|
+
/** Per-kind panel writes: the authority checked, the selection that must be present, the call,
|
|
128
|
+
* and the selection cleared on success (email keeps its field, but still notifies). */
|
|
129
|
+
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) },
|
|
136
|
+
};
|
|
137
|
+
/** Busy, expanded-row and authority checks run before the selection check, so only a refusal of authority reports. */
|
|
138
|
+
function panelSavable(s, principalId, panel) {
|
|
139
|
+
return !s.grantSaving && s.expandedId === principalId && gate(principalId, panel.action) && !panel.missing(s);
|
|
140
|
+
}
|
|
126
141
|
async function savePanel({ principalId, kind }, _optional = {}) {
|
|
127
|
-
const s = read({}),
|
|
128
|
-
if (s
|
|
129
|
-
return false;
|
|
130
|
-
if ((kind === 'role' && !s.pendingRoleId) || (kind === 'policy' && !s.pendingPolicyId))
|
|
142
|
+
const s = read({}), panel = panelWrites[kind];
|
|
143
|
+
if (!panelSavable(s, principalId, panel))
|
|
131
144
|
return false;
|
|
132
145
|
const generation = panelGeneration, draft = draftGeneration;
|
|
133
146
|
set({ grantSaving: true, grantError: null });
|
|
147
|
+
let saved = false;
|
|
134
148
|
try {
|
|
135
|
-
|
|
136
|
-
await api.updateUser({ principalId }, { ...call, email: s.editEmail });
|
|
137
|
-
else if (kind === 'role')
|
|
138
|
-
await api.assignRole({ principalId, roleId: s.pendingRoleId }, call);
|
|
139
|
-
else
|
|
140
|
-
await api.attachPolicy({ principalId, policyId: s.pendingPolicyId }, call);
|
|
149
|
+
await panel.write(principalId, s);
|
|
141
150
|
if (generation === panelGeneration && draft === draftGeneration)
|
|
142
|
-
set(
|
|
151
|
+
set(panel.cleared);
|
|
143
152
|
await load({});
|
|
144
|
-
|
|
153
|
+
saved = store.active({});
|
|
145
154
|
}
|
|
146
155
|
catch (error) {
|
|
147
156
|
if (generation === panelGeneration)
|
|
148
|
-
set({ grantError: report(error,
|
|
149
|
-
return false;
|
|
157
|
+
set({ grantError: report(error, panel.fallback) });
|
|
150
158
|
}
|
|
151
159
|
finally {
|
|
152
160
|
if (generation === panelGeneration)
|
|
153
161
|
set({ grantSaving: false });
|
|
154
162
|
}
|
|
163
|
+
return saved;
|
|
155
164
|
}
|
|
156
165
|
async function enableUser({ principalId }, _optional = {}) {
|
|
157
166
|
if (read({}).rowSavingId || !gate(principalId, 'enable'))
|
|
158
167
|
return false;
|
|
159
168
|
set({ rowSavingId: principalId, rowError: null });
|
|
169
|
+
let enabled = false;
|
|
160
170
|
try {
|
|
161
171
|
await api.enableUser({ principalId }, call);
|
|
162
172
|
await load({});
|
|
163
|
-
|
|
173
|
+
enabled = store.active({});
|
|
164
174
|
}
|
|
165
175
|
catch (error) {
|
|
166
176
|
set({ rowError: report(error, 'failed to change status') });
|
|
167
|
-
return false;
|
|
168
177
|
}
|
|
169
178
|
finally {
|
|
170
179
|
if (read({}).rowSavingId === principalId)
|
|
171
180
|
set({ rowSavingId: null });
|
|
172
181
|
}
|
|
182
|
+
return enabled;
|
|
173
183
|
}
|
|
174
|
-
|
|
175
|
-
|
|
184
|
+
/** The pending target when it may be confirmed now; a reset also needs a matching, non-empty pair. */
|
|
185
|
+
function confirmable(s) {
|
|
186
|
+
const target = s.pending;
|
|
176
187
|
if (!target || s.confirming || s.rowSavingId || !gate(target.user.principalId, target.kind))
|
|
177
|
-
return
|
|
188
|
+
return null;
|
|
178
189
|
if (target.kind === 'reset' && (!s.newPassword || s.newPassword !== s.confirmPassword))
|
|
190
|
+
return null;
|
|
191
|
+
return target;
|
|
192
|
+
}
|
|
193
|
+
async function destroy(target, s) {
|
|
194
|
+
const principalId = target.user.principalId;
|
|
195
|
+
if (target.kind === 'reset') {
|
|
196
|
+
await api.resetUserPassword({ principalId, password: s.newPassword }, call);
|
|
197
|
+
set({ notice: principalId === s.caller?.user.id ? 'Password changed. Sign in again with your new password.' : `Password reset for "${target.user.username}" — every active session for this user was revoked.` });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (target.kind === 'disable')
|
|
201
|
+
await api.disableUser({ principalId }, call);
|
|
202
|
+
else
|
|
203
|
+
await api.deleteUser({ principalId }, call);
|
|
204
|
+
await load({});
|
|
205
|
+
}
|
|
206
|
+
async function confirmDestructive(_required, _optional = {}) {
|
|
207
|
+
const s = read({}), target = confirmable(s);
|
|
208
|
+
if (!target)
|
|
179
209
|
return false;
|
|
180
210
|
set({ confirming: true, rowError: null, passwordError: null });
|
|
181
|
-
let success = false;
|
|
211
|
+
let success = false, live = false;
|
|
182
212
|
try {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
await api.resetUserPassword({ principalId, password: s.newPassword }, call);
|
|
186
|
-
set({ notice: principalId === s.caller?.user.id ? 'Password changed. Sign in again with your new password.' : `Password reset for "${target.user.username}" — every active session for this user was revoked.` });
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
if (target.kind === 'disable')
|
|
190
|
-
await api.disableUser({ principalId }, call);
|
|
191
|
-
else
|
|
192
|
-
await api.deleteUser({ principalId }, call);
|
|
193
|
-
await load({});
|
|
194
|
-
}
|
|
213
|
+
await destroy(target, s);
|
|
214
|
+
// Read liveness before the close below: its host callback may dispose this controller.
|
|
195
215
|
success = true;
|
|
196
|
-
|
|
216
|
+
live = store.active({});
|
|
197
217
|
}
|
|
198
218
|
catch (error) {
|
|
199
219
|
set(target.kind === 'reset' ? { passwordError: report(error, 'failed to reset password') } : { rowError: report(error, `failed to ${target.kind} user`) });
|
|
200
|
-
return false;
|
|
201
220
|
}
|
|
202
221
|
finally {
|
|
203
222
|
// Reset stays open on failure so the operator can retry the exact typed credential.
|
|
@@ -205,6 +224,7 @@ export function createUsersController({ api, safety }, { permissions = [], openO
|
|
|
205
224
|
close();
|
|
206
225
|
set({ confirming: false });
|
|
207
226
|
}
|
|
227
|
+
return live;
|
|
208
228
|
}
|
|
209
229
|
return {
|
|
210
230
|
getSnapshot: store.getSnapshot, subscribe: store.subscribe, allowed,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Shared pieces of the People and Roles error-copy mappers (`describePeopleError`, `describeRolesError`). */
|
|
2
|
+
/** The string `code` of a structured error, or undefined when there is none. */
|
|
3
|
+
export declare function errorCode({ error }: {
|
|
4
|
+
error: unknown;
|
|
5
|
+
}, _optional?: Record<string, never>): string | undefined;
|
|
6
|
+
/** A real Error's non-empty message, else the given fallback copy. */
|
|
7
|
+
export declare function errorMessageOr({ error, fallback }: {
|
|
8
|
+
error: unknown;
|
|
9
|
+
fallback: string;
|
|
10
|
+
}, _optional?: Record<string, never>): string;
|
|
11
|
+
//# sourceMappingURL=error-copy.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared pieces of the People and Roles error-copy mappers (`describePeopleError`, `describeRolesError`). */
|
|
2
|
+
/** The string `code` of a structured error, or undefined when there is none. */
|
|
3
|
+
export function errorCode({ error }, _optional = {}) {
|
|
4
|
+
return error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
|
|
5
|
+
}
|
|
6
|
+
/** A real Error's non-empty message, else the given fallback copy. */
|
|
7
|
+
export function errorMessageOr({ error, fallback }, _optional = {}) {
|
|
8
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=error-copy.js.map
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
+
import { errorCode, errorMessageOr } from './error-copy.js';
|
|
1
2
|
export function hasAdminGrant({ permissions, permission }, _optional = {}) {
|
|
2
3
|
return permissions.includes('*') || permissions.includes(permission);
|
|
3
4
|
}
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
* Unknown ownership never becomes permission. The member.manage self-email exception does not
|
|
7
|
-
* confer any credential, status or grant authority over other operators. */
|
|
8
|
-
export function userActionAllowed({ user, action, permissions, caller, safety }, _optional = {}) {
|
|
9
|
-
if (!caller)
|
|
10
|
-
return false;
|
|
5
|
+
/** Unknown ownership never becomes permission, and only a wildcard holder may act over an owner. */
|
|
6
|
+
function ownershipAllows({ user, permissions, safety }) {
|
|
11
7
|
let owner;
|
|
12
8
|
try {
|
|
13
9
|
owner = safety.ownerStatus({ principalId: user.principalId });
|
|
@@ -15,34 +11,44 @@ export function userActionAllowed({ user, action, permissions, caller, safety },
|
|
|
15
11
|
catch {
|
|
16
12
|
return false;
|
|
17
13
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
14
|
+
return owner === 'operator' || (owner === 'owner' && permissions.includes('*'));
|
|
15
|
+
}
|
|
16
|
+
const userManage = (c) => hasAdminGrant({ permissions: c.permissions, permission: 'user.manage' });
|
|
17
|
+
/** The seeded owner must be known, and the target must not be it. */
|
|
18
|
+
const targetsNonSeeded = (c) => c.seeded !== null && c.user.principalId !== c.seeded;
|
|
19
|
+
// A Map (not an object literal) so a malformed action such as "constructor" cannot resolve to an
|
|
20
|
+
// Object.prototype member; any action without a rule of its own needs user.manage.
|
|
21
|
+
const ACTION_RULES = new Map([
|
|
22
|
+
['delete', c => userManage(c) && c.caller.canManageUserTrash === true && !c.self && targetsNonSeeded(c)],
|
|
23
|
+
['disable', c => c.user.status === 'active' && targetsNonSeeded(c) && userManage(c)],
|
|
24
|
+
['enable', c => c.user.status === 'disabled' && userManage(c)],
|
|
25
|
+
// The seeded owner may reset its own password, but nobody else may reset it.
|
|
26
|
+
['reset', c => c.seeded !== null && (c.self || c.user.principalId !== c.seeded) && userManage(c)],
|
|
27
|
+
['grants', c => hasAdminGrant({ permissions: c.permissions, permission: 'role.manage' })],
|
|
28
|
+
['email', c => (c.self && hasAdminGrant({ permissions: c.permissions, permission: 'member.manage' })) || userManage(c)],
|
|
29
|
+
]);
|
|
30
|
+
/** Server trash authority is a required additional check. This portable UI also requires
|
|
31
|
+
* user.manage: member.manage by itself must never reveal actions over operators.
|
|
32
|
+
* Unknown ownership never becomes permission. The member.manage self-email exception does not
|
|
33
|
+
* confer any credential, status or grant authority over other operators. */
|
|
34
|
+
export function userActionAllowed({ user, action, permissions, caller, safety }, _optional = {}) {
|
|
35
|
+
if (!caller || !ownershipAllows({ user, permissions, safety }))
|
|
28
36
|
return false;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (action === 'email' && self && hasAdminGrant({ permissions, permission: 'member.manage' }))
|
|
32
|
-
return true;
|
|
33
|
-
return hasAdminGrant({ permissions, permission: 'user.manage' });
|
|
37
|
+
const context = { user, permissions, caller, seeded: safety.seededOwnerPrincipalId, self: caller.user.id === user.principalId };
|
|
38
|
+
return (ACTION_RULES.get(action) ?? userManage)(context);
|
|
34
39
|
}
|
|
40
|
+
const PEOPLE_ERROR_COPY = Object.freeze({
|
|
41
|
+
FORBIDDEN: 'You do not have permission to do that.', GRANT_EXCEEDS_ISSUER: 'You cannot grant a permission you do not hold.',
|
|
42
|
+
RESOURCE_CONFLICT: 'That username is already in use.', OWNER_REQUIRED: 'The workspace must keep at least one active owner.',
|
|
43
|
+
SELF_DELETE: 'You cannot delete your own account.', USER_IN_TRASH: 'This user is in the Trash; restore them first.',
|
|
44
|
+
USERNAME_IN_TRASH: 'A user with this username is in the Trash; restore or delete them permanently first.',
|
|
45
|
+
});
|
|
35
46
|
export function describePeopleError({ error, fallback }, _optional = {}) {
|
|
36
|
-
const
|
|
37
|
-
FORBIDDEN: 'You do not have permission to do that.', GRANT_EXCEEDS_ISSUER: 'You cannot grant a permission you do not hold.',
|
|
38
|
-
RESOURCE_CONFLICT: 'That username is already in use.', OWNER_REQUIRED: 'The workspace must keep at least one active owner.',
|
|
39
|
-
SELF_DELETE: 'You cannot delete your own account.', USER_IN_TRASH: 'This user is in the Trash; restore them first.',
|
|
40
|
-
USERNAME_IN_TRASH: 'A user with this username is in the Trash; restore or delete them permanently first.',
|
|
41
|
-
};
|
|
47
|
+
const code = errorCode({ error });
|
|
42
48
|
// Own keys only: a server code such as "constructor" must not resolve to an Object.prototype member.
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
45
|
-
return
|
|
49
|
+
if (code !== undefined && Object.hasOwn(PEOPLE_ERROR_COPY, code))
|
|
50
|
+
return PEOPLE_ERROR_COPY[code];
|
|
51
|
+
return errorMessageOr({ error, fallback });
|
|
46
52
|
}
|
|
47
53
|
export function grantLabel({ ids, records }, _optional = {}) {
|
|
48
54
|
// Unknown grant ids stay visible: dropping them would conceal dangling references.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AdminMember } from '../../models.js';
|
|
2
2
|
export declare function MemberRow({ member }: {
|
|
3
3
|
member: AdminMember;
|
|
4
|
-
}
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
5
|
//# sourceMappingURL=MemberRow.d.ts.map
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Badge, Button, Menu, Notice, Spinner } from '@jini-ai/ui-kit/react';
|
|
3
3
|
import { useMemberRow } from '../hooks/MembersPage.hooks.js';
|
|
4
|
-
|
|
4
|
+
/** The expanded detail cell: loading wins over an error, and an error over the loaded record. */
|
|
5
|
+
function MemberDetail({ v }) {
|
|
6
|
+
if (v.loading)
|
|
7
|
+
return _jsx(Spinner, { label: "Loading detail\u2026" });
|
|
8
|
+
if (v.s.detailError)
|
|
9
|
+
return _jsx(Notice, { tone: "danger", children: v.s.detailError });
|
|
10
|
+
if (!v.detail)
|
|
11
|
+
return null;
|
|
12
|
+
return _jsxs("dl", { children: [_jsx("dt", { children: "ID" }), _jsx("dd", { children: v.detail.id }), _jsx("dt", { children: "Email verified" }), _jsx("dd", { children: v.verified }), _jsx("dt", { children: "Updated" }), _jsx("dd", { children: v.detail.updatedAt }), _jsx("dt", { children: "Version" }), _jsx("dd", { children: v.detail.version })] });
|
|
13
|
+
}
|
|
14
|
+
export function MemberRow({ member }) {
|
|
5
15
|
const v = useMemberRow({ member });
|
|
6
|
-
return _jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: _jsx(Button, { onPress: v.toggle, attrs: { 'aria-expanded': v.expanded }, children: member.email }) }), _jsx("td", { children: member.name ?? '—' }), _jsx("td", { children: _jsx(Badge, { children: member.status }) }), _jsx("td", { children: v.created }), _jsxs("td", { children: [v.items.length ? _jsx(Menu, { label: v.menuLabel, items: v.items }) : null, v.rs.error ? _jsx(Notice, { tone: "danger", children: v.rs.error }) : null, v.rs.notice ? _jsx(Notice, { children: v.rs.notice }) : null] })] }), v.expanded ? _jsx("tr", { children: _jsx("td", { colSpan: 5, children:
|
|
16
|
+
return _jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: _jsx(Button, { onPress: v.toggle, attrs: { 'aria-expanded': v.expanded }, children: member.email }) }), _jsx("td", { children: member.name ?? '—' }), _jsx("td", { children: _jsx(Badge, { children: member.status }) }), _jsx("td", { children: v.created }), _jsxs("td", { children: [v.items.length ? _jsx(Menu, { label: v.menuLabel, items: v.items }) : null, v.rs.error ? _jsx(Notice, { tone: "danger", children: v.rs.error }) : null, v.rs.notice ? _jsx(Notice, { children: v.rs.notice }) : null] })] }), v.expanded ? _jsx("tr", { children: _jsx("td", { colSpan: 5, children: _jsx(MemberDetail, { v: v }) }) }) : null] });
|
|
7
17
|
}
|
|
8
18
|
//# sourceMappingURL=MemberRow.js.map
|
|
@@ -1,33 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type AuthScope } from './AuthScope.hooks.js';
|
|
2
|
+
export { useAuthScope } from './AuthScope.hooks.js';
|
|
3
|
+
export type { AuthScope, AuthPageProps } from './AuthScope.hooks.js';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
2
5
|
import type { AdminInstance } from '@jini-ai/admin/core/module';
|
|
3
|
-
import type { AuthApiPort } from '../../ports.js';
|
|
4
6
|
import { authModule } from '../../auth.module.js';
|
|
5
|
-
import type { AdminAccount } from "../../models.js";
|
|
6
|
-
export interface AuthScope {
|
|
7
|
-
readonly api: AuthApiPort;
|
|
8
|
-
readonly permissions: readonly string[];
|
|
9
|
-
}
|
|
10
|
-
export interface AuthPageProps {
|
|
11
|
-
readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
|
|
12
|
-
readonly params?: Readonly<Record<string, unknown>>;
|
|
13
|
-
}>>>>;
|
|
14
|
-
readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
|
|
15
|
-
readonly title?: string;
|
|
16
|
-
readonly initialUsername?: string;
|
|
17
|
-
readonly onLogin: (required: {
|
|
18
|
-
user: AdminAccount;
|
|
19
|
-
}, optional?: Record<string, never>) => void;
|
|
20
|
-
}
|
|
21
|
-
export declare function useAuthScope(_required?: Record<string, never>, _optional?: Record<string, never>): AuthScope;
|
|
22
7
|
/** Descriptor identity must be the exact enriched object supplied to createAdmin. */
|
|
23
8
|
export declare function createAuthBinding({ module }: {
|
|
24
9
|
module: typeof authModule;
|
|
25
10
|
}, _optional?: Record<string, never>): {
|
|
26
11
|
pages: {
|
|
27
12
|
auth: {
|
|
28
|
-
Page: LazyExoticComponent<typeof import("../pages/LoginPage.js").default>;
|
|
13
|
+
Page: import("react").LazyExoticComponent<typeof import("../pages/LoginPage.js").default>;
|
|
29
14
|
tabs: {
|
|
30
|
-
auth: LazyExoticComponent<typeof import("../tabs/AuthTab.js").default>;
|
|
15
|
+
auth: import("react").LazyExoticComponent<typeof import("../tabs/AuthTab.js").default>;
|
|
31
16
|
};
|
|
32
17
|
};
|
|
33
18
|
};
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ScopeContext } from './AuthScope.hooks.js';
|
|
2
|
+
export { useAuthScope } from './AuthScope.hooks.js';
|
|
3
|
+
import { createElement, lazy } from 'react';
|
|
2
4
|
import { authModule } from '../../auth.module.js';
|
|
3
|
-
const ScopeContext = createContext(null);
|
|
4
|
-
export function useAuthScope(_required = {}, _optional = {}) {
|
|
5
|
-
const scope = useContext(ScopeContext);
|
|
6
|
-
if (!scope)
|
|
7
|
-
throw new Error('Auth admin scope is unavailable.');
|
|
8
|
-
return scope;
|
|
9
|
-
}
|
|
10
5
|
/** Descriptor identity must be the exact enriched object supplied to createAdmin. */
|
|
11
6
|
export function createAuthBinding({ module }, _optional = {}) {
|
|
12
7
|
const pages = { auth: { Page: lazy(() => import('../pages/LoginPage.js')), tabs: { auth: lazy(() => import('../tabs/AuthTab.js')) } } };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ComponentType, LazyExoticComponent } from 'react';
|
|
2
|
+
import type { AdminInstance } from '@jini-ai/admin/core/module';
|
|
3
|
+
import type { AuthApiPort } from '../../ports.js';
|
|
4
|
+
import type { AdminAccount } from '../../models.js';
|
|
5
|
+
export interface AuthScope {
|
|
6
|
+
readonly api: AuthApiPort;
|
|
7
|
+
readonly permissions: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const ScopeContext: import("react").Context<AuthScope | null>;
|
|
10
|
+
export interface AuthPageProps {
|
|
11
|
+
readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
|
|
12
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
13
|
+
}>>>>;
|
|
14
|
+
readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
|
|
15
|
+
readonly title?: string;
|
|
16
|
+
readonly initialUsername?: string;
|
|
17
|
+
readonly onLogin: (required: {
|
|
18
|
+
user: AdminAccount;
|
|
19
|
+
}, optional?: Record<string, never>) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function useAuthScope(_required?: Record<string, never>, _optional?: Record<string, never>): AuthScope;
|
|
22
|
+
//# sourceMappingURL=AuthScope.hooks.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Scope and page contracts must not import the lazy page/binding assembly.
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
export const ScopeContext = createContext(null);
|
|
4
|
+
export function useAuthScope(_required = {}, _optional = {}) {
|
|
5
|
+
const scope = useContext(ScopeContext);
|
|
6
|
+
if (!scope)
|
|
7
|
+
throw new Error('Auth admin scope is unavailable.');
|
|
8
|
+
return scope;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=AuthScope.hooks.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FormEvent } from 'react';
|
|
2
2
|
import type { AuthState } from '../../models.js';
|
|
3
3
|
import type { AuthController } from '../../controllers/auth.controller.js';
|
|
4
|
-
import type { AuthPageProps } from './
|
|
4
|
+
import type { AuthPageProps } from './AuthScope.hooks.js';
|
|
5
5
|
export interface AuthView {
|
|
6
6
|
controller: AuthController;
|
|
7
7
|
state: AuthState;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement, createContext, useContext, useMemo } from 'react';
|
|
2
2
|
import { createAuthController } from '../../controllers/auth.controller.js';
|
|
3
|
-
import { useAuthScope } from './
|
|
3
|
+
import { useAuthScope } from './AuthScope.hooks.js';
|
|
4
4
|
import { usePeopleController } from './PeopleController.hooks.js';
|
|
5
5
|
export const AuthViewContext = createContext(null);
|
|
6
6
|
export function useLoginPage(props, _optional = {}) {
|
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type MembersScope } from './MembersScope.hooks.js';
|
|
2
|
+
export { useMembersScope } from './MembersScope.hooks.js';
|
|
3
|
+
export type { MembersScope, MembersPageProps } from './MembersScope.hooks.js';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
2
5
|
import type { AdminInstance } from '@jini-ai/admin/core/module';
|
|
3
|
-
import type { MembersApiPort } from '../../ports.js';
|
|
4
6
|
import { membersModule } from '../../members.module.js';
|
|
5
|
-
import type { MembersRefreshPort } from "../../ports.js";
|
|
6
|
-
export interface MembersScope {
|
|
7
|
-
readonly api: MembersApiPort;
|
|
8
|
-
readonly permissions: readonly string[];
|
|
9
|
-
}
|
|
10
|
-
export interface MembersPageProps {
|
|
11
|
-
readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
|
|
12
|
-
readonly params?: Readonly<Record<string, unknown>>;
|
|
13
|
-
}>>>>;
|
|
14
|
-
readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
|
|
15
|
-
readonly refresh?: MembersRefreshPort;
|
|
16
|
-
}
|
|
17
|
-
export declare function useMembersScope(_required?: Record<string, never>, _optional?: Record<string, never>): MembersScope;
|
|
18
7
|
/** Descriptor identity must be the exact enriched object supplied to createAdmin. */
|
|
19
8
|
export declare function createMembersBinding({ module }: {
|
|
20
9
|
module: typeof membersModule;
|
|
21
10
|
}, _optional?: Record<string, never>): {
|
|
22
11
|
pages: {
|
|
23
12
|
members: {
|
|
24
|
-
Page: LazyExoticComponent<typeof import("../pages/MembersPage.js").default>;
|
|
13
|
+
Page: import("react").LazyExoticComponent<typeof import("../pages/MembersPage.js").default>;
|
|
25
14
|
tabs: {
|
|
26
|
-
members: LazyExoticComponent<typeof import("../tabs/MembersTab.js").default>;
|
|
15
|
+
members: import("react").LazyExoticComponent<typeof import("../tabs/MembersTab.js").default>;
|
|
27
16
|
};
|
|
28
17
|
};
|
|
29
18
|
};
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ScopeContext } from './MembersScope.hooks.js';
|
|
2
|
+
export { useMembersScope } from './MembersScope.hooks.js';
|
|
3
|
+
import { createElement, lazy } from 'react';
|
|
2
4
|
import { membersModule } from '../../members.module.js';
|
|
3
|
-
const ScopeContext = createContext(null);
|
|
4
|
-
export function useMembersScope(_required = {}, _optional = {}) {
|
|
5
|
-
const scope = useContext(ScopeContext);
|
|
6
|
-
if (!scope)
|
|
7
|
-
throw new Error('Members admin scope is unavailable.');
|
|
8
|
-
return scope;
|
|
9
|
-
}
|
|
10
5
|
/** Descriptor identity must be the exact enriched object supplied to createAdmin. */
|
|
11
6
|
export function createMembersBinding({ module }, _optional = {}) {
|
|
12
7
|
const pages = { members: { Page: lazy(() => import('../pages/MembersPage.js')), tabs: { members: lazy(() => import('../tabs/MembersTab.js')) } } };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MembersState, AdminMember } from '../../models.js';
|
|
2
2
|
import type { MembersController } from '../../controllers/members.controller.js';
|
|
3
3
|
import type { MenuItem } from '@jini-ai/ui-kit/react';
|
|
4
|
-
import type { MembersPageProps } from './
|
|
4
|
+
import type { MembersPageProps } from './MembersScope.hooks.js';
|
|
5
5
|
export interface MembersView {
|
|
6
6
|
controller: MembersController;
|
|
7
7
|
state: MembersState;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createElement, createContext, useContext, useMemo } from 'react';
|
|
2
2
|
import { createMembersController } from '../../controllers/members.controller.js';
|
|
3
3
|
import { hasAdminGrant } from '../../people.rules.js';
|
|
4
|
-
import { useMembersScope } from './
|
|
4
|
+
import { useMembersScope } from './MembersScope.hooks.js';
|
|
5
5
|
import { usePeopleController } from './PeopleController.hooks.js';
|
|
6
6
|
export const MembersViewContext = createContext(null);
|
|
7
7
|
export function useMembersView(_required = {}, _optional = {}) { const v = useContext(MembersViewContext); if (!v)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ComponentType, LazyExoticComponent } from 'react';
|
|
2
|
+
import type { AdminInstance } from '@jini-ai/admin/core/module';
|
|
3
|
+
import type { MembersApiPort, MembersRefreshPort } from '../../ports.js';
|
|
4
|
+
export interface MembersScope {
|
|
5
|
+
readonly api: MembersApiPort;
|
|
6
|
+
readonly permissions: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const ScopeContext: import("react").Context<MembersScope | null>;
|
|
9
|
+
export interface MembersPageProps {
|
|
10
|
+
readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<{
|
|
11
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
12
|
+
}>>>>;
|
|
13
|
+
readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
|
|
14
|
+
readonly refresh?: MembersRefreshPort;
|
|
15
|
+
}
|
|
16
|
+
export declare function useMembersScope(_required?: Record<string, never>, _optional?: Record<string, never>): MembersScope;
|
|
17
|
+
//# sourceMappingURL=MembersScope.hooks.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Scope and page contracts must not import the lazy page/binding assembly.
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
export const ScopeContext = createContext(null);
|
|
4
|
+
export function useMembersScope(_required = {}, _optional = {}) {
|
|
5
|
+
const scope = useContext(ScopeContext);
|
|
6
|
+
if (!scope)
|
|
7
|
+
throw new Error('Members admin scope is unavailable.');
|
|
8
|
+
return scope;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=MembersScope.hooks.js.map
|
|
@@ -1,33 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type RolesScope } from './RolesScope.hooks.js';
|
|
2
|
+
export { useRolesScope } from './RolesScope.hooks.js';
|
|
3
|
+
export type { RolesScope, RolesTabProps, RolesPageProps } from './RolesScope.hooks.js';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
2
5
|
import type { AdminInstance } from '@jini-ai/admin/core/module';
|
|
3
6
|
import { rolesModule } from '../../roles.module.js';
|
|
4
|
-
import type { RolesApiPort } from '../../ports.js';
|
|
5
|
-
export interface RolesScope {
|
|
6
|
-
readonly api: RolesApiPort;
|
|
7
|
-
readonly permissions: readonly string[];
|
|
8
|
-
}
|
|
9
|
-
export interface RolesTabProps {
|
|
10
|
-
readonly params?: Readonly<Record<string, unknown>>;
|
|
11
|
-
}
|
|
12
|
-
export interface RolesPageProps {
|
|
13
|
-
readonly tabs: Readonly<Record<string, LazyExoticComponent<ComponentType<RolesTabProps>>>>;
|
|
14
|
-
readonly description: ReturnType<AdminInstance['describe']>['pages'][number];
|
|
15
|
-
readonly requestedTab?: string;
|
|
16
|
-
readonly onTabChange?: (required: {
|
|
17
|
-
tab: string;
|
|
18
|
-
}, optional?: Record<string, never>) => void;
|
|
19
|
-
}
|
|
20
|
-
export declare function useRolesScope(_required?: Record<string, never>, _optional?: Record<string, never>): RolesScope;
|
|
21
7
|
/** A private scope binds only this module's declared ports. No admin React internals are needed. */
|
|
22
8
|
export declare function createRolesBinding({ module }: {
|
|
23
9
|
module: typeof rolesModule;
|
|
24
10
|
}, _optional?: Record<string, never>): {
|
|
25
11
|
pages: {
|
|
26
12
|
roles: {
|
|
27
|
-
Page: LazyExoticComponent<typeof import("../pages/RolesPage.js").default>;
|
|
13
|
+
Page: import("react").LazyExoticComponent<typeof import("../pages/RolesPage.js").default>;
|
|
28
14
|
tabs: {
|
|
29
|
-
roles: LazyExoticComponent<typeof import("../tabs/RolesTab.js").default>;
|
|
30
|
-
policies: LazyExoticComponent<typeof import("../tabs/PoliciesTab.js").default>;
|
|
15
|
+
roles: import("react").LazyExoticComponent<typeof import("../tabs/RolesTab.js").default>;
|
|
16
|
+
policies: import("react").LazyExoticComponent<typeof import("../tabs/PoliciesTab.js").default>;
|
|
31
17
|
};
|
|
32
18
|
};
|
|
33
19
|
};
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ScopeContext } from './RolesScope.hooks.js';
|
|
2
|
+
export { useRolesScope } from './RolesScope.hooks.js';
|
|
3
|
+
import { createElement, lazy } from 'react';
|
|
2
4
|
import { rolesModule } from '../../roles.module.js';
|
|
3
|
-
const ScopeContext = createContext(null);
|
|
4
|
-
export function useRolesScope(_required = {}, _optional = {}) {
|
|
5
|
-
const scope = useContext(ScopeContext);
|
|
6
|
-
if (!scope)
|
|
7
|
-
throw new Error('Roles admin scope is unavailable.');
|
|
8
|
-
return scope;
|
|
9
|
-
}
|
|
10
5
|
/** A private scope binds only this module's declared ports. No admin React internals are needed. */
|
|
11
6
|
export function createRolesBinding({ module }, _optional = {}) {
|
|
12
7
|
const pages = { roles: { Page: lazy(() => import('../pages/RolesPage.js')), tabs: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';
|
|
2
2
|
import { createRolesController } from '../../controllers/roles.controller.js';
|
|
3
|
-
import { useRolesScope } from './
|
|
3
|
+
import { useRolesScope } from './RolesScope.hooks.js';
|
|
4
4
|
/** Controller creation belongs to the effect, so StrictMode replay gets a fresh instance.
|
|
5
5
|
* Both tabs read the page-owned controller; neither tab starts its own duplicate fetches. */
|
|
6
6
|
export function useRolesController(_required = {}, _optional = {}) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RolesPageProps } from './
|
|
1
|
+
import type { RolesPageProps } from './RolesScope.hooks.js';
|
|
2
2
|
import type { RolesController } from '../../controllers/roles.controller.js';
|
|
3
3
|
import type { RolesState } from '../../models.js';
|
|
4
4
|
import type { ConfirmDialogProps } from '@jini-ai/ui-kit/react';
|
|
@@ -51,7 +51,7 @@ export declare function useRolesPage(props: RolesPageProps, _optional?: Record<s
|
|
|
51
51
|
items: {
|
|
52
52
|
id: string;
|
|
53
53
|
label: string;
|
|
54
|
-
content: import("react").FunctionComponentElement<import("./
|
|
54
|
+
content: import("react").FunctionComponentElement<import("./RolesScope.hooks.js").RolesTabProps | (import("./RolesScope.hooks.js").RolesTabProps & import("react").RefAttributes<import("react").Component<import("./RolesScope.hooks.js").RolesTabProps, any, any>>)> | null;
|
|
55
55
|
}[];
|
|
56
56
|
tab: string;
|
|
57
57
|
confirm: ConfirmDialogProps;
|