@oneuptime/common 11.7.0 → 11.7.2

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 (264) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +1 -0
  2. package/Models/DatabaseModels/Incident.ts +2 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  4. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  5. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  6. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/Monitor.ts +1 -0
  9. package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
  10. package/Models/DatabaseModels/NetworkSite.ts +1 -0
  11. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
  12. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
  14. package/Models/DatabaseModels/Probe.ts +13 -13
  15. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  17. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  18. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
  19. package/Models/DatabaseModels/StatusPage.ts +1 -0
  20. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  21. package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
  22. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
  23. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
  24. package/Server/API/BaseAPI.ts +17 -1
  25. package/Server/API/CommonAPI.ts +32 -0
  26. package/Server/API/MicrosoftTeamsAPI.ts +99 -2
  27. package/Server/API/ProbeAPI.ts +2 -0
  28. package/Server/API/SlackAPI.ts +62 -0
  29. package/Server/API/StatusPageAPI.ts +157 -192
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  34. package/Server/Services/DatabaseService.ts +11 -3
  35. package/Server/Services/MonitorGroupService.ts +145 -0
  36. package/Server/Services/MonitorService.ts +231 -30
  37. package/Server/Services/ProbeService.ts +319 -16
  38. package/Server/Services/StatusPageService.ts +72 -42
  39. package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
  40. package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
  41. package/Server/Utils/Monitor/MonitorResource.ts +88 -35
  42. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
  43. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
  44. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
  45. package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
  46. package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
  47. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
  48. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
  49. package/Tests/Server/API/ProbeAPI.test.ts +6 -0
  50. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
  51. package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
  52. package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
  53. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
  54. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
  55. package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
  56. package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
  57. package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
  58. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
  59. package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
  60. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
  61. package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
  62. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
  63. package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
  64. package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
  65. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
  66. package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
  67. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
  68. package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
  69. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
  70. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
  71. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
  72. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
  73. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
  74. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
  75. package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
  76. package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
  77. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
  78. package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
  79. package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
  80. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
  81. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
  82. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
  84. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
  85. package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
  86. package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
  87. package/Tests/Types/DateUserTimezone.test.ts +205 -0
  88. package/Tests/Types/DateUserTimezoneOverridesBrowser.test.ts +73 -0
  89. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
  90. package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
  91. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
  92. package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
  93. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
  94. package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
  95. package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
  96. package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
  97. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
  98. package/Tests/UI/Components/TimePicker/TimePicker.test.tsx +11 -0
  99. package/Tests/UI/Utils/Project.test.ts +809 -0
  100. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
  101. package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
  102. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
  103. package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
  104. package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
  105. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
  106. package/Types/Date.ts +119 -50
  107. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
  108. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
  109. package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
  110. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
  111. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  112. package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
  113. package/UI/Components/Forms/Fields/FormField.tsx +13 -3
  114. package/UI/Components/Forms/ModelForm.tsx +41 -18
  115. package/UI/Components/Input/Input.tsx +6 -1
  116. package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
  117. package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
  118. package/UI/Components/ModelTable/BaseModelTable.tsx +300 -30
  119. package/UI/Components/ModelTable/Column.ts +12 -0
  120. package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
  121. package/UI/Components/Navbar/NavBar.tsx +66 -1
  122. package/UI/Components/Table/Table.tsx +52 -13
  123. package/UI/Components/Table/Types/Column.ts +18 -0
  124. package/UI/Components/TimePicker/TimePicker.tsx +8 -4
  125. package/UI/Utils/Project.ts +214 -18
  126. package/UI/Utils/TableColumnsToCsv.ts +86 -6
  127. package/UI/Utils/User.ts +19 -0
  128. package/UI/esbuild-config.js +34 -0
  129. package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
  130. package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
  131. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  133. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  134. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  135. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  137. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  139. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  140. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  141. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  142. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  143. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  144. package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
  145. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  146. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
  147. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  148. package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
  149. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  150. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
  151. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
  152. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  153. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  154. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
  155. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  156. package/build/dist/Models/DatabaseModels/Probe.js +13 -13
  157. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  159. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  161. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  163. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  164. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
  165. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  166. package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
  167. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  168. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  169. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  170. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
  171. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  172. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
  173. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
  174. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  175. package/build/dist/Server/API/BaseAPI.js +12 -1
  176. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  177. package/build/dist/Server/API/CommonAPI.js +21 -0
  178. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  179. package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
  180. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  181. package/build/dist/Server/API/ProbeAPI.js +2 -0
  182. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  183. package/build/dist/Server/API/SlackAPI.js +44 -0
  184. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  185. package/build/dist/Server/API/StatusPageAPI.js +98 -152
  186. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
  190. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
  191. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
  192. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
  193. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  194. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  195. package/build/dist/Server/Services/DatabaseService.js +10 -2
  196. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  197. package/build/dist/Server/Services/MonitorGroupService.js +118 -0
  198. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  199. package/build/dist/Server/Services/MonitorService.js +198 -24
  200. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  201. package/build/dist/Server/Services/ProbeService.js +238 -9
  202. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  203. package/build/dist/Server/Services/StatusPageService.js +46 -39
  204. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  205. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
  206. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  207. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
  208. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
  209. package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
  210. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  211. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
  212. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  213. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
  214. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
  215. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
  216. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  217. package/build/dist/Types/Date.js +106 -35
  218. package/build/dist/Types/Date.js.map +1 -1
  219. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
  220. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  221. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
  222. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  223. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
  224. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  225. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
  226. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
  227. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -3
  228. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  229. package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
  230. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  231. package/build/dist/UI/Components/Input/Input.js +6 -1
  232. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  233. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
  234. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  235. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
  236. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  237. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +234 -21
  238. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  239. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
  240. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
  241. package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
  242. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  243. package/build/dist/UI/Components/Table/Table.js +31 -11
  244. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  245. package/build/dist/UI/Components/TimePicker/TimePicker.js +4 -4
  246. package/build/dist/UI/Components/TimePicker/TimePicker.js.map +1 -1
  247. package/build/dist/UI/Utils/Project.js +161 -15
  248. package/build/dist/UI/Utils/Project.js.map +1 -1
  249. package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
  250. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
  251. package/build/dist/UI/Utils/User.js +19 -0
  252. package/build/dist/UI/Utils/User.js.map +1 -1
  253. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
  254. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
  255. package/package.json +4 -3
  256. package/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.ts +0 -79
  257. package/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.ts +0 -41
  258. package/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.ts +0 -17
  259. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js +0 -34
  260. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js.map +0 -1
  261. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js +0 -22
  262. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js.map +0 -1
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js +0 -12
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js.map +0 -1
@@ -1,6 +1,7 @@
1
+ import crypto from "crypto";
1
2
  import User from "../../Models/DatabaseModels/User";
2
3
  import CreateBy from "../Types/Database/CreateBy";
3
- import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
4
+ import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
4
5
  import DatabaseService from "./DatabaseService";
5
6
  import ObjectID from "../../Types/ObjectID";
6
7
  import Version from "../../Types/Version";
@@ -36,11 +37,253 @@ import GlobalCache from "../Infrastructure/GlobalCache";
36
37
  import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
37
38
  import { WhatsAppMessagePayload } from "../../Types/WhatsApp/WhatsAppMessage";
38
39
 
40
+ /*
41
+ * Cache namespace for the probe heartbeat throttle: holds the wall-clock of
42
+ * the last attempted lastAlive write per probe, so at most one Postgres
43
+ * write happens per probe per 30 seconds no matter how many requests the
44
+ * probe sends.
45
+ */
46
+ const PROBE_LAST_ALIVE_CACHE_NAMESPACE: string = "probe-last-alive";
47
+
48
+ /*
49
+ * Cache namespace for probe authentication: probeId → SHA-256 of the
50
+ * probe's CURRENT key, written only after the key was verified against the
51
+ * database. Lets the probe-auth middleware skip its per-request Probe
52
+ * SELECT — the exact query that queues behind a starved connection pool and
53
+ * stalls heartbeats. Entries are deleted whenever a probe's key changes or
54
+ * the probe is deleted (see onUpdateSuccess / onDeleteSuccess below), and
55
+ * the TTL bounds the exposure if that invalidation is ever missed.
56
+ */
57
+ const PROBE_AUTH_KEY_HASH_CACHE_NAMESPACE: string = "probe-auth-key-hash";
58
+ const PROBE_AUTH_KEY_HASH_CACHE_TTL_IN_SECONDS: number = 120;
59
+
60
+ /*
61
+ * Revocation sentinel. Invalidation WRITES this value (with the normal TTL)
62
+ * instead of deleting the key: a bare DELETE can be silently undone by an
63
+ * in-flight verification that DB-checked the OLD key just before a rotation
64
+ * committed and then re-cached it just after the delete ran. The sentinel —
65
+ * combined with verifyProbeKey's write-only-if-unchanged re-read below —
66
+ * makes the straggler skip its write, so invalidation wins the race. It can
67
+ * never equal a real SHA-256 hex digest, so on the auth path it always
68
+ * behaves as a cache miss.
69
+ */
70
+ const PROBE_AUTH_KEY_REVOKED_SENTINEL: string = "revoked";
71
+
72
+ /*
73
+ * Upper bound for a single Redis cache operation on the probe request path.
74
+ * The ioredis client has no command timeout, so a stalled-but-open Redis
75
+ * socket would otherwise hang the probe-auth middleware forever with no
76
+ * error at all. Cache is an optimization here — on timeout we fall back to
77
+ * the database, exactly as we do when Redis rejects.
78
+ */
79
+ const PROBE_CACHE_OPERATION_TIMEOUT_IN_MS: number = 2000;
80
+
39
81
  export class Service extends DatabaseService<Model> {
40
82
  public constructor() {
41
83
  super(Model);
42
84
  }
43
85
 
86
+ /*
87
+ * Bounds a cache operation in time. Promise.race subscribes to both
88
+ * promises, so a late settlement of the losing cache operation is
89
+ * observed and can never surface as an unhandled rejection.
90
+ */
91
+ private async boundedCacheOperation<T>(
92
+ operationName: string,
93
+ operation: Promise<T>,
94
+ ): Promise<T> {
95
+ let timer: ReturnType<typeof setTimeout> | undefined = undefined;
96
+
97
+ const timeout: Promise<never> = new Promise<never>(
98
+ (_resolve: (value: never) => void, reject: (err: Error) => void) => {
99
+ timer = setTimeout(() => {
100
+ reject(
101
+ new Error(
102
+ `${operationName} timed out after ${PROBE_CACHE_OPERATION_TIMEOUT_IN_MS}ms`,
103
+ ),
104
+ );
105
+ }, PROBE_CACHE_OPERATION_TIMEOUT_IN_MS);
106
+ },
107
+ );
108
+
109
+ try {
110
+ return await Promise.race([operation, timeout]);
111
+ } finally {
112
+ if (timer) {
113
+ clearTimeout(timer);
114
+ }
115
+ }
116
+ }
117
+
118
+ public hashProbeKey(probeKey: string): string {
119
+ return crypto.createHash("sha256").update(probeKey).digest("hex");
120
+ }
121
+
122
+ /*
123
+ * Verifies a probeId/probeKey pair, serving repeat verifications from
124
+ * cache. This runs on EVERY authenticated probe request (heartbeats,
125
+ * monitor list fetches, result ingest), so it must stay responsive even
126
+ * when the Postgres pool is saturated — which is exactly when the
127
+ * heartbeat matters most: if this check queues behind a starved pool, the
128
+ * probe's lastAlive goes stale and a healthy probe gets flagged
129
+ * Disconnected.
130
+ *
131
+ * Security invariants:
132
+ * - The cache stores only a SHA-256 hash of the key, never the key.
133
+ * - A cache entry is only ever written from a value that was JUST
134
+ * verified against the database.
135
+ * - A cache mismatch falls through to the database, so a rotated key
136
+ * works immediately.
137
+ * - Rotating or deleting a probe overwrites its cache entry cluster-wide
138
+ * with the revocation sentinel (GlobalCache is Redis-backed), and the
139
+ * write below is write-only-if-unchanged so an in-flight verification
140
+ * straddling the rotation cannot resurrect the old hash. The TTL caps
141
+ * the stale window if the invalidation write itself fails: a REVOKED
142
+ * key can then still authenticate for at most
143
+ * PROBE_AUTH_KEY_HASH_CACHE_TTL_IN_SECONDS.
144
+ * - Any cache failure (Redis down, slow, timeout) falls back to the
145
+ * database check — never open.
146
+ */
147
+ public async verifyProbeKey(data: {
148
+ probeId: ObjectID;
149
+ probeKey: string;
150
+ }): Promise<boolean> {
151
+ if (!data.probeId) {
152
+ throw new BadDataException("probeId is required");
153
+ }
154
+
155
+ if (!data.probeKey) {
156
+ throw new BadDataException("probeKey is required");
157
+ }
158
+
159
+ const presentedKeyHash: string = this.hashProbeKey(data.probeKey);
160
+
161
+ try {
162
+ const cachedKeyHash: string | null = await this.boundedCacheOperation(
163
+ "Probe auth cache read",
164
+ GlobalCache.getString(
165
+ PROBE_AUTH_KEY_HASH_CACHE_NAMESPACE,
166
+ data.probeId.toString(),
167
+ ),
168
+ );
169
+
170
+ if (cachedKeyHash && cachedKeyHash === presentedKeyHash) {
171
+ return true;
172
+ }
173
+ } catch (err) {
174
+ logger.error("Error in reading probe auth cache", {
175
+ probeId: data.probeId?.toString(),
176
+ } as LogAttributes);
177
+ logger.error(err, {
178
+ probeId: data.probeId?.toString(),
179
+ } as LogAttributes);
180
+ }
181
+
182
+ // Cache miss, mismatch (e.g. key rotated), or cache failure: ask the DB.
183
+ const probe: Model | null = await this.findOneBy({
184
+ query: {
185
+ _id: data.probeId.toString(),
186
+ key: data.probeKey,
187
+ },
188
+ select: {
189
+ _id: true,
190
+ },
191
+ props: {
192
+ isRoot: true,
193
+ },
194
+ });
195
+
196
+ if (!probe) {
197
+ return false;
198
+ }
199
+
200
+ try {
201
+ /*
202
+ * Write-only-if-unchanged: re-read the entry right before writing and
203
+ * skip the write when someone else changed it since our first read.
204
+ * The DB verification above can straddle a key rotation — its SELECT
205
+ * resolves against the pre-rotation row, the rotation commits and
206
+ * invalidates, and an unconditional write here would then re-cache
207
+ * the just-revoked key for a fresh TTL. Seeing the revocation
208
+ * sentinel (or any other concurrent write) means skip: the DB check
209
+ * stays the source of truth and caching resumes once the entry
210
+ * expires. The remaining race window is the microseconds between
211
+ * this re-read and the write — down from the full DB round-trip.
212
+ */
213
+ const currentCacheValue: string | null = await this.boundedCacheOperation(
214
+ "Probe auth cache pre-write read",
215
+ GlobalCache.getString(
216
+ PROBE_AUTH_KEY_HASH_CACHE_NAMESPACE,
217
+ data.probeId.toString(),
218
+ ),
219
+ );
220
+
221
+ if (
222
+ currentCacheValue === null ||
223
+ currentCacheValue === presentedKeyHash
224
+ ) {
225
+ await this.boundedCacheOperation(
226
+ "Probe auth cache write",
227
+ GlobalCache.setString(
228
+ PROBE_AUTH_KEY_HASH_CACHE_NAMESPACE,
229
+ data.probeId.toString(),
230
+ presentedKeyHash,
231
+ {
232
+ expiresInSeconds: PROBE_AUTH_KEY_HASH_CACHE_TTL_IN_SECONDS,
233
+ },
234
+ ),
235
+ );
236
+ }
237
+ } catch (err) {
238
+ // Next request re-verifies against the DB. Nothing is broken.
239
+ logger.error("Error in writing probe auth cache", {
240
+ probeId: data.probeId?.toString(),
241
+ } as LogAttributes);
242
+ logger.error(err, {
243
+ probeId: data.probeId?.toString(),
244
+ } as LogAttributes);
245
+ }
246
+
247
+ return true;
248
+ }
249
+
250
+ /*
251
+ * Replaces cached auth entries with the revocation sentinel so a rotated
252
+ * key stops working everywhere immediately (the cache is Redis-backed and
253
+ * shared by all pods). A sentinel WRITE rather than a delete: a deleted
254
+ * key can be silently re-created by an in-flight verification that
255
+ * DB-checked the old key just before the rotation committed, whereas the
256
+ * sentinel makes that straggler's write-only-if-unchanged check fail.
257
+ * Errors are logged loudly but not thrown: the entries also expire via
258
+ * TTL, and failing the surrounding update/delete over a cache hiccup
259
+ * would be worse than a bounded stale window.
260
+ */
261
+ public async invalidateProbeAuthCache(
262
+ probeIds: Array<ObjectID>,
263
+ ): Promise<void> {
264
+ for (const probeId of probeIds) {
265
+ try {
266
+ await this.boundedCacheOperation(
267
+ "Probe auth cache invalidation",
268
+ GlobalCache.setString(
269
+ PROBE_AUTH_KEY_HASH_CACHE_NAMESPACE,
270
+ probeId.toString(),
271
+ PROBE_AUTH_KEY_REVOKED_SENTINEL,
272
+ {
273
+ expiresInSeconds: PROBE_AUTH_KEY_HASH_CACHE_TTL_IN_SECONDS,
274
+ },
275
+ ),
276
+ );
277
+ } catch (err) {
278
+ logger.error(
279
+ `CRITICAL: failed to invalidate probe auth cache for probe ${probeId.toString()} — a rotated/revoked probe key may keep authenticating for up to ${PROBE_AUTH_KEY_HASH_CACHE_TTL_IN_SECONDS}s`,
280
+ { probeId: probeId?.toString() } as LogAttributes,
281
+ );
282
+ logger.error(err, { probeId: probeId?.toString() } as LogAttributes);
283
+ }
284
+ }
285
+ }
286
+
44
287
  public async saveLastAliveInCache(
45
288
  probeId: ObjectID,
46
289
  lastAlive: Date,
@@ -50,10 +293,13 @@ export class Service extends DatabaseService<Model> {
50
293
  }
51
294
 
52
295
  try {
53
- await GlobalCache.setString(
54
- "probe-last-alive",
55
- probeId.toString(),
56
- OneUptimeDate.toString(lastAlive),
296
+ await this.boundedCacheOperation(
297
+ "Probe last-alive cache write",
298
+ GlobalCache.setString(
299
+ PROBE_LAST_ALIVE_CACHE_NAMESPACE,
300
+ probeId.toString(),
301
+ OneUptimeDate.toString(lastAlive),
302
+ ),
57
303
  );
58
304
  } catch (err) {
59
305
  logger.error("Error in saving last alive in cache", {
@@ -68,10 +314,14 @@ export class Service extends DatabaseService<Model> {
68
314
 
69
315
  try {
70
316
  // before we hit the database, we need to check if the lastAlive was updated in Global Cache.
71
- const previousLastAliveCheck: string | null = await GlobalCache.getString(
72
- "probe-last-alive",
73
- probeId.toString(),
74
- );
317
+ const previousLastAliveCheck: string | null =
318
+ await this.boundedCacheOperation(
319
+ "Probe last-alive cache read",
320
+ GlobalCache.getString(
321
+ PROBE_LAST_ALIVE_CACHE_NAMESPACE,
322
+ probeId.toString(),
323
+ ),
324
+ );
75
325
 
76
326
  if (!previousLastAliveCheck) {
77
327
  await this.saveLastAliveInCache(probeId, now);
@@ -129,12 +379,44 @@ export class Service extends DatabaseService<Model> {
129
379
  * hooks (onBeforeUpdate/onUpdateSuccess) are inert here. See
130
380
  * ServiceService.updateLastSeen.
131
381
  */
132
- await this.updateColumnsByIdWithoutHooks({
133
- id: probeId,
134
- data: {
135
- lastAlive: now,
136
- },
137
- });
382
+ try {
383
+ await this.updateColumnsByIdWithoutHooks({
384
+ id: probeId,
385
+ data: {
386
+ lastAlive: now,
387
+ },
388
+ });
389
+ } catch (err) {
390
+ /*
391
+ * The 30s throttle stamp was written BEFORE this write was attempted
392
+ * (shouldSaveLastAlive sets it — deliberately, as dogpile
393
+ * protection). If we leave it in place after a FAILED write, every
394
+ * request for the next 30 seconds silently skips the retry, lastAlive
395
+ * quietly crosses the connection-status worker's staleness threshold,
396
+ * and a healthy probe gets flagged Disconnected. Clear the stamp so
397
+ * the very next request retries the write.
398
+ */
399
+ await this.clearLastAliveThrottle(probeId);
400
+ throw err;
401
+ }
402
+ }
403
+
404
+ // Best-effort: on failure the throttle simply expires on its own.
405
+ private async clearLastAliveThrottle(probeId: ObjectID): Promise<void> {
406
+ try {
407
+ await this.boundedCacheOperation(
408
+ "Probe last-alive throttle clear",
409
+ GlobalCache.deleteKey(
410
+ PROBE_LAST_ALIVE_CACHE_NAMESPACE,
411
+ probeId.toString(),
412
+ ),
413
+ );
414
+ } catch (err) {
415
+ logger.error("Error in clearing last alive throttle", {
416
+ probeId: probeId?.toString(),
417
+ } as LogAttributes);
418
+ logger.error(err, { probeId: probeId?.toString() } as LogAttributes);
419
+ }
138
420
  }
139
421
 
140
422
  @CaptureSpan()
@@ -266,8 +548,18 @@ export class Service extends DatabaseService<Model> {
266
548
  @CaptureSpan()
267
549
  protected override async onUpdateSuccess(
268
550
  onUpdate: OnUpdate<Model>,
269
- _updatedItemIds: Array<ObjectID>,
551
+ updatedItemIds: Array<ObjectID>,
270
552
  ): Promise<OnUpdate<Model>> {
553
+ /*
554
+ * Key rotation: the auth middleware trusts a cached hash of the key, so
555
+ * the moment the key column changes the cached entries for the updated
556
+ * probes must go — otherwise the OLD key keeps authenticating until the
557
+ * cache TTL runs out.
558
+ */
559
+ if (onUpdate.updateBy.data.key !== undefined) {
560
+ await this.invalidateProbeAuthCache(updatedItemIds);
561
+ }
562
+
271
563
  if (
272
564
  onUpdate.carryForward &&
273
565
  onUpdate.carryForward.probesToNotifyOwners.length > 0
@@ -284,6 +576,17 @@ export class Service extends DatabaseService<Model> {
284
576
  return Promise.resolve(onUpdate);
285
577
  }
286
578
 
579
+ @CaptureSpan()
580
+ protected override async onDeleteSuccess(
581
+ onDelete: OnDelete<Model>,
582
+ itemIdsBeforeDelete: Array<ObjectID>,
583
+ ): Promise<OnDelete<Model>> {
584
+ // A deleted probe's key must stop authenticating.
585
+ await this.invalidateProbeAuthCache(itemIdsBeforeDelete);
586
+
587
+ return Promise.resolve(onDelete);
588
+ }
589
+
287
590
  @CaptureSpan()
288
591
  public async notifyOwnersOnStatusChange(data: {
289
592
  probeId: ObjectID;
@@ -58,7 +58,7 @@ import StatusPageResourceService from "./StatusPageResourceService";
58
58
  import Dictionary from "../../Types/Dictionary";
59
59
  import { JSONObject } from "../../Types/JSON";
60
60
  import MonitorGroupResource from "../../Models/DatabaseModels/MonitorGroupResource";
61
- import MonitorGroupResourceService from "./MonitorGroupResourceService";
61
+ import MonitorGroupService from "./MonitorGroupService";
62
62
  import QueryHelper from "../Types/Database/QueryHelper";
63
63
  import OneUptimeDate from "../../Types/Date";
64
64
  import IncidentService from "./IncidentService";
@@ -107,6 +107,22 @@ export class Service extends DatabaseService<StatusPage> {
107
107
  10_000,
108
108
  );
109
109
 
110
+ /*
111
+ * Caches verified custom-domain -> statusPageId resolution. Every public
112
+ * status-page API call served on a custom domain (overview, incidents,
113
+ * announcements, polling — several per page view) starts with this lookup,
114
+ * and the mapping only changes when a customer provisions or removes a
115
+ * domain (which takes minutes anyway), so a 60s staleness window is the
116
+ * same tradeoff `statusPageUrlCache` above already accepts. Only
117
+ * SUCCESSFUL resolutions are cached: misses re-query, so a freshly
118
+ * verified domain works immediately and attacker-controlled Host headers
119
+ * cannot fill the cache with negative entries.
120
+ */
121
+ private statusPageDomainToIdCache: InMemoryTTLCache<string> =
122
+ new InMemoryTTLCache(10_000);
123
+
124
+ private static readonly DOMAIN_TO_ID_CACHE_TTL_MS: number = 60 * 1000;
125
+
110
126
  public constructor() {
111
127
  super(StatusPage);
112
128
  }
@@ -119,6 +135,14 @@ export class Service extends DatabaseService<StatusPage> {
119
135
  this.statusPageUrlCache.clear();
120
136
  }
121
137
 
138
+ public clearStatusPageDomainToIdCache(fullDomain?: string): void {
139
+ if (fullDomain) {
140
+ this.statusPageDomainToIdCache.delete(fullDomain);
141
+ return;
142
+ }
143
+ this.statusPageDomainToIdCache.clear();
144
+ }
145
+
122
146
  /*
123
147
  * Mirrors `resolveStatusPageIdOrThrow` in `Common/Server/API/StatusPageAPI.ts`
124
148
  * (module-private there), but returns null instead of throwing so callers can
@@ -135,6 +159,13 @@ export class Service extends DatabaseService<StatusPage> {
135
159
  }
136
160
 
137
161
  if (statusPageIdOrDomain.includes(".")) {
162
+ const cachedStatusPageId: string | undefined =
163
+ this.statusPageDomainToIdCache.get(statusPageIdOrDomain);
164
+
165
+ if (cachedStatusPageId) {
166
+ return new ObjectID(cachedStatusPageId);
167
+ }
168
+
138
169
  const statusPageDomain: StatusPageDomain | null =
139
170
  await StatusPageDomainService.findOneBy({
140
171
  query: {
@@ -155,6 +186,12 @@ export class Service extends DatabaseService<StatusPage> {
155
186
  return null;
156
187
  }
157
188
 
189
+ this.statusPageDomainToIdCache.set(
190
+ statusPageIdOrDomain,
191
+ statusPageDomain.statusPageId.toString(),
192
+ Service.DOMAIN_TO_ID_CACHE_TTL_MS,
193
+ );
194
+
158
195
  return statusPageDomain.statusPageId;
159
196
  }
160
197
 
@@ -1378,12 +1415,22 @@ export class Service extends DatabaseService<StatusPage> {
1378
1415
  @CaptureSpan()
1379
1416
  public async getMonitorIdsOnStatusPage(data: {
1380
1417
  statusPageId: ObjectID;
1418
+ /*
1419
+ * Pass when the caller has already loaded the page's resources —
1420
+ * several public endpoints fetch them right before calling this, and
1421
+ * without this parameter the identical StatusPageResource query used to
1422
+ * run twice per request.
1423
+ */
1424
+ statusPageResources?: Array<StatusPageResource> | undefined;
1381
1425
  }): Promise<{
1382
1426
  monitorsOnStatusPage: Array<ObjectID>;
1383
1427
  monitorsInGroup: Dictionary<Array<ObjectID>>;
1384
1428
  }> {
1385
1429
  const statusPageResources: Array<StatusPageResource> =
1386
- await this.getStatusPageResources(data);
1430
+ data.statusPageResources ||
1431
+ (await this.getStatusPageResources({
1432
+ statusPageId: data.statusPageId,
1433
+ }));
1387
1434
 
1388
1435
  const monitorGroupIds: Array<ObjectID> = statusPageResources
1389
1436
  .map((resource: StatusPageResource) => {
@@ -1404,33 +1451,15 @@ export class Service extends DatabaseService<StatusPage> {
1404
1451
  return Boolean(id); // remove nulls
1405
1452
  });
1406
1453
 
1407
- for (const monitorGroupId of monitorGroupIds) {
1408
- // get current status of monitors in the group.
1454
+ // Batched: one query for all monitor groups instead of one per group.
1455
+ const monitorIdsByGroupId: Dictionary<Array<ObjectID>> =
1456
+ await MonitorGroupService.getMonitorIdsInMonitorGroups(monitorGroupIds);
1409
1457
 
1458
+ for (const monitorGroupId of monitorGroupIds) {
1410
1459
  // get monitors in the group.
1411
1460
 
1412
- const groupResources: Array<MonitorGroupResource> =
1413
- await MonitorGroupResourceService.findBy({
1414
- query: {
1415
- monitorGroupId: monitorGroupId,
1416
- },
1417
- select: {
1418
- monitorId: true,
1419
- },
1420
- props: {
1421
- isRoot: true,
1422
- },
1423
- limit: LIMIT_PER_PROJECT,
1424
- skip: 0,
1425
- });
1426
-
1427
- const monitorsInGroupIds: Array<ObjectID> = groupResources
1428
- .map((resource: MonitorGroupResource) => {
1429
- return resource.monitorId!;
1430
- })
1431
- .filter((id: ObjectID) => {
1432
- return Boolean(id); // remove nulls
1433
- });
1461
+ const monitorsInGroupIds: Array<ObjectID> =
1462
+ monitorIdsByGroupId[monitorGroupId.toString()] || [];
1434
1463
 
1435
1464
  for (const monitorId of monitorsInGroupIds) {
1436
1465
  if (
@@ -1511,25 +1540,26 @@ export class Service extends DatabaseService<StatusPage> {
1511
1540
  }): Promise<Dictionary<ObjectID>> {
1512
1541
  const monitorGroupCurrentStatuses: Dictionary<ObjectID> = {};
1513
1542
 
1543
+ /*
1544
+ * Batched: this used to issue one MonitorGroupResource query per status
1545
+ * page resource (not even per distinct group) on every badge render and
1546
+ * subscriber report. One query now serves all groups.
1547
+ */
1548
+ const resourcesByGroupId: Dictionary<Array<MonitorGroupResource>> =
1549
+ await MonitorGroupService.getMonitorGroupResourcesByGroupIds(
1550
+ data.statusPageResources
1551
+ .map((resource: StatusPageResource) => {
1552
+ return resource.monitorGroupId!;
1553
+ })
1554
+ .filter((id: ObjectID) => {
1555
+ return Boolean(id); // remove nulls
1556
+ }),
1557
+ );
1558
+
1514
1559
  for (const resource of data.statusPageResources) {
1515
1560
  if (resource.monitorGroupId) {
1516
1561
  const monitorGroupResources: Array<MonitorGroupResource> =
1517
- await MonitorGroupResourceService.findBy({
1518
- query: {
1519
- monitorGroupId: resource.monitorGroupId,
1520
- },
1521
- select: {
1522
- monitorId: true,
1523
- monitor: {
1524
- currentMonitorStatusId: true,
1525
- },
1526
- },
1527
- skip: 0,
1528
- limit: LIMIT_PER_PROJECT,
1529
- props: {
1530
- isRoot: true,
1531
- },
1532
- });
1562
+ resourcesByGroupId[resource.monitorGroupId.toString()] || [];
1533
1563
 
1534
1564
  const statuses: Array<ObjectID> = monitorGroupResources
1535
1565
  .filter((item: MonitorGroupResource) => {