@oneuptime/common 14.0.7 → 14.0.9
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/DatabaseModels/Incident.ts +267 -1
- package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
- package/Models/DatabaseModels/Project.ts +55 -5
- package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
- package/Models/DatabaseModels/RunnerJob.ts +75 -0
- package/Models/DatabaseModels/StatusPage.ts +55 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
- package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
- package/Server/API/AIChatAPI.ts +6 -3
- package/Server/API/AIInvestigationAPI.ts +17 -10
- package/Server/API/AutoRemediationAPI.ts +207 -25
- package/Server/API/IncidentAPI.ts +162 -0
- package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
- package/Server/API/MicrosoftTeamsAPI.ts +93 -14
- package/Server/API/ProxmoxResourceAPI.ts +244 -14
- package/Server/API/StatusPageAPI.ts +487 -171
- package/Server/API/StatusPageSubscriberAPI.ts +63 -15
- package/Server/Infrastructure/GlobalCache.ts +32 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
- package/Server/Infrastructure/Semaphore.ts +10 -0
- package/Server/Middleware/IdentityRateLimit.ts +108 -3
- package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
- package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
- package/Server/Middleware/TelemetryIngest.ts +41 -18
- package/Server/Services/AIService.ts +70 -1
- package/Server/Services/AlertService.ts +352 -11
- package/Server/Services/AlertSeverityService.ts +65 -15
- package/Server/Services/AutoRemediationRuleService.ts +1 -1
- package/Server/Services/DatabaseService.ts +93 -10
- package/Server/Services/IncidentCustomFieldService.ts +276 -1
- package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
- package/Server/Services/IncidentEpisodeService.ts +15 -0
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
- package/Server/Services/IncidentPublicNoteService.ts +30 -1
- package/Server/Services/IncidentService.ts +1788 -142
- package/Server/Services/IncidentSeverityService.ts +66 -15
- package/Server/Services/IncidentStateTimelineService.ts +13 -0
- package/Server/Services/IncidentTemplateService.ts +109 -0
- package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
- package/Server/Services/KubernetesAiAgentService.ts +1634 -0
- package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
- package/Server/Services/KubernetesClusterService.ts +129 -21
- package/Server/Services/ProjectService.ts +32 -0
- package/Server/Services/ProxmoxResourceService.ts +322 -5
- package/Server/Services/RunnerJobService.ts +241 -104
- package/Server/Services/RunnerService.ts +1 -1
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
- package/Server/Services/ScheduledMaintenanceService.ts +805 -19
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
- package/Server/Services/SmsService.ts +10 -0
- package/Server/Services/StatusPageService.ts +65 -8
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
- package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
- package/Server/Services/StatusPageSubscriberService.ts +1161 -64
- package/Server/Services/TeamComplianceSettingService.ts +1125 -9
- package/Server/Types/Markdown.ts +153 -6
- package/Server/Types/Workflow/Components/Email.ts +61 -2
- package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
- package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
- package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
- package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
- package/Server/Utils/DataSource/EgressGuard.ts +66 -21
- package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
- package/Server/Utils/SSRFProtection.ts +76 -16
- package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
- package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
- package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
- package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
- package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
- package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
- package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
- package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
- package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
- package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
- package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
- package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
- package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
- package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
- package/Server/Utils/StatusPageResource.ts +51 -20
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
- package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
- package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +10 -0
- package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
- package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
- package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
- package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
- package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
- package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
- package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
- package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
- package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
- package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
- package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
- package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
- package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
- package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
- package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
- package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
- package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
- package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
- package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
- package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
- package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
- package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
- package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
- package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
- package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
- package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
- package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
- package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
- package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
- package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
- package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
- package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
- package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
- package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
- package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
- package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
- package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
- package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
- package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
- package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
- package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
- package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
- package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
- package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
- package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +778 -2
- package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
- package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +828 -17
- package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
- package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
- package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
- package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
- package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
- package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
- package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
- package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
- package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
- package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
- package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
- package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
- package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
- package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
- package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
- package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
- package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
- package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
- package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
- package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
- package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
- package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
- package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
- package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
- package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
- package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
- package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
- package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
- package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
- package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
- package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
- package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
- package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
- package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
- package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
- package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
- package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
- package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
- package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
- package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
- package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
- package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
- package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
- package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
- package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
- package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
- package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
- package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
- package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
- package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
- package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
- package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
- package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
- package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
- package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
- package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
- package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
- package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
- package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
- package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
- package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
- package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
- package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
- package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
- package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
- package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
- package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
- package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
- package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
- package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
- package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
- package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
- package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
- package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
- package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
- package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
- package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
- package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
- package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
- package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
- package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
- package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
- package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
- package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
- package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
- package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
- package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
- package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
- package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
- package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
- package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
- package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
- package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
- package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
- package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
- package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
- package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
- package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
- package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
- package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
- package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
- package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
- package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
- package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
- package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
- package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
- package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
- package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
- package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
- package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
- package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
- package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
- package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
- package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
- package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
- package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
- package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
- package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
- package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
- package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
- package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
- package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
- package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
- package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
- package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
- package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
- package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
- package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
- package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
- package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
- package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
- package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
- package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
- package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
- package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
- package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
- package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
- package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
- package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
- package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
- package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
- package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
- package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
- package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
- package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
- package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
- package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
- package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
- package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
- package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
- package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
- package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
- package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
- package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
- package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
- package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
- package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
- package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
- package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
- package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
- package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
- package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
- package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
- package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
- package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
- package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
- package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
- package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
- package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
- package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
- package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
- package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
- package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
- package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
- package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
- package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
- package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
- package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
- package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
- package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
- package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
- package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
- package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
- package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
- package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
- package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
- package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
- package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
- package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
- package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
- package/Tests/Types/SafeHtml.test.ts +102 -0
- package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
- package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
- package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
- package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
- package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
- package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
- package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
- package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
- package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
- package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
- package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
- package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
- package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
- package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
- package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
- package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
- package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
- package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
- package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
- package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
- package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
- package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
- package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
- package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
- package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
- package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
- package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
- package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
- package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
- package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
- package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
- package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
- package/Tests/UI/Components/Input.test.tsx +80 -1
- package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
- package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
- package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
- package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
- package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
- package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
- package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
- package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
- package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
- package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
- package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
- package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
- package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
- package/Tests/UI/Components/Toggle.test.tsx +66 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
- package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
- package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
- package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
- package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
- package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
- package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
- package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
- package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
- package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
- package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
- package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
- package/Types/AI/AIRunType.ts +9 -4
- package/Types/AI/InvestigationNotStartedReason.ts +6 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
- package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
- package/Types/CustomField/CustomFieldOrder.ts +55 -0
- package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
- package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
- package/Types/CustomField/CustomFieldType.ts +14 -0
- package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
- package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
- package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
- package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
- package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
- package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
- package/Types/SafeHtml.ts +93 -0
- package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
- package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
- package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
- package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
- package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
- package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
- package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
- package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
- package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
- package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
- package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
- package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
- package/Types/Team/ComplianceNotificationChannel.ts +22 -0
- package/Types/Team/ComplianceRule.ts +434 -0
- package/Types/Team/ComplianceRuleType.ts +21 -0
- package/Types/Team/TeamComplianceStatus.ts +81 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
- package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
- package/UI/Components/ActionButton/RowActions.tsx +238 -0
- package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
- package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
- package/UI/Components/Charts/Line/LineChart.tsx +32 -2
- package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
- package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
- package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
- package/UI/Components/Charts/Utils/XAxis.ts +75 -0
- package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
- package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
- package/UI/Components/Forms/Fields/FormField.tsx +10 -0
- package/UI/Components/Forms/ModelForm.tsx +70 -8
- package/UI/Components/Forms/Validation.ts +1 -1
- package/UI/Components/Input/Input.tsx +90 -57
- package/UI/Components/List/ListRow.tsx +8 -74
- package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
- package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
- package/UI/Components/ModelTable/BaseModelTable.tsx +21 -9
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
- package/UI/Components/ModelTable/ModelTable.tsx +6 -7
- package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
- package/UI/Components/OrderedStatesList/Item.tsx +7 -72
- package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
- package/UI/Components/Table/TableBody.tsx +8 -2
- package/UI/Components/Table/TableRow.tsx +20 -140
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
- package/UI/Components/TelemetryViewer/types.ts +2 -0
- package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
- package/UI/Components/Toggle/Toggle.tsx +26 -0
- package/UI/Components/Tooltip/Tooltip.tsx +8 -1
- package/UI/Styles/Theme.css +14 -2
- package/UI/Utils/SensitiveUrlToken.ts +10 -5
- package/Utils/API.ts +247 -2
- package/Utils/AiRemediation/CommandPolicy.ts +1 -1
- package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
- package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
- package/Utils/Markdown/MarkdownEscape.ts +65 -0
- package/Utils/ProbeNetworkFailureUtil.ts +673 -0
- package/build/dist/Models/DatabaseModels/Incident.js +267 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
- package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +56 -5
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +6 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +100 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
- package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +417 -176
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
- package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +59 -1
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +248 -13
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertSeverityService.js +51 -15
- package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +56 -7
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1251 -72
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
- package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
- package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
- package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +178 -92
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +1 -1
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +582 -20
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/SmsService.js +2 -22
- package/build/dist/Server/Services/SmsService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +47 -9
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +110 -6
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
- package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
- package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +48 -6
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageResource.js +39 -7
- package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIRunType.js +9 -4
- package/build/dist/Types/AI/AIRunType.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
- package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/SafeHtml.js +84 -0
- package/build/dist/Types/SafeHtml.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
- package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRule.js +304 -0
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
- package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
- package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
- package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
- package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
- package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
- package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
- package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
- package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
- package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +1 -1
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +69 -54
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/List/ListRow.js +4 -39
- package/build/dist/UI/Components/List/ListRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
- package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/Table/TableBody.js +6 -2
- package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +12 -75
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
- package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
- package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
- package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
- package/build/dist/Utils/API.js +177 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
- package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
- package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -7,10 +7,13 @@ import {
|
|
|
7
7
|
import ProjectSMTPConfigService from "../Services/ProjectSmtpConfigService";
|
|
8
8
|
import CreateBy from "../Types/Database/CreateBy";
|
|
9
9
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
10
|
-
import { OnCreate } from "../Types/Database/Hooks";
|
|
10
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
11
11
|
import QueryHelper from "../Types/Database/QueryHelper";
|
|
12
|
+
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
13
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
12
14
|
import logger, { LogAttributes } from "../Utils/Logger";
|
|
13
15
|
import DatabaseService from "./DatabaseService";
|
|
16
|
+
import GlobalCache from "../Infrastructure/GlobalCache";
|
|
14
17
|
import MailService from "./MailService";
|
|
15
18
|
import ProjectCallSMSConfigService from "./ProjectCallSMSConfigService";
|
|
16
19
|
import ProjectService, { CurrentPlan } from "./ProjectService";
|
|
@@ -33,6 +36,9 @@ import PositiveNumber from "../../Types/PositiveNumber";
|
|
|
33
36
|
import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
|
|
34
37
|
import StatusPageSubscriberNotificationEventType from "../../Types/StatusPage/StatusPageSubscriberNotificationEventType";
|
|
35
38
|
import StatusPageSubscriberNotificationMethod from "../../Types/StatusPage/StatusPageSubscriberNotificationMethod";
|
|
39
|
+
import { IncidentSubscriberAudienceCounts } from "../../Types/StatusPage/IncidentSubscriberAudience";
|
|
40
|
+
import Dictionary from "../../Types/Dictionary";
|
|
41
|
+
import { JSONObject } from "../../Types/JSON";
|
|
36
42
|
import NumberUtil from "../../Utils/Number";
|
|
37
43
|
import SlackUtil from "../Utils/Workspace/Slack/Slack";
|
|
38
44
|
import MicrosoftTeamsUtil from "../Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
|
|
@@ -41,12 +47,174 @@ import SSRFProtection from "../Utils/SSRFProtection";
|
|
|
41
47
|
import StatusPageSubscriberNotificationTemplateService, {
|
|
42
48
|
Service as StatusPageSubscriberNotificationTemplateServiceClass,
|
|
43
49
|
} from "./StatusPageSubscriberNotificationTemplateService";
|
|
50
|
+
import TeamMemberService from "./TeamMemberService";
|
|
51
|
+
import UserService from "./UserService";
|
|
52
|
+
import User from "../../Models/DatabaseModels/User";
|
|
53
|
+
import OneUptimeDate from "../../Types/Date";
|
|
54
|
+
import ModelEventType from "../../Types/Realtime/ModelEventType";
|
|
55
|
+
import StatusPageSubscriberUnsubscribe, {
|
|
56
|
+
StatusPageSubscriberContact,
|
|
57
|
+
StatusPageSubscriberUnsubscribeDetails,
|
|
58
|
+
StatusPageSubscriberUnsubscribeState,
|
|
59
|
+
} from "../../Types/StatusPage/StatusPageSubscriberUnsubscribe";
|
|
60
|
+
import StatusPageSubscriberUnsubscribeToken from "../Utils/StatusPage/StatusPageSubscriberUnsubscribeToken";
|
|
61
|
+
import StatusPageSubscriberUnsubscribeNotice, {
|
|
62
|
+
StatusPageSubscriberUnsubscribeNoticeEmail,
|
|
63
|
+
StatusPageSubscriberUnsubscribeSource,
|
|
64
|
+
} from "../Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice";
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* For an UPDATE ... RETURNING, the postgres driver hands TypeORM's
|
|
68
|
+
* manager.query back `[rows, rowCount]` rather than the bare row array (see
|
|
69
|
+
* UserTwoFactorBackupCodeService.consumeCode). Read defensively: anything
|
|
70
|
+
* unrecognisable is "no row changed", never a silent success.
|
|
71
|
+
*/
|
|
72
|
+
type ReturnedRowsFunction = (result: unknown) => Array<JSONObject>;
|
|
73
|
+
|
|
74
|
+
const returnedRows: ReturnedRowsFunction = (
|
|
75
|
+
result: unknown,
|
|
76
|
+
): Array<JSONObject> => {
|
|
77
|
+
if (!Array.isArray(result)) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
result.length === 2 &&
|
|
83
|
+
Array.isArray(result[0]) &&
|
|
84
|
+
typeof result[1] === "number"
|
|
85
|
+
) {
|
|
86
|
+
return result[0] as Array<JSONObject>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result.filter((row: unknown): boolean => {
|
|
90
|
+
return Boolean(row) && typeof row === "object" && !Array.isArray(row);
|
|
91
|
+
}) as Array<JSONObject>;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* How many rows an UPDATE without RETURNING changed: the postgres driver
|
|
96
|
+
* answers it with `[rows, rowCount]`. Anything else reads as none.
|
|
97
|
+
*/
|
|
98
|
+
type AffectedRowCountFunction = (result: unknown) => number;
|
|
99
|
+
|
|
100
|
+
const affectedRowCount: AffectedRowCountFunction = (
|
|
101
|
+
result: unknown,
|
|
102
|
+
): number => {
|
|
103
|
+
if (
|
|
104
|
+
Array.isArray(result) &&
|
|
105
|
+
result.length === 2 &&
|
|
106
|
+
typeof result[1] === "number"
|
|
107
|
+
) {
|
|
108
|
+
return result[1];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return 0;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
interface UnsubscribedAtCarryForward {
|
|
115
|
+
// The value this update writes to isUnsubscribed, when it writes one.
|
|
116
|
+
isUnsubscribed: boolean | null;
|
|
117
|
+
// The matched subscribers that are subscribed now, which this update cancels.
|
|
118
|
+
subscriberIdsBeingUnsubscribed: Array<string>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/*
|
|
122
|
+
* The subscriber rows being created for a visitor who signed up on the status
|
|
123
|
+
* page itself (see createFromStatusPageSignUp). Every other create - a
|
|
124
|
+
* teammate on the dashboard, an API key, a workflow - is the team adding a
|
|
125
|
+
* subscriber, and onBeforeCreate marks it so (Is Added By Team).
|
|
126
|
+
*
|
|
127
|
+
* The very model instances, held only for the length of the create: nothing
|
|
128
|
+
* a request body carries can put a row in here, so no client can pass its
|
|
129
|
+
* subscriber off as a sign-up and keep the page's owners from hearing when
|
|
130
|
+
* it unsubscribes.
|
|
131
|
+
*/
|
|
132
|
+
const statusPageSignUps: WeakSet<Model> = new WeakSet<Model>();
|
|
133
|
+
|
|
134
|
+
// How many subscribers the backfill and the token top-up handle per statement.
|
|
135
|
+
const UNSUBSCRIBE_COLUMNS_BATCH_SIZE: number = 1000;
|
|
136
|
+
|
|
137
|
+
export interface StatusPageSubscriberUnsubscribeBackfillResult {
|
|
138
|
+
// Subscribers given an unsubscribe token.
|
|
139
|
+
tokensGiven: number;
|
|
140
|
+
// Subscribers marked Is Added By Team because they have a creator.
|
|
141
|
+
markedAddedByTeam: number;
|
|
142
|
+
}
|
|
44
143
|
|
|
45
144
|
export class Service extends DatabaseService<Model> {
|
|
46
145
|
public constructor() {
|
|
47
146
|
super(Model);
|
|
48
147
|
}
|
|
49
148
|
|
|
149
|
+
/*
|
|
150
|
+
* A create hands its caller the row it saved, and every caller passes it
|
|
151
|
+
* on whole: BaseAPI serializes it into the response of POST
|
|
152
|
+
* /status-page-subscriber without a column read check (a teammate's Add
|
|
153
|
+
* Subscriber and Add in Bulk, any API key), and a workflow's Create Status
|
|
154
|
+
* Page Subscriber step returns it, to be kept in the workflow's logs.
|
|
155
|
+
*
|
|
156
|
+
* So the two secrets onBeforeCreate minted are taken off it first: the
|
|
157
|
+
* unsubscribe token, which lets whoever holds it cancel the subscription
|
|
158
|
+
* without signing in - and makes the team's notice say the subscriber did -
|
|
159
|
+
* and the six-digit confirmation code. Nobody can read either column
|
|
160
|
+
* (read: []). They leave the server only inside the messages to the
|
|
161
|
+
* subscription's own contact, which onCreateSuccess has sent by now; the
|
|
162
|
+
* confirmation email reads its code back from the database.
|
|
163
|
+
*/
|
|
164
|
+
@CaptureSpan()
|
|
165
|
+
public override async create(createBy: CreateBy<Model>): Promise<Model> {
|
|
166
|
+
const created: Model = await super.create(createBy);
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
* Cleared rather than deleted, as BaseAPI.createItem clears a model's
|
|
170
|
+
* `_id`: an unset column holds undefined, which serializes as nothing.
|
|
171
|
+
*/
|
|
172
|
+
const row: Record<string, unknown> = created as unknown as Record<
|
|
173
|
+
string,
|
|
174
|
+
unknown
|
|
175
|
+
>;
|
|
176
|
+
row["unsubscribeToken"] = undefined;
|
|
177
|
+
row["subscriptionConfirmationToken"] = undefined;
|
|
178
|
+
|
|
179
|
+
return created;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/*
|
|
183
|
+
* Create the subscriber a visitor signed up for on the status page itself
|
|
184
|
+
* (StatusPageAPI's subscribe endpoint). The one create that is not the team
|
|
185
|
+
* adding a subscriber: it is created as root, like the rest of that
|
|
186
|
+
* endpoint, and Is Added By Team is left off.
|
|
187
|
+
*/
|
|
188
|
+
@CaptureSpan()
|
|
189
|
+
public async createFromStatusPageSignUp(data: Model): Promise<Model> {
|
|
190
|
+
statusPageSignUps.add(data);
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
return await this.create({
|
|
194
|
+
data: data,
|
|
195
|
+
props: {
|
|
196
|
+
isRoot: true,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
} finally {
|
|
200
|
+
statusPageSignUps.delete(data);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/*
|
|
205
|
+
* Whether the team added this subscriber, rather than it signing up on the
|
|
206
|
+
* status page. Is Added By Team says so for every subscriber created since
|
|
207
|
+
* it existed; Created By is read as well for the ones created before, until
|
|
208
|
+
* the backfill has marked them (every create with a creator is the team's).
|
|
209
|
+
*/
|
|
210
|
+
public isAddedByTeam(
|
|
211
|
+
subscriber: Pick<Model, "isAddedByTeam" | "createdByUserId">,
|
|
212
|
+
): boolean {
|
|
213
|
+
return (
|
|
214
|
+
subscriber.isAddedByTeam === true || Boolean(subscriber.createdByUserId)
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
50
218
|
@CaptureSpan()
|
|
51
219
|
protected override async onBeforeCreate(
|
|
52
220
|
data: CreateBy<Model>,
|
|
@@ -359,6 +527,36 @@ export class Service extends DatabaseService<Model> {
|
|
|
359
527
|
100000,
|
|
360
528
|
999999,
|
|
361
529
|
).toString();
|
|
530
|
+
|
|
531
|
+
/*
|
|
532
|
+
* The secret in this subscriber's unsubscribe link. Always minted here,
|
|
533
|
+
* whatever the request carried: the column is computed, so the create
|
|
534
|
+
* column check lets a client send one, and a token the client chose would
|
|
535
|
+
* be one somebody else could know. A re-subscribe deletes the old row
|
|
536
|
+
* above and gets a new token, so links to the old subscription stop
|
|
537
|
+
* working.
|
|
538
|
+
*/
|
|
539
|
+
data.data.unsubscribeToken =
|
|
540
|
+
StatusPageSubscriberUnsubscribeToken.generate();
|
|
541
|
+
|
|
542
|
+
/*
|
|
543
|
+
* Never taken from a client either: whether the team added this
|
|
544
|
+
* subscriber. Only a sign-up on the status page is not the team's, and
|
|
545
|
+
* only createFromStatusPageSignUp creates one. A teammate's create carries
|
|
546
|
+
* a creator (Created By) but an API key's or a workflow's does not, so
|
|
547
|
+
* this is the column that says it for all of them.
|
|
548
|
+
*/
|
|
549
|
+
data.data.isAddedByTeam = !statusPageSignUps.has(data.data);
|
|
550
|
+
|
|
551
|
+
/*
|
|
552
|
+
* Likewise never taken from a client. A subscriber is only ever created
|
|
553
|
+
* unsubscribed through the API, and then it was cancelled now.
|
|
554
|
+
*/
|
|
555
|
+
if (data.data.isUnsubscribed) {
|
|
556
|
+
data.data.unsubscribedAt = OneUptimeDate.getCurrentDate();
|
|
557
|
+
} else {
|
|
558
|
+
delete data.data.unsubscribedAt;
|
|
559
|
+
}
|
|
362
560
|
logger.debug(
|
|
363
561
|
`Subscription Confirmation Token: ${data.data.subscriptionConfirmationToken}`,
|
|
364
562
|
{
|
|
@@ -367,11 +565,8 @@ export class Service extends DatabaseService<Model> {
|
|
|
367
565
|
} as LogAttributes,
|
|
368
566
|
);
|
|
369
567
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
statusPageId: data.data.statusPageId?.toString(),
|
|
373
|
-
} as LogAttributes);
|
|
374
|
-
logger.debug(data, {
|
|
568
|
+
// Not the data itself: it now carries the unsubscribe token.
|
|
569
|
+
logger.debug("onBeforeCreate processed data.", {
|
|
375
570
|
projectId: data.data.projectId?.toString(),
|
|
376
571
|
statusPageId: data.data.statusPageId?.toString(),
|
|
377
572
|
} as LogAttributes);
|
|
@@ -379,17 +574,543 @@ export class Service extends DatabaseService<Model> {
|
|
|
379
574
|
return { createBy: data, carryForward: statuspage };
|
|
380
575
|
}
|
|
381
576
|
|
|
577
|
+
/*
|
|
578
|
+
* Unsubscribed At follows Is Unsubscribed on every write that sets it - a
|
|
579
|
+
* teammate's toggle on the dashboard, the API, a workflow - not only on the
|
|
580
|
+
* unsubscribe link, which sets both itself (see unsubscribe()).
|
|
581
|
+
*
|
|
582
|
+
* Which rows an update cancels has to be known before it runs: afterwards
|
|
583
|
+
* every matched row reads unsubscribed, and a row that already was would
|
|
584
|
+
* get today's date for a cancellation that happened long ago (or before the
|
|
585
|
+
* column existed). The stamp itself is written after the update, as root,
|
|
586
|
+
* by a plain UPDATE: through updateBy it would be a second update of the
|
|
587
|
+
* row and fire the "on update" workflow trigger a second time.
|
|
588
|
+
*/
|
|
589
|
+
@CaptureSpan()
|
|
590
|
+
protected override async onBeforeUpdate(
|
|
591
|
+
updateBy: UpdateBy<Model>,
|
|
592
|
+
): Promise<OnUpdate<Model>> {
|
|
593
|
+
const isUnsubscribed: unknown = (
|
|
594
|
+
updateBy.data as unknown as JSONObject | undefined
|
|
595
|
+
)?.["isUnsubscribed"];
|
|
596
|
+
|
|
597
|
+
const carryForward: UnsubscribedAtCarryForward = {
|
|
598
|
+
isUnsubscribed:
|
|
599
|
+
typeof isUnsubscribed === "boolean" ? isUnsubscribed : null,
|
|
600
|
+
subscriberIdsBeingUnsubscribed: [],
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
if (carryForward.isUnsubscribed === true) {
|
|
604
|
+
const matched: Array<Model> = await this.findBy({
|
|
605
|
+
query: updateBy.query,
|
|
606
|
+
select: {
|
|
607
|
+
_id: true,
|
|
608
|
+
isUnsubscribed: true,
|
|
609
|
+
},
|
|
610
|
+
skip: 0,
|
|
611
|
+
limit: LIMIT_MAX,
|
|
612
|
+
props: {
|
|
613
|
+
isRoot: true,
|
|
614
|
+
ignoreHooks: true,
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
carryForward.subscriberIdsBeingUnsubscribed = matched
|
|
619
|
+
.filter((subscriber: Model): boolean => {
|
|
620
|
+
return Boolean(subscriber._id) && subscriber.isUnsubscribed !== true;
|
|
621
|
+
})
|
|
622
|
+
.map((subscriber: Model): string => {
|
|
623
|
+
return subscriber._id!.toString();
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return { updateBy, carryForward };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
@CaptureSpan()
|
|
631
|
+
protected override async onUpdateSuccess(
|
|
632
|
+
onUpdate: OnUpdate<Model>,
|
|
633
|
+
updatedItemIds: Array<ObjectID>,
|
|
634
|
+
): Promise<OnUpdate<Model>> {
|
|
635
|
+
const carryForward: UnsubscribedAtCarryForward | undefined =
|
|
636
|
+
onUpdate.carryForward as UnsubscribedAtCarryForward | undefined;
|
|
637
|
+
|
|
638
|
+
if (!carryForward || carryForward.isUnsubscribed === null) {
|
|
639
|
+
return onUpdate;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const updatedIds: Array<string> = updatedItemIds.map(
|
|
643
|
+
(id: ObjectID): string => {
|
|
644
|
+
return id.toString();
|
|
645
|
+
},
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
if (carryForward.isUnsubscribed === true) {
|
|
649
|
+
const cancelledIds: Array<string> =
|
|
650
|
+
carryForward.subscriberIdsBeingUnsubscribed.filter(
|
|
651
|
+
(id: string): boolean => {
|
|
652
|
+
return updatedIds.includes(id);
|
|
653
|
+
},
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
if (cancelledIds.length > 0) {
|
|
657
|
+
await this.getRepository().manager.query(
|
|
658
|
+
`UPDATE "StatusPageSubscriber" SET "unsubscribedAt" = $1 WHERE "_id" = ANY($2::uuid[]) AND "isUnsubscribed" = true AND "unsubscribedAt" IS NULL`,
|
|
659
|
+
[OneUptimeDate.getCurrentDate(), cancelledIds],
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return onUpdate;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Subscribed again: the old date no longer describes this subscription.
|
|
667
|
+
if (updatedIds.length > 0) {
|
|
668
|
+
await this.getRepository().manager.query(
|
|
669
|
+
`UPDATE "StatusPageSubscriber" SET "unsubscribedAt" = NULL WHERE "_id" = ANY($1::uuid[]) AND "isUnsubscribed" = false AND "unsubscribedAt" IS NOT NULL`,
|
|
670
|
+
[updatedIds],
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return onUpdate;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/*
|
|
678
|
+
* Cancel a subscription, once. Returns whether this call cancelled it:
|
|
679
|
+
* false when it was already cancelled (or does not exist), which makes the
|
|
680
|
+
* unsubscribe link idempotent.
|
|
681
|
+
*
|
|
682
|
+
* ONE STATEMENT, ON PURPOSE: the WHERE clause's `"isUnsubscribed" = false`
|
|
683
|
+
* lets Postgres decide which of two simultaneous requests - a double click,
|
|
684
|
+
* a mail client that follows the confirmation twice - cancels the
|
|
685
|
+
* subscription, so its Unsubscribed At is written once and the team is told
|
|
686
|
+
* once. `RETURNING` is what tells this call whether it was the one.
|
|
687
|
+
*
|
|
688
|
+
* Written as raw SQL because no DatabaseService write both takes that
|
|
689
|
+
* predicate and reports what it matched. That skips the update hooks, so
|
|
690
|
+
* the "on update" workflow trigger and the realtime event a DatabaseService
|
|
691
|
+
* update fires are fired here, by hand, with the two columns this wrote.
|
|
692
|
+
*/
|
|
693
|
+
@CaptureSpan()
|
|
694
|
+
public async unsubscribe(data: {
|
|
695
|
+
subscriberId: ObjectID;
|
|
696
|
+
source: StatusPageSubscriberUnsubscribeSource;
|
|
697
|
+
}): Promise<boolean> {
|
|
698
|
+
const unsubscribedAt: Date = OneUptimeDate.getCurrentDate();
|
|
699
|
+
|
|
700
|
+
const rows: Array<JSONObject> = returnedRows(
|
|
701
|
+
await this.getRepository().manager.query(
|
|
702
|
+
`UPDATE "StatusPageSubscriber"
|
|
703
|
+
SET "isUnsubscribed" = true,
|
|
704
|
+
"unsubscribedAt" = $1,
|
|
705
|
+
"updatedAt" = CURRENT_TIMESTAMP
|
|
706
|
+
WHERE "_id" = $2
|
|
707
|
+
AND "isUnsubscribed" = false
|
|
708
|
+
AND "deletedAt" IS NULL
|
|
709
|
+
RETURNING "_id", "projectId"`,
|
|
710
|
+
[unsubscribedAt, data.subscriberId.toString()],
|
|
711
|
+
),
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
const row: JSONObject | undefined = rows[0];
|
|
715
|
+
|
|
716
|
+
if (!row || !row["_id"]) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const projectId: ObjectID | null = row["projectId"]
|
|
721
|
+
? new ObjectID(row["projectId"].toString())
|
|
722
|
+
: null;
|
|
723
|
+
|
|
724
|
+
if (projectId) {
|
|
725
|
+
if (this.getModel().enableWorkflowOn?.update) {
|
|
726
|
+
await this.onTriggerWorkflow(
|
|
727
|
+
data.subscriberId,
|
|
728
|
+
projectId,
|
|
729
|
+
"on-update",
|
|
730
|
+
{
|
|
731
|
+
updatedFields: {
|
|
732
|
+
isUnsubscribed: true,
|
|
733
|
+
unsubscribedAt: unsubscribedAt.toISOString(),
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
await this.onTriggerRealtime(
|
|
740
|
+
data.subscriberId,
|
|
741
|
+
projectId,
|
|
742
|
+
ModelEventType.Update,
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/*
|
|
747
|
+
* The subscription is cancelled either way; a failure to tell the team
|
|
748
|
+
* must not turn that into an error for the person who asked.
|
|
749
|
+
*/
|
|
750
|
+
try {
|
|
751
|
+
await this.notifyTeamOfUnsubscribe({
|
|
752
|
+
subscriberId: data.subscriberId,
|
|
753
|
+
source: data.source,
|
|
754
|
+
unsubscribedAt: unsubscribedAt,
|
|
755
|
+
});
|
|
756
|
+
} catch (err) {
|
|
757
|
+
logger.error(err, {
|
|
758
|
+
statusPageSubscriberId: data.subscriberId.toString(),
|
|
759
|
+
} as LogAttributes);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/*
|
|
766
|
+
* What the status page's unsubscribe page shows for a link, before anything
|
|
767
|
+
* is changed. Opening the page calls this; only a POST unsubscribes.
|
|
768
|
+
*
|
|
769
|
+
* Every way a link can be wrong - a malformed id or token, no such
|
|
770
|
+
* subscriber, a deleted one, one on another status page, a token that does
|
|
771
|
+
* not match - reads the same, Invalid, after the same constant-time token
|
|
772
|
+
* comparison, so the answer cannot be used to tell them apart.
|
|
773
|
+
*/
|
|
774
|
+
@CaptureSpan()
|
|
775
|
+
public async getUnsubscribeLinkDetails(data: {
|
|
776
|
+
statusPageId: string;
|
|
777
|
+
subscriberId: string;
|
|
778
|
+
token: string;
|
|
779
|
+
}): Promise<StatusPageSubscriberUnsubscribeDetails> {
|
|
780
|
+
const subscriber: Model | null =
|
|
781
|
+
await this.findSubscriberByUnsubscribeLink(data);
|
|
782
|
+
|
|
783
|
+
if (!subscriber) {
|
|
784
|
+
return { state: StatusPageSubscriberUnsubscribeState.Invalid };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const contact: StatusPageSubscriberContact | null =
|
|
788
|
+
StatusPageSubscriberUnsubscribe.describeContact(subscriber);
|
|
789
|
+
|
|
790
|
+
return {
|
|
791
|
+
state: subscriber.isUnsubscribed
|
|
792
|
+
? StatusPageSubscriberUnsubscribeState.Unsubscribed
|
|
793
|
+
: StatusPageSubscriberUnsubscribeState.Subscribed,
|
|
794
|
+
channel: contact?.channel,
|
|
795
|
+
contact: contact?.contact,
|
|
796
|
+
wasAddedByTeam: this.isAddedByTeam(subscriber),
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/*
|
|
801
|
+
* The unsubscribe page's POST: cancel the subscription the link belongs to.
|
|
802
|
+
* Idempotent - a second confirmation, or one for a subscription already
|
|
803
|
+
* cancelled some other way, answers Unsubscribed again and changes nothing.
|
|
804
|
+
* A bad link answers Invalid, exactly as getUnsubscribeLinkDetails does.
|
|
805
|
+
*/
|
|
806
|
+
@CaptureSpan()
|
|
807
|
+
public async unsubscribeWithLink(data: {
|
|
808
|
+
statusPageId: string;
|
|
809
|
+
subscriberId: string;
|
|
810
|
+
token: string;
|
|
811
|
+
}): Promise<StatusPageSubscriberUnsubscribeState> {
|
|
812
|
+
const subscriber: Model | null =
|
|
813
|
+
await this.findSubscriberByUnsubscribeLink(data);
|
|
814
|
+
|
|
815
|
+
if (!subscriber || !subscriber.id) {
|
|
816
|
+
return StatusPageSubscriberUnsubscribeState.Invalid;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (!subscriber.isUnsubscribed) {
|
|
820
|
+
await this.unsubscribe({
|
|
821
|
+
subscriberId: subscriber.id,
|
|
822
|
+
source: StatusPageSubscriberUnsubscribeSource.UnsubscribeLink,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return StatusPageSubscriberUnsubscribeState.Unsubscribed;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/*
|
|
830
|
+
* The subscriber a link belongs to, or null when the link is not good. The
|
|
831
|
+
* subscriber is found by its primary key, never by the token, and the token
|
|
832
|
+
* is then compared in constant time - a query on the token would let the
|
|
833
|
+
* database's own comparison time a guess. The comparison runs even when
|
|
834
|
+
* there is no subscriber to compare with.
|
|
835
|
+
*/
|
|
836
|
+
private async findSubscriberByUnsubscribeLink(data: {
|
|
837
|
+
statusPageId: string;
|
|
838
|
+
subscriberId: string;
|
|
839
|
+
token: string;
|
|
840
|
+
}): Promise<Model | null> {
|
|
841
|
+
const isWellFormed: boolean =
|
|
842
|
+
ObjectID.isValidUUID(data.statusPageId) &&
|
|
843
|
+
ObjectID.isValidUUID(data.subscriberId);
|
|
844
|
+
|
|
845
|
+
const subscriber: Model | null = isWellFormed
|
|
846
|
+
? await this.findOneBy({
|
|
847
|
+
query: {
|
|
848
|
+
_id: data.subscriberId,
|
|
849
|
+
statusPageId: new ObjectID(data.statusPageId),
|
|
850
|
+
},
|
|
851
|
+
select: {
|
|
852
|
+
_id: true,
|
|
853
|
+
statusPageId: true,
|
|
854
|
+
unsubscribeToken: true,
|
|
855
|
+
isUnsubscribed: true,
|
|
856
|
+
isAddedByTeam: true,
|
|
857
|
+
createdByUserId: true,
|
|
858
|
+
subscriberEmail: true,
|
|
859
|
+
subscriberPhone: true,
|
|
860
|
+
slackIncomingWebhookUrl: true,
|
|
861
|
+
slackWorkspaceName: true,
|
|
862
|
+
microsoftTeamsIncomingWebhookUrl: true,
|
|
863
|
+
microsoftTeamsWorkspaceName: true,
|
|
864
|
+
subscriberWebhook: true,
|
|
865
|
+
},
|
|
866
|
+
props: {
|
|
867
|
+
isRoot: true,
|
|
868
|
+
ignoreHooks: true,
|
|
869
|
+
},
|
|
870
|
+
})
|
|
871
|
+
: null;
|
|
872
|
+
|
|
873
|
+
const tokenMatches: boolean = StatusPageSubscriberUnsubscribeToken.matches({
|
|
874
|
+
stored: subscriber?.unsubscribeToken,
|
|
875
|
+
presented: data.token,
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
if (!subscriber || !tokenMatches) {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return subscriber;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/*
|
|
886
|
+
* Tell the team that a subscriber it added has unsubscribed itself (see
|
|
887
|
+
* StatusPageSubscriberUnsubscribeNotice for why). Subscribers people signed
|
|
888
|
+
* up for themselves on the status page tell nobody.
|
|
889
|
+
*
|
|
890
|
+
* A subscriber the team added is one with Is Added By Team - from the
|
|
891
|
+
* dashboard, with an API key or by a workflow - or, created before that
|
|
892
|
+
* column, with a creator (see isAddedByTeam).
|
|
893
|
+
*
|
|
894
|
+
* It goes to the status page's owners - its owner users and the members of
|
|
895
|
+
* its owner teams - and to the teammate who added the subscriber, when a
|
|
896
|
+
* teammate did, each once and only while they are still members of the
|
|
897
|
+
* project, as one plain email through MailService rather than a
|
|
898
|
+
* notification rule: it is about a subscriber list, not an event anyone is
|
|
899
|
+
* on call for. A page with no owners tells only the teammate who added the
|
|
900
|
+
* subscriber; one an API key or a workflow added, on a page with no owners,
|
|
901
|
+
* tells nobody.
|
|
902
|
+
*/
|
|
903
|
+
@CaptureSpan()
|
|
904
|
+
public async notifyTeamOfUnsubscribe(data: {
|
|
905
|
+
subscriberId: ObjectID;
|
|
906
|
+
source: StatusPageSubscriberUnsubscribeSource;
|
|
907
|
+
unsubscribedAt: Date;
|
|
908
|
+
}): Promise<void> {
|
|
909
|
+
const subscriber: Model | null = await this.findOneById({
|
|
910
|
+
id: data.subscriberId,
|
|
911
|
+
select: {
|
|
912
|
+
_id: true,
|
|
913
|
+
projectId: true,
|
|
914
|
+
statusPageId: true,
|
|
915
|
+
isAddedByTeam: true,
|
|
916
|
+
createdByUserId: true,
|
|
917
|
+
createdAt: true,
|
|
918
|
+
subscriberEmail: true,
|
|
919
|
+
subscriberPhone: true,
|
|
920
|
+
slackIncomingWebhookUrl: true,
|
|
921
|
+
slackWorkspaceName: true,
|
|
922
|
+
microsoftTeamsIncomingWebhookUrl: true,
|
|
923
|
+
microsoftTeamsWorkspaceName: true,
|
|
924
|
+
subscriberWebhook: true,
|
|
925
|
+
},
|
|
926
|
+
props: {
|
|
927
|
+
isRoot: true,
|
|
928
|
+
ignoreHooks: true,
|
|
929
|
+
},
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
if (
|
|
933
|
+
!subscriber ||
|
|
934
|
+
!this.isAddedByTeam(subscriber) ||
|
|
935
|
+
!subscriber.projectId ||
|
|
936
|
+
!subscriber.statusPageId
|
|
937
|
+
) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const contact: StatusPageSubscriberContact | null =
|
|
942
|
+
StatusPageSubscriberUnsubscribe.describeContact(subscriber, {
|
|
943
|
+
maskPhone: false,
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
if (!contact) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const statusPage: StatusPage | null = await StatusPageService.findOneById({
|
|
951
|
+
id: subscriber.statusPageId,
|
|
952
|
+
select: {
|
|
953
|
+
_id: true,
|
|
954
|
+
name: true,
|
|
955
|
+
pageTitle: true,
|
|
956
|
+
},
|
|
957
|
+
props: {
|
|
958
|
+
isRoot: true,
|
|
959
|
+
ignoreHooks: true,
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
if (!statusPage) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Only a teammate's create has a creator; an API key's or a workflow's does not.
|
|
968
|
+
const creator: User | null = subscriber.createdByUserId
|
|
969
|
+
? await UserService.findOneById({
|
|
970
|
+
id: subscriber.createdByUserId,
|
|
971
|
+
select: {
|
|
972
|
+
_id: true,
|
|
973
|
+
name: true,
|
|
974
|
+
email: true,
|
|
975
|
+
},
|
|
976
|
+
props: {
|
|
977
|
+
isRoot: true,
|
|
978
|
+
},
|
|
979
|
+
})
|
|
980
|
+
: null;
|
|
981
|
+
|
|
982
|
+
const candidates: Array<User> = [
|
|
983
|
+
...(await StatusPageService.findOwners(subscriber.statusPageId)),
|
|
984
|
+
...(creator ? [creator] : []),
|
|
985
|
+
];
|
|
986
|
+
|
|
987
|
+
const recipients: Array<User> = [];
|
|
988
|
+
const seenUserIds: Set<string> = new Set<string>();
|
|
989
|
+
|
|
990
|
+
for (const user of await TeamMemberService.filterUsersToProjectMembers({
|
|
991
|
+
projectId: subscriber.projectId,
|
|
992
|
+
users: candidates,
|
|
993
|
+
})) {
|
|
994
|
+
const userId: string = user.id?.toString().toLowerCase() || "";
|
|
995
|
+
|
|
996
|
+
if (!userId || !user.email || seenUserIds.has(userId)) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
seenUserIds.add(userId);
|
|
1001
|
+
recipients.push(user);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (recipients.length === 0) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (!(await this.isFirstUnsubscribeNoticeInWindow(subscriber.id!))) {
|
|
1009
|
+
logger.debug(
|
|
1010
|
+
`The team was told about subscriber ${subscriber.id!.toString()} unsubscribing within the last ${StatusPageSubscriberUnsubscribeNotice.noticeWindowInHours} hours; not telling it again.`,
|
|
1011
|
+
{
|
|
1012
|
+
projectId: subscriber.projectId.toString(),
|
|
1013
|
+
statusPageId: subscriber.statusPageId.toString(),
|
|
1014
|
+
} as LogAttributes,
|
|
1015
|
+
);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const subscriberListUrl: string = (
|
|
1020
|
+
await StatusPageService.getStatusPageLinkInDashboard(
|
|
1021
|
+
subscriber.projectId,
|
|
1022
|
+
subscriber.statusPageId,
|
|
1023
|
+
)
|
|
1024
|
+
)
|
|
1025
|
+
.addRoute(
|
|
1026
|
+
`/${StatusPageSubscriberUnsubscribeNotice.getSubscriberListRoute(
|
|
1027
|
+
contact.channel,
|
|
1028
|
+
)}`,
|
|
1029
|
+
)
|
|
1030
|
+
.toString();
|
|
1031
|
+
|
|
1032
|
+
const email: StatusPageSubscriberUnsubscribeNoticeEmail =
|
|
1033
|
+
StatusPageSubscriberUnsubscribeNotice.build({
|
|
1034
|
+
statusPageName:
|
|
1035
|
+
statusPage.name || statusPage.pageTitle || "Status Page",
|
|
1036
|
+
contact: contact,
|
|
1037
|
+
source: data.source,
|
|
1038
|
+
unsubscribedAt: data.unsubscribedAt,
|
|
1039
|
+
addedByName: creator?.name?.toString() || creator?.email?.toString(),
|
|
1040
|
+
addedAt: subscriber.createdAt,
|
|
1041
|
+
subscriberListUrl: subscriberListUrl,
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
for (const recipient of recipients) {
|
|
1045
|
+
MailService.sendMail(
|
|
1046
|
+
{
|
|
1047
|
+
toEmail: recipient.email!,
|
|
1048
|
+
templateType: EmailTemplateType.SimpleMessage,
|
|
1049
|
+
vars: {
|
|
1050
|
+
subject: email.subject,
|
|
1051
|
+
message: email.message,
|
|
1052
|
+
},
|
|
1053
|
+
subject: email.subject,
|
|
1054
|
+
isSubjectLiteral: true,
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
projectId: subscriber.projectId,
|
|
1058
|
+
userId: recipient.id!,
|
|
1059
|
+
},
|
|
1060
|
+
).catch((err: Error) => {
|
|
1061
|
+
logger.error(err, {
|
|
1062
|
+
projectId: subscriber.projectId?.toString(),
|
|
1063
|
+
statusPageId: subscriber.statusPageId?.toString(),
|
|
1064
|
+
} as LogAttributes);
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/*
|
|
1070
|
+
* The team is told about a subscriber unsubscribing at most once per
|
|
1071
|
+
* window. The public manage page both re-subscribes a subscriber and
|
|
1072
|
+
* cancels it again, and it is open to anyone who can see the status page
|
|
1073
|
+
* and knows the subscriber's id (it is in the manage link), so without this
|
|
1074
|
+
* a loop of the two would email every owner of the page on every turn.
|
|
1075
|
+
*
|
|
1076
|
+
* One atomic SET NX per subscriber: exactly one notice wins the window
|
|
1077
|
+
* however many cancellations arrive together. If the cache cannot be
|
|
1078
|
+
* reached the notice goes out: telling the team twice is better than not
|
|
1079
|
+
* at all.
|
|
1080
|
+
*/
|
|
1081
|
+
private async isFirstUnsubscribeNoticeInWindow(
|
|
1082
|
+
subscriberId: ObjectID,
|
|
1083
|
+
): Promise<boolean> {
|
|
1084
|
+
try {
|
|
1085
|
+
return await GlobalCache.setStringIfNotExists(
|
|
1086
|
+
StatusPageSubscriberUnsubscribeNotice.noticeCacheNamespace,
|
|
1087
|
+
subscriberId.toString().toLowerCase(),
|
|
1088
|
+
OneUptimeDate.getCurrentDate().toISOString(),
|
|
1089
|
+
{
|
|
1090
|
+
expiresInSeconds:
|
|
1091
|
+
StatusPageSubscriberUnsubscribeNotice.noticeWindowInHours * 60 * 60,
|
|
1092
|
+
},
|
|
1093
|
+
);
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
logger.warn(
|
|
1096
|
+
`Could not check when the team was last told about subscriber ${subscriberId.toString()} unsubscribing; telling it: ${err instanceof Error ? err.message : String(err)}`,
|
|
1097
|
+
);
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
382
1102
|
@CaptureSpan()
|
|
383
1103
|
protected override async onCreateSuccess(
|
|
384
1104
|
onCreate: OnCreate<Model>,
|
|
385
1105
|
createdItem: Model,
|
|
386
1106
|
): Promise<Model> {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
1107
|
+
// Not the item itself: it carries the unsubscribe token.
|
|
1108
|
+
logger.debug(
|
|
1109
|
+
`onCreateSuccess called with createdItem ${createdItem.id?.toString()}.`,
|
|
1110
|
+
{
|
|
1111
|
+
projectId: createdItem.projectId?.toString(),
|
|
1112
|
+
} as LogAttributes,
|
|
1113
|
+
);
|
|
393
1114
|
|
|
394
1115
|
if (!createdItem.statusPageId) {
|
|
395
1116
|
logger.debug("Status Page ID is missing in createdItem.", {
|
|
@@ -413,13 +1134,11 @@ export class Service extends DatabaseService<Model> {
|
|
|
413
1134
|
projectId: createdItem.projectId?.toString(),
|
|
414
1135
|
} as LogAttributes);
|
|
415
1136
|
|
|
1137
|
+
// createdItem carries the unsubscribe token onBeforeCreate minted.
|
|
416
1138
|
const unsubscribeLink: string = this.getUnsubscribeLink(
|
|
417
1139
|
URL.fromString(statusPageURL),
|
|
418
|
-
createdItem
|
|
1140
|
+
createdItem,
|
|
419
1141
|
).toString();
|
|
420
|
-
logger.debug(`Unsubscribe Link: ${unsubscribeLink}`, {
|
|
421
|
-
projectId: createdItem.projectId?.toString(),
|
|
422
|
-
} as LogAttributes);
|
|
423
1142
|
|
|
424
1143
|
if (
|
|
425
1144
|
createdItem.statusPageId &&
|
|
@@ -462,10 +1181,24 @@ export class Service extends DatabaseService<Model> {
|
|
|
462
1181
|
{ projectId: createdItem.projectId?.toString() } as LogAttributes,
|
|
463
1182
|
);
|
|
464
1183
|
|
|
1184
|
+
/*
|
|
1185
|
+
* On a public status page the SMS keeps the shorter manage link, which
|
|
1186
|
+
* works there without signing in: an SMS is billed by the segment (see
|
|
1187
|
+
* StatusPageSubscriberUnsubscribe.buildSmsLink).
|
|
1188
|
+
*/
|
|
1189
|
+
const smsUnsubscribeLink: string =
|
|
1190
|
+
StatusPageSubscriberUnsubscribe.buildSmsLink({
|
|
1191
|
+
isPublicStatusPage: (onCreate.carryForward as StatusPage | undefined)
|
|
1192
|
+
?.isPublicStatusPage,
|
|
1193
|
+
statusPageUrl: statusPageURL,
|
|
1194
|
+
subscriberId: createdItem.id!,
|
|
1195
|
+
unsubscribeUrl: unsubscribeLink,
|
|
1196
|
+
});
|
|
1197
|
+
|
|
465
1198
|
SmsService.sendSms(
|
|
466
1199
|
{
|
|
467
1200
|
to: createdItem.subscriberPhone,
|
|
468
|
-
message: `You have been subscribed to ${statusPageName}. To unsubscribe, click on the link: ${
|
|
1201
|
+
message: `You have been subscribed to ${statusPageName}. To unsubscribe, click on the link: ${smsUnsubscribeLink}`,
|
|
469
1202
|
},
|
|
470
1203
|
{
|
|
471
1204
|
projectId: createdItem.projectId,
|
|
@@ -528,7 +1261,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
528
1261
|
**You have successfully subscribed to receive status updates!**
|
|
529
1262
|
|
|
530
1263
|
🔗 **Status Page:** [${statusPageName}](${statusPageURL})
|
|
531
|
-
|
|
1264
|
+
🔕 **Unsubscribe:** [Stop these notifications](${unsubscribeLink})
|
|
532
1265
|
|
|
533
1266
|
You will receive real-time notifications for:
|
|
534
1267
|
• Incidents and outages
|
|
@@ -611,7 +1344,7 @@ Stay informed about service availability! 🚀`;
|
|
|
611
1344
|
**You have successfully subscribed to receive status updates!**
|
|
612
1345
|
|
|
613
1346
|
🔗 **Status Page:** [${statusPageName}](${statusPageURL})
|
|
614
|
-
|
|
1347
|
+
🔕 **Unsubscribe:** [Stop these notifications](${unsubscribeLink})
|
|
615
1348
|
|
|
616
1349
|
You will receive real-time notifications for:
|
|
617
1350
|
• Incidents and outages
|
|
@@ -675,13 +1408,14 @@ Stay informed about service availability! 🚀`;
|
|
|
675
1408
|
projectId: true,
|
|
676
1409
|
subscriptionConfirmationToken: true,
|
|
677
1410
|
sendYouHaveSubscribedMessage: true,
|
|
1411
|
+
unsubscribeToken: true,
|
|
678
1412
|
},
|
|
679
1413
|
props: {
|
|
680
1414
|
isRoot: true,
|
|
681
1415
|
ignoreHooks: true,
|
|
682
1416
|
},
|
|
683
1417
|
});
|
|
684
|
-
logger.debug(`Found Subscriber: ${
|
|
1418
|
+
logger.debug(`Found Subscriber: ${subscriber?.id?.toString()}`, {
|
|
685
1419
|
statusPageSubscriberId: data.subscriberId?.toString(),
|
|
686
1420
|
} as LogAttributes);
|
|
687
1421
|
|
|
@@ -693,6 +1427,9 @@ Stay informed about service availability! 🚀`;
|
|
|
693
1427
|
return;
|
|
694
1428
|
}
|
|
695
1429
|
|
|
1430
|
+
// Its unsubscribe link needs its token (see ensureUnsubscribeTokens).
|
|
1431
|
+
await this.ensureUnsubscribeTokens([subscriber]);
|
|
1432
|
+
|
|
696
1433
|
const statusPage: StatusPage | null = await StatusPageService.findOneBy({
|
|
697
1434
|
query: {
|
|
698
1435
|
_id: subscriber.statusPageId.toString(),
|
|
@@ -777,11 +1514,8 @@ Stay informed about service availability! 🚀`;
|
|
|
777
1514
|
) {
|
|
778
1515
|
const unsubscribeUrl: string = this.getUnsubscribeLink(
|
|
779
1516
|
URL.fromString(statusPageURL),
|
|
780
|
-
subscriber
|
|
1517
|
+
subscriber,
|
|
781
1518
|
).toString();
|
|
782
|
-
logger.debug(`Unsubscribe URL: ${unsubscribeUrl}`, {
|
|
783
|
-
statusPageSubscriberId: data.subscriberId?.toString(),
|
|
784
|
-
} as LogAttributes);
|
|
785
1519
|
|
|
786
1520
|
const customTemplate: StatusPageSubscriberNotificationTemplate | null =
|
|
787
1521
|
await StatusPageSubscriberNotificationTemplateService.getTemplateForStatusPage(
|
|
@@ -806,8 +1540,9 @@ Stay informed about service availability! 🚀`;
|
|
|
806
1540
|
* pattern used elsewhere — without custom SMTP we keep the styled
|
|
807
1541
|
* OneUptime default so emails still look right out-of-the-box).
|
|
808
1542
|
*/
|
|
1543
|
+
// The body is HTML, so the (plain-text) values are escaped into it.
|
|
809
1544
|
const compiledBody: string =
|
|
810
|
-
StatusPageSubscriberNotificationTemplateServiceClass.
|
|
1545
|
+
StatusPageSubscriberNotificationTemplateServiceClass.compileEmailBodyTemplate(
|
|
811
1546
|
customTemplate.templateBody,
|
|
812
1547
|
templateVariables,
|
|
813
1548
|
);
|
|
@@ -909,13 +1644,14 @@ Stay informed about service availability! 🚀`;
|
|
|
909
1644
|
subscriberPhone: true,
|
|
910
1645
|
projectId: true,
|
|
911
1646
|
sendYouHaveSubscribedMessage: true,
|
|
1647
|
+
unsubscribeToken: true,
|
|
912
1648
|
},
|
|
913
1649
|
props: {
|
|
914
1650
|
isRoot: true,
|
|
915
1651
|
ignoreHooks: true,
|
|
916
1652
|
},
|
|
917
1653
|
});
|
|
918
|
-
logger.debug(`Found Subscriber: ${
|
|
1654
|
+
logger.debug(`Found Subscriber: ${subscriber?.id?.toString()}`, {
|
|
919
1655
|
statusPageSubscriberId: data.subscriberId?.toString(),
|
|
920
1656
|
} as LogAttributes);
|
|
921
1657
|
|
|
@@ -927,6 +1663,9 @@ Stay informed about service availability! 🚀`;
|
|
|
927
1663
|
return;
|
|
928
1664
|
}
|
|
929
1665
|
|
|
1666
|
+
// Its unsubscribe link needs its token (see ensureUnsubscribeTokens).
|
|
1667
|
+
await this.ensureUnsubscribeTokens([subscriber]);
|
|
1668
|
+
|
|
930
1669
|
const statusPage: StatusPage | null = await StatusPageService.findOneBy({
|
|
931
1670
|
query: {
|
|
932
1671
|
_id: subscriber.statusPageId.toString(),
|
|
@@ -997,11 +1736,8 @@ Stay informed about service availability! 🚀`;
|
|
|
997
1736
|
|
|
998
1737
|
const unsubscribeLink: string = this.getUnsubscribeLink(
|
|
999
1738
|
URL.fromString(statusPageURL),
|
|
1000
|
-
subscriber
|
|
1739
|
+
subscriber,
|
|
1001
1740
|
).toString();
|
|
1002
|
-
logger.debug(`Unsubscribe Link: ${unsubscribeLink}`, {
|
|
1003
|
-
statusPageSubscriberId: data.subscriberId?.toString(),
|
|
1004
|
-
} as LogAttributes);
|
|
1005
1741
|
|
|
1006
1742
|
if (
|
|
1007
1743
|
subscriber.statusPageId &&
|
|
@@ -1029,8 +1765,9 @@ Stay informed about service availability! 🚀`;
|
|
|
1029
1765
|
};
|
|
1030
1766
|
|
|
1031
1767
|
if (customTemplate?.templateBody && statusPage.smtpConfig) {
|
|
1768
|
+
// The body is HTML, so the (plain-text) values are escaped into it.
|
|
1032
1769
|
const compiledBody: string =
|
|
1033
|
-
StatusPageSubscriberNotificationTemplateServiceClass.
|
|
1770
|
+
StatusPageSubscriberNotificationTemplateServiceClass.compileEmailBodyTemplate(
|
|
1034
1771
|
customTemplate.templateBody,
|
|
1035
1772
|
templateVariables,
|
|
1036
1773
|
);
|
|
@@ -1139,10 +1876,26 @@ Stay informed about service availability! 🚀`;
|
|
|
1139
1876
|
return confirmSubscriptionLink;
|
|
1140
1877
|
}
|
|
1141
1878
|
|
|
1879
|
+
/*
|
|
1880
|
+
* The confirmed, still-subscribed subscribers of a status page, in _id
|
|
1881
|
+
* order, at most `limit` (LIMIT_MAX by default) of them.
|
|
1882
|
+
*
|
|
1883
|
+
* One read stops at the limit, so a page with more subscribers than that
|
|
1884
|
+
* is read in batches: pass the _id of the last subscriber of one batch as
|
|
1885
|
+
* `afterId` to read the next, until a batch comes back smaller than the
|
|
1886
|
+
* limit (SubscriberNotificationFanOut does this for the subscriber jobs).
|
|
1887
|
+
* The cursor is an _id rather than an offset, so a subscriber who
|
|
1888
|
+
* unsubscribes while a send is part-way through cannot shift the rows
|
|
1889
|
+
* after it into a batch already read, and nobody is skipped.
|
|
1890
|
+
*/
|
|
1142
1891
|
@CaptureSpan()
|
|
1143
1892
|
public async getSubscribersByStatusPage(
|
|
1144
1893
|
statusPageId: ObjectID,
|
|
1145
1894
|
props: DatabaseCommonInteractionProps,
|
|
1895
|
+
options?: {
|
|
1896
|
+
afterId?: ObjectID | undefined;
|
|
1897
|
+
limit?: number | undefined;
|
|
1898
|
+
},
|
|
1146
1899
|
): Promise<Array<Model>> {
|
|
1147
1900
|
logger.debug("getSubscribersByStatusPage called with statusPageId:", {
|
|
1148
1901
|
statusPageId: statusPageId?.toString(),
|
|
@@ -1162,6 +1915,9 @@ Stay informed about service availability! 🚀`;
|
|
|
1162
1915
|
statusPageId: statusPageId,
|
|
1163
1916
|
isUnsubscribed: false,
|
|
1164
1917
|
isSubscriptionConfirmed: true,
|
|
1918
|
+
...(options?.afterId
|
|
1919
|
+
? { _id: QueryHelper.greaterThan(options.afterId) }
|
|
1920
|
+
: {}),
|
|
1165
1921
|
},
|
|
1166
1922
|
select: {
|
|
1167
1923
|
_id: true,
|
|
@@ -1174,51 +1930,380 @@ Stay informed about service availability! 🚀`;
|
|
|
1174
1930
|
statusPageResources: true,
|
|
1175
1931
|
isSubscribedToAllEventTypes: true,
|
|
1176
1932
|
statusPageEventTypes: true,
|
|
1933
|
+
// Every sender puts this subscriber's unsubscribe link in its message.
|
|
1934
|
+
unsubscribeToken: true,
|
|
1935
|
+
/*
|
|
1936
|
+
* Whether the team added this subscriber rather than it signing up
|
|
1937
|
+
* on the status page (see isAddedByTeam).
|
|
1938
|
+
*/
|
|
1939
|
+
isAddedByTeam: true,
|
|
1940
|
+
createdByUserId: true,
|
|
1941
|
+
},
|
|
1942
|
+
// A stable order, which the afterId cursor reads on from.
|
|
1943
|
+
sort: {
|
|
1944
|
+
_id: SortOrder.Ascending,
|
|
1177
1945
|
},
|
|
1178
1946
|
skip: 0,
|
|
1179
|
-
limit: LIMIT_MAX,
|
|
1947
|
+
limit: options?.limit || LIMIT_MAX,
|
|
1180
1948
|
props: props,
|
|
1181
1949
|
});
|
|
1182
1950
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1951
|
+
await this.ensureUnsubscribeTokens(subscribers);
|
|
1952
|
+
|
|
1953
|
+
/*
|
|
1954
|
+
* The count, not the rows: each row now carries its unsubscribe token,
|
|
1955
|
+
* which is a credential and has no place in a log line.
|
|
1956
|
+
*/
|
|
1957
|
+
logger.debug(`Found ${subscribers.length} subscribers.`, {
|
|
1187
1958
|
statusPageId: statusPageId?.toString(),
|
|
1188
1959
|
} as LogAttributes);
|
|
1189
1960
|
|
|
1190
1961
|
return subscribers;
|
|
1191
1962
|
}
|
|
1192
1963
|
|
|
1964
|
+
/*
|
|
1965
|
+
* How many subscribers of each status page each channel reaches, for the
|
|
1966
|
+
* audience summary shown before an incident or a public note is sent
|
|
1967
|
+
* (IncidentSubscriberAudience). The same subscribers getSubscribersByStatusPage
|
|
1968
|
+
* sends to - confirmed, not unsubscribed - counted per channel in one
|
|
1969
|
+
* aggregate query rather than read out: it never loads an address.
|
|
1970
|
+
*
|
|
1971
|
+
* A channel counts a subscriber whenever the job would send on it, which is
|
|
1972
|
+
* whenever that column holds a value; a subscription with both an email and
|
|
1973
|
+
* a phone counts once on each. Rows are pinned to the project, whatever ids
|
|
1974
|
+
* the caller passes.
|
|
1975
|
+
*
|
|
1976
|
+
* Keyed by the lower-cased status page id. A page with no active subscriber
|
|
1977
|
+
* has no entry.
|
|
1978
|
+
*/
|
|
1979
|
+
@CaptureSpan()
|
|
1980
|
+
public async countActiveSubscribersByChannel(data: {
|
|
1981
|
+
projectId: ObjectID;
|
|
1982
|
+
statusPageIds: Array<ObjectID>;
|
|
1983
|
+
}): Promise<Dictionary<IncidentSubscriberAudienceCounts>> {
|
|
1984
|
+
const counts: Dictionary<IncidentSubscriberAudienceCounts> = {};
|
|
1985
|
+
|
|
1986
|
+
const statusPageIds: Array<string> = [];
|
|
1987
|
+
|
|
1988
|
+
for (const statusPageId of data.statusPageIds) {
|
|
1989
|
+
const id: string = statusPageId.toString().trim().toLowerCase();
|
|
1990
|
+
|
|
1991
|
+
if (id && !statusPageIds.includes(id)) {
|
|
1992
|
+
statusPageIds.push(id);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
if (statusPageIds.length === 0) {
|
|
1997
|
+
return counts;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
const rows: Array<{
|
|
2001
|
+
statusPageId: string;
|
|
2002
|
+
email: string;
|
|
2003
|
+
sms: string;
|
|
2004
|
+
slack: string;
|
|
2005
|
+
microsoftTeams: string;
|
|
2006
|
+
webhook: string;
|
|
2007
|
+
}> = await this.getRepository().manager.query(
|
|
2008
|
+
`SELECT
|
|
2009
|
+
"statusPageId"::text AS "statusPageId",
|
|
2010
|
+
COUNT(*) FILTER (WHERE NULLIF(TRIM("subscriberEmail"), '') IS NOT NULL)::text AS "email",
|
|
2011
|
+
COUNT(*) FILTER (WHERE NULLIF(TRIM("subscriberPhone"), '') IS NOT NULL)::text AS "sms",
|
|
2012
|
+
COUNT(*) FILTER (WHERE NULLIF(TRIM("slackIncomingWebhookUrl"), '') IS NOT NULL)::text AS "slack",
|
|
2013
|
+
COUNT(*) FILTER (WHERE NULLIF(TRIM("microsoftTeamsIncomingWebhookUrl"), '') IS NOT NULL)::text AS "microsoftTeams",
|
|
2014
|
+
COUNT(*) FILTER (WHERE NULLIF(TRIM("subscriberWebhook"), '') IS NOT NULL)::text AS "webhook"
|
|
2015
|
+
FROM "StatusPageSubscriber"
|
|
2016
|
+
WHERE "projectId" = $1
|
|
2017
|
+
AND "statusPageId" = ANY($2::uuid[])
|
|
2018
|
+
AND "isUnsubscribed" = false
|
|
2019
|
+
AND "isSubscriptionConfirmed" = true
|
|
2020
|
+
AND "deletedAt" IS NULL
|
|
2021
|
+
GROUP BY "statusPageId"`,
|
|
2022
|
+
[data.projectId.toString(), statusPageIds],
|
|
2023
|
+
);
|
|
2024
|
+
|
|
2025
|
+
const toCount: (value: string | undefined) => number = (
|
|
2026
|
+
value: string | undefined,
|
|
2027
|
+
): number => {
|
|
2028
|
+
const count: number = parseInt(value || "0", 10);
|
|
2029
|
+
return Number.isFinite(count) && count > 0 ? count : 0;
|
|
2030
|
+
};
|
|
2031
|
+
|
|
2032
|
+
for (const row of rows) {
|
|
2033
|
+
counts[row.statusPageId.toLowerCase()] = {
|
|
2034
|
+
email: toCount(row.email),
|
|
2035
|
+
sms: toCount(row.sms),
|
|
2036
|
+
slack: toCount(row.slack),
|
|
2037
|
+
microsoftTeams: toCount(row.microsoftTeams),
|
|
2038
|
+
webhook: toCount(row.webhook),
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
return counts;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
/*
|
|
2046
|
+
* The link to a subscriber's unsubscribe page, which every notification to
|
|
2047
|
+
* it carries: {statusPageUrl}/unsubscribe/{id}-{token}. It works on public
|
|
2048
|
+
* and private status pages alike and without signing in, and it only ever
|
|
2049
|
+
* asks - the page unsubscribes when its reader confirms (see
|
|
2050
|
+
* Common/Types/StatusPage/StatusPageSubscriberUnsubscribe).
|
|
2051
|
+
*
|
|
2052
|
+
* Pass the subscriber read with its unsubscribeToken: every loader that
|
|
2053
|
+
* feeds a sender selects it (getSubscribersByStatusPage and the subscribe
|
|
2054
|
+
* and manage emails). A subscriber without one still gets a link, to the
|
|
2055
|
+
* page that says the link is out of date, rather than none; that is logged,
|
|
2056
|
+
* because only a subscriber created around the migration that added the
|
|
2057
|
+
* token, or one written with hooks skipped, can lack it.
|
|
2058
|
+
*/
|
|
1193
2059
|
public getUnsubscribeLink(
|
|
2060
|
+
statusPageUrl: URL,
|
|
2061
|
+
subscriber: Pick<Model, "_id" | "id" | "unsubscribeToken">,
|
|
2062
|
+
): URL {
|
|
2063
|
+
const subscriberId: ObjectID | null = subscriber.id;
|
|
2064
|
+
|
|
2065
|
+
if (!subscriberId) {
|
|
2066
|
+
throw new BadDataException(
|
|
2067
|
+
"A subscriber id is required to build an unsubscribe link.",
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
if (
|
|
2072
|
+
!StatusPageSubscriberUnsubscribe.isWellFormedToken(
|
|
2073
|
+
subscriber.unsubscribeToken,
|
|
2074
|
+
)
|
|
2075
|
+
) {
|
|
2076
|
+
logger.error(
|
|
2077
|
+
`Status page subscriber ${subscriberId.toString()} has no unsubscribe token; its unsubscribe link leads to the out-of-date link page.`,
|
|
2078
|
+
{
|
|
2079
|
+
statusPageSubscriberId: subscriberId.toString(),
|
|
2080
|
+
} as LogAttributes,
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
return StatusPageSubscriberUnsubscribe.buildLink({
|
|
2085
|
+
statusPageUrl: statusPageUrl,
|
|
2086
|
+
subscriberId: subscriberId,
|
|
2087
|
+
unsubscribeToken: subscriber.unsubscribeToken,
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
/*
|
|
2092
|
+
* The subscriber's Update Subscription page, where it can choose resources
|
|
2093
|
+
* and event types (and unsubscribe). The "manage your subscription" email a
|
|
2094
|
+
* visitor asks for from the Subscribe page links here. On a private status
|
|
2095
|
+
* page it needs a signed-in visitor, like the rest of the page.
|
|
2096
|
+
*/
|
|
2097
|
+
public getManageSubscriptionLink(
|
|
1194
2098
|
statusPageUrl: URL,
|
|
1195
2099
|
statusPageSubscriberId: ObjectID,
|
|
1196
2100
|
): URL {
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
} as LogAttributes);
|
|
1203
|
-
logger.debug("statusPageSubscriberId:", {
|
|
1204
|
-
statusPageSubscriberId: statusPageSubscriberId?.toString(),
|
|
1205
|
-
} as LogAttributes);
|
|
1206
|
-
logger.debug(statusPageSubscriberId, {
|
|
1207
|
-
statusPageSubscriberId: statusPageSubscriberId?.toString(),
|
|
1208
|
-
} as LogAttributes);
|
|
2101
|
+
return StatusPageSubscriberUnsubscribe.buildManageSubscriptionLink({
|
|
2102
|
+
statusPageUrl: statusPageUrl,
|
|
2103
|
+
subscriberId: statusPageSubscriberId,
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
1209
2106
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
2107
|
+
/*
|
|
2108
|
+
* Give every subscriber in the list an unsubscribe token it lacks, in place,
|
|
2109
|
+
* so the links built from them work. Every create mints one, and the
|
|
2110
|
+
* BackfillStatusPageSubscriberUnsubscribeColumns data migration gives one
|
|
2111
|
+
* to every subscriber that existed before; this covers the ones that
|
|
2112
|
+
* migration has not reached yet (it runs after the upgrade, in batches), a
|
|
2113
|
+
* subscriber created in between, and one written with hooks skipped.
|
|
2114
|
+
*
|
|
2115
|
+
* One statement per batch of subscribers, not per subscriber: until the
|
|
2116
|
+
* backfill has run, a whole page's list can be missing its tokens.
|
|
2117
|
+
*
|
|
2118
|
+
* The write only fills an empty column, so two senders racing on the same
|
|
2119
|
+
* subscriber - or a sender and the backfill - cannot hand out two different
|
|
2120
|
+
* tokens: the loser reads back the winner's.
|
|
2121
|
+
*/
|
|
2122
|
+
@CaptureSpan()
|
|
2123
|
+
public async ensureUnsubscribeTokens(
|
|
2124
|
+
subscribers: Array<Model>,
|
|
2125
|
+
): Promise<void> {
|
|
2126
|
+
// The rows that lack one, by id: a list can hold the same subscriber twice.
|
|
2127
|
+
const missing: Map<string, Array<Model>> = new Map<string, Array<Model>>();
|
|
1213
2128
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
2129
|
+
for (const subscriber of subscribers) {
|
|
2130
|
+
if (
|
|
2131
|
+
!subscriber._id ||
|
|
2132
|
+
StatusPageSubscriberUnsubscribe.isWellFormedToken(
|
|
2133
|
+
subscriber.unsubscribeToken,
|
|
2134
|
+
)
|
|
2135
|
+
) {
|
|
2136
|
+
continue;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
const id: string = subscriber._id.toString().toLowerCase();
|
|
2140
|
+
missing.set(id, [...(missing.get(id) || []), subscriber]);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
const ids: Array<string> = Array.from(missing.keys());
|
|
2144
|
+
|
|
2145
|
+
for (
|
|
2146
|
+
let start: number = 0;
|
|
2147
|
+
start < ids.length;
|
|
2148
|
+
start += UNSUBSCRIBE_COLUMNS_BATCH_SIZE
|
|
2149
|
+
) {
|
|
2150
|
+
const batch: Array<string> = ids.slice(
|
|
2151
|
+
start,
|
|
2152
|
+
start + UNSUBSCRIBE_COLUMNS_BATCH_SIZE,
|
|
2153
|
+
);
|
|
2154
|
+
|
|
2155
|
+
await this.fillMissingUnsubscribeTokens(batch);
|
|
1220
2156
|
|
|
1221
|
-
|
|
2157
|
+
const stored: Array<Model> = await this.findBy({
|
|
2158
|
+
query: {
|
|
2159
|
+
_id: QueryHelper.any(batch),
|
|
2160
|
+
},
|
|
2161
|
+
select: {
|
|
2162
|
+
_id: true,
|
|
2163
|
+
unsubscribeToken: true,
|
|
2164
|
+
},
|
|
2165
|
+
skip: 0,
|
|
2166
|
+
limit: batch.length,
|
|
2167
|
+
props: {
|
|
2168
|
+
isRoot: true,
|
|
2169
|
+
ignoreHooks: true,
|
|
2170
|
+
},
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2173
|
+
for (const row of stored) {
|
|
2174
|
+
if (
|
|
2175
|
+
!row._id ||
|
|
2176
|
+
!StatusPageSubscriberUnsubscribe.isWellFormedToken(
|
|
2177
|
+
row.unsubscribeToken,
|
|
2178
|
+
)
|
|
2179
|
+
) {
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
for (const subscriber of missing.get(
|
|
2184
|
+
row._id.toString().toLowerCase(),
|
|
2185
|
+
) || []) {
|
|
2186
|
+
subscriber.unsubscribeToken = row.unsubscribeToken;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
/*
|
|
2193
|
+
* The BackfillStatusPageSubscriberUnsubscribeColumns data migration: give
|
|
2194
|
+
* every subscriber that existed before the upgrade its unsubscribe token,
|
|
2195
|
+
* and mark the ones with a creator as added by the team (Is Added By
|
|
2196
|
+
* Team). Soft-deleted subscribers are included: one that is restored must
|
|
2197
|
+
* not come back without a token.
|
|
2198
|
+
*
|
|
2199
|
+
* The table is walked in primary key order, a batch of ids at a time, and
|
|
2200
|
+
* each batch is written by primary key in short statements of their own.
|
|
2201
|
+
* So however large the table, no statement holds its rows for long or runs
|
|
2202
|
+
* into the connection's statement timeout, and notifications, sign-ups and
|
|
2203
|
+
* the subscriber lists carry on while it runs - which one UPDATE of every
|
|
2204
|
+
* row, in the migration that added the columns, would have stopped.
|
|
2205
|
+
*
|
|
2206
|
+
* Idempotent, and safe to run twice at once (the data migration runner is
|
|
2207
|
+
* not serialized): every write only fills what is still empty, and
|
|
2208
|
+
* re-checks that under the row lock, so a token a sender has already put
|
|
2209
|
+
* in a message is never replaced.
|
|
2210
|
+
*/
|
|
2211
|
+
@CaptureSpan()
|
|
2212
|
+
public async backfillUnsubscribeColumns(options?: {
|
|
2213
|
+
batchSize?: number | undefined;
|
|
2214
|
+
}): Promise<StatusPageSubscriberUnsubscribeBackfillResult> {
|
|
2215
|
+
const batchSize: number = Math.max(
|
|
2216
|
+
1,
|
|
2217
|
+
Math.floor(options?.batchSize || UNSUBSCRIBE_COLUMNS_BATCH_SIZE),
|
|
2218
|
+
);
|
|
2219
|
+
|
|
2220
|
+
const result: StatusPageSubscriberUnsubscribeBackfillResult = {
|
|
2221
|
+
tokensGiven: 0,
|
|
2222
|
+
markedAddedByTeam: 0,
|
|
2223
|
+
};
|
|
2224
|
+
|
|
2225
|
+
let lastId: string | null = null;
|
|
2226
|
+
|
|
2227
|
+
for (;;) {
|
|
2228
|
+
const rows: Array<{
|
|
2229
|
+
_id: string;
|
|
2230
|
+
needsToken: boolean;
|
|
2231
|
+
needsAddedByTeam: boolean;
|
|
2232
|
+
}> = await this.getRepository().manager.query(
|
|
2233
|
+
`SELECT "_id"::text AS "_id",
|
|
2234
|
+
("unsubscribeToken" IS NULL) AS "needsToken",
|
|
2235
|
+
("isAddedByTeam" = false AND "createdByUserId" IS NOT NULL) AS "needsAddedByTeam"
|
|
2236
|
+
FROM "StatusPageSubscriber"
|
|
2237
|
+
WHERE ($2::uuid IS NULL OR "_id" > $2::uuid)
|
|
2238
|
+
ORDER BY "_id" ASC
|
|
2239
|
+
LIMIT $1`,
|
|
2240
|
+
[batchSize, lastId],
|
|
2241
|
+
);
|
|
2242
|
+
|
|
2243
|
+
if (rows.length === 0) {
|
|
2244
|
+
break;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
const needToken: Array<string> = rows
|
|
2248
|
+
.filter((row: { needsToken: boolean }): boolean => {
|
|
2249
|
+
return row.needsToken === true;
|
|
2250
|
+
})
|
|
2251
|
+
.map((row: { _id: string }): string => {
|
|
2252
|
+
return row._id;
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2255
|
+
const needAddedByTeam: Array<string> = rows
|
|
2256
|
+
.filter((row: { needsAddedByTeam: boolean }): boolean => {
|
|
2257
|
+
return row.needsAddedByTeam === true;
|
|
2258
|
+
})
|
|
2259
|
+
.map((row: { _id: string }): string => {
|
|
2260
|
+
return row._id;
|
|
2261
|
+
});
|
|
2262
|
+
|
|
2263
|
+
result.tokensGiven += await this.fillMissingUnsubscribeTokens(needToken);
|
|
2264
|
+
|
|
2265
|
+
if (needAddedByTeam.length > 0) {
|
|
2266
|
+
result.markedAddedByTeam += affectedRowCount(
|
|
2267
|
+
await this.getRepository().manager.query(
|
|
2268
|
+
`UPDATE "StatusPageSubscriber" SET "isAddedByTeam" = true WHERE "_id" = ANY($1::uuid[]) AND "createdByUserId" IS NOT NULL AND "isAddedByTeam" = false`,
|
|
2269
|
+
[needAddedByTeam],
|
|
2270
|
+
),
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
lastId = rows[rows.length - 1]!._id;
|
|
2275
|
+
|
|
2276
|
+
if (rows.length < batchSize) {
|
|
2277
|
+
break;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
return result;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
/*
|
|
2285
|
+
* Mint a token, from Node's CSPRNG like every other, for each of these
|
|
2286
|
+
* subscribers that has none, in one statement. Returns how many were given
|
|
2287
|
+
* one. `"unsubscribeToken" IS NULL` is checked again under each row's lock,
|
|
2288
|
+
* so a token another writer stored first is kept.
|
|
2289
|
+
*/
|
|
2290
|
+
private async fillMissingUnsubscribeTokens(
|
|
2291
|
+
subscriberIds: Array<string>,
|
|
2292
|
+
): Promise<number> {
|
|
2293
|
+
if (subscriberIds.length === 0) {
|
|
2294
|
+
return 0;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
const tokens: Array<string> = subscriberIds.map((): string => {
|
|
2298
|
+
return StatusPageSubscriberUnsubscribeToken.generate();
|
|
2299
|
+
});
|
|
2300
|
+
|
|
2301
|
+
return affectedRowCount(
|
|
2302
|
+
await this.getRepository().manager.query(
|
|
2303
|
+
`UPDATE "StatusPageSubscriber" AS "subscriber" SET "unsubscribeToken" = "minted"."token" FROM unnest($1::uuid[], $2::text[]) AS "minted"("id", "token") WHERE "subscriber"."_id" = "minted"."id" AND "subscriber"."unsubscribeToken" IS NULL`,
|
|
2304
|
+
[subscriberIds, tokens],
|
|
2305
|
+
),
|
|
2306
|
+
);
|
|
1222
2307
|
}
|
|
1223
2308
|
|
|
1224
2309
|
public shouldSendNotification(data: {
|
|
@@ -1227,12 +2312,17 @@ Stay informed about service availability! 🚀`;
|
|
|
1227
2312
|
statusPage: StatusPage;
|
|
1228
2313
|
eventType: StatusPageEventType;
|
|
1229
2314
|
}): boolean {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
2315
|
+
/*
|
|
2316
|
+
* Ids only: the subscriber carries its unsubscribe token, which is a
|
|
2317
|
+
* credential (it cancels the subscription, private pages included), and
|
|
2318
|
+
* every subscriber job calls this for every subscriber.
|
|
2319
|
+
*/
|
|
2320
|
+
logger.debug(
|
|
2321
|
+
`shouldSendNotification called for subscriber ${data.subscriber?._id?.toString() || ""} (${data.eventType}, ${data.statusPageResources?.length || 0} resource(s)).`,
|
|
2322
|
+
{
|
|
2323
|
+
statusPageId: data.statusPage?.id?.toString(),
|
|
2324
|
+
} as LogAttributes,
|
|
2325
|
+
);
|
|
1236
2326
|
|
|
1237
2327
|
let shouldSendNotification: boolean = true; // default to true.
|
|
1238
2328
|
|
|
@@ -1410,6 +2500,13 @@ Stay informed about service availability! 🚀`;
|
|
|
1410
2500
|
* every episode notification from being sent.
|
|
1411
2501
|
*/
|
|
1412
2502
|
showEpisodesOnStatusPage: true,
|
|
2503
|
+
/*
|
|
2504
|
+
* IncidentStatusPageScope decides from this which incidents reach a
|
|
2505
|
+
* page, and treats a page without it as showing only incidents
|
|
2506
|
+
* limited to it - so leaving it out would silence every unscoped
|
|
2507
|
+
* incident's notifications.
|
|
2508
|
+
*/
|
|
2509
|
+
onlyShowScopedIncidents: true,
|
|
1413
2510
|
},
|
|
1414
2511
|
});
|
|
1415
2512
|
|