@oneuptime/common 12.0.31 → 12.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/Span.ts +14 -0
- package/Models/DatabaseModels/AlertState.ts +0 -2
- package/Models/DatabaseModels/Domain.ts +1 -1
- package/Models/DatabaseModels/EnterpriseLicense.ts +56 -0
- package/Models/DatabaseModels/Incident.ts +1 -1
- package/Models/DatabaseModels/IncidentTemplate.ts +1 -1
- package/Models/DatabaseModels/Index.ts +12 -0
- package/Models/DatabaseModels/MetricType.ts +0 -2
- package/Models/DatabaseModels/Monitor.ts +76 -0
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +0 -2
- package/Models/DatabaseModels/MonitorTest.ts +0 -2
- package/Models/DatabaseModels/NetworkAlertPolicy.ts +690 -0
- package/Models/DatabaseModels/NetworkDevice.ts +297 -3
- package/Models/DatabaseModels/NetworkDeviceOidTemplate.ts +3 -1
- package/Models/DatabaseModels/NetworkDeviceRole.ts +648 -0
- package/Models/DatabaseModels/NetworkSite.ts +201 -2
- package/Models/DatabaseModels/NetworkSiteType.ts +102 -3
- package/Models/DatabaseModels/NetworkSnmpCredentialProfile.ts +841 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -1
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -1
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +0 -2
- package/Models/DatabaseModels/StatusPageAnnouncementTemplate.ts +0 -2
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +0 -2
- package/Models/DatabaseModels/StatusPageResource.ts +0 -2
- package/Models/DatabaseModels/StatusPageSubscriber.ts +0 -2
- package/Models/DatabaseModels/TelemetryException.ts +119 -0
- package/Models/DatabaseModels/UserNotificationEmailRollupBatch.ts +334 -0
- package/Models/DatabaseModels/UserNotificationEmailRollupItem.ts +370 -0
- package/Models/DatabaseModels/UserNotificationEmailRollupSetting.ts +282 -0
- package/Server/API/EnterpriseLicenseAPI.ts +391 -64
- package/Server/API/LlmProviderAPI.ts +137 -22
- package/Server/EnvironmentConfig.ts +14 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.ts +55 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.ts +72 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.ts +124 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.ts +132 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.ts +191 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
- package/Server/Infrastructure/QueueWorker.ts +38 -8
- package/Server/Middleware/BearerTokenAuthorization.ts +12 -0
- package/Server/Middleware/ProjectAuthorization.ts +11 -0
- package/Server/Middleware/SCIMAuthorization.ts +12 -0
- package/Server/Middleware/TelemetryIngest.ts +11 -0
- package/Server/Services/AIBillingService.ts +101 -27
- package/Server/Services/DatabaseService.ts +39 -9
- package/Server/Services/EnterpriseLicenseInstanceService.ts +197 -0
- package/Server/Services/EnterpriseLicenseService.ts +55 -0
- package/Server/Services/Index.ts +12 -0
- package/Server/Services/MonitorService.ts +143 -21
- package/Server/Services/MonitorStatusTimelineService.ts +150 -8
- package/Server/Services/MonitorTemplateService.ts +217 -1
- package/Server/Services/NetworkAlertPolicyEngineService.ts +2159 -0
- package/Server/Services/NetworkAlertPolicyService.ts +944 -0
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +9 -10
- package/Server/Services/NetworkDeviceAutoImportRuleService.ts +60 -15
- package/Server/Services/NetworkDeviceRoleService.ts +90 -0
- package/Server/Services/NetworkDeviceService.ts +1032 -73
- package/Server/Services/NetworkSiteService.ts +1502 -129
- package/Server/Services/NetworkSiteTypeService.ts +1216 -0
- package/Server/Services/NetworkSnmpCredentialProfileService.ts +238 -0
- package/Server/Services/NotificationService.ts +118 -28
- package/Server/Services/ProjectService.ts +186 -44
- package/Server/Services/StatusPagePrivateUserService.ts +103 -3
- package/Server/Services/UserNotificationEmailRollupBatchService.ts +78 -0
- package/Server/Services/UserNotificationEmailRollupItemService.ts +79 -0
- package/Server/Services/UserNotificationEmailRollupSettingService.ts +113 -0
- package/Server/Services/UserNotificationSettingService.ts +62 -31
- package/Server/Services/UserService.ts +95 -0
- package/Server/Types/Database/QueryHelper.ts +2 -2
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +14 -3
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +10 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +10 -0
- package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +62 -1
- package/Server/Utils/AI/Toolbox/AlertTools.ts +15 -1
- package/Server/Utils/Billing/BillingFailureNoticeThrottle.ts +80 -0
- package/Server/Utils/DataSource/HttpFetch.ts +9 -2
- package/Server/Utils/EmailRollup/EmailRollupConstants.ts +98 -0
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +872 -0
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +781 -0
- package/Server/Utils/EmailRollup/EmailRollupWriter.ts +368 -0
- package/Server/Utils/LLM/LLMService.ts +78 -0
- package/Server/Utils/LogRedaction.ts +8 -0
- package/Server/Utils/Logger.ts +79 -1
- package/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.ts +190 -0
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +9 -2
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +61 -1
- package/Server/Utils/Monitor/MonitorCriteriaDataExtractor.ts +14 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +49 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +255 -4
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +41 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +43 -0
- package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +499 -19
- package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +66 -10
- package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +238 -44
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +146 -39
- package/Server/Utils/NetworkDevice/DeviceHealthAggregation.ts +31 -18
- package/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.ts +213 -0
- package/Server/Utils/OutboundUserAgent.ts +152 -0
- package/Server/Utils/StartServer.ts +51 -19
- package/Server/Utils/Telemetry/CaptureSpan.ts +32 -8
- package/Server/Utils/Telemetry/ErrorClassResolver.ts +120 -0
- package/Server/Utils/Telemetry/SpanUtil.ts +33 -0
- package/Server/Utils/Telemetry.ts +198 -51
- package/Server/Views/Partials/SensitiveUrlToken.ejs +140 -0
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +158 -27
- package/Tests/App/Dashboard/AskAiSuggestedPrompts.test.ts +279 -0
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +35 -8
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +107 -3
- package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +517 -0
- package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +26 -0
- package/Tests/App/Dashboard/DashboardWideChartZoom.test.tsx +401 -0
- package/Tests/App/Dashboard/DeviceStatusHero.test.tsx +588 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +18 -9
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +632 -0
- package/Tests/App/Dashboard/MonitorRecommendationCreateProgress.test.tsx +682 -0
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +318 -0
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +4 -2
- package/Tests/App/Dashboard/NetworkDeviceBulkDelete.test.tsx +357 -0
- package/Tests/App/Dashboard/NetworkDeviceCreateFormMonitorBinding.test.tsx +676 -0
- package/Tests/App/Dashboard/NetworkDeviceCreateFormPingMonitor.test.tsx +791 -0
- package/Tests/App/Dashboard/NetworkDeviceCreateFormProbeAndSnmp.test.tsx +833 -0
- package/Tests/App/Dashboard/NetworkDeviceCriteriaFilter.test.ts +161 -0
- package/Tests/App/Dashboard/NetworkDeviceSettingsMonitoringSection.test.tsx +458 -0
- package/Tests/App/Dashboard/NetworkSideMenu.test.tsx +626 -0
- package/Tests/App/Dashboard/PingMonitorProvisioning.test.ts +368 -0
- package/Tests/App/Dashboard/RecommendationCard.test.tsx +796 -0
- package/Tests/App/Dashboard/RecommendationToolbar.test.tsx +519 -0
- package/Tests/App/Dashboard/RecommendationsList.test.tsx +661 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +11 -3
- package/Tests/App/Dashboard/UseBulkCreatePingMonitors.test.tsx +899 -0
- package/Tests/App/Dashboard/UseBulkSnmpCredentialProfileActions.test.tsx +732 -0
- package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +256 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +11 -1
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +9 -4
- package/Tests/Models/DatabaseModels/ModelRegistryInvariants.test.ts +115 -41
- package/Tests/Models/NetworkSiteHierarchy.test.ts +41 -4
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +951 -7
- package/Tests/Server/API/LlmProviderConnectionTest.test.ts +522 -0
- package/Tests/Server/Infrastructure/Postgres/NetworkDeviceRoleTableMigration.test.ts +476 -0
- package/Tests/Server/Infrastructure/Postgres/NetworkSnmpCredentialProfilesAndAlertPoliciesMigration.test.ts +854 -0
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +18 -0
- package/Tests/Server/Infrastructure/Postgres/UserNotificationEmailRollupTableMigration.test.ts +535 -0
- package/Tests/Server/Services/AIChatPrivacyPin.test.ts +560 -0
- package/Tests/Server/Services/BillingRechargeOwnerEmailGuards.test.ts +783 -0
- package/Tests/Server/Services/DatabaseServiceGenerateSlug.test.ts +215 -0
- package/Tests/Server/Services/EnterpriseLicenseInstanceServiceDeletionUsage.test.ts +707 -0
- package/Tests/Server/Services/EnterpriseLicenseServiceUsageAggregationLock.test.ts +329 -0
- package/Tests/Server/Services/MonitorServiceDeleteNetworkDeviceCleanup.test.ts +294 -0
- package/Tests/Server/Services/MonitorStatusBridgeProbePath.test.ts +802 -0
- package/Tests/Server/Services/MonitorStatusTimelineService.test.ts +72 -2
- package/Tests/Server/Services/NetworkAlertPolicyEngineHooks.test.ts +752 -0
- package/Tests/Server/Services/NetworkAlertPolicyEngineService.test.ts +1849 -0
- package/Tests/Server/Services/NetworkAlertPolicyModel.test.ts +1793 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +47 -4
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleService.test.ts +97 -12
- package/Tests/Server/Services/NetworkDeviceAutoMonitorLifecycle.test.ts +148 -3
- package/Tests/Server/Services/NetworkDeviceMonitorBindingUpdateGuard.test.ts +514 -0
- package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +447 -58
- package/Tests/Server/Services/NetworkDeviceMonitoringMethodTransition.test.ts +739 -0
- package/Tests/Server/Services/NetworkDevicePollingTenancy.test.ts +501 -0
- package/Tests/Server/Services/NetworkDeviceRoleModel.test.ts +672 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +1 -1
- package/Tests/Server/Services/NetworkDeviceSiteDefaultProbe.test.ts +432 -0
- package/Tests/Server/Services/NetworkSiteMonitoringDefaults.test.ts +498 -0
- package/Tests/Server/Services/NetworkSiteRollupPolicyAndMaintenance.test.ts +337 -55
- package/Tests/Server/Services/NetworkSiteService.test.ts +1868 -131
- package/Tests/Server/Services/NetworkSiteTypeService.test.ts +1109 -0
- package/Tests/Server/Services/NetworkSnmpCredentialProfileModel.test.ts +926 -0
- package/Tests/Server/Services/NetworkSnmpCredentialProfileService.test.ts +796 -0
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +67 -4
- package/Tests/Server/Services/ProjectServiceNetworkSiteTypeDefaults.test.ts +214 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberEndTime.test.ts +369 -0
- package/Tests/Server/Services/StatusPagePrivateUserEmailChangePasswordResetExpiry.test.ts +269 -0
- package/Tests/Server/Services/UserEmailChangePasswordResetExpiry.test.ts +472 -0
- package/Tests/Server/Services/UserNotificationEmailRollupModels.test.ts +569 -0
- package/Tests/Server/Services/UserNotificationEmailRollupSettingModel.test.ts +314 -0
- package/Tests/Server/Services/UserNotificationEmailRollupSettingService.test.ts +409 -0
- package/Tests/Server/Services/UserNotificationSettingMonitorDefaults.test.ts +248 -0
- package/Tests/Server/Services/UserNotificationSettingRollupRouting.test.ts +856 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +145 -3
- package/Tests/Server/Types/Database/Permissions/DashboardAccessPermission.test.ts +321 -0
- package/Tests/Server/Types/Database/QueryHelperOperators.test.ts +17 -0
- package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +124 -0
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +208 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +235 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +253 -0
- package/Tests/Server/Utils/BasicCron.test.ts +283 -0
- package/Tests/Server/Utils/Captcha.test.ts +422 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +96 -2
- package/Tests/Server/Utils/Database/MigrationFailureLog.test.ts +466 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1096 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerClaim.test.ts +466 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +1428 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +752 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupWriter.test.ts +864 -0
- package/Tests/Server/Utils/IncomingCallPhoneNumber.test.ts +174 -0
- package/Tests/Server/Utils/LoggerFaultDemotion.test.ts +346 -0
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +633 -0
- package/Tests/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.test.ts +196 -0
- package/Tests/Server/Utils/Monitor/DatabaseMetricWrite.test.ts +367 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +100 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +5 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +512 -31
- package/Tests/Server/Utils/Monitor/NetworkDeviceMetricUtil.test.ts +252 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +479 -42
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +351 -5
- package/Tests/Server/Utils/NetworkDevice/DeviceHealthAggregation.test.ts +136 -12
- package/Tests/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.test.ts +306 -0
- package/Tests/Server/Utils/OutboundUserAgent.test.ts +266 -0
- package/Tests/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClientUserAgent.test.ts +141 -0
- package/Tests/Server/Utils/Telemetry/ErrorClassResolver.test.ts +298 -0
- package/Tests/Server/Utils/Telemetry.test.ts +456 -66
- package/Tests/Types/Monitor/DatabaseMetricCatalog.test.ts +388 -0
- package/Tests/Types/Monitor/MonitorStepDatabaseMonitor.test.ts +243 -0
- package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +1 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +1 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +16 -7
- package/Tests/Types/Monitor/NetworkDeviceAlertPack.test.ts +60 -0
- package/Tests/Types/NetworkDevice/DefaultNetworkDeviceRole.test.ts +417 -0
- package/Tests/Types/NetworkDevice/NetworkAlertPolicyScope.test.ts +655 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +115 -26
- package/Tests/Types/NetworkSite/DefaultNetworkSiteType.test.ts +39 -0
- package/Tests/Types/NotificationSetting/NotificationEmailRollupPolicy.test.ts +299 -0
- package/Tests/Types/Telemetry/ErrorClass.test.ts +483 -0
- package/Tests/UI/Components/BasicRadioButtons.test.tsx +336 -0
- package/Tests/UI/Components/Charts/ChartClickDoubleClickDisambiguation.test.tsx +221 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickReset.test.tsx +220 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +751 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +475 -43
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +89 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +15 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +103 -8
- package/Tests/UI/Components/ProgressBar.test.tsx +38 -2
- package/Tests/UI/Components/SideMenuItem.test.tsx +256 -4
- package/Tests/UI/Components/SideMenuSection.test.tsx +310 -0
- package/Tests/UI/Components/SideOverSubmitState.test.tsx +335 -0
- package/Tests/UI/Utils/PageScrollLock.test.tsx +377 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +78 -0
- package/Tests/UI/Utils/SensitiveUrlToken.test.ts +140 -0
- package/Tests/UI/Utils/TableColumnsToCsv.test.ts +30 -0
- package/Tests/UI/Utils/UseDashboardTimeRangeZoom.test.tsx +187 -0
- package/Tests/Utils/Dashboard/DashboardResourceListSharedArgs.test.ts +302 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +242 -0
- package/Tests/Utils/EnterpriseLicenseSeats.test.ts +69 -0
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +7 -1
- package/Tests/Utils/EnterpriseLicenseUsage.test.ts +433 -2
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +280 -1
- package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +1 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleCatalog.test.ts +981 -0
- package/Tests/Utils/Monitor/NetworkTopologyRoleCatalog.test.ts +865 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +38 -0
- package/Tests/Utils/NetworkDevice/DeviceHealthStateUtil.test.ts +284 -23
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +177 -0
- package/Tests/Utils/NetworkDevice/DeviceRoleKeyUtil.test.ts +538 -0
- package/Tests/Utils/NetworkDevice/MonitoringMethodThreadedClassifiers.test.ts +992 -0
- package/Tests/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.test.ts +427 -0
- package/Tests/Utils/NetworkDevice/ReachabilityStampConsistency.test.ts +185 -0
- package/Tests/Utils/NetworkDevice/SnmpCredentialUtil.test.ts +268 -0
- package/Tests/Utils/NetworkDiscovery/AutoImportRuleMatcher.test.ts +55 -5
- package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +98 -21
- package/Tests/Utils/NetworkDiscovery/DiscoveryImportEligibility.test.ts +98 -20
- package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +8 -7
- package/Tests/Utils/NetworkDiscovery/PingMonitorBuilderForAddress.test.ts +274 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +230 -51
- package/Tests/Utils/NetworkSite/TypeHierarchyUtil.test.ts +209 -0
- package/Tests/Utils/Slo/SloEvaluation.test.ts +171 -0
- package/Tests/Utils/Slug.test.ts +61 -1
- package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +145 -0
- package/Tests/jest.setup.ts +19 -0
- package/Types/AI/ExceptionAIClassification.ts +10 -24
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +5 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.ts +13 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.ts +6 -0
- package/Types/Exception/ApiException.ts +10 -0
- package/Types/Exception/BadDataException.ts +11 -0
- package/Types/Exception/BadRequestException.ts +11 -0
- package/Types/Exception/Exception.ts +65 -0
- package/Types/Exception/ForbiddenException.ts +10 -0
- package/Types/Exception/NotAuthenticatedException.ts +10 -0
- package/Types/Exception/NotAuthorizedException.ts +10 -0
- package/Types/Exception/NotFoundException.ts +11 -0
- package/Types/Exception/PayloadTooLargeException.ts +11 -0
- package/Types/Exception/PaymentRequiredException.ts +10 -0
- package/Types/Exception/ServiceUnavailableException.ts +10 -0
- package/Types/Exception/SsoAuthorizationException.ts +10 -0
- package/Types/Exception/TenantNotFoundException.ts +11 -0
- package/Types/Exception/TimeoutException.ts +10 -0
- package/Types/Exception/TooManyRequestsException.ts +10 -0
- package/Types/Exception/UnableToReachServer.ts +10 -0
- package/Types/Exception/WebsiteRequestException.ts +10 -0
- package/Types/Monitor/CriteriaFilter.ts +60 -2
- package/Types/Monitor/DatabaseMetricCatalog.ts +693 -0
- package/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.ts +83 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +76 -0
- package/Types/Monitor/MonitorMetricType.ts +71 -0
- package/Types/Monitor/MonitorStep.ts +76 -0
- package/Types/Monitor/MonitorStepDatabaseMonitor.ts +171 -0
- package/Types/Monitor/MonitorStepSqlMonitor.ts +2 -20
- package/Types/Monitor/MonitorType.ts +47 -1
- package/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.ts +22 -1
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +61 -0
- package/Types/Monitor/SqlConnectionConfig.ts +31 -0
- package/Types/NetworkDevice/DefaultNetworkDeviceRole.ts +145 -0
- package/Types/NetworkDevice/NetworkAlertPolicyScope.ts +343 -0
- package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +42 -22
- package/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.ts +30 -0
- package/Types/NotificationSetting/NotificationEmailRollupCategory.ts +255 -0
- package/Types/NotificationSetting/NotificationEmailRollupPolicy.ts +104 -0
- package/Types/Permission.ts +135 -0
- package/Types/Probe/ProbeMonitorResponse.ts +16 -0
- package/Types/Telemetry/ErrorClass.ts +274 -0
- package/Types/Telemetry/UnitOfWork.ts +59 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +7 -0
- package/UI/Components/Charts/Bar/BarChart.tsx +6 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +9 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +14 -0
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +2 -8
- package/UI/Components/EditionLabel/EditionLabel.tsx +4 -0
- package/UI/Components/Modal/Modal.tsx +2 -8
- package/UI/Components/ModelTable/BaseModelTable.tsx +50 -15
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +44 -0
- package/UI/Components/Navbar/NavBar.tsx +62 -13
- package/UI/Components/Navbar/NavBarMenuModal.tsx +7 -2
- package/UI/Components/ProgressBar/ProgressBar.tsx +12 -1
- package/UI/Components/RadioButtons/BasicRadioButtons.tsx +37 -7
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +2 -0
- package/UI/Components/SideMenu/SideMenu.tsx +135 -7
- package/UI/Components/SideMenu/SideMenuItem.tsx +80 -85
- package/UI/Components/SideMenu/SideMenuSection.tsx +69 -25
- package/UI/Components/SideOver/SideOver.tsx +21 -20
- package/UI/Utils/PageScrollLock.ts +79 -3
- package/UI/Utils/PermissionGate.ts +61 -0
- package/UI/Utils/SensitiveUrlToken.ts +112 -0
- package/UI/Utils/TestLLMProvider.ts +11 -0
- package/UI/Utils/UseDashboardTimeRangeZoom.ts +77 -0
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +118 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +16 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +191 -11
- package/Utils/Monitor/MonitorMetricType.ts +153 -1
- package/Utils/Monitor/NetworkDeviceRoleCatalog.ts +232 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +2 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +139 -36
- package/Utils/NetworkDevice/DeviceHealthStateUtil.ts +65 -14
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +43 -14
- package/Utils/NetworkDevice/DeviceRoleKeyUtil.ts +137 -0
- package/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.ts +247 -0
- package/Utils/NetworkDevice/SnmpCredentialUtil.ts +68 -0
- package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +15 -7
- package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +52 -17
- package/Utils/NetworkDiscovery/DiscoveryImportEligibility.ts +55 -23
- package/Utils/NetworkDiscovery/PingMonitorBuilder.ts +115 -13
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +66 -20
- package/Utils/NetworkSite/TypeHierarchyUtil.ts +313 -0
- package/Utils/Rules/RulePatternMatchUtil.ts +1 -1
- package/Utils/Slug.ts +34 -1
- package/build/dist/Models/AnalyticsModels/Span.js +14 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertState.js +0 -2
- package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +59 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +12 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MetricType.js +0 -2
- package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +75 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTest.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorTest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkAlertPolicy.js +713 -0
- package/build/dist/Models/DatabaseModels/NetworkAlertPolicy.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +304 -4
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js +3 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js +677 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkSite.js +200 -2
- package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteType.js +102 -3
- package/build/dist/Models/DatabaseModels/NetworkSiteType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSnmpCredentialProfile.js +878 -0
- package/build/dist/Models/DatabaseModels/NetworkSnmpCredentialProfile.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +123 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupBatch.js +363 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupBatch.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js +402 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupSetting.js +296 -0
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupSetting.js.map +1 -0
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +273 -60
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +108 -13
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +12 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.js +24 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790700000000-AddNetworkSiteTypeParentHierarchy.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddEnterpriseLicenseUsageProvenance.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.js +111 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790900000000-AddTelemetryExceptionErrorClass.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.js +77 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791000000000-AddUserNotificationEmailRollup.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.js +50 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791100000000-AddUserNotificationEmailRollupSetting.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.js +83 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791200000000-AddNetworkSnmpCredentialProfilesAndAlertPolicies.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +33 -5
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Middleware/BearerTokenAuthorization.js +12 -0
- package/build/dist/Server/Middleware/BearerTokenAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +11 -0
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/SCIMAuthorization.js +12 -0
- package/build/dist/Server/Middleware/SCIMAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +11 -0
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIBillingService.js +70 -16
- package/build/dist/Server/Services/AIBillingService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +31 -5
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js +167 -0
- package/build/dist/Server/Services/EnterpriseLicenseInstanceService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +39 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +12 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +119 -21
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +127 -8
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +162 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkAlertPolicyEngineService.js +1683 -0
- package/build/dist/Server/Services/NetworkAlertPolicyEngineService.js.map +1 -0
- package/build/dist/Server/Services/NetworkAlertPolicyService.js +732 -0
- package/build/dist/Server/Services/NetworkAlertPolicyService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +10 -9
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +54 -11
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceRoleService.js +70 -0
- package/build/dist/Server/Services/NetworkDeviceRoleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceService.js +841 -70
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +1145 -107
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteTypeService.js +863 -0
- package/build/dist/Server/Services/NetworkSiteTypeService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSnmpCredentialProfileService.js +217 -0
- package/build/dist/Server/Services/NetworkSnmpCredentialProfileService.js.map +1 -0
- package/build/dist/Server/Services/NotificationService.js +80 -17
- package/build/dist/Server/Services/NotificationService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +131 -30
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +92 -2
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationEmailRollupBatchService.js +76 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupBatchService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupItemService.js +77 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupItemService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupSettingService.js +111 -0
- package/build/dist/Server/Services/UserNotificationEmailRollupSettingService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js +46 -21
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +90 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +14 -3
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +10 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +10 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +54 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +18 -4
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
- package/build/dist/Server/Utils/Billing/BillingFailureNoticeThrottle.js +59 -0
- package/build/dist/Server/Utils/Billing/BillingFailureNoticeThrottle.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +2 -1
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js +91 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js.map +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +691 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +497 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js +257 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js.map +1 -0
- package/build/dist/Server/Utils/LLM/LLMService.js +64 -1
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +8 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +63 -1
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.js +141 -0
- package/build/dist/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +51 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js +7 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaDataExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +37 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +158 -6
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +32 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +330 -21
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +55 -10
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +178 -49
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +119 -29
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js +30 -17
- package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js.map +1 -1
- package/build/dist/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.js +158 -0
- package/build/dist/Server/Utils/NetworkSite/NetworkSiteHierarchyLock.js.map +1 -0
- package/build/dist/Server/Utils/OutboundUserAgent.js +123 -0
- package/build/dist/Server/Utils/OutboundUserAgent.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +38 -14
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +28 -8
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ErrorClassResolver.js +95 -0
- package/build/dist/Server/Utils/Telemetry/ErrorClassResolver.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/SpanUtil.js +29 -0
- package/build/dist/Server/Utils/Telemetry/SpanUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +162 -46
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Types/AI/ExceptionAIClassification.js +10 -24
- package/build/dist/Types/AI/ExceptionAIClassification.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.js +2 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUsageSnapshot.js.map +1 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.js +7 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicenseUserCountSource.js.map +1 -0
- package/build/dist/Types/Exception/ApiException.js +9 -0
- package/build/dist/Types/Exception/ApiException.js.map +1 -1
- package/build/dist/Types/Exception/BadDataException.js +10 -0
- package/build/dist/Types/Exception/BadDataException.js.map +1 -1
- package/build/dist/Types/Exception/BadRequestException.js +10 -0
- package/build/dist/Types/Exception/BadRequestException.js.map +1 -1
- package/build/dist/Types/Exception/Exception.js +56 -0
- package/build/dist/Types/Exception/Exception.js.map +1 -1
- package/build/dist/Types/Exception/ForbiddenException.js +9 -0
- package/build/dist/Types/Exception/ForbiddenException.js.map +1 -1
- package/build/dist/Types/Exception/NotAuthenticatedException.js +9 -0
- package/build/dist/Types/Exception/NotAuthenticatedException.js.map +1 -1
- package/build/dist/Types/Exception/NotAuthorizedException.js +9 -0
- package/build/dist/Types/Exception/NotAuthorizedException.js.map +1 -1
- package/build/dist/Types/Exception/NotFoundException.js +10 -0
- package/build/dist/Types/Exception/NotFoundException.js.map +1 -1
- package/build/dist/Types/Exception/PayloadTooLargeException.js +10 -0
- package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -1
- package/build/dist/Types/Exception/PaymentRequiredException.js +9 -0
- package/build/dist/Types/Exception/PaymentRequiredException.js.map +1 -1
- package/build/dist/Types/Exception/ServiceUnavailableException.js +9 -0
- package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -1
- package/build/dist/Types/Exception/SsoAuthorizationException.js +9 -0
- package/build/dist/Types/Exception/SsoAuthorizationException.js.map +1 -1
- package/build/dist/Types/Exception/TenantNotFoundException.js +10 -0
- package/build/dist/Types/Exception/TenantNotFoundException.js.map +1 -1
- package/build/dist/Types/Exception/TimeoutException.js +9 -0
- package/build/dist/Types/Exception/TimeoutException.js.map +1 -1
- package/build/dist/Types/Exception/TooManyRequestsException.js +9 -0
- package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -1
- package/build/dist/Types/Exception/UnableToReachServer.js +9 -0
- package/build/dist/Types/Exception/UnableToReachServer.js.map +1 -1
- package/build/dist/Types/Exception/WebsiteRequestException.js +9 -0
- package/build/dist/Types/Exception/WebsiteRequestException.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +45 -2
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DatabaseMetricCatalog.js +594 -0
- package/build/dist/Types/Monitor/DatabaseMetricCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.js +21 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +70 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorMetricType.js +61 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +54 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepDatabaseMonitor.js +122 -0
- package/build/dist/Types/Monitor/MonitorStepDatabaseMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +45 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js +21 -1
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js.map +1 -1
- package/build/dist/Types/Monitor/SqlConnectionConfig.js +2 -0
- package/build/dist/Types/Monitor/SqlConnectionConfig.js.map +1 -0
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js +105 -0
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js.map +1 -0
- package/build/dist/Types/NetworkDevice/NetworkAlertPolicyScope.js +172 -0
- package/build/dist/Types/NetworkDevice/NetworkAlertPolicyScope.js.map +1 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +40 -22
- package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -1
- package/build/dist/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.js +26 -0
- package/build/dist/Types/NetworkSite/DefaultNetworkSiteTypeHierarchy.js.map +1 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupCategory.js +186 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupCategory.js.map +1 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupPolicy.js +87 -0
- package/build/dist/Types/NotificationSetting/NotificationEmailRollupPolicy.js.map +1 -0
- package/build/dist/Types/Permission.js +120 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Telemetry/ErrorClass.js +216 -0
- package/build/dist/Types/Telemetry/ErrorClass.js.map +1 -0
- package/build/dist/Types/Telemetry/UnitOfWork.js +59 -0
- package/build/dist/Types/Telemetry/UnitOfWork.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +7 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +14 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +2 -7
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +3 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +2 -7
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +39 -13
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +39 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +44 -9
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/ProgressBar/ProgressBar.js +12 -1
- package/build/dist/UI/Components/ProgressBar/ProgressBar.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +6 -6
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +76 -11
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js +50 -54
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js +32 -9
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
- package/build/dist/UI/Components/SideOver/SideOver.js +5 -20
- package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
- package/build/dist/UI/Utils/PageScrollLock.js +31 -3
- package/build/dist/UI/Utils/PageScrollLock.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +42 -1
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/build/dist/UI/Utils/SensitiveUrlToken.js +97 -0
- package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -0
- package/build/dist/UI/Utils/TestLLMProvider.js +4 -5
- package/build/dist/UI/Utils/TestLLMProvider.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js +42 -0
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +74 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +15 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +111 -9
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +109 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js +126 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +2 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +75 -35
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js +27 -7
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +7 -5
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js +114 -0
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.js +180 -0
- package/build/dist/Utils/NetworkDevice/NetworkAlertPolicyBootstrapUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDevice/SnmpCredentialUtil.js +12 -0
- package/build/dist/Utils/NetworkDevice/SnmpCredentialUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js +15 -5
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +44 -15
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js +52 -23
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js +85 -13
- package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js.map +1 -1
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +30 -15
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js +191 -0
- package/build/dist/Utils/NetworkSite/TypeHierarchyUtil.js.map +1 -0
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js +1 -1
- package/build/dist/Utils/Slug.js +31 -1
- package/build/dist/Utils/Slug.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,1683 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import MonitorService from "./MonitorService";
|
|
11
|
+
import MonitorTemplateService from "./MonitorTemplateService";
|
|
12
|
+
import NetworkAlertPolicyService from "./NetworkAlertPolicyService";
|
|
13
|
+
import NetworkDeviceService from "./NetworkDeviceService";
|
|
14
|
+
import ProjectService from "./ProjectService";
|
|
15
|
+
import Semaphore from "../Infrastructure/Semaphore";
|
|
16
|
+
import { AllowedActiveMonitorCountInFreePlan, IsBillingEnabled, } from "../EnvironmentConfig";
|
|
17
|
+
import QueryHelper from "../Types/Database/QueryHelper";
|
|
18
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
19
|
+
import logger from "../Utils/Logger";
|
|
20
|
+
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
21
|
+
import { PlanType } from "../../Types/Billing/SubscriptionPlan";
|
|
22
|
+
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
23
|
+
import OneUptimeDate from "../../Types/Date";
|
|
24
|
+
import MonitorType, { MonitorTypeHelper, } from "../../Types/Monitor/MonitorType";
|
|
25
|
+
import { NetworkAlertPolicyScopeUtil, } from "../../Types/NetworkDevice/NetworkAlertPolicyScope";
|
|
26
|
+
import { NetworkDeviceMonitoringMethodUtil } from "../../Types/NetworkDevice/NetworkDeviceMonitoringMethod";
|
|
27
|
+
import ObjectID from "../../Types/ObjectID";
|
|
28
|
+
import NetworkDeviceMonitorTemplateUtil from "../../Utils/Monitor/NetworkDeviceMonitorTemplateUtil";
|
|
29
|
+
/*
|
|
30
|
+
* The engine behind Network Alert Policies: the ONE place in the product
|
|
31
|
+
* that creates or deletes a policy-owned Monitor.
|
|
32
|
+
*
|
|
33
|
+
* A policy is intent — "every device in these sites, with these roles, with
|
|
34
|
+
* these labels, gets a Network Device monitor cloned from this template".
|
|
35
|
+
* The monitors are the consequence, and they are billed and they own
|
|
36
|
+
* incident history, so every path that could produce or destroy one funnels
|
|
37
|
+
* through `reconcileDevice(projectId, deviceId)`. Device created, device
|
|
38
|
+
* re-sited, device re-labelled, device archived, policy saved, policy
|
|
39
|
+
* enabled, policy scope widened, the five-minute sweep — all of them end up
|
|
40
|
+
* calling the same function for one device, under one Redis lock, and that
|
|
41
|
+
* function computes:
|
|
42
|
+
*
|
|
43
|
+
* DESIRED every enabled policy of the project whose scope matches this
|
|
44
|
+
* device, PROVIDED the device is provisionable at all: not
|
|
45
|
+
* archived, not monitor-backed (a monitor-backed device's health
|
|
46
|
+
* IS a monitor's — a policy monitor on it would poll nothing),
|
|
47
|
+
* and carrying a probe (nothing polls a device without one, so a
|
|
48
|
+
* monitor on it would sit Pending forever and still be billed).
|
|
49
|
+
*
|
|
50
|
+
* ACTUAL the monitors whose provenance says a policy made them:
|
|
51
|
+
* `autoProvisionedNetworkDeviceId = this device AND
|
|
52
|
+
* networkAlertPolicyId IS NOT NULL`. Both halves matter. Without
|
|
53
|
+
* the first the engine would wander outside the device; without
|
|
54
|
+
* the second it would treat a hand-made monitor, or one an
|
|
55
|
+
* auto-import rule provisioned, as its own and delete it.
|
|
56
|
+
*
|
|
57
|
+
* and applies the difference. There is deliberately no "create the monitors
|
|
58
|
+
* for a new policy" path and no "delete the monitors for a shrunk scope"
|
|
59
|
+
* path: both are the same difference, computed per device, so the two can
|
|
60
|
+
* never disagree about who owns what.
|
|
61
|
+
*
|
|
62
|
+
* WHAT THE ENGINE WILL NOT DO, ever:
|
|
63
|
+
*
|
|
64
|
+
* - Touch a monitor another policy owns. Ownership is the PAIR
|
|
65
|
+
* (networkAlertPolicyId, monitorTemplateId): a monitor is policy P's
|
|
66
|
+
* current monitor only while it carries P's id AND the template P names
|
|
67
|
+
* right now. A monitor stamped with a different policy's id is left
|
|
68
|
+
* exactly where it is.
|
|
69
|
+
* - Delete a hand-made monitor. A Network Device monitor with no
|
|
70
|
+
* `networkAlertPolicyId` is not in ACTUAL at all, so a scope that shrinks
|
|
71
|
+
* away from a device deletes only the rows stamped with the policy that
|
|
72
|
+
* shrank.
|
|
73
|
+
* - Duplicate an unowned monitor. If a (device, template) monitor already
|
|
74
|
+
* exists without an owner — an auto-import rule's, or one a person built
|
|
75
|
+
* from the same template — the engine ADOPTS it by stamping its
|
|
76
|
+
* `networkAlertPolicyId`, rather than trying to create a second one and
|
|
77
|
+
* failing forever on Monitor's partial unique index on
|
|
78
|
+
* (autoProvisionedNetworkDeviceId, monitorTemplateId).
|
|
79
|
+
* - Rewrite a policy's scope. Stale ids in a scope simply match nothing;
|
|
80
|
+
* the engine reads the column and never writes it.
|
|
81
|
+
*
|
|
82
|
+
* WHY A LOCK PER DEVICE. Two policies matching one device, a device event
|
|
83
|
+
* and the sweep, or two replicas of the sweep, all reconcile the SAME device
|
|
84
|
+
* concurrently. Every decision here is check-then-write with no transaction
|
|
85
|
+
* around it, so without the lock two passes both observe "no monitor yet"
|
|
86
|
+
* and both create — one of them then failing on the unique index, or worse
|
|
87
|
+
* both succeeding on different templates and each deleting the other's row
|
|
88
|
+
* on the next pass. The lock is keyed on the device because the device is
|
|
89
|
+
* what the difference is computed over.
|
|
90
|
+
*/
|
|
91
|
+
/*
|
|
92
|
+
* Monitor writes one project's reconciliation may make before it stops and
|
|
93
|
+
* leaves the rest to the next sweep.
|
|
94
|
+
*
|
|
95
|
+
* A monitor create runs the whole MonitorService pipeline — billing, plan
|
|
96
|
+
* limits, operational status, labels, owners, SLOs, status pages, workflows,
|
|
97
|
+
* realtime — so five hundred of them is already minutes of work. The cap is
|
|
98
|
+
* per project per RUN rather than per policy, because the thing being
|
|
99
|
+
* protected is the project's database and its bill, and three policies each
|
|
100
|
+
* provisioning five hundred monitors is fifteen hundred billable rows from
|
|
101
|
+
* one sweep tick.
|
|
102
|
+
*
|
|
103
|
+
* A capped run loses nothing: it stamps what it did, the next sweep recomputes
|
|
104
|
+
* the same difference and continues, and `reconcileDevice` is idempotent.
|
|
105
|
+
*/
|
|
106
|
+
export const MAX_MONITORS_PER_POLICY_SYNC = 500;
|
|
107
|
+
/*
|
|
108
|
+
* How many devices one update may reconcile INSIDE the request that wrote
|
|
109
|
+
* them.
|
|
110
|
+
*
|
|
111
|
+
* A device write is usually one device — the Settings form, a discovery
|
|
112
|
+
* import, a topology adoption — and reconciling it inline is what makes the
|
|
113
|
+
* monitor appear before the operator's next page load. A BULK write is a
|
|
114
|
+
* different animal: "move 1,200 devices to this site" or "archive the whole
|
|
115
|
+
* warehouse" matches thousands of rows, and reconciling thousands of devices
|
|
116
|
+
* inside an HTTP request would turn a one-statement update into an hour of
|
|
117
|
+
* monitor provisioning while the caller waits. Past this many devices the
|
|
118
|
+
* write returns immediately and the five-minute sweep converges the fleet.
|
|
119
|
+
*/
|
|
120
|
+
export const MAX_INLINE_RECONCILE_DEVICES = 5;
|
|
121
|
+
/*
|
|
122
|
+
* The per-device lock. `Semaphore.lock` joins namespace and key with a dash,
|
|
123
|
+
* so the Redis key is "NetworkAlertPolicy-NetworkAlertPolicy:Device:<id>";
|
|
124
|
+
* the key itself is spelled in full so a `KEYS NetworkAlertPolicy:Device:*`
|
|
125
|
+
* during an incident finds what an operator would expect it to.
|
|
126
|
+
*/
|
|
127
|
+
export const POLICY_DEVICE_LOCK_NAMESPACE = "NetworkAlertPolicy";
|
|
128
|
+
export function policyDeviceLockKey(deviceId) {
|
|
129
|
+
return `NetworkAlertPolicy:Device:${deviceId.toString()}`;
|
|
130
|
+
}
|
|
131
|
+
/*
|
|
132
|
+
* Long enough for one device's difference (a handful of queries plus at most
|
|
133
|
+
* a few monitor creates), short enough that a crashed worker does not block
|
|
134
|
+
* a device for minutes. redis-semaphore refreshes a lock while it is held,
|
|
135
|
+
* so this only bounds how long a DEAD holder's lock lingers.
|
|
136
|
+
*/
|
|
137
|
+
export const POLICY_DEVICE_LOCK_TIMEOUT_MS = 60 * 1000;
|
|
138
|
+
/*
|
|
139
|
+
* One attempt, no queueing. Every caller of reconcileDevice is either
|
|
140
|
+
* idempotent-and-repeated (the sweep) or fire-and-forget (a device hook), so
|
|
141
|
+
* a device already being reconciled by somebody else needs no second pass
|
|
142
|
+
* behind the first: that pass is computing the same difference from fresher
|
|
143
|
+
* data than this one would.
|
|
144
|
+
*/
|
|
145
|
+
const POLICY_DEVICE_LOCK_ACQUIRE_ATTEMPTS = 1;
|
|
146
|
+
/*
|
|
147
|
+
* A syntactically valid UUID that no row can carry, used to keep a scope kind
|
|
148
|
+
* whose ids are ALL unusable matching nothing — see buildScopeDeviceQuery.
|
|
149
|
+
* The nil UUID is the natural choice: it is well-formed, and
|
|
150
|
+
* PrimaryGeneratedColumn("uuid") never produces it.
|
|
151
|
+
*/
|
|
152
|
+
const UNMATCHABLE_ID = "00000000-0000-0000-0000-000000000000";
|
|
153
|
+
// Devices and monitors read per statement while paging a policy's fleet.
|
|
154
|
+
const POLICY_SYNC_PAGE_SIZE = 500;
|
|
155
|
+
/*
|
|
156
|
+
* The ceiling on how many devices ONE policy's forward pass will page
|
|
157
|
+
* through before giving up on being exhaustive. It is not a correctness
|
|
158
|
+
* bound — the pass is read-only until it finds a difference — but a run
|
|
159
|
+
* bound: an unscoped policy on a 200,000-device estate must not spend a
|
|
160
|
+
* whole sweep tick reading rows that are already correct. A pass that hits
|
|
161
|
+
* it leaves `coveredDeviceCount` alone rather than stamping a number it
|
|
162
|
+
* knows is short.
|
|
163
|
+
*/
|
|
164
|
+
export const MAX_DEVICES_SCANNED_PER_POLICY_SYNC = LIMIT_PER_PROJECT;
|
|
165
|
+
const MAX_RECORDED_FAILURES_PER_RUN = 20;
|
|
166
|
+
/*
|
|
167
|
+
* What a policy's monitors are keyed by, everywhere in this file: the policy
|
|
168
|
+
* that owns them AND the template that policy names right now. A monitor
|
|
169
|
+
* carrying the policy's id but a template the policy no longer names is a
|
|
170
|
+
* leftover from the policy's previous template and is removed — that is how
|
|
171
|
+
* "re-point a policy at a different template" re-clones the fleet.
|
|
172
|
+
*/
|
|
173
|
+
function ownershipKey(policyId, monitorTemplateId) {
|
|
174
|
+
return `${policyId.toString()}:${monitorTemplateId.toString()}`;
|
|
175
|
+
}
|
|
176
|
+
/*
|
|
177
|
+
* The scope columns the matcher needs, plus the columns that decide whether a
|
|
178
|
+
* device is provisionable at all. `labels` is a relation, so it has to be
|
|
179
|
+
* asked for by name or it arrives undefined and every label-scoped policy
|
|
180
|
+
* silently matches nothing.
|
|
181
|
+
*/
|
|
182
|
+
const RECONCILE_DEVICE_SELECT = {
|
|
183
|
+
_id: true,
|
|
184
|
+
projectId: true,
|
|
185
|
+
name: true,
|
|
186
|
+
hostname: true,
|
|
187
|
+
siteId: true,
|
|
188
|
+
networkDeviceRoleId: true,
|
|
189
|
+
isArchived: true,
|
|
190
|
+
monitoringMethod: true,
|
|
191
|
+
probeId: true,
|
|
192
|
+
labels: {
|
|
193
|
+
_id: true,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
class NetworkAlertPolicyEngineServiceClass {
|
|
197
|
+
/*
|
|
198
|
+
* A fresh run. `monitorBudget` is per project per run — see
|
|
199
|
+
* MAX_MONITORS_PER_POLICY_SYNC.
|
|
200
|
+
*/
|
|
201
|
+
createRunContext(projectId, monitorBudget = MAX_MONITORS_PER_POLICY_SYNC) {
|
|
202
|
+
return {
|
|
203
|
+
projectId: projectId,
|
|
204
|
+
policies: null,
|
|
205
|
+
templatesById: new Map(),
|
|
206
|
+
planChecked: false,
|
|
207
|
+
planException: null,
|
|
208
|
+
isStopped: false,
|
|
209
|
+
monitorBudget: monitorBudget,
|
|
210
|
+
monitorsWritten: 0,
|
|
211
|
+
monitorsCreated: 0,
|
|
212
|
+
monitorsAdopted: 0,
|
|
213
|
+
monitorsDeleted: 0,
|
|
214
|
+
monitorsPaused: 0,
|
|
215
|
+
isTruncated: false,
|
|
216
|
+
failures: [],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* THE mutation path. Brings one device's policy-owned monitors in line
|
|
221
|
+
* with the project's policies, under the device's own Redis lock.
|
|
222
|
+
*
|
|
223
|
+
* Never throws for an ordinary failure: a device create must not fail
|
|
224
|
+
* because a template was deleted or Redis is down. Failures are recorded
|
|
225
|
+
* on the run context (and reach `lastSyncError` when the caller is a
|
|
226
|
+
* policy sync); the next sweep recomputes the same difference.
|
|
227
|
+
*/
|
|
228
|
+
async reconcileDevice(data) {
|
|
229
|
+
const context = data.context || this.createRunContext(data.projectId);
|
|
230
|
+
if (context.isStopped) {
|
|
231
|
+
return context;
|
|
232
|
+
}
|
|
233
|
+
let mutex = null;
|
|
234
|
+
try {
|
|
235
|
+
mutex = await Semaphore.lock({
|
|
236
|
+
key: policyDeviceLockKey(data.deviceId),
|
|
237
|
+
namespace: POLICY_DEVICE_LOCK_NAMESPACE,
|
|
238
|
+
lockTimeout: POLICY_DEVICE_LOCK_TIMEOUT_MS,
|
|
239
|
+
acquireAttemptsLimit: POLICY_DEVICE_LOCK_ACQUIRE_ATTEMPTS,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
/*
|
|
244
|
+
* Somebody else is reconciling this device right now (or Redis is
|
|
245
|
+
* unavailable). Both are safely skippable: the other pass computes the
|
|
246
|
+
* same difference from fresher data, and the sweep runs again in five
|
|
247
|
+
* minutes. Debug, not error — under a busy sweep this is the normal
|
|
248
|
+
* outcome of a device event landing mid-pass.
|
|
249
|
+
*/
|
|
250
|
+
logger.debug(`NetworkAlertPolicy engine: could not lock device ${data.deviceId.toString()} for reconciliation; another pass holds it (or Redis is unavailable): ${error}`);
|
|
251
|
+
return context;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
await this.reconcileDeviceUnderLock({
|
|
255
|
+
projectId: data.projectId,
|
|
256
|
+
deviceId: data.deviceId,
|
|
257
|
+
context: context,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
this.recordFailure(context, `Could not reconcile device ${data.deviceId.toString()}: ${error}`);
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
try {
|
|
265
|
+
await Semaphore.release(mutex);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
logger.error(`NetworkAlertPolicy engine: failed to release the lock on device ${data.deviceId.toString()}: ${error}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return context;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Reconcile a handful of devices in one run — what a device write does
|
|
275
|
+
* inline. Callers that may have written more than
|
|
276
|
+
* MAX_INLINE_RECONCILE_DEVICES devices must not call this; the sweep is
|
|
277
|
+
* for them.
|
|
278
|
+
*/
|
|
279
|
+
async reconcileDevices(data) {
|
|
280
|
+
const context = data.context || this.createRunContext(data.projectId);
|
|
281
|
+
for (const deviceId of data.deviceIds) {
|
|
282
|
+
if (context.isStopped) {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
await this.reconcileDevice({
|
|
286
|
+
projectId: data.projectId,
|
|
287
|
+
deviceId: deviceId,
|
|
288
|
+
context: context,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return context;
|
|
292
|
+
}
|
|
293
|
+
async reconcileDeviceUnderLock(data) {
|
|
294
|
+
var _a;
|
|
295
|
+
const context = data.context;
|
|
296
|
+
/*
|
|
297
|
+
* Read fresh under the lock, never from the caller's snapshot: the whole
|
|
298
|
+
* point of the lock is that the difference is computed from the state
|
|
299
|
+
* that is true right now.
|
|
300
|
+
*/
|
|
301
|
+
const device = await NetworkDeviceService.findOneBy({
|
|
302
|
+
query: {
|
|
303
|
+
_id: data.deviceId,
|
|
304
|
+
projectId: data.projectId,
|
|
305
|
+
},
|
|
306
|
+
select: RECONCILE_DEVICE_SELECT,
|
|
307
|
+
props: {
|
|
308
|
+
isRoot: true,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
const actualMonitors = await MonitorService.findBy({
|
|
312
|
+
query: {
|
|
313
|
+
projectId: data.projectId,
|
|
314
|
+
autoProvisionedNetworkDeviceId: data.deviceId,
|
|
315
|
+
// The second half of "a policy made this". See the file header.
|
|
316
|
+
networkAlertPolicyId: QueryHelper.notNull(),
|
|
317
|
+
},
|
|
318
|
+
select: {
|
|
319
|
+
_id: true,
|
|
320
|
+
networkAlertPolicyId: true,
|
|
321
|
+
monitorTemplateId: true,
|
|
322
|
+
disableActiveMonitoring: true,
|
|
323
|
+
},
|
|
324
|
+
limit: LIMIT_MAX,
|
|
325
|
+
skip: 0,
|
|
326
|
+
props: {
|
|
327
|
+
isRoot: true,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
/*
|
|
331
|
+
* A device that is gone — or that never was in this project — keeps no
|
|
332
|
+
* policy monitors.
|
|
333
|
+
*
|
|
334
|
+
* NetworkDeviceService.onBeforeDelete removes them before the device row
|
|
335
|
+
* goes, so this branch should be unreachable through the ordinary path;
|
|
336
|
+
* and for a HARD delete Monitor's RESTRICT foreign key would refuse
|
|
337
|
+
* outright. What makes it reachable at all is a SOFT delete, which
|
|
338
|
+
* stamps deletedAt without touching the foreign key: the device stops
|
|
339
|
+
* being readable and its monitors are left behind. The cleanup is scoped
|
|
340
|
+
* to the same (project, device) pair the monitors were read under, so it
|
|
341
|
+
* can never reach further than the device that vanished.
|
|
342
|
+
*/
|
|
343
|
+
if (!device) {
|
|
344
|
+
await this.deleteMonitors({
|
|
345
|
+
projectId: data.projectId,
|
|
346
|
+
monitors: actualMonitors,
|
|
347
|
+
context: context,
|
|
348
|
+
reason: `device ${data.deviceId.toString()} no longer exists`,
|
|
349
|
+
});
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const isProvisionable = this.isDeviceProvisionable(device);
|
|
353
|
+
/*
|
|
354
|
+
* Not provisionable: archived, monitor-backed, or without a probe.
|
|
355
|
+
* EVERY policy-owned monitor goes, including a paused one belonging to a
|
|
356
|
+
* disabled policy — a device whose health is now a bound monitor's has
|
|
357
|
+
* no use for a Network Device monitor at all, and leaving one behind
|
|
358
|
+
* would keep billing for a monitor nothing can ever feed. Turning the
|
|
359
|
+
* device back into a probe-polled one re-provisions it, because the
|
|
360
|
+
* method write reconciles it again.
|
|
361
|
+
*/
|
|
362
|
+
if (!isProvisionable) {
|
|
363
|
+
await this.deleteMonitors({
|
|
364
|
+
projectId: data.projectId,
|
|
365
|
+
monitors: actualMonitors,
|
|
366
|
+
context: context,
|
|
367
|
+
reason: this.describeUnprovisionable(device),
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const policies = await this.getProjectPolicies(context);
|
|
372
|
+
const policiesById = new Map();
|
|
373
|
+
for (const policy of policies) {
|
|
374
|
+
if (policy.id) {
|
|
375
|
+
policiesById.set(policy.id.toString(), policy);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const deviceScope = this.toScopeDevice(device);
|
|
379
|
+
// DESIRED: enabled, template-carrying policies whose scope covers this device.
|
|
380
|
+
const desiredByOwnershipKey = new Map();
|
|
381
|
+
for (const policy of policies) {
|
|
382
|
+
if (!policy.id || !policy.isEnabled || !policy.monitorTemplateId) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (!NetworkAlertPolicyScopeUtil.matchesDevice(policy.scope, deviceScope)) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
desiredByOwnershipKey.set(ownershipKey(policy.id, policy.monitorTemplateId), {
|
|
389
|
+
policy: policy,
|
|
390
|
+
monitorTemplateId: policy.monitorTemplateId,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
const satisfiedOwnershipKeys = new Set();
|
|
394
|
+
const monitorsToDelete = [];
|
|
395
|
+
const monitorsToPause = [];
|
|
396
|
+
for (const monitor of actualMonitors) {
|
|
397
|
+
const policyId = monitor.networkAlertPolicyId;
|
|
398
|
+
if (!policyId) {
|
|
399
|
+
// The query asked for NOT NULL; belt and braces against a stale row.
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const owner = policiesById.get(policyId.toString());
|
|
403
|
+
/*
|
|
404
|
+
* The owning policy is gone (deleted, and this monitor outlived the
|
|
405
|
+
* delete's own cleanup). Nothing owns it any more, and nobody will
|
|
406
|
+
* ever tear it down if this pass does not.
|
|
407
|
+
*/
|
|
408
|
+
if (!owner) {
|
|
409
|
+
monitorsToDelete.push(monitor);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const key = monitor.monitorTemplateId
|
|
413
|
+
? ownershipKey(policyId, monitor.monitorTemplateId)
|
|
414
|
+
: "";
|
|
415
|
+
if (key && desiredByOwnershipKey.has(key)) {
|
|
416
|
+
satisfiedOwnershipKeys.add(key);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
/*
|
|
420
|
+
* A DISABLED policy's monitors survive. Disabling is a pause, not a
|
|
421
|
+
* teardown — the operator's way to stop alerting on a fleet for an
|
|
422
|
+
* afternoon without losing every monitor's incident history and
|
|
423
|
+
* without re-provisioning (and re-billing) hundreds of rows when they
|
|
424
|
+
* switch it back on. What disabling DOES do is stop the monitors
|
|
425
|
+
* running, which is `disableActiveMonitoring` (honoured by the walk
|
|
426
|
+
* fan-out), asserted here so a monitor that missed the transition
|
|
427
|
+
* write still ends up paused.
|
|
428
|
+
*
|
|
429
|
+
* A template-less policy — only reachable through the FK's SET NULL
|
|
430
|
+
* backstop, since the template delete guard refuses the delete — is
|
|
431
|
+
* treated identically: there is nothing to provision from, so nothing
|
|
432
|
+
* is provisioned and nothing is destroyed.
|
|
433
|
+
*/
|
|
434
|
+
const isPausedOwner = !owner.isEnabled || !owner.monitorTemplateId;
|
|
435
|
+
const stillMatchesOwnerScope = NetworkAlertPolicyScopeUtil.matchesDevice(owner.scope, deviceScope);
|
|
436
|
+
const carriesOwnerTemplate = Boolean(owner.monitorTemplateId &&
|
|
437
|
+
monitor.monitorTemplateId &&
|
|
438
|
+
owner.monitorTemplateId.toString() ===
|
|
439
|
+
monitor.monitorTemplateId.toString());
|
|
440
|
+
if (isPausedOwner &&
|
|
441
|
+
stillMatchesOwnerScope &&
|
|
442
|
+
(carriesOwnerTemplate || !owner.monitorTemplateId)) {
|
|
443
|
+
if (!monitor.disableActiveMonitoring) {
|
|
444
|
+
monitorsToPause.push(monitor);
|
|
445
|
+
}
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
/*
|
|
449
|
+
* What is left is a monitor this policy no longer wants on this
|
|
450
|
+
* device: the scope shrank away from it, or the policy was re-pointed
|
|
451
|
+
* at a different template and this row is the old template's. Either
|
|
452
|
+
* way it is stamped with THIS policy's id, which is the only thing
|
|
453
|
+
* that makes it safe to delete.
|
|
454
|
+
*/
|
|
455
|
+
monitorsToDelete.push(monitor);
|
|
456
|
+
}
|
|
457
|
+
await this.deleteMonitors({
|
|
458
|
+
projectId: data.projectId,
|
|
459
|
+
monitors: monitorsToDelete,
|
|
460
|
+
context: context,
|
|
461
|
+
reason: `device ${(_a = device.id) === null || _a === void 0 ? void 0 : _a.toString()} is no longer covered by the owning policy`,
|
|
462
|
+
});
|
|
463
|
+
await this.pauseMonitors({
|
|
464
|
+
projectId: data.projectId,
|
|
465
|
+
monitors: monitorsToPause,
|
|
466
|
+
context: context,
|
|
467
|
+
});
|
|
468
|
+
for (const desired of desiredByOwnershipKey.values()) {
|
|
469
|
+
if (context.isStopped) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const key = ownershipKey(desired.policy.id, desired.monitorTemplateId);
|
|
473
|
+
if (satisfiedOwnershipKeys.has(key)) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
await this.provisionMonitor({
|
|
477
|
+
context: context,
|
|
478
|
+
device: device,
|
|
479
|
+
policy: desired.policy,
|
|
480
|
+
monitorTemplateId: desired.monitorTemplateId,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/*
|
|
485
|
+
* Give one device one policy's monitor: adopt the (device, template)
|
|
486
|
+
* monitor that already exists without an owner, or create it.
|
|
487
|
+
*
|
|
488
|
+
* ADOPTION, not duplication, and the reason is Monitor's partial unique
|
|
489
|
+
* index on (autoProvisionedNetworkDeviceId, monitorTemplateId). An
|
|
490
|
+
* auto-import rule provisioned from the same template, or a person built a
|
|
491
|
+
* monitor from it by hand, and that row already occupies the pair. A
|
|
492
|
+
* create would fail on the index on this pass and on every pass after it,
|
|
493
|
+
* so the policy would report a failure forever while a perfectly good
|
|
494
|
+
* monitor for exactly the right thing sat next to it. Stamping the policy
|
|
495
|
+
* id onto it is what "this policy covers this device" actually means.
|
|
496
|
+
*/
|
|
497
|
+
async provisionMonitor(data) {
|
|
498
|
+
var _a, _b, _c;
|
|
499
|
+
const context = data.context;
|
|
500
|
+
/*
|
|
501
|
+
* Peeked, not consumed. Nothing has been written yet, and the two ways
|
|
502
|
+
* this method can return without writing — an unusable template, a plan
|
|
503
|
+
* refusal — must not spend a unit of a budget they never used.
|
|
504
|
+
*/
|
|
505
|
+
if (!this.hasMonitorBudget(context)) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const unowned = await MonitorService.findOneBy({
|
|
509
|
+
query: {
|
|
510
|
+
projectId: context.projectId,
|
|
511
|
+
autoProvisionedNetworkDeviceId: data.device.id,
|
|
512
|
+
monitorTemplateId: data.monitorTemplateId,
|
|
513
|
+
networkAlertPolicyId: QueryHelper.isNull(),
|
|
514
|
+
},
|
|
515
|
+
select: {
|
|
516
|
+
_id: true,
|
|
517
|
+
},
|
|
518
|
+
props: {
|
|
519
|
+
isRoot: true,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
if (unowned && unowned.id) {
|
|
523
|
+
if (!this.takeMonitorBudget(context)) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
await this.adoptMonitor({
|
|
527
|
+
context: context,
|
|
528
|
+
monitorId: unowned.id,
|
|
529
|
+
policy: data.policy,
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const template = await this.getMonitorTemplate({
|
|
534
|
+
context: context,
|
|
535
|
+
monitorTemplateId: data.monitorTemplateId,
|
|
536
|
+
});
|
|
537
|
+
if (!template) {
|
|
538
|
+
this.recordFailure(context, `Monitor template ${data.monitorTemplateId.toString()} is missing or is not a Network Device template, so policy "${data.policy.name || ((_a = data.policy.id) === null || _a === void 0 ? void 0 : _a.toString())}" could not provision a monitor.`);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
/*
|
|
542
|
+
* Asked once per run, BEFORE the first create, so a project whose plan
|
|
543
|
+
* cannot hold another monitor gets one sentence on its policies rather
|
|
544
|
+
* than one failure per device.
|
|
545
|
+
*/
|
|
546
|
+
if (await this.stopIfPlanRefusesAnotherMonitor(context)) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (!this.takeMonitorBudget(context)) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
const monitor = NetworkDeviceMonitorTemplateUtil.buildMonitor({
|
|
554
|
+
template: template,
|
|
555
|
+
networkDevice: data.device,
|
|
556
|
+
});
|
|
557
|
+
/*
|
|
558
|
+
* The stamp that makes this monitor the policy's. Written on the
|
|
559
|
+
* create rather than in a follow-up update because a monitor that
|
|
560
|
+
* exists for a moment without its owner is a monitor the next pass
|
|
561
|
+
* would ADOPT — harmless — but also one a concurrent pass of a
|
|
562
|
+
* different policy could adopt, which would not be.
|
|
563
|
+
*/
|
|
564
|
+
monitor.networkAlertPolicyId = new ObjectID(data.policy.id.toString());
|
|
565
|
+
await MonitorService.create({
|
|
566
|
+
data: monitor,
|
|
567
|
+
props: {
|
|
568
|
+
isRoot: true,
|
|
569
|
+
tenantId: context.projectId,
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
context.monitorsCreated++;
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
/*
|
|
576
|
+
* A create can fail for two very different reasons, and telling them
|
|
577
|
+
* apart is the whole of prerequisite 5.
|
|
578
|
+
*
|
|
579
|
+
* If the PLAN now refuses another monitor, every remaining create in
|
|
580
|
+
* this run would fail identically: stop, and let the one message reach
|
|
581
|
+
* every policy the run stamps. The verdict is re-asked rather than
|
|
582
|
+
* pattern-matched out of the error text, so the classification cannot
|
|
583
|
+
* drift when MonitorService rewords its refusal.
|
|
584
|
+
*
|
|
585
|
+
* Otherwise this is one device's problem — a lost race on the unique
|
|
586
|
+
* index, a name collision, a template that stopped validating — so
|
|
587
|
+
* record it and carry on with the rest of the fleet.
|
|
588
|
+
*/
|
|
589
|
+
if (await this.stopIfPlanRefusesAnotherMonitor(context, true)) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const adopted = await this.adoptAfterCreateRace({
|
|
593
|
+
context: context,
|
|
594
|
+
device: data.device,
|
|
595
|
+
policy: data.policy,
|
|
596
|
+
monitorTemplateId: data.monitorTemplateId,
|
|
597
|
+
});
|
|
598
|
+
if (adopted) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
this.recordFailure(context, `Could not provision a monitor for device ${(_b = data.device.id) === null || _b === void 0 ? void 0 : _b.toString()} from policy "${data.policy.name || ((_c = data.policy.id) === null || _c === void 0 ? void 0 : _c.toString())}": ${error}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/*
|
|
605
|
+
* The unique index is the final race backstop: if another writer created
|
|
606
|
+
* the (device, template) monitor between our look and our create, the row
|
|
607
|
+
* that exists is the one the policy wanted. Claim it if it is unowned,
|
|
608
|
+
* count it as satisfied if it is already ours, and leave it alone if some
|
|
609
|
+
* other policy got there first (which the one-policy-per-template rule
|
|
610
|
+
* makes unreachable, but which must never turn into a delete).
|
|
611
|
+
*/
|
|
612
|
+
async adoptAfterCreateRace(data) {
|
|
613
|
+
const existing = await MonitorService.findOneBy({
|
|
614
|
+
query: {
|
|
615
|
+
projectId: data.context.projectId,
|
|
616
|
+
autoProvisionedNetworkDeviceId: data.device.id,
|
|
617
|
+
monitorTemplateId: data.monitorTemplateId,
|
|
618
|
+
},
|
|
619
|
+
select: {
|
|
620
|
+
_id: true,
|
|
621
|
+
networkAlertPolicyId: true,
|
|
622
|
+
},
|
|
623
|
+
props: {
|
|
624
|
+
isRoot: true,
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
if (!existing || !existing.id) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
if (!existing.networkAlertPolicyId) {
|
|
631
|
+
await this.adoptMonitor({
|
|
632
|
+
context: data.context,
|
|
633
|
+
monitorId: existing.id,
|
|
634
|
+
policy: data.policy,
|
|
635
|
+
});
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
return (existing.networkAlertPolicyId.toString() === data.policy.id.toString());
|
|
639
|
+
}
|
|
640
|
+
async adoptMonitor(data) {
|
|
641
|
+
var _a;
|
|
642
|
+
/*
|
|
643
|
+
* A provenance stamp, not an operational change: no hooks, no workflow,
|
|
644
|
+
* no realtime event, no audit row. The monitor keeps doing exactly what
|
|
645
|
+
* it was doing; all that changes is who is answerable for it.
|
|
646
|
+
*/
|
|
647
|
+
await MonitorService.updateColumnsByIdWithoutHooks({
|
|
648
|
+
id: data.monitorId,
|
|
649
|
+
data: {
|
|
650
|
+
networkAlertPolicyId: new ObjectID(data.policy.id.toString()),
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
data.context.monitorsAdopted++;
|
|
654
|
+
logger.debug(`NetworkAlertPolicy engine: adopted existing monitor ${data.monitorId.toString()} into policy "${data.policy.name || ((_a = data.policy.id) === null || _a === void 0 ? void 0 : _a.toString())}" rather than creating a duplicate.`);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Bring one policy's whole fleet in line: every device its scope matches
|
|
658
|
+
* gets a monitor, and every monitor it owns whose device no longer matches
|
|
659
|
+
* loses one. This is what the five-minute sweep runs, and what a policy
|
|
660
|
+
* save runs for the policy that was saved.
|
|
661
|
+
*
|
|
662
|
+
* Stamps the four engine columns the settings table reads, so a policy
|
|
663
|
+
* that has been failing since Tuesday says so where the operator is.
|
|
664
|
+
*/
|
|
665
|
+
async syncPolicy(data) {
|
|
666
|
+
const policy = await NetworkAlertPolicyService.findOneBy({
|
|
667
|
+
query: {
|
|
668
|
+
_id: data.policyId,
|
|
669
|
+
},
|
|
670
|
+
select: {
|
|
671
|
+
_id: true,
|
|
672
|
+
projectId: true,
|
|
673
|
+
name: true,
|
|
674
|
+
isEnabled: true,
|
|
675
|
+
monitorTemplateId: true,
|
|
676
|
+
scope: true,
|
|
677
|
+
},
|
|
678
|
+
props: {
|
|
679
|
+
isRoot: true,
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
if (!policy || !policy.id || !policy.projectId) {
|
|
683
|
+
// Deleted between the hook and here; its monitors went with it.
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
/*
|
|
687
|
+
* A run context carries a project's monitor budget and its plan verdict,
|
|
688
|
+
* so using one that belongs to a different project would spend the wrong
|
|
689
|
+
* project's budget and — worse — could read a plan refusal as this
|
|
690
|
+
* project's. The sweep groups policies by project, so this only fires on
|
|
691
|
+
* a caller mistake; it is corrected rather than trusted.
|
|
692
|
+
*/
|
|
693
|
+
const context = data.context &&
|
|
694
|
+
data.context.projectId.toString() === policy.projectId.toString()
|
|
695
|
+
? data.context
|
|
696
|
+
: this.createRunContext(policy.projectId);
|
|
697
|
+
/*
|
|
698
|
+
* A disabled or template-less policy provisions nothing and tears down
|
|
699
|
+
* nothing (see the paused branch in reconcileDeviceUnderLock). Its
|
|
700
|
+
* monitors are paused by the transition that disabled it, and stamping
|
|
701
|
+
* lastSyncAt here is honest: the engine did look, and the answer was
|
|
702
|
+
* "nothing to do".
|
|
703
|
+
*/
|
|
704
|
+
if (!policy.isEnabled || !policy.monitorTemplateId) {
|
|
705
|
+
await this.stampPolicy({
|
|
706
|
+
policyId: policy.id,
|
|
707
|
+
lastSyncAt: OneUptimeDate.getCurrentDate(),
|
|
708
|
+
lastSyncError: null,
|
|
709
|
+
});
|
|
710
|
+
return context;
|
|
711
|
+
}
|
|
712
|
+
const scannedDeviceIds = new Set();
|
|
713
|
+
let coveredDeviceCount = 0;
|
|
714
|
+
let isScanComplete = true;
|
|
715
|
+
try {
|
|
716
|
+
const forwardPass = await this.provisionForMatchingDevices({
|
|
717
|
+
policy: policy,
|
|
718
|
+
context: context,
|
|
719
|
+
scannedDeviceIds: scannedDeviceIds,
|
|
720
|
+
});
|
|
721
|
+
coveredDeviceCount = forwardPass.coveredDeviceCount;
|
|
722
|
+
isScanComplete = forwardPass.isComplete;
|
|
723
|
+
await this.reconcileOrphanedPolicyMonitors({
|
|
724
|
+
policy: policy,
|
|
725
|
+
context: context,
|
|
726
|
+
confirmedDeviceIds: isScanComplete ? scannedDeviceIds : null,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
this.recordFailure(context, `Could not sync policy "${policy.name || policy.id.toString()}": ${error}`);
|
|
731
|
+
}
|
|
732
|
+
const isCleanCompletePass = isScanComplete &&
|
|
733
|
+
!context.isTruncated &&
|
|
734
|
+
!context.isStopped &&
|
|
735
|
+
context.failures.length === 0;
|
|
736
|
+
await this.stampPolicy({
|
|
737
|
+
policyId: policy.id,
|
|
738
|
+
lastSyncAt: OneUptimeDate.getCurrentDate(),
|
|
739
|
+
lastSyncError: context.planException || context.failures[0] || null,
|
|
740
|
+
/*
|
|
741
|
+
* Only from a pass that actually counted the whole fleet. A truncated
|
|
742
|
+
* pass knows its number is short, and a short number beside a scope
|
|
743
|
+
* sentence reads as "the policy lost devices", which is worse than the
|
|
744
|
+
* previous pass's number staying put for five minutes.
|
|
745
|
+
*/
|
|
746
|
+
coveredDeviceCount: isScanComplete ? coveredDeviceCount : undefined,
|
|
747
|
+
/*
|
|
748
|
+
* ...and only when the pass rebuilt the fleet from a template the
|
|
749
|
+
* policy had just been re-pointed at, with nothing adopted (an adopted
|
|
750
|
+
* monitor was built by somebody else and its criteria are not this
|
|
751
|
+
* template's). Pushing a template EDIT onto an existing fleet is
|
|
752
|
+
* MonitorTemplateService.syncLinkedMonitors' job; it stamps this
|
|
753
|
+
* column through onMonitorTemplateSynced.
|
|
754
|
+
*/
|
|
755
|
+
templateSyncedAt: data.stampTemplateSyncedOnCleanPass &&
|
|
756
|
+
isCleanCompletePass &&
|
|
757
|
+
context.monitorsAdopted === 0
|
|
758
|
+
? OneUptimeDate.getCurrentDate()
|
|
759
|
+
: undefined,
|
|
760
|
+
});
|
|
761
|
+
return context;
|
|
762
|
+
}
|
|
763
|
+
/*
|
|
764
|
+
* Forward pass: every device the scope matches, paged by `_id`.
|
|
765
|
+
*
|
|
766
|
+
* `_id` keyset paging rather than skip/offset because the pass WRITES —
|
|
767
|
+
* provisioning a monitor does not move a device row, but archiving one
|
|
768
|
+
* during the pass does remove it from the result set, and an offset page
|
|
769
|
+
* then steps over a device that was never looked at. A keyset resumes from
|
|
770
|
+
* the last id seen, so a row leaving the set behind the cursor costs
|
|
771
|
+
* nothing.
|
|
772
|
+
*
|
|
773
|
+
* The monitors for a whole page are read in ONE query, so a fleet that is
|
|
774
|
+
* already correct — the overwhelmingly common case, five minutes after the
|
|
775
|
+
* last sweep — costs two statements per five hundred devices and takes no
|
|
776
|
+
* per-device lock at all.
|
|
777
|
+
*/
|
|
778
|
+
async provisionForMatchingDevices(data) {
|
|
779
|
+
var _a;
|
|
780
|
+
const monitorTemplateId = data.policy.monitorTemplateId;
|
|
781
|
+
const deviceQuery = this.buildScopeDeviceQuery({
|
|
782
|
+
projectId: data.context.projectId,
|
|
783
|
+
scope: data.policy.scope,
|
|
784
|
+
});
|
|
785
|
+
let cursor = null;
|
|
786
|
+
let coveredDeviceCount = 0;
|
|
787
|
+
let scanned = 0;
|
|
788
|
+
for (;;) {
|
|
789
|
+
const devices = await NetworkDeviceService.findBy({
|
|
790
|
+
query: cursor
|
|
791
|
+
? Object.assign(Object.assign({}, deviceQuery), { _id: QueryHelper.greaterThan(cursor) }) : deviceQuery,
|
|
792
|
+
select: RECONCILE_DEVICE_SELECT,
|
|
793
|
+
sort: {
|
|
794
|
+
_id: SortOrder.Ascending,
|
|
795
|
+
},
|
|
796
|
+
limit: POLICY_SYNC_PAGE_SIZE,
|
|
797
|
+
skip: 0,
|
|
798
|
+
props: {
|
|
799
|
+
isRoot: true,
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
if (devices.length === 0) {
|
|
803
|
+
return { coveredDeviceCount: coveredDeviceCount, isComplete: true };
|
|
804
|
+
}
|
|
805
|
+
cursor = devices[devices.length - 1].id;
|
|
806
|
+
scanned += devices.length;
|
|
807
|
+
/*
|
|
808
|
+
* `monitoringMethod` is free text whose NULL, "" and legacy "SNMP" all
|
|
809
|
+
* mean Probe, so it cannot be filtered in SQL without writing a
|
|
810
|
+
* predicate that would silently drop every pre-ping-first row. Filtered
|
|
811
|
+
* here instead, over one page at a time.
|
|
812
|
+
*/
|
|
813
|
+
const provisionableDevices = devices.filter((device) => {
|
|
814
|
+
return this.isDeviceProvisionable(device);
|
|
815
|
+
});
|
|
816
|
+
coveredDeviceCount += provisionableDevices.length;
|
|
817
|
+
for (const device of provisionableDevices) {
|
|
818
|
+
if (device.id) {
|
|
819
|
+
data.scannedDeviceIds.add(device.id.toString());
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const deviceIdsMissingMonitor = await this.findDevicesMissingPolicyMonitor({
|
|
823
|
+
projectId: data.context.projectId,
|
|
824
|
+
policyId: data.policy.id,
|
|
825
|
+
monitorTemplateId: monitorTemplateId,
|
|
826
|
+
devices: provisionableDevices,
|
|
827
|
+
});
|
|
828
|
+
for (const deviceId of deviceIdsMissingMonitor) {
|
|
829
|
+
if (data.context.isStopped || data.context.isTruncated) {
|
|
830
|
+
return {
|
|
831
|
+
coveredDeviceCount: coveredDeviceCount,
|
|
832
|
+
isComplete: false,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
await this.reconcileDevice({
|
|
836
|
+
projectId: data.context.projectId,
|
|
837
|
+
deviceId: deviceId,
|
|
838
|
+
context: data.context,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
if (devices.length < POLICY_SYNC_PAGE_SIZE) {
|
|
842
|
+
return { coveredDeviceCount: coveredDeviceCount, isComplete: true };
|
|
843
|
+
}
|
|
844
|
+
if (scanned >= MAX_DEVICES_SCANNED_PER_POLICY_SYNC) {
|
|
845
|
+
logger.debug(`NetworkAlertPolicy engine: policy ${(_a = data.policy.id) === null || _a === void 0 ? void 0 : _a.toString()} stopped scanning after ${scanned} devices; the next sweep continues.`);
|
|
846
|
+
return { coveredDeviceCount: coveredDeviceCount, isComplete: false };
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/*
|
|
851
|
+
* Which of these devices do NOT already carry this policy's current
|
|
852
|
+
* monitor. One statement for the whole page; only the answers get the
|
|
853
|
+
* per-device lock and the full difference.
|
|
854
|
+
*/
|
|
855
|
+
async findDevicesMissingPolicyMonitor(data) {
|
|
856
|
+
const deviceIds = data.devices.flatMap((device) => {
|
|
857
|
+
return device.id ? [device.id] : [];
|
|
858
|
+
});
|
|
859
|
+
if (deviceIds.length === 0) {
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
const monitors = await MonitorService.findBy({
|
|
863
|
+
query: {
|
|
864
|
+
projectId: data.projectId,
|
|
865
|
+
networkAlertPolicyId: data.policyId,
|
|
866
|
+
monitorTemplateId: data.monitorTemplateId,
|
|
867
|
+
autoProvisionedNetworkDeviceId: QueryHelper.any(deviceIds),
|
|
868
|
+
},
|
|
869
|
+
select: {
|
|
870
|
+
_id: true,
|
|
871
|
+
autoProvisionedNetworkDeviceId: true,
|
|
872
|
+
},
|
|
873
|
+
limit: LIMIT_MAX,
|
|
874
|
+
skip: 0,
|
|
875
|
+
props: {
|
|
876
|
+
isRoot: true,
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
const covered = new Set();
|
|
880
|
+
for (const monitor of monitors) {
|
|
881
|
+
if (monitor.autoProvisionedNetworkDeviceId) {
|
|
882
|
+
covered.add(monitor.autoProvisionedNetworkDeviceId.toString());
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return deviceIds.filter((deviceId) => {
|
|
886
|
+
return !covered.has(deviceId.toString());
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
/*
|
|
890
|
+
* Reverse pass: the policy's own monitors, paged by `_id`, looking for the
|
|
891
|
+
* ones whose device the forward pass did NOT confirm — a device that left
|
|
892
|
+
* the scope, was archived, went monitor-backed or lost its probe. These
|
|
893
|
+
* are the deletes a shrinking scope produces, and they are computed from
|
|
894
|
+
* the MONITORS rather than from the devices because the set of monitors a
|
|
895
|
+
* policy owns is bounded by what it provisioned, while "every device that
|
|
896
|
+
* no longer matches" is the whole estate.
|
|
897
|
+
*
|
|
898
|
+
* `confirmedDeviceIds` is the forward pass's answer, and it is only
|
|
899
|
+
* trustworthy when that pass was exhaustive. When it was not, every
|
|
900
|
+
* monitor's device is re-evaluated instead — slower, and correct.
|
|
901
|
+
*/
|
|
902
|
+
async reconcileOrphanedPolicyMonitors(data) {
|
|
903
|
+
let cursor = null;
|
|
904
|
+
for (;;) {
|
|
905
|
+
if (data.context.isStopped || data.context.isTruncated) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const baseQuery = {
|
|
909
|
+
projectId: data.context.projectId,
|
|
910
|
+
networkAlertPolicyId: data.policy.id,
|
|
911
|
+
};
|
|
912
|
+
const monitors = await MonitorService.findBy({
|
|
913
|
+
query: cursor
|
|
914
|
+
? Object.assign(Object.assign({}, baseQuery), { _id: QueryHelper.greaterThan(cursor) }) : baseQuery,
|
|
915
|
+
select: {
|
|
916
|
+
_id: true,
|
|
917
|
+
autoProvisionedNetworkDeviceId: true,
|
|
918
|
+
monitorTemplateId: true,
|
|
919
|
+
},
|
|
920
|
+
sort: {
|
|
921
|
+
_id: SortOrder.Ascending,
|
|
922
|
+
},
|
|
923
|
+
limit: POLICY_SYNC_PAGE_SIZE,
|
|
924
|
+
skip: 0,
|
|
925
|
+
props: {
|
|
926
|
+
isRoot: true,
|
|
927
|
+
},
|
|
928
|
+
});
|
|
929
|
+
if (monitors.length === 0) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
cursor = monitors[monitors.length - 1].id;
|
|
933
|
+
const deviceIdsToRecheck = await this.findMonitorDevicesNeedingRecheck({
|
|
934
|
+
policy: data.policy,
|
|
935
|
+
context: data.context,
|
|
936
|
+
monitors: monitors,
|
|
937
|
+
confirmedDeviceIds: data.confirmedDeviceIds,
|
|
938
|
+
});
|
|
939
|
+
for (const deviceId of deviceIdsToRecheck) {
|
|
940
|
+
if (data.context.isStopped || data.context.isTruncated) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
await this.reconcileDevice({
|
|
944
|
+
projectId: data.context.projectId,
|
|
945
|
+
deviceId: deviceId,
|
|
946
|
+
context: data.context,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
if (monitors.length < POLICY_SYNC_PAGE_SIZE) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async findMonitorDevicesNeedingRecheck(data) {
|
|
955
|
+
var _a, _b, _c;
|
|
956
|
+
const monitorTemplateId = data.policy.monitorTemplateId.toString();
|
|
957
|
+
const candidates = new Map();
|
|
958
|
+
for (const monitor of data.monitors) {
|
|
959
|
+
const deviceId = monitor.autoProvisionedNetworkDeviceId;
|
|
960
|
+
if (!deviceId) {
|
|
961
|
+
/*
|
|
962
|
+
* A policy-stamped monitor with no device provenance cannot be
|
|
963
|
+
* reconciled by device, and deleting it on a guess would destroy a
|
|
964
|
+
* monitor the engine cannot prove is its own. Report it instead: it
|
|
965
|
+
* takes a hand-written UPDATE to produce, and the operator should
|
|
966
|
+
* see that it happened.
|
|
967
|
+
*/
|
|
968
|
+
this.recordFailure(data.context, `Monitor ${(_a = monitor.id) === null || _a === void 0 ? void 0 : _a.toString()} carries policy "${data.policy.name || ((_b = data.policy.id) === null || _b === void 0 ? void 0 : _b.toString())}" but no device provenance; it was left untouched.`);
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
/*
|
|
972
|
+
* A monitor from the policy's PREVIOUS template always needs the full
|
|
973
|
+
* per-device difference, even for a device the forward pass confirmed:
|
|
974
|
+
* the forward pass only asked whether the CURRENT template's monitor
|
|
975
|
+
* exists.
|
|
976
|
+
*/
|
|
977
|
+
const carriesCurrentTemplate = ((_c = monitor.monitorTemplateId) === null || _c === void 0 ? void 0 : _c.toString()) === monitorTemplateId;
|
|
978
|
+
if (carriesCurrentTemplate &&
|
|
979
|
+
data.confirmedDeviceIds &&
|
|
980
|
+
data.confirmedDeviceIds.has(deviceId.toString())) {
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
candidates.set(deviceId.toString(), deviceId);
|
|
984
|
+
}
|
|
985
|
+
if (candidates.size === 0) {
|
|
986
|
+
return [];
|
|
987
|
+
}
|
|
988
|
+
/*
|
|
989
|
+
* When the forward pass was exhaustive its set is the whole answer:
|
|
990
|
+
* anything not in it has already failed the scope or provisionability
|
|
991
|
+
* test, so it goes straight to reconcileDevice without a second read.
|
|
992
|
+
*/
|
|
993
|
+
if (data.confirmedDeviceIds) {
|
|
994
|
+
return Array.from(candidates.values());
|
|
995
|
+
}
|
|
996
|
+
const devices = await NetworkDeviceService.findBy({
|
|
997
|
+
query: {
|
|
998
|
+
projectId: data.context.projectId,
|
|
999
|
+
_id: QueryHelper.any(Array.from(candidates.values())),
|
|
1000
|
+
},
|
|
1001
|
+
select: RECONCILE_DEVICE_SELECT,
|
|
1002
|
+
limit: LIMIT_MAX,
|
|
1003
|
+
skip: 0,
|
|
1004
|
+
props: {
|
|
1005
|
+
isRoot: true,
|
|
1006
|
+
},
|
|
1007
|
+
});
|
|
1008
|
+
const stillCovered = new Set();
|
|
1009
|
+
for (const device of devices) {
|
|
1010
|
+
if (!device.id || !this.isDeviceProvisionable(device)) {
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (NetworkAlertPolicyScopeUtil.matchesDevice(data.policy.scope, this.toScopeDevice(device))) {
|
|
1014
|
+
stillCovered.add(device.id.toString());
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const needsRecheck = [];
|
|
1018
|
+
for (const [deviceIdString, deviceId] of candidates) {
|
|
1019
|
+
if (!stillCovered.has(deviceIdString)) {
|
|
1020
|
+
needsRecheck.push(deviceId);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return needsRecheck;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Every monitor a policy owns, removed as root, paged — what
|
|
1027
|
+
* NetworkAlertPolicyService.onBeforeDelete calls before the row goes.
|
|
1028
|
+
*
|
|
1029
|
+
* As ROOT, deliberately. These monitors are system-managed: the API cannot
|
|
1030
|
+
* create the `networkAlertPolicyId` column and cannot update it, so the
|
|
1031
|
+
* operator deleting the policy never chose to own them individually.
|
|
1032
|
+
* Making the delete depend on their monitor-delete permission would leave
|
|
1033
|
+
* a project with orphaned, still-billed monitors and a policy row that no
|
|
1034
|
+
* longer explains them.
|
|
1035
|
+
*
|
|
1036
|
+
* The SET NULL foreign key stays a backstop for a row this never reached,
|
|
1037
|
+
* not the mechanism: a monitor that loses its policy id that way becomes
|
|
1038
|
+
* an ordinary auto-provisioned monitor rather than a dangling reference.
|
|
1039
|
+
*/
|
|
1040
|
+
async deleteMonitorsOwnedByPolicy(data) {
|
|
1041
|
+
let deleted = 0;
|
|
1042
|
+
let previousFirstMonitorId = "";
|
|
1043
|
+
for (;;) {
|
|
1044
|
+
const monitors = await MonitorService.findBy({
|
|
1045
|
+
query: {
|
|
1046
|
+
projectId: data.projectId,
|
|
1047
|
+
networkAlertPolicyId: data.policyId,
|
|
1048
|
+
},
|
|
1049
|
+
select: {
|
|
1050
|
+
_id: true,
|
|
1051
|
+
},
|
|
1052
|
+
sort: {
|
|
1053
|
+
_id: SortOrder.Ascending,
|
|
1054
|
+
},
|
|
1055
|
+
limit: POLICY_SYNC_PAGE_SIZE,
|
|
1056
|
+
skip: 0,
|
|
1057
|
+
props: {
|
|
1058
|
+
isRoot: true,
|
|
1059
|
+
},
|
|
1060
|
+
});
|
|
1061
|
+
if (monitors.length === 0) {
|
|
1062
|
+
return deleted;
|
|
1063
|
+
}
|
|
1064
|
+
const monitorIds = monitors.flatMap((monitor) => {
|
|
1065
|
+
return monitor.id ? [monitor.id] : [];
|
|
1066
|
+
});
|
|
1067
|
+
if (monitorIds.length === 0) {
|
|
1068
|
+
return deleted;
|
|
1069
|
+
}
|
|
1070
|
+
/*
|
|
1071
|
+
* Deleted by id AND still keyed on the policy, so a row that changed
|
|
1072
|
+
* owner between the read and the delete is left alone. Always from the
|
|
1073
|
+
* front of the set rather than at an offset: each batch shrinks it.
|
|
1074
|
+
*/
|
|
1075
|
+
await MonitorService.deleteBy({
|
|
1076
|
+
query: {
|
|
1077
|
+
_id: QueryHelper.any(monitorIds),
|
|
1078
|
+
projectId: data.projectId,
|
|
1079
|
+
networkAlertPolicyId: data.policyId,
|
|
1080
|
+
},
|
|
1081
|
+
limit: LIMIT_MAX,
|
|
1082
|
+
skip: 0,
|
|
1083
|
+
props: {
|
|
1084
|
+
isRoot: true,
|
|
1085
|
+
},
|
|
1086
|
+
});
|
|
1087
|
+
deleted += monitorIds.length;
|
|
1088
|
+
if (monitors.length < POLICY_SYNC_PAGE_SIZE) {
|
|
1089
|
+
return deleted;
|
|
1090
|
+
}
|
|
1091
|
+
/*
|
|
1092
|
+
* The page is always read from the front, because each batch is
|
|
1093
|
+
* supposed to shrink the set. If the same first row comes back the set
|
|
1094
|
+
* did NOT shrink, and reading it again would spin a worker forever
|
|
1095
|
+
* rather than delete anything. Stop and say so.
|
|
1096
|
+
*/
|
|
1097
|
+
const firstMonitorId = monitorIds[0].toString();
|
|
1098
|
+
if (firstMonitorId === previousFirstMonitorId) {
|
|
1099
|
+
logger.error(`NetworkAlertPolicy engine: could not delete the monitors of policy ${data.policyId.toString()}; the set stopped shrinking at ${monitorIds.length} row(s).`);
|
|
1100
|
+
return deleted;
|
|
1101
|
+
}
|
|
1102
|
+
previousFirstMonitorId = firstMonitorId;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* The policy-owned monitors of a set of devices, removed as root — what
|
|
1107
|
+
* NetworkDeviceService.onBeforeDelete calls before the devices go.
|
|
1108
|
+
*
|
|
1109
|
+
* Monitor's provenance foreign key is RESTRICT, so a policy monitor left
|
|
1110
|
+
* behind would make the device delete fail outright. Root for the same
|
|
1111
|
+
* reason as the policy delete: nobody chose these monitors individually,
|
|
1112
|
+
* so nobody should need permission on them individually to delete the
|
|
1113
|
+
* device they describe.
|
|
1114
|
+
*/
|
|
1115
|
+
async deletePolicyMonitorsForDevices(data) {
|
|
1116
|
+
if (data.deviceIds.length === 0) {
|
|
1117
|
+
return 0;
|
|
1118
|
+
}
|
|
1119
|
+
let deleted = 0;
|
|
1120
|
+
let previousFirstMonitorId = "";
|
|
1121
|
+
for (;;) {
|
|
1122
|
+
const monitors = await MonitorService.findBy({
|
|
1123
|
+
query: {
|
|
1124
|
+
projectId: data.projectId,
|
|
1125
|
+
autoProvisionedNetworkDeviceId: QueryHelper.any(data.deviceIds),
|
|
1126
|
+
networkAlertPolicyId: QueryHelper.notNull(),
|
|
1127
|
+
},
|
|
1128
|
+
select: {
|
|
1129
|
+
_id: true,
|
|
1130
|
+
},
|
|
1131
|
+
sort: {
|
|
1132
|
+
_id: SortOrder.Ascending,
|
|
1133
|
+
},
|
|
1134
|
+
limit: POLICY_SYNC_PAGE_SIZE,
|
|
1135
|
+
skip: 0,
|
|
1136
|
+
props: {
|
|
1137
|
+
isRoot: true,
|
|
1138
|
+
},
|
|
1139
|
+
});
|
|
1140
|
+
if (monitors.length === 0) {
|
|
1141
|
+
return deleted;
|
|
1142
|
+
}
|
|
1143
|
+
const monitorIds = monitors.flatMap((monitor) => {
|
|
1144
|
+
return monitor.id ? [monitor.id] : [];
|
|
1145
|
+
});
|
|
1146
|
+
if (monitorIds.length === 0) {
|
|
1147
|
+
return deleted;
|
|
1148
|
+
}
|
|
1149
|
+
await MonitorService.deleteBy({
|
|
1150
|
+
query: {
|
|
1151
|
+
_id: QueryHelper.any(monitorIds),
|
|
1152
|
+
projectId: data.projectId,
|
|
1153
|
+
// Still policy-owned at delete time, never a hand-made monitor.
|
|
1154
|
+
networkAlertPolicyId: QueryHelper.notNull(),
|
|
1155
|
+
},
|
|
1156
|
+
limit: LIMIT_MAX,
|
|
1157
|
+
skip: 0,
|
|
1158
|
+
props: {
|
|
1159
|
+
isRoot: true,
|
|
1160
|
+
},
|
|
1161
|
+
});
|
|
1162
|
+
deleted += monitorIds.length;
|
|
1163
|
+
if (monitors.length < POLICY_SYNC_PAGE_SIZE) {
|
|
1164
|
+
return deleted;
|
|
1165
|
+
}
|
|
1166
|
+
// Same no-progress guard as deleteMonitorsOwnedByPolicy.
|
|
1167
|
+
const firstMonitorId = monitorIds[0].toString();
|
|
1168
|
+
if (firstMonitorId === previousFirstMonitorId) {
|
|
1169
|
+
logger.error(`NetworkAlertPolicy engine: could not delete the policy-owned monitors of ${data.deviceIds.length} device(s); the set stopped shrinking at ${monitorIds.length} row(s).`);
|
|
1170
|
+
return deleted;
|
|
1171
|
+
}
|
|
1172
|
+
previousFirstMonitorId = firstMonitorId;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Disabling a policy pauses its monitors; enabling one resumes them.
|
|
1177
|
+
*
|
|
1178
|
+
* `disableActiveMonitoring` is what the Network Device walk fan-out reads
|
|
1179
|
+
* before it feeds a monitor, so a paused monitor stops producing criteria
|
|
1180
|
+
* verdicts, incidents and alerts while keeping its history, its
|
|
1181
|
+
* dependencies and its id. That is what makes disabling reversible in a
|
|
1182
|
+
* way deleting is not.
|
|
1183
|
+
*
|
|
1184
|
+
* Paged by the flag itself: each page writes the rows it read, so the
|
|
1185
|
+
* matching set shrinks every pass and the loop terminates without a
|
|
1186
|
+
* cursor. Through updateBy, not a raw column write, so workflows, realtime
|
|
1187
|
+
* and audit see the pause exactly as they would an operator's.
|
|
1188
|
+
*/
|
|
1189
|
+
async setPolicyMonitorsPaused(data) {
|
|
1190
|
+
let updated = 0;
|
|
1191
|
+
for (;;) {
|
|
1192
|
+
const monitors = await MonitorService.findBy({
|
|
1193
|
+
query: {
|
|
1194
|
+
projectId: data.projectId,
|
|
1195
|
+
networkAlertPolicyId: data.policyId,
|
|
1196
|
+
disableActiveMonitoring: !data.isPaused,
|
|
1197
|
+
},
|
|
1198
|
+
select: {
|
|
1199
|
+
_id: true,
|
|
1200
|
+
},
|
|
1201
|
+
sort: {
|
|
1202
|
+
_id: SortOrder.Ascending,
|
|
1203
|
+
},
|
|
1204
|
+
limit: POLICY_SYNC_PAGE_SIZE,
|
|
1205
|
+
skip: 0,
|
|
1206
|
+
props: {
|
|
1207
|
+
isRoot: true,
|
|
1208
|
+
},
|
|
1209
|
+
});
|
|
1210
|
+
if (monitors.length === 0) {
|
|
1211
|
+
return updated;
|
|
1212
|
+
}
|
|
1213
|
+
const monitorIds = monitors.flatMap((monitor) => {
|
|
1214
|
+
return monitor.id ? [monitor.id] : [];
|
|
1215
|
+
});
|
|
1216
|
+
if (monitorIds.length === 0) {
|
|
1217
|
+
return updated;
|
|
1218
|
+
}
|
|
1219
|
+
const written = await MonitorService.updateBy({
|
|
1220
|
+
query: {
|
|
1221
|
+
_id: QueryHelper.any(monitorIds),
|
|
1222
|
+
projectId: data.projectId,
|
|
1223
|
+
networkAlertPolicyId: data.policyId,
|
|
1224
|
+
},
|
|
1225
|
+
data: {
|
|
1226
|
+
disableActiveMonitoring: data.isPaused,
|
|
1227
|
+
},
|
|
1228
|
+
limit: LIMIT_MAX,
|
|
1229
|
+
skip: 0,
|
|
1230
|
+
props: {
|
|
1231
|
+
isRoot: true,
|
|
1232
|
+
},
|
|
1233
|
+
});
|
|
1234
|
+
updated += monitorIds.length;
|
|
1235
|
+
/*
|
|
1236
|
+
* The page is re-read on the flag, so a page that wrote nothing would
|
|
1237
|
+
* be read again forever. Only reachable if the write is being refused
|
|
1238
|
+
* or reverted by something else; stop and say so rather than spin.
|
|
1239
|
+
*/
|
|
1240
|
+
if (written === 0) {
|
|
1241
|
+
logger.error(`NetworkAlertPolicy engine: could not ${data.isPaused ? "pause" : "resume"} ${monitorIds.length} monitor(s) of policy ${data.policyId.toString()}; giving up on this pass.`);
|
|
1242
|
+
return updated;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* The template's configuration has just been pushed onto every monitor
|
|
1248
|
+
* cloned from it (MonitorTemplateService's explicit Sync). If a policy
|
|
1249
|
+
* provisions from that template, its fleet is now in step with it — which
|
|
1250
|
+
* is precisely what `templateSyncedAt` means to the settings table.
|
|
1251
|
+
*
|
|
1252
|
+
* Never throws: a failure to stamp a bookkeeping column must not fail the
|
|
1253
|
+
* sync the operator asked for.
|
|
1254
|
+
*/
|
|
1255
|
+
async onMonitorTemplateSynced(data) {
|
|
1256
|
+
try {
|
|
1257
|
+
const policies = await NetworkAlertPolicyService.findBy({
|
|
1258
|
+
query: {
|
|
1259
|
+
projectId: data.projectId,
|
|
1260
|
+
monitorTemplateId: data.monitorTemplateId,
|
|
1261
|
+
},
|
|
1262
|
+
select: {
|
|
1263
|
+
_id: true,
|
|
1264
|
+
},
|
|
1265
|
+
limit: LIMIT_MAX,
|
|
1266
|
+
skip: 0,
|
|
1267
|
+
props: {
|
|
1268
|
+
isRoot: true,
|
|
1269
|
+
},
|
|
1270
|
+
});
|
|
1271
|
+
for (const policy of policies) {
|
|
1272
|
+
if (!policy.id) {
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
await this.stampPolicy({
|
|
1276
|
+
policyId: policy.id,
|
|
1277
|
+
templateSyncedAt: OneUptimeDate.getCurrentDate(),
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
catch (error) {
|
|
1282
|
+
logger.error(`NetworkAlertPolicy engine: could not stamp templateSyncedAt for monitor template ${data.monitorTemplateId.toString()}: ${error}`);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
/*
|
|
1286
|
+
* The four engine columns, written without hooks.
|
|
1287
|
+
*
|
|
1288
|
+
* Not through updateOneById, and that is load-bearing rather than a
|
|
1289
|
+
* performance choice: NetworkAlertPolicyService.onUpdateSuccess calls the
|
|
1290
|
+
* engine, so a stamp made through the ordinary update path would sync the
|
|
1291
|
+
* policy, which would stamp it, which would sync it. `undefined` fields
|
|
1292
|
+
* are left out entirely, so a truncated pass does not overwrite the
|
|
1293
|
+
* previous pass's coveredDeviceCount with a number it knows is short.
|
|
1294
|
+
*/
|
|
1295
|
+
async stampPolicy(data) {
|
|
1296
|
+
const columns = {};
|
|
1297
|
+
if (data.lastSyncAt !== undefined) {
|
|
1298
|
+
columns["lastSyncAt"] = data.lastSyncAt;
|
|
1299
|
+
}
|
|
1300
|
+
if (data.lastSyncError !== undefined) {
|
|
1301
|
+
columns["lastSyncError"] = data.lastSyncError;
|
|
1302
|
+
}
|
|
1303
|
+
if (data.coveredDeviceCount !== undefined) {
|
|
1304
|
+
columns["coveredDeviceCount"] = data.coveredDeviceCount;
|
|
1305
|
+
}
|
|
1306
|
+
if (data.templateSyncedAt !== undefined) {
|
|
1307
|
+
columns["templateSyncedAt"] = data.templateSyncedAt;
|
|
1308
|
+
}
|
|
1309
|
+
if (Object.keys(columns).length === 0) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
try {
|
|
1313
|
+
await NetworkAlertPolicyService.updateColumnsByIdWithoutHooks({
|
|
1314
|
+
id: data.policyId,
|
|
1315
|
+
data: columns,
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
catch (error) {
|
|
1319
|
+
logger.error(`NetworkAlertPolicy engine: could not stamp policy ${data.policyId.toString()}: ${error}`);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
/*
|
|
1323
|
+
* The SQL half of the scope. Sites, roles and labels are plain columns (and
|
|
1324
|
+
* one join table), so they filter in Postgres; provisionability does not,
|
|
1325
|
+
* and is filtered per page by the caller.
|
|
1326
|
+
*
|
|
1327
|
+
* Ids that are not UUIDs are dropped rather than sent to Postgres, where
|
|
1328
|
+
* `uuid = 'not-an-id'` is a statement error that would take the whole
|
|
1329
|
+
* sweep down. A scope is validated at write, so this only fires for a row
|
|
1330
|
+
* hand-edited in the database — and "match nothing" is exactly what a
|
|
1331
|
+
* meaningless id should do.
|
|
1332
|
+
*
|
|
1333
|
+
* A kind whose ids are ALL unusable is not the same as a kind with no ids:
|
|
1334
|
+
* the empty kind matches every device, so dropping the last id of a kind
|
|
1335
|
+
* would silently widen the policy to the whole estate. Such a kind is
|
|
1336
|
+
* given an id that cannot exist instead, so it keeps matching nothing.
|
|
1337
|
+
*/
|
|
1338
|
+
buildScopeDeviceQuery(data) {
|
|
1339
|
+
const scope = NetworkAlertPolicyScopeUtil.normalize(data.scope);
|
|
1340
|
+
const query = {
|
|
1341
|
+
projectId: data.projectId,
|
|
1342
|
+
isArchived: false,
|
|
1343
|
+
// A device nothing polls can carry no monitor worth billing for.
|
|
1344
|
+
probeId: QueryHelper.notNull(),
|
|
1345
|
+
};
|
|
1346
|
+
const siteIds = this.toQueryableIds(scope.siteIds);
|
|
1347
|
+
if (siteIds) {
|
|
1348
|
+
query.siteId = QueryHelper.any(siteIds);
|
|
1349
|
+
}
|
|
1350
|
+
const roleIds = this.toQueryableIds(scope.networkDeviceRoleIds);
|
|
1351
|
+
if (roleIds) {
|
|
1352
|
+
query.networkDeviceRoleId = QueryHelper.any(roleIds);
|
|
1353
|
+
}
|
|
1354
|
+
const labelIds = this.toQueryableIds(scope.labelIds);
|
|
1355
|
+
if (labelIds) {
|
|
1356
|
+
query.labels = labelIds;
|
|
1357
|
+
}
|
|
1358
|
+
return query;
|
|
1359
|
+
}
|
|
1360
|
+
/*
|
|
1361
|
+
* null when the kind is empty (it matches everything and adds no clause);
|
|
1362
|
+
* otherwise the kind's usable ids, or one impossible id when none of them
|
|
1363
|
+
* are usable.
|
|
1364
|
+
*/
|
|
1365
|
+
toQueryableIds(ids) {
|
|
1366
|
+
if (!ids || ids.length === 0) {
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
const usable = ids
|
|
1370
|
+
.filter((id) => {
|
|
1371
|
+
return ObjectID.isValidUUID(id);
|
|
1372
|
+
})
|
|
1373
|
+
.map((id) => {
|
|
1374
|
+
return new ObjectID(id);
|
|
1375
|
+
});
|
|
1376
|
+
if (usable.length === 0) {
|
|
1377
|
+
return [new ObjectID(UNMATCHABLE_ID)];
|
|
1378
|
+
}
|
|
1379
|
+
return usable;
|
|
1380
|
+
}
|
|
1381
|
+
/*
|
|
1382
|
+
* The three questions that decide whether a device may carry a policy
|
|
1383
|
+
* monitor at all. All three are about the DEVICE, never about the policy:
|
|
1384
|
+
* a policy cannot make a monitor-backed device pollable.
|
|
1385
|
+
*/
|
|
1386
|
+
isDeviceProvisionable(device) {
|
|
1387
|
+
if (device.isArchived) {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
if (NetworkDeviceMonitoringMethodUtil.isMonitorBacked(device.monitoringMethod)) {
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
return Boolean(device.probeId);
|
|
1394
|
+
}
|
|
1395
|
+
describeUnprovisionable(device) {
|
|
1396
|
+
var _a, _b, _c;
|
|
1397
|
+
if (device.isArchived) {
|
|
1398
|
+
return `device ${(_a = device.id) === null || _a === void 0 ? void 0 : _a.toString()} is archived`;
|
|
1399
|
+
}
|
|
1400
|
+
if (NetworkDeviceMonitoringMethodUtil.isMonitorBacked(device.monitoringMethod)) {
|
|
1401
|
+
return `device ${(_b = device.id) === null || _b === void 0 ? void 0 : _b.toString()} is monitor-backed`;
|
|
1402
|
+
}
|
|
1403
|
+
return `device ${(_c = device.id) === null || _c === void 0 ? void 0 : _c.toString()} has no probe`;
|
|
1404
|
+
}
|
|
1405
|
+
toScopeDevice(device) {
|
|
1406
|
+
var _a, _b;
|
|
1407
|
+
return {
|
|
1408
|
+
siteId: ((_a = device.siteId) === null || _a === void 0 ? void 0 : _a.toString()) || null,
|
|
1409
|
+
networkDeviceRoleId: ((_b = device.networkDeviceRoleId) === null || _b === void 0 ? void 0 : _b.toString()) || null,
|
|
1410
|
+
labelIds: (device.labels || []).flatMap((label) => {
|
|
1411
|
+
return label.id ? [label.id.toString()] : [];
|
|
1412
|
+
}),
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
/*
|
|
1416
|
+
* Every non-deleted policy of the project, enabled or not, read once per
|
|
1417
|
+
* run. The disabled ones are needed as much as the enabled ones: they are
|
|
1418
|
+
* what tells "a monitor whose owner paused it" (keep) apart from "a
|
|
1419
|
+
* monitor whose owner is gone" (delete).
|
|
1420
|
+
*/
|
|
1421
|
+
async getProjectPolicies(context) {
|
|
1422
|
+
if (context.policies) {
|
|
1423
|
+
return context.policies;
|
|
1424
|
+
}
|
|
1425
|
+
const policies = await NetworkAlertPolicyService.findBy({
|
|
1426
|
+
query: {
|
|
1427
|
+
projectId: context.projectId,
|
|
1428
|
+
},
|
|
1429
|
+
select: {
|
|
1430
|
+
_id: true,
|
|
1431
|
+
projectId: true,
|
|
1432
|
+
name: true,
|
|
1433
|
+
isEnabled: true,
|
|
1434
|
+
monitorTemplateId: true,
|
|
1435
|
+
scope: true,
|
|
1436
|
+
},
|
|
1437
|
+
limit: LIMIT_PER_PROJECT,
|
|
1438
|
+
skip: 0,
|
|
1439
|
+
props: {
|
|
1440
|
+
isRoot: true,
|
|
1441
|
+
},
|
|
1442
|
+
});
|
|
1443
|
+
context.policies = policies;
|
|
1444
|
+
return policies;
|
|
1445
|
+
}
|
|
1446
|
+
/*
|
|
1447
|
+
* The template, read once per run and re-checked against the project and
|
|
1448
|
+
* the type every time it is loaded. A policy's template was validated when
|
|
1449
|
+
* the policy was saved, but it can be edited, and the engine clones it as
|
|
1450
|
+
* ROOT — so nothing here trusts the policy's word for what it points at.
|
|
1451
|
+
*/
|
|
1452
|
+
async getMonitorTemplate(data) {
|
|
1453
|
+
const key = data.monitorTemplateId.toString();
|
|
1454
|
+
if (data.context.templatesById.has(key)) {
|
|
1455
|
+
return data.context.templatesById.get(key) || null;
|
|
1456
|
+
}
|
|
1457
|
+
const template = await MonitorTemplateService.findOneBy({
|
|
1458
|
+
query: {
|
|
1459
|
+
_id: data.monitorTemplateId,
|
|
1460
|
+
projectId: data.context.projectId,
|
|
1461
|
+
monitorType: MonitorType.NetworkDevice,
|
|
1462
|
+
},
|
|
1463
|
+
select: {
|
|
1464
|
+
_id: true,
|
|
1465
|
+
projectId: true,
|
|
1466
|
+
monitorType: true,
|
|
1467
|
+
monitorName: true,
|
|
1468
|
+
monitorDescription: true,
|
|
1469
|
+
monitorSteps: true,
|
|
1470
|
+
monitoringInterval: true,
|
|
1471
|
+
minimumProbeAgreement: true,
|
|
1472
|
+
customFields: true,
|
|
1473
|
+
labels: {
|
|
1474
|
+
_id: true,
|
|
1475
|
+
},
|
|
1476
|
+
},
|
|
1477
|
+
props: {
|
|
1478
|
+
isRoot: true,
|
|
1479
|
+
},
|
|
1480
|
+
});
|
|
1481
|
+
data.context.templatesById.set(key, template);
|
|
1482
|
+
return template;
|
|
1483
|
+
}
|
|
1484
|
+
/*
|
|
1485
|
+
* Prerequisite 5, and the reason it is a precheck rather than an error
|
|
1486
|
+
* handler: a project on a plan that cannot hold another monitor would
|
|
1487
|
+
* otherwise produce one failed create per device — five hundred identical
|
|
1488
|
+
* log lines and a lastSyncError that says nothing about the plan. Asked
|
|
1489
|
+
* once per run, remembered, and terminal.
|
|
1490
|
+
*
|
|
1491
|
+
* `force` re-asks after a create failed, so the free-plan ceiling reached
|
|
1492
|
+
* DURING a run (the precheck passed at 9 of 10 monitors) stops the run at
|
|
1493
|
+
* the first refusal instead of at the five hundredth.
|
|
1494
|
+
*/
|
|
1495
|
+
async stopIfPlanRefusesAnotherMonitor(context, force = false) {
|
|
1496
|
+
if (context.isStopped) {
|
|
1497
|
+
return true;
|
|
1498
|
+
}
|
|
1499
|
+
if (context.planChecked && !force) {
|
|
1500
|
+
return false;
|
|
1501
|
+
}
|
|
1502
|
+
context.planChecked = true;
|
|
1503
|
+
if (!IsBillingEnabled) {
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
let exception = null;
|
|
1507
|
+
try {
|
|
1508
|
+
const currentPlan = await ProjectService.getCurrentPlan(context.projectId);
|
|
1509
|
+
if (currentPlan.isSubscriptionUnpaid) {
|
|
1510
|
+
exception =
|
|
1511
|
+
"This project's subscription is unpaid, so no monitors can be provisioned. Update the payment method and settle the outstanding invoices, and the next sync will provision them.";
|
|
1512
|
+
}
|
|
1513
|
+
else if (currentPlan.plan === PlanType.Free) {
|
|
1514
|
+
const monitorCount = await MonitorService.countBy({
|
|
1515
|
+
query: {
|
|
1516
|
+
projectId: context.projectId,
|
|
1517
|
+
monitorType: QueryHelper.any(MonitorTypeHelper.getActiveMonitorTypes()),
|
|
1518
|
+
},
|
|
1519
|
+
props: {
|
|
1520
|
+
isRoot: true,
|
|
1521
|
+
},
|
|
1522
|
+
});
|
|
1523
|
+
if (monitorCount.toNumber() >= AllowedActiveMonitorCountInFreePlan) {
|
|
1524
|
+
exception = `This project has reached the free plan's limit of ${AllowedActiveMonitorCountInFreePlan} active monitors, so alert policies cannot provision more. Upgrade the plan and the next sync will provision them.`;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
catch (error) {
|
|
1529
|
+
/*
|
|
1530
|
+
* The billing lookup itself failed. That is NOT a plan refusal — a
|
|
1531
|
+
* Stripe blip must not stop a fleet from being provisioned — so it is
|
|
1532
|
+
* recorded as an ordinary failure and the run carries on.
|
|
1533
|
+
*/
|
|
1534
|
+
this.recordFailure(context, `Could not read the project's plan before provisioning monitors: ${error}`);
|
|
1535
|
+
return false;
|
|
1536
|
+
}
|
|
1537
|
+
if (!exception) {
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
context.planException = exception;
|
|
1541
|
+
context.isStopped = true;
|
|
1542
|
+
logger.warn(`NetworkAlertPolicy engine: stopping this run for project ${context.projectId.toString()} — ${exception}`, { projectId: context.projectId.toString() });
|
|
1543
|
+
return true;
|
|
1544
|
+
}
|
|
1545
|
+
/*
|
|
1546
|
+
* Is there budget left? Marks the run truncated when there is not, so
|
|
1547
|
+
* callers stop provisioning rather than stop dead: what has been written
|
|
1548
|
+
* stays written, the policy is stamped, and the next sweep continues.
|
|
1549
|
+
*/
|
|
1550
|
+
hasMonitorBudget(context) {
|
|
1551
|
+
if (context.monitorsWritten >= context.monitorBudget) {
|
|
1552
|
+
context.isTruncated = true;
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
return true;
|
|
1556
|
+
}
|
|
1557
|
+
// ...and spend one unit of it, immediately before a write.
|
|
1558
|
+
takeMonitorBudget(context) {
|
|
1559
|
+
if (!this.hasMonitorBudget(context)) {
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
context.monitorsWritten++;
|
|
1563
|
+
return true;
|
|
1564
|
+
}
|
|
1565
|
+
async deleteMonitors(data) {
|
|
1566
|
+
for (const monitor of data.monitors) {
|
|
1567
|
+
if (!monitor.id) {
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (!this.takeMonitorBudget(data.context)) {
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
/*
|
|
1575
|
+
* Keyed on the policy id the row was read with as well as on the
|
|
1576
|
+
* row's own id, so a monitor that changed hands between the read and
|
|
1577
|
+
* the delete is left to whoever owns it now.
|
|
1578
|
+
*/
|
|
1579
|
+
await MonitorService.deleteBy({
|
|
1580
|
+
query: {
|
|
1581
|
+
_id: monitor.id,
|
|
1582
|
+
projectId: data.projectId,
|
|
1583
|
+
networkAlertPolicyId: monitor.networkAlertPolicyId,
|
|
1584
|
+
},
|
|
1585
|
+
limit: 1,
|
|
1586
|
+
skip: 0,
|
|
1587
|
+
props: {
|
|
1588
|
+
isRoot: true,
|
|
1589
|
+
},
|
|
1590
|
+
});
|
|
1591
|
+
data.context.monitorsDeleted++;
|
|
1592
|
+
logger.debug(`NetworkAlertPolicy engine: deleted policy-owned monitor ${monitor.id.toString()} because ${data.reason}.`);
|
|
1593
|
+
}
|
|
1594
|
+
catch (error) {
|
|
1595
|
+
this.recordFailure(data.context, `Could not delete policy-owned monitor ${monitor.id.toString()} (${data.reason}): ${error}`);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
async pauseMonitors(data) {
|
|
1600
|
+
for (const monitor of data.monitors) {
|
|
1601
|
+
if (!monitor.id) {
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (!this.takeMonitorBudget(data.context)) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
try {
|
|
1608
|
+
await MonitorService.updateBy({
|
|
1609
|
+
query: {
|
|
1610
|
+
_id: monitor.id,
|
|
1611
|
+
projectId: data.projectId,
|
|
1612
|
+
networkAlertPolicyId: monitor.networkAlertPolicyId,
|
|
1613
|
+
},
|
|
1614
|
+
data: {
|
|
1615
|
+
disableActiveMonitoring: true,
|
|
1616
|
+
},
|
|
1617
|
+
limit: 1,
|
|
1618
|
+
skip: 0,
|
|
1619
|
+
props: {
|
|
1620
|
+
isRoot: true,
|
|
1621
|
+
},
|
|
1622
|
+
});
|
|
1623
|
+
data.context.monitorsPaused++;
|
|
1624
|
+
}
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
this.recordFailure(data.context, `Could not pause policy-owned monitor ${monitor.id.toString()}: ${error}`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
recordFailure(context, message) {
|
|
1631
|
+
logger.error(`NetworkAlertPolicy engine: ${message}`, {
|
|
1632
|
+
projectId: context.projectId.toString(),
|
|
1633
|
+
});
|
|
1634
|
+
if (context.failures.length >= MAX_RECORDED_FAILURES_PER_RUN) {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
context.failures.push(message);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
__decorate([
|
|
1641
|
+
CaptureSpan(),
|
|
1642
|
+
__metadata("design:type", Function),
|
|
1643
|
+
__metadata("design:paramtypes", [Object]),
|
|
1644
|
+
__metadata("design:returntype", Promise)
|
|
1645
|
+
], NetworkAlertPolicyEngineServiceClass.prototype, "reconcileDevice", null);
|
|
1646
|
+
__decorate([
|
|
1647
|
+
CaptureSpan(),
|
|
1648
|
+
__metadata("design:type", Function),
|
|
1649
|
+
__metadata("design:paramtypes", [Object]),
|
|
1650
|
+
__metadata("design:returntype", Promise)
|
|
1651
|
+
], NetworkAlertPolicyEngineServiceClass.prototype, "reconcileDevices", null);
|
|
1652
|
+
__decorate([
|
|
1653
|
+
CaptureSpan(),
|
|
1654
|
+
__metadata("design:type", Function),
|
|
1655
|
+
__metadata("design:paramtypes", [Object]),
|
|
1656
|
+
__metadata("design:returntype", Promise)
|
|
1657
|
+
], NetworkAlertPolicyEngineServiceClass.prototype, "syncPolicy", null);
|
|
1658
|
+
__decorate([
|
|
1659
|
+
CaptureSpan(),
|
|
1660
|
+
__metadata("design:type", Function),
|
|
1661
|
+
__metadata("design:paramtypes", [Object]),
|
|
1662
|
+
__metadata("design:returntype", Promise)
|
|
1663
|
+
], NetworkAlertPolicyEngineServiceClass.prototype, "deleteMonitorsOwnedByPolicy", null);
|
|
1664
|
+
__decorate([
|
|
1665
|
+
CaptureSpan(),
|
|
1666
|
+
__metadata("design:type", Function),
|
|
1667
|
+
__metadata("design:paramtypes", [Object]),
|
|
1668
|
+
__metadata("design:returntype", Promise)
|
|
1669
|
+
], NetworkAlertPolicyEngineServiceClass.prototype, "deletePolicyMonitorsForDevices", null);
|
|
1670
|
+
__decorate([
|
|
1671
|
+
CaptureSpan(),
|
|
1672
|
+
__metadata("design:type", Function),
|
|
1673
|
+
__metadata("design:paramtypes", [Object]),
|
|
1674
|
+
__metadata("design:returntype", Promise)
|
|
1675
|
+
], NetworkAlertPolicyEngineServiceClass.prototype, "setPolicyMonitorsPaused", null);
|
|
1676
|
+
__decorate([
|
|
1677
|
+
CaptureSpan(),
|
|
1678
|
+
__metadata("design:type", Function),
|
|
1679
|
+
__metadata("design:paramtypes", [Object]),
|
|
1680
|
+
__metadata("design:returntype", Promise)
|
|
1681
|
+
], NetworkAlertPolicyEngineServiceClass.prototype, "onMonitorTemplateSynced", null);
|
|
1682
|
+
export default new NetworkAlertPolicyEngineServiceClass();
|
|
1683
|
+
//# sourceMappingURL=NetworkAlertPolicyEngineService.js.map
|