@camstack/ui-library 0.1.46 → 0.1.48

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.
@@ -17,9 +17,9 @@ interface ScopePickerProps {
17
17
  export declare function ScopePicker({ value, onChange, clampToParent, emptyHint }: ScopePickerProps): React.ReactElement;
18
18
  /**
19
19
  * Validation helper. The wire schema (`TokenScopeSchema`) requires every
20
- * scope to carry a non-empty target and `access.length >= 1`. Use this
21
- * before passing the picker output to a mutate call so the user sees a
22
- * clean inline error instead of a tRPC ZodError.
20
+ * scope to carry a non-empty target/targets and `access.length >= 1`.
21
+ * Use this before passing the picker output to a mutate call so the
22
+ * user sees a clean inline error instead of a tRPC ZodError.
23
23
  */
24
24
  export declare function validateScopes(scopes: readonly TokenScope[]): string | null;
25
25
  export {};
@@ -903,6 +903,16 @@ export declare const useUserManagementRevokeScopedToken: typeof trpc.userManagem
903
903
  export declare const useUserManagementValidateScopedToken: typeof trpc.userManagement.validateScopedToken.useQuery;
904
904
  /** Generated alias around `trpc.userManagement.listScopedTokens.useQuery`. */
905
905
  export declare const useUserManagementListScopedTokens: typeof trpc.userManagement.listScopedTokens.useQuery;
906
+ /** Generated alias around `trpc.userManagement.setupTotp.useMutation`. */
907
+ export declare const useUserManagementSetupTotp: typeof trpc.userManagement.setupTotp.useMutation;
908
+ /** Generated alias around `trpc.userManagement.confirmTotp.useMutation`. */
909
+ export declare const useUserManagementConfirmTotp: typeof trpc.userManagement.confirmTotp.useMutation;
910
+ /** Generated alias around `trpc.userManagement.disableTotp.useMutation`. */
911
+ export declare const useUserManagementDisableTotp: typeof trpc.userManagement.disableTotp.useMutation;
912
+ /** Generated alias around `trpc.userManagement.getTotpStatus.useQuery`. */
913
+ export declare const useUserManagementGetTotpStatus: typeof trpc.userManagement.getTotpStatus.useQuery;
914
+ /** Generated alias around `trpc.userManagement.verifyTotp.useMutation`. */
915
+ export declare const useUserManagementVerifyTotp: typeof trpc.userManagement.verifyTotp.useMutation;
906
916
  /** Generated alias around `trpc.webrtcSession.listStreams.useQuery`. */
907
917
  export declare const useWebrtcSessionListStreams: typeof trpc.webrtcSession.listStreams.useQuery;
908
918
  /** Generated alias around `trpc.webrtcSession.createSession.useMutation`. */
package/dist/index.cjs CHANGED
@@ -16519,6 +16519,16 @@ var useUserManagementRevokeScopedToken = trpc.userManagement.revokeScopedToken.u
16519
16519
  var useUserManagementValidateScopedToken = trpc.userManagement.validateScopedToken.useQuery;
16520
16520
  /** Generated alias around `trpc.userManagement.listScopedTokens.useQuery`. */
16521
16521
  var useUserManagementListScopedTokens = trpc.userManagement.listScopedTokens.useQuery;
16522
+ /** Generated alias around `trpc.userManagement.setupTotp.useMutation`. */
16523
+ var useUserManagementSetupTotp = trpc.userManagement.setupTotp.useMutation;
16524
+ /** Generated alias around `trpc.userManagement.confirmTotp.useMutation`. */
16525
+ var useUserManagementConfirmTotp = trpc.userManagement.confirmTotp.useMutation;
16526
+ /** Generated alias around `trpc.userManagement.disableTotp.useMutation`. */
16527
+ var useUserManagementDisableTotp = trpc.userManagement.disableTotp.useMutation;
16528
+ /** Generated alias around `trpc.userManagement.getTotpStatus.useQuery`. */
16529
+ var useUserManagementGetTotpStatus = trpc.userManagement.getTotpStatus.useQuery;
16530
+ /** Generated alias around `trpc.userManagement.verifyTotp.useMutation`. */
16531
+ var useUserManagementVerifyTotp = trpc.userManagement.verifyTotp.useMutation;
16522
16532
  /** Generated alias around `trpc.webrtcSession.listStreams.useQuery`. */
16523
16533
  var useWebrtcSessionListStreams = trpc.webrtcSession.listStreams.useQuery;
16524
16534
  /** Generated alias around `trpc.webrtcSession.createSession.useMutation`. */
@@ -23013,23 +23023,20 @@ function KebabMenu({ items, header, triggerClassName, title = "More actions" })
23013
23023
  * <ScopePicker> — operator-facing editor for a `TokenScope[]` value.
23014
23024
  *
23015
23025
  * Two consumers: the user-scope editor on `/system/users` (admin grants
23016
- * a baseline scope set to a viewer / agent / scoped user) and the
23017
- * scoped-token create modal on `/system/api-keys` (a caller carves
23018
- * out a subset of THEIR scopes for a `cst_*` token).
23026
+ * a baseline scope set to a non-admin user) and the scoped-token create
23027
+ * modal on `/system/api-keys` (a caller carves out a subset of THEIR
23028
+ * scopes for a `cst_*` token).
23019
23029
  *
23020
- * Each row is `{ type, target, access }`:
23021
- * - `type`: `addon` or `capability` (route-prefix retired in the
23022
- * caps-only refactor every endpoint lives behind a cap).
23023
- * - `target`: a known addon id (live from `useAddonsList`) or a known
23024
- * cap name (from `KNOWN_CAP_NAMES` emitted by codegen).
23025
- * - `access`: three independent checkboxes (`view` / `create` /
23026
- * `delete`). At least one must be selected; an empty grant is
23027
- * rejected by the server-side Zod schema (`access.min(1)`).
23030
+ * Row shapes per scope `type`:
23031
+ * - category → target: 'device' | 'system'
23032
+ * - capability target: cap name from `KNOWN_CAP_NAMES`
23033
+ * - addon → target: addon id from `useAddonsList`
23034
+ * - device targets: readonly deviceId[] (multi-select)
23035
+ *
23036
+ * `access` is always 3 independent checkboxes (view / create / delete).
23028
23037
  *
23029
23038
  * `clampToParent` narrows the target dropdown + access checkboxes to
23030
- * what the CALLER possesses this is the subset check rendered in
23031
- * the UI, mirroring the structural check in `createScopedToken`.
23032
- * Admins should pass `null` to allow everything.
23039
+ * what the CALLER possesses. Admins pass `null` to allow everything.
23033
23040
  */
23034
23041
  var ACCESS_OPTIONS = [
23035
23042
  {
@@ -23051,6 +23058,8 @@ var ACCESS_OPTIONS = [
23051
23058
  hint: "Destructive ops (delete/revoke/reset/…)"
23052
23059
  }
23053
23060
  ];
23061
+ /** Static targets for the `category` scope type (auto-grow categories). */
23062
+ var CATEGORY_CHOICES = ["device", "system"];
23054
23063
  function defaultRow() {
23055
23064
  return {
23056
23065
  type: "capability",
@@ -23058,11 +23067,36 @@ function defaultRow() {
23058
23067
  access: ["view"]
23059
23068
  };
23060
23069
  }
23061
- function clampedTargetsForType(type, parent, addonChoices, capChoices) {
23062
- const universe = type === "addon" ? addonChoices : capChoices;
23070
+ /**
23071
+ * Collect the union of device targets a parent grants. Used to clamp
23072
+ * the device picker so a child grant can't refer to a deviceId the
23073
+ * caller themselves doesn't have.
23074
+ */
23075
+ function parentDeviceTargets(parent) {
23076
+ if (parent == null) return [];
23077
+ const out = /* @__PURE__ */ new Set();
23078
+ for (const s of parent) if (s.type === "device") for (const t of s.targets) out.add(t);
23079
+ return [...out];
23080
+ }
23081
+ function clampedSingleTargets(type, parent, addonChoices, capChoices) {
23082
+ let universe;
23083
+ switch (type) {
23084
+ case "addon":
23085
+ universe = addonChoices;
23086
+ break;
23087
+ case "capability":
23088
+ universe = capChoices;
23089
+ break;
23090
+ case "category":
23091
+ universe = CATEGORY_CHOICES.map((c) => ({
23092
+ value: c,
23093
+ label: c
23094
+ }));
23095
+ break;
23096
+ }
23063
23097
  if (parent == null) return universe;
23064
- const allowed = new Set(parent.filter((s) => s.type === type).map((s) => s.target));
23065
- return universe.filter((t) => allowed.has(t));
23098
+ const allowed = new Set(parent.filter((s) => s.type !== "device" && s.type === type).map((s) => s.target));
23099
+ return universe.filter((t) => allowed.has(t.value));
23066
23100
  }
23067
23101
  function clampedAccessForRow(row, parent) {
23068
23102
  if (parent == null) return [
@@ -23070,7 +23104,12 @@ function clampedAccessForRow(row, parent) {
23070
23104
  "create",
23071
23105
  "delete"
23072
23106
  ];
23073
- const matches = parent.filter((s) => s.type === row.type && s.target === row.target);
23107
+ const matches = parent.filter((s) => {
23108
+ if (s.type !== row.type) return false;
23109
+ if (s.type === "device" && row.type === "device") return s.targets.some((t) => row.targets.includes(t));
23110
+ if (s.type !== "device" && row.type !== "device") return s.target === row.target;
23111
+ return false;
23112
+ });
23074
23113
  if (matches.length === 0) return [];
23075
23114
  const out = /* @__PURE__ */ new Set();
23076
23115
  for (const m of matches) for (const a of m.access) out.add(a);
@@ -23078,17 +23117,75 @@ function clampedAccessForRow(row, parent) {
23078
23117
  }
23079
23118
  function ScopePicker({ value, onChange, clampToParent, emptyHint }) {
23080
23119
  const { data: addons } = useAddonsList();
23081
- const addonChoices = (0, react.useMemo)(() => (addons ?? []).map((a) => a.manifest.id).sort(), [addons]);
23082
- const capChoices = (0, react.useMemo)(() => [..._camstack_types.KNOWN_CAP_NAMES].sort(), []);
23120
+ const { data: devices } = useDeviceManagerListAll({});
23121
+ const addonChoices = (0, react.useMemo)(() => (addons ?? []).map((a) => ({
23122
+ value: a.manifest.id,
23123
+ label: a.manifest.id
23124
+ })).sort((a, b) => a.label.localeCompare(b.label)), [addons]);
23125
+ const capChoices = (0, react.useMemo)(() => [..._camstack_types.KNOWN_CAP_NAMES].sort().map((c) => ({
23126
+ value: c,
23127
+ label: c
23128
+ })), []);
23129
+ const deviceChoices = (0, react.useMemo)(() => (devices ?? []).map((d) => ({
23130
+ value: String(d.id),
23131
+ label: `${d.name} (#${d.id} · ${d.addonId})`
23132
+ })).sort((a, b) => a.label.localeCompare(b.label)), [devices]);
23133
+ const parentDeviceClamp = (0, react.useMemo)(() => clampToParent == null ? null : parentDeviceTargets(clampToParent), [clampToParent]);
23134
+ /**
23135
+ * Build a fresh scope row when `type` changes. The discriminated union
23136
+ * forces us to re-construct the object per variant so the resulting
23137
+ * shape matches one of the four union members exactly.
23138
+ */
23139
+ const rebuildForType = (next, access) => {
23140
+ const accessArr = [...access];
23141
+ switch (next) {
23142
+ case "category": return {
23143
+ type: "category",
23144
+ target: "device",
23145
+ access: accessArr
23146
+ };
23147
+ case "capability": return {
23148
+ type: "capability",
23149
+ target: "",
23150
+ access: accessArr
23151
+ };
23152
+ case "addon": return {
23153
+ type: "addon",
23154
+ target: "",
23155
+ access: accessArr
23156
+ };
23157
+ case "device": return {
23158
+ type: "device",
23159
+ targets: [],
23160
+ access: accessArr
23161
+ };
23162
+ }
23163
+ };
23083
23164
  const update = (idx, patch) => {
23084
23165
  onChange(value.map((s, i) => {
23085
23166
  if (i !== idx) return s;
23086
- const next = {
23087
- ...s,
23088
- ...patch
23167
+ if (patch.type !== void 0 && patch.type !== s.type) return rebuildForType(patch.type, patch.access ?? s.access);
23168
+ const access = [...patch.access ?? s.access];
23169
+ if (s.type === "device") return {
23170
+ type: "device",
23171
+ targets: [...patch.targets ?? s.targets],
23172
+ access
23173
+ };
23174
+ if (s.type === "category") return {
23175
+ type: "category",
23176
+ target: patch.target ?? s.target,
23177
+ access
23178
+ };
23179
+ if (s.type === "capability") return {
23180
+ type: "capability",
23181
+ target: patch.target ?? s.target,
23182
+ access
23183
+ };
23184
+ return {
23185
+ type: "addon",
23186
+ target: patch.target ?? s.target,
23187
+ access
23089
23188
  };
23090
- if (patch.type !== void 0 && patch.type !== s.type) next.target = "";
23091
- return next;
23092
23189
  }));
23093
23190
  };
23094
23191
  const removeAt = (idx) => {
@@ -23105,24 +23202,39 @@ function ScopePicker({ value, onChange, clampToParent, emptyHint }) {
23105
23202
  children: emptyHint
23106
23203
  }),
23107
23204
  value.map((row, idx) => {
23108
- const targetChoices = clampedTargetsForType(row.type, clampToParent, addonChoices, capChoices);
23109
23205
  const allowedAccess = clampedAccessForRow(row, clampToParent);
23110
23206
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
23111
- className: "flex flex-wrap items-center gap-1.5 rounded border border-border bg-surface px-2 py-1.5",
23207
+ className: "flex flex-wrap items-start gap-1.5 rounded border border-border bg-surface px-2 py-1.5",
23112
23208
  children: [
23113
23209
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
23114
23210
  value: row.type,
23115
23211
  onChange: (e) => update(idx, { type: e.target.value }),
23116
23212
  className: "rounded border border-border bg-background px-2 py-1 text-[11px] focus:outline-none focus:ring-1 focus:ring-primary",
23117
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23118
- value: "capability",
23119
- children: "capability"
23120
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23121
- value: "addon",
23122
- children: "addon"
23123
- })]
23213
+ children: [
23214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23215
+ value: "category",
23216
+ children: "category"
23217
+ }),
23218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23219
+ value: "capability",
23220
+ children: "capability"
23221
+ }),
23222
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23223
+ value: "addon",
23224
+ children: "addon"
23225
+ }),
23226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23227
+ value: "device",
23228
+ children: "device"
23229
+ })
23230
+ ]
23124
23231
  }),
23125
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
23232
+ row.type === "device" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceTargetPicker, {
23233
+ value: row.targets,
23234
+ choices: deviceChoices,
23235
+ clampToParent: parentDeviceClamp,
23236
+ onChange: (targets) => update(idx, { targets })
23237
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
23126
23238
  value: row.target,
23127
23239
  onChange: (e) => update(idx, { target: e.target.value }),
23128
23240
  className: "flex-1 min-w-[160px] rounded border border-border bg-background px-2 py-1 text-[11px] font-mono focus:outline-none focus:ring-1 focus:ring-primary",
@@ -23133,10 +23245,10 @@ function ScopePicker({ value, onChange, clampToParent, emptyHint }) {
23133
23245
  row.type,
23134
23246
  " —"
23135
23247
  ]
23136
- }), targetChoices.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23137
- value: t,
23138
- children: t
23139
- }, t))]
23248
+ }), clampedSingleTargets(row.type, clampToParent, addonChoices, capChoices).map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23249
+ value: t.value,
23250
+ children: t.label
23251
+ }, t.value))]
23140
23252
  }),
23141
23253
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
23142
23254
  className: "flex items-center gap-1",
@@ -23184,16 +23296,73 @@ function ScopePicker({ value, onChange, clampToParent, emptyHint }) {
23184
23296
  ]
23185
23297
  });
23186
23298
  }
23299
+ function DeviceTargetPicker({ value, choices, clampToParent, onChange }) {
23300
+ const [pendingAdd, setPendingAdd] = (0, react.useState)("");
23301
+ const visibleChoices = (0, react.useMemo)(() => {
23302
+ const filtered = clampToParent == null ? choices : choices.filter((c) => clampToParent.includes(c.value));
23303
+ const taken = new Set(value);
23304
+ return filtered.filter((c) => !taken.has(c.value));
23305
+ }, [
23306
+ choices,
23307
+ clampToParent,
23308
+ value
23309
+ ]);
23310
+ const labelFor = (id) => choices.find((c) => c.value === id)?.label ?? `#${id}`;
23311
+ const handleAdd = (id) => {
23312
+ if (!id) return;
23313
+ onChange([...value, id]);
23314
+ setPendingAdd("");
23315
+ };
23316
+ const handleRemove = (id) => {
23317
+ onChange(value.filter((v) => v !== id));
23318
+ };
23319
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
23320
+ className: "flex-1 min-w-[160px] flex flex-wrap items-center gap-1",
23321
+ children: [
23322
+ value.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
23323
+ className: "text-[10px] text-foreground-subtle italic px-1",
23324
+ children: "no devices selected"
23325
+ }),
23326
+ value.map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
23327
+ className: "inline-flex items-center gap-1 rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-mono text-primary",
23328
+ children: [labelFor(id), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
23329
+ type: "button",
23330
+ onClick: () => handleRemove(id),
23331
+ className: "hover:text-danger transition-colors",
23332
+ title: "Remove device",
23333
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-2.5 w-2.5" })
23334
+ })]
23335
+ }, id)),
23336
+ visibleChoices.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
23337
+ value: pendingAdd,
23338
+ onChange: (e) => handleAdd(e.target.value),
23339
+ className: "rounded border border-border bg-background px-1.5 py-0.5 text-[10px] focus:outline-none focus:ring-1 focus:ring-primary",
23340
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23341
+ value: "",
23342
+ children: "+ add device…"
23343
+ }), visibleChoices.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23344
+ value: c.value,
23345
+ children: c.label
23346
+ }, c.value))]
23347
+ })
23348
+ ]
23349
+ });
23350
+ }
23187
23351
  /**
23188
23352
  * Validation helper. The wire schema (`TokenScopeSchema`) requires every
23189
- * scope to carry a non-empty target and `access.length >= 1`. Use this
23190
- * before passing the picker output to a mutate call so the user sees a
23191
- * clean inline error instead of a tRPC ZodError.
23353
+ * scope to carry a non-empty target/targets and `access.length >= 1`.
23354
+ * Use this before passing the picker output to a mutate call so the
23355
+ * user sees a clean inline error instead of a tRPC ZodError.
23192
23356
  */
23193
23357
  function validateScopes(scopes) {
23194
23358
  for (const s of scopes) {
23195
- if (s.target.trim().length === 0) return `Scope ${s.type}: pick a target`;
23196
- if (s.access.length === 0) return `Scope ${s.type}:${s.target}: pick at least one access`;
23359
+ if (s.type === "device") {
23360
+ if (s.targets.length === 0) return `Scope device: pick at least one device`;
23361
+ } else if (s.target.trim().length === 0) return `Scope ${s.type}: pick a target`;
23362
+ if (s.access.length === 0) {
23363
+ const targetDesc = s.type === "device" ? `[${s.targets.join(",")}]` : s.target;
23364
+ return `Scope ${s.type}:${targetDesc}: pick at least one access`;
23365
+ }
23197
23366
  }
23198
23367
  return null;
23199
23368
  }
@@ -24555,10 +24724,13 @@ exports.useTurnOrchestratorGetAllServers = useTurnOrchestratorGetAllServers;
24555
24724
  exports.useTurnOrchestratorListProviders = useTurnOrchestratorListProviders;
24556
24725
  exports.useTurnOrchestratorSetProviderEnabled = useTurnOrchestratorSetProviderEnabled;
24557
24726
  exports.useTurnProviderGetTurnServers = useTurnProviderGetTurnServers;
24727
+ exports.useUserManagementConfirmTotp = useUserManagementConfirmTotp;
24558
24728
  exports.useUserManagementCreateApiKey = useUserManagementCreateApiKey;
24559
24729
  exports.useUserManagementCreateScopedToken = useUserManagementCreateScopedToken;
24560
24730
  exports.useUserManagementCreateUser = useUserManagementCreateUser;
24561
24731
  exports.useUserManagementDeleteUser = useUserManagementDeleteUser;
24732
+ exports.useUserManagementDisableTotp = useUserManagementDisableTotp;
24733
+ exports.useUserManagementGetTotpStatus = useUserManagementGetTotpStatus;
24562
24734
  exports.useUserManagementListApiKeys = useUserManagementListApiKeys;
24563
24735
  exports.useUserManagementListScopedTokens = useUserManagementListScopedTokens;
24564
24736
  exports.useUserManagementListUsers = useUserManagementListUsers;
@@ -24566,10 +24738,12 @@ exports.useUserManagementResetPassword = useUserManagementResetPassword;
24566
24738
  exports.useUserManagementRevokeApiKey = useUserManagementRevokeApiKey;
24567
24739
  exports.useUserManagementRevokeScopedToken = useUserManagementRevokeScopedToken;
24568
24740
  exports.useUserManagementSetUserScopes = useUserManagementSetUserScopes;
24741
+ exports.useUserManagementSetupTotp = useUserManagementSetupTotp;
24569
24742
  exports.useUserManagementUpdateUser = useUserManagementUpdateUser;
24570
24743
  exports.useUserManagementValidateApiKey = useUserManagementValidateApiKey;
24571
24744
  exports.useUserManagementValidateCredentials = useUserManagementValidateCredentials;
24572
24745
  exports.useUserManagementValidateScopedToken = useUserManagementValidateScopedToken;
24746
+ exports.useUserManagementVerifyTotp = useUserManagementVerifyTotp;
24573
24747
  exports.useWebrtcSessionCloseSession = useWebrtcSessionCloseSession;
24574
24748
  exports.useWebrtcSessionCreateSession = useWebrtcSessionCreateSession;
24575
24749
  exports.useWebrtcSessionHandleAnswer = useWebrtcSessionHandleAnswer;