@camstack/addon-post-analysis 1.1.12 → 1.1.13

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.
@@ -7049,7 +7049,21 @@ var StorageLocationDeclarationSchema = object({
7049
7049
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
7050
7050
  * configure the primary location.
7051
7051
  */
7052
- defaultsTo: string().optional()
7052
+ defaultsTo: string().optional(),
7053
+ /**
7054
+ * Which node root the seeded `<id>:default` instance is placed under on a
7055
+ * FRESH install:
7056
+ * - `'data'` (default) — the node's data dir (`CAMSTACK_DATA` / boot dir),
7057
+ * the appData volume. Right for small/durable data (backups, logs, models).
7058
+ * - `'media'` — the dedicated media volume (`CAMSTACK_MEDIA_ROOT`) when that
7059
+ * env is set, else falls back to the data root. Right for bulky, hot media
7060
+ * (recordings, event media) that should stay off the appData disk.
7061
+ *
7062
+ * Only affects the seeded default's `basePath`; operators can repoint any
7063
+ * location afterwards, and a `defaultsTo` slot inherits its parent's root
7064
+ * regardless of this field. Absent (the common case) is treated as `'data'`.
7065
+ */
7066
+ defaultRoot: _enum(["data", "media"]).optional()
7053
7067
  });
7054
7068
  var DecoderStatsSchema = object({
7055
7069
  inputFps: number(),
@@ -7942,6 +7956,10 @@ var RtspRestreamEntrySchema = object({
7942
7956
  var BrokerRtspClientSchema = object({
7943
7957
  sessionId: string(),
7944
7958
  remoteAddr: string(),
7959
+ /** Client `User-Agent` (e.g. `recorder` for the recorder's RTSP pull), or
7960
+ * null/absent when the client sent none. Lets the UI label a consumer by
7961
+ * purpose. Optional so a client built against an older schema stays valid. */
7962
+ userAgent: string().nullish(),
7945
7963
  playing: boolean(),
7946
7964
  muted: boolean(),
7947
7965
  connectedAt: number(),
@@ -7071,7 +7071,21 @@ var StorageLocationDeclarationSchema = object({
7071
7071
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
7072
7072
  * configure the primary location.
7073
7073
  */
7074
- defaultsTo: string().optional()
7074
+ defaultsTo: string().optional(),
7075
+ /**
7076
+ * Which node root the seeded `<id>:default` instance is placed under on a
7077
+ * FRESH install:
7078
+ * - `'data'` (default) — the node's data dir (`CAMSTACK_DATA` / boot dir),
7079
+ * the appData volume. Right for small/durable data (backups, logs, models).
7080
+ * - `'media'` — the dedicated media volume (`CAMSTACK_MEDIA_ROOT`) when that
7081
+ * env is set, else falls back to the data root. Right for bulky, hot media
7082
+ * (recordings, event media) that should stay off the appData disk.
7083
+ *
7084
+ * Only affects the seeded default's `basePath`; operators can repoint any
7085
+ * location afterwards, and a `defaultsTo` slot inherits its parent's root
7086
+ * regardless of this field. Absent (the common case) is treated as `'data'`.
7087
+ */
7088
+ defaultRoot: _enum(["data", "media"]).optional()
7075
7089
  });
7076
7090
  var DecoderStatsSchema = object({
7077
7091
  inputFps: number(),
@@ -7964,6 +7978,10 @@ var RtspRestreamEntrySchema = object({
7964
7978
  var BrokerRtspClientSchema = object({
7965
7979
  sessionId: string(),
7966
7980
  remoteAddr: string(),
7981
+ /** Client `User-Agent` (e.g. `recorder` for the recorder's RTSP pull), or
7982
+ * null/absent when the client sent none. Lets the UI label a consumer by
7983
+ * purpose. Optional so a client built against an older schema stays valid. */
7984
+ userAgent: string().nullish(),
7967
7985
  playing: boolean(),
7968
7986
  muted: boolean(),
7969
7987
  connectedAt: number(),
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-d6siFYqC.js");
5
+ const require_dist = require("../dist-DlqrO_A-.js");
6
6
  let node_fs = require("node:fs");
7
7
  let node_fs$1 = require_dist.__toESM(node_fs, 1);
8
8
  node_fs = require_dist.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { f as BaseAddon, i as embeddingEncoderCapability, o as hfModelUrl } from "../dist-CdflY87D.mjs";
1
+ import { f as BaseAddon, i as embeddingEncoderCapability, o as hfModelUrl } from "../dist-COXXWoXN.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-d6siFYqC.js");
6
- const require_resolve_frame = require("../resolve-frame-BACRPcr1.js");
5
+ const require_dist = require("../dist-DlqrO_A-.js");
6
+ const require_resolve_frame = require("../resolve-frame-DCyc2Qwb.js");
7
7
  let _camstack_shm_ring = require("@camstack/shm-ring");
8
8
  //#region src/enrichment-engine/types.ts
9
9
  var DEFAULT_ENRICHMENT_CONFIG = {
@@ -1,4 +1,4 @@
1
- import { C as string, S as object, b as boolean, f as BaseAddon, g as createEvent, h as asJsonObject, m as EventCategory, v as _enum, w as tuple, x as number, y as array } from "../dist-CdflY87D.mjs";
1
+ import { C as string, S as object, b as boolean, f as BaseAddon, g as createEvent, h as asJsonObject, m as EventCategory, v as _enum, w as tuple, x as number, y as array } from "../dist-COXXWoXN.mjs";
2
2
  import { n as extractCrop, t as resolveFrame } from "../resolve-frame-CT1T1tWy.mjs";
3
3
  import { FrameRingReaderCache } from "@camstack/shm-ring";
4
4
  //#region src/enrichment-engine/types.ts
@@ -0,0 +1,145 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ var STORAGE_LOCATION_TYPES = [
4
+ "data",
5
+ "media",
6
+ "recordings",
7
+ "recordings-high",
8
+ "recordings-low",
9
+ "recordings-clips",
10
+ "event-images",
11
+ "models",
12
+ "addons-data",
13
+ "cache",
14
+ "logs",
15
+ "backups"
16
+ ];
17
+ var DEFAULT_LOCATION_SUBDIRS = {
18
+ data: "db",
19
+ media: "media",
20
+ recordings: "recordings",
21
+ "recordings-high": "recordings-high",
22
+ "recordings-low": "recordings-low",
23
+ "recordings-clips": "recordings-clips",
24
+ "event-images": "event-images",
25
+ models: "models",
26
+ "addons-data": "addons-data",
27
+ cache: "/tmp/camstack-cache",
28
+ logs: "logs",
29
+ backups: "backups"
30
+ };
31
+ /**
32
+ * Filesystem storage provider — serves all location types from a local directory tree.
33
+ *
34
+ * Default layout:
35
+ * {rootPath}/recordings-high/
36
+ * {rootPath}/recordings-low/
37
+ * {rootPath}/recordings-clips/
38
+ * {rootPath}/event-images/
39
+ * {rootPath}/models/
40
+ * {rootPath}/addons-data/
41
+ * {rootPath}/logs/
42
+ * /tmp/camstack-cache/ (cache is always local)
43
+ *
44
+ * Individual location paths can be overridden.
45
+ */
46
+ var FilesystemStorageProvider = class {
47
+ id = "local";
48
+ name = "Local Filesystem";
49
+ supportedLocations = [...STORAGE_LOCATION_TYPES];
50
+ rootPath;
51
+ locationPaths;
52
+ constructor(rootPath, overrides) {
53
+ this.rootPath = path.resolve(rootPath);
54
+ this.locationPaths = /* @__PURE__ */ new Map();
55
+ for (const loc of STORAGE_LOCATION_TYPES) {
56
+ const override = overrides?.[loc];
57
+ if (override) this.locationPaths.set(loc, path.resolve(override));
58
+ else {
59
+ const subdir = DEFAULT_LOCATION_SUBDIRS[loc] ?? loc;
60
+ this.locationPaths.set(loc, path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir));
61
+ }
62
+ }
63
+ }
64
+ async resolve({ location, relativePath }) {
65
+ const base = this.locationPaths.get(location) ?? path.join(this.rootPath, location);
66
+ return path.join(base, relativePath);
67
+ }
68
+ async write({ location, relativePath, data }) {
69
+ const filePath = await this.resolve({
70
+ location,
71
+ relativePath
72
+ });
73
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
74
+ if (Buffer.isBuffer(data)) await fs.promises.writeFile(filePath, data);
75
+ else {
76
+ const writeStream = fs.createWriteStream(filePath);
77
+ await new Promise((resolve, reject) => {
78
+ data.pipe(writeStream);
79
+ writeStream.on("finish", resolve);
80
+ writeStream.on("error", reject);
81
+ });
82
+ }
83
+ }
84
+ async read({ location, relativePath }) {
85
+ return fs.promises.readFile(await this.resolve({
86
+ location,
87
+ relativePath
88
+ }));
89
+ }
90
+ async exists({ location, relativePath }) {
91
+ try {
92
+ await fs.promises.access(await this.resolve({
93
+ location,
94
+ relativePath
95
+ }));
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ async list({ location, prefix }) {
102
+ const base = this.locationPaths.get(location);
103
+ if (!base) return [];
104
+ const dir = prefix ? path.join(base, prefix) : base;
105
+ try {
106
+ return (await fs.promises.readdir(dir, { withFileTypes: true })).map((e) => prefix ? `${prefix}/${e.name}` : e.name);
107
+ } catch {
108
+ return [];
109
+ }
110
+ }
111
+ async delete({ location, relativePath }) {
112
+ const filePath = await this.resolve({
113
+ location,
114
+ relativePath
115
+ });
116
+ await fs.promises.rm(filePath, { force: true });
117
+ }
118
+ async getAvailableSpace({ location }) {
119
+ const base = this.locationPaths.get(location);
120
+ if (!base) return null;
121
+ try {
122
+ let target = base;
123
+ while (!fs.existsSync(target)) {
124
+ const parent = path.dirname(target);
125
+ if (!parent || parent === target) return null;
126
+ target = parent;
127
+ }
128
+ const stats = await fs.promises.statfs(target);
129
+ return stats.bavail * stats.bsize;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ /** Get the resolved path for a location type (addon-declared ids fall back
135
+ * to `<rootPath>/<id>`). */
136
+ getLocationPath(location) {
137
+ return this.locationPaths.get(location) ?? path.join(this.rootPath, location);
138
+ }
139
+ /** Get the root path */
140
+ getRootPath() {
141
+ return this.rootPath;
142
+ }
143
+ };
144
+ //#endregion
145
+ export { FilesystemStorageProvider };
@@ -0,0 +1,150 @@
1
+ const require_dist = require("./dist-DlqrO_A-.js");
2
+ let node_fs = require("node:fs");
3
+ node_fs = require_dist.__toESM(node_fs, 1);
4
+ let node_path = require("node:path");
5
+ node_path = require_dist.__toESM(node_path, 1);
6
+ require("node:crypto");
7
+ require("node:child_process");
8
+ var STORAGE_LOCATION_TYPES = [
9
+ "data",
10
+ "media",
11
+ "recordings",
12
+ "recordings-high",
13
+ "recordings-low",
14
+ "recordings-clips",
15
+ "event-images",
16
+ "models",
17
+ "addons-data",
18
+ "cache",
19
+ "logs",
20
+ "backups"
21
+ ];
22
+ var DEFAULT_LOCATION_SUBDIRS = {
23
+ data: "db",
24
+ media: "media",
25
+ recordings: "recordings",
26
+ "recordings-high": "recordings-high",
27
+ "recordings-low": "recordings-low",
28
+ "recordings-clips": "recordings-clips",
29
+ "event-images": "event-images",
30
+ models: "models",
31
+ "addons-data": "addons-data",
32
+ cache: "/tmp/camstack-cache",
33
+ logs: "logs",
34
+ backups: "backups"
35
+ };
36
+ /**
37
+ * Filesystem storage provider — serves all location types from a local directory tree.
38
+ *
39
+ * Default layout:
40
+ * {rootPath}/recordings-high/
41
+ * {rootPath}/recordings-low/
42
+ * {rootPath}/recordings-clips/
43
+ * {rootPath}/event-images/
44
+ * {rootPath}/models/
45
+ * {rootPath}/addons-data/
46
+ * {rootPath}/logs/
47
+ * /tmp/camstack-cache/ (cache is always local)
48
+ *
49
+ * Individual location paths can be overridden.
50
+ */
51
+ var FilesystemStorageProvider = class {
52
+ id = "local";
53
+ name = "Local Filesystem";
54
+ supportedLocations = [...STORAGE_LOCATION_TYPES];
55
+ rootPath;
56
+ locationPaths;
57
+ constructor(rootPath, overrides) {
58
+ this.rootPath = node_path.resolve(rootPath);
59
+ this.locationPaths = /* @__PURE__ */ new Map();
60
+ for (const loc of STORAGE_LOCATION_TYPES) {
61
+ const override = overrides?.[loc];
62
+ if (override) this.locationPaths.set(loc, node_path.resolve(override));
63
+ else {
64
+ const subdir = DEFAULT_LOCATION_SUBDIRS[loc] ?? loc;
65
+ this.locationPaths.set(loc, node_path.isAbsolute(subdir) ? subdir : node_path.join(this.rootPath, subdir));
66
+ }
67
+ }
68
+ }
69
+ async resolve({ location, relativePath }) {
70
+ const base = this.locationPaths.get(location) ?? node_path.join(this.rootPath, location);
71
+ return node_path.join(base, relativePath);
72
+ }
73
+ async write({ location, relativePath, data }) {
74
+ const filePath = await this.resolve({
75
+ location,
76
+ relativePath
77
+ });
78
+ await node_fs.promises.mkdir(node_path.dirname(filePath), { recursive: true });
79
+ if (Buffer.isBuffer(data)) await node_fs.promises.writeFile(filePath, data);
80
+ else {
81
+ const writeStream = node_fs.createWriteStream(filePath);
82
+ await new Promise((resolve, reject) => {
83
+ data.pipe(writeStream);
84
+ writeStream.on("finish", resolve);
85
+ writeStream.on("error", reject);
86
+ });
87
+ }
88
+ }
89
+ async read({ location, relativePath }) {
90
+ return node_fs.promises.readFile(await this.resolve({
91
+ location,
92
+ relativePath
93
+ }));
94
+ }
95
+ async exists({ location, relativePath }) {
96
+ try {
97
+ await node_fs.promises.access(await this.resolve({
98
+ location,
99
+ relativePath
100
+ }));
101
+ return true;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+ async list({ location, prefix }) {
107
+ const base = this.locationPaths.get(location);
108
+ if (!base) return [];
109
+ const dir = prefix ? node_path.join(base, prefix) : base;
110
+ try {
111
+ return (await node_fs.promises.readdir(dir, { withFileTypes: true })).map((e) => prefix ? `${prefix}/${e.name}` : e.name);
112
+ } catch {
113
+ return [];
114
+ }
115
+ }
116
+ async delete({ location, relativePath }) {
117
+ const filePath = await this.resolve({
118
+ location,
119
+ relativePath
120
+ });
121
+ await node_fs.promises.rm(filePath, { force: true });
122
+ }
123
+ async getAvailableSpace({ location }) {
124
+ const base = this.locationPaths.get(location);
125
+ if (!base) return null;
126
+ try {
127
+ let target = base;
128
+ while (!node_fs.existsSync(target)) {
129
+ const parent = node_path.dirname(target);
130
+ if (!parent || parent === target) return null;
131
+ target = parent;
132
+ }
133
+ const stats = await node_fs.promises.statfs(target);
134
+ return stats.bavail * stats.bsize;
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+ /** Get the resolved path for a location type (addon-declared ids fall back
140
+ * to `<rootPath>/<id>`). */
141
+ getLocationPath(location) {
142
+ return this.locationPaths.get(location) ?? node_path.join(this.rootPath, location);
143
+ }
144
+ /** Get the root path */
145
+ getRootPath() {
146
+ return this.rootPath;
147
+ }
148
+ };
149
+ //#endregion
150
+ exports.FilesystemStorageProvider = FilesystemStorageProvider;
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.1.12",
6
+ version: "1.1.13",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_pipeline_analytics_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.1.13",
21
+ version: "1.1.16",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.1.13",
36
+ version: "1.1.15",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.1.13",
39
+ version: "1.1.16",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.1.12",
48
+ version: "1.1.13",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.1.13",
84
+ version: "1.1.15",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-d6siFYqC.js");
6
- const require_resolve_frame = require("../resolve-frame-BACRPcr1.js");
5
+ const require_dist = require("../dist-DlqrO_A-.js");
6
+ const require_resolve_frame = require("../resolve-frame-DCyc2Qwb.js");
7
7
  let _camstack_shm_ring = require("@camstack/shm-ring");
8
8
  let sharp = require("sharp");
9
9
  sharp = require_dist.__toESM(sharp);
@@ -5647,7 +5647,13 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
5647
5647
  await PlateStore.declare(api.settingsStore);
5648
5648
  await ObjectEmbeddingStore.declare(api.settingsStore);
5649
5649
  const logger = this.ctx.logger;
5650
- const storage = this.ctx.kernel.storage;
5650
+ let storage = this.ctx.kernel.storage;
5651
+ const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
5652
+ if (mediaRoot) {
5653
+ const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-CGM1poCk.js"));
5654
+ storage = new FilesystemStorageProvider(mediaRoot);
5655
+ logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
5656
+ }
5651
5657
  if (!storage) throw new Error("pipeline-analytics requires ctx.kernel.storage");
5652
5658
  this.trackStore = new TrackStore({
5653
5659
  store: api.settingsStore,
@@ -1,4 +1,4 @@
1
- import { S as object, _ as hydrateSchema, a as faceGalleryCapability, b as boolean, c as plateGalleryCapability, d as errMsg, f as BaseAddon, l as videoclipsCapability, m as EventCategory, n as audioMetricsCapability, p as DeviceType, r as cosineSimilarity, s as pipelineAnalyticsCapability, t as addonWidgetsSourceCapability, u as zoneAnalyticsCapability, x as number } from "../dist-CdflY87D.mjs";
1
+ import { S as object, _ as hydrateSchema, a as faceGalleryCapability, b as boolean, c as plateGalleryCapability, d as errMsg, f as BaseAddon, l as videoclipsCapability, m as EventCategory, n as audioMetricsCapability, p as DeviceType, r as cosineSimilarity, s as pipelineAnalyticsCapability, t as addonWidgetsSourceCapability, u as zoneAnalyticsCapability, x as number } from "../dist-COXXWoXN.mjs";
2
2
  import { n as extractCrop, t as resolveFrame } from "../resolve-frame-CT1T1tWy.mjs";
3
3
  import { FrameRingReaderCache } from "@camstack/shm-ring";
4
4
  import sharp from "sharp";
@@ -5642,7 +5642,13 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
5642
5642
  await PlateStore.declare(api.settingsStore);
5643
5643
  await ObjectEmbeddingStore.declare(api.settingsStore);
5644
5644
  const logger = this.ctx.logger;
5645
- const storage = this.ctx.kernel.storage;
5645
+ let storage = this.ctx.kernel.storage;
5646
+ const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
5647
+ if (mediaRoot) {
5648
+ const { FilesystemStorageProvider } = await import("../node-BuPMb-ti.mjs");
5649
+ storage = new FilesystemStorageProvider(mediaRoot);
5650
+ logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
5651
+ }
5646
5652
  if (!storage) throw new Error("pipeline-analytics requires ctx.kernel.storage");
5647
5653
  this.trackStore = new TrackStore({
5648
5654
  store: api.settingsStore,
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BIz6KjXS.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B483pGUH.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-d6siFYqC.js");
1
+ const require_dist = require("./dist-DlqrO_A-.js");
2
2
  let sharp = require("sharp");
3
3
  sharp = require_dist.__toESM(sharp);
4
4
  //#region src/shared/frame/crop-extractor.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.1.12",
3
+ "version": "1.1.13",
4
4
  "description": "CamStack Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",
@@ -109,7 +109,8 @@
109
109
  "id": "eventMedia",
110
110
  "displayName": "Event Media",
111
111
  "cardinality": "single",
112
- "description": "Detection snapshots, thumbnails and crops."
112
+ "description": "Detection snapshots, thumbnails and crops.",
113
+ "defaultRoot": "media"
113
114
  }
114
115
  ],
115
116
  "icon": "assets/icon.svg",