@oneuptime/common 11.5.11 → 11.5.12

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 (71) hide show
  1. package/Models/AnalyticsModels/NetworkFlow.ts +12 -1
  2. package/Server/API/StatusPageAPI.ts +14 -1
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +0 -2
  4. package/Server/Services/IncidentService.ts +62 -67
  5. package/Server/Services/MonitorGroupService.ts +15 -2
  6. package/Server/Services/MonitorService.ts +91 -3
  7. package/Server/Services/MonitorStatusTimelineService.ts +285 -100
  8. package/Server/Services/StatusPageService.ts +59 -34
  9. package/Server/Utils/Monitor/MonitorResource.ts +43 -14
  10. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +32 -7
  11. package/Server/Utils/Monitor/MonitorStatusTimelineReconciler.ts +362 -0
  12. package/Tests/Server/Services/MonitorStatusTimelineService.test.ts +235 -0
  13. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineReconciler.test.ts +555 -0
  14. package/Tests/Types/Database/CompareOperatorWireSerialization.test.ts +216 -0
  15. package/Tests/Types/Database/GreaterThan.test.ts +5 -1
  16. package/Tests/Types/Database/GreaterThanOrEqual.test.ts +5 -1
  17. package/Tests/Types/Database/GreaterThanOrNull.test.ts +24 -1
  18. package/Tests/Types/Database/LessThan.test.ts +5 -1
  19. package/Tests/Types/Database/LessThanOrEqual.test.ts +24 -1
  20. package/Tests/Types/Database/LessThanOrNull.test.ts +5 -1
  21. package/Tests/Utils/Uptime/UptimeUtil.test.ts +726 -0
  22. package/Types/BaseDatabase/GreaterThan.ts +8 -1
  23. package/Types/BaseDatabase/GreaterThanOrEqual.ts +8 -1
  24. package/Types/BaseDatabase/GreaterThanOrNull.ts +9 -1
  25. package/Types/BaseDatabase/LessThan.ts +8 -1
  26. package/Types/BaseDatabase/LessThanOrEqual.ts +9 -1
  27. package/Types/BaseDatabase/LessThanOrNull.ts +8 -1
  28. package/Utils/StatusPage/ResourceUptime.ts +11 -1
  29. package/Utils/Uptime/UptimeUtil.ts +142 -36
  30. package/build/dist/Models/AnalyticsModels/NetworkFlow.js +12 -1
  31. package/build/dist/Models/AnalyticsModels/NetworkFlow.js.map +1 -1
  32. package/build/dist/Server/API/StatusPageAPI.js +12 -2
  33. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  34. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +0 -2
  35. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  36. package/build/dist/Server/Services/IncidentService.js +47 -56
  37. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  38. package/build/dist/Server/Services/MonitorGroupService.js +15 -2
  39. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  40. package/build/dist/Server/Services/MonitorService.js +66 -3
  41. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  42. package/build/dist/Server/Services/MonitorStatusTimelineService.js +244 -102
  43. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  44. package/build/dist/Server/Services/StatusPageService.js +57 -33
  45. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  46. package/build/dist/Server/Utils/Monitor/MonitorResource.js +34 -12
  47. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  48. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +25 -8
  49. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  50. package/build/dist/Server/Utils/Monitor/MonitorStatusTimelineReconciler.js +268 -0
  51. package/build/dist/Server/Utils/Monitor/MonitorStatusTimelineReconciler.js.map +1 -0
  52. package/build/dist/Types/BaseDatabase/GreaterThan.js +8 -1
  53. package/build/dist/Types/BaseDatabase/GreaterThan.js.map +1 -1
  54. package/build/dist/Types/BaseDatabase/GreaterThanOrEqual.js +8 -1
  55. package/build/dist/Types/BaseDatabase/GreaterThanOrEqual.js.map +1 -1
  56. package/build/dist/Types/BaseDatabase/GreaterThanOrNull.js +9 -1
  57. package/build/dist/Types/BaseDatabase/GreaterThanOrNull.js.map +1 -1
  58. package/build/dist/Types/BaseDatabase/LessThan.js +8 -1
  59. package/build/dist/Types/BaseDatabase/LessThan.js.map +1 -1
  60. package/build/dist/Types/BaseDatabase/LessThanOrEqual.js +9 -1
  61. package/build/dist/Types/BaseDatabase/LessThanOrEqual.js.map +1 -1
  62. package/build/dist/Types/BaseDatabase/LessThanOrNull.js +8 -1
  63. package/build/dist/Types/BaseDatabase/LessThanOrNull.js.map +1 -1
  64. package/build/dist/Utils/StatusPage/ResourceUptime.js +4 -1
  65. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  66. package/build/dist/Utils/Uptime/UptimeUtil.js +86 -33
  67. package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
  68. package/package.json +1 -1
  69. package/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.ts +0 -101
  70. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js +0 -40
  71. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js.map +0 -1
@@ -285,8 +285,19 @@ export default class NetworkFlow extends AnalyticsBaseModel {
285
285
  * (a retentionDate column computed at ingest, like Log/Metric) is
286
286
  * the phase-2 follow-up. Keyed on server-assigned ingestedAt so a
287
287
  * device with a wrong clock cannot make rows expire early.
288
+ *
289
+ * toDateTime() is NOT redundant: ingestedAt is DateTime64(9), so
290
+ * `ingestedAt + INTERVAL 30 DAY` stays DateTime64(9), and a TTL
291
+ * expression must evaluate to Date or DateTime. ClickHouse only began
292
+ * accepting DateTime64 there in 25.x; on 24.x it fails the CREATE with
293
+ * BAD_TTL_EXPRESSION, which aborts the whole boot schema-sync (it has
294
+ * no per-table error handling) and takes every table registered after
295
+ * this one down with it. The Helm chart ships `tag: latest` but tells
296
+ * operators to pin for production, so older servers are supported and
297
+ * this must stay version-portable. Narrowing to second precision is
298
+ * lossless for a 30-day retention window.
288
299
  */
289
- ttlExpression: "ingestedAt + INTERVAL 30 DAY DELETE",
300
+ ttlExpression: "toDateTime(ingestedAt) + INTERVAL 30 DAY DELETE",
290
301
  defaultSortColumn: "flowStartAt",
291
302
  });
292
303
  }
@@ -88,7 +88,7 @@ import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
88
88
  import StatusPageResourceUptimeUtil from "../../Utils/StatusPage/ResourceUptime";
89
89
  import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
90
90
  import { Green } from "../../Types/BrandColors";
91
- import UptimeUtil from "../../Utils/Uptime/UptimeUtil";
91
+ import UptimeUtil, { UptimeWindow } from "../../Utils/Uptime/UptimeUtil";
92
92
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
93
93
  import URL from "../../Types/API/URL";
94
94
  import SMS from "../../Types/SMS/SMS";
@@ -1265,6 +1265,17 @@ export default class StatusPageAPI extends BaseAPI<
1265
1265
  const downtimeMonitorStatuses: Array<MonitorStatus> =
1266
1266
  statusPage.downtimeMonitorStatuses || [];
1267
1267
 
1268
+ /*
1269
+ * this endpoint reports uptime over an explicit [startDate, endDate] range, so events
1270
+ * have to be clipped to it and the denominator has to be the range itself. Without
1271
+ * this an open (endsAt = null) row that started before the range contributes its
1272
+ * entire duration to the downtime total.
1273
+ */
1274
+ const uptimeWindow: UptimeWindow = {
1275
+ startDate: startDate,
1276
+ endDate: endDate,
1277
+ };
1278
+
1268
1279
  type ResourceUptime = {
1269
1280
  statusPageResourceId: ObjectID;
1270
1281
  uptimePercent: number | null;
@@ -1369,6 +1380,7 @@ export default class StatusPageAPI extends BaseAPI<
1369
1380
  resourceStatusTimelines,
1370
1381
  precision,
1371
1382
  downtimeMonitorStatuses,
1383
+ uptimeWindow,
1372
1384
  );
1373
1385
 
1374
1386
  resourceUptime.uptimePercent = uptimePercent;
@@ -1419,6 +1431,7 @@ export default class StatusPageAPI extends BaseAPI<
1419
1431
  resourceStatusTimelines,
1420
1432
  precision,
1421
1433
  downtimeMonitorStatuses,
1434
+ uptimeWindow,
1422
1435
  );
1423
1436
 
1424
1437
  resourceUptime.uptimePercent = uptimePercent;
@@ -461,7 +461,6 @@ import { AddNetworkDeviceInventoryAndDiscoverySchedule1784211212164 } from "./17
461
461
  import { AddEnterpriseLicenseNotificationColumns1784218257664 } from "./1784218257664-AddEnterpriseLicenseNotificationColumns";
462
462
  import { AddAttributionColumnsToUserAndProject1784293516000 } from "./1784293516000-AddAttributionColumnsToUserAndProject";
463
463
  import { AddMarketingConversionTable1784298000000 } from "./1784298000000-AddMarketingConversionTable";
464
- import { MigrationName1784401962564 } from "./1784401962564-MigrationName";
465
464
 
466
465
  export default [
467
466
  InitialMigration,
@@ -927,5 +926,4 @@ export default [
927
926
  AddEnterpriseLicenseNotificationColumns1784218257664,
928
927
  AddAttributionColumnsToUserAndProject1784293516000,
929
928
  AddMarketingConversionTable1784298000000,
930
- MigrationName1784401962564,
931
929
  ];
@@ -15,7 +15,6 @@ import IncidentStateService from "./IncidentStateService";
15
15
  import IncidentStateTimelineService from "./IncidentStateTimelineService";
16
16
  import MonitorService from "./MonitorService";
17
17
  import MonitorStatusService from "./MonitorStatusService";
18
- import MonitorStatusTimelineService from "./MonitorStatusTimelineService";
19
18
  import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
20
19
  import TeamMemberService from "./TeamMemberService";
21
20
  import UserService from "./UserService";
@@ -43,7 +42,6 @@ import IncidentState from "../../Models/DatabaseModels/IncidentState";
43
42
  import IncidentStateTimeline from "../../Models/DatabaseModels/IncidentStateTimeline";
44
43
  import Monitor from "../../Models/DatabaseModels/Monitor";
45
44
  import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
46
- import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
47
45
  import User from "../../Models/DatabaseModels/User";
48
46
  import { IsBillingEnabled } from "../EnvironmentConfig";
49
47
  import MutableMetricService from "./MutableMetricService";
@@ -2538,84 +2536,81 @@ ${incidentSeverity.name}
2538
2536
 
2539
2537
  if (resolvedMonitorState) {
2540
2538
  for (const monitor of monitors) {
2541
- //check state of the monitor.
2542
-
2543
- const doesMonitorHasMoreActiveManualIncidents: boolean =
2544
- await this.doesMonitorHasMoreActiveManualIncidents(
2545
- monitor.id!,
2546
- projectId!,
2547
- );
2548
-
2549
- if (doesMonitorHasMoreActiveManualIncidents) {
2550
- continue;
2551
- }
2552
-
2553
- await MonitorService.updateOneById({
2554
- id: monitor.id!,
2555
- data: {
2556
- disableActiveMonitoringBecauseOfManualIncident: false,
2557
- },
2558
- props: {
2559
- isRoot: true,
2560
- },
2561
- });
2562
-
2563
2539
  /*
2564
- * Don't flip the monitor to operational while other incidents
2565
- * are still open on it e.g. a metric monitor with group-by
2566
- * may have one incident per series, and resolving one series
2567
- * shouldn't claim the whole monitor is healthy.
2540
+ * Per-monitor isolation: one monitor failing here (for example the
2541
+ * fail-closed status timeline lock, or a transient DB error) must not
2542
+ * abort the loop and strand the REMAINING monitors with
2543
+ * disableActiveMonitoringBecauseOfManualIncident still true - a
2544
+ * monitor left in that state is skipped by probes and sits in its
2545
+ * down status indefinitely, silently accruing downtime.
2568
2546
  */
2569
- const hasOtherActiveIncidents: boolean =
2570
- await this.doesMonitorHaveActiveIncidents(monitor.id!, projectId!);
2547
+ try {
2548
+ //check state of the monitor.
2571
2549
 
2572
- if (hasOtherActiveIncidents) {
2573
- continue;
2574
- }
2550
+ const doesMonitorHasMoreActiveManualIncidents: boolean =
2551
+ await this.doesMonitorHasMoreActiveManualIncidents(
2552
+ monitor.id!,
2553
+ projectId!,
2554
+ );
2575
2555
 
2576
- const latestState: MonitorStatusTimeline | null =
2577
- await MonitorStatusTimelineService.findOneBy({
2578
- query: {
2579
- monitorId: monitor.id!,
2580
- projectId: projectId!,
2581
- },
2582
- select: {
2583
- _id: true,
2584
- monitorStatusId: true,
2556
+ if (doesMonitorHasMoreActiveManualIncidents) {
2557
+ continue;
2558
+ }
2559
+
2560
+ await MonitorService.updateOneById({
2561
+ id: monitor.id!,
2562
+ data: {
2563
+ disableActiveMonitoringBecauseOfManualIncident: false,
2585
2564
  },
2586
2565
  props: {
2587
2566
  isRoot: true,
2588
2567
  },
2589
- sort: {
2590
- startsAt: SortOrder.Descending,
2591
- },
2592
2568
  });
2593
2569
 
2594
- if (
2595
- latestState &&
2596
- latestState.monitorStatusId?.toString() ===
2597
- resolvedMonitorState.id!.toString()
2598
- ) {
2599
- // already on this state. Skip.
2600
- continue;
2601
- }
2570
+ /*
2571
+ * Don't flip the monitor to operational while other incidents
2572
+ * are still open on it — e.g. a metric monitor with group-by
2573
+ * may have one incident per series, and resolving one series
2574
+ * shouldn't claim the whole monitor is healthy.
2575
+ */
2576
+ const hasOtherActiveIncidents: boolean =
2577
+ await this.doesMonitorHaveActiveIncidents(
2578
+ monitor.id!,
2579
+ projectId!,
2580
+ );
2602
2581
 
2603
- const monitorStatusTimeline: MonitorStatusTimeline =
2604
- new MonitorStatusTimeline();
2605
- monitorStatusTimeline.monitorId = monitor.id!;
2606
- monitorStatusTimeline.projectId = projectId!;
2607
- monitorStatusTimeline.monitorStatusId = resolvedMonitorState.id!;
2582
+ if (hasOtherActiveIncidents) {
2583
+ continue;
2584
+ }
2608
2585
 
2609
- if (startsAt) {
2610
- monitorStatusTimeline.startsAt = startsAt;
2586
+ /*
2587
+ * changeMonitorStatus performs the same latest-status dedupe check
2588
+ * this loop used to do inline, and additionally absorbs the two
2589
+ * recoverable error classes (status already set by a concurrent
2590
+ * writer, lock not acquired after retries) by skipping the write,
2591
+ * so the common failure modes never even reach the catch below.
2592
+ * notifyOwners is true to preserve the previous behavior here: the
2593
+ * created row had isOwnerNotified unset, so owners were notified.
2594
+ */
2595
+ await MonitorService.changeMonitorStatus(
2596
+ projectId!,
2597
+ [monitor.id!],
2598
+ resolvedMonitorState.id!,
2599
+ true, // notifyOwners - matches the pre-existing behavior of this loop.
2600
+ undefined,
2601
+ undefined,
2602
+ {
2603
+ isRoot: true,
2604
+ },
2605
+ startsAt,
2606
+ );
2607
+ } catch (err) {
2608
+ logger.error(
2609
+ `IncidentService.markMonitorsActiveForMonitoring: failed for monitor ${monitor.id?.toString()}; continuing with the remaining monitors.`,
2610
+ );
2611
+ logger.error(err);
2612
+ continue;
2611
2613
  }
2612
-
2613
- await MonitorStatusTimelineService.create({
2614
- data: monitorStatusTimeline,
2615
- props: {
2616
- isRoot: true,
2617
- },
2618
- });
2619
2614
  }
2620
2615
  }
2621
2616
  }
@@ -93,7 +93,16 @@ export class Service extends DatabaseService<MonitorGroup> {
93
93
  },
94
94
  ),
95
95
  ),
96
- createdAt: QueryHelper.inBetween(startDate, endDate),
96
+ /*
97
+ * Select every row that OVERLAPS [startDate, endDate] - it started on or before the
98
+ * window ends, and it either ended on or after the window started or is still open
99
+ * (endsAt IS NULL). Filtering by createdAt-in-window missed rows that started before
100
+ * the window but carry downtime into it (e.g. a monitor Offline since before the
101
+ * window rendered as 100% uptime on the group page). Same predicate as
102
+ * StatusPageService.getMonitorStatusTimelineForStatusPage.
103
+ */
104
+ startsAt: QueryHelper.lessThanEqualTo(endDate),
105
+ endsAt: QueryHelper.greaterThanEqualToOrNull(startDate),
97
106
  },
98
107
  select: {
99
108
  createdAt: true,
@@ -108,7 +117,11 @@ export class Service extends DatabaseService<MonitorGroup> {
108
117
  } as any,
109
118
  },
110
119
  sort: {
111
- createdAt: SortOrder.Ascending,
120
+ /*
121
+ * startsAt, not createdAt: they are different clocks (DB now() vs worker moment())
122
+ * with real skew, and startsAt is the order the timeline math relies on.
123
+ */
124
+ startsAt: SortOrder.Ascending,
112
125
  },
113
126
  skip: 0,
114
127
  limit: LIMIT_MAX, // This can be optimized.
@@ -14,7 +14,12 @@ import MonitorOwnerTeamService from "./MonitorOwnerTeamService";
14
14
  import MonitorOwnerUserService from "./MonitorOwnerUserService";
15
15
  import MonitorProbeService from "./MonitorProbeService";
16
16
  import MonitorStatusService from "./MonitorStatusService";
17
- import MonitorStatusTimelineService from "./MonitorStatusTimelineService";
17
+ import MonitorStatusTimelineService, {
18
+ MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE,
19
+ MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE,
20
+ } from "./MonitorStatusTimelineService";
21
+ import ServerException from "../../Types/Exception/ServerException";
22
+ import Sleep from "../../Types/Sleep";
18
23
  import ProbeService from "./ProbeService";
19
24
  import ProjectService, { CurrentPlan } from "./ProjectService";
20
25
  import TeamMemberService from "./TeamMemberService";
@@ -1733,13 +1738,96 @@ ${createdItem.description?.trim() || "No description provided."}
1733
1738
  statusTimeline.startsAt = startsAt;
1734
1739
  }
1735
1740
 
1736
- await MonitorStatusTimelineService.create({
1737
- data: statusTimeline,
1741
+ await this.createStatusTimelineWithRetry({
1742
+ statusTimeline: statusTimeline,
1738
1743
  props: props,
1744
+ projectId: projectId,
1745
+ monitorId: monitorId,
1739
1746
  });
1740
1747
  }
1741
1748
  }
1742
1749
 
1750
+ /*
1751
+ * Creates one status timeline row, absorbing the two error classes that are
1752
+ * recoverable per monitor so one monitor cannot abort a caller's loop over
1753
+ * many (incident resolve, scheduled maintenance end, incident create with
1754
+ * changeMonitorStatusTo):
1755
+ *
1756
+ * - "same as previous" from onBeforeCreate's dedupe check: a concurrent
1757
+ * writer (or an earlier backfilled row at this startsAt) already put the
1758
+ * monitor in this status, so the desired state holds. Idempotent no-op.
1759
+ *
1760
+ * - the fail-closed lock error from MonitorStatusTimelineService.create():
1761
+ * retried a few times with a short delay, because the common causes (a
1762
+ * transient Redis blip, or a concurrent writer holding the per-monitor
1763
+ * mutex) clear quickly. This retry is also what protects the INITIAL
1764
+ * status row written from MonitorService.onCreateSuccess - without it a
1765
+ * lock failure at monitor-create time leaves a Manual monitor with no
1766
+ * timeline forever, since Manual monitors are never probed and so never
1767
+ * self-heal. If all attempts fail, log and continue: for probed monitors
1768
+ * the next probe result recreates the transition, and failing the caller
1769
+ * outright would strand its remaining monitors instead.
1770
+ *
1771
+ * Every other error still propagates.
1772
+ */
1773
+ private async createStatusTimelineWithRetry(data: {
1774
+ statusTimeline: MonitorStatusTimeline;
1775
+ props: DatabaseCommonInteractionProps;
1776
+ projectId: ObjectID;
1777
+ monitorId: ObjectID;
1778
+ }): Promise<void> {
1779
+ const maxAttempts: number = 3;
1780
+ const retryDelayInMs: number = 2000;
1781
+
1782
+ const logAttributes: LogAttributes = {
1783
+ projectId: data.projectId.toString(),
1784
+ monitorId: data.monitorId.toString(),
1785
+ } as LogAttributes;
1786
+
1787
+ for (let attempt: number = 1; attempt <= maxAttempts; attempt++) {
1788
+ try {
1789
+ await MonitorStatusTimelineService.create({
1790
+ data: data.statusTimeline,
1791
+ props: data.props,
1792
+ });
1793
+ return;
1794
+ } catch (err) {
1795
+ if (
1796
+ err instanceof BadDataException &&
1797
+ err.message === MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE
1798
+ ) {
1799
+ logger.debug(
1800
+ `changeMonitorStatus: monitor ${data.monitorId.toString()} is already in the requested status; skipping duplicate status timeline.`,
1801
+ );
1802
+ return;
1803
+ }
1804
+
1805
+ const isLockError: boolean =
1806
+ err instanceof ServerException &&
1807
+ err.message === MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE;
1808
+
1809
+ if (isLockError && attempt < maxAttempts) {
1810
+ logger.warn(
1811
+ `changeMonitorStatus: could not acquire the status timeline lock for monitor ${data.monitorId.toString()} (attempt ${attempt} of ${maxAttempts}); retrying.`,
1812
+ logAttributes,
1813
+ );
1814
+ await Sleep.sleep(retryDelayInMs);
1815
+ continue;
1816
+ }
1817
+
1818
+ if (isLockError) {
1819
+ logger.error(
1820
+ `changeMonitorStatus: could not acquire the status timeline lock for monitor ${data.monitorId.toString()} after ${maxAttempts} attempt(s); skipping this status change. The monitor keeps its current status.`,
1821
+ logAttributes,
1822
+ );
1823
+ return;
1824
+ }
1825
+
1826
+ throw err;
1827
+ }
1828
+ }
1829
+ }
1830
+
1743
1831
  @CaptureSpan()
1744
1832
  public async getWorkspaceChannelForMonitor(data: {
1745
1833
  monitorId: ObjectID;