rhombus-node-mcp 0.1.21 → 0.1.23
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 +8 -0
- package/dist/api/access-control-tool-api.js +256 -0
- package/dist/api/alarm-monitoring-tool-api.js +65 -0
- package/dist/api/camera-tool-api.js +35 -15
- package/dist/api/camera-uptime-tool-api.js +112 -0
- package/dist/api/clips-tool-api.js +110 -21
- package/dist/api/create-tool-api.js +143 -16
- package/dist/api/door-tool-api.js +66 -0
- package/dist/api/events-tool-api.js +151 -28
- package/dist/api/faces-tool-api.js +118 -91
- package/dist/api/get-entity-tool-api.js +2 -0
- package/dist/api/guest-management-tool-api.js +75 -0
- package/dist/api/location-tool-api.js +48 -1
- package/dist/api/lpr-tool-api.js +26 -0
- package/dist/api/policy-alerts-tool-api.js +54 -0
- package/dist/api/report-tool-api.js +345 -23
- package/dist/api/rules-tool-api.js +78 -0
- package/dist/api/search-tool-api.js +93 -0
- package/dist/api/time-tool-api.js +3 -1
- package/dist/api/update-tool-api.js +204 -0
- package/dist/api/user-access-trail-tool-api.js +45 -0
- package/dist/api/user-audit-tool-api.js +47 -0
- package/dist/api/user-tool-api.js +77 -0
- package/dist/createServer.js +3 -1
- package/dist/filtering-utils.js +298 -0
- package/dist/index.js +0 -9
- package/dist/logger.js +6 -5
- package/dist/network.js +18 -4
- package/dist/tools/access-control-tool.js +97 -0
- package/dist/tools/alarm-monitoring-tool.js +50 -0
- package/dist/tools/analytics-tool.js +383 -0
- package/dist/tools/camera-tool.js +27 -9
- package/dist/tools/camera-uptime-tool.js +50 -0
- package/dist/tools/clips-tool.js +46 -16
- package/dist/tools/door-tool.js +66 -0
- package/dist/tools/events-tool.js +103 -81
- package/dist/tools/faces-tool.js +175 -32
- package/dist/tools/get-entity-tool.js +6 -1
- package/dist/tools/guest-management-tool.js +50 -0
- package/dist/tools/location-tool.js +23 -4
- package/dist/tools/lpr-tool.js +26 -20
- package/dist/tools/policy-alerts-tool.js +34 -4
- package/dist/tools/report-tool.js +183 -46
- package/dist/tools/rules-tool.js +76 -0
- package/dist/tools/search-tool.js +69 -0
- package/dist/tools/update-tool.js +63 -2
- package/dist/tools/user-access-trail-tool.js +64 -0
- package/dist/tools/user-audit-tool.js +53 -0
- package/dist/tools/user-tool.js +55 -0
- package/dist/tools/video-walls-tool.js +41 -0
- package/dist/transports/streamable-http.js +25 -15
- package/dist/types/access-control-tool-types.js +120 -0
- package/dist/types/alarm-monitoring-tool-types.js +60 -0
- package/dist/types/analytics-tool-types.js +190 -0
- package/dist/types/camera-tool-types.js +3 -2
- package/dist/types/camera-uptime-tool-types.js +52 -0
- package/dist/types/clips-tool-types.js +60 -11
- package/dist/types/door-tool-types.js +64 -0
- package/dist/types/events-tools-types.js +71 -3
- package/dist/types/faces-tools-types.js +36 -0
- package/dist/types/guest-management-tool-types.js +57 -0
- package/dist/types/location-tool-types.js +10 -1
- package/dist/types/lpr-tool-types.js +14 -0
- package/dist/types/policy-alerts-tool-types.js +38 -3
- package/dist/types/report-tool-types.js +284 -42
- package/dist/types/rules-tool-types.js +58 -0
- package/dist/types/schema.js +3123 -1
- package/dist/types/search-tool-types.js +82 -0
- package/dist/types/update-tool-types.js +4 -1
- package/dist/types/user-access-trail-tool-types.js +54 -0
- package/dist/types/user-audit-tool-types.js +48 -0
- package/dist/types/user-tool-types.js +61 -0
- package/dist/types/video-walls-tool-types.js +59 -0
- package/dist/types/zod-schemas.js +3237 -986
- package/dist/types.js +0 -21
- package/dist/util.js +34 -57
- package/package.json +7 -5
- package/dist/disabled-tools/semantic-search-tool.js +0 -90
- package/dist/tools/create-tool.js +0 -30
- package/dist/types/create-tool-types.js +0 -8
- package/dist/types/schema-components.js +0 -7211
- package/dist/types/semantic-search-tool-types.js +0 -5
|
@@ -1,6 +1,123 @@
|
|
|
1
1
|
import { logger } from "../logger.js";
|
|
2
2
|
import { postApi } from "../network.js";
|
|
3
3
|
export async function createVideoWall(options, requestModifiers, sessionId) {
|
|
4
|
+
// our grid layout settings is a little complicated. we'll dumb it down
|
|
5
|
+
// to certain presets based on length of device list
|
|
6
|
+
const numDevices = options?.settings.numVisibleDevicesAtOnce ??
|
|
7
|
+
options?.deviceList?.length ??
|
|
8
|
+
1;
|
|
9
|
+
let gridSettings;
|
|
10
|
+
if (numDevices <= 1) {
|
|
11
|
+
gridSettings = {
|
|
12
|
+
gridLayout: "1",
|
|
13
|
+
gridSize: {
|
|
14
|
+
width: 1,
|
|
15
|
+
height: 1,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
else if (numDevices <= 2) {
|
|
20
|
+
gridSettings = {
|
|
21
|
+
gridLayout: "1\n2",
|
|
22
|
+
gridSize: {
|
|
23
|
+
width: 1,
|
|
24
|
+
height: 2,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
else if (numDevices <= 3) {
|
|
29
|
+
gridSettings = {
|
|
30
|
+
gridLayout: "1 2\n1 3",
|
|
31
|
+
gridSize: {
|
|
32
|
+
width: 2,
|
|
33
|
+
height: 2,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else if (numDevices <= 4) {
|
|
38
|
+
gridSettings = {
|
|
39
|
+
gridLayout: "1 2\n3 4",
|
|
40
|
+
gridSize: {
|
|
41
|
+
width: 4,
|
|
42
|
+
height: 4,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
else if (numDevices <= 5) {
|
|
47
|
+
gridSettings = {
|
|
48
|
+
gridLayout: "1 1 1 2\n 1 1 1 3\n 1 1 1 4\n 1 1 1 5",
|
|
49
|
+
gridSize: {
|
|
50
|
+
width: 4,
|
|
51
|
+
height: 4,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
else if (numDevices <= 6) {
|
|
56
|
+
gridSettings = {
|
|
57
|
+
gridLayout: "1 2 3\n4 5 6",
|
|
58
|
+
gridSize: {
|
|
59
|
+
width: 3,
|
|
60
|
+
height: 2,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
else if (numDevices <= 7) {
|
|
65
|
+
gridSettings = {
|
|
66
|
+
gridLayout: "1 1 2\n 1 1 3\n 4 5 6",
|
|
67
|
+
gridSize: {
|
|
68
|
+
width: 3,
|
|
69
|
+
height: 3,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else if (numDevices <= 9) {
|
|
74
|
+
gridSettings = {
|
|
75
|
+
gridLayout: "1 2 3\n4 5 6\n7 8 9",
|
|
76
|
+
gridSize: { width: 3, height: 3 },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
else if (numDevices <= 12) {
|
|
80
|
+
gridSettings = {
|
|
81
|
+
gridLayout: "1 2 3 4\n5 6 7 8\n9 10 11 12",
|
|
82
|
+
gridSize: { width: 4, height: 3 },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
else if (numDevices <= 16) {
|
|
86
|
+
gridSettings = {
|
|
87
|
+
gridLayout: "1 2 3 4\n5 6 7 8\n9 10 11 12\n13 14 15 16",
|
|
88
|
+
gridSize: { width: 4, height: 4 },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
else if (numDevices <= 25) {
|
|
92
|
+
gridSettings = {
|
|
93
|
+
gridLayout: "1 2 3 4 5\n6 7 8 9 10\n11 12 13 14 15\n16 17 18 19 20\n21 22 23 24 25",
|
|
94
|
+
gridSize: { width: 5, height: 5 },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else if (numDevices <= 30) {
|
|
98
|
+
gridSettings = {
|
|
99
|
+
gridLayout: "1 2 3 4 5 6\n7 8 9 10 11 12\n13 14 15 16 17 18\n19 20 21 22 23 24\n25 26 27 28 29 30",
|
|
100
|
+
gridSize: { width: 6, height: 5 },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
else if (numDevices <= 36) {
|
|
104
|
+
gridSettings = {
|
|
105
|
+
gridLayout: "1 2 3 4 5 6\n7 8 9 10 11 12\n13 14 15 16 17 18\n19 20 21 22 23 24\n25 26 27 28 29 30\n31 32 33 34 35 36",
|
|
106
|
+
gridSize: { width: 6, height: 6 },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
else if (numDevices <= 49) {
|
|
110
|
+
gridSettings = {
|
|
111
|
+
gridLayout: "1 2 3 4 5 6 7\n8 9 10 11 12 13 14\n15 16 17 18 19 20 21\n22 23 24 25 26 27 28\n29 30 31 32 33 34 35\n36 37 38 39 40 41 42\n43 44 45 46 47 48 49",
|
|
112
|
+
gridSize: { width: 7, height: 7 },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
gridSettings = {
|
|
117
|
+
gridLayout: "1 2 3 4 5 6 7 8\n9 10 11 12 13 14 15 16\n17 18 19 20 21 22 23 24\n25 26 27 28 29 30 31 32\n33 34 35 36 37 38 39 40\n41 42 43 44 45 46 47 48\n49 50 51 52 53 54 55 56\n57 58 59 60 61 62 63 64",
|
|
118
|
+
gridSize: { width: 8, height: 8 },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
4
121
|
const body = {
|
|
5
122
|
videoWall: {
|
|
6
123
|
displayName: options?.displayName,
|
|
@@ -9,8 +126,7 @@ export async function createVideoWall(options, requestModifiers, sessionId) {
|
|
|
9
126
|
orgUuid: options?.orgUuid,
|
|
10
127
|
shared: true,
|
|
11
128
|
settings: {
|
|
12
|
-
|
|
13
|
-
gridLayout: "1 2\n3 4",
|
|
129
|
+
...gridSettings,
|
|
14
130
|
intervalSeconds: options?.settings.intervalSeconds || 5,
|
|
15
131
|
},
|
|
16
132
|
},
|
|
@@ -23,31 +139,42 @@ export async function createVideoWall(options, requestModifiers, sessionId) {
|
|
|
23
139
|
});
|
|
24
140
|
return response;
|
|
25
141
|
}
|
|
142
|
+
export async function getVideoWalls(requestModifiers, sessionId) {
|
|
143
|
+
const response = await postApi({
|
|
144
|
+
route: "/camera/getVideoWalls",
|
|
145
|
+
body: {},
|
|
146
|
+
modifiers: requestModifiers,
|
|
147
|
+
sessionId,
|
|
148
|
+
});
|
|
149
|
+
if (response?.error) {
|
|
150
|
+
return {
|
|
151
|
+
error: response.errorMsg ?? "No error message.",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
videoWalls: response?.videoWalls ?? [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
26
158
|
export async function handleCreateVideoWallRequest(videoWallCreateOptions, requestModifiers, sessionId) {
|
|
27
|
-
let text = "Unable to create video wall!";
|
|
28
159
|
logger.info("🔨 Creating video wall");
|
|
29
160
|
if (!videoWallCreateOptions?.displayName) {
|
|
30
|
-
|
|
161
|
+
return {
|
|
31
162
|
needUserInput: true,
|
|
32
163
|
commandForUser: "What should the name of the video wall be?",
|
|
33
|
-
}
|
|
164
|
+
};
|
|
34
165
|
}
|
|
35
166
|
else if ((videoWallCreateOptions?.deviceList || []).length === 0) {
|
|
36
|
-
|
|
167
|
+
return {
|
|
37
168
|
needUserInput: true,
|
|
38
169
|
commandForUser: "Which cameras would you like on this video wall?",
|
|
39
|
-
}
|
|
170
|
+
};
|
|
40
171
|
}
|
|
41
172
|
else {
|
|
42
173
|
logger.info("Creating video wall with options: ", JSON.stringify(videoWallCreateOptions));
|
|
43
|
-
|
|
174
|
+
const response = await createVideoWall(videoWallCreateOptions, requestModifiers, sessionId);
|
|
175
|
+
return {
|
|
176
|
+
uuid: response?.uuid ?? undefined,
|
|
177
|
+
error: response?.errorMsg ?? undefined,
|
|
178
|
+
};
|
|
44
179
|
}
|
|
45
|
-
return Promise.resolve({
|
|
46
|
-
content: [
|
|
47
|
-
{
|
|
48
|
-
type: "text",
|
|
49
|
-
text,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
});
|
|
53
180
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { postApi } from "../network.js";
|
|
2
|
+
export async function getDoorControllerRules(doorControllerUuid, requestModifiers, sessionId) {
|
|
3
|
+
const res = await postApi({
|
|
4
|
+
route: "/doorcontroller/getDoorControllerRules",
|
|
5
|
+
body: { doorControllerUuid },
|
|
6
|
+
modifiers: requestModifiers,
|
|
7
|
+
sessionId,
|
|
8
|
+
});
|
|
9
|
+
if (res.error) {
|
|
10
|
+
throw new Error(JSON.stringify(res));
|
|
11
|
+
}
|
|
12
|
+
return (res.rules?.map((rule) => ({
|
|
13
|
+
uuid: rule.uuid ?? undefined,
|
|
14
|
+
name: rule.name ?? undefined,
|
|
15
|
+
deviceUuid: rule.deviceUuid ?? undefined,
|
|
16
|
+
enabled: rule.enabled ?? undefined,
|
|
17
|
+
ruleType: rule.ruleType ?? undefined,
|
|
18
|
+
})) ?? []);
|
|
19
|
+
}
|
|
20
|
+
export async function createDoorControllerRule(ruleConfig, requestModifiers, sessionId) {
|
|
21
|
+
const res = await postApi({
|
|
22
|
+
route: "/doorcontroller/createDoorControllerRule",
|
|
23
|
+
body: ruleConfig,
|
|
24
|
+
modifiers: requestModifiers,
|
|
25
|
+
sessionId,
|
|
26
|
+
});
|
|
27
|
+
if (res.error) {
|
|
28
|
+
throw new Error(JSON.stringify(res));
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
uuid: res.ruleUuid ?? undefined,
|
|
32
|
+
success: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function getDoorPolicies(requestModifiers, sessionId) {
|
|
36
|
+
const res = await postApi({
|
|
37
|
+
route: "/policy/getDoorPolicies",
|
|
38
|
+
body: {},
|
|
39
|
+
modifiers: requestModifiers,
|
|
40
|
+
sessionId,
|
|
41
|
+
});
|
|
42
|
+
if (res.error) {
|
|
43
|
+
throw new Error(JSON.stringify(res));
|
|
44
|
+
}
|
|
45
|
+
return (res.policies?.map((policy) => ({
|
|
46
|
+
uuid: policy.uuid ?? undefined,
|
|
47
|
+
name: policy.name ?? undefined,
|
|
48
|
+
orgUuid: policy.orgUuid ?? undefined,
|
|
49
|
+
enabled: policy.enabled ?? undefined,
|
|
50
|
+
})) ?? []);
|
|
51
|
+
}
|
|
52
|
+
export async function createDoorPolicy(policyConfig, requestModifiers, sessionId) {
|
|
53
|
+
const res = await postApi({
|
|
54
|
+
route: "/policy/createDoorPolicy",
|
|
55
|
+
body: policyConfig,
|
|
56
|
+
modifiers: requestModifiers,
|
|
57
|
+
sessionId,
|
|
58
|
+
});
|
|
59
|
+
if (res.error) {
|
|
60
|
+
throw new Error(JSON.stringify(res));
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
uuid: res.policyUuid ?? undefined,
|
|
64
|
+
success: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -8,6 +8,73 @@ export const HumanEvent = z.object({
|
|
|
8
8
|
timestamp: z.number(),
|
|
9
9
|
id: z.number(),
|
|
10
10
|
});
|
|
11
|
+
const ACCESS_CONTROL_EVENT_BATCH_SIZE = 1000;
|
|
12
|
+
const ACCESS_CONTROL_EVENT_TYPE_FILTER = ["CredentialReceivedEvent"];
|
|
13
|
+
function mapAccessControlEvent(credEvent, timeZone) {
|
|
14
|
+
return {
|
|
15
|
+
authenticationResult: credEvent?.authenticationResult,
|
|
16
|
+
authorizationResult: credEvent?.authorizationResult,
|
|
17
|
+
doorUuid: credEvent?.componentCompositeUuid,
|
|
18
|
+
locationUuid: credEvent?.locationUuid,
|
|
19
|
+
user: credEvent?.originator?.username,
|
|
20
|
+
credSource: credEvent?.credSource,
|
|
21
|
+
timestampMs: credEvent?.timestampMs,
|
|
22
|
+
datetime: credEvent?.timestampMs ? formatTimestamp(credEvent.timestampMs, timeZone) : undefined,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function getAccessControlEventsForDoor(doorUuid, startTime, endTime, timeZone, requestModifiers, sessionId) {
|
|
26
|
+
const allEvents = [];
|
|
27
|
+
let createdBeforeMs = endTime;
|
|
28
|
+
while (true) {
|
|
29
|
+
const body = {
|
|
30
|
+
accessControlledDoorUuid: doorUuid,
|
|
31
|
+
...(startTime !== undefined ? { createdAfterMs: startTime } : {}),
|
|
32
|
+
...(createdBeforeMs !== undefined ? { createdBeforeMs } : {}),
|
|
33
|
+
limit: ACCESS_CONTROL_EVENT_BATCH_SIZE,
|
|
34
|
+
typeFilter: ACCESS_CONTROL_EVENT_TYPE_FILTER,
|
|
35
|
+
};
|
|
36
|
+
const response = await postApi({
|
|
37
|
+
route: "/component/findComponentEventsByAccessControlledDoor",
|
|
38
|
+
body,
|
|
39
|
+
modifiers: requestModifiers,
|
|
40
|
+
sessionId,
|
|
41
|
+
});
|
|
42
|
+
const componentEvents = response.componentEvents || [];
|
|
43
|
+
if (componentEvents.length === 0) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
const mappedEvents = componentEvents
|
|
47
|
+
.map((credEvent) => mapAccessControlEvent(credEvent, timeZone))
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
allEvents.push(...mappedEvents);
|
|
50
|
+
if (componentEvents.length < ACCESS_CONTROL_EVENT_BATCH_SIZE) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
const oldestTimestamp = componentEvents.reduce((oldest, event) => {
|
|
54
|
+
const ts = event?.timestampMs;
|
|
55
|
+
if (typeof ts !== "number") {
|
|
56
|
+
return oldest;
|
|
57
|
+
}
|
|
58
|
+
if (oldest === null || ts < oldest) {
|
|
59
|
+
return ts;
|
|
60
|
+
}
|
|
61
|
+
return oldest;
|
|
62
|
+
}, null);
|
|
63
|
+
if (oldestTimestamp === null) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
// createdBeforeMs is exclusive. Moving the window to the oldest seen timestamp
|
|
67
|
+
// prevents duplicate pages and keeps fetching older events until the range is exhausted.
|
|
68
|
+
if (createdBeforeMs !== undefined && oldestTimestamp >= createdBeforeMs) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if (startTime !== undefined && oldestTimestamp <= startTime) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
createdBeforeMs = oldestTimestamp;
|
|
75
|
+
}
|
|
76
|
+
return allEvents;
|
|
77
|
+
}
|
|
11
78
|
export async function getFaceEvents(_locationUuid, timeZone, requestModifiers, sessionId) {
|
|
12
79
|
const nowMs = Date.now();
|
|
13
80
|
const rangeStartMs = nowMs - THREE_HOURS_MS;
|
|
@@ -47,36 +114,10 @@ export async function getFaceEvents(_locationUuid, timeZone, requestModifiers, s
|
|
|
47
114
|
return response;
|
|
48
115
|
}
|
|
49
116
|
export async function getAccessControlEvents(doorUuids, startTime, endTime, timeZone, requestModifiers, sessionId) {
|
|
50
|
-
const
|
|
51
|
-
accessControlledDoorUuid: doorUuid,
|
|
52
|
-
...(startTime ? { createdAfterMs: startTime } : {}),
|
|
53
|
-
...(endTime ? { createdBeforeMs: endTime } : {}),
|
|
54
|
-
typeFilter: ["CredentialReceivedEvent"],
|
|
55
|
-
}));
|
|
56
|
-
const responses = await Promise.all(bodies.map(body => postApi({
|
|
57
|
-
route: "/component/findComponentEventsByAccessControlledDoor",
|
|
58
|
-
body,
|
|
59
|
-
modifiers: requestModifiers,
|
|
60
|
-
sessionId,
|
|
61
|
-
}).then(response => {
|
|
62
|
-
return (response.componentEvents || [])
|
|
63
|
-
.map((credEvent) => {
|
|
64
|
-
return {
|
|
65
|
-
authenticationResult: credEvent?.authenticationResult,
|
|
66
|
-
authorizationResult: credEvent?.authorizationResult,
|
|
67
|
-
doorUuid: credEvent?.componentCompositeUuid,
|
|
68
|
-
locationUuid: credEvent?.locationUuid,
|
|
69
|
-
user: credEvent?.originator?.username,
|
|
70
|
-
credSource: credEvent?.credSource,
|
|
71
|
-
datetime: credEvent?.timestampMs
|
|
72
|
-
? formatTimestamp(credEvent.timestampMs, timeZone)
|
|
73
|
-
: undefined,
|
|
74
|
-
};
|
|
75
|
-
})
|
|
76
|
-
.filter(Boolean);
|
|
77
|
-
})));
|
|
117
|
+
const responses = await Promise.all(doorUuids.map(doorUuid => getAccessControlEventsForDoor(doorUuid, startTime, endTime, timeZone, requestModifiers, sessionId)));
|
|
78
118
|
// Flatten all componentEvents into a single array
|
|
79
119
|
const accessControlEvents = responses.flatMap(events => events);
|
|
120
|
+
accessControlEvents.sort((a, b) => (b.timestampMs || 0) - (a.timestampMs || 0));
|
|
80
121
|
console.error(`componentEvents: ${JSON.stringify(accessControlEvents)}`);
|
|
81
122
|
return accessControlEvents;
|
|
82
123
|
}
|
|
@@ -318,3 +359,85 @@ startTime, endTime, timeZone, requestModifiers, sessionId) {
|
|
|
318
359
|
console.error(`componentEvents: ${JSON.stringify(events)}`);
|
|
319
360
|
return events;
|
|
320
361
|
}
|
|
362
|
+
export async function getButtonPressEvents(sensorUuid, startTime, endTime, timeZone, requestModifiers, sessionId) {
|
|
363
|
+
const body = {
|
|
364
|
+
sensorUuid,
|
|
365
|
+
...(startTime ? { createdAfterMs: startTime } : {}),
|
|
366
|
+
...(endTime ? { createdBeforeMs: endTime } : {}),
|
|
367
|
+
};
|
|
368
|
+
const response = await postApi({
|
|
369
|
+
route: "/button/getButtonPressEventsForSensor",
|
|
370
|
+
body,
|
|
371
|
+
modifiers: requestModifiers,
|
|
372
|
+
sessionId,
|
|
373
|
+
});
|
|
374
|
+
return (response.events || []).map(event => ({
|
|
375
|
+
timestampMs: event?.timestampMs ?? undefined,
|
|
376
|
+
datetime: event?.timestampMs ? formatTimestamp(event.timestampMs, timeZone) : undefined,
|
|
377
|
+
sensorUuid: event?.componentUuid ?? undefined,
|
|
378
|
+
buttonState: event?.buttonPress ?? undefined,
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
export async function getOccupancyEvents(sensorUuid, startTime, endTime, timeZone, requestModifiers, sessionId) {
|
|
382
|
+
const body = {
|
|
383
|
+
sensorUuid,
|
|
384
|
+
...(startTime ? { createdAfterMs: startTime } : {}),
|
|
385
|
+
...(endTime ? { createdBeforeMs: endTime } : {}),
|
|
386
|
+
};
|
|
387
|
+
const response = await postApi({
|
|
388
|
+
route: "/occupancy/getOccupancyEventsForSensor",
|
|
389
|
+
body,
|
|
390
|
+
modifiers: requestModifiers,
|
|
391
|
+
sessionId,
|
|
392
|
+
});
|
|
393
|
+
return (response.occupancyEvents || response.events || []).map(event => ({
|
|
394
|
+
timestampMs: event.timestampMs ?? undefined,
|
|
395
|
+
datetime: event.timestampMs ? formatTimestamp(event.timestampMs, timeZone) : undefined,
|
|
396
|
+
sensorUuid: event.sensorUuid ?? event.componentUuid ?? undefined,
|
|
397
|
+
count: event.count ?? undefined,
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
export async function getProximityEvents(tagUuids, startTime, endTime, timeZone, requestModifiers, sessionId) {
|
|
401
|
+
const allEvents = [];
|
|
402
|
+
for (const tagUuid of tagUuids) {
|
|
403
|
+
const body = {
|
|
404
|
+
tagUuid,
|
|
405
|
+
...(startTime ? { createdAfterMs: startTime } : {}),
|
|
406
|
+
...(endTime ? { createdBeforeMs: endTime } : {}),
|
|
407
|
+
};
|
|
408
|
+
const response = await postApi({
|
|
409
|
+
route: "/proximity/getProximityEventsForTag",
|
|
410
|
+
body,
|
|
411
|
+
modifiers: requestModifiers,
|
|
412
|
+
sessionId,
|
|
413
|
+
});
|
|
414
|
+
const mapped = (response.proximityEvents || []).map(event => ({
|
|
415
|
+
timestampMs: event.startTimeMs ?? undefined,
|
|
416
|
+
datetime: event.startTimeMs ? formatTimestamp(event.startTimeMs, timeZone) : undefined,
|
|
417
|
+
tagUuid: event.bleDeviceUuid ?? undefined,
|
|
418
|
+
rssi: event.bleRssi ?? undefined,
|
|
419
|
+
}));
|
|
420
|
+
allEvents.push(...mapped);
|
|
421
|
+
}
|
|
422
|
+
return allEvents;
|
|
423
|
+
}
|
|
424
|
+
export async function getDoorbellEvents(doorbellCameraUuid, startTime, endTime, timeZone, requestModifiers, sessionId) {
|
|
425
|
+
const body = {
|
|
426
|
+
doorbellCameraUuid,
|
|
427
|
+
limit: 100,
|
|
428
|
+
...(startTime ? { createdAfterMs: startTime } : {}),
|
|
429
|
+
...(endTime ? { createdBeforeMs: endTime } : {}),
|
|
430
|
+
};
|
|
431
|
+
const response = await postApi({
|
|
432
|
+
route: "/doorbellcamera/findComponentEventsForDoorbellCamera",
|
|
433
|
+
body,
|
|
434
|
+
modifiers: requestModifiers,
|
|
435
|
+
sessionId,
|
|
436
|
+
});
|
|
437
|
+
return (response.componentEvents || []).map(event => ({
|
|
438
|
+
timestampMs: event.timestampMs ?? undefined,
|
|
439
|
+
datetime: event.timestampMs ? formatTimestamp(event.timestampMs, timeZone) : undefined,
|
|
440
|
+
doorbellCameraUuid: event.componentUuid ?? undefined,
|
|
441
|
+
eventType: event.type ?? undefined,
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
@@ -2,103 +2,58 @@ import { postApi } from "../network.js";
|
|
|
2
2
|
import { formatTimestamp } from "../util.js";
|
|
3
3
|
import { removeNulls } from "../utils/remove-nulls.js";
|
|
4
4
|
export async function getFaceEvents(args, timeZone, requestModifiers, sessionId) {
|
|
5
|
-
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
delete filteredArgs.pageRequest.lastEvaluatedKey;
|
|
16
|
-
}
|
|
5
|
+
// OptionalNullable so that we can remove some fields before sending to the API
|
|
6
|
+
let filteredArgs = { ...args };
|
|
7
|
+
if (filteredArgs.pageRequest && filteredArgs.pageRequest.lastEvaluatedKey === "") {
|
|
8
|
+
delete filteredArgs.pageRequest.lastEvaluatedKey;
|
|
9
|
+
}
|
|
10
|
+
if (filteredArgs.searchFilter) {
|
|
11
|
+
if (filteredArgs.searchFilter.faceNames &&
|
|
12
|
+
filteredArgs.searchFilter.faceNames.length === 0) {
|
|
13
|
+
// @ts-expect-error - we can break typing
|
|
14
|
+
delete filteredArgs.searchFilter.faceNames;
|
|
17
15
|
}
|
|
18
|
-
if (filteredArgs.searchFilter
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// delete filteredArgs.searchFilter.deviceUuids;
|
|
26
|
-
// }
|
|
27
|
-
if (filteredArgs.searchFilter.faceNames && filteredArgs.searchFilter.faceNames.length === 0) {
|
|
28
|
-
// @ts-expect-error - we can break typing
|
|
29
|
-
delete filteredArgs.searchFilter.faceNames;
|
|
30
|
-
}
|
|
31
|
-
if (filteredArgs.searchFilter.labels && filteredArgs.searchFilter.labels.length === 0) {
|
|
32
|
-
// @ts-expect-error - we can break typing
|
|
33
|
-
delete filteredArgs.searchFilter.labels;
|
|
34
|
-
}
|
|
35
|
-
// Remove empty strings
|
|
36
|
-
// if (filteredArgs.searchFilter.faceNameContains === "") {
|
|
37
|
-
// delete filteredArgs.searchFilter.faceNameContains;
|
|
38
|
-
// }
|
|
39
|
-
// Remove false booleans
|
|
40
|
-
if (filteredArgs.searchFilter.hasEmbedding === false) {
|
|
41
|
-
delete filteredArgs.searchFilter.hasEmbedding;
|
|
42
|
-
}
|
|
43
|
-
if (filteredArgs.searchFilter.hasName === false) {
|
|
44
|
-
delete filteredArgs.searchFilter.hasName;
|
|
45
|
-
}
|
|
46
|
-
// if there is no timestampFilter, we can remove it
|
|
47
|
-
// rangeEnd and rangeStart are ISO 8601 strings
|
|
48
|
-
if (filteredArgs.searchFilter.timestampFilter) {
|
|
49
|
-
if (filteredArgs.searchFilter.timestampFilter.rangeEnd === null) {
|
|
50
|
-
delete filteredArgs.searchFilter.timestampFilter.rangeEnd;
|
|
51
|
-
}
|
|
52
|
-
if (filteredArgs.searchFilter.timestampFilter.rangeStart === null) {
|
|
53
|
-
delete filteredArgs.searchFilter.timestampFilter.rangeStart;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
16
|
+
if (filteredArgs.searchFilter.labels &&
|
|
17
|
+
filteredArgs.searchFilter.labels.length === 0) {
|
|
18
|
+
// @ts-expect-error - we can break typing
|
|
19
|
+
delete filteredArgs.searchFilter.labels;
|
|
20
|
+
}
|
|
21
|
+
if (filteredArgs.searchFilter.hasEmbedding === false) {
|
|
22
|
+
delete filteredArgs.searchFilter.hasEmbedding;
|
|
56
23
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
modifiers: requestModifiers,
|
|
64
|
-
sessionId,
|
|
65
|
-
});
|
|
66
|
-
lastResponse = response;
|
|
67
|
-
// Filter the faceEvents to only include the specified fields
|
|
68
|
-
if (response && response.faceEvents) {
|
|
69
|
-
const filteredEvents = response.faceEvents.map((event) => ({
|
|
70
|
-
deviceUuid: event.deviceUuid,
|
|
71
|
-
eventTimestampMs: event.eventTimestamp,
|
|
72
|
-
eventTimestamp: formatTimestamp(event.eventTimestamp, timeZone),
|
|
73
|
-
faceName: event.faceName,
|
|
74
|
-
locationUuid: event.locationUuid,
|
|
75
|
-
personUuid: event.personUuid,
|
|
76
|
-
// selectedPersonMatch: event.selectedPersonMatch,
|
|
77
|
-
thumbnailS3Key: event.thumbnailS3Key,
|
|
78
|
-
// topPersonMatches: event.topPersonMatches,
|
|
79
|
-
uuid: event.uuid,
|
|
80
|
-
}));
|
|
81
|
-
allFaceEvents.push(...filteredEvents);
|
|
82
|
-
// Check if we should continue pagination
|
|
83
|
-
if (response.faceEvents.length === 100 && response.lastEvaluatedKey) {
|
|
84
|
-
// Update the pageRequest for the next iteration
|
|
85
|
-
currentArgs = {
|
|
86
|
-
...currentArgs,
|
|
87
|
-
pageRequest: {
|
|
88
|
-
maxPageSize: currentArgs.pageRequest?.maxPageSize || 100,
|
|
89
|
-
lastEvaluatedKey: response.lastEvaluatedKey,
|
|
90
|
-
},
|
|
91
|
-
};
|
|
24
|
+
if (filteredArgs.searchFilter.hasName === false) {
|
|
25
|
+
delete filteredArgs.searchFilter.hasName;
|
|
26
|
+
}
|
|
27
|
+
if (filteredArgs.searchFilter.timestampFilter) {
|
|
28
|
+
if (filteredArgs.searchFilter.timestampFilter.rangeEnd === null) {
|
|
29
|
+
delete filteredArgs.searchFilter.timestampFilter.rangeEnd;
|
|
92
30
|
}
|
|
93
|
-
|
|
94
|
-
|
|
31
|
+
if (filteredArgs.searchFilter.timestampFilter.rangeStart === null) {
|
|
32
|
+
delete filteredArgs.searchFilter.timestampFilter.rangeStart;
|
|
95
33
|
}
|
|
96
34
|
}
|
|
97
|
-
else {
|
|
98
|
-
hasMore = false;
|
|
99
|
-
}
|
|
100
35
|
}
|
|
101
|
-
|
|
36
|
+
filteredArgs = removeNulls(filteredArgs);
|
|
37
|
+
const response = await postApi({
|
|
38
|
+
route: "/faceRecognition/faceEvent/findFaceEventsByOrg",
|
|
39
|
+
body: filteredArgs,
|
|
40
|
+
modifiers: requestModifiers,
|
|
41
|
+
sessionId,
|
|
42
|
+
});
|
|
43
|
+
const faceEvents = (response?.faceEvents ?? []).map((event) => ({
|
|
44
|
+
deviceUuid: event.deviceUuid,
|
|
45
|
+
eventTimestampMs: event.eventTimestamp,
|
|
46
|
+
eventTimestamp: formatTimestamp(event.eventTimestamp, timeZone),
|
|
47
|
+
faceName: event.faceName,
|
|
48
|
+
locationUuid: event.locationUuid,
|
|
49
|
+
personUuid: event.personUuid,
|
|
50
|
+
thumbnailS3Key: event.thumbnailS3Key,
|
|
51
|
+
uuid: event.uuid,
|
|
52
|
+
}));
|
|
53
|
+
return {
|
|
54
|
+
faceEvents,
|
|
55
|
+
lastEvaluatedKey: response?.lastEvaluatedKey,
|
|
56
|
+
};
|
|
102
57
|
}
|
|
103
58
|
export async function getRegisteredFaces(_args, requestModifiers, sessionId) {
|
|
104
59
|
return await postApi({
|
|
@@ -108,3 +63,75 @@ export async function getRegisteredFaces(_args, requestModifiers, sessionId) {
|
|
|
108
63
|
sessionId,
|
|
109
64
|
});
|
|
110
65
|
}
|
|
66
|
+
export async function getPersonLabels(requestModifiers, sessionId) {
|
|
67
|
+
return await postApi({
|
|
68
|
+
route: "/faceRecognition/person/findPersonLabelsByOrg",
|
|
69
|
+
body: {},
|
|
70
|
+
modifiers: requestModifiers,
|
|
71
|
+
sessionId,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export async function searchSimilarFaces(faceEventUuid, timeZone, requestModifiers, sessionId) {
|
|
75
|
+
const res = await postApi({
|
|
76
|
+
route: "/faceRecognition/faceEvent/findSimilarFaceEvents",
|
|
77
|
+
body: { faceEventUuid },
|
|
78
|
+
modifiers: requestModifiers,
|
|
79
|
+
sessionId,
|
|
80
|
+
});
|
|
81
|
+
if (res.error)
|
|
82
|
+
throw new Error(JSON.stringify(res));
|
|
83
|
+
return (res.faceEvents || []).map((event) => ({
|
|
84
|
+
uuid: event.uuid ?? undefined,
|
|
85
|
+
personUuid: event.personUuid ?? undefined,
|
|
86
|
+
similarity: event.similarity ?? undefined,
|
|
87
|
+
eventTimestamp: event.eventTimestamp
|
|
88
|
+
? formatTimestamp(event.eventTimestamp, timeZone)
|
|
89
|
+
: undefined,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
export async function getFaceMatchmakers(requestModifiers, sessionId) {
|
|
93
|
+
const res = await postApi({
|
|
94
|
+
route: "/faceRecognition/matchmaker/findFaceMatchmakersByOrg",
|
|
95
|
+
body: {},
|
|
96
|
+
modifiers: requestModifiers,
|
|
97
|
+
sessionId,
|
|
98
|
+
});
|
|
99
|
+
if (res.error)
|
|
100
|
+
throw new Error(JSON.stringify(res));
|
|
101
|
+
return (res.faceMatchmakers || []).map((m) => ({
|
|
102
|
+
uuid: m.uuid ?? undefined,
|
|
103
|
+
personUuid: m.personUuid ?? undefined,
|
|
104
|
+
name: m.name ?? undefined,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
export async function getFaceEventsByPerson(personUuid, timeZone, requestModifiers, sessionId) {
|
|
108
|
+
const res = await postApi({
|
|
109
|
+
route: "/faceRecognition/faceEvent/findFaceEventsByOrg",
|
|
110
|
+
body: {
|
|
111
|
+
pageRequest: { maxPageSize: 75 },
|
|
112
|
+
searchFilter: {
|
|
113
|
+
personUuids: [personUuid],
|
|
114
|
+
deviceUuids: [],
|
|
115
|
+
faceNames: [],
|
|
116
|
+
labels: [],
|
|
117
|
+
locationUuids: [],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
modifiers: requestModifiers,
|
|
121
|
+
sessionId,
|
|
122
|
+
});
|
|
123
|
+
if (res.error)
|
|
124
|
+
throw new Error(JSON.stringify(res));
|
|
125
|
+
const faceEvents = (res.faceEvents || []).map((event) => ({
|
|
126
|
+
uuid: event.uuid ?? undefined,
|
|
127
|
+
personUuid: event.personUuid ?? undefined,
|
|
128
|
+
eventTimestamp: event.eventTimestamp
|
|
129
|
+
? formatTimestamp(event.eventTimestamp, timeZone)
|
|
130
|
+
: undefined,
|
|
131
|
+
deviceUuid: event.deviceUuid ?? undefined,
|
|
132
|
+
}));
|
|
133
|
+
return {
|
|
134
|
+
faceEvents,
|
|
135
|
+
lastEvaluatedKey: res.lastEvaluatedKey,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -47,6 +47,8 @@ export async function getAccessControlledDoors(requestModifiers, sessionId) {
|
|
|
47
47
|
geofenceEnabled: door.geofenceEnabled,
|
|
48
48
|
locationUuid: door.locationUuid,
|
|
49
49
|
policyUuid: door.policyUuid,
|
|
50
|
+
remoteUnlockEnabled: door.remoteUnlockEnabled,
|
|
51
|
+
associatedCameras: door.associatedCameras?.filter((c) => c !== null) ?? [],
|
|
50
52
|
name: door.name,
|
|
51
53
|
uuid: door.uuid,
|
|
52
54
|
};
|