@tai42/studio-sdk 25.0.0 → 26.0.0
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/README.md +143 -26
- package/dist/components/action-menu.d.ts +30 -0
- package/dist/components/action-menu.d.ts.map +1 -0
- package/dist/components/action-menu.js +101 -0
- package/dist/components/action-menu.js.map +1 -0
- package/dist/components/agent-picker.d.ts +22 -0
- package/dist/components/agent-picker.d.ts.map +1 -0
- package/dist/components/agent-picker.js +17 -0
- package/dist/components/agent-picker.js.map +1 -0
- package/dist/components/channel-picker.d.ts +27 -0
- package/dist/components/channel-picker.d.ts.map +1 -0
- package/dist/components/channel-picker.js +26 -0
- package/dist/components/channel-picker.js.map +1 -0
- package/dist/components/components.css +226 -8
- package/dist/components/confirm-dialog.d.ts +3 -1
- package/dist/components/confirm-dialog.d.ts.map +1 -1
- package/dist/components/confirm-dialog.js +2 -2
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/execution-key-picker.d.ts +26 -10
- package/dist/components/execution-key-picker.d.ts.map +1 -1
- package/dist/components/execution-key-picker.js +134 -15
- package/dist/components/execution-key-picker.js.map +1 -1
- package/dist/components/extension-combo-builder.d.ts +7 -7
- package/dist/components/extension-combo-builder.d.ts.map +1 -1
- package/dist/components/extension-combo-builder.js +42 -16
- package/dist/components/extension-combo-builder.js.map +1 -1
- package/dist/components/extension-combo-parts.d.ts +13 -11
- package/dist/components/extension-combo-parts.d.ts.map +1 -1
- package/dist/components/extension-combo-parts.js +10 -19
- package/dist/components/extension-combo-parts.js.map +1 -1
- package/dist/components/extension-config-editor.d.ts +33 -0
- package/dist/components/extension-config-editor.d.ts.map +1 -0
- package/dist/components/extension-config-editor.js +190 -0
- package/dist/components/extension-config-editor.js.map +1 -0
- package/dist/components/extension-grouping.d.ts +4 -41
- package/dist/components/extension-grouping.d.ts.map +1 -1
- package/dist/components/extension-grouping.js +1 -48
- package/dist/components/extension-grouping.js.map +1 -1
- package/dist/components/extension-picker.d.ts +4 -3
- package/dist/components/extension-picker.d.ts.map +1 -1
- package/dist/components/extension-picker.js +6 -6
- package/dist/components/extension-picker.js.map +1 -1
- package/dist/components/fleet-report.d.ts +8 -3
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +12 -2
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/holder-kinds.d.ts +18 -0
- package/dist/components/holder-kinds.d.ts.map +1 -0
- package/dist/components/holder-kinds.js +41 -0
- package/dist/components/holder-kinds.js.map +1 -0
- package/dist/components/holder-list.d.ts +58 -0
- package/dist/components/holder-list.d.ts.map +1 -0
- package/dist/components/holder-list.js +77 -0
- package/dist/components/holder-list.js.map +1 -0
- package/dist/components/icons/index.d.ts +1 -1
- package/dist/components/icons/index.d.ts.map +1 -1
- package/dist/components/icons/index.js +1 -1
- package/dist/components/icons/index.js.map +1 -1
- package/dist/components/icons/nav-map.d.ts.map +1 -1
- package/dist/components/icons/nav-map.js +1 -3
- package/dist/components/icons/nav-map.js.map +1 -1
- package/dist/components/icons/nav-marks.d.ts +0 -4
- package/dist/components/icons/nav-marks.d.ts.map +1 -1
- package/dist/components/icons/nav-marks.js +0 -4
- package/dist/components/icons/nav-marks.js.map +1 -1
- package/dist/components/named-list-picker.d.ts +39 -0
- package/dist/components/named-list-picker.d.ts.map +1 -0
- package/dist/components/named-list-picker.js +61 -0
- package/dist/components/named-list-picker.js.map +1 -0
- package/dist/components/named-list-test-utils.d.ts +23 -0
- package/dist/components/named-list-test-utils.d.ts.map +1 -0
- package/dist/components/named-list-test-utils.js +33 -0
- package/dist/components/named-list-test-utils.js.map +1 -0
- package/dist/components/recycle-roll.d.ts +23 -0
- package/dist/components/recycle-roll.d.ts.map +1 -0
- package/dist/components/recycle-roll.js +47 -0
- package/dist/components/recycle-roll.js.map +1 -0
- package/dist/components/stepper.d.ts +28 -0
- package/dist/components/stepper.d.ts.map +1 -0
- package/dist/components/stepper.js +33 -0
- package/dist/components/stepper.js.map +1 -0
- package/dist/components/subject-section.d.ts +2 -1
- package/dist/components/subject-section.d.ts.map +1 -1
- package/dist/components/subject-section.js +10 -5
- package/dist/components/subject-section.js.map +1 -1
- package/dist/components/tokens.css +31 -41
- package/dist/components/tokens.d.ts +3 -4
- package/dist/components/tokens.d.ts.map +1 -1
- package/dist/components/tokens.js +5 -13
- package/dist/components/tokens.js.map +1 -1
- package/dist/components/tool-picker-parts.d.ts +5 -1
- package/dist/components/tool-picker-parts.d.ts.map +1 -1
- package/dist/components/tool-picker-parts.js +8 -6
- package/dist/components/tool-picker-parts.js.map +1 -1
- package/dist/components/tool-picker.d.ts +77 -2
- package/dist/components/tool-picker.d.ts.map +1 -1
- package/dist/components/tool-picker.js +87 -29
- package/dist/components/tool-picker.js.map +1 -1
- package/dist/components/version-history-panel.d.ts +7 -1
- package/dist/components/version-history-panel.d.ts.map +1 -1
- package/dist/components/version-history-panel.js +2 -2
- package/dist/components/version-history-panel.js.map +1 -1
- package/dist/components/version-history-rows.d.ts +4 -2
- package/dist/components/version-history-rows.d.ts.map +1 -1
- package/dist/components/version-history-rows.js +14 -2
- package/dist/components/version-history-rows.js.map +1 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/hooks/useApi.d.ts +524 -197
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.d.ts +43 -10
- package/dist/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.js +39 -10
- package/dist/hooks/useCapabilities.js.map +1 -1
- package/dist/index.d.ts +25 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/marketplace-install/env-var-fields.d.ts +17 -0
- package/dist/marketplace-install/env-var-fields.d.ts.map +1 -0
- package/dist/marketplace-install/env-var-fields.js +22 -0
- package/dist/marketplace-install/env-var-fields.js.map +1 -0
- package/dist/marketplace-install/index.d.ts +18 -0
- package/dist/marketplace-install/index.d.ts.map +1 -0
- package/dist/marketplace-install/index.js +8 -0
- package/dist/marketplace-install/index.js.map +1 -0
- package/dist/marketplace-install/install-env.d.ts +21 -0
- package/dist/marketplace-install/install-env.d.ts.map +1 -0
- package/dist/marketplace-install/install-env.js +22 -0
- package/dist/marketplace-install/install-env.js.map +1 -0
- package/dist/marketplace-install/install-mount.d.ts +42 -0
- package/dist/marketplace-install/install-mount.d.ts.map +1 -0
- package/dist/marketplace-install/install-mount.js +62 -0
- package/dist/marketplace-install/install-mount.js.map +1 -0
- package/dist/marketplace-install/install-report.d.ts +14 -0
- package/dist/marketplace-install/install-report.d.ts.map +1 -0
- package/dist/marketplace-install/install-report.js +19 -0
- package/dist/marketplace-install/install-report.js.map +1 -0
- package/dist/marketplace-install/install-shell.d.ts +34 -0
- package/dist/marketplace-install/install-shell.d.ts.map +1 -0
- package/dist/marketplace-install/install-shell.js +32 -0
- package/dist/marketplace-install/install-shell.js.map +1 -0
- package/dist/marketplace-install/mount-install.d.ts +41 -0
- package/dist/marketplace-install/mount-install.d.ts.map +1 -0
- package/dist/marketplace-install/mount-install.js +90 -0
- package/dist/marketplace-install/mount-install.js.map +1 -0
- package/dist/marketplace-install/plugin-detail.d.ts +17 -0
- package/dist/marketplace-install/plugin-detail.d.ts.map +1 -0
- package/dist/marketplace-install/plugin-detail.js +41 -0
- package/dist/marketplace-install/plugin-detail.js.map +1 -0
- package/dist/marketplace-install/plugin-install.d.ts +27 -0
- package/dist/marketplace-install/plugin-install.d.ts.map +1 -0
- package/dist/marketplace-install/plugin-install.js +89 -0
- package/dist/marketplace-install/plugin-install.js.map +1 -0
- package/dist/marketplace-install/route-items.d.ts +14 -0
- package/dist/marketplace-install/route-items.d.ts.map +1 -0
- package/dist/marketplace-install/route-items.js +11 -0
- package/dist/marketplace-install/route-items.js.map +1 -0
- package/dist/marketplace-install/use-debounced-value.d.ts +6 -0
- package/dist/marketplace-install/use-debounced-value.d.ts.map +1 -0
- package/dist/marketplace-install/use-debounced-value.js +18 -0
- package/dist/marketplace-install/use-debounced-value.js.map +1 -0
- package/dist/marketplace-install/use-install-action.d.ts +26 -0
- package/dist/marketplace-install/use-install-action.d.ts.map +1 -0
- package/dist/marketplace-install/use-install-action.js +35 -0
- package/dist/marketplace-install/use-install-action.js.map +1 -0
- package/dist/marketplace-install/use-install-preview.d.ts +33 -0
- package/dist/marketplace-install/use-install-preview.d.ts.map +1 -0
- package/dist/marketplace-install/use-install-preview.js +44 -0
- package/dist/marketplace-install/use-install-preview.js.map +1 -0
- package/dist/marketplace-install/use-mount-install.d.ts +80 -0
- package/dist/marketplace-install/use-mount-install.d.ts.map +1 -0
- package/dist/marketplace-install/use-mount-install.js +75 -0
- package/dist/marketplace-install/use-mount-install.js.map +1 -0
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/types.d.ts +23 -7
- package/dist/navigation/types.d.ts.map +1 -1
- package/dist/plugin/registry.d.ts +0 -8
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +25 -1
- package/dist/plugin/registry.js.map +1 -1
- package/dist/plugin/types.d.ts +36 -12
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/version.d.ts +6 -4
- package/dist/plugin/version.d.ts.map +1 -1
- package/dist/plugin/version.js +6 -4
- package/dist/plugin/version.js.map +1 -1
- package/dist/query-keys.d.ts +45 -4
- package/dist/query-keys.d.ts.map +1 -1
- package/dist/query-keys.js +52 -4
- package/dist/query-keys.js.map +1 -1
- package/dist/schema-form/json-schema-meta.d.ts +3 -0
- package/dist/schema-form/json-schema-meta.d.ts.map +1 -0
- package/dist/schema-form/json-schema-meta.js +3 -0
- package/dist/schema-form/json-schema-meta.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -1
- package/dist/token-rules.d.ts +26 -0
- package/dist/token-rules.d.ts.map +1 -0
- package/dist/token-rules.js +64 -0
- package/dist/token-rules.js.map +1 -0
- package/package.json +7 -7
- package/src/components/action-menu.tsx +160 -0
- package/src/components/agent-picker.tsx +53 -0
- package/src/components/channel-picker.tsx +68 -0
- package/src/components/components.css +226 -8
- package/src/components/confirm-dialog.tsx +4 -1
- package/src/components/execution-key-picker.tsx +252 -36
- package/src/components/extension-combo-builder.tsx +61 -29
- package/src/components/extension-combo-parts.tsx +28 -38
- package/src/components/extension-config-editor.tsx +307 -0
- package/src/components/extension-grouping.ts +4 -73
- package/src/components/extension-picker.tsx +13 -13
- package/src/components/fleet-report.tsx +38 -4
- package/src/components/holder-kinds.ts +60 -0
- package/src/components/holder-list.tsx +163 -0
- package/src/components/icons/index.ts +0 -2
- package/src/components/icons/nav-map.ts +0 -4
- package/src/components/icons/nav-marks.tsx +0 -15
- package/src/components/named-list-picker.tsx +182 -0
- package/src/components/named-list-test-utils.tsx +47 -0
- package/src/components/recycle-roll.tsx +120 -0
- package/src/components/stepper.tsx +111 -0
- package/src/components/subject-section.tsx +10 -5
- package/src/components/tokens.css +31 -41
- package/src/components/tokens.ts +5 -13
- package/src/components/tool-picker-parts.tsx +17 -6
- package/src/components/tool-picker.tsx +215 -41
- package/src/components/version-history-panel.tsx +8 -0
- package/src/components/version-history-rows.tsx +14 -2
- package/src/errors.ts +10 -0
- package/src/hooks/useCapabilities.ts +59 -10
- package/src/index.ts +54 -14
- package/src/marketplace-install/env-var-fields.tsx +59 -0
- package/src/marketplace-install/index.ts +17 -0
- package/src/marketplace-install/install-env.ts +39 -0
- package/src/marketplace-install/install-mount.ts +96 -0
- package/src/marketplace-install/install-report.tsx +54 -0
- package/src/marketplace-install/install-shell.tsx +99 -0
- package/src/marketplace-install/mount-install.tsx +290 -0
- package/src/marketplace-install/plugin-detail.ts +60 -0
- package/src/marketplace-install/plugin-install.tsx +221 -0
- package/src/marketplace-install/route-items.ts +20 -0
- package/src/marketplace-install/use-debounced-value.ts +18 -0
- package/src/marketplace-install/use-install-action.ts +60 -0
- package/src/marketplace-install/use-install-preview.ts +74 -0
- package/src/marketplace-install/use-mount-install.ts +116 -0
- package/src/navigation/index.ts +1 -0
- package/src/navigation/types.ts +27 -7
- package/src/plugin/registry.ts +19 -1
- package/src/plugin/types.ts +40 -12
- package/src/plugin/version.ts +6 -4
- package/src/query-keys.ts +71 -4
- package/src/schema-form/json-schema-meta.ts +2 -0
- package/src/testing.ts +2 -0
- package/src/token-rules.ts +66 -0
|
@@ -121,7 +121,7 @@ export function useCapabilities(): CapabilityContextValue {
|
|
|
121
121
|
* seeded editor/viewer carries `["*"]` scopes PLUS a jq fence, so keying on `"*"`
|
|
122
122
|
* scopes would hand every editor/viewer the unfiltered nav and a wall of 403s on
|
|
123
123
|
* the admin routes their fence denies. Keying on `admin` makes editor/viewer
|
|
124
|
-
* SCOPED sessions whose nav is filtered by their
|
|
124
|
+
* SCOPED sessions whose nav is filtered by their projected `routes`, which is
|
|
125
125
|
* exactly right — and the gate-off synthetic projection (`admin: true`, empty
|
|
126
126
|
* `routes`) still reads as full, so an AC-disabled deployment shows everything.
|
|
127
127
|
*/
|
|
@@ -143,8 +143,8 @@ function pathUnderPrefix(path: string, prefix: string): boolean {
|
|
|
143
143
|
/**
|
|
144
144
|
* Whether the projection reaches ANY route under one of `prefixes` — the
|
|
145
145
|
* declarative test behind nav filtering and plugin `requiredCapabilities`. A
|
|
146
|
-
*
|
|
147
|
-
* segment-nested descendant of it; a dynamic `route_patterns[]` entry covers a
|
|
146
|
+
* `routes[].path` (a concrete path, or a route template such as `/api/presets/{name}`)
|
|
147
|
+
* covers a prefix when it is that prefix or a segment-nested descendant of it; a dynamic `route_patterns[]` entry covers a
|
|
148
148
|
* prefix through its served `open_prefix` — the literal the server rules every
|
|
149
149
|
* path starting with is reachable — when it overlaps the prefix at a segment
|
|
150
150
|
* boundary. A `null` open prefix (a pattern whose matched set is not a clean
|
|
@@ -167,14 +167,16 @@ export function coversAnyRoute(projection: MeProjection, prefixes: readonly stri
|
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
169
|
* Whether the projection reaches a specific WRITE route — an EXACT `path` whose
|
|
170
|
-
* admitted `methods` include `method`.
|
|
170
|
+
* admitted `methods` include `method`. A templated route is listed under its template
|
|
171
|
+
* path (`/api/presets/{name}`) with the methods the caller's grant admits on a
|
|
172
|
+
* representative instance of it, so a caller names the template on both sides. This is the method-aware companion to
|
|
171
173
|
* {@link coversAnyRoute}: a read/nav surface gates path-only (any method reaching a
|
|
172
174
|
* prefix means the surface is navigable), but an action that issues `method path`
|
|
173
175
|
* must gate on the method too, or a caller whose jq fence admits only GET on `path`
|
|
174
176
|
* would be shown a control that 403s on submit (projection ⊆ gate).
|
|
175
177
|
*
|
|
176
|
-
* Only
|
|
177
|
-
*
|
|
178
|
+
* Only `routes` are consulted: dynamic `route_patterns` carry no method, so no write
|
|
179
|
+
* capability can be soundly inferred from them, and a synthetic TOTAL
|
|
178
180
|
* (admin) projection carries no concrete `routes` at all — callers short-circuit it
|
|
179
181
|
* with {@link isFullProjection} before reaching here. Under-showing is safe (the
|
|
180
182
|
* server stays the authority); over-showing an action the gate denies is the bug.
|
|
@@ -191,10 +193,11 @@ export function coversRoute(projection: MeProjection, path: string, method: stri
|
|
|
191
193
|
* `method path`. While the projection is loading/failed it is `false` — a write control
|
|
192
194
|
* never enables before the gate is known (fail closed; not-ready ⇒ disabled/read-only).
|
|
193
195
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
196
|
+
* A templated write route is gated by its template path: the projection lists it with
|
|
197
|
+
* the methods the caller's grant admits on a representative instance. A credential fenced
|
|
198
|
+
* per instance is decided at the server, which answers 403 on submit where the fence
|
|
199
|
+
* denies that instance. Under-showing is safe (the server stays the authority);
|
|
200
|
+
* over-showing a write the gate denies is the bug.
|
|
198
201
|
*/
|
|
199
202
|
export function coversWrite(state: CapabilityState, path: string, method: string): boolean {
|
|
200
203
|
return (
|
|
@@ -217,3 +220,49 @@ export function useCanWrite(path: string, method = 'POST'): boolean {
|
|
|
217
220
|
const { state } = useCapabilities();
|
|
218
221
|
return coversWrite(state, path, method);
|
|
219
222
|
}
|
|
223
|
+
|
|
224
|
+
/** The session's own identity, as the execution-key pickers offer it. */
|
|
225
|
+
export interface OwnIdentity {
|
|
226
|
+
readonly userId: string;
|
|
227
|
+
/** 'account' when the session is a principal itself (no owner claim, a principal row): the bind check refuses
|
|
228
|
+
* it. 'key' otherwise: an owned api key, or a key with no owner. */
|
|
229
|
+
readonly kind: 'key' | 'account';
|
|
230
|
+
readonly admin: boolean;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** How a form binds the execution key a stored record runs as, read from the session's projection. */
|
|
234
|
+
export type ExecutionKeyBinding =
|
|
235
|
+
/** The projection is not ready (loading, or failed: the shell shows its own error); the form cannot submit. */
|
|
236
|
+
| { readonly mode: 'pending' }
|
|
237
|
+
/** Access control is off: no identity is checked, no key field is shown, a blank key is sent as `fillKey`. */
|
|
238
|
+
| { readonly mode: 'unchecked'; readonly fillKey: string }
|
|
239
|
+
/** Access control is on: the key list, plus the session's own identity. */
|
|
240
|
+
| { readonly mode: 'checked'; readonly own: OwnIdentity };
|
|
241
|
+
|
|
242
|
+
/** The execution-key binding a capability state implies. */
|
|
243
|
+
export function executionKeyBindingOf(state: CapabilityState): ExecutionKeyBinding {
|
|
244
|
+
if (state.status !== 'ready') return { mode: 'pending' };
|
|
245
|
+
const { projection } = state;
|
|
246
|
+
if (!projection.access_control_enabled) {
|
|
247
|
+
return { mode: 'unchecked', fillKey: projection.user_id };
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
mode: 'checked',
|
|
251
|
+
own: {
|
|
252
|
+
userId: projection.user_id,
|
|
253
|
+
kind: projection.owner_user_id === null && projection.principal !== null ? 'account' : 'key',
|
|
254
|
+
admin: projection.admin,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** The execution-key binding of the live session. */
|
|
260
|
+
export function useExecutionKeyBinding(): ExecutionKeyBinding {
|
|
261
|
+
const { state } = useCapabilities();
|
|
262
|
+
return useMemo(() => executionKeyBindingOf(state), [state]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** The key a write sends: the form's value; with access control off and a blank value, the fill key. */
|
|
266
|
+
export function boundExecutionKey(value: string, binding: ExecutionKeyBinding): string {
|
|
267
|
+
return binding.mode === 'unchecked' && value.trim() === '' ? binding.fillKey : value;
|
|
268
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -42,6 +42,8 @@ import './components/components.css';
|
|
|
42
42
|
// -- Plugin API --------------------------------------------------------------
|
|
43
43
|
export type {
|
|
44
44
|
ChannelConversationRoute,
|
|
45
|
+
HolderLinkContribution,
|
|
46
|
+
HolderLinkTarget,
|
|
45
47
|
NavEntryContribution,
|
|
46
48
|
NavEntrySection,
|
|
47
49
|
PageContribution,
|
|
@@ -50,6 +52,7 @@ export type {
|
|
|
50
52
|
PluginEntry,
|
|
51
53
|
PluginPageParamsSchema,
|
|
52
54
|
PluginPageProps,
|
|
55
|
+
RegisteredHolderLink,
|
|
53
56
|
RegisteredNavEntry,
|
|
54
57
|
RegisteredPage,
|
|
55
58
|
RegisteredRoutePanel,
|
|
@@ -65,7 +68,7 @@ export type {
|
|
|
65
68
|
export type { VersionGateResult } from './plugin/version';
|
|
66
69
|
export { checkPluginApiVersion, STUDIO_PLUGIN_API_VERSION } from './plugin/version';
|
|
67
70
|
/** What `useApi().runTool` resolves with: the run-tool door's answer, read by its `kind`. */
|
|
68
|
-
export type { RunAnswer } from '@tai42/api-client';
|
|
71
|
+
export type { ReferenceHolder, RunAnswer } from '@tai42/api-client';
|
|
69
72
|
|
|
70
73
|
// -- Hooks -------------------------------------------------------------------
|
|
71
74
|
export type { ApiClient } from './hooks/useApi';
|
|
@@ -74,8 +77,14 @@ export type { AuthState } from './hooks/useAuth';
|
|
|
74
77
|
export { AuthProvider, useAuth } from './hooks/useAuth';
|
|
75
78
|
export type { Breakpoint, BreakpointState } from './hooks/useBreakpoint';
|
|
76
79
|
export { useBreakpoint } from './hooks/useBreakpoint';
|
|
77
|
-
export type {
|
|
80
|
+
export type {
|
|
81
|
+
CapabilityContextValue,
|
|
82
|
+
CapabilityState,
|
|
83
|
+
ExecutionKeyBinding,
|
|
84
|
+
OwnIdentity,
|
|
85
|
+
} from './hooks/useCapabilities';
|
|
78
86
|
export {
|
|
87
|
+
boundExecutionKey,
|
|
79
88
|
CapabilityProvider,
|
|
80
89
|
coversAnyRoute,
|
|
81
90
|
coversRoute,
|
|
@@ -83,6 +92,7 @@ export {
|
|
|
83
92
|
isFullProjection,
|
|
84
93
|
useCanWrite,
|
|
85
94
|
useCapabilities,
|
|
95
|
+
useExecutionKeyBinding,
|
|
86
96
|
} from './hooks/useCapabilities';
|
|
87
97
|
export type {
|
|
88
98
|
InteractionsStreamOptions,
|
|
@@ -118,6 +128,7 @@ export type {
|
|
|
118
128
|
NavigationContextValue,
|
|
119
129
|
NavigationGuardHandler,
|
|
120
130
|
PageProps,
|
|
131
|
+
PluginHolderTarget,
|
|
121
132
|
PluginNavigateOptions,
|
|
122
133
|
PluginSearch,
|
|
123
134
|
RouteSearch,
|
|
@@ -141,7 +152,7 @@ export {
|
|
|
141
152
|
export { downloadBlob } from './download';
|
|
142
153
|
export type { EnvRef } from './env-markers';
|
|
143
154
|
export { collectEnvRefs, ENV_MARKER_PREFIX, formatEnvMarker, parseEnvMarker } from './env-markers';
|
|
144
|
-
export { errorMessage } from './errors';
|
|
155
|
+
export { errorMessage, isForbiddenError } from './errors';
|
|
145
156
|
|
|
146
157
|
// -- Disabled-feature idiom (one helper + component, every consumer) ----------
|
|
147
158
|
export type { FeatureDisabledProps } from './feature-disabled';
|
|
@@ -149,8 +160,21 @@ export { FeatureDisabled, featureDisabledMessage, isFeatureDisabled } from './fe
|
|
|
149
160
|
|
|
150
161
|
// -- Cross-feature query keys ------------------------------------------------
|
|
151
162
|
export {
|
|
163
|
+
agentsListKey,
|
|
164
|
+
channelsListKey,
|
|
165
|
+
envConfigKey,
|
|
152
166
|
extensionsQueryKey,
|
|
167
|
+
installedMarketplacePluginsKey,
|
|
168
|
+
marketplacePluginKey,
|
|
169
|
+
mcpAdvertisedToolsKey,
|
|
170
|
+
mcpConfigSchemaKey,
|
|
171
|
+
mcpEnvRefsKey,
|
|
172
|
+
mcpExtensionsKey,
|
|
173
|
+
mcpFailedKey,
|
|
174
|
+
mcpStatusKey,
|
|
175
|
+
preservedManifestKey,
|
|
153
176
|
statesListKey,
|
|
177
|
+
stateTemplateRefereesKey,
|
|
154
178
|
stateTemplatesKey,
|
|
155
179
|
subMcpKey,
|
|
156
180
|
tokensPayloadKey,
|
|
@@ -167,6 +191,8 @@ export { effectiveHidden, hiddenToolNames } from './tool-visibility';
|
|
|
167
191
|
export { mergeToolBadges, toolBadgesByName } from './tool-badges';
|
|
168
192
|
|
|
169
193
|
// -- Design system -----------------------------------------------------------
|
|
194
|
+
export type { ActionMenuItem, ActionMenuProps } from './components/action-menu';
|
|
195
|
+
export { ActionMenu } from './components/action-menu';
|
|
170
196
|
export type { BadgeProps } from './components/badge';
|
|
171
197
|
export { Badge } from './components/badge';
|
|
172
198
|
export type { CheckboxProps } from './components/checkbox';
|
|
@@ -298,10 +324,19 @@ export {
|
|
|
298
324
|
FolderPicker,
|
|
299
325
|
FolderRow,
|
|
300
326
|
} from './components/folder-nav';
|
|
327
|
+
export type {
|
|
328
|
+
HolderHref,
|
|
329
|
+
HolderListProps,
|
|
330
|
+
HolderPreviewProps,
|
|
331
|
+
HolderPreviewState,
|
|
332
|
+
} from './components/holder-list';
|
|
333
|
+
export { HolderList, HolderPreview, previewHolders, useHolderHref } from './components/holder-list';
|
|
301
334
|
export type { HeadingLevel, MarkdownBlock, MarkdownProps } from './components/markdown';
|
|
302
335
|
export { Markdown, parseMarkdown } from './components/markdown';
|
|
303
336
|
export type { OpenTargetOptions, OpenTargetProps } from './components/open-target';
|
|
304
337
|
export { openTargetProps } from './components/open-target';
|
|
338
|
+
export type { RecycleRollSectionProps } from './components/recycle-roll';
|
|
339
|
+
export { RecycleRollSection } from './components/recycle-roll';
|
|
305
340
|
export type {
|
|
306
341
|
MediaDocumentProps,
|
|
307
342
|
MediaImageProps,
|
|
@@ -316,21 +351,28 @@ export {
|
|
|
316
351
|
MediaPlayer,
|
|
317
352
|
resolveMediaSrc,
|
|
318
353
|
} from './components/served-media';
|
|
354
|
+
export type { StepperProps, StepperStatus, StepperStep } from './components/stepper';
|
|
355
|
+
export { Stepper } from './components/stepper';
|
|
356
|
+
// The marketplace install UI; its directory index lists the published names.
|
|
357
|
+
export * from './marketplace-install';
|
|
319
358
|
// The URL half of the link-safety pair: `isSafeHttpUrl` answers yes/no, this one
|
|
320
359
|
// hands back the parsed URL, so a caller that needs the URL does not re-parse it.
|
|
360
|
+
export type { AgentListQuery, AgentPickerProps } from './components/agent-picker';
|
|
361
|
+
export { AgentPicker } from './components/agent-picker';
|
|
362
|
+
export type { ChannelListQuery, ChannelPickerProps } from './components/channel-picker';
|
|
363
|
+
export { channelIdentityHint, ChannelPicker } from './components/channel-picker';
|
|
321
364
|
export type { CopyFieldProps } from './components/copy-field';
|
|
322
365
|
export { CopyField } from './components/copy-field';
|
|
323
366
|
export type { ExecutionKeyPickerProps, ExecutionKeyQuery } from './components/execution-key-picker';
|
|
324
|
-
export {
|
|
367
|
+
export {
|
|
368
|
+
canHoldExecutionKey,
|
|
369
|
+
ExecutionKeyPicker,
|
|
370
|
+
isExecutionKeyListEmpty,
|
|
371
|
+
} from './components/execution-key-picker';
|
|
325
372
|
export type { ExtensionComboBuilderProps } from './components/extension-combo-builder';
|
|
326
373
|
export { ExtensionComboBuilder } from './components/extension-combo-builder';
|
|
327
|
-
export type {
|
|
328
|
-
export {
|
|
329
|
-
baseNameOf,
|
|
330
|
-
groupByKind,
|
|
331
|
-
groupIntoFamilies,
|
|
332
|
-
kindVariant,
|
|
333
|
-
} from './components/extension-grouping';
|
|
374
|
+
export type { ExtensionKindGroup } from './components/extension-grouping';
|
|
375
|
+
export { groupByKind } from './components/extension-grouping';
|
|
334
376
|
export type { ExtensionPickerProps } from './components/extension-picker';
|
|
335
377
|
export { ExtensionPicker } from './components/extension-picker';
|
|
336
378
|
export type { ExternalLinkButtonProps } from './components/external-link';
|
|
@@ -347,7 +389,7 @@ export {
|
|
|
347
389
|
} from './components/subject-section';
|
|
348
390
|
export type { TokenName } from './components/tokens';
|
|
349
391
|
export { TOKEN_NAMES } from './components/tokens';
|
|
350
|
-
export type { ToolPickerProps } from './components/tool-picker';
|
|
392
|
+
export type { ToolListQuery, ToolPickerProps } from './components/tool-picker';
|
|
351
393
|
export { BADGES_NOTE, ToolPicker } from './components/tool-picker';
|
|
352
394
|
export type {
|
|
353
395
|
VersionHistoryEntry,
|
|
@@ -374,7 +416,6 @@ export {
|
|
|
374
416
|
DashboardIcon,
|
|
375
417
|
DatabaseIcon,
|
|
376
418
|
EditIcon,
|
|
377
|
-
ExtensionsIcon,
|
|
378
419
|
ExternalLinkIcon,
|
|
379
420
|
EyeIcon,
|
|
380
421
|
EyeOffIcon,
|
|
@@ -406,7 +447,6 @@ export {
|
|
|
406
447
|
StorageIcon,
|
|
407
448
|
SunIcon,
|
|
408
449
|
SystemIcon,
|
|
409
|
-
TemplatesIcon,
|
|
410
450
|
ToolsIcon,
|
|
411
451
|
UnplugIcon,
|
|
412
452
|
XCircleIcon,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Checkbox } from '../components/checkbox';
|
|
4
|
+
import { Field } from '../components/field';
|
|
5
|
+
import { TextInput } from '../components/inputs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One input + secret toggle per required env var, fully controlled. `requiredSecret`
|
|
9
|
+
* is the server's per-var secret-ness: a `true` var (an OAuth client secret) is
|
|
10
|
+
* LOCKED on — the server masks it regardless, so an off toggle would lie — while a
|
|
11
|
+
* `false` var starts off and stays free. `hints` names the item that needs each var.
|
|
12
|
+
*/
|
|
13
|
+
export function EnvVarFields({
|
|
14
|
+
requiredVars,
|
|
15
|
+
values,
|
|
16
|
+
secret,
|
|
17
|
+
requiredSecret,
|
|
18
|
+
hints,
|
|
19
|
+
onChangeValue,
|
|
20
|
+
onToggleSecret,
|
|
21
|
+
}: {
|
|
22
|
+
readonly requiredVars: readonly string[];
|
|
23
|
+
readonly values: Record<string, string>;
|
|
24
|
+
readonly secret: Record<string, boolean>;
|
|
25
|
+
readonly requiredSecret?: Record<string, boolean>;
|
|
26
|
+
readonly hints?: Record<string, string>;
|
|
27
|
+
readonly onChangeValue: (name: string, value: string) => void;
|
|
28
|
+
readonly onToggleSecret: (name: string, checked: boolean) => void;
|
|
29
|
+
}): ReactNode {
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
{requiredVars.map((name) => {
|
|
33
|
+
const locked = requiredSecret?.[name] === true;
|
|
34
|
+
const hint = hints?.[name];
|
|
35
|
+
return (
|
|
36
|
+
<div key={name} className="tai-stack tai-stack-2">
|
|
37
|
+
<Field label={name}>
|
|
38
|
+
<TextInput
|
|
39
|
+
value={values[name] ?? ''}
|
|
40
|
+
onChange={(event) => {
|
|
41
|
+
onChangeValue(name, event.currentTarget.value);
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
</Field>
|
|
45
|
+
{hint !== undefined ? <span className="tai-muted">Required by {hint}</span> : null}
|
|
46
|
+
<Checkbox
|
|
47
|
+
checked={secret[name] === true}
|
|
48
|
+
disabled={locked}
|
|
49
|
+
onCheckedChange={(checked) => {
|
|
50
|
+
onToggleSecret(name, checked);
|
|
51
|
+
}}
|
|
52
|
+
label="Store as secret"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
})}
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The marketplace install UI the package publishes: the install and update forms,
|
|
3
|
+
* the install report, and the helpers a screen composes them with. The Marketplace
|
|
4
|
+
* detail screen and the Connectors flow render the same forms and the same report.
|
|
5
|
+
*/
|
|
6
|
+
export type { InstallReportProps } from './install-report';
|
|
7
|
+
export { InstallReport } from './install-report';
|
|
8
|
+
export type { MountInstallDialogProps } from './mount-install';
|
|
9
|
+
export { MountInstallDialog, MountInstallForm } from './mount-install';
|
|
10
|
+
export { deliveryOf } from './plugin-detail';
|
|
11
|
+
export type { PluginInstallProps } from './plugin-install';
|
|
12
|
+
export { INSTALL_NO_GRANT, PluginInstallDialog, PluginInstallPanel } from './plugin-install';
|
|
13
|
+
export type { RouteItem } from './route-items';
|
|
14
|
+
export { routeItemsOf } from './route-items';
|
|
15
|
+
export { useDebouncedValue } from './use-debounced-value';
|
|
16
|
+
export type { InstallAction, InstallActionState } from './use-install-action';
|
|
17
|
+
export { useInstallAction } from './use-install-action';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** The secret bands for the install env form: which collected vars are marked
|
|
2
|
+
* secret, and which are LOCKED secret (the server masks them regardless). */
|
|
3
|
+
|
|
4
|
+
/** One required-env entry as the install preview reports it. */
|
|
5
|
+
export interface PreviewEnvRequirement {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly secret: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface InstallEnvBands {
|
|
11
|
+
/** Per var, whether its value is treated as a secret (preview, detail, or override). */
|
|
12
|
+
readonly secretMap: Record<string, boolean>;
|
|
13
|
+
/** Per var, whether the secret mark is LOCKED on (preview or detail says secret). */
|
|
14
|
+
readonly lockedSecret: Record<string, boolean>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fold the preview's authority, the detail-derived fallback, and the operator's
|
|
19
|
+
* per-var override into the two bands the env fields render from. A var the preview
|
|
20
|
+
* or the detail marks secret is LOCKED on (an off toggle would lie, since the server
|
|
21
|
+
* masks it regardless); the override only turns a free var on.
|
|
22
|
+
*/
|
|
23
|
+
export function installEnvBands(
|
|
24
|
+
missingVars: readonly string[],
|
|
25
|
+
previewRequired: readonly PreviewEnvRequirement[],
|
|
26
|
+
detailSecret: Record<string, boolean>,
|
|
27
|
+
override: Record<string, boolean>,
|
|
28
|
+
): InstallEnvBands {
|
|
29
|
+
const previewSecret: Record<string, boolean> = {};
|
|
30
|
+
for (const req of previewRequired) previewSecret[req.name] = req.secret;
|
|
31
|
+
const secretMap: Record<string, boolean> = {};
|
|
32
|
+
const lockedSecret: Record<string, boolean> = {};
|
|
33
|
+
for (const name of missingVars) {
|
|
34
|
+
const locked = previewSecret[name] === true || detailSecret[name] === true;
|
|
35
|
+
lockedSecret[name] = locked;
|
|
36
|
+
secretMap[name] = locked || override[name] === true;
|
|
37
|
+
}
|
|
38
|
+
return { secretMap, lockedSecret };
|
|
39
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure model for the route-mounting install flow: the env value/secret split, the
|
|
3
|
+
* per-var secret band derivation, and the install/update body assembly.
|
|
4
|
+
*/
|
|
5
|
+
import type { MarketplaceInstallPreview } from '@tai42/api-client';
|
|
6
|
+
|
|
7
|
+
/** The install/update body fields the mount form contributes. */
|
|
8
|
+
export interface InstallExtras {
|
|
9
|
+
route_mounts: Record<string, string>;
|
|
10
|
+
accept_public_routes: boolean;
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
secret_keys?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The env value + secret-key split from the collected inputs: a blank field is
|
|
17
|
+
* omitted (the deployment may already provide it); a filled field is marked secret
|
|
18
|
+
* only when its toggle is ON.
|
|
19
|
+
*
|
|
20
|
+
* The toggle state is SEEDED from the server's per-var `required_env[].secret`, so
|
|
21
|
+
* secret-ness comes from that authority, not a blanket default: a `secret: true`
|
|
22
|
+
* var (an OAuth client secret) is masked, a `secret: false` var (a client id) is
|
|
23
|
+
* not unless the operator turns it on. Keying off `=== true` honors an off toggle
|
|
24
|
+
* and never force-masks — an un-seeded var stays out of the secret band.
|
|
25
|
+
*/
|
|
26
|
+
export function collectEnv(
|
|
27
|
+
requiredVars: readonly string[],
|
|
28
|
+
values: Record<string, string>,
|
|
29
|
+
secret: Record<string, boolean>,
|
|
30
|
+
): { env: Record<string, string>; secretKeys: string[] } {
|
|
31
|
+
const env: Record<string, string> = {};
|
|
32
|
+
const secretKeys: string[] = [];
|
|
33
|
+
for (const name of requiredVars) {
|
|
34
|
+
const value = values[name] ?? '';
|
|
35
|
+
if (value === '') continue;
|
|
36
|
+
env[name] = value;
|
|
37
|
+
if (secret[name] === true) secretKeys.push(name);
|
|
38
|
+
}
|
|
39
|
+
return { env, secretKeys };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The effective per-var secret band for the vars to collect: the SERVER's per-var
|
|
44
|
+
* `required_env[].secret` from the preview (the authority), OR the caller-supplied
|
|
45
|
+
* `requiredEnvSecret`, OR an operator override.
|
|
46
|
+
*/
|
|
47
|
+
export function deriveEnvSecretMap(
|
|
48
|
+
envToCollect: readonly string[],
|
|
49
|
+
preview: MarketplaceInstallPreview | undefined,
|
|
50
|
+
requiredEnvSecret: Record<string, boolean> | undefined,
|
|
51
|
+
envSecretOverride: Record<string, boolean>,
|
|
52
|
+
): Record<string, boolean> {
|
|
53
|
+
const previewEnvSecret: Record<string, boolean> = {};
|
|
54
|
+
for (const req of preview?.required_env ?? []) previewEnvSecret[req.name] = req.secret;
|
|
55
|
+
return Object.fromEntries(
|
|
56
|
+
envToCollect.map((name) => [
|
|
57
|
+
name,
|
|
58
|
+
previewEnvSecret[name] === true ||
|
|
59
|
+
requiredEnvSecret?.[name] === true ||
|
|
60
|
+
envSecretOverride[name] === true,
|
|
61
|
+
]),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Assemble the install/update body. INSTALL sends every base (declared default is
|
|
67
|
+
* correct). UPDATE sends ONLY the items whose base changed from its current stored
|
|
68
|
+
* base — omitted items are preserved by the server's stored-mount precedence, so a
|
|
69
|
+
* plugin installed at a non-default base is never reset to default on an edit-free
|
|
70
|
+
* update. Collected env is attached only when at least one value was filled.
|
|
71
|
+
*/
|
|
72
|
+
export function buildInstallExtras(
|
|
73
|
+
verb: 'Install' | 'Update',
|
|
74
|
+
bases: Record<string, string>,
|
|
75
|
+
seededBases: Record<string, string>,
|
|
76
|
+
accepted: boolean,
|
|
77
|
+
envToCollect: readonly string[],
|
|
78
|
+
envValues: Record<string, string>,
|
|
79
|
+
envSecretMap: Record<string, boolean>,
|
|
80
|
+
): InstallExtras {
|
|
81
|
+
const route_mounts =
|
|
82
|
+
verb === 'Update'
|
|
83
|
+
? Object.fromEntries(
|
|
84
|
+
Object.entries(bases).filter(([name, base]) => base !== seededBases[name]),
|
|
85
|
+
)
|
|
86
|
+
: bases;
|
|
87
|
+
const extras: InstallExtras = { route_mounts, accept_public_routes: accepted };
|
|
88
|
+
if (envToCollect.length > 0) {
|
|
89
|
+
const { env, secretKeys } = collectEnv(envToCollect, envValues, envSecretMap);
|
|
90
|
+
if (Object.keys(env).length > 0) {
|
|
91
|
+
extras.env = env;
|
|
92
|
+
extras.secret_keys = secretKeys;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return extras;
|
|
96
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fleet side of an install, update or uninstall: the reload fan-out report and,
|
|
3
|
+
* when the reload recycled workers, the roll. The host folds the receipt's
|
|
4
|
+
* `reload.fanout` / `awaiting_respawn` / `span_release_error` into `summary` with
|
|
5
|
+
* `summarizeFleetFanout` and passes `reload.recycle` as `recycle`.
|
|
6
|
+
*/
|
|
7
|
+
import type { FleetReportSummary, RecycleRoll } from '@tai42/api-client';
|
|
8
|
+
import { type CSSProperties, type ReactNode, useId } from 'react';
|
|
9
|
+
|
|
10
|
+
import { FleetReport } from '../components/fleet-report';
|
|
11
|
+
import { RecycleRollSection } from '../components/recycle-roll';
|
|
12
|
+
|
|
13
|
+
export interface InstallReportProps {
|
|
14
|
+
readonly summary: FleetReportSummary | null;
|
|
15
|
+
readonly recycle: RecycleRoll | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sectionTitleStyle: CSSProperties = {
|
|
19
|
+
margin: '0 0 var(--tai-space-2)',
|
|
20
|
+
fontSize: 'var(--tai-text-md)',
|
|
21
|
+
color: 'var(--tai-color-text)',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const sectionIntroStyle: CSSProperties = {
|
|
25
|
+
margin: '0 0 var(--tai-space-3)',
|
|
26
|
+
color: 'var(--tai-color-text-muted)',
|
|
27
|
+
fontSize: 'var(--tai-text-sm)',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function InstallReport({ summary, recycle }: InstallReportProps): ReactNode {
|
|
31
|
+
const headingId = useId();
|
|
32
|
+
return (
|
|
33
|
+
<div className="tai-stack tai-stack-3">
|
|
34
|
+
<FleetReport summary={summary} />
|
|
35
|
+
{recycle !== null ? (
|
|
36
|
+
<section aria-labelledby={headingId}>
|
|
37
|
+
<h4 id={headingId} style={sectionTitleStyle}>
|
|
38
|
+
Worker recycle
|
|
39
|
+
</h4>
|
|
40
|
+
<p style={sectionIntroStyle}>
|
|
41
|
+
Some changed settings take effect only in a fresh worker process, so the fleet recycled
|
|
42
|
+
its workers one at a time.
|
|
43
|
+
</p>
|
|
44
|
+
<RecycleRollSection
|
|
45
|
+
recycle={recycle.recycle}
|
|
46
|
+
fresh={recycle.fresh}
|
|
47
|
+
stopped={recycle.recycle_stopped}
|
|
48
|
+
writerNote="this worker recycles itself after replying"
|
|
49
|
+
/>
|
|
50
|
+
</section>
|
|
51
|
+
) : null}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two frames an install form renders in: a dialog (the confirm shell) or a panel
|
|
3
|
+
* in place (its own `<form>` with Cancel and the submit button), so a step can embed
|
|
4
|
+
* the form without a dialog inside a dialog. The failure of the last call renders
|
|
5
|
+
* inside the frame and the frame stays open.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactNode, SyntheticEvent } from 'react';
|
|
8
|
+
|
|
9
|
+
import { ConfirmDialog } from '../components/confirm-dialog';
|
|
10
|
+
import { Button, ErrorState, Spinner } from '../components/primitives';
|
|
11
|
+
import { errorMessage } from '../errors';
|
|
12
|
+
import { FeatureDisabled, featureDisabledMessage, isFeatureDisabled } from '../feature-disabled';
|
|
13
|
+
|
|
14
|
+
export type InstallLayout = 'panel' | 'dialog';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The muted note for a store-off refusal (an OFF feature is a state, not an error),
|
|
18
|
+
* or `undefined` for any other value.
|
|
19
|
+
*/
|
|
20
|
+
export function storeOffNote(error: unknown): ReactNode {
|
|
21
|
+
if (!isFeatureDisabled(error)) return undefined;
|
|
22
|
+
return <FeatureDisabled feature="Marketplace installs" message={featureDisabledMessage(error)} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A failed call's surface: the store-off note, else the server message as an alert. */
|
|
26
|
+
export function InstallFailure({ error }: { readonly error: unknown }): ReactNode {
|
|
27
|
+
return storeOffNote(error) ?? <ErrorState message={errorMessage(error)} />;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface InstallShellProps {
|
|
31
|
+
readonly layout: InstallLayout;
|
|
32
|
+
readonly title: string;
|
|
33
|
+
readonly submitLabel: string;
|
|
34
|
+
readonly pendingLabel: string;
|
|
35
|
+
readonly isPending: boolean;
|
|
36
|
+
/** The loud failure; `null` while there is none. */
|
|
37
|
+
readonly error: unknown;
|
|
38
|
+
/** A muted note that takes the failure slot and blocks the submit. */
|
|
39
|
+
readonly disabledNote?: ReactNode;
|
|
40
|
+
readonly submitDisabled?: boolean;
|
|
41
|
+
readonly onSubmit: () => void;
|
|
42
|
+
readonly onDismiss: () => void;
|
|
43
|
+
readonly children: ReactNode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function InstallShell({
|
|
47
|
+
layout,
|
|
48
|
+
title,
|
|
49
|
+
submitLabel,
|
|
50
|
+
pendingLabel,
|
|
51
|
+
isPending,
|
|
52
|
+
error,
|
|
53
|
+
disabledNote,
|
|
54
|
+
submitDisabled = false,
|
|
55
|
+
onSubmit,
|
|
56
|
+
onDismiss,
|
|
57
|
+
children,
|
|
58
|
+
}: InstallShellProps): ReactNode {
|
|
59
|
+
const errorText = error === null || error === undefined ? null : errorMessage(error);
|
|
60
|
+
if (layout === 'dialog') {
|
|
61
|
+
return (
|
|
62
|
+
<ConfirmDialog
|
|
63
|
+
title={title}
|
|
64
|
+
confirmLabel={submitLabel}
|
|
65
|
+
pendingLabel={pendingLabel}
|
|
66
|
+
confirmVariant="primary"
|
|
67
|
+
isPending={isPending}
|
|
68
|
+
error={errorText}
|
|
69
|
+
disabledNote={disabledNote}
|
|
70
|
+
confirmDisabled={submitDisabled}
|
|
71
|
+
onConfirm={onSubmit}
|
|
72
|
+
onClose={onDismiss}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</ConfirmDialog>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const blocked =
|
|
79
|
+
isPending || submitDisabled || (disabledNote !== undefined && disabledNote !== null);
|
|
80
|
+
const handleSubmit = (event: SyntheticEvent<HTMLFormElement>): void => {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
if (!blocked) onSubmit();
|
|
83
|
+
};
|
|
84
|
+
return (
|
|
85
|
+
<form aria-label={title} className="tai-stack tai-stack-3" onSubmit={handleSubmit}>
|
|
86
|
+
{children}
|
|
87
|
+
{disabledNote ?? (errorText !== null ? <ErrorState message={errorText} /> : null)}
|
|
88
|
+
<div className="tai-dialog-actions">
|
|
89
|
+
<Button type="button" onClick={onDismiss}>
|
|
90
|
+
Cancel
|
|
91
|
+
</Button>
|
|
92
|
+
<Button type="submit" variant="primary" disabled={blocked}>
|
|
93
|
+
{isPending ? <Spinner label={pendingLabel} /> : null}
|
|
94
|
+
{submitLabel}
|
|
95
|
+
</Button>
|
|
96
|
+
</div>
|
|
97
|
+
</form>
|
|
98
|
+
);
|
|
99
|
+
}
|