apiblaze 0.18.3 → 0.18.7
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/dist/index.js +583 -507
- package/dist/react/index.js +366 -233
- package/dist/react/index.mjs +366 -233
- package/dist/server/index.d.mts +21 -0
- package/dist/server/index.d.ts +21 -0
- package/dist/server/index.js +29 -8
- package/dist/server/index.mjs +29 -8
- package/package.json +3 -3
package/dist/react/index.js
CHANGED
|
@@ -74,14 +74,79 @@ function relTime(iso) {
|
|
|
74
74
|
if (d < 30) return `${d}d ago`;
|
|
75
75
|
return new Date(iso).toLocaleDateString();
|
|
76
76
|
}
|
|
77
|
+
function Typeahead(props) {
|
|
78
|
+
const [q, setQ] = React.useState("");
|
|
79
|
+
const [results, setResults] = React.useState(null);
|
|
80
|
+
const [loading, setLoading] = React.useState(false);
|
|
81
|
+
const seq = React.useRef(0);
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const term = q.trim();
|
|
84
|
+
if (term.length < 2) {
|
|
85
|
+
setResults(null);
|
|
86
|
+
setLoading(false);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const my = ++seq.current;
|
|
90
|
+
setLoading(true);
|
|
91
|
+
const h = setTimeout(async () => {
|
|
92
|
+
try {
|
|
93
|
+
const r = await props.search(term);
|
|
94
|
+
if (my === seq.current) {
|
|
95
|
+
setResults(r);
|
|
96
|
+
setLoading(false);
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
if (my === seq.current) {
|
|
100
|
+
setResults([]);
|
|
101
|
+
setLoading(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, 250);
|
|
105
|
+
return () => clearTimeout(h);
|
|
106
|
+
}, [q]);
|
|
107
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative", flex: 1, minWidth: 180 }, children: [
|
|
108
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
109
|
+
"input",
|
|
110
|
+
{
|
|
111
|
+
style: { ...props.s.input, width: "100%" },
|
|
112
|
+
placeholder: props.placeholder,
|
|
113
|
+
value: q,
|
|
114
|
+
onChange: (e) => setQ(e.target.value),
|
|
115
|
+
disabled: props.disabled,
|
|
116
|
+
"aria-label": props.placeholder
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
q.trim().length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "absolute", zIndex: 5, left: 0, right: 0, top: "100%", marginTop: 4, background: props.s.surface, border: `1px solid ${props.s.border}`, borderRadius: 8, maxHeight: 220, overflowY: "auto", boxShadow: "0 6px 18px rgba(0,0,0,0.12)" }, children: [
|
|
120
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "8px 10px", fontSize: 12, color: props.s.muted }, children: "Searching\u2026" }),
|
|
121
|
+
!loading && results && results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "8px 10px", fontSize: 12, color: props.s.muted }, children: "No matches" }),
|
|
122
|
+
!loading && results && results.map((it) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
123
|
+
"button",
|
|
124
|
+
{
|
|
125
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
126
|
+
onClick: () => {
|
|
127
|
+
props.onPick(it);
|
|
128
|
+
setQ("");
|
|
129
|
+
setResults(null);
|
|
130
|
+
},
|
|
131
|
+
style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", border: "none", borderBottom: `1px solid ${props.s.border}`, background: "transparent", cursor: "pointer", color: props.s.text, fontFamily: "inherit", fontSize: 13 },
|
|
132
|
+
children: props.itemLabel(it)
|
|
133
|
+
},
|
|
134
|
+
props.itemKey(it)
|
|
135
|
+
))
|
|
136
|
+
] })
|
|
137
|
+
] });
|
|
138
|
+
}
|
|
77
139
|
var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint = "/api/apiblaze/groups", theme, title = "Users & groups", className }, ref) {
|
|
78
140
|
const t = { ...DEFAULTS, ...theme ?? {} };
|
|
79
141
|
const headerBg = t.headerBackground || t.surface;
|
|
80
142
|
const [access, setAccess] = React.useState("ok");
|
|
81
143
|
const [myEmail, setMyEmail] = React.useState(null);
|
|
82
144
|
const [groups, setGroups] = React.useState(null);
|
|
83
|
-
const [
|
|
145
|
+
const [groupTotal, setGroupTotal] = React.useState(0);
|
|
146
|
+
const [groupQuery, setGroupQuery] = React.useState("");
|
|
84
147
|
const [observed, setObserved] = React.useState([]);
|
|
148
|
+
const [observedNew, setObservedNew] = React.useState(0);
|
|
149
|
+
const [observedLoaded, setObservedLoaded] = React.useState(false);
|
|
85
150
|
const [loadError, setLoadError] = React.useState(null);
|
|
86
151
|
const [open, setOpen] = React.useState(null);
|
|
87
152
|
const [detail, setDetail] = React.useState(null);
|
|
@@ -89,15 +154,13 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
89
154
|
const [creating, setCreating] = React.useState(false);
|
|
90
155
|
const [newName, setNewName] = React.useState("");
|
|
91
156
|
const [showCreate, setShowCreate] = React.useState(false);
|
|
157
|
+
const [showNewUser, setShowNewUser] = React.useState(false);
|
|
158
|
+
const [newUserRef, setNewUserRef] = React.useState("");
|
|
92
159
|
const [pending, setPending] = React.useState(null);
|
|
93
160
|
const [err, setErr] = React.useState(null);
|
|
94
161
|
const [confirmDelete, setConfirmDelete] = React.useState(null);
|
|
95
|
-
const [showTraffic, setShowTraffic] = React.useState(false);
|
|
96
|
-
const [addSel, setAddSel] = React.useState("");
|
|
97
162
|
const [addRole, setAddRole] = React.useState("member");
|
|
98
|
-
const [nestSel, setNestSel] = React.useState("");
|
|
99
163
|
const [admins, setAdmins] = React.useState([]);
|
|
100
|
-
const [showAdmins, setShowAdmins] = React.useState(false);
|
|
101
164
|
const [newAdmin, setNewAdmin] = React.useState("");
|
|
102
165
|
const call = React.useCallback(async (body) => {
|
|
103
166
|
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
@@ -111,9 +174,11 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
111
174
|
const d = await call();
|
|
112
175
|
setAccess(d.access === "pending" ? "pending" : "ok");
|
|
113
176
|
setMyEmail(d.email ?? null);
|
|
114
|
-
setGroups(
|
|
115
|
-
|
|
116
|
-
|
|
177
|
+
setGroups(d.groups ?? []);
|
|
178
|
+
setGroupTotal(d.groupTotal ?? (d.groups?.length ?? 0));
|
|
179
|
+
setObservedNew(d.observedNew ?? 0);
|
|
180
|
+
setObservedLoaded(false);
|
|
181
|
+
setGroupQuery("");
|
|
117
182
|
if (d.access !== "pending") {
|
|
118
183
|
const a = await call({ action: "list-admins" });
|
|
119
184
|
setAdmins(a.admin_emails ?? []);
|
|
@@ -127,12 +192,26 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
127
192
|
load();
|
|
128
193
|
}, [load]);
|
|
129
194
|
React.useImperativeHandle(ref, () => ({ refresh: load }), [load]);
|
|
195
|
+
const searchGroups = React.useCallback(async (q) => {
|
|
196
|
+
setGroupQuery(q);
|
|
197
|
+
const d = await call({ action: "list-groups", q, offset: 0 });
|
|
198
|
+
setGroups(d.groups ?? []);
|
|
199
|
+
setGroupTotal(d.total ?? (d.groups?.length ?? 0));
|
|
200
|
+
}, [call]);
|
|
201
|
+
const loadMoreGroups = React.useCallback(async () => {
|
|
202
|
+
const d = await call({ action: "list-groups", q: groupQuery, offset: groups?.length ?? 0 });
|
|
203
|
+
setGroups((prev) => [...prev ?? [], ...d.groups ?? []]);
|
|
204
|
+
if (typeof d.total === "number") setGroupTotal(d.total);
|
|
205
|
+
}, [call, groupQuery, groups]);
|
|
206
|
+
const loadObserved = React.useCallback(async () => {
|
|
207
|
+
const d = await call({ action: "list-observed", offset: 0 });
|
|
208
|
+
setObserved(d.identities ?? []);
|
|
209
|
+
setObservedLoaded(true);
|
|
210
|
+
}, [call]);
|
|
130
211
|
const loadDetail = React.useCallback(async (groupId) => {
|
|
131
212
|
setDetailLoading(true);
|
|
132
213
|
setDetail(null);
|
|
133
214
|
setErr(null);
|
|
134
|
-
setAddSel("");
|
|
135
|
-
setNestSel("");
|
|
136
215
|
try {
|
|
137
216
|
setDetail(await call({ action: "group-detail", groupId }));
|
|
138
217
|
} catch (e) {
|
|
@@ -141,6 +220,14 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
141
220
|
setDetailLoading(false);
|
|
142
221
|
}
|
|
143
222
|
}, [call]);
|
|
223
|
+
const reloadAdmins = React.useCallback(async () => {
|
|
224
|
+
const a = await call({ action: "list-admins" });
|
|
225
|
+
setAdmins(a.admin_emails ?? []);
|
|
226
|
+
}, [call]);
|
|
227
|
+
const loadMoreMembers = React.useCallback(async (groupId, offset) => {
|
|
228
|
+
const d = await call({ action: "group-members", groupId, offset });
|
|
229
|
+
setDetail((prev) => prev && prev.id === groupId ? { ...prev, members: [...prev.members, ...d.members ?? []] } : prev);
|
|
230
|
+
}, [call]);
|
|
144
231
|
function toggle(g) {
|
|
145
232
|
if (open === g.id) {
|
|
146
233
|
setOpen(null);
|
|
@@ -155,7 +242,6 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
155
242
|
setErr(null);
|
|
156
243
|
try {
|
|
157
244
|
await call(body);
|
|
158
|
-
await load();
|
|
159
245
|
if (after) await after();
|
|
160
246
|
} catch (e) {
|
|
161
247
|
setErr(e instanceof Error ? e.message : "The action failed.");
|
|
@@ -172,7 +258,7 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
172
258
|
await call({ action: "create-group", name });
|
|
173
259
|
setNewName("");
|
|
174
260
|
setShowCreate(false);
|
|
175
|
-
await
|
|
261
|
+
await searchGroups(groupQuery);
|
|
176
262
|
} catch (e) {
|
|
177
263
|
setErr(e instanceof Error ? e.message : "Could not create the group.");
|
|
178
264
|
} finally {
|
|
@@ -182,7 +268,18 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
182
268
|
async function addAdmin() {
|
|
183
269
|
const email = newAdmin.trim().toLowerCase();
|
|
184
270
|
if (!email) return;
|
|
185
|
-
await act("add-admin", { action: "add-admin", email }, () =>
|
|
271
|
+
await act("add-admin", { action: "add-admin", email }, async () => {
|
|
272
|
+
setNewAdmin("");
|
|
273
|
+
await reloadAdmins();
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async function createUser() {
|
|
277
|
+
const ref2 = newUserRef.trim();
|
|
278
|
+
if (!ref2) return;
|
|
279
|
+
await act("create-user", { action: "create-user", ref: ref2 }, async () => {
|
|
280
|
+
setNewUserRef("");
|
|
281
|
+
await loadObserved();
|
|
282
|
+
});
|
|
186
283
|
}
|
|
187
284
|
const vars = {
|
|
188
285
|
// @ts-expect-error CSS custom properties
|
|
@@ -223,7 +320,18 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
223
320
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: card, children: [
|
|
224
321
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, padding: "14px 16px", borderBottom: `1px solid ${t.border}`, background: headerBg }, children: [
|
|
225
322
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 15 }, children: title }),
|
|
226
|
-
access === "ok" && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
323
|
+
access === "ok" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), disabled: busy, onClick: () => {
|
|
325
|
+
const nv = !showNewUser;
|
|
326
|
+
setShowNewUser(nv);
|
|
327
|
+
setShowCreate(false);
|
|
328
|
+
if (nv && !observedLoaded) loadObserved();
|
|
329
|
+
}, "aria-label": "Add a new user", children: "+ New user" }),
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("primary", true), disabled: busy, onClick: () => {
|
|
331
|
+
setShowCreate((v) => !v);
|
|
332
|
+
setShowNewUser(false);
|
|
333
|
+
}, "aria-label": "Create a new group", children: "+ New group" })
|
|
334
|
+
] })
|
|
227
335
|
] }),
|
|
228
336
|
access === "pending" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted, lineHeight: 1.5 }, children: [
|
|
229
337
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 700, color: t.text, marginBottom: 6, fontSize: 15 }, children: "Admin access required" }),
|
|
@@ -272,13 +380,88 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
272
380
|
setNewName("");
|
|
273
381
|
}, children: "Cancel" })
|
|
274
382
|
] }),
|
|
383
|
+
access === "ok" && showNewUser && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "12px 16px", borderBottom: `1px solid ${t.border}` }, children: [
|
|
384
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
|
|
385
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
386
|
+
"input",
|
|
387
|
+
{
|
|
388
|
+
style: { ...inputStyle, flex: 1 },
|
|
389
|
+
placeholder: "Email (or a user id)",
|
|
390
|
+
value: newUserRef,
|
|
391
|
+
onChange: (e) => setNewUserRef(e.target.value),
|
|
392
|
+
onKeyDown: (e) => e.key === "Enter" && createUser(),
|
|
393
|
+
"aria-label": "New user email or id"
|
|
394
|
+
}
|
|
395
|
+
),
|
|
396
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("primary", true), disabled: busy || !newUserRef.trim(), onClick: createUser, children: "Add" }),
|
|
397
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), onClick: () => {
|
|
398
|
+
setShowNewUser(false);
|
|
399
|
+
setNewUserRef("");
|
|
400
|
+
}, children: "Cancel" })
|
|
401
|
+
] }),
|
|
402
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, color: t.muted, marginTop: 6 }, children: [
|
|
403
|
+
"An ",
|
|
404
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "email" }),
|
|
405
|
+
" is pre-approved \u2014 they join with their real identity the first time they sign in. A ",
|
|
406
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "user id" }),
|
|
407
|
+
" is provisioned now."
|
|
408
|
+
] }),
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 12 }, children: [
|
|
410
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, color: t.muted, marginBottom: 6 }, children: [
|
|
411
|
+
"\u2026or add someone already seen in your API traffic",
|
|
412
|
+
observedNew ? ` (${observedNew} new)` : "",
|
|
413
|
+
":"
|
|
414
|
+
] }),
|
|
415
|
+
!observedLoaded && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: t.muted }, children: "Loading\u2026" }),
|
|
416
|
+
observedLoaded && observed.filter((o) => !o.provisioned).length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: t.muted }, children: "No new identities in traffic \u2014 they appear here after their first API call." }),
|
|
417
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: observed.filter((o) => !o.provisioned).map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "5px 0", flexWrap: "wrap" }, children: [
|
|
418
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
|
|
419
|
+
o.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: o.source_label }),
|
|
420
|
+
o.last_seen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 11, color: t.muted }, children: [
|
|
421
|
+
"seen ",
|
|
422
|
+
relTime(o.last_seen),
|
|
423
|
+
o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
|
|
424
|
+
] }),
|
|
425
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
426
|
+
"button",
|
|
427
|
+
{
|
|
428
|
+
style: btn("ghost", true),
|
|
429
|
+
disabled: busy,
|
|
430
|
+
"aria-label": `Add ${o.email || o.abz_sub} as a user`,
|
|
431
|
+
onClick: () => act(`provision:${o.abz_sub}`, { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 }, loadObserved),
|
|
432
|
+
children: "Add"
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
] }, o.abz_sub)) })
|
|
436
|
+
] })
|
|
437
|
+
] }),
|
|
275
438
|
access === "ok" && groups === null && !loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-busy": "true", style: { padding: 24, color: t.muted }, children: "Loading groups\u2026" }),
|
|
276
439
|
access === "ok" && loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "alert", style: { padding: 20, color: t.danger, fontSize: 13 }, children: [
|
|
277
440
|
loadError,
|
|
278
441
|
" ",
|
|
279
442
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
|
|
280
443
|
] }),
|
|
281
|
-
access === "ok" && groups !== null && groups.length
|
|
444
|
+
access === "ok" && groups !== null && (groupTotal > groups.length || groupQuery) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "10px 16px", borderBottom: `1px solid ${t.border}` }, children: [
|
|
445
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
446
|
+
"input",
|
|
447
|
+
{
|
|
448
|
+
style: { ...inputStyle, flex: 1 },
|
|
449
|
+
placeholder: "Search groups\u2026",
|
|
450
|
+
defaultValue: groupQuery,
|
|
451
|
+
onChange: (e) => {
|
|
452
|
+
const v = e.target.value;
|
|
453
|
+
searchGroups(v);
|
|
454
|
+
},
|
|
455
|
+
"aria-label": "Search groups"
|
|
456
|
+
}
|
|
457
|
+
),
|
|
458
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
|
|
459
|
+
groups.length,
|
|
460
|
+
" of ",
|
|
461
|
+
groupTotal
|
|
462
|
+
] })
|
|
463
|
+
] }),
|
|
464
|
+
access === "ok" && groups !== null && groups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted }, children: groupQuery ? `No groups match \u201C${groupQuery}\u201D.` : "No groups yet. Create one to start organizing your users." }),
|
|
282
465
|
access === "ok" && groups !== null && groups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: groups.map((g) => {
|
|
283
466
|
const sealed = g.name === SEALED;
|
|
284
467
|
const isOpen = open === g.id;
|
|
@@ -291,9 +474,12 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
291
474
|
style: { display: "flex", width: "100%", gap: 10, alignItems: "center", padding: "12px 16px", background: "transparent", border: "none", cursor: "pointer", color: t.text, fontFamily: "inherit", fontSize: 14, textAlign: "left" },
|
|
292
475
|
children: [
|
|
293
476
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: isOpen ? "\u25BE" : "\u25B8" }),
|
|
294
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: g.name }),
|
|
295
|
-
sealed
|
|
296
|
-
|
|
477
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: sealed ? "Admins" : g.name }),
|
|
478
|
+
sealed ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: chip, children: [
|
|
479
|
+
admins.length,
|
|
480
|
+
" admin",
|
|
481
|
+
admins.length === 1 ? "" : "s"
|
|
482
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: chip, children: [
|
|
297
483
|
g.member_count ?? 0,
|
|
298
484
|
" member",
|
|
299
485
|
(g.member_count ?? 0) === 1 ? "" : "s"
|
|
@@ -303,64 +489,66 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
303
489
|
),
|
|
304
490
|
isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "0 16px 14px 34px" }, children: [
|
|
305
491
|
detailLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: t.muted, fontSize: 13 }, children: "Loading\u2026" }),
|
|
306
|
-
detail && detail.id === g.id && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
492
|
+
detail && detail.id === g.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: sealed ? (
|
|
493
|
+
/* apiblaze_admins is managed HERE, by email — the seal means
|
|
494
|
+
nobody is added by group membership; adding an admin adds
|
|
495
|
+
their email (they activate on first sign-in). */
|
|
496
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, color: t.muted, marginBottom: 10 }, children: [
|
|
498
|
+
"Admins are granted by ",
|
|
499
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "email" }),
|
|
500
|
+
" \u2014 never by adding someone to a group. A new admin gets access the first time they sign in."
|
|
501
|
+
] }),
|
|
502
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: [
|
|
503
|
+
admins.map((a) => {
|
|
504
|
+
const removable = !a.pinned && admins.length > 1;
|
|
505
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
506
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 160 }, children: a.email }),
|
|
507
|
+
a.pinned && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, title: "Set by the API provider \u2014 you can\u2019t remove this one", children: "provider" }),
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...chip, ...a.active ? { color: t.success, borderColor: t.success } : {} }, children: a.active ? "active" : "pending sign-in" }),
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
510
|
+
"button",
|
|
511
|
+
{
|
|
512
|
+
style: { ...btn("danger", true), ...removable ? {} : { opacity: 0.4, cursor: "not-allowed" } },
|
|
513
|
+
disabled: busy || !removable,
|
|
514
|
+
title: a.pinned ? "Provider-set admins can only be removed by the API provider" : admins.length <= 1 ? "A tenant must keep at least one admin" : `Remove ${a.email}`,
|
|
515
|
+
onClick: () => act(`rm-admin:${a.email}`, { action: "remove-admin", email: a.email }, reloadAdmins),
|
|
516
|
+
children: "Remove"
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
] }, a.email);
|
|
520
|
+
}),
|
|
521
|
+
admins.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No admins yet." })
|
|
522
|
+
] }),
|
|
523
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 10 }, children: [
|
|
524
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
525
|
+
"input",
|
|
526
|
+
{
|
|
527
|
+
style: { ...inputStyle, flex: 1 },
|
|
528
|
+
type: "email",
|
|
529
|
+
placeholder: "Add an admin by email",
|
|
530
|
+
value: newAdmin,
|
|
531
|
+
onChange: (e) => setNewAdmin(e.target.value),
|
|
532
|
+
onKeyDown: (e) => e.key === "Enter" && addAdmin(),
|
|
533
|
+
"aria-label": "New admin email"
|
|
534
|
+
}
|
|
535
|
+
),
|
|
536
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("primary", true), disabled: busy || !newAdmin.trim(), onClick: addAdmin, children: "Add admin" })
|
|
537
|
+
] })
|
|
538
|
+
] })
|
|
539
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
307
540
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: [
|
|
308
541
|
detail.members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
309
542
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140 }, children: userLabel(m) }),
|
|
310
543
|
m.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: m.source_label }),
|
|
311
|
-
sealed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...chip, fontWeight: 600 }, children: "admin" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
312
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
313
|
-
"select",
|
|
314
|
-
{
|
|
315
|
-
style: selectStyle,
|
|
316
|
-
value: m.role,
|
|
317
|
-
disabled: busy,
|
|
318
|
-
"aria-label": `Role of ${userLabel(m)}`,
|
|
319
|
-
onChange: (e) => act(`role:${m.abz_sub}`, { action: "change-role", groupId: g.id, abzSub: m.abz_sub, role: e.target.value }, () => loadDetail(g.id)),
|
|
320
|
-
children: [
|
|
321
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "member", children: "member" }),
|
|
322
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "admin", children: "group admin" })
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
),
|
|
326
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
327
|
-
"button",
|
|
328
|
-
{
|
|
329
|
-
style: btn("danger", true),
|
|
330
|
-
disabled: busy,
|
|
331
|
-
"aria-label": `Remove ${userLabel(m)} from ${g.name}`,
|
|
332
|
-
onClick: () => act(`rm:${m.abz_sub}`, { action: "remove-member", groupId: g.id, abzSub: m.abz_sub }, () => loadDetail(g.id)),
|
|
333
|
-
children: "Remove"
|
|
334
|
-
}
|
|
335
|
-
)
|
|
336
|
-
] })
|
|
337
|
-
] }, m.abz_sub)),
|
|
338
|
-
detail.members.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No members yet." })
|
|
339
|
-
] }),
|
|
340
|
-
sealed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: t.muted, marginTop: 8 }, children: "Workspace admins are granted by email on the provider\u2019s admin list \u2014 membership can\u2019t be edited here." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
341
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap" }, children: [
|
|
342
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
343
|
-
"select",
|
|
344
|
-
{
|
|
345
|
-
style: { ...selectStyle, flex: 1, minWidth: 160 },
|
|
346
|
-
value: addSel,
|
|
347
|
-
disabled: busy,
|
|
348
|
-
"aria-label": "User to add",
|
|
349
|
-
onChange: (e) => setAddSel(e.target.value),
|
|
350
|
-
children: [
|
|
351
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Add a user\u2026" }),
|
|
352
|
-
users.filter((u) => !detail.members.some((m) => m.abz_sub === u.abz_sub)).map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: u.abz_sub, children: userLabel(u) }, u.abz_sub))
|
|
353
|
-
]
|
|
354
|
-
}
|
|
355
|
-
),
|
|
356
544
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
357
545
|
"select",
|
|
358
546
|
{
|
|
359
547
|
style: selectStyle,
|
|
360
|
-
value:
|
|
548
|
+
value: m.role,
|
|
361
549
|
disabled: busy,
|
|
362
|
-
"aria-label":
|
|
363
|
-
onChange: (e) =>
|
|
550
|
+
"aria-label": `Role of ${userLabel(m)}`,
|
|
551
|
+
onChange: (e) => act(`role:${m.abz_sub}`, { action: "change-role", groupId: g.id, abzSub: m.abz_sub, role: e.target.value }, () => loadDetail(g.id)),
|
|
364
552
|
children: [
|
|
365
553
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "member", children: "member" }),
|
|
366
554
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "admin", children: "group admin" })
|
|
@@ -370,184 +558,129 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
370
558
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
371
559
|
"button",
|
|
372
560
|
{
|
|
373
|
-
style: btn("
|
|
374
|
-
disabled: busy || !addSel,
|
|
375
|
-
onClick: () => act("add-member", { action: "add-member", groupId: g.id, abzSub: addSel, role: addRole }, () => {
|
|
376
|
-
setAddSel("");
|
|
377
|
-
return loadDetail(g.id);
|
|
378
|
-
}),
|
|
379
|
-
children: "Add"
|
|
380
|
-
}
|
|
381
|
-
)
|
|
382
|
-
] }),
|
|
383
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 12, flexWrap: "wrap" }, children: [
|
|
384
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "Subgroups:" }),
|
|
385
|
-
detail.subgroups.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip, display: "inline-flex", gap: 6, alignItems: "center" }, children: [
|
|
386
|
-
s.name,
|
|
387
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
388
|
-
"button",
|
|
389
|
-
{
|
|
390
|
-
style: { background: "none", border: "none", cursor: "pointer", color: t.danger, fontSize: 12, padding: 0 },
|
|
391
|
-
disabled: busy,
|
|
392
|
-
"aria-label": `Un-nest ${s.name} from ${g.name}`,
|
|
393
|
-
onClick: () => act("unnest", { action: "remove-subgroup", groupId: g.id, childGroupId: s.id }, () => loadDetail(g.id)),
|
|
394
|
-
children: "\u2715"
|
|
395
|
-
}
|
|
396
|
-
)
|
|
397
|
-
] }, s.id)),
|
|
398
|
-
detail.subgroups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "none" }),
|
|
399
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
400
|
-
"select",
|
|
401
|
-
{
|
|
402
|
-
style: selectStyle,
|
|
403
|
-
value: nestSel,
|
|
561
|
+
style: btn("danger", true),
|
|
404
562
|
disabled: busy,
|
|
405
|
-
"aria-label":
|
|
406
|
-
|
|
407
|
-
children:
|
|
408
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Nest a group\u2026" }),
|
|
409
|
-
groups.filter((x) => x.id !== g.id && x.name !== SEALED && !detail.subgroups.some((s) => s.id === x.id)).map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: x.id, children: x.name }, x.id))
|
|
410
|
-
]
|
|
411
|
-
}
|
|
412
|
-
),
|
|
413
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
414
|
-
"button",
|
|
415
|
-
{
|
|
416
|
-
style: btn("ghost", true),
|
|
417
|
-
disabled: busy || !nestSel,
|
|
418
|
-
onClick: () => act("nest", { action: "add-subgroup", groupId: g.id, childGroupId: nestSel }, () => {
|
|
419
|
-
setNestSel("");
|
|
420
|
-
return loadDetail(g.id);
|
|
421
|
-
}),
|
|
422
|
-
children: "Nest"
|
|
563
|
+
"aria-label": `Remove ${userLabel(m)} from ${g.name}`,
|
|
564
|
+
onClick: () => act(`rm:${m.abz_sub}`, { action: "remove-member", groupId: g.id, abzSub: m.abz_sub }, () => loadDetail(g.id)),
|
|
565
|
+
children: "Remove"
|
|
423
566
|
}
|
|
424
567
|
)
|
|
425
|
-
] }),
|
|
426
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
568
|
+
] }, m.abz_sub)),
|
|
569
|
+
detail.members.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No members yet." })
|
|
570
|
+
] }),
|
|
571
|
+
typeof detail.member_count === "number" && detail.members.length < detail.member_count && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
572
|
+
"button",
|
|
573
|
+
{
|
|
574
|
+
style: { ...btn("ghost", true), marginTop: 6 },
|
|
575
|
+
disabled: busy,
|
|
576
|
+
onClick: () => loadMoreMembers(g.id, detail.members.length),
|
|
577
|
+
children: [
|
|
578
|
+
"Load more (",
|
|
579
|
+
detail.members.length,
|
|
580
|
+
" of ",
|
|
581
|
+
detail.member_count,
|
|
582
|
+
")"
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
),
|
|
586
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "flex-start" }, children: [
|
|
587
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
588
|
+
Typeahead,
|
|
589
|
+
{
|
|
590
|
+
placeholder: "Search users to add\u2026",
|
|
591
|
+
disabled: busy,
|
|
592
|
+
s: { input: inputStyle, surface: t.surface, border: t.border, muted: t.muted, text: t.text },
|
|
593
|
+
search: async (q) => {
|
|
594
|
+
const d = await call({ action: "search-users", q, limit: 20 });
|
|
595
|
+
return (d.users ?? []).filter((u) => !detail.members.some((m) => m.abz_sub === u.abz_sub));
|
|
596
|
+
},
|
|
597
|
+
itemKey: (u) => u.abz_sub,
|
|
598
|
+
itemLabel: (u) => userLabel(u),
|
|
599
|
+
onPick: (u) => act(`add-member:${u.abz_sub}`, { action: "add-member", groupId: g.id, abzSub: u.abz_sub, role: addRole }, () => loadDetail(g.id))
|
|
600
|
+
}
|
|
601
|
+
),
|
|
602
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
603
|
+
"select",
|
|
604
|
+
{
|
|
605
|
+
style: selectStyle,
|
|
606
|
+
value: addRole,
|
|
607
|
+
disabled: busy,
|
|
608
|
+
"aria-label": "Role for the new member",
|
|
609
|
+
onChange: (e) => setAddRole(e.target.value),
|
|
610
|
+
children: [
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "member", children: "member" }),
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "admin", children: "group admin" })
|
|
613
|
+
]
|
|
614
|
+
}
|
|
615
|
+
)
|
|
616
|
+
] }),
|
|
617
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 12, flexWrap: "wrap" }, children: [
|
|
618
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "Subgroups:" }),
|
|
619
|
+
detail.subgroups.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip, display: "inline-flex", gap: 6, alignItems: "center" }, children: [
|
|
620
|
+
s.name,
|
|
432
621
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
433
622
|
"button",
|
|
434
623
|
{
|
|
435
|
-
style:
|
|
624
|
+
style: { background: "none", border: "none", cursor: "pointer", color: t.danger, fontSize: 12, padding: 0 },
|
|
436
625
|
disabled: busy,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
setOpen(null);
|
|
441
|
-
setDetail(null);
|
|
442
|
-
});
|
|
443
|
-
},
|
|
444
|
-
children: "Delete group"
|
|
626
|
+
"aria-label": `Un-nest ${s.name} from ${g.name}`,
|
|
627
|
+
onClick: () => act("unnest", { action: "remove-subgroup", groupId: g.id, childGroupId: s.id }, () => loadDetail(g.id)),
|
|
628
|
+
children: "\u2715"
|
|
445
629
|
}
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
] })
|
|
450
|
-
] })
|
|
451
|
-
] })
|
|
452
|
-
] }, g.id);
|
|
453
|
-
}) }),
|
|
454
|
-
access === "ok" && groups !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderTop: `1px solid ${t.border}` }, children: [
|
|
455
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
456
|
-
"button",
|
|
457
|
-
{
|
|
458
|
-
onClick: () => setShowAdmins((v) => !v),
|
|
459
|
-
"aria-expanded": showAdmins,
|
|
460
|
-
style: { display: "flex", width: "100%", gap: 10, alignItems: "center", padding: "12px 16px", background: "transparent", border: "none", cursor: "pointer", color: t.text, fontFamily: "inherit", fontSize: 13, textAlign: "left" },
|
|
461
|
-
children: [
|
|
462
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: showAdmins ? "\u25BE" : "\u25B8" }),
|
|
463
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: "Admins" }),
|
|
464
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: admins.length })
|
|
465
|
-
]
|
|
466
|
-
}
|
|
467
|
-
),
|
|
468
|
-
showAdmins && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "0 16px 14px 34px" }, children: [
|
|
469
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: [
|
|
470
|
-
admins.map((a) => {
|
|
471
|
-
const removable = !a.pinned && admins.length > 1;
|
|
472
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
473
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 160 }, children: a.email }),
|
|
474
|
-
a.pinned && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, title: "Set by the API provider \u2014 you can\u2019t remove this one", children: "provider" }),
|
|
475
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...chip, ...a.active ? { color: t.success, borderColor: t.success } : {} }, children: a.active ? "active" : "pending sign-in" }),
|
|
630
|
+
)
|
|
631
|
+
] }, s.id)),
|
|
632
|
+
detail.subgroups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "none" }),
|
|
476
633
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
477
|
-
|
|
634
|
+
Typeahead,
|
|
478
635
|
{
|
|
479
|
-
|
|
480
|
-
disabled: busy
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
636
|
+
placeholder: "Nest a group\u2026",
|
|
637
|
+
disabled: busy,
|
|
638
|
+
s: { input: inputStyle, surface: t.surface, border: t.border, muted: t.muted, text: t.text },
|
|
639
|
+
search: async (q) => {
|
|
640
|
+
const d = await call({ action: "list-groups", q, offset: 0 });
|
|
641
|
+
return (d.groups ?? []).filter((x) => x.id !== g.id && x.name !== SEALED && !detail.subgroups.some((s) => s.id === x.id));
|
|
642
|
+
},
|
|
643
|
+
itemKey: (x) => x.id,
|
|
644
|
+
itemLabel: (x) => x.name,
|
|
645
|
+
onPick: (x) => act(`nest:${x.id}`, { action: "add-subgroup", groupId: g.id, childGroupId: x.id }, () => loadDetail(g.id))
|
|
484
646
|
}
|
|
485
647
|
)
|
|
486
|
-
] },
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
]
|
|
523
|
-
}
|
|
524
|
-
),
|
|
525
|
-
showTraffic && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: "0 16px 14px 34px" }, children: [
|
|
526
|
-
observed.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: t.muted, fontSize: 13 }, children: "No identities observed yet \u2014 they appear here after their first API call." }),
|
|
527
|
-
observed.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
528
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
|
|
529
|
-
o.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: o.source_label }),
|
|
530
|
-
o.last_seen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 11, color: t.muted }, children: [
|
|
531
|
-
"seen ",
|
|
532
|
-
relTime(o.last_seen),
|
|
533
|
-
o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
|
|
534
|
-
] }),
|
|
535
|
-
o.provisioned ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip, color: t.success, borderColor: t.success }, children: [
|
|
536
|
-
"user",
|
|
537
|
-
o.groups?.length ? ` \xB7 ${o.groups.join(", ")}` : ""
|
|
538
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
539
|
-
"button",
|
|
540
|
-
{
|
|
541
|
-
style: btn("primary", true),
|
|
542
|
-
disabled: busy,
|
|
543
|
-
"aria-label": `Add ${o.email || o.abz_sub} as a user`,
|
|
544
|
-
onClick: () => act("provision", { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 }),
|
|
545
|
-
children: "Add as user"
|
|
546
|
-
}
|
|
547
|
-
)
|
|
548
|
-
] }, o.abz_sub))
|
|
549
|
-
] })
|
|
550
|
-
] }),
|
|
648
|
+
] }),
|
|
649
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 12, display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: confirmDelete === g.id ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
650
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: t.text }, children: [
|
|
651
|
+
"Delete \u201C",
|
|
652
|
+
g.name,
|
|
653
|
+
"\u201D? Members lose this group everywhere it\u2019s used. This can\u2019t be undone."
|
|
654
|
+
] }),
|
|
655
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
656
|
+
"button",
|
|
657
|
+
{
|
|
658
|
+
style: btn("danger", true),
|
|
659
|
+
disabled: busy,
|
|
660
|
+
onClick: () => {
|
|
661
|
+
setConfirmDelete(null);
|
|
662
|
+
act("del", { action: "delete-group", groupId: g.id }, () => {
|
|
663
|
+
setOpen(null);
|
|
664
|
+
setDetail(null);
|
|
665
|
+
return searchGroups(groupQuery);
|
|
666
|
+
});
|
|
667
|
+
},
|
|
668
|
+
children: "Delete group"
|
|
669
|
+
}
|
|
670
|
+
),
|
|
671
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), onClick: () => setConfirmDelete(null), children: "Cancel" })
|
|
672
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("ghost", true), color: t.danger }, disabled: busy, onClick: () => setConfirmDelete(g.id), children: "Delete group" }) })
|
|
673
|
+
] }) })
|
|
674
|
+
] })
|
|
675
|
+
] }, g.id);
|
|
676
|
+
}) }),
|
|
677
|
+
access === "ok" && groups !== null && groups.length > 0 && groups.length < groupTotal && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "10px 16px", borderTop: `1px solid ${t.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { style: btn("ghost", true), disabled: busy, onClick: loadMoreGroups, children: [
|
|
678
|
+
"Load more groups (",
|
|
679
|
+
groups.length,
|
|
680
|
+
" of ",
|
|
681
|
+
groupTotal,
|
|
682
|
+
")"
|
|
683
|
+
] }) }),
|
|
551
684
|
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: err })
|
|
552
685
|
] })
|
|
553
686
|
] });
|