apiblaze 0.19.5 → 0.19.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 CHANGED
@@ -933,7 +933,7 @@ var import_commander = require("commander");
933
933
  var import_chalk44 = __toESM(require("chalk"));
934
934
 
935
935
  // package.json
936
- var version = "0.19.5";
936
+ var version = "0.19.7";
937
937
 
938
938
  // src/index.ts
939
939
  init_types();
@@ -5297,7 +5297,17 @@ async function runGroupMembers(name, opts) {
5297
5297
  }
5298
5298
  async function runGroupAddUser(userRef, groupName, opts) {
5299
5299
  const t = await resolveTransport(opts);
5300
- const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
5300
+ const g = await groupByName(t, groupName);
5301
+ let u;
5302
+ try {
5303
+ u = await userByRef(t, userRef);
5304
+ } catch (e) {
5305
+ if (!userRef.includes("@")) throw e;
5306
+ const created = await t.call("/v1/admin/users", "POST", { email: userRef.trim().toLowerCase() });
5307
+ if (!created?.abz_sub) throw e;
5308
+ console.log(import_chalk34.default.dim(` ${userRef} wasn't a user yet \u2014 created.`));
5309
+ u = { abz_sub: created.abz_sub, email: created.email ?? userRef, display_name: null };
5310
+ }
5301
5311
  const role = opts.role === "admin" ? "admin" : "member";
5302
5312
  const spinner = (0, import_ora17.default)(`Adding ${label2(u)} to ${g.name}...`).start();
5303
5313
  try {
@@ -144,9 +144,15 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
144
144
  const [groups, setGroups] = React.useState(null);
145
145
  const [groupTotal, setGroupTotal] = React.useState(0);
146
146
  const [groupQuery, setGroupQuery] = React.useState("");
147
+ const [users, setUsers] = React.useState([]);
148
+ const [userTotal, setUserTotal] = React.useState(0);
149
+ const [userQuery, setUserQuery] = React.useState("");
147
150
  const [observed, setObserved] = React.useState([]);
151
+ const [observedTotal, setObservedTotal] = React.useState(0);
152
+ const [observedQuery, setObservedQuery] = React.useState("");
148
153
  const [observedNew, setObservedNew] = React.useState(0);
149
154
  const [observedLoaded, setObservedLoaded] = React.useState(false);
155
+ const [showObserved, setShowObserved] = React.useState(false);
150
156
  const [loadError, setLoadError] = React.useState(null);
151
157
  const [open, setOpen] = React.useState(null);
152
158
  const [detail, setDetail] = React.useState(null);
@@ -176,6 +182,9 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
176
182
  setMyEmail(d.email ?? null);
177
183
  setGroups(d.groups ?? []);
178
184
  setGroupTotal(d.groupTotal ?? (d.groups?.length ?? 0));
185
+ setUsers(d.users ?? []);
186
+ setUserTotal(d.userTotal ?? (d.users?.length ?? 0));
187
+ setUserQuery("");
179
188
  setObservedNew(d.observedNew ?? 0);
180
189
  setObservedLoaded(false);
181
190
  setGroupQuery("");
@@ -203,11 +212,30 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
203
212
  setGroups((prev) => [...prev ?? [], ...d.groups ?? []]);
204
213
  if (typeof d.total === "number") setGroupTotal(d.total);
205
214
  }, [call, groupQuery, groups]);
206
- const loadObserved = React.useCallback(async () => {
207
- const d = await call({ action: "list-observed", offset: 0 });
215
+ const searchUsers = React.useCallback(async (q) => {
216
+ setUserQuery(q);
217
+ const d = await call({ action: "search-users", q, limit: 25, offset: 0 });
218
+ setUsers(d.users ?? []);
219
+ setUserTotal(d.total ?? (d.users?.length ?? 0));
220
+ }, [call]);
221
+ const loadMoreUsers = React.useCallback(async () => {
222
+ const d = await call({ action: "search-users", q: userQuery, limit: 25, offset: users.length });
223
+ setUsers((prev) => [...prev, ...d.users ?? []]);
224
+ if (typeof d.total === "number") setUserTotal(d.total);
225
+ }, [call, userQuery, users]);
226
+ const searchObserved = React.useCallback(async (q) => {
227
+ setObservedQuery(q);
228
+ const d = await call({ action: "list-observed", q, offset: 0 });
208
229
  setObserved(d.identities ?? []);
230
+ setObservedTotal(d.total ?? (d.identities?.length ?? 0));
209
231
  setObservedLoaded(true);
210
232
  }, [call]);
233
+ const loadObserved = React.useCallback(() => searchObserved(""), [searchObserved]);
234
+ const loadMoreObserved = React.useCallback(async () => {
235
+ const d = await call({ action: "list-observed", q: observedQuery, offset: observed.length });
236
+ setObserved((prev) => [...prev, ...d.identities ?? []]);
237
+ if (typeof d.total === "number") setObservedTotal(d.total);
238
+ }, [call, observedQuery, observed]);
211
239
  const loadDetail = React.useCallback(async (groupId) => {
212
240
  setDetailLoading(true);
213
241
  setDetail(null);
@@ -278,7 +306,8 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
278
306
  if (!ref2) return;
279
307
  await act("create-user", { action: "create-user", ref: ref2 }, async () => {
280
308
  setNewUserRef("");
281
- await loadObserved();
309
+ setShowNewUser(false);
310
+ await searchUsers("");
282
311
  });
283
312
  }
284
313
  const vars = {
@@ -322,10 +351,8 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
322
351
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 15 }, children: title }),
323
352
  access === "ok" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
324
353
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), disabled: busy, onClick: () => {
325
- const nv = !showNewUser;
326
- setShowNewUser(nv);
354
+ setShowNewUser((v) => !v);
327
355
  setShowCreate(false);
328
- if (nv && !observedLoaded) loadObserved();
329
356
  }, "aria-label": "Add a new user", children: "+ New user" }),
330
357
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("primary", true), disabled: busy, onClick: () => {
331
358
  setShowCreate((v) => !v);
@@ -380,8 +407,12 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
380
407
  setNewName("");
381
408
  }, children: "Cancel" })
382
409
  ] }),
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: [
410
+ access === "ok" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderBottom: `1px solid ${t.border}` }, children: [
411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "12px 16px 6px" }, children: [
412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 13 }, children: "Users" }),
413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: userTotal })
414
+ ] }),
415
+ showNewUser && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, padding: "6px 16px" }, children: [
385
416
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
386
417
  "input",
387
418
  {
@@ -390,7 +421,8 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
390
421
  value: newUserRef,
391
422
  onChange: (e) => setNewUserRef(e.target.value),
392
423
  onKeyDown: (e) => e.key === "Enter" && createUser(),
393
- "aria-label": "New user email or id"
424
+ "aria-label": "New user email or id",
425
+ autoFocus: true
394
426
  }
395
427
  ),
396
428
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("primary", true), disabled: busy || !newUserRef.trim(), onClick: createUser, children: "Add" }),
@@ -399,22 +431,77 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
399
431
  setNewUserRef("");
400
432
  }, children: "Cancel" })
401
433
  ] }),
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."
434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 16px" }, children: [
435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
436
+ "input",
437
+ {
438
+ style: { ...inputStyle, flex: 1 },
439
+ placeholder: "Search users\u2026",
440
+ defaultValue: userQuery,
441
+ onChange: (e) => searchUsers(e.target.value),
442
+ "aria-label": "Search users"
443
+ }
444
+ ),
445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
446
+ users.length,
447
+ " of ",
448
+ userTotal
449
+ ] })
408
450
  ] }),
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
- ":"
451
+ users.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "6px 16px 12px", fontSize: 13, color: t.muted }, children: userQuery ? `No users match \u201C${userQuery}\u201D.` : "No users yet \u2014 add one with + New user." }),
452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: "0 16px 8px" }, children: users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "5px 0", flexWrap: "wrap" }, children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140, fontSize: 13 }, children: userLabel(u) }),
454
+ u.email && u.email !== userLabel(u) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted, fontFamily: t.monoFontFamily }, children: u.email }),
455
+ u.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: u.source_label }),
456
+ u.status && u.status !== "active" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...chip, color: t.danger, borderColor: t.danger }, children: u.status })
457
+ ] }, u.abz_sub)) }),
458
+ users.length < userTotal && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "0 16px 12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { style: btn("ghost", true), disabled: busy, onClick: loadMoreUsers, children: [
459
+ "Load more (",
460
+ users.length,
461
+ " of ",
462
+ userTotal,
463
+ ")"
464
+ ] }) })
465
+ ] }),
466
+ access === "ok" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderBottom: `1px solid ${t.border}` }, children: [
467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
468
+ "button",
469
+ {
470
+ onClick: () => {
471
+ const nv = !showObserved;
472
+ setShowObserved(nv);
473
+ if (nv && !observedLoaded) loadObserved();
474
+ },
475
+ "aria-expanded": showObserved,
476
+ 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" },
477
+ children: [
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: showObserved ? "\u25BE" : "\u25B8" }),
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: "Candidates seen in traffic" }),
480
+ observedNew > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: observedNew })
481
+ ]
482
+ }
483
+ ),
484
+ showObserved && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "0 16px 12px 34px" }, children: [
485
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 6 }, children: [
486
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
487
+ "input",
488
+ {
489
+ style: { ...inputStyle, flex: 1 },
490
+ placeholder: "Search candidates\u2026",
491
+ defaultValue: observedQuery,
492
+ onChange: (e) => searchObserved(e.target.value),
493
+ "aria-label": "Search candidates seen in traffic"
494
+ }
495
+ ),
496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
497
+ observed.length,
498
+ " of ",
499
+ observedTotal
500
+ ] })
414
501
  ] }),
415
502
  !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: [
503
+ observedLoaded && observed.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: t.muted }, children: observedQuery ? `No candidates match \u201C${observedQuery}\u201D.` : "Nothing yet \u2014 identities appear here after their first API call." }),
504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: observed.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "5px 0", flexWrap: "wrap" }, children: [
418
505
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
419
506
  o.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: o.source_label }),
420
507
  o.last_seen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 11, color: t.muted }, children: [
@@ -422,17 +509,31 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
422
509
  relTime(o.last_seen),
423
510
  o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
424
511
  ] }),
425
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
512
+ o.provisioned ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...chip, color: t.success, borderColor: t.success }, children: "user \u2713" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
426
513
  "button",
427
514
  {
428
515
  style: btn("ghost", true),
429
516
  disabled: busy,
430
517
  "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),
518
+ onClick: () => act(
519
+ `provision:${o.abz_sub}`,
520
+ { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 },
521
+ async () => {
522
+ await searchObserved(observedQuery);
523
+ await searchUsers("");
524
+ }
525
+ ),
432
526
  children: "Add"
433
527
  }
434
528
  )
435
- ] }, o.abz_sub)) })
529
+ ] }, o.abz_sub)) }),
530
+ observed.length < observedTotal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { style: { ...btn("ghost", true), marginTop: 6 }, disabled: busy, onClick: loadMoreObserved, children: [
531
+ "Load more (",
532
+ observed.length,
533
+ " of ",
534
+ observedTotal,
535
+ ")"
536
+ ] })
436
537
  ] })
437
538
  ] }),
438
539
  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" }),
@@ -441,24 +542,30 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
441
542
  " ",
442
543
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
443
544
  ] }),
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
545
+ access === "ok" && groups !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "12px 16px 6px" }, children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 13 }, children: "Groups" }),
548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: groupTotal })
549
+ ] }),
550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 16px 10px", borderBottom: `1px solid ${t.border}` }, children: [
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
552
+ "input",
553
+ {
554
+ style: { ...inputStyle, flex: 1 },
555
+ placeholder: "Search groups\u2026",
556
+ defaultValue: groupQuery,
557
+ onChange: (e) => {
558
+ const v = e.target.value;
559
+ searchGroups(v);
560
+ },
561
+ "aria-label": "Search groups"
562
+ }
563
+ ),
564
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
565
+ groups.length,
566
+ " of ",
567
+ groupTotal
568
+ ] })
462
569
  ] })
463
570
  ] }),
464
571
  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." }),
@@ -731,6 +838,8 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
731
838
  const [confirming, setConfirming] = React2.useState(null);
732
839
  const [rowError, setRowError] = React2.useState(null);
733
840
  const [copied, setCopied] = React2.useState(false);
841
+ const [keyQuery, setKeyQuery] = React2.useState("");
842
+ const [visibleCount, setVisibleCount] = React2.useState(10);
734
843
  const call = React2.useCallback(async (body) => {
735
844
  const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
736
845
  const data = await res.json().catch(() => ({}));
@@ -914,7 +1023,20 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
914
1023
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: { ...btn("primary"), ...disabledStyle(creating) }, disabled: creating, onClick: create, children: creating ? "Creating\u2026" : "Create API key" })
915
1024
  ] })
916
1025
  ] }),
917
- !denied && keys !== null && keys.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: keys.map((k) => {
1026
+ !denied && keys !== null && keys.length > 5 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "10px 16px", borderBottom: `1px solid ${t.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1027
+ "input",
1028
+ {
1029
+ value: keyQuery,
1030
+ onChange: (e) => {
1031
+ setKeyQuery(e.target.value);
1032
+ setVisibleCount(10);
1033
+ },
1034
+ placeholder: "Search keys\u2026",
1035
+ "aria-label": "Search keys",
1036
+ style: { flex: 1, fontFamily: "inherit", fontSize: 13, padding: "6px 10px", borderRadius: 8, border: `1px solid ${t.border}`, background: t.surface, color: t.text, minWidth: 0 }
1037
+ }
1038
+ ) }),
1039
+ !denied && keys !== null && keys.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: keys.filter(matchesKeyQuery(keyQuery)).slice(0, visibleCount).map((k) => {
918
1040
  const anyBusy = busyAny;
919
1041
  const used = relTime2(k.last_used);
920
1042
  const created = k.created_at ? new Date(k.created_at).toLocaleDateString() : null;
@@ -973,6 +1095,24 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
973
1095
  rowError?.keyId === k.key_id && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { role: "alert", style: { fontSize: 12, color: t.danger, marginTop: 6 }, children: rowError.msg })
974
1096
  ] }, k.key_id);
975
1097
  }) }),
1098
+ !denied && keys !== null && keys.length > 0 && (() => {
1099
+ const matched = keys.filter(matchesKeyQuery(keyQuery));
1100
+ if (matched.length === 0) {
1101
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "14px 16px", fontSize: 13, color: t.muted }, children: [
1102
+ "No keys match \u201C",
1103
+ keyQuery,
1104
+ "\u201D."
1105
+ ] });
1106
+ }
1107
+ if (matched.length <= visibleCount) return null;
1108
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "10px 16px", borderTop: `1px solid ${t.border}` }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: btn("ghost", true), onClick: () => setVisibleCount((v) => v + 10), children: [
1109
+ "Show more (",
1110
+ Math.min(visibleCount, matched.length),
1111
+ " of ",
1112
+ matched.length,
1113
+ ")"
1114
+ ] }) });
1115
+ })(),
976
1116
  !denied && rowError && rowError.keyId === "" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: rowError.msg })
977
1117
  ] })
978
1118
  ] });
@@ -980,6 +1120,11 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
980
1120
  function byNewest(a, b) {
981
1121
  return new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime();
982
1122
  }
1123
+ function matchesKeyQuery(q) {
1124
+ const term = q.trim().toLowerCase();
1125
+ if (!term) return () => true;
1126
+ return (k) => `${k.key_prefix ?? ""} ${k.key_suffix ?? ""} ${k.environment ?? ""} ${k.key_id}`.toLowerCase().includes(term);
1127
+ }
983
1128
  var react_default = ApiKeyWidget;
984
1129
  // Annotate the CommonJS export names for ESM import in node:
985
1130
  0 && (module.exports = {
@@ -109,9 +109,15 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
109
109
  const [groups, setGroups] = React.useState(null);
110
110
  const [groupTotal, setGroupTotal] = React.useState(0);
111
111
  const [groupQuery, setGroupQuery] = React.useState("");
112
+ const [users, setUsers] = React.useState([]);
113
+ const [userTotal, setUserTotal] = React.useState(0);
114
+ const [userQuery, setUserQuery] = React.useState("");
112
115
  const [observed, setObserved] = React.useState([]);
116
+ const [observedTotal, setObservedTotal] = React.useState(0);
117
+ const [observedQuery, setObservedQuery] = React.useState("");
113
118
  const [observedNew, setObservedNew] = React.useState(0);
114
119
  const [observedLoaded, setObservedLoaded] = React.useState(false);
120
+ const [showObserved, setShowObserved] = React.useState(false);
115
121
  const [loadError, setLoadError] = React.useState(null);
116
122
  const [open, setOpen] = React.useState(null);
117
123
  const [detail, setDetail] = React.useState(null);
@@ -141,6 +147,9 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
141
147
  setMyEmail(d.email ?? null);
142
148
  setGroups(d.groups ?? []);
143
149
  setGroupTotal(d.groupTotal ?? (d.groups?.length ?? 0));
150
+ setUsers(d.users ?? []);
151
+ setUserTotal(d.userTotal ?? (d.users?.length ?? 0));
152
+ setUserQuery("");
144
153
  setObservedNew(d.observedNew ?? 0);
145
154
  setObservedLoaded(false);
146
155
  setGroupQuery("");
@@ -168,11 +177,30 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
168
177
  setGroups((prev) => [...prev ?? [], ...d.groups ?? []]);
169
178
  if (typeof d.total === "number") setGroupTotal(d.total);
170
179
  }, [call, groupQuery, groups]);
171
- const loadObserved = React.useCallback(async () => {
172
- const d = await call({ action: "list-observed", offset: 0 });
180
+ const searchUsers = React.useCallback(async (q) => {
181
+ setUserQuery(q);
182
+ const d = await call({ action: "search-users", q, limit: 25, offset: 0 });
183
+ setUsers(d.users ?? []);
184
+ setUserTotal(d.total ?? (d.users?.length ?? 0));
185
+ }, [call]);
186
+ const loadMoreUsers = React.useCallback(async () => {
187
+ const d = await call({ action: "search-users", q: userQuery, limit: 25, offset: users.length });
188
+ setUsers((prev) => [...prev, ...d.users ?? []]);
189
+ if (typeof d.total === "number") setUserTotal(d.total);
190
+ }, [call, userQuery, users]);
191
+ const searchObserved = React.useCallback(async (q) => {
192
+ setObservedQuery(q);
193
+ const d = await call({ action: "list-observed", q, offset: 0 });
173
194
  setObserved(d.identities ?? []);
195
+ setObservedTotal(d.total ?? (d.identities?.length ?? 0));
174
196
  setObservedLoaded(true);
175
197
  }, [call]);
198
+ const loadObserved = React.useCallback(() => searchObserved(""), [searchObserved]);
199
+ const loadMoreObserved = React.useCallback(async () => {
200
+ const d = await call({ action: "list-observed", q: observedQuery, offset: observed.length });
201
+ setObserved((prev) => [...prev, ...d.identities ?? []]);
202
+ if (typeof d.total === "number") setObservedTotal(d.total);
203
+ }, [call, observedQuery, observed]);
176
204
  const loadDetail = React.useCallback(async (groupId) => {
177
205
  setDetailLoading(true);
178
206
  setDetail(null);
@@ -243,7 +271,8 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
243
271
  if (!ref2) return;
244
272
  await act("create-user", { action: "create-user", ref: ref2 }, async () => {
245
273
  setNewUserRef("");
246
- await loadObserved();
274
+ setShowNewUser(false);
275
+ await searchUsers("");
247
276
  });
248
277
  }
249
278
  const vars = {
@@ -287,10 +316,8 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
287
316
  /* @__PURE__ */ jsx("strong", { style: { fontSize: 15 }, children: title }),
288
317
  access === "ok" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
289
318
  /* @__PURE__ */ jsx("button", { style: btn("ghost", true), disabled: busy, onClick: () => {
290
- const nv = !showNewUser;
291
- setShowNewUser(nv);
319
+ setShowNewUser((v) => !v);
292
320
  setShowCreate(false);
293
- if (nv && !observedLoaded) loadObserved();
294
321
  }, "aria-label": "Add a new user", children: "+ New user" }),
295
322
  /* @__PURE__ */ jsx("button", { style: btn("primary", true), disabled: busy, onClick: () => {
296
323
  setShowCreate((v) => !v);
@@ -345,8 +372,12 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
345
372
  setNewName("");
346
373
  }, children: "Cancel" })
347
374
  ] }),
348
- access === "ok" && showNewUser && /* @__PURE__ */ jsxs("div", { style: { padding: "12px 16px", borderBottom: `1px solid ${t.border}` }, children: [
349
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
375
+ access === "ok" && /* @__PURE__ */ jsxs("div", { style: { borderBottom: `1px solid ${t.border}` }, children: [
376
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "12px 16px 6px" }, children: [
377
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Users" }),
378
+ /* @__PURE__ */ jsx("span", { style: chip, children: userTotal })
379
+ ] }),
380
+ showNewUser && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, padding: "6px 16px" }, children: [
350
381
  /* @__PURE__ */ jsx(
351
382
  "input",
352
383
  {
@@ -355,7 +386,8 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
355
386
  value: newUserRef,
356
387
  onChange: (e) => setNewUserRef(e.target.value),
357
388
  onKeyDown: (e) => e.key === "Enter" && createUser(),
358
- "aria-label": "New user email or id"
389
+ "aria-label": "New user email or id",
390
+ autoFocus: true
359
391
  }
360
392
  ),
361
393
  /* @__PURE__ */ jsx("button", { style: btn("primary", true), disabled: busy || !newUserRef.trim(), onClick: createUser, children: "Add" }),
@@ -364,22 +396,77 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
364
396
  setNewUserRef("");
365
397
  }, children: "Cancel" })
366
398
  ] }),
367
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: t.muted, marginTop: 6 }, children: [
368
- "An ",
369
- /* @__PURE__ */ jsx("strong", { children: "email" }),
370
- " is pre-approved \u2014 they join with their real identity the first time they sign in. A ",
371
- /* @__PURE__ */ jsx("strong", { children: "user id" }),
372
- " is provisioned now."
399
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 16px" }, children: [
400
+ /* @__PURE__ */ jsx(
401
+ "input",
402
+ {
403
+ style: { ...inputStyle, flex: 1 },
404
+ placeholder: "Search users\u2026",
405
+ defaultValue: userQuery,
406
+ onChange: (e) => searchUsers(e.target.value),
407
+ "aria-label": "Search users"
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
411
+ users.length,
412
+ " of ",
413
+ userTotal
414
+ ] })
373
415
  ] }),
374
- /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
375
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: t.muted, marginBottom: 6 }, children: [
376
- "\u2026or add someone already seen in your API traffic",
377
- observedNew ? ` (${observedNew} new)` : "",
378
- ":"
416
+ users.length === 0 && /* @__PURE__ */ jsx("div", { style: { padding: "6px 16px 12px", fontSize: 13, color: t.muted }, children: userQuery ? `No users match \u201C${userQuery}\u201D.` : "No users yet \u2014 add one with + New user." }),
417
+ /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: "0 16px 8px" }, children: users.map((u) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "5px 0", flexWrap: "wrap" }, children: [
418
+ /* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 140, fontSize: 13 }, children: userLabel(u) }),
419
+ u.email && u.email !== userLabel(u) && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: t.muted, fontFamily: t.monoFontFamily }, children: u.email }),
420
+ u.source_label && /* @__PURE__ */ jsx("span", { style: chip, children: u.source_label }),
421
+ u.status && u.status !== "active" && /* @__PURE__ */ jsx("span", { style: { ...chip, color: t.danger, borderColor: t.danger }, children: u.status })
422
+ ] }, u.abz_sub)) }),
423
+ users.length < userTotal && /* @__PURE__ */ jsx("div", { style: { padding: "0 16px 12px" }, children: /* @__PURE__ */ jsxs("button", { style: btn("ghost", true), disabled: busy, onClick: loadMoreUsers, children: [
424
+ "Load more (",
425
+ users.length,
426
+ " of ",
427
+ userTotal,
428
+ ")"
429
+ ] }) })
430
+ ] }),
431
+ access === "ok" && /* @__PURE__ */ jsxs("div", { style: { borderBottom: `1px solid ${t.border}` }, children: [
432
+ /* @__PURE__ */ jsxs(
433
+ "button",
434
+ {
435
+ onClick: () => {
436
+ const nv = !showObserved;
437
+ setShowObserved(nv);
438
+ if (nv && !observedLoaded) loadObserved();
439
+ },
440
+ "aria-expanded": showObserved,
441
+ 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" },
442
+ children: [
443
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: showObserved ? "\u25BE" : "\u25B8" }),
444
+ /* @__PURE__ */ jsx("strong", { style: { flex: 1 }, children: "Candidates seen in traffic" }),
445
+ observedNew > 0 && /* @__PURE__ */ jsx("span", { style: chip, children: observedNew })
446
+ ]
447
+ }
448
+ ),
449
+ showObserved && /* @__PURE__ */ jsxs("div", { style: { padding: "0 16px 12px 34px" }, children: [
450
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 6 }, children: [
451
+ /* @__PURE__ */ jsx(
452
+ "input",
453
+ {
454
+ style: { ...inputStyle, flex: 1 },
455
+ placeholder: "Search candidates\u2026",
456
+ defaultValue: observedQuery,
457
+ onChange: (e) => searchObserved(e.target.value),
458
+ "aria-label": "Search candidates seen in traffic"
459
+ }
460
+ ),
461
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
462
+ observed.length,
463
+ " of ",
464
+ observedTotal
465
+ ] })
379
466
  ] }),
380
467
  !observedLoaded && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: t.muted }, children: "Loading\u2026" }),
381
- observedLoaded && observed.filter((o) => !o.provisioned).length === 0 && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: t.muted }, children: "No new identities in traffic \u2014 they appear here after their first API call." }),
382
- /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: observed.filter((o) => !o.provisioned).map((o) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "5px 0", flexWrap: "wrap" }, children: [
468
+ observedLoaded && observed.length === 0 && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: t.muted }, children: observedQuery ? `No candidates match \u201C${observedQuery}\u201D.` : "Nothing yet \u2014 identities appear here after their first API call." }),
469
+ /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: observed.map((o) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "5px 0", flexWrap: "wrap" }, children: [
383
470
  /* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
384
471
  o.source_label && /* @__PURE__ */ jsx("span", { style: chip, children: o.source_label }),
385
472
  o.last_seen && /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, color: t.muted }, children: [
@@ -387,17 +474,31 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
387
474
  relTime(o.last_seen),
388
475
  o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
389
476
  ] }),
390
- /* @__PURE__ */ jsx(
477
+ o.provisioned ? /* @__PURE__ */ jsx("span", { style: { ...chip, color: t.success, borderColor: t.success }, children: "user \u2713" }) : /* @__PURE__ */ jsx(
391
478
  "button",
392
479
  {
393
480
  style: btn("ghost", true),
394
481
  disabled: busy,
395
482
  "aria-label": `Add ${o.email || o.abz_sub} as a user`,
396
- onClick: () => act(`provision:${o.abz_sub}`, { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 }, loadObserved),
483
+ onClick: () => act(
484
+ `provision:${o.abz_sub}`,
485
+ { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 },
486
+ async () => {
487
+ await searchObserved(observedQuery);
488
+ await searchUsers("");
489
+ }
490
+ ),
397
491
  children: "Add"
398
492
  }
399
493
  )
400
- ] }, o.abz_sub)) })
494
+ ] }, o.abz_sub)) }),
495
+ observed.length < observedTotal && /* @__PURE__ */ jsxs("button", { style: { ...btn("ghost", true), marginTop: 6 }, disabled: busy, onClick: loadMoreObserved, children: [
496
+ "Load more (",
497
+ observed.length,
498
+ " of ",
499
+ observedTotal,
500
+ ")"
501
+ ] })
401
502
  ] })
402
503
  ] }),
403
504
  access === "ok" && groups === null && !loadError && /* @__PURE__ */ jsx("div", { "aria-busy": "true", style: { padding: 24, color: t.muted }, children: "Loading groups\u2026" }),
@@ -406,24 +507,30 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
406
507
  " ",
407
508
  /* @__PURE__ */ jsx("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
408
509
  ] }),
409
- access === "ok" && groups !== null && (groupTotal > groups.length || groupQuery) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "10px 16px", borderBottom: `1px solid ${t.border}` }, children: [
410
- /* @__PURE__ */ jsx(
411
- "input",
412
- {
413
- style: { ...inputStyle, flex: 1 },
414
- placeholder: "Search groups\u2026",
415
- defaultValue: groupQuery,
416
- onChange: (e) => {
417
- const v = e.target.value;
418
- searchGroups(v);
419
- },
420
- "aria-label": "Search groups"
421
- }
422
- ),
423
- /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
424
- groups.length,
425
- " of ",
426
- groupTotal
510
+ access === "ok" && groups !== null && /* @__PURE__ */ jsxs(Fragment, { children: [
511
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "12px 16px 6px" }, children: [
512
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Groups" }),
513
+ /* @__PURE__ */ jsx("span", { style: chip, children: groupTotal })
514
+ ] }),
515
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 16px 10px", borderBottom: `1px solid ${t.border}` }, children: [
516
+ /* @__PURE__ */ jsx(
517
+ "input",
518
+ {
519
+ style: { ...inputStyle, flex: 1 },
520
+ placeholder: "Search groups\u2026",
521
+ defaultValue: groupQuery,
522
+ onChange: (e) => {
523
+ const v = e.target.value;
524
+ searchGroups(v);
525
+ },
526
+ "aria-label": "Search groups"
527
+ }
528
+ ),
529
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
530
+ groups.length,
531
+ " of ",
532
+ groupTotal
533
+ ] })
427
534
  ] })
428
535
  ] }),
429
536
  access === "ok" && groups !== null && groups.length === 0 && /* @__PURE__ */ 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." }),
@@ -696,6 +803,8 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
696
803
  const [confirming, setConfirming] = React2.useState(null);
697
804
  const [rowError, setRowError] = React2.useState(null);
698
805
  const [copied, setCopied] = React2.useState(false);
806
+ const [keyQuery, setKeyQuery] = React2.useState("");
807
+ const [visibleCount, setVisibleCount] = React2.useState(10);
699
808
  const call = React2.useCallback(async (body) => {
700
809
  const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
701
810
  const data = await res.json().catch(() => ({}));
@@ -879,7 +988,20 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
879
988
  /* @__PURE__ */ jsx2("button", { style: { ...btn("primary"), ...disabledStyle(creating) }, disabled: creating, onClick: create, children: creating ? "Creating\u2026" : "Create API key" })
880
989
  ] })
881
990
  ] }),
882
- !denied && keys !== null && keys.length > 0 && /* @__PURE__ */ jsx2("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: keys.map((k) => {
991
+ !denied && keys !== null && keys.length > 5 && /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "10px 16px", borderBottom: `1px solid ${t.border}` }, children: /* @__PURE__ */ jsx2(
992
+ "input",
993
+ {
994
+ value: keyQuery,
995
+ onChange: (e) => {
996
+ setKeyQuery(e.target.value);
997
+ setVisibleCount(10);
998
+ },
999
+ placeholder: "Search keys\u2026",
1000
+ "aria-label": "Search keys",
1001
+ style: { flex: 1, fontFamily: "inherit", fontSize: 13, padding: "6px 10px", borderRadius: 8, border: `1px solid ${t.border}`, background: t.surface, color: t.text, minWidth: 0 }
1002
+ }
1003
+ ) }),
1004
+ !denied && keys !== null && keys.length > 0 && /* @__PURE__ */ jsx2("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: keys.filter(matchesKeyQuery(keyQuery)).slice(0, visibleCount).map((k) => {
883
1005
  const anyBusy = busyAny;
884
1006
  const used = relTime2(k.last_used);
885
1007
  const created = k.created_at ? new Date(k.created_at).toLocaleDateString() : null;
@@ -938,6 +1060,24 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
938
1060
  rowError?.keyId === k.key_id && /* @__PURE__ */ jsx2("div", { role: "alert", style: { fontSize: 12, color: t.danger, marginTop: 6 }, children: rowError.msg })
939
1061
  ] }, k.key_id);
940
1062
  }) }),
1063
+ !denied && keys !== null && keys.length > 0 && (() => {
1064
+ const matched = keys.filter(matchesKeyQuery(keyQuery));
1065
+ if (matched.length === 0) {
1066
+ return /* @__PURE__ */ jsxs2("div", { style: { padding: "14px 16px", fontSize: 13, color: t.muted }, children: [
1067
+ "No keys match \u201C",
1068
+ keyQuery,
1069
+ "\u201D."
1070
+ ] });
1071
+ }
1072
+ if (matched.length <= visibleCount) return null;
1073
+ return /* @__PURE__ */ jsx2("div", { style: { padding: "10px 16px", borderTop: `1px solid ${t.border}` }, children: /* @__PURE__ */ jsxs2("button", { style: btn("ghost", true), onClick: () => setVisibleCount((v) => v + 10), children: [
1074
+ "Show more (",
1075
+ Math.min(visibleCount, matched.length),
1076
+ " of ",
1077
+ matched.length,
1078
+ ")"
1079
+ ] }) });
1080
+ })(),
941
1081
  !denied && rowError && rowError.keyId === "" && /* @__PURE__ */ jsx2("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: rowError.msg })
942
1082
  ] })
943
1083
  ] });
@@ -945,6 +1085,11 @@ var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/a
945
1085
  function byNewest(a, b) {
946
1086
  return new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime();
947
1087
  }
1088
+ function matchesKeyQuery(q) {
1089
+ const term = q.trim().toLowerCase();
1090
+ if (!term) return () => true;
1091
+ return (k) => `${k.key_prefix ?? ""} ${k.key_suffix ?? ""} ${k.environment ?? ""} ${k.key_id}`.toLowerCase().includes(term);
1092
+ }
948
1093
  var react_default = ApiKeyWidget;
949
1094
  export {
950
1095
  ApiKeyWidget,
@@ -109,17 +109,26 @@ function createApiblazeGroups(cfg) {
109
109
  async function run(user, op) {
110
110
  const { tenant } = await context(user);
111
111
  if (op.action === "snapshot") {
112
- const [groupsRes, observedRes] = await Promise.all([
112
+ const [groupsRes, usersRes, observedRes] = await Promise.all([
113
113
  iam("GET", "/groups", user, tenant, void 0, { limit: "25" }),
114
+ iam("GET", "/users", user, tenant, void 0, { limit: "25" }),
114
115
  iam("GET", "/observed-identities", user, tenant, void 0, { limit: "1" })
115
116
  ]);
116
- if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], groupTotal: 0, observedNew: 0 } };
117
+ if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], groupTotal: 0, users: [], userTotal: 0, observedNew: 0 } };
117
118
  if (!groupsRes.ok) return { status: groupsRes.status, data: { error: await safeErr(groupsRes) } };
118
119
  const g = await groupsRes.json();
120
+ const u = usersRes.ok ? await usersRes.json() : {};
119
121
  const o = observedRes.ok ? await observedRes.json() : {};
120
122
  return {
121
123
  status: 200,
122
- data: { access: "ok", groups: g.groups ?? [], groupTotal: g.total ?? (g.groups?.length ?? 0), observedNew: o.total ?? 0 }
124
+ data: {
125
+ access: "ok",
126
+ groups: g.groups ?? [],
127
+ groupTotal: g.total ?? (g.groups?.length ?? 0),
128
+ users: u.users ?? [],
129
+ userTotal: u.total ?? (u.users?.length ?? 0),
130
+ observedNew: o.total ?? 0
131
+ }
123
132
  };
124
133
  }
125
134
  switch (op.action) {
@@ -81,17 +81,26 @@ function createApiblazeGroups(cfg) {
81
81
  async function run(user, op) {
82
82
  const { tenant } = await context(user);
83
83
  if (op.action === "snapshot") {
84
- const [groupsRes, observedRes] = await Promise.all([
84
+ const [groupsRes, usersRes, observedRes] = await Promise.all([
85
85
  iam("GET", "/groups", user, tenant, void 0, { limit: "25" }),
86
+ iam("GET", "/users", user, tenant, void 0, { limit: "25" }),
86
87
  iam("GET", "/observed-identities", user, tenant, void 0, { limit: "1" })
87
88
  ]);
88
- if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], groupTotal: 0, observedNew: 0 } };
89
+ if (groupsRes.status === 403) return { status: 200, data: { access: "pending", email: user.email ?? null, groups: [], groupTotal: 0, users: [], userTotal: 0, observedNew: 0 } };
89
90
  if (!groupsRes.ok) return { status: groupsRes.status, data: { error: await safeErr(groupsRes) } };
90
91
  const g = await groupsRes.json();
92
+ const u = usersRes.ok ? await usersRes.json() : {};
91
93
  const o = observedRes.ok ? await observedRes.json() : {};
92
94
  return {
93
95
  status: 200,
94
- data: { access: "ok", groups: g.groups ?? [], groupTotal: g.total ?? (g.groups?.length ?? 0), observedNew: o.total ?? 0 }
96
+ data: {
97
+ access: "ok",
98
+ groups: g.groups ?? [],
99
+ groupTotal: g.total ?? (g.groups?.length ?? 0),
100
+ users: u.users ?? [],
101
+ userTotal: u.total ?? (u.users?.length ?? 0),
102
+ observedNew: o.total ?? 0
103
+ }
95
104
  };
96
105
  }
97
106
  switch (op.action) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.19.5",
3
+ "version": "0.19.7",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",