@oneuptime/common 11.1.2 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/Incident.ts +55 -0
  3. package/Models/DatabaseModels/Index.ts +12 -0
  4. package/Models/DatabaseModels/IoTDevice.ts +690 -0
  5. package/Models/DatabaseModels/IoTFleet.ts +862 -0
  6. package/Models/DatabaseModels/IoTFleetLabelRule.ts +514 -0
  7. package/Models/DatabaseModels/IoTFleetOwnerRule.ts +596 -0
  8. package/Models/DatabaseModels/IoTFleetOwnerTeam.ts +485 -0
  9. package/Models/DatabaseModels/IoTFleetOwnerUser.ts +484 -0
  10. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  11. package/Server/API/IoTDeviceAPI.ts +128 -0
  12. package/Server/API/TelemetryAPI.ts +104 -35
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.ts +399 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.ts +97 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.ts +979 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  17. package/Server/Services/IoTDeviceService.ts +356 -0
  18. package/Server/Services/IoTFleetLabelRuleEngineService.ts +198 -0
  19. package/Server/Services/IoTFleetLabelRuleService.ts +14 -0
  20. package/Server/Services/IoTFleetOwnerRuleEngineService.ts +216 -0
  21. package/Server/Services/IoTFleetOwnerRuleService.ts +14 -0
  22. package/Server/Services/IoTFleetOwnerTeamService.ts +10 -0
  23. package/Server/Services/IoTFleetOwnerUserService.ts +10 -0
  24. package/Server/Services/IoTFleetService.ts +376 -0
  25. package/Server/Services/OpenTelemetryIngestService.ts +16 -0
  26. package/Server/Services/StatusPageService.ts +69 -26
  27. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +36 -0
  28. package/Server/Services/TelemetryUsageBillingService.ts +18 -0
  29. package/Server/Services/TraceAggregationService.ts +174 -0
  30. package/Server/Utils/Monitor/MonitorAlert.ts +24 -4
  31. package/Server/Utils/Monitor/MonitorClusterContext.ts +26 -2
  32. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +4 -2
  33. package/Server/Utils/Monitor/MonitorIncident.ts +39 -5
  34. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +14 -0
  35. package/Server/Utils/Monitor/SeriesResourceLabels.ts +22 -0
  36. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +408 -0
  37. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +36 -0
  38. package/Types/Icon/IconProp.ts +1 -0
  39. package/Types/Monitor/IotAlertTemplates.ts +593 -0
  40. package/Types/Monitor/IotMetricCatalog.ts +146 -0
  41. package/Types/Monitor/MonitorStep.ts +31 -0
  42. package/Types/Monitor/MonitorStepIoTMonitor.ts +65 -0
  43. package/Types/Monitor/MonitorType.ts +12 -1
  44. package/Types/Permission.ts +230 -0
  45. package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +3 -0
  46. package/Types/Telemetry/ServiceType.ts +1 -0
  47. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
  48. package/UI/Components/Icon/Icon.tsx +18 -2
  49. package/UI/Components/Page/Page.tsx +1 -2
  50. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  51. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  52. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  53. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  54. package/build/dist/Models/DatabaseModels/Index.js +12 -0
  55. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  56. package/build/dist/Models/DatabaseModels/IoTDevice.js +722 -0
  57. package/build/dist/Models/DatabaseModels/IoTDevice.js.map +1 -0
  58. package/build/dist/Models/DatabaseModels/IoTFleet.js +882 -0
  59. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -0
  60. package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js +522 -0
  61. package/build/dist/Models/DatabaseModels/IoTFleetLabelRule.js.map +1 -0
  62. package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js +603 -0
  63. package/build/dist/Models/DatabaseModels/IoTFleetOwnerRule.js.map +1 -0
  64. package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js +503 -0
  65. package/build/dist/Models/DatabaseModels/IoTFleetOwnerTeam.js.map +1 -0
  66. package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js +502 -0
  67. package/build/dist/Models/DatabaseModels/IoTFleetOwnerUser.js.map +1 -0
  68. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  69. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  70. package/build/dist/Server/API/IoTDeviceAPI.js +92 -0
  71. package/build/dist/Server/API/IoTDeviceAPI.js.map +1 -0
  72. package/build/dist/Server/API/TelemetryAPI.js +76 -27
  73. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.js +187 -0
  75. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782900000000-AddIoTFleetAndDeviceTables.js.map +1 -0
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.js +48 -0
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783000000000-AddIoTFleetActivityJoinTables.js.map +1 -0
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.js +340 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783010000000-MigrationName.js.map +1 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  82. package/build/dist/Server/Services/IoTDeviceService.js +249 -0
  83. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -0
  84. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +166 -0
  85. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -0
  86. package/build/dist/Server/Services/IoTFleetLabelRuleService.js +13 -0
  87. package/build/dist/Server/Services/IoTFleetLabelRuleService.js.map +1 -0
  88. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +186 -0
  89. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -0
  90. package/build/dist/Server/Services/IoTFleetOwnerRuleService.js +13 -0
  91. package/build/dist/Server/Services/IoTFleetOwnerRuleService.js.map +1 -0
  92. package/build/dist/Server/Services/IoTFleetOwnerTeamService.js +9 -0
  93. package/build/dist/Server/Services/IoTFleetOwnerTeamService.js.map +1 -0
  94. package/build/dist/Server/Services/IoTFleetOwnerUserService.js +9 -0
  95. package/build/dist/Server/Services/IoTFleetOwnerUserService.js.map +1 -0
  96. package/build/dist/Server/Services/IoTFleetService.js +340 -0
  97. package/build/dist/Server/Services/IoTFleetService.js.map +1 -0
  98. package/build/dist/Server/Services/OpenTelemetryIngestService.js +26 -11
  99. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  100. package/build/dist/Server/Services/StatusPageService.js +57 -23
  101. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  102. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +34 -0
  103. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  104. package/build/dist/Server/Services/TelemetryUsageBillingService.js +13 -0
  105. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  106. package/build/dist/Server/Services/TraceAggregationService.js +136 -0
  107. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  108. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +37 -19
  109. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  110. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +27 -4
  111. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +1 -1
  112. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +4 -2
  113. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  114. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +39 -11
  115. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  116. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +8 -0
  117. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  118. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +17 -0
  119. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  120. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +300 -0
  121. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -0
  122. package/build/dist/Types/Icon/IconProp.js +1 -0
  123. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  124. package/build/dist/Types/Monitor/IotAlertTemplates.js +467 -0
  125. package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -0
  126. package/build/dist/Types/Monitor/IotMetricCatalog.js +108 -0
  127. package/build/dist/Types/Monitor/IotMetricCatalog.js.map +1 -0
  128. package/build/dist/Types/Monitor/MonitorStep.js +23 -0
  129. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  130. package/build/dist/Types/Monitor/MonitorStepIoTMonitor.js +32 -0
  131. package/build/dist/Types/Monitor/MonitorStepIoTMonitor.js.map +1 -0
  132. package/build/dist/Types/Monitor/MonitorType.js +11 -1
  133. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  134. package/build/dist/Types/Permission.js +204 -0
  135. package/build/dist/Types/Permission.js.map +1 -1
  136. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +2 -0
  137. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -1
  138. package/build/dist/Types/Telemetry/ServiceType.js +1 -0
  139. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  140. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +3 -3
  141. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  142. package/build/dist/UI/Components/Icon/Icon.js +12 -2
  143. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  144. package/build/dist/UI/Components/Page/Page.js +1 -2
  145. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  146. package/package.json +1 -1
@@ -0,0 +1,593 @@
1
+ import ObjectID from "../ObjectID";
2
+ import MonitorStep from "./MonitorStep";
3
+ import MonitorCriteria from "./MonitorCriteria";
4
+ import MonitorCriteriaInstance from "./MonitorCriteriaInstance";
5
+ import FilterCondition from "../Filter/FilterCondition";
6
+ import { CheckOn, FilterType, EvaluateOverTimeType } from "./CriteriaFilter";
7
+ import MonitorStepIoTMonitor from "./MonitorStepIoTMonitor";
8
+ import RollingTime from "../RollingTime/RollingTime";
9
+ import MetricsAggregationType from "../Metrics/MetricsAggregationType";
10
+
11
+ export type IoTAlertTemplateCategory =
12
+ | "Availability"
13
+ | "Power"
14
+ | "Connectivity"
15
+ | "Environment"
16
+ | "System";
17
+
18
+ export type IoTAlertTemplateSeverity = "Critical" | "Warning";
19
+
20
+ export interface IoTAlertTemplateArgs {
21
+ fleetIdentifier: string;
22
+ onlineMonitorStatusId: ObjectID;
23
+ offlineMonitorStatusId: ObjectID;
24
+ defaultIncidentSeverityId: ObjectID;
25
+ defaultAlertSeverityId: ObjectID;
26
+ monitorName: string;
27
+ }
28
+
29
+ export interface IoTAlertTemplate {
30
+ id: string;
31
+ name: string;
32
+ description: string;
33
+ category: IoTAlertTemplateCategory;
34
+ severity: IoTAlertTemplateSeverity;
35
+ getMonitorStep: (args: IoTAlertTemplateArgs) => MonitorStep;
36
+ }
37
+
38
+ /*
39
+ * Filter contract: IoT devices push OTel metrics that carry the `device.id`
40
+ * datapoint label per device, plus the datapoint attributes `iot.scope`
41
+ * (fleet | device), `iot.device.type` and `iot.device.kind`. Templates filter
42
+ * on those attributes and group by the untouched `device.id` label so one
43
+ * incident fires per device. All of these are datapoint attributes, so they
44
+ * are NOT `resource.`-prefixed in ClickHouse.
45
+ */
46
+
47
+ export function buildIoTMonitorStep(args: {
48
+ iotMonitor: MonitorStepIoTMonitor;
49
+ offlineCriteriaInstance: MonitorCriteriaInstance;
50
+ onlineCriteriaInstance: MonitorCriteriaInstance;
51
+ }): MonitorStep {
52
+ const monitorStep: MonitorStep = new MonitorStep();
53
+
54
+ const monitorCriteria: MonitorCriteria = new MonitorCriteria();
55
+
56
+ monitorCriteria.data = {
57
+ monitorCriteriaInstanceArray: [
58
+ args.offlineCriteriaInstance,
59
+ args.onlineCriteriaInstance,
60
+ ],
61
+ };
62
+
63
+ monitorStep.data = {
64
+ id: ObjectID.generate().toString(),
65
+ monitorDestination: undefined,
66
+ doNotFollowRedirects: undefined,
67
+ monitorDestinationPort: undefined,
68
+ monitorCriteria: monitorCriteria,
69
+ requestType: "GET" as any,
70
+ requestHeaders: undefined,
71
+ requestBody: undefined,
72
+ customCode: undefined,
73
+ screenSizeTypes: undefined,
74
+ browserTypes: undefined,
75
+ retryCountOnError: undefined,
76
+ logMonitor: undefined,
77
+ traceMonitor: undefined,
78
+ metricMonitor: undefined,
79
+ exceptionMonitor: undefined,
80
+ snmpMonitor: undefined,
81
+ dnsMonitor: undefined,
82
+ domainMonitor: undefined,
83
+ externalStatusPageMonitor: undefined,
84
+ kubernetesMonitor: undefined,
85
+ profileMonitor: undefined,
86
+ dockerMonitor: undefined,
87
+ iotMonitor: args.iotMonitor,
88
+ };
89
+
90
+ return monitorStep;
91
+ }
92
+
93
+ export function buildIoTOfflineCriteriaInstance(args: {
94
+ offlineMonitorStatusId: ObjectID;
95
+ incidentSeverityId: ObjectID;
96
+ alertSeverityId: ObjectID;
97
+ monitorName: string;
98
+ metricAlias: string;
99
+ filterType: FilterType;
100
+ value: number;
101
+ incidentTitle?: string;
102
+ incidentDescription?: string;
103
+ criteriaName?: string;
104
+ criteriaDescription?: string;
105
+ }): MonitorCriteriaInstance {
106
+ const instance: MonitorCriteriaInstance = new MonitorCriteriaInstance();
107
+
108
+ const incidentTitle: string =
109
+ args.incidentTitle || `${args.monitorName} - Alert Triggered`;
110
+ const incidentDescription: string =
111
+ args.incidentDescription ||
112
+ `${args.monitorName} has triggered an alert condition. See root cause for detailed IoT device information.`;
113
+
114
+ instance.data = {
115
+ id: ObjectID.generate().toString(),
116
+ monitorStatusId: args.offlineMonitorStatusId,
117
+ filterCondition: FilterCondition.Any,
118
+ filters: [
119
+ {
120
+ checkOn: CheckOn.MetricValue,
121
+ filterType: args.filterType,
122
+ metricMonitorOptions: {
123
+ metricAggregationType: EvaluateOverTimeType.AnyValue,
124
+ metricAlias: args.metricAlias,
125
+ },
126
+ value: args.value,
127
+ },
128
+ ],
129
+ incidents: [
130
+ {
131
+ title: incidentTitle,
132
+ description: incidentDescription,
133
+ incidentSeverityId: args.incidentSeverityId,
134
+ autoResolveIncident: true,
135
+ id: ObjectID.generate().toString(),
136
+ onCallPolicyIds: [],
137
+ },
138
+ ],
139
+ alerts: [
140
+ {
141
+ title: incidentTitle,
142
+ description: incidentDescription,
143
+ alertSeverityId: args.alertSeverityId,
144
+ autoResolveAlert: true,
145
+ id: ObjectID.generate().toString(),
146
+ onCallPolicyIds: [],
147
+ },
148
+ ],
149
+ changeMonitorStatus: true,
150
+ createIncidents: true,
151
+ createAlerts: true,
152
+ name: args.criteriaName || `${args.monitorName} - Unhealthy`,
153
+ description:
154
+ args.criteriaDescription || `Criteria for detecting unhealthy state.`,
155
+ };
156
+
157
+ return instance;
158
+ }
159
+
160
+ export function buildIoTOnlineCriteriaInstance(args: {
161
+ onlineMonitorStatusId: ObjectID;
162
+ metricAlias: string;
163
+ filterType: FilterType;
164
+ value: number;
165
+ }): MonitorCriteriaInstance {
166
+ const instance: MonitorCriteriaInstance = new MonitorCriteriaInstance();
167
+
168
+ instance.data = {
169
+ id: ObjectID.generate().toString(),
170
+ monitorStatusId: args.onlineMonitorStatusId,
171
+ filterCondition: FilterCondition.Any,
172
+ filters: [
173
+ {
174
+ checkOn: CheckOn.MetricValue,
175
+ filterType: args.filterType,
176
+ metricMonitorOptions: {
177
+ metricAggregationType: EvaluateOverTimeType.AnyValue,
178
+ metricAlias: args.metricAlias,
179
+ },
180
+ value: args.value,
181
+ },
182
+ ],
183
+ incidents: [],
184
+ alerts: [],
185
+ changeMonitorStatus: true,
186
+ createIncidents: false,
187
+ createAlerts: false,
188
+ name: "Healthy",
189
+ description: "Criteria for healthy state.",
190
+ };
191
+
192
+ return instance;
193
+ }
194
+
195
+ export function buildIoTMonitorConfig(args: {
196
+ fleetIdentifier: string;
197
+ metricName: string;
198
+ metricAlias: string;
199
+ rollingTime: RollingTime;
200
+ aggregationType: MetricsAggregationType;
201
+ attributes?: Record<string, string>;
202
+ groupByAttributeKey?: string | undefined;
203
+ }): MonitorStepIoTMonitor {
204
+ return {
205
+ fleetIdentifier: args.fleetIdentifier,
206
+ resourceFilters: {},
207
+ metricViewConfig: {
208
+ queryConfigs: [
209
+ {
210
+ metricAliasData: {
211
+ metricVariable: args.metricAlias,
212
+ title: args.metricAlias,
213
+ description: args.metricAlias,
214
+ legend: args.metricAlias,
215
+ legendUnit: undefined,
216
+ },
217
+ metricQueryData: {
218
+ filterData: {
219
+ metricName: args.metricName,
220
+ attributes: args.attributes || {},
221
+ aggegationType: args.aggregationType,
222
+ aggregateBy: {},
223
+ },
224
+ ...(args.groupByAttributeKey
225
+ ? { groupByAttributeKeys: [args.groupByAttributeKey] }
226
+ : {}),
227
+ },
228
+ },
229
+ ],
230
+ formulaConfigs: [],
231
+ },
232
+ rollingTime: args.rollingTime,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Build a ratio monitor: `(numerator / denominator) * 100`, optionally
238
+ * grouped by an OpenTelemetry attribute so one incident fires per group
239
+ * (e.g. per `device.id` = per device).
240
+ *
241
+ * Aggregation contract (see buildKubernetesRatioMonitorConfig for the full
242
+ * derivation): the per-series worker buckets raw rows by (group, minute)
243
+ * and applies the aggregation across both the grouped series AND the
244
+ * scrapes in that minute. `Sum` is only correct when numerator and
245
+ * denominator ride the SAME receiver/scrape so the scrape multiple
246
+ * cancels: `(Σnum × scrapes) / (Σden × scrapes)`. Every IoT metric for a
247
+ * device comes from ONE push from that device — so all IoT ratios are
248
+ * same-receiver and default to `Sum`/`Sum`. (`Avg`/`Avg` is the
249
+ * cross-receiver variant; not needed here.)
250
+ *
251
+ * `attributes` is applied to BOTH queries — the device stamps `iot.scope` /
252
+ * `iot.device.type` on every series of a push (including the *_info metadata
253
+ * series, which also carry `device.id`), so a shared equality filter is safe.
254
+ */
255
+ export function buildIoTRatioMonitorConfig(args: {
256
+ fleetIdentifier: string;
257
+ numeratorMetricName: string;
258
+ denominatorMetricName: string;
259
+ numeratorAlias: string;
260
+ denominatorAlias: string;
261
+ resultAlias: string;
262
+ resultLegend: string;
263
+ rollingTime: RollingTime;
264
+ attributes?: Record<string, string> | undefined;
265
+ groupByAttributeKey?: string | undefined;
266
+ aggregationType?: MetricsAggregationType | undefined;
267
+ }): MonitorStepIoTMonitor {
268
+ const aggregationType: MetricsAggregationType =
269
+ args.aggregationType || MetricsAggregationType.Sum;
270
+
271
+ const buildQueryConfig: (alias: string, metricName: string) => any = (
272
+ alias: string,
273
+ metricName: string,
274
+ ): any => {
275
+ return {
276
+ metricAliasData: {
277
+ metricVariable: alias,
278
+ title: alias,
279
+ description: alias,
280
+ legend: alias,
281
+ legendUnit: undefined,
282
+ },
283
+ metricQueryData: {
284
+ filterData: {
285
+ metricName: metricName,
286
+ attributes: args.attributes || {},
287
+ aggegationType: aggregationType,
288
+ aggregateBy: {},
289
+ },
290
+ ...(args.groupByAttributeKey
291
+ ? { groupByAttributeKeys: [args.groupByAttributeKey] }
292
+ : {}),
293
+ },
294
+ };
295
+ };
296
+
297
+ return {
298
+ fleetIdentifier: args.fleetIdentifier,
299
+ resourceFilters: {},
300
+ metricViewConfig: {
301
+ queryConfigs: [
302
+ buildQueryConfig(args.numeratorAlias, args.numeratorMetricName),
303
+ buildQueryConfig(args.denominatorAlias, args.denominatorMetricName),
304
+ ],
305
+ formulaConfigs: [
306
+ {
307
+ metricAliasData: {
308
+ metricVariable: args.resultAlias,
309
+ title: args.resultLegend,
310
+ description: args.resultLegend,
311
+ legend: args.resultLegend,
312
+ legendUnit: "%",
313
+ },
314
+ metricFormulaData: {
315
+ metricFormula: `(${args.numeratorAlias} / ${args.denominatorAlias}) * 100`,
316
+ },
317
+ },
318
+ ],
319
+ },
320
+ rollingTime: args.rollingTime,
321
+ };
322
+ }
323
+
324
+ // --- Template Definitions ---
325
+
326
+ const deviceOfflineTemplate: IoTAlertTemplate = {
327
+ id: "iot-device-offline",
328
+ name: "Device Offline",
329
+ description:
330
+ "Alert when any IoT device reports as down (iot_device_up = 0). One incident per device, grouped by device.id.",
331
+ category: "Availability",
332
+ severity: "Critical",
333
+ getMonitorStep: (args: IoTAlertTemplateArgs): MonitorStep => {
334
+ const metricAlias: string = "device_up";
335
+
336
+ return buildIoTMonitorStep({
337
+ iotMonitor: buildIoTMonitorConfig({
338
+ fleetIdentifier: args.fleetIdentifier,
339
+ metricName: "iot_device_up",
340
+ metricAlias,
341
+ rollingTime: RollingTime.Past5Minutes,
342
+ /*
343
+ * Min per device — a single down push trips the threshold instead
344
+ * of being masked by pushes where the device was still up.
345
+ */
346
+ aggregationType: MetricsAggregationType.Min,
347
+ attributes: {},
348
+ groupByAttributeKey: "device.id",
349
+ }),
350
+ offlineCriteriaInstance: buildIoTOfflineCriteriaInstance({
351
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
352
+ incidentSeverityId: args.defaultIncidentSeverityId,
353
+ alertSeverityId: args.defaultAlertSeverityId,
354
+ monitorName: args.monitorName,
355
+ metricAlias,
356
+ filterType: FilterType.LessThan,
357
+ value: 1,
358
+ incidentTitle: `[IoT] Device Offline - ${args.monitorName}`,
359
+ incidentDescription: `An IoT device is reporting as down (iot_device_up = 0). The device is unreachable, powered off, or has lost connectivity to its gateway. Check the root cause for the affected device id, verify the device's power and network state, and confirm its gateway is forwarding telemetry.`,
360
+ criteriaName: "Device Offline - iot_device_up < 1",
361
+ criteriaDescription:
362
+ "Triggers when any device reports iot_device_up below 1 over the monitoring window.",
363
+ }),
364
+ onlineCriteriaInstance: buildIoTOnlineCriteriaInstance({
365
+ onlineMonitorStatusId: args.onlineMonitorStatusId,
366
+ metricAlias,
367
+ filterType: FilterType.GreaterThanOrEqualTo,
368
+ value: 1,
369
+ }),
370
+ });
371
+ },
372
+ };
373
+
374
+ const lowBatteryTemplate: IoTAlertTemplate = {
375
+ id: "iot-low-battery",
376
+ name: "Low Battery",
377
+ description:
378
+ "Alert when any IoT device's battery level drops below 20% (iot_battery_percent < 20). One incident per device, grouped by device.id.",
379
+ category: "Power",
380
+ severity: "Warning",
381
+ getMonitorStep: (args: IoTAlertTemplateArgs): MonitorStep => {
382
+ const metricAlias: string = "battery_percent";
383
+
384
+ return buildIoTMonitorStep({
385
+ iotMonitor: buildIoTMonitorConfig({
386
+ fleetIdentifier: args.fleetIdentifier,
387
+ metricName: "iot_battery_percent",
388
+ metricAlias,
389
+ rollingTime: RollingTime.Past5Minutes,
390
+ /*
391
+ * Avg per device — battery percentage is a slow-moving level, so
392
+ * the per-minute average is the representative reading regardless
393
+ * of push count.
394
+ */
395
+ aggregationType: MetricsAggregationType.Avg,
396
+ attributes: {},
397
+ groupByAttributeKey: "device.id",
398
+ }),
399
+ offlineCriteriaInstance: buildIoTOfflineCriteriaInstance({
400
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
401
+ incidentSeverityId: args.defaultIncidentSeverityId,
402
+ alertSeverityId: args.defaultAlertSeverityId,
403
+ monitorName: args.monitorName,
404
+ metricAlias,
405
+ filterType: FilterType.LessThan,
406
+ value: 20,
407
+ incidentTitle: `[IoT] Low Battery (<20%) - ${args.monitorName}`,
408
+ incidentDescription: `An IoT device's battery has dropped below 20%. The device will stop reporting once its battery is exhausted. Check the root cause for the affected device id, then replace or recharge its battery before it dies.`,
409
+ criteriaName: "Low Battery - iot_battery_percent < 20",
410
+ criteriaDescription:
411
+ "Triggers when any device's battery level drops below 20% over the monitoring window.",
412
+ }),
413
+ onlineCriteriaInstance: buildIoTOnlineCriteriaInstance({
414
+ onlineMonitorStatusId: args.onlineMonitorStatusId,
415
+ metricAlias,
416
+ filterType: FilterType.GreaterThanOrEqualTo,
417
+ value: 20,
418
+ }),
419
+ });
420
+ },
421
+ };
422
+
423
+ const weakSignalTemplate: IoTAlertTemplate = {
424
+ id: "iot-weak-signal",
425
+ name: "Weak Signal",
426
+ description:
427
+ "Alert when any IoT device's signal strength drops below -100 dBm (iot_signal_strength_dbm < -100). One incident per device, grouped by device.id.",
428
+ category: "Connectivity",
429
+ severity: "Warning",
430
+ getMonitorStep: (args: IoTAlertTemplateArgs): MonitorStep => {
431
+ const metricAlias: string = "signal_strength";
432
+
433
+ return buildIoTMonitorStep({
434
+ iotMonitor: buildIoTMonitorConfig({
435
+ fleetIdentifier: args.fleetIdentifier,
436
+ metricName: "iot_signal_strength_dbm",
437
+ metricAlias,
438
+ rollingTime: RollingTime.Past5Minutes,
439
+ /*
440
+ * Avg per device — signal strength (dBm) is a level reading, so
441
+ * the per-minute average is the representative value regardless
442
+ * of push count.
443
+ */
444
+ aggregationType: MetricsAggregationType.Avg,
445
+ attributes: {},
446
+ groupByAttributeKey: "device.id",
447
+ }),
448
+ offlineCriteriaInstance: buildIoTOfflineCriteriaInstance({
449
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
450
+ incidentSeverityId: args.defaultIncidentSeverityId,
451
+ alertSeverityId: args.defaultAlertSeverityId,
452
+ monitorName: args.monitorName,
453
+ metricAlias,
454
+ filterType: FilterType.LessThan,
455
+ value: -100,
456
+ incidentTitle: `[IoT] Weak Signal (<-100 dBm) - ${args.monitorName}`,
457
+ incidentDescription: `An IoT device's radio signal strength has dropped below -100 dBm. A weak signal causes dropped telemetry and intermittent connectivity. Check the root cause for the affected device id, then verify its proximity to the gateway, check for interference, or reposition the device or gateway.`,
458
+ criteriaName: "Weak Signal - iot_signal_strength_dbm < -100",
459
+ criteriaDescription:
460
+ "Triggers when any device's signal strength drops below -100 dBm over the monitoring window.",
461
+ }),
462
+ onlineCriteriaInstance: buildIoTOnlineCriteriaInstance({
463
+ onlineMonitorStatusId: args.onlineMonitorStatusId,
464
+ metricAlias,
465
+ filterType: FilterType.GreaterThanOrEqualTo,
466
+ value: -100,
467
+ }),
468
+ });
469
+ },
470
+ };
471
+
472
+ const highTemperatureTemplate: IoTAlertTemplate = {
473
+ id: "iot-high-temperature",
474
+ name: "High Temperature",
475
+ description:
476
+ "Alert when any IoT device's temperature exceeds 70°C (iot_temperature_celsius > 70). One incident per device, grouped by device.id.",
477
+ category: "Environment",
478
+ severity: "Critical",
479
+ getMonitorStep: (args: IoTAlertTemplateArgs): MonitorStep => {
480
+ const metricAlias: string = "temperature_celsius";
481
+
482
+ return buildIoTMonitorStep({
483
+ iotMonitor: buildIoTMonitorConfig({
484
+ fleetIdentifier: args.fleetIdentifier,
485
+ metricName: "iot_temperature_celsius",
486
+ metricAlias,
487
+ rollingTime: RollingTime.Past5Minutes,
488
+ /*
489
+ * Max per device — a single hot reading should trip the threshold
490
+ * instead of being masked by cooler readings in the same minute.
491
+ */
492
+ aggregationType: MetricsAggregationType.Max,
493
+ attributes: {},
494
+ groupByAttributeKey: "device.id",
495
+ }),
496
+ offlineCriteriaInstance: buildIoTOfflineCriteriaInstance({
497
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
498
+ incidentSeverityId: args.defaultIncidentSeverityId,
499
+ alertSeverityId: args.defaultAlertSeverityId,
500
+ monitorName: args.monitorName,
501
+ metricAlias,
502
+ filterType: FilterType.GreaterThan,
503
+ value: 70,
504
+ incidentTitle: `[IoT] High Temperature (>70°C) - ${args.monitorName}`,
505
+ incidentDescription: `An IoT device is reporting a temperature above 70°C. Overheating can damage the device, shorten battery life, and corrupt readings. Check the root cause for the affected device id, then verify ventilation, ambient conditions, and the device's workload.`,
506
+ criteriaName: "High Temperature - iot_temperature_celsius > 70",
507
+ criteriaDescription:
508
+ "Triggers when any device's temperature exceeds 70°C over the monitoring window.",
509
+ }),
510
+ onlineCriteriaInstance: buildIoTOnlineCriteriaInstance({
511
+ onlineMonitorStatusId: args.onlineMonitorStatusId,
512
+ metricAlias,
513
+ filterType: FilterType.LessThanOrEqualTo,
514
+ value: 70,
515
+ }),
516
+ });
517
+ },
518
+ };
519
+
520
+ const highCpuTemplate: IoTAlertTemplate = {
521
+ id: "iot-high-cpu",
522
+ name: "High CPU Usage",
523
+ description:
524
+ "Alert when any IoT device's CPU usage exceeds 90% (iot_cpu_usage_ratio > 0.9). One incident per device, grouped by device.id.",
525
+ category: "System",
526
+ severity: "Warning",
527
+ getMonitorStep: (args: IoTAlertTemplateArgs): MonitorStep => {
528
+ const metricAlias: string = "cpu_usage";
529
+
530
+ return buildIoTMonitorStep({
531
+ iotMonitor: buildIoTMonitorConfig({
532
+ fleetIdentifier: args.fleetIdentifier,
533
+ metricName: "iot_cpu_usage_ratio",
534
+ metricAlias,
535
+ rollingTime: RollingTime.Past5Minutes,
536
+ /*
537
+ * Avg per device — iot_cpu_usage_ratio is already a true 0-1 ratio
538
+ * (one series per device), so the per-minute average is the
539
+ * sustained utilization regardless of push count.
540
+ */
541
+ aggregationType: MetricsAggregationType.Avg,
542
+ attributes: {},
543
+ groupByAttributeKey: "device.id",
544
+ }),
545
+ offlineCriteriaInstance: buildIoTOfflineCriteriaInstance({
546
+ offlineMonitorStatusId: args.offlineMonitorStatusId,
547
+ incidentSeverityId: args.defaultIncidentSeverityId,
548
+ alertSeverityId: args.defaultAlertSeverityId,
549
+ monitorName: args.monitorName,
550
+ metricAlias,
551
+ filterType: FilterType.GreaterThan,
552
+ value: 0.9,
553
+ incidentTitle: `[IoT] High CPU Usage (>90%) - ${args.monitorName}`,
554
+ incidentDescription: `An IoT device's CPU usage has exceeded 90% of its capacity. Sustained high CPU can delay telemetry, drain the battery faster, and cause the device to become unresponsive. Check the root cause for the affected device id, then investigate the workload running on the device.`,
555
+ criteriaName: "High CPU - iot_cpu_usage_ratio > 0.9",
556
+ criteriaDescription:
557
+ "Triggers when any device's average CPU usage ratio exceeds 0.9 over the monitoring window.",
558
+ }),
559
+ onlineCriteriaInstance: buildIoTOnlineCriteriaInstance({
560
+ onlineMonitorStatusId: args.onlineMonitorStatusId,
561
+ metricAlias,
562
+ filterType: FilterType.LessThanOrEqualTo,
563
+ value: 0.9,
564
+ }),
565
+ });
566
+ },
567
+ };
568
+
569
+ export function getAllIoTAlertTemplates(): Array<IoTAlertTemplate> {
570
+ return [
571
+ deviceOfflineTemplate,
572
+ lowBatteryTemplate,
573
+ weakSignalTemplate,
574
+ highTemperatureTemplate,
575
+ highCpuTemplate,
576
+ ];
577
+ }
578
+
579
+ export function getIoTAlertTemplatesByCategory(
580
+ category: IoTAlertTemplateCategory,
581
+ ): Array<IoTAlertTemplate> {
582
+ return getAllIoTAlertTemplates().filter((template: IoTAlertTemplate) => {
583
+ return template.category === category;
584
+ });
585
+ }
586
+
587
+ export function getIoTAlertTemplateById(
588
+ id: string,
589
+ ): IoTAlertTemplate | undefined {
590
+ return getAllIoTAlertTemplates().find((template: IoTAlertTemplate) => {
591
+ return template.id === id;
592
+ });
593
+ }