@camstack/ui-library 0.1.43 → 0.1.45
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/composites/index.d.ts +2 -0
- package/dist/composites/scope-picker.d.ts +25 -0
- package/dist/generated/system-hooks.d.ts +2 -0
- package/dist/index.cjs +202 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +201 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -97,3 +97,5 @@ export type { KebabMenuProps, KebabMenuItem } from './kebab-menu';
|
|
|
97
97
|
export { ConfirmDialogProvider, useConfirm } from './confirm-dialog';
|
|
98
98
|
export { WidgetSlot } from './widget-slot';
|
|
99
99
|
export type { WidgetSlotProps } from './widget-slot';
|
|
100
|
+
export { ScopePicker, validateScopes } from './scope-picker';
|
|
101
|
+
export type { ScopeAccess } from './scope-picker';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { TokenScope } from '@camstack/types';
|
|
3
|
+
export type ScopeAccess = 'view' | 'create' | 'delete';
|
|
4
|
+
interface ScopePickerProps {
|
|
5
|
+
readonly value: readonly TokenScope[];
|
|
6
|
+
readonly onChange: (next: TokenScope[]) => void;
|
|
7
|
+
/**
|
|
8
|
+
* When set, the caller's own scope set. Each row's target dropdown
|
|
9
|
+
* is narrowed to entries the caller already grants, and access
|
|
10
|
+
* checkboxes are disabled for flavours the caller lacks. Admins
|
|
11
|
+
* (unscoped) should pass `null` to allow everything.
|
|
12
|
+
*/
|
|
13
|
+
readonly clampToParent?: readonly TokenScope[] | null;
|
|
14
|
+
/** Optional empty-state CTA when `value.length === 0`. */
|
|
15
|
+
readonly emptyHint?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export declare function ScopePicker({ value, onChange, clampToParent, emptyHint }: ScopePickerProps): React.ReactElement;
|
|
18
|
+
/**
|
|
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.
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateScopes(scopes: readonly TokenScope[]): string | null;
|
|
25
|
+
export {};
|
|
@@ -883,6 +883,8 @@ export declare const useUserManagementUpdateUser: typeof trpc.userManagement.upd
|
|
|
883
883
|
export declare const useUserManagementDeleteUser: typeof trpc.userManagement.deleteUser.useMutation;
|
|
884
884
|
/** Generated alias around `trpc.userManagement.resetPassword.useMutation`. */
|
|
885
885
|
export declare const useUserManagementResetPassword: typeof trpc.userManagement.resetPassword.useMutation;
|
|
886
|
+
/** Generated alias around `trpc.userManagement.setUserScopes.useMutation`. */
|
|
887
|
+
export declare const useUserManagementSetUserScopes: typeof trpc.userManagement.setUserScopes.useMutation;
|
|
886
888
|
/** Generated alias around `trpc.userManagement.validateCredentials.useMutation`. */
|
|
887
889
|
export declare const useUserManagementValidateCredentials: typeof trpc.userManagement.validateCredentials.useMutation;
|
|
888
890
|
/** Generated alias around `trpc.userManagement.listApiKeys.useQuery`. */
|
package/dist/index.cjs
CHANGED
|
@@ -7937,6 +7937,13 @@ var Network = createLucideIcon("network", [
|
|
|
7937
7937
|
key: "2874zd"
|
|
7938
7938
|
}]
|
|
7939
7939
|
]);
|
|
7940
|
+
var Pencil = createLucideIcon("pencil", [["path", {
|
|
7941
|
+
d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
|
|
7942
|
+
key: "1a8usu"
|
|
7943
|
+
}], ["path", {
|
|
7944
|
+
d: "m15 5 4 4",
|
|
7945
|
+
key: "1mk7zo"
|
|
7946
|
+
}]]);
|
|
7940
7947
|
var Play = createLucideIcon("play", [["path", {
|
|
7941
7948
|
d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z",
|
|
7942
7949
|
key: "10ikf1"
|
|
@@ -16492,6 +16499,8 @@ var useUserManagementUpdateUser = trpc.userManagement.updateUser.useMutation;
|
|
|
16492
16499
|
var useUserManagementDeleteUser = trpc.userManagement.deleteUser.useMutation;
|
|
16493
16500
|
/** Generated alias around `trpc.userManagement.resetPassword.useMutation`. */
|
|
16494
16501
|
var useUserManagementResetPassword = trpc.userManagement.resetPassword.useMutation;
|
|
16502
|
+
/** Generated alias around `trpc.userManagement.setUserScopes.useMutation`. */
|
|
16503
|
+
var useUserManagementSetUserScopes = trpc.userManagement.setUserScopes.useMutation;
|
|
16495
16504
|
/** Generated alias around `trpc.userManagement.validateCredentials.useMutation`. */
|
|
16496
16505
|
var useUserManagementValidateCredentials = trpc.userManagement.validateCredentials.useMutation;
|
|
16497
16506
|
/** Generated alias around `trpc.userManagement.listApiKeys.useQuery`. */
|
|
@@ -22999,6 +23008,196 @@ function KebabMenu({ items, header, triggerClassName, title = "More actions" })
|
|
|
22999
23008
|
});
|
|
23000
23009
|
}
|
|
23001
23010
|
//#endregion
|
|
23011
|
+
//#region src/composites/scope-picker.tsx
|
|
23012
|
+
/**
|
|
23013
|
+
* <ScopePicker> — operator-facing editor for a `TokenScope[]` value.
|
|
23014
|
+
*
|
|
23015
|
+
* 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).
|
|
23019
|
+
*
|
|
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)`).
|
|
23028
|
+
*
|
|
23029
|
+
* `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.
|
|
23033
|
+
*/
|
|
23034
|
+
var ACCESS_OPTIONS = [
|
|
23035
|
+
{
|
|
23036
|
+
value: "view",
|
|
23037
|
+
label: "View",
|
|
23038
|
+
icon: Eye,
|
|
23039
|
+
hint: "Read-only — queries + subscriptions"
|
|
23040
|
+
},
|
|
23041
|
+
{
|
|
23042
|
+
value: "create",
|
|
23043
|
+
label: "Create",
|
|
23044
|
+
icon: Pencil,
|
|
23045
|
+
hint: "Mutations that create or update"
|
|
23046
|
+
},
|
|
23047
|
+
{
|
|
23048
|
+
value: "delete",
|
|
23049
|
+
label: "Delete",
|
|
23050
|
+
icon: Trash2,
|
|
23051
|
+
hint: "Destructive ops (delete/revoke/reset/…)"
|
|
23052
|
+
}
|
|
23053
|
+
];
|
|
23054
|
+
function defaultRow() {
|
|
23055
|
+
return {
|
|
23056
|
+
type: "capability",
|
|
23057
|
+
target: "",
|
|
23058
|
+
access: ["view"]
|
|
23059
|
+
};
|
|
23060
|
+
}
|
|
23061
|
+
function clampedTargetsForType(type, parent, addonChoices, capChoices) {
|
|
23062
|
+
const universe = type === "addon" ? addonChoices : capChoices;
|
|
23063
|
+
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));
|
|
23066
|
+
}
|
|
23067
|
+
function clampedAccessForRow(row, parent) {
|
|
23068
|
+
if (parent == null) return [
|
|
23069
|
+
"view",
|
|
23070
|
+
"create",
|
|
23071
|
+
"delete"
|
|
23072
|
+
];
|
|
23073
|
+
const matches = parent.filter((s) => s.type === row.type && s.target === row.target);
|
|
23074
|
+
if (matches.length === 0) return [];
|
|
23075
|
+
const out = /* @__PURE__ */ new Set();
|
|
23076
|
+
for (const m of matches) for (const a of m.access) out.add(a);
|
|
23077
|
+
return [...out];
|
|
23078
|
+
}
|
|
23079
|
+
function ScopePicker({ value, onChange, clampToParent, emptyHint }) {
|
|
23080
|
+
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(), []);
|
|
23083
|
+
const update = (idx, patch) => {
|
|
23084
|
+
onChange(value.map((s, i) => {
|
|
23085
|
+
if (i !== idx) return s;
|
|
23086
|
+
const next = {
|
|
23087
|
+
...s,
|
|
23088
|
+
...patch
|
|
23089
|
+
};
|
|
23090
|
+
if (patch.type !== void 0 && patch.type !== s.type) next.target = "";
|
|
23091
|
+
return next;
|
|
23092
|
+
}));
|
|
23093
|
+
};
|
|
23094
|
+
const removeAt = (idx) => {
|
|
23095
|
+
onChange(value.filter((_, i) => i !== idx));
|
|
23096
|
+
};
|
|
23097
|
+
const append = () => {
|
|
23098
|
+
onChange([...value, defaultRow()]);
|
|
23099
|
+
};
|
|
23100
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
23101
|
+
className: "space-y-2",
|
|
23102
|
+
children: [
|
|
23103
|
+
value.length === 0 && emptyHint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
23104
|
+
className: "text-[10px] text-foreground-subtle italic px-2 py-2",
|
|
23105
|
+
children: emptyHint
|
|
23106
|
+
}),
|
|
23107
|
+
value.map((row, idx) => {
|
|
23108
|
+
const targetChoices = clampedTargetsForType(row.type, clampToParent, addonChoices, capChoices);
|
|
23109
|
+
const allowedAccess = clampedAccessForRow(row, clampToParent);
|
|
23110
|
+
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",
|
|
23112
|
+
children: [
|
|
23113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
23114
|
+
value: row.type,
|
|
23115
|
+
onChange: (e) => update(idx, { type: e.target.value }),
|
|
23116
|
+
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
|
+
})]
|
|
23124
|
+
}),
|
|
23125
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
23126
|
+
value: row.target,
|
|
23127
|
+
onChange: (e) => update(idx, { target: e.target.value }),
|
|
23128
|
+
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",
|
|
23129
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
23130
|
+
value: "",
|
|
23131
|
+
children: [
|
|
23132
|
+
"— pick ",
|
|
23133
|
+
row.type,
|
|
23134
|
+
" —"
|
|
23135
|
+
]
|
|
23136
|
+
}), targetChoices.map((t) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
23137
|
+
value: t,
|
|
23138
|
+
children: t
|
|
23139
|
+
}, t))]
|
|
23140
|
+
}),
|
|
23141
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
23142
|
+
className: "flex items-center gap-1",
|
|
23143
|
+
children: ACCESS_OPTIONS.map(({ value: access, label, icon: Icon, hint }) => {
|
|
23144
|
+
const checked = row.access.includes(access);
|
|
23145
|
+
const disabled = clampToParent != null && !allowedAccess.includes(access) && !checked;
|
|
23146
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
23147
|
+
title: disabled ? `${hint} — not granted by your scope` : hint,
|
|
23148
|
+
className: `inline-flex items-center gap-1 rounded px-1.5 py-1 text-[10px] cursor-pointer transition-colors ${checked ? "bg-primary/15 text-primary font-medium" : "text-foreground-subtle hover:bg-foreground-subtle/10"} ${disabled ? "opacity-40 cursor-not-allowed" : ""}`,
|
|
23149
|
+
children: [
|
|
23150
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
23151
|
+
type: "checkbox",
|
|
23152
|
+
checked,
|
|
23153
|
+
disabled,
|
|
23154
|
+
onChange: (e) => {
|
|
23155
|
+
const nextSet = new Set(row.access);
|
|
23156
|
+
if (e.target.checked) nextSet.add(access);
|
|
23157
|
+
else nextSet.delete(access);
|
|
23158
|
+
update(idx, { access: [...nextSet] });
|
|
23159
|
+
},
|
|
23160
|
+
className: "sr-only"
|
|
23161
|
+
}),
|
|
23162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "h-3 w-3" }),
|
|
23163
|
+
label
|
|
23164
|
+
]
|
|
23165
|
+
}, access);
|
|
23166
|
+
})
|
|
23167
|
+
}),
|
|
23168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
23169
|
+
type: "button",
|
|
23170
|
+
onClick: () => removeAt(idx),
|
|
23171
|
+
className: "rounded p-1 text-foreground-subtle hover:bg-danger/10 hover:text-danger transition-colors",
|
|
23172
|
+
title: "Remove scope",
|
|
23173
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(X, { className: "h-3 w-3" })
|
|
23174
|
+
})
|
|
23175
|
+
]
|
|
23176
|
+
}, idx);
|
|
23177
|
+
}),
|
|
23178
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
23179
|
+
type: "button",
|
|
23180
|
+
onClick: append,
|
|
23181
|
+
className: "inline-flex items-center gap-1 rounded px-2 py-1 text-[10px] text-foreground-subtle hover:text-foreground hover:bg-foreground-subtle/10 transition-colors",
|
|
23182
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Plus, { className: "h-3 w-3" }), "Add scope"]
|
|
23183
|
+
})
|
|
23184
|
+
]
|
|
23185
|
+
});
|
|
23186
|
+
}
|
|
23187
|
+
/**
|
|
23188
|
+
* 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.
|
|
23192
|
+
*/
|
|
23193
|
+
function validateScopes(scopes) {
|
|
23194
|
+
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`;
|
|
23197
|
+
}
|
|
23198
|
+
return null;
|
|
23199
|
+
}
|
|
23200
|
+
//#endregion
|
|
23002
23201
|
//#region src/contexts/zone-editing.tsx
|
|
23003
23202
|
/**
|
|
23004
23203
|
* Per-device zone-editing state shared between the player overlay
|
|
@@ -23828,6 +24027,7 @@ exports.SECTION_HEADER = SECTION_HEADER;
|
|
|
23828
24027
|
exports.SPLIT_PANEL_OUTER = SPLIT_PANEL_OUTER;
|
|
23829
24028
|
exports.SPLIT_PANEL_SIDE = SPLIT_PANEL_SIDE;
|
|
23830
24029
|
exports.STACK_GAP = STACK_GAP;
|
|
24030
|
+
exports.ScopePicker = ScopePicker;
|
|
23831
24031
|
exports.ScrollArea = ScrollArea;
|
|
23832
24032
|
exports.Select = Select;
|
|
23833
24033
|
exports.SemanticBadge = SemanticBadge;
|
|
@@ -24365,6 +24565,7 @@ exports.useUserManagementListUsers = useUserManagementListUsers;
|
|
|
24365
24565
|
exports.useUserManagementResetPassword = useUserManagementResetPassword;
|
|
24366
24566
|
exports.useUserManagementRevokeApiKey = useUserManagementRevokeApiKey;
|
|
24367
24567
|
exports.useUserManagementRevokeScopedToken = useUserManagementRevokeScopedToken;
|
|
24568
|
+
exports.useUserManagementSetUserScopes = useUserManagementSetUserScopes;
|
|
24368
24569
|
exports.useUserManagementUpdateUser = useUserManagementUpdateUser;
|
|
24369
24570
|
exports.useUserManagementValidateApiKey = useUserManagementValidateApiKey;
|
|
24370
24571
|
exports.useUserManagementValidateCredentials = useUserManagementValidateCredentials;
|
|
@@ -24388,5 +24589,6 @@ exports.useZonesAddZone = useZonesAddZone;
|
|
|
24388
24589
|
exports.useZonesListZones = useZonesListZones;
|
|
24389
24590
|
exports.useZonesRemoveZone = useZonesRemoveZone;
|
|
24390
24591
|
exports.useZonesUpdateZone = useZonesUpdateZone;
|
|
24592
|
+
exports.validateScopes = validateScopes;
|
|
24391
24593
|
|
|
24392
24594
|
//# sourceMappingURL=index.cjs.map
|