@oneuptime/common 11.7.1 → 11.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
- package/Models/DatabaseModels/DashboardDomain.ts +1 -0
- package/Models/DatabaseModels/Incident.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Monitor.ts +1 -0
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
- package/Models/DatabaseModels/NetworkSite.ts +1 -0
- package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
- package/Models/DatabaseModels/Probe.ts +13 -13
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
- package/Models/DatabaseModels/StatusPage.ts +1 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
- package/Server/API/BaseAPI.ts +17 -1
- package/Server/API/CommonAPI.ts +32 -0
- package/Server/API/MicrosoftTeamsAPI.ts +99 -2
- package/Server/API/ProbeAPI.ts +2 -0
- package/Server/API/SlackAPI.ts +62 -0
- package/Server/API/StatusPageAPI.ts +157 -192
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Services/DatabaseService.ts +11 -3
- package/Server/Services/MonitorGroupService.ts +145 -0
- package/Server/Services/MonitorService.ts +231 -30
- package/Server/Services/ProbeService.ts +319 -16
- package/Server/Services/StatusPageService.ts +72 -42
- package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
- package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
- package/Server/Utils/Monitor/MonitorResource.ts +88 -35
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
- package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
- package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
- package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
- package/Tests/Server/API/ProbeAPI.test.ts +6 -0
- package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
- package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
- package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
- package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
- package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
- package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
- package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
- package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
- package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
- package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
- package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
- package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
- package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
- package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
- package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
- package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
- package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
- package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
- package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
- package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
- package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
- package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
- package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
- package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
- package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
- package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
- package/Tests/Types/JSONFunctions.test.ts +103 -1
- package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
- package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
- package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +572 -0
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
- package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
- package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
- package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
- package/Tests/UI/Utils/Project.test.ts +809 -0
- package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
- package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
- package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
- package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
- package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
- package/Tests/Utils/UserPreferences.test.ts +563 -0
- package/Types/JSONFunctions.ts +7 -2
- package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
- package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
- package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
- package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
- package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
- package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
- package/UI/Components/Forms/ModelForm.tsx +41 -18
- package/UI/Components/MasterPage/MasterPage.tsx +65 -0
- package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
- package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
- package/UI/Components/ModelTable/BaseModelTable.tsx +687 -34
- package/UI/Components/ModelTable/Column.ts +29 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +420 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
- package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
- package/UI/Components/ModelTable/TableView.tsx +24 -2
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
- package/UI/Components/Navbar/NavBar.tsx +66 -1
- package/UI/Components/SideMenu/SideMenu.tsx +24 -4
- package/UI/Components/Table/Table.tsx +66 -14
- package/UI/Components/Table/TableRow.tsx +180 -175
- package/UI/Components/Table/Types/Column.ts +20 -0
- package/UI/Utils/Project.ts +214 -18
- package/UI/Utils/TableColumnsToCsv.ts +86 -6
- package/UI/esbuild-config.js +34 -0
- package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
- package/Utils/UserPreferences.ts +53 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
- package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Probe.js +13 -13
- package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TableView.js +40 -0
- package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +12 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +21 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProbeAPI.js +2 -0
- package/build/dist/Server/API/ProbeAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +44 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +98 -152
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +10 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/MonitorGroupService.js +118 -0
- package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +198 -24
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +238 -9
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +46 -39
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +8 -3
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
- package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +472 -27
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +189 -0
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
- package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +46 -13
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +11 -6
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +161 -15
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
- package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
- package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
- package/build/dist/Utils/UserPreferences.js +33 -0
- package/build/dist/Utils/UserPreferences.js.map +1 -1
- package/package.json +4 -3
|
@@ -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
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
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 =
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1408
|
-
|
|
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
|
|
1413
|
-
|
|
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
|
-
|
|
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) => {
|