apiblaze 0.18.2 → 0.18.6
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 +575 -506
- package/dist/react/index.js +377 -178
- package/dist/react/index.mjs +377 -178
- package/dist/server/index.d.mts +29 -0
- package/dist/server/index.d.ts +29 -0
- package/dist/server/index.js +38 -8
- package/dist/server/index.mjs +38 -8
- package/package.json +1 -1
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,13 +154,14 @@ 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 [
|
|
163
|
+
const [admins, setAdmins] = React.useState([]);
|
|
164
|
+
const [newAdmin, setNewAdmin] = React.useState("");
|
|
99
165
|
const call = React.useCallback(async (body) => {
|
|
100
166
|
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
101
167
|
const data = await res.json().catch(() => ({}));
|
|
@@ -108,9 +174,15 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
108
174
|
const d = await call();
|
|
109
175
|
setAccess(d.access === "pending" ? "pending" : "ok");
|
|
110
176
|
setMyEmail(d.email ?? null);
|
|
111
|
-
setGroups(
|
|
112
|
-
|
|
113
|
-
|
|
177
|
+
setGroups(d.groups ?? []);
|
|
178
|
+
setGroupTotal(d.groupTotal ?? (d.groups?.length ?? 0));
|
|
179
|
+
setObservedNew(d.observedNew ?? 0);
|
|
180
|
+
setObservedLoaded(false);
|
|
181
|
+
setGroupQuery("");
|
|
182
|
+
if (d.access !== "pending") {
|
|
183
|
+
const a = await call({ action: "list-admins" });
|
|
184
|
+
setAdmins(a.admin_emails ?? []);
|
|
185
|
+
}
|
|
114
186
|
} catch (e) {
|
|
115
187
|
setLoadError(e instanceof Error ? e.message : "Could not load groups.");
|
|
116
188
|
setGroups(null);
|
|
@@ -120,12 +192,26 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
120
192
|
load();
|
|
121
193
|
}, [load]);
|
|
122
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]);
|
|
123
211
|
const loadDetail = React.useCallback(async (groupId) => {
|
|
124
212
|
setDetailLoading(true);
|
|
125
213
|
setDetail(null);
|
|
126
214
|
setErr(null);
|
|
127
|
-
setAddSel("");
|
|
128
|
-
setNestSel("");
|
|
129
215
|
try {
|
|
130
216
|
setDetail(await call({ action: "group-detail", groupId }));
|
|
131
217
|
} catch (e) {
|
|
@@ -134,6 +220,14 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
134
220
|
setDetailLoading(false);
|
|
135
221
|
}
|
|
136
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]);
|
|
137
231
|
function toggle(g) {
|
|
138
232
|
if (open === g.id) {
|
|
139
233
|
setOpen(null);
|
|
@@ -148,7 +242,6 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
148
242
|
setErr(null);
|
|
149
243
|
try {
|
|
150
244
|
await call(body);
|
|
151
|
-
await load();
|
|
152
245
|
if (after) await after();
|
|
153
246
|
} catch (e) {
|
|
154
247
|
setErr(e instanceof Error ? e.message : "The action failed.");
|
|
@@ -165,13 +258,29 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
165
258
|
await call({ action: "create-group", name });
|
|
166
259
|
setNewName("");
|
|
167
260
|
setShowCreate(false);
|
|
168
|
-
await
|
|
261
|
+
await searchGroups(groupQuery);
|
|
169
262
|
} catch (e) {
|
|
170
263
|
setErr(e instanceof Error ? e.message : "Could not create the group.");
|
|
171
264
|
} finally {
|
|
172
265
|
setCreating(false);
|
|
173
266
|
}
|
|
174
267
|
}
|
|
268
|
+
async function addAdmin() {
|
|
269
|
+
const email = newAdmin.trim().toLowerCase();
|
|
270
|
+
if (!email) return;
|
|
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
|
+
});
|
|
283
|
+
}
|
|
175
284
|
const vars = {
|
|
176
285
|
// @ts-expect-error CSS custom properties
|
|
177
286
|
"--abz-accent": t.accent,
|
|
@@ -211,7 +320,18 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
211
320
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: card, children: [
|
|
212
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: [
|
|
213
322
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 15 }, children: title }),
|
|
214
|
-
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
|
+
] })
|
|
215
335
|
] }),
|
|
216
336
|
access === "pending" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted, lineHeight: 1.5 }, children: [
|
|
217
337
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 700, color: t.text, marginBottom: 6, fontSize: 15 }, children: "Admin access required" }),
|
|
@@ -260,13 +380,88 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
260
380
|
setNewName("");
|
|
261
381
|
}, children: "Cancel" })
|
|
262
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
|
+
] }),
|
|
263
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" }),
|
|
264
439
|
access === "ok" && loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "alert", style: { padding: 20, color: t.danger, fontSize: 13 }, children: [
|
|
265
440
|
loadError,
|
|
266
441
|
" ",
|
|
267
442
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
|
|
268
443
|
] }),
|
|
269
|
-
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." }),
|
|
270
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) => {
|
|
271
466
|
const sealed = g.name === SEALED;
|
|
272
467
|
const isOpen = open === g.id;
|
|
@@ -279,9 +474,12 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
279
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" },
|
|
280
475
|
children: [
|
|
281
476
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: isOpen ? "\u25BE" : "\u25B8" }),
|
|
282
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: g.name }),
|
|
283
|
-
sealed
|
|
284
|
-
|
|
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: [
|
|
285
483
|
g.member_count ?? 0,
|
|
286
484
|
" member",
|
|
287
485
|
(g.member_count ?? 0) === 1 ? "" : "s"
|
|
@@ -291,64 +489,66 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
291
489
|
),
|
|
292
490
|
isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "0 16px 14px 34px" }, children: [
|
|
293
491
|
detailLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: t.muted, fontSize: 13 }, children: "Loading\u2026" }),
|
|
294
|
-
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: [
|
|
295
540
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: [
|
|
296
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: [
|
|
297
542
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140 }, children: userLabel(m) }),
|
|
298
543
|
m.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: m.source_label }),
|
|
299
|
-
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: [
|
|
300
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
301
|
-
"select",
|
|
302
|
-
{
|
|
303
|
-
style: selectStyle,
|
|
304
|
-
value: m.role,
|
|
305
|
-
disabled: busy,
|
|
306
|
-
"aria-label": `Role of ${userLabel(m)}`,
|
|
307
|
-
onChange: (e) => act(`role:${m.abz_sub}`, { action: "change-role", groupId: g.id, abzSub: m.abz_sub, role: e.target.value }, () => loadDetail(g.id)),
|
|
308
|
-
children: [
|
|
309
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "member", children: "member" }),
|
|
310
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "admin", children: "group admin" })
|
|
311
|
-
]
|
|
312
|
-
}
|
|
313
|
-
),
|
|
314
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
315
|
-
"button",
|
|
316
|
-
{
|
|
317
|
-
style: btn("danger", true),
|
|
318
|
-
disabled: busy,
|
|
319
|
-
"aria-label": `Remove ${userLabel(m)} from ${g.name}`,
|
|
320
|
-
onClick: () => act(`rm:${m.abz_sub}`, { action: "remove-member", groupId: g.id, abzSub: m.abz_sub }, () => loadDetail(g.id)),
|
|
321
|
-
children: "Remove"
|
|
322
|
-
}
|
|
323
|
-
)
|
|
324
|
-
] })
|
|
325
|
-
] }, m.abz_sub)),
|
|
326
|
-
detail.members.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No members yet." })
|
|
327
|
-
] }),
|
|
328
|
-
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: [
|
|
329
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap" }, children: [
|
|
330
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
331
|
-
"select",
|
|
332
|
-
{
|
|
333
|
-
style: { ...selectStyle, flex: 1, minWidth: 160 },
|
|
334
|
-
value: addSel,
|
|
335
|
-
disabled: busy,
|
|
336
|
-
"aria-label": "User to add",
|
|
337
|
-
onChange: (e) => setAddSel(e.target.value),
|
|
338
|
-
children: [
|
|
339
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Add a user\u2026" }),
|
|
340
|
-
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))
|
|
341
|
-
]
|
|
342
|
-
}
|
|
343
|
-
),
|
|
344
544
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
345
545
|
"select",
|
|
346
546
|
{
|
|
347
547
|
style: selectStyle,
|
|
348
|
-
value:
|
|
548
|
+
value: m.role,
|
|
349
549
|
disabled: busy,
|
|
350
|
-
"aria-label":
|
|
351
|
-
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)),
|
|
352
552
|
children: [
|
|
353
553
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "member", children: "member" }),
|
|
354
554
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "admin", children: "group admin" })
|
|
@@ -358,130 +558,129 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
358
558
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
359
559
|
"button",
|
|
360
560
|
{
|
|
361
|
-
style: btn("
|
|
362
|
-
disabled: busy || !addSel,
|
|
363
|
-
onClick: () => act("add-member", { action: "add-member", groupId: g.id, abzSub: addSel, role: addRole }, () => {
|
|
364
|
-
setAddSel("");
|
|
365
|
-
return loadDetail(g.id);
|
|
366
|
-
}),
|
|
367
|
-
children: "Add"
|
|
368
|
-
}
|
|
369
|
-
)
|
|
370
|
-
] }),
|
|
371
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 12, flexWrap: "wrap" }, children: [
|
|
372
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "Subgroups:" }),
|
|
373
|
-
detail.subgroups.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip, display: "inline-flex", gap: 6, alignItems: "center" }, children: [
|
|
374
|
-
s.name,
|
|
375
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
376
|
-
"button",
|
|
377
|
-
{
|
|
378
|
-
style: { background: "none", border: "none", cursor: "pointer", color: t.danger, fontSize: 12, padding: 0 },
|
|
379
|
-
disabled: busy,
|
|
380
|
-
"aria-label": `Un-nest ${s.name} from ${g.name}`,
|
|
381
|
-
onClick: () => act("unnest", { action: "remove-subgroup", groupId: g.id, childGroupId: s.id }, () => loadDetail(g.id)),
|
|
382
|
-
children: "\u2715"
|
|
383
|
-
}
|
|
384
|
-
)
|
|
385
|
-
] }, s.id)),
|
|
386
|
-
detail.subgroups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "none" }),
|
|
387
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
388
|
-
"select",
|
|
389
|
-
{
|
|
390
|
-
style: selectStyle,
|
|
391
|
-
value: nestSel,
|
|
561
|
+
style: btn("danger", true),
|
|
392
562
|
disabled: busy,
|
|
393
|
-
"aria-label":
|
|
394
|
-
|
|
395
|
-
children:
|
|
396
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Nest a group\u2026" }),
|
|
397
|
-
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))
|
|
398
|
-
]
|
|
399
|
-
}
|
|
400
|
-
),
|
|
401
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
402
|
-
"button",
|
|
403
|
-
{
|
|
404
|
-
style: btn("ghost", true),
|
|
405
|
-
disabled: busy || !nestSel,
|
|
406
|
-
onClick: () => act("nest", { action: "add-subgroup", groupId: g.id, childGroupId: nestSel }, () => {
|
|
407
|
-
setNestSel("");
|
|
408
|
-
return loadDetail(g.id);
|
|
409
|
-
}),
|
|
410
|
-
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"
|
|
411
566
|
}
|
|
412
567
|
)
|
|
413
|
-
] }),
|
|
414
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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,
|
|
420
621
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
421
622
|
"button",
|
|
422
623
|
{
|
|
423
|
-
style:
|
|
624
|
+
style: { background: "none", border: "none", cursor: "pointer", color: t.danger, fontSize: 12, padding: 0 },
|
|
424
625
|
disabled: busy,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
setOpen(null);
|
|
429
|
-
setDetail(null);
|
|
430
|
-
});
|
|
431
|
-
},
|
|
432
|
-
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"
|
|
433
629
|
}
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
630
|
+
)
|
|
631
|
+
] }, s.id)),
|
|
632
|
+
detail.subgroups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "none" }),
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
634
|
+
Typeahead,
|
|
635
|
+
{
|
|
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))
|
|
646
|
+
}
|
|
647
|
+
)
|
|
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
|
+
] }) })
|
|
439
674
|
] })
|
|
440
675
|
] }, g.id);
|
|
441
676
|
}) }),
|
|
442
|
-
access === "ok" && groups !== null && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
children: [
|
|
450
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: showTraffic ? "\u25BE" : "\u25B8" }),
|
|
451
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: "Seen in traffic" }),
|
|
452
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: chip, children: [
|
|
453
|
-
observed.filter((o) => !o.provisioned).length,
|
|
454
|
-
" new"
|
|
455
|
-
] })
|
|
456
|
-
]
|
|
457
|
-
}
|
|
458
|
-
),
|
|
459
|
-
showTraffic && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: "0 16px 14px 34px" }, children: [
|
|
460
|
-
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." }),
|
|
461
|
-
observed.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
462
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
|
|
463
|
-
o.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: o.source_label }),
|
|
464
|
-
o.last_seen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 11, color: t.muted }, children: [
|
|
465
|
-
"seen ",
|
|
466
|
-
relTime(o.last_seen),
|
|
467
|
-
o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
|
|
468
|
-
] }),
|
|
469
|
-
o.provisioned ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip, color: t.success, borderColor: t.success }, children: [
|
|
470
|
-
"user",
|
|
471
|
-
o.groups?.length ? ` \xB7 ${o.groups.join(", ")}` : ""
|
|
472
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
473
|
-
"button",
|
|
474
|
-
{
|
|
475
|
-
style: btn("primary", true),
|
|
476
|
-
disabled: busy,
|
|
477
|
-
"aria-label": `Add ${o.email || o.abz_sub} as a user`,
|
|
478
|
-
onClick: () => act("provision", { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 }),
|
|
479
|
-
children: "Add as user"
|
|
480
|
-
}
|
|
481
|
-
)
|
|
482
|
-
] }, o.abz_sub))
|
|
483
|
-
] })
|
|
484
|
-
] }),
|
|
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
|
+
] }) }),
|
|
485
684
|
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: err })
|
|
486
685
|
] })
|
|
487
686
|
] });
|