@camstack/addon-pipeline-orchestrator 0.1.13 → 0.1.15
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/@mf-types/compiled-types/widgets/MotionZonesEditor.d.ts +19 -0
- package/dist/@mf-types/compiled-types/widgets/MotionZonesEditor.d.ts.map +1 -0
- package/dist/@mf-types/compiled-types/widgets/index.d.ts +2 -0
- package/dist/@mf-types/compiled-types/widgets/index.d.ts.map +1 -1
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionGridCanvas.d.ts +20 -0
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionGridCanvas.d.ts.map +1 -0
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesOverlay.d.ts +14 -0
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesOverlay.d.ts.map +1 -0
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesTab.d.ts +5 -0
- package/dist/@mf-types/compiled-types/widgets/motion-zones/MotionZonesTab.d.ts.map +1 -0
- package/dist/@mf-types.zip +0 -0
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-UNj4rttw.mjs +20 -0
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-Ue-jHwF0.mjs +35 -0
- package/dist/_stub.js +4592 -4319
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-CPFxFaeP.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-sOGV7J8L.mjs} +6 -6
- package/dist/{hostInit-C2nlXV13.mjs → hostInit-DMdjwivI.mjs} +6 -6
- package/dist/{index-DfLih_hW.mjs → index-BIlr4dIX.mjs} +1 -1
- package/dist/{index-COebxMhm.mjs → index-BK5-EWzN.mjs} +2488 -2460
- package/dist/{index-CIjsCPwy.mjs → index-BUn7hM0v.mjs} +2871 -2758
- package/dist/index.js +362 -309
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +362 -309
- package/dist/index.mjs.map +1 -1
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-B47e6UPj.mjs +0 -20
- package/dist/__mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BErlAIMU.mjs +0 -34
package/dist/index.mjs
CHANGED
|
@@ -5134,6 +5134,9 @@ function hydrateField(field, values) {
|
|
|
5134
5134
|
return { ...field, value: items };
|
|
5135
5135
|
}
|
|
5136
5136
|
const rawValue = storedValue !== void 0 ? storedValue : defaultValue !== void 0 ? defaultValue : null;
|
|
5137
|
+
if (field.type === "password") {
|
|
5138
|
+
return { ...field, value: "" };
|
|
5139
|
+
}
|
|
5137
5140
|
const value = field.type === "textarea" && field.isJson && rawValue !== null && typeof rawValue === "object" ? JSON.stringify(rawValue, null, 2) : rawValue;
|
|
5138
5141
|
const hydrated = { ...field, value };
|
|
5139
5142
|
return hydrated;
|
|
@@ -7343,6 +7346,34 @@ MotionTriggerStatusSchema.extend({
|
|
|
7343
7346
|
}) }
|
|
7344
7347
|
}
|
|
7345
7348
|
});
|
|
7349
|
+
object({
|
|
7350
|
+
enabled: boolean(),
|
|
7351
|
+
sensitivity: number(),
|
|
7352
|
+
/** Row-major active-cell grid. Length = gridWidth*gridHeight (see getOptions). */
|
|
7353
|
+
cells: array(boolean()),
|
|
7354
|
+
lastFetchedAt: number()
|
|
7355
|
+
});
|
|
7356
|
+
const MotionZoneOptionsSchema = object({
|
|
7357
|
+
gridWidth: number(),
|
|
7358
|
+
gridHeight: number(),
|
|
7359
|
+
sensitivity: object({ min: number(), max: number(), step: number() })
|
|
7360
|
+
});
|
|
7361
|
+
const MotionZonePatchSchema = object({
|
|
7362
|
+
enabled: boolean().optional(),
|
|
7363
|
+
sensitivity: number().optional(),
|
|
7364
|
+
cells: array(boolean()).optional()
|
|
7365
|
+
});
|
|
7366
|
+
({
|
|
7367
|
+
deviceTypes: [DeviceType.Camera],
|
|
7368
|
+
methods: {
|
|
7369
|
+
getOptions: method(object({ deviceId: number() }), MotionZoneOptionsSchema),
|
|
7370
|
+
setZone: method(
|
|
7371
|
+
object({ deviceId: number(), patch: MotionZonePatchSchema }),
|
|
7372
|
+
_void(),
|
|
7373
|
+
{ kind: "mutation", auth: "admin" }
|
|
7374
|
+
)
|
|
7375
|
+
}
|
|
7376
|
+
});
|
|
7346
7377
|
const AutotrackTargetTypeSchema = string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
|
|
7347
7378
|
const PtzAutotrackSettingsSchema = object({
|
|
7348
7379
|
targetType: AutotrackTargetTypeSchema,
|
|
@@ -7431,6 +7462,72 @@ PtzAutotrackStatusSchema.extend({
|
|
|
7431
7462
|
}) }
|
|
7432
7463
|
}
|
|
7433
7464
|
});
|
|
7465
|
+
const StreamProfileSchema = _enum(["main", "sub", "ext"]);
|
|
7466
|
+
const StreamProfileConfigSchema = object({
|
|
7467
|
+
width: number(),
|
|
7468
|
+
height: number(),
|
|
7469
|
+
codec: _enum(["h264", "h265"]),
|
|
7470
|
+
framerate: number(),
|
|
7471
|
+
bitrate: number(),
|
|
7472
|
+
// kbps
|
|
7473
|
+
bitrateMode: _enum(["vbr", "cbr"]).optional(),
|
|
7474
|
+
encoderProfile: _enum(["high", "main", "baseline"]).optional(),
|
|
7475
|
+
gop: number().optional(),
|
|
7476
|
+
audio: boolean().optional()
|
|
7477
|
+
});
|
|
7478
|
+
object({
|
|
7479
|
+
/** Per-profile current config. A profile absent = the camera doesn't have it. */
|
|
7480
|
+
main: StreamProfileConfigSchema.optional(),
|
|
7481
|
+
sub: StreamProfileConfigSchema.optional(),
|
|
7482
|
+
ext: StreamProfileConfigSchema.optional(),
|
|
7483
|
+
lastFetchedAt: number()
|
|
7484
|
+
});
|
|
7485
|
+
const StreamProfileOptionsSchema = object({
|
|
7486
|
+
resolutions: array(object({ width: number(), height: number() })),
|
|
7487
|
+
codecs: array(_enum(["h264", "h265"])),
|
|
7488
|
+
framerates: array(number()),
|
|
7489
|
+
/** Allowed bitrate values (kbps). Empty if the camera takes a free range. */
|
|
7490
|
+
bitrates: array(number()),
|
|
7491
|
+
/** Optional [min,max] kbps when the camera accepts a continuous range. */
|
|
7492
|
+
bitrateRange: tuple([number(), number()]).optional(),
|
|
7493
|
+
supportsBitrateMode: boolean(),
|
|
7494
|
+
supportsEncoderProfile: boolean(),
|
|
7495
|
+
supportsGop: boolean()
|
|
7496
|
+
});
|
|
7497
|
+
const StreamParamsOptionsSchema = object({
|
|
7498
|
+
main: StreamProfileOptionsSchema.optional(),
|
|
7499
|
+
sub: StreamProfileOptionsSchema.optional(),
|
|
7500
|
+
ext: StreamProfileOptionsSchema.optional()
|
|
7501
|
+
});
|
|
7502
|
+
const StreamProfilePatchSchema = object({
|
|
7503
|
+
width: number().optional(),
|
|
7504
|
+
height: number().optional(),
|
|
7505
|
+
codec: _enum(["h264", "h265"]).optional(),
|
|
7506
|
+
framerate: number().optional(),
|
|
7507
|
+
bitrate: number().optional(),
|
|
7508
|
+
bitrateMode: _enum(["vbr", "cbr"]).optional(),
|
|
7509
|
+
encoderProfile: _enum(["high", "main", "baseline"]).optional(),
|
|
7510
|
+
gop: number().optional(),
|
|
7511
|
+
audio: boolean().optional()
|
|
7512
|
+
});
|
|
7513
|
+
({
|
|
7514
|
+
deviceTypes: [DeviceType.Camera],
|
|
7515
|
+
methods: {
|
|
7516
|
+
getOptions: method(
|
|
7517
|
+
object({ deviceId: number() }),
|
|
7518
|
+
StreamParamsOptionsSchema
|
|
7519
|
+
),
|
|
7520
|
+
setProfile: method(
|
|
7521
|
+
object({
|
|
7522
|
+
deviceId: number(),
|
|
7523
|
+
profile: StreamProfileSchema,
|
|
7524
|
+
patch: StreamProfilePatchSchema
|
|
7525
|
+
}),
|
|
7526
|
+
_void(),
|
|
7527
|
+
{ kind: "mutation", auth: "admin" }
|
|
7528
|
+
)
|
|
7529
|
+
}
|
|
7530
|
+
});
|
|
7434
7531
|
object({
|
|
7435
7532
|
on: boolean(),
|
|
7436
7533
|
/** Ms epoch of the last state change. Useful for UI "X minutes ago". */
|
|
@@ -8371,6 +8468,83 @@ const VersionOutputSchema = object({ version: string() });
|
|
|
8371
8468
|
getVersion: method(_void(), VersionOutputSchema)
|
|
8372
8469
|
}
|
|
8373
8470
|
});
|
|
8471
|
+
const MethodAccessSchema = _enum(["view", "create", "delete"]);
|
|
8472
|
+
const AllowedProviderSchema = union([literal("*"), array(string())]);
|
|
8473
|
+
const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
|
|
8474
|
+
const CapScopeSchema = _enum(["device", "system"]);
|
|
8475
|
+
const TokenScopeSchema = discriminatedUnion("type", [
|
|
8476
|
+
object({
|
|
8477
|
+
type: literal("category"),
|
|
8478
|
+
target: CapScopeSchema,
|
|
8479
|
+
access: array(MethodAccessSchema).min(1)
|
|
8480
|
+
}),
|
|
8481
|
+
object({
|
|
8482
|
+
type: literal("capability"),
|
|
8483
|
+
target: string(),
|
|
8484
|
+
access: array(MethodAccessSchema).min(1)
|
|
8485
|
+
}),
|
|
8486
|
+
object({
|
|
8487
|
+
type: literal("addon"),
|
|
8488
|
+
target: string(),
|
|
8489
|
+
access: array(MethodAccessSchema).min(1)
|
|
8490
|
+
}),
|
|
8491
|
+
object({
|
|
8492
|
+
type: literal("device"),
|
|
8493
|
+
/**
|
|
8494
|
+
* One or more deviceIds (serialised as strings for wire-format
|
|
8495
|
+
* consistency with the rest of the union). Matcher accepts if
|
|
8496
|
+
* `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
|
|
8497
|
+
* of one scope-per-device when granting access to a set of cameras.
|
|
8498
|
+
*/
|
|
8499
|
+
targets: array(string()).min(1),
|
|
8500
|
+
access: array(MethodAccessSchema).min(1)
|
|
8501
|
+
})
|
|
8502
|
+
]);
|
|
8503
|
+
object({
|
|
8504
|
+
id: string(),
|
|
8505
|
+
username: string(),
|
|
8506
|
+
passwordHash: string(),
|
|
8507
|
+
/**
|
|
8508
|
+
* Admin bypass. When true, the middleware skips the scope-access
|
|
8509
|
+
* check entirely. There is no other axis of privilege; the legacy
|
|
8510
|
+
* role enum collapsed onto this boolean in v2.
|
|
8511
|
+
*/
|
|
8512
|
+
isAdmin: boolean().default(false),
|
|
8513
|
+
allowedProviders: AllowedProviderSchema,
|
|
8514
|
+
allowedDevices: AllowedDevicesSchema,
|
|
8515
|
+
/**
|
|
8516
|
+
* Scopes granted to this user. Admins bypass; their `scopes` is
|
|
8517
|
+
* ignored. Non-admins without scopes are locked out of every
|
|
8518
|
+
* protected call.
|
|
8519
|
+
*/
|
|
8520
|
+
scopes: array(TokenScopeSchema).default([]),
|
|
8521
|
+
createdAt: number(),
|
|
8522
|
+
updatedAt: number()
|
|
8523
|
+
});
|
|
8524
|
+
object({
|
|
8525
|
+
id: string(),
|
|
8526
|
+
label: string(),
|
|
8527
|
+
isAdmin: boolean().default(false),
|
|
8528
|
+
allowedProviders: AllowedProviderSchema,
|
|
8529
|
+
allowedDevices: AllowedDevicesSchema,
|
|
8530
|
+
tokenHash: string(),
|
|
8531
|
+
tokenPrefix: string(),
|
|
8532
|
+
createdAt: number(),
|
|
8533
|
+
lastUsedAt: number().optional()
|
|
8534
|
+
});
|
|
8535
|
+
object({
|
|
8536
|
+
id: string(),
|
|
8537
|
+
userId: string(),
|
|
8538
|
+
name: string(),
|
|
8539
|
+
tokenHash: string(),
|
|
8540
|
+
tokenPrefix: string(),
|
|
8541
|
+
scopes: array(TokenScopeSchema),
|
|
8542
|
+
// SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
|
|
8543
|
+
// schema accepts both `null` (read from disk) and `undefined` (in-memory).
|
|
8544
|
+
expiresAt: number().nullish(),
|
|
8545
|
+
lastUsedAt: number().nullish(),
|
|
8546
|
+
createdAt: number()
|
|
8547
|
+
});
|
|
8374
8548
|
const SsoBridgeClaimsSchema = object({
|
|
8375
8549
|
userId: string(),
|
|
8376
8550
|
username: string(),
|
|
@@ -8386,7 +8560,18 @@ const SsoBridgeClaimsSchema = object({
|
|
|
8386
8560
|
* JWT WITHOUT verifying the signature — the hub re-verifies on every
|
|
8387
8561
|
* inbound call so trust still rests with the signing hub.
|
|
8388
8562
|
*/
|
|
8389
|
-
hubUrl: string().optional()
|
|
8563
|
+
hubUrl: string().optional(),
|
|
8564
|
+
/** Permission scopes baked into the token. Set by the OAuth
|
|
8565
|
+
* account-linking grant; absent on ordinary SSO-login tokens. */
|
|
8566
|
+
scopes: array(TokenScopeSchema).optional(),
|
|
8567
|
+
/** OAuth authorization-code binding — set only on `oauth-code` tokens. */
|
|
8568
|
+
redirectUri: string().optional(),
|
|
8569
|
+
integrationId: string().optional(),
|
|
8570
|
+
/** JWT ID — unique per issued code; consumed-set enforces single-use. */
|
|
8571
|
+
jti: string().optional(),
|
|
8572
|
+
/** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
|
|
8573
|
+
* tokens so the verify path can check the session is not revoked. */
|
|
8574
|
+
sessionId: string().optional()
|
|
8390
8575
|
});
|
|
8391
8576
|
({
|
|
8392
8577
|
methods: {
|
|
@@ -8403,6 +8588,23 @@ const SsoBridgeClaimsSchema = object({
|
|
|
8403
8588
|
)
|
|
8404
8589
|
}
|
|
8405
8590
|
});
|
|
8591
|
+
const OauthIntegrationDescriptorSchema = object({
|
|
8592
|
+
/** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
|
|
8593
|
+
integrationId: string(),
|
|
8594
|
+
/** Human label rendered on the consent page. */
|
|
8595
|
+
displayName: string(),
|
|
8596
|
+
/** Scopes baked into every token issued for this integration. */
|
|
8597
|
+
requestedScopes: array(TokenScopeSchema),
|
|
8598
|
+
/** Allowed redirect_uri prefixes. /oauth2/authorize rejects any
|
|
8599
|
+
* redirect_uri that does not start with one of these. Required —
|
|
8600
|
+
* an empty list means the integration can never complete linking. */
|
|
8601
|
+
allowedRedirectPrefixes: array(string()).min(1)
|
|
8602
|
+
});
|
|
8603
|
+
({
|
|
8604
|
+
methods: {
|
|
8605
|
+
getDescriptor: method(_void(), OauthIntegrationDescriptorSchema)
|
|
8606
|
+
}
|
|
8607
|
+
});
|
|
8406
8608
|
const PasskeySummarySchema = object({
|
|
8407
8609
|
credentialId: string(),
|
|
8408
8610
|
label: string(),
|
|
@@ -10292,51 +10494,6 @@ const AuthResultSchema = object({
|
|
|
10292
10494
|
validateToken: method(object({ token: string() }), AuthResultSchema.nullable())
|
|
10293
10495
|
}
|
|
10294
10496
|
});
|
|
10295
|
-
const AuthProviderInfoSchema = object({
|
|
10296
|
-
/** Stable id matching the addon id (used for `getLoginUrl({addonId,…})`). */
|
|
10297
|
-
addonId: string(),
|
|
10298
|
-
/**
|
|
10299
|
-
* Per-instance id when one addon registers multiple "logical"
|
|
10300
|
-
* providers (e.g. OIDC with Google + Microsoft + custom). The login
|
|
10301
|
-
* URL becomes `/addon/${addonId}/${instanceId}/start` — handler reads
|
|
10302
|
-
* `:instanceId` from the route. Empty/unset means the addon is a
|
|
10303
|
-
* single-instance provider; the URL is `/addon/${addonId}/start`.
|
|
10304
|
-
*/
|
|
10305
|
-
instanceId: string().optional(),
|
|
10306
|
-
/** Display label shown on the login button + admin row. */
|
|
10307
|
-
displayName: string(),
|
|
10308
|
-
/** Optional iconography hint (lucide-react icon name OR emoji). */
|
|
10309
|
-
icon: string().optional(),
|
|
10310
|
-
/** When true, the provider exposes a redirect-based login flow
|
|
10311
|
-
* (`getLoginUrl` returns a URL the browser navigates to). */
|
|
10312
|
-
hasRedirectFlow: boolean(),
|
|
10313
|
-
/** When true, the provider exposes a credential-form login flow
|
|
10314
|
-
* (`validateCredentials` accepts username + password). */
|
|
10315
|
-
hasCredentialFlow: boolean(),
|
|
10316
|
-
/** Provider kind, drives admin-UI hint dispatch (oidc / saml / totp / …). */
|
|
10317
|
-
kind: string().optional(),
|
|
10318
|
-
/** Operator-facing status string (e.g. "Connected to https://login.acme.com"). */
|
|
10319
|
-
status: string().optional(),
|
|
10320
|
-
/** When false, the provider is registered but disabled by config; the
|
|
10321
|
-
* UI surfaces it as inactive without enumerating it for login. */
|
|
10322
|
-
enabled: boolean()
|
|
10323
|
-
});
|
|
10324
|
-
({
|
|
10325
|
-
methods: {
|
|
10326
|
-
/** All registered auth providers, both enabled and disabled. */
|
|
10327
|
-
listProviders: method(_void(), array(AuthProviderInfoSchema).readonly()),
|
|
10328
|
-
/**
|
|
10329
|
-
* Toggle a provider's enabled flag. Disabled providers stay
|
|
10330
|
-
* registered but aren't surfaced on the login page. The orchestrator
|
|
10331
|
-
* persists the state in `addon-settings` so it survives restarts.
|
|
10332
|
-
*/
|
|
10333
|
-
setProviderEnabled: method(
|
|
10334
|
-
object({ addonId: string(), enabled: boolean() }),
|
|
10335
|
-
object({ success: literal(true) }),
|
|
10336
|
-
{ kind: "mutation", auth: "admin" }
|
|
10337
|
-
)
|
|
10338
|
-
}
|
|
10339
|
-
});
|
|
10340
10497
|
const NetworkEndpointSchema = object({
|
|
10341
10498
|
url: string(),
|
|
10342
10499
|
hostname: string(),
|
|
@@ -10368,55 +10525,13 @@ const NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
10368
10525
|
getEndpoint: method(_void(), NetworkEndpointSchema.nullable()),
|
|
10369
10526
|
getStatus: method(_void(), NetworkAccessStatusSchema),
|
|
10370
10527
|
/**
|
|
10371
|
-
* Enumerate every active ingress entry.
|
|
10372
|
-
*
|
|
10373
|
-
*
|
|
10528
|
+
* Enumerate every active ingress entry. Providers that expose only a
|
|
10529
|
+
* single endpoint may omit this method; callers fall back to
|
|
10530
|
+
* `getEndpoint()` in that case.
|
|
10374
10531
|
*/
|
|
10375
10532
|
listEndpoints: method(_void(), array(NetworkEndpointEntrySchema).readonly())
|
|
10376
10533
|
}
|
|
10377
10534
|
});
|
|
10378
|
-
const RemoteAccessEndpointSchema = object({
|
|
10379
|
-
url: string(),
|
|
10380
|
-
hostname: string(),
|
|
10381
|
-
port: number(),
|
|
10382
|
-
protocol: _enum(["http", "https"])
|
|
10383
|
-
});
|
|
10384
|
-
const RemoteAccessProviderInfoSchema = object({
|
|
10385
|
-
/** Stable id matching the addon id. */
|
|
10386
|
-
addonId: string(),
|
|
10387
|
-
/** Display label shown on the admin row — sourced from the addon manifest. */
|
|
10388
|
-
displayName: string(),
|
|
10389
|
-
/** When false, the provider is registered but disabled. */
|
|
10390
|
-
enabled: boolean(),
|
|
10391
|
-
/** True when the underlying tunnel/connection is up. */
|
|
10392
|
-
connected: boolean(),
|
|
10393
|
-
/** Public-facing endpoint, when connected. Null otherwise. */
|
|
10394
|
-
endpoint: RemoteAccessEndpointSchema.nullable(),
|
|
10395
|
-
/** Last error message (when connected=false), if available. */
|
|
10396
|
-
error: string().optional()
|
|
10397
|
-
});
|
|
10398
|
-
({
|
|
10399
|
-
methods: {
|
|
10400
|
-
/** All registered remote-access providers + their live status. */
|
|
10401
|
-
listProviders: method(_void(), array(RemoteAccessProviderInfoSchema).readonly()),
|
|
10402
|
-
/**
|
|
10403
|
-
* Start a specific provider's tunnel. Per-provider config still
|
|
10404
|
-
* lives on the addon's settings panel; this is just the on/off
|
|
10405
|
-
* trigger so the admin UI can manage the lifecycle from one place.
|
|
10406
|
-
*/
|
|
10407
|
-
startProvider: method(
|
|
10408
|
-
object({ addonId: string() }),
|
|
10409
|
-
RemoteAccessEndpointSchema,
|
|
10410
|
-
{ kind: "mutation", auth: "admin" }
|
|
10411
|
-
),
|
|
10412
|
-
/** Stop a specific provider's tunnel (idempotent on already-stopped). */
|
|
10413
|
-
stopProvider: method(
|
|
10414
|
-
object({ addonId: string() }),
|
|
10415
|
-
object({ success: literal(true) }),
|
|
10416
|
-
{ kind: "mutation", auth: "admin" }
|
|
10417
|
-
)
|
|
10418
|
-
}
|
|
10419
|
-
});
|
|
10420
10535
|
const TurnServerSchema = object({
|
|
10421
10536
|
/** Single URL or list of URLs (e.g. "turn:turn.example.com:3478?transport=udp"). */
|
|
10422
10537
|
urls: union([string(), array(string())]),
|
|
@@ -10436,45 +10551,6 @@ const TurnServerSchema = object({
|
|
|
10436
10551
|
)
|
|
10437
10552
|
}
|
|
10438
10553
|
});
|
|
10439
|
-
const TurnProviderInfoSchema = object({
|
|
10440
|
-
/** Stable id matching the addon id. */
|
|
10441
|
-
addonId: string(),
|
|
10442
|
-
/** Display label shown on the admin row — sourced from the addon manifest. */
|
|
10443
|
-
displayName: string(),
|
|
10444
|
-
/** When false, the provider is registered but disabled. */
|
|
10445
|
-
enabled: boolean(),
|
|
10446
|
-
/** Number of servers this provider is currently exposing. */
|
|
10447
|
-
serverCount: number(),
|
|
10448
|
-
/**
|
|
10449
|
-
* Flat list of every TURN/STUN URL this provider currently exposes.
|
|
10450
|
-
* One row per URL (multi-URL ICE server entries are flattened). The
|
|
10451
|
-
* admin UI shows this in a compact per-provider list so operators
|
|
10452
|
-
* can verify what's actually being negotiated without having to dig
|
|
10453
|
-
* into the combined `getAllServers` output.
|
|
10454
|
-
*/
|
|
10455
|
-
urls: array(string()).readonly(),
|
|
10456
|
-
/** Last fetch error (when serverCount=0 due to API failure), if any. */
|
|
10457
|
-
error: string().optional()
|
|
10458
|
-
});
|
|
10459
|
-
({
|
|
10460
|
-
methods: {
|
|
10461
|
-
/** All registered TURN providers + per-provider stats. */
|
|
10462
|
-
listProviders: method(_void(), array(TurnProviderInfoSchema).readonly()),
|
|
10463
|
-
/**
|
|
10464
|
-
* Combined list of TURN/STUN servers from all ENABLED providers.
|
|
10465
|
-
* Consumed by the WebRTC layer at session-creation time —
|
|
10466
|
-
* implementations may fetch fresh short-lived credentials each
|
|
10467
|
-
* call (e.g. Cloudflare API), so consumers SHOULD call per-session.
|
|
10468
|
-
*/
|
|
10469
|
-
getAllServers: method(_void(), array(TurnServerSchema).readonly()),
|
|
10470
|
-
/** Toggle a provider's enabled flag. */
|
|
10471
|
-
setProviderEnabled: method(
|
|
10472
|
-
object({ addonId: string(), enabled: boolean() }),
|
|
10473
|
-
object({ success: literal(true) }),
|
|
10474
|
-
{ kind: "mutation", auth: "admin" }
|
|
10475
|
-
)
|
|
10476
|
-
}
|
|
10477
|
-
});
|
|
10478
10554
|
const SnapshotImageSchema = object({
|
|
10479
10555
|
base64: string(),
|
|
10480
10556
|
contentType: string()
|
|
@@ -11202,6 +11278,14 @@ const PtzMoveCommandSchema = object({
|
|
|
11202
11278
|
zoom: number().optional(),
|
|
11203
11279
|
speed: number().optional()
|
|
11204
11280
|
});
|
|
11281
|
+
const PtzOptionsSchema = object({
|
|
11282
|
+
hasPan: boolean(),
|
|
11283
|
+
hasTilt: boolean(),
|
|
11284
|
+
hasZoom: boolean(),
|
|
11285
|
+
supportsPresets: boolean(),
|
|
11286
|
+
/** Max number of named presets the camera supports, when known. */
|
|
11287
|
+
maxPresets: number().optional()
|
|
11288
|
+
});
|
|
11205
11289
|
({
|
|
11206
11290
|
deviceTypes: [DeviceType.Camera],
|
|
11207
11291
|
methods: {
|
|
@@ -11229,6 +11313,20 @@ const PtzMoveCommandSchema = object({
|
|
|
11229
11313
|
_void(),
|
|
11230
11314
|
{ kind: "mutation" }
|
|
11231
11315
|
),
|
|
11316
|
+
savePreset: method(
|
|
11317
|
+
object({ deviceId: number(), presetId: string(), name: string() }),
|
|
11318
|
+
_void(),
|
|
11319
|
+
{ kind: "mutation", auth: "admin" }
|
|
11320
|
+
),
|
|
11321
|
+
deletePreset: method(
|
|
11322
|
+
object({ deviceId: number(), presetId: string() }),
|
|
11323
|
+
_void(),
|
|
11324
|
+
{ kind: "mutation", auth: "admin" }
|
|
11325
|
+
),
|
|
11326
|
+
getOptions: method(
|
|
11327
|
+
object({ deviceId: number() }),
|
|
11328
|
+
PtzOptionsSchema
|
|
11329
|
+
),
|
|
11232
11330
|
goHome: method(
|
|
11233
11331
|
object({ deviceId: number() }),
|
|
11234
11332
|
_void(),
|
|
@@ -11942,7 +12040,7 @@ const AllowedAddressesSchema = object({
|
|
|
11942
12040
|
)
|
|
11943
12041
|
}
|
|
11944
12042
|
});
|
|
11945
|
-
const MeshEndpointSchema
|
|
12043
|
+
const MeshEndpointSchema = object({
|
|
11946
12044
|
/** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
|
|
11947
12045
|
id: string(),
|
|
11948
12046
|
/** Operator-facing label (e.g. "Mesh IPv4", "MagicDNS"). */
|
|
@@ -12015,7 +12113,7 @@ const MeshStatusSchema = object({
|
|
|
12015
12113
|
/** Number of peers visible to this host (excluding self). */
|
|
12016
12114
|
peerCount: number(),
|
|
12017
12115
|
/** Every endpoint this provider exposes for the current host. */
|
|
12018
|
-
endpoints: array(MeshEndpointSchema
|
|
12116
|
+
endpoints: array(MeshEndpointSchema).readonly(),
|
|
12019
12117
|
/** Last error from the daemon, when not joined. */
|
|
12020
12118
|
error: string().optional(),
|
|
12021
12119
|
// ── Account / tenant identity (generic across providers) ────────
|
|
@@ -12178,182 +12276,6 @@ const MeshStatusSchema = object({
|
|
|
12178
12276
|
// tabs driven by this cap.
|
|
12179
12277
|
}
|
|
12180
12278
|
});
|
|
12181
|
-
const MeshEndpointSchema = object({
|
|
12182
|
-
id: string(),
|
|
12183
|
-
label: string(),
|
|
12184
|
-
scope: _enum(["mesh", "public"]),
|
|
12185
|
-
url: string(),
|
|
12186
|
-
hostname: string(),
|
|
12187
|
-
port: number(),
|
|
12188
|
-
protocol: _enum(["http", "https"])
|
|
12189
|
-
});
|
|
12190
|
-
const MeshProviderInfoSchema = object({
|
|
12191
|
-
/** Stable id matching the addon id. */
|
|
12192
|
-
addonId: string(),
|
|
12193
|
-
/** Display label shown on the admin row — sourced from the addon manifest. */
|
|
12194
|
-
displayName: string(),
|
|
12195
|
-
/** True when the host is joined to this provider's mesh. */
|
|
12196
|
-
joined: boolean(),
|
|
12197
|
-
/** Local mesh IP (empty when not joined). */
|
|
12198
|
-
meshIp: string(),
|
|
12199
|
-
/** MagicDNS / mesh hostname (empty when not configured). */
|
|
12200
|
-
magicDnsHostname: string(),
|
|
12201
|
-
/** Peer count (excluding self). */
|
|
12202
|
-
peerCount: number(),
|
|
12203
|
-
/** Active endpoints (mesh IP + MagicDNS + optional public Funnel). */
|
|
12204
|
-
endpoints: array(MeshEndpointSchema).readonly(),
|
|
12205
|
-
/** Last error reported by the provider. */
|
|
12206
|
-
error: string().optional(),
|
|
12207
|
-
// ── Generic identity fields mirrored from MeshStatus ─────────────
|
|
12208
|
-
/** Tenant / tailnet / network display name. Empty pre-join. */
|
|
12209
|
-
tenantName: string(),
|
|
12210
|
-
/** Mesh DNS suffix (e.g. tailXXXX.ts.net). Empty when not configured. */
|
|
12211
|
-
magicDnsSuffix: string(),
|
|
12212
|
-
/** Authenticated user / account login. Null for token-only providers. */
|
|
12213
|
-
userLogin: string().nullable(),
|
|
12214
|
-
/** Provider control-plane URL. */
|
|
12215
|
-
controlPlaneUrl: string(),
|
|
12216
|
-
/** Machine-key expiry (epoch ms). Null when keys don't rotate. */
|
|
12217
|
-
keyExpiry: number().nullable()
|
|
12218
|
-
});
|
|
12219
|
-
({
|
|
12220
|
-
methods: {
|
|
12221
|
-
/** All registered mesh-network providers + live status. */
|
|
12222
|
-
listProviders: method(_void(), array(MeshProviderInfoSchema).readonly()),
|
|
12223
|
-
/**
|
|
12224
|
-
* Join the mesh of a specific provider. Per-provider config still
|
|
12225
|
-
* lives on its settings panel; the orchestrator forwards.
|
|
12226
|
-
*/
|
|
12227
|
-
joinProvider: method(
|
|
12228
|
-
object({
|
|
12229
|
-
addonId: string(),
|
|
12230
|
-
authKey: string().min(8),
|
|
12231
|
-
hostname: string().optional()
|
|
12232
|
-
}),
|
|
12233
|
-
object({ joined: literal(true) }),
|
|
12234
|
-
{ kind: "mutation" }
|
|
12235
|
-
),
|
|
12236
|
-
leaveProvider: method(
|
|
12237
|
-
object({ addonId: string() }),
|
|
12238
|
-
object({ success: literal(true) }),
|
|
12239
|
-
{ kind: "mutation" }
|
|
12240
|
-
),
|
|
12241
|
-
/**
|
|
12242
|
-
* Browser-redirect login flow. Forwards to the named provider's
|
|
12243
|
-
* `mesh-network.startLogin` and returns the URL the daemon
|
|
12244
|
-
* prints. UI opens it in a new tab, then polls `listProviders`
|
|
12245
|
-
* for `joined: true`.
|
|
12246
|
-
*/
|
|
12247
|
-
startLoginProvider: method(
|
|
12248
|
-
object({
|
|
12249
|
-
addonId: string(),
|
|
12250
|
-
hostname: string().optional()
|
|
12251
|
-
}),
|
|
12252
|
-
object({ loginUrl: string() }),
|
|
12253
|
-
{ kind: "mutation" }
|
|
12254
|
-
),
|
|
12255
|
-
/**
|
|
12256
|
-
* Sign out of the provider's account entirely (`mesh-network.logout`).
|
|
12257
|
-
* Distinct from `leaveProvider` which only takes the host off-mesh;
|
|
12258
|
-
* `logoutProvider` wipes credentials so the next start requires a
|
|
12259
|
-
* fresh login.
|
|
12260
|
-
*/
|
|
12261
|
-
logoutProvider: method(
|
|
12262
|
-
object({ addonId: string() }),
|
|
12263
|
-
object({ loggedOut: literal(true) }),
|
|
12264
|
-
{ kind: "mutation" }
|
|
12265
|
-
),
|
|
12266
|
-
/**
|
|
12267
|
-
* Per-provider peer list. Forwards to `mesh-network.listPeers` on
|
|
12268
|
-
* the addressed provider. Separate from `listProviders` because
|
|
12269
|
-
* peer payloads can be large on a heavily-populated tailnet —
|
|
12270
|
-
* fetch only when the operator opens the Peers tab.
|
|
12271
|
-
*/
|
|
12272
|
-
listProviderPeers: method(
|
|
12273
|
-
object({ addonId: string() }),
|
|
12274
|
-
object({
|
|
12275
|
-
peers: array(MeshPeerSchema).readonly()
|
|
12276
|
-
})
|
|
12277
|
-
)
|
|
12278
|
-
}
|
|
12279
|
-
});
|
|
12280
|
-
const MethodAccessSchema = _enum(["view", "create", "delete"]);
|
|
12281
|
-
const AllowedProviderSchema = union([literal("*"), array(string())]);
|
|
12282
|
-
const AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
|
|
12283
|
-
const CapScopeSchema = _enum(["device", "system"]);
|
|
12284
|
-
const TokenScopeSchema = discriminatedUnion("type", [
|
|
12285
|
-
object({
|
|
12286
|
-
type: literal("category"),
|
|
12287
|
-
target: CapScopeSchema,
|
|
12288
|
-
access: array(MethodAccessSchema).min(1)
|
|
12289
|
-
}),
|
|
12290
|
-
object({
|
|
12291
|
-
type: literal("capability"),
|
|
12292
|
-
target: string(),
|
|
12293
|
-
access: array(MethodAccessSchema).min(1)
|
|
12294
|
-
}),
|
|
12295
|
-
object({
|
|
12296
|
-
type: literal("addon"),
|
|
12297
|
-
target: string(),
|
|
12298
|
-
access: array(MethodAccessSchema).min(1)
|
|
12299
|
-
}),
|
|
12300
|
-
object({
|
|
12301
|
-
type: literal("device"),
|
|
12302
|
-
/**
|
|
12303
|
-
* One or more deviceIds (serialised as strings for wire-format
|
|
12304
|
-
* consistency with the rest of the union). Matcher accepts if
|
|
12305
|
-
* `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
|
|
12306
|
-
* of one scope-per-device when granting access to a set of cameras.
|
|
12307
|
-
*/
|
|
12308
|
-
targets: array(string()).min(1),
|
|
12309
|
-
access: array(MethodAccessSchema).min(1)
|
|
12310
|
-
})
|
|
12311
|
-
]);
|
|
12312
|
-
object({
|
|
12313
|
-
id: string(),
|
|
12314
|
-
username: string(),
|
|
12315
|
-
passwordHash: string(),
|
|
12316
|
-
/**
|
|
12317
|
-
* Admin bypass. When true, the middleware skips the scope-access
|
|
12318
|
-
* check entirely. There is no other axis of privilege; the legacy
|
|
12319
|
-
* role enum collapsed onto this boolean in v2.
|
|
12320
|
-
*/
|
|
12321
|
-
isAdmin: boolean().default(false),
|
|
12322
|
-
allowedProviders: AllowedProviderSchema,
|
|
12323
|
-
allowedDevices: AllowedDevicesSchema,
|
|
12324
|
-
/**
|
|
12325
|
-
* Scopes granted to this user. Admins bypass; their `scopes` is
|
|
12326
|
-
* ignored. Non-admins without scopes are locked out of every
|
|
12327
|
-
* protected call.
|
|
12328
|
-
*/
|
|
12329
|
-
scopes: array(TokenScopeSchema).default([]),
|
|
12330
|
-
createdAt: number(),
|
|
12331
|
-
updatedAt: number()
|
|
12332
|
-
});
|
|
12333
|
-
object({
|
|
12334
|
-
id: string(),
|
|
12335
|
-
label: string(),
|
|
12336
|
-
isAdmin: boolean().default(false),
|
|
12337
|
-
allowedProviders: AllowedProviderSchema,
|
|
12338
|
-
allowedDevices: AllowedDevicesSchema,
|
|
12339
|
-
tokenHash: string(),
|
|
12340
|
-
tokenPrefix: string(),
|
|
12341
|
-
createdAt: number(),
|
|
12342
|
-
lastUsedAt: number().optional()
|
|
12343
|
-
});
|
|
12344
|
-
object({
|
|
12345
|
-
id: string(),
|
|
12346
|
-
userId: string(),
|
|
12347
|
-
name: string(),
|
|
12348
|
-
tokenHash: string(),
|
|
12349
|
-
tokenPrefix: string(),
|
|
12350
|
-
scopes: array(TokenScopeSchema),
|
|
12351
|
-
// SQLite/JSON storage round-trips undefined → null. Use `nullish` so the
|
|
12352
|
-
// schema accepts both `null` (read from disk) and `undefined` (in-memory).
|
|
12353
|
-
expiresAt: number().nullish(),
|
|
12354
|
-
lastUsedAt: number().nullish(),
|
|
12355
|
-
createdAt: number()
|
|
12356
|
-
});
|
|
12357
12279
|
const UserSummarySchema = object({
|
|
12358
12280
|
id: string(),
|
|
12359
12281
|
username: string(),
|
|
@@ -12426,6 +12348,16 @@ const CreateScopedTokenResultSchema = object({
|
|
|
12426
12348
|
token: string(),
|
|
12427
12349
|
record: ScopedTokenSummarySchema
|
|
12428
12350
|
});
|
|
12351
|
+
const OauthSessionSummarySchema = object({
|
|
12352
|
+
id: string(),
|
|
12353
|
+
userId: string(),
|
|
12354
|
+
username: string(),
|
|
12355
|
+
integrationId: string(),
|
|
12356
|
+
scopes: array(TokenScopeSchema),
|
|
12357
|
+
createdAt: number(),
|
|
12358
|
+
lastUsedAt: number(),
|
|
12359
|
+
revokedAt: number().nullable()
|
|
12360
|
+
});
|
|
12429
12361
|
const TotpSetupResultSchema = object({
|
|
12430
12362
|
secret: string(),
|
|
12431
12363
|
otpauthUrl: string()
|
|
@@ -12501,6 +12433,66 @@ const TotpStatusSchema = object({
|
|
|
12501
12433
|
object({ userId: string(), code: string() }),
|
|
12502
12434
|
object({ valid: boolean() }),
|
|
12503
12435
|
{ kind: "mutation", access: "view" }
|
|
12436
|
+
),
|
|
12437
|
+
// ── OAuth account-linking grant ────────────────────────────────
|
|
12438
|
+
//
|
|
12439
|
+
// Core's /oauth2/* endpoints delegate here. Tokens are sso-bridge
|
|
12440
|
+
// JWTs (kinds oauth-code / oauth-access / oauth-refresh) and ALWAYS
|
|
12441
|
+
// carry isAdmin:false — the operator login proves hub control; the
|
|
12442
|
+
// issued token is minimal (device scope only).
|
|
12443
|
+
oauthIssueCode: method(
|
|
12444
|
+
object({
|
|
12445
|
+
integrationId: string(),
|
|
12446
|
+
userId: string(),
|
|
12447
|
+
username: string(),
|
|
12448
|
+
scopes: array(TokenScopeSchema),
|
|
12449
|
+
redirectUri: string(),
|
|
12450
|
+
hubUrl: string()
|
|
12451
|
+
}),
|
|
12452
|
+
object({ code: string() }),
|
|
12453
|
+
{ kind: "mutation", access: "create" }
|
|
12454
|
+
),
|
|
12455
|
+
oauthExchangeCode: method(
|
|
12456
|
+
object({ code: string(), redirectUri: string() }),
|
|
12457
|
+
object({
|
|
12458
|
+
accessToken: string(),
|
|
12459
|
+
refreshToken: string(),
|
|
12460
|
+
expiresIn: number()
|
|
12461
|
+
}).nullable(),
|
|
12462
|
+
{ kind: "mutation", access: "view" }
|
|
12463
|
+
),
|
|
12464
|
+
oauthRefresh: method(
|
|
12465
|
+
object({ refreshToken: string() }),
|
|
12466
|
+
object({
|
|
12467
|
+
accessToken: string(),
|
|
12468
|
+
refreshToken: string(),
|
|
12469
|
+
expiresIn: number()
|
|
12470
|
+
}).nullable(),
|
|
12471
|
+
{ kind: "mutation", access: "view" }
|
|
12472
|
+
),
|
|
12473
|
+
oauthVerifyAccessToken: method(
|
|
12474
|
+
object({ token: string() }),
|
|
12475
|
+
object({
|
|
12476
|
+
userId: string(),
|
|
12477
|
+
username: string(),
|
|
12478
|
+
scopes: array(TokenScopeSchema)
|
|
12479
|
+
}).nullable(),
|
|
12480
|
+
{ access: "view" }
|
|
12481
|
+
),
|
|
12482
|
+
// ── OAuth linked-session management (Phase D) ──────────────────
|
|
12483
|
+
//
|
|
12484
|
+
// The admin UI lists active account-linking sessions and revokes
|
|
12485
|
+
// them; revocation makes the linked integration's tokens fail
|
|
12486
|
+
// verification immediately.
|
|
12487
|
+
listOauthSessions: method(
|
|
12488
|
+
_void(),
|
|
12489
|
+
array(OauthSessionSummarySchema),
|
|
12490
|
+
{ auth: "admin" }
|
|
12491
|
+
),
|
|
12492
|
+
revokeOauthSession: method(
|
|
12493
|
+
object({ id: string() }),
|
|
12494
|
+
object({ success: boolean() }),
|
|
12495
|
+
{ kind: "mutation", auth: "admin", access: "delete" }
|
|
12504
12496
|
)
|
|
12505
12497
|
}
|
|
12506
12498
|
});
|
|
@@ -13146,6 +13138,29 @@ const CustomActionInputSchema = object({
|
|
|
13146
13138
|
isActive: boolean()
|
|
13147
13139
|
})).readonly()
|
|
13148
13140
|
),
|
|
13141
|
+
/**
|
|
13142
|
+
* Toggle a single collection-cap provider on/off. Generic write-side
|
|
13143
|
+
* counterpart of `listCapabilityProviders` — drives the per-provider
|
|
13144
|
+
* Enable/Disable affordance in admin pages (TURN servers, etc.)
|
|
13145
|
+
* without needing a bespoke orchestrator cap.
|
|
13146
|
+
*
|
|
13147
|
+
* Reaches the hub's `CapabilityRegistry` directly:
|
|
13148
|
+
* `enableCollectionProvider` / `disableCollectionProvider` flip the
|
|
13149
|
+
* registry-level `disabledProviders` set. `getCollectionEntries`
|
|
13150
|
+
* already filters disabled providers out, so a disabled provider
|
|
13151
|
+
* drops out of every collection aggregate immediately. Only valid
|
|
13152
|
+
* for `mode: 'collection'` caps — the registry no-ops + warns for
|
|
13153
|
+
* singletons.
|
|
13154
|
+
*/
|
|
13155
|
+
setCapabilityProviderEnabled: method(
|
|
13156
|
+
object({
|
|
13157
|
+
capName: string().min(1),
|
|
13158
|
+
addonId: string().min(1),
|
|
13159
|
+
enabled: boolean()
|
|
13160
|
+
}),
|
|
13161
|
+
object({ success: literal(true) }),
|
|
13162
|
+
{ kind: "mutation", auth: "admin" }
|
|
13163
|
+
),
|
|
13149
13164
|
/**
|
|
13150
13165
|
* Live-update one of the framework packages marked
|
|
13151
13166
|
* `camstack.system: true` (`@camstack/types|kernel|core|sdk|ui-library`).
|
|
@@ -14980,6 +14995,24 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
14980
14995
|
allowedSizes: ["lg", "xl"],
|
|
14981
14996
|
defaultColumns: 12,
|
|
14982
14997
|
defaultRows: 4
|
|
14998
|
+
},
|
|
14999
|
+
{
|
|
15000
|
+
// On-camera motion-detection grid editor. The `motion-zones`
|
|
15001
|
+
// cap is owned by the camera provider addons; the widget only
|
|
15002
|
+
// talks to it over tRPC, so hosting the React surface here
|
|
15003
|
+
// (one bundle) avoids duplicating it per provider.
|
|
15004
|
+
stableId: "motion-zones-editor",
|
|
15005
|
+
label: "Motion Zones",
|
|
15006
|
+
description: "On-camera motion-detection grid editor (live-frame overlay).",
|
|
15007
|
+
icon: "grid-3x3",
|
|
15008
|
+
remoteName: "addon_pipeline_orchestrator_widgets",
|
|
15009
|
+
bundle: "remoteEntry.js",
|
|
15010
|
+
hosts: ["device-tab"],
|
|
15011
|
+
requires: { deviceContext: true, integrationContext: false },
|
|
15012
|
+
defaultSize: "lg",
|
|
15013
|
+
allowedSizes: ["md", "lg"],
|
|
15014
|
+
defaultColumns: 12,
|
|
15015
|
+
defaultRows: 1
|
|
14983
15016
|
}
|
|
14984
15017
|
]
|
|
14985
15018
|
};
|
|
@@ -17151,8 +17184,28 @@ class PipelineOrchestratorAddon extends BaseAddon {
|
|
|
17151
17184
|
}
|
|
17152
17185
|
]
|
|
17153
17186
|
};
|
|
17187
|
+
const motionZonesSection = {
|
|
17188
|
+
id: "motion-zones",
|
|
17189
|
+
title: "Motion Zones",
|
|
17190
|
+
tab: "motion",
|
|
17191
|
+
location: "settings",
|
|
17192
|
+
columns: 1,
|
|
17193
|
+
order: 0,
|
|
17194
|
+
fields: [
|
|
17195
|
+
{
|
|
17196
|
+
// Widget fields manage their own state via DeviceProxy —
|
|
17197
|
+
// `ConfigWidgetField` carries no `value` (unlike the legacy
|
|
17198
|
+
// `zone-editor` field type which still does).
|
|
17199
|
+
type: "widget",
|
|
17200
|
+
key: "motion-zones",
|
|
17201
|
+
label: "Motion Zones",
|
|
17202
|
+
widgetId: "pipeline-orchestrator/motion-zones-editor",
|
|
17203
|
+
span: 1
|
|
17204
|
+
}
|
|
17205
|
+
]
|
|
17206
|
+
};
|
|
17154
17207
|
return {
|
|
17155
|
-
sections: [...baseSections, zonesSection]
|
|
17208
|
+
sections: [...baseSections, zonesSection, motionZonesSection]
|
|
17156
17209
|
};
|
|
17157
17210
|
}
|
|
17158
17211
|
// `getCameraPipelineWithFallback` was a thin wrapper over
|