@oneuptime/common 12.0.23 → 12.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/DetectionRule.ts +55 -0
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
- package/Models/DatabaseModels/Project.ts +68 -0
- package/Models/DatabaseModels/User.ts +68 -0
- package/Server/API/TelemetryAPI.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/GzipRequestBody.ts +311 -0
- package/Server/Middleware/MultipartFormData.ts +87 -3
- package/Server/Services/DetectionRuleService.ts +73 -6
- package/Server/Services/Index.ts +205 -0
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
- package/Server/Services/NetworkDeviceService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
- package/Server/Services/ProjectService.ts +29 -19
- package/Server/Services/SecurityEventService.ts +90 -13
- package/Server/Services/StatusPageResourceService.ts +223 -0
- package/Server/Services/TelemetryAttributeService.ts +17 -0
- package/Server/Services/UserService.ts +4 -6
- package/Server/Types/Markdown.ts +161 -67
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
- package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
- package/Server/Utils/Attribution.ts +4 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
- package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
- package/Server/Utils/StartServer.ts +12 -32
- package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
- package/Server/Utils/Telemetry/LlmMetricSpend.ts +130 -0
- package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
- package/Server/Views/Partials/Head.ejs +2 -1
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
- package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
- package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
- package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
- package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
- package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
- package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
- package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
- package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
- package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
- package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
- package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
- package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
- package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
- package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
- package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
- package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
- package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
- package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
- package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
- package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
- package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
- package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
- package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
- package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
- package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
- package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
- package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
- package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +368 -4
- package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +245 -0
- package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
- package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
- package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
- package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
- package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +193 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
- package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
- package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
- package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
- package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
- package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
- package/Tests/UI/Components/SideOver.test.tsx +1 -1
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
- package/Tests/UI/Components/Toggle.test.tsx +25 -0
- package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
- package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
- package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +472 -0
- package/Types/Code/YamlSyntax.ts +396 -0
- package/Types/Exception/ExceptionCode.ts +1 -0
- package/Types/Exception/PayloadTooLargeException.ts +8 -0
- package/Types/Marketing/Attribution.ts +10 -0
- package/Types/Marketing/MarketingEvent.ts +4 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
- package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
- package/Types/Telemetry/LlmMetricConventions.ts +124 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
- package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
- package/UI/Components/Detail/Detail.tsx +10 -0
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
- package/UI/Components/Forms/Fields/FormField.tsx +31 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
- package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
- package/UI/Components/Forms/Validation.ts +46 -0
- package/UI/Components/Input/Input.tsx +3 -0
- package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
- package/UI/Components/ModelTable/Column.ts +8 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
- package/UI/Components/SideOver/SideOver.tsx +5 -5
- package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
- package/UI/Components/StatusPage/ResourceList.tsx +197 -38
- package/UI/Components/Toggle/Toggle.tsx +9 -1
- package/UI/Components/Types/FieldType.ts +1 -0
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
- package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
- package/Utils/StatusPage/ResourceExplorer.ts +258 -0
- package/Utils/Telemetry/LlmMetricQuery.ts +204 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +76 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +76 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +17 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
- package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
- package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
- package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
- package/build/dist/Server/Services/DetectionRuleService.js +49 -4
- package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +204 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +25 -45
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventService.js +57 -5
- package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
- package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +2 -12
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +147 -61
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -21
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +102 -0
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
- package/build/dist/Types/Code/YamlSyntax.js +212 -0
- package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
- package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
- package/build/dist/Types/Marketing/Attribution.js +10 -0
- package/build/dist/Types/Marketing/Attribution.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmMetricConventions.js +98 -0
- package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +9 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +27 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
- package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Types/FieldType.js +1 -0
- package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js +119 -0
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +2 -1
- package/Tests/UI/Components/Toast.test.tsx +0 -197
- package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
- package/UI/Components/Toast/Toast.tsx +0 -189
- package/UI/Components/Toast/ToastInit.tsx +0 -110
- package/build/dist/UI/Components/Toast/Toast.js +0 -123
- package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
- package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
import MonitorService from "../../../Server/Services/MonitorService";
|
|
2
|
+
import NetworkDeviceLabelRuleEngineService from "../../../Server/Services/NetworkDeviceLabelRuleEngineService";
|
|
3
|
+
import NetworkDeviceOwnerRuleEngineService from "../../../Server/Services/NetworkDeviceOwnerRuleEngineService";
|
|
4
|
+
import NetworkDeviceService from "../../../Server/Services/NetworkDeviceService";
|
|
5
|
+
import NetworkSiteService from "../../../Server/Services/NetworkSiteService";
|
|
6
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
7
|
+
import NetworkDevice from "../../../Models/DatabaseModels/NetworkDevice";
|
|
8
|
+
import NetworkDeviceMonitoringMethod from "../../../Types/NetworkDevice/NetworkDeviceMonitoringMethod";
|
|
9
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
10
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
11
|
+
import { OnUpdate } from "../../../Server/Types/Database/Hooks";
|
|
12
|
+
import { afterEach, describe, expect, it } from "@jest/globals";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Contract under test — OneUptime/oneuptime#3392, "a correctly bound
|
|
16
|
+
* ping-only device is stuck on Pending".
|
|
17
|
+
*
|
|
18
|
+
* A monitor-backed NetworkDevice is never polled: no probe, no
|
|
19
|
+
* credentials, no walk. Its ONLY source of health is the Monitor bound to
|
|
20
|
+
* it, stamped onto `currentMonitorStatusId`, which the device list pill,
|
|
21
|
+
* the site rollup and the topology node all read.
|
|
22
|
+
*
|
|
23
|
+
* Before this fix the only writer of that column was
|
|
24
|
+
* `NetworkSiteService.onMonitorStatusChanged`, which runs when a monitor's
|
|
25
|
+
* status CHANGES. So the documented workflow — create a Ping monitor, set
|
|
26
|
+
* Monitoring Method to Monitor, bind the monitor — produced a device with
|
|
27
|
+
* an empty stamp, and it stayed empty until the monitor happened to go
|
|
28
|
+
* down. On a healthy device that is never, which is why the reporter's
|
|
29
|
+
* phone stayed on "Pending" while it answered every ping.
|
|
30
|
+
*
|
|
31
|
+
* What is pinned here:
|
|
32
|
+
*
|
|
33
|
+
* - binding stamps the monitor's CURRENT status immediately, on create
|
|
34
|
+
* and on update, rather than waiting for its next change,
|
|
35
|
+
* - the site chain is recomputed when the stamp moves, and only then,
|
|
36
|
+
* - unbinding (or switching the device back to SNMP) clears the stamp,
|
|
37
|
+
* so a ping monitor's verdict cannot outlive the binding,
|
|
38
|
+
* - an SNMP device's stamp — which comes from the Network Device monitor
|
|
39
|
+
* watching its walk, not from this column — is never touched,
|
|
40
|
+
* - the monitor lookup is scoped to the device's own project,
|
|
41
|
+
* - and a failure in any of it never escapes into the device update.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
45
|
+
"22222222-2222-4222-8222-222222222222",
|
|
46
|
+
);
|
|
47
|
+
const OTHER_PROJECT_ID: ObjectID = new ObjectID(
|
|
48
|
+
"99999999-9999-4999-8999-999999999999",
|
|
49
|
+
);
|
|
50
|
+
const DEVICE_ID: ObjectID = new ObjectID(
|
|
51
|
+
"cccccccc-cccc-4ccc-8ccc-cccccccccccc",
|
|
52
|
+
);
|
|
53
|
+
const SECOND_DEVICE_ID: ObjectID = new ObjectID(
|
|
54
|
+
"dddddddd-dddd-4ddd-8ddd-dddddddddddd",
|
|
55
|
+
);
|
|
56
|
+
const SITE_ID: ObjectID = new ObjectID("11111111-1111-4111-8111-111111111111");
|
|
57
|
+
const MONITOR_ID: ObjectID = new ObjectID(
|
|
58
|
+
"44444444-4444-4444-8444-444444444444",
|
|
59
|
+
);
|
|
60
|
+
const OPERATIONAL_STATUS_ID: ObjectID = new ObjectID(
|
|
61
|
+
"55555555-5555-4555-8555-555555555555",
|
|
62
|
+
);
|
|
63
|
+
const OFFLINE_STATUS_ID: ObjectID = new ObjectID(
|
|
64
|
+
"66666666-6666-4666-8666-666666666666",
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
function fakeDevice(overrides: Record<string, unknown>): NetworkDevice {
|
|
68
|
+
return {
|
|
69
|
+
id: DEVICE_ID,
|
|
70
|
+
_id: DEVICE_ID.toString(),
|
|
71
|
+
projectId: PROJECT_ID,
|
|
72
|
+
...overrides,
|
|
73
|
+
} as unknown as NetworkDevice;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fakeMonitor(currentMonitorStatusId: ObjectID | undefined): Monitor {
|
|
77
|
+
return {
|
|
78
|
+
id: MONITOR_ID,
|
|
79
|
+
_id: MONITOR_ID.toString(),
|
|
80
|
+
currentMonitorStatusId: currentMonitorStatusId,
|
|
81
|
+
} as unknown as Monitor;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* The whole write path this method owns, mocked at the three seams it
|
|
86
|
+
* touches: read the device, read its monitor, write the stamp (plus the
|
|
87
|
+
* rollup it triggers).
|
|
88
|
+
*/
|
|
89
|
+
function mockDeviceAndMonitor(data: {
|
|
90
|
+
device: NetworkDevice | null;
|
|
91
|
+
monitor?: Monitor | null;
|
|
92
|
+
}): {
|
|
93
|
+
update: jest.SpyInstance;
|
|
94
|
+
rollup: jest.SpyInstance;
|
|
95
|
+
findMonitor: jest.SpyInstance;
|
|
96
|
+
} {
|
|
97
|
+
jest
|
|
98
|
+
.spyOn(NetworkDeviceService, "findOneById")
|
|
99
|
+
.mockResolvedValue(data.device);
|
|
100
|
+
|
|
101
|
+
const findMonitor: jest.SpyInstance = jest
|
|
102
|
+
.spyOn(MonitorService, "findOneBy")
|
|
103
|
+
.mockResolvedValue(data.monitor === undefined ? null : data.monitor);
|
|
104
|
+
|
|
105
|
+
const update: jest.SpyInstance = jest
|
|
106
|
+
.spyOn(NetworkDeviceService, "updateColumnsByIdWithoutHooks")
|
|
107
|
+
.mockResolvedValue(undefined as never);
|
|
108
|
+
|
|
109
|
+
const rollup: jest.SpyInstance = jest
|
|
110
|
+
.spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
|
|
111
|
+
.mockResolvedValue(undefined as never);
|
|
112
|
+
|
|
113
|
+
return { update, rollup, findMonitor };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
describe("NetworkDeviceService.refreshStampedMonitorStatus", () => {
|
|
117
|
+
afterEach(() => {
|
|
118
|
+
jest.restoreAllMocks();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/*
|
|
122
|
+
* The fix, stated once: the device in the issue, bound to a Ping monitor
|
|
123
|
+
* that is Operational and has been for weeks.
|
|
124
|
+
*/
|
|
125
|
+
it("stamps the bound monitor's current status onto a monitor-backed device", async () => {
|
|
126
|
+
const { update } = mockDeviceAndMonitor({
|
|
127
|
+
device: fakeDevice({
|
|
128
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
129
|
+
monitorId: MONITOR_ID,
|
|
130
|
+
currentMonitorStatusId: undefined,
|
|
131
|
+
}),
|
|
132
|
+
monitor: fakeMonitor(OPERATIONAL_STATUS_ID),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
136
|
+
deviceId: DEVICE_ID,
|
|
137
|
+
clearWhenNotMonitorBacked: false,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(update).toHaveBeenCalledTimes(1);
|
|
141
|
+
const args: any = update.mock.calls[0]![0];
|
|
142
|
+
expect(args.id.toString()).toBe(DEVICE_ID.toString());
|
|
143
|
+
expect(args.data.currentMonitorStatusId.toString()).toBe(
|
|
144
|
+
OPERATIONAL_STATUS_ID.toString(),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("stamps an offline monitor just the same, so the device reads Down", async () => {
|
|
149
|
+
const { update } = mockDeviceAndMonitor({
|
|
150
|
+
device: fakeDevice({
|
|
151
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
152
|
+
monitorId: MONITOR_ID,
|
|
153
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
154
|
+
}),
|
|
155
|
+
monitor: fakeMonitor(OFFLINE_STATUS_ID),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
159
|
+
deviceId: DEVICE_ID,
|
|
160
|
+
clearWhenNotMonitorBacked: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(
|
|
164
|
+
update.mock.calls[0]![0].data.currentMonitorStatusId.toString(),
|
|
165
|
+
).toBe(OFFLINE_STATUS_ID.toString());
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
* The site card above the device shows a worst-of rollup over its
|
|
170
|
+
* devices' stamps, so a stamp that moves without recomputing it leaves
|
|
171
|
+
* the site and the device contradicting each other on screen.
|
|
172
|
+
*/
|
|
173
|
+
it("refreshes the device's site chain when the stamp moves", async () => {
|
|
174
|
+
const { rollup } = mockDeviceAndMonitor({
|
|
175
|
+
device: fakeDevice({
|
|
176
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
177
|
+
monitorId: MONITOR_ID,
|
|
178
|
+
siteId: SITE_ID,
|
|
179
|
+
}),
|
|
180
|
+
monitor: fakeMonitor(OPERATIONAL_STATUS_ID),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
184
|
+
deviceId: DEVICE_ID,
|
|
185
|
+
clearWhenNotMonitorBacked: false,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(rollup).toHaveBeenCalledTimes(1);
|
|
189
|
+
expect(rollup.mock.calls[0]![0].toString()).toBe(SITE_ID.toString());
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("skips the rollup for a device that belongs to no site", async () => {
|
|
193
|
+
const { update, rollup } = mockDeviceAndMonitor({
|
|
194
|
+
device: fakeDevice({
|
|
195
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
196
|
+
monitorId: MONITOR_ID,
|
|
197
|
+
}),
|
|
198
|
+
monitor: fakeMonitor(OPERATIONAL_STATUS_ID),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
202
|
+
deviceId: DEVICE_ID,
|
|
203
|
+
clearWhenNotMonitorBacked: false,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(update).toHaveBeenCalledTimes(1);
|
|
207
|
+
expect(rollup).not.toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
/*
|
|
211
|
+
* Idempotence is what makes this safe to call from every save and from
|
|
212
|
+
* the upgrade backfill: re-deriving a stamp that already agrees must
|
|
213
|
+
* cost nothing and, more importantly, must not churn the site rollups of
|
|
214
|
+
* a whole estate on every unrelated device edit.
|
|
215
|
+
*/
|
|
216
|
+
it("writes nothing when the stamp already agrees", async () => {
|
|
217
|
+
const { update, rollup } = mockDeviceAndMonitor({
|
|
218
|
+
device: fakeDevice({
|
|
219
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
220
|
+
monitorId: MONITOR_ID,
|
|
221
|
+
siteId: SITE_ID,
|
|
222
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
223
|
+
}),
|
|
224
|
+
monitor: fakeMonitor(OPERATIONAL_STATUS_ID),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
228
|
+
deviceId: DEVICE_ID,
|
|
229
|
+
clearWhenNotMonitorBacked: false,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(update).not.toHaveBeenCalled();
|
|
233
|
+
expect(rollup).not.toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/*
|
|
237
|
+
* Discovery import creates ping-only hosts with no monitor on purpose —
|
|
238
|
+
* a subnet sweep has nothing to bind them to yet. "Pending" is the true
|
|
239
|
+
* answer for those, so nothing is invented for them.
|
|
240
|
+
*/
|
|
241
|
+
it("leaves an already-empty stamp alone when no monitor is bound", async () => {
|
|
242
|
+
const { update, findMonitor } = mockDeviceAndMonitor({
|
|
243
|
+
device: fakeDevice({
|
|
244
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
245
|
+
monitorId: undefined,
|
|
246
|
+
}),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
250
|
+
deviceId: DEVICE_ID,
|
|
251
|
+
clearWhenNotMonitorBacked: false,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(findMonitor).not.toHaveBeenCalled();
|
|
255
|
+
expect(update).not.toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
/*
|
|
259
|
+
* ...and the same device once its monitor is unbound. The old verdict
|
|
260
|
+
* must not survive the binding that produced it.
|
|
261
|
+
*/
|
|
262
|
+
it("clears the stamp when the monitor is unbound", async () => {
|
|
263
|
+
const { update } = mockDeviceAndMonitor({
|
|
264
|
+
device: fakeDevice({
|
|
265
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
266
|
+
monitorId: undefined,
|
|
267
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
268
|
+
}),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
272
|
+
deviceId: DEVICE_ID,
|
|
273
|
+
clearWhenNotMonitorBacked: false,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(update).toHaveBeenCalledTimes(1);
|
|
277
|
+
expect(update.mock.calls[0]![0].data.currentMonitorStatusId).toBeNull();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/*
|
|
281
|
+
* A bound monitor that has somehow never had a status is the same case:
|
|
282
|
+
* there is nothing to adopt, and a stale stamp is worse than none.
|
|
283
|
+
*/
|
|
284
|
+
it("clears the stamp when the bound monitor has no status of its own", async () => {
|
|
285
|
+
const { update } = mockDeviceAndMonitor({
|
|
286
|
+
device: fakeDevice({
|
|
287
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
288
|
+
monitorId: MONITOR_ID,
|
|
289
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
290
|
+
}),
|
|
291
|
+
monitor: fakeMonitor(undefined),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
295
|
+
deviceId: DEVICE_ID,
|
|
296
|
+
clearWhenNotMonitorBacked: false,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(update.mock.calls[0]![0].data.currentMonitorStatusId).toBeNull();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("clears the stamp when the bound monitor is gone", async () => {
|
|
303
|
+
const { update } = mockDeviceAndMonitor({
|
|
304
|
+
device: fakeDevice({
|
|
305
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
306
|
+
monitorId: MONITOR_ID,
|
|
307
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
308
|
+
}),
|
|
309
|
+
monitor: null,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
313
|
+
deviceId: DEVICE_ID,
|
|
314
|
+
clearWhenNotMonitorBacked: false,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
expect(update.mock.calls[0]![0].data.currentMonitorStatusId).toBeNull();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
/*
|
|
321
|
+
* The monitorId FK only requires the Monitor row to exist, not that it
|
|
322
|
+
* belongs to the device's project — the same hole the create/update
|
|
323
|
+
* guards close. Reading it back through a project-scoped query is what
|
|
324
|
+
* stops another tenant's status being stamped here.
|
|
325
|
+
*/
|
|
326
|
+
it("looks the monitor up inside the device's own project", async () => {
|
|
327
|
+
const { findMonitor } = mockDeviceAndMonitor({
|
|
328
|
+
device: fakeDevice({
|
|
329
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
330
|
+
monitorId: MONITOR_ID,
|
|
331
|
+
}),
|
|
332
|
+
monitor: fakeMonitor(OPERATIONAL_STATUS_ID),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
336
|
+
deviceId: DEVICE_ID,
|
|
337
|
+
clearWhenNotMonitorBacked: false,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const query: any = findMonitor.mock.calls[0]![0].query;
|
|
341
|
+
expect(query._id.toString()).toBe(MONITOR_ID.toString());
|
|
342
|
+
expect(query.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
343
|
+
expect(query.projectId.toString()).not.toBe(OTHER_PROJECT_ID.toString());
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
/*
|
|
347
|
+
* An SNMP device's stamp comes from the Network Device monitor watching
|
|
348
|
+
* its walk, and that binding lives in the monitor's step data rather
|
|
349
|
+
* than in this column. Re-deriving it from `monitorId` would wipe a
|
|
350
|
+
* legitimate status the walk pipeline put there.
|
|
351
|
+
*/
|
|
352
|
+
it("does not touch an SNMP device by default", async () => {
|
|
353
|
+
const { update, findMonitor } = mockDeviceAndMonitor({
|
|
354
|
+
device: fakeDevice({
|
|
355
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Snmp,
|
|
356
|
+
monitorId: MONITOR_ID,
|
|
357
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
358
|
+
}),
|
|
359
|
+
monitor: fakeMonitor(OFFLINE_STATUS_ID),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
363
|
+
deviceId: DEVICE_ID,
|
|
364
|
+
clearWhenNotMonitorBacked: false,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(findMonitor).not.toHaveBeenCalled();
|
|
368
|
+
expect(update).not.toHaveBeenCalled();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
/*
|
|
372
|
+
* A device created before the column existed holds NULL, which reads as
|
|
373
|
+
* SNMP — and must behave as one.
|
|
374
|
+
*/
|
|
375
|
+
it("treats a device with no monitoring method as SNMP", async () => {
|
|
376
|
+
const { update } = mockDeviceAndMonitor({
|
|
377
|
+
device: fakeDevice({
|
|
378
|
+
monitoringMethod: undefined,
|
|
379
|
+
monitorId: MONITOR_ID,
|
|
380
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
381
|
+
}),
|
|
382
|
+
monitor: fakeMonitor(OFFLINE_STATUS_ID),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
386
|
+
deviceId: DEVICE_ID,
|
|
387
|
+
clearWhenNotMonitorBacked: false,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
expect(update).not.toHaveBeenCalled();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
/*
|
|
394
|
+
* The one write that DOES clear an SNMP device: the one that just moved
|
|
395
|
+
* it off monitor-backed. The ping monitor's verdict outliving the
|
|
396
|
+
* binding matters because a stamped status beats reachability in
|
|
397
|
+
* DeviceHealthStateUtil — it would keep deciding the site rollup of a
|
|
398
|
+
* device that is now being walked.
|
|
399
|
+
*/
|
|
400
|
+
it("clears the stamp when a device is switched back to SNMP", async () => {
|
|
401
|
+
const { update, rollup } = mockDeviceAndMonitor({
|
|
402
|
+
device: fakeDevice({
|
|
403
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Snmp,
|
|
404
|
+
monitorId: MONITOR_ID,
|
|
405
|
+
siteId: SITE_ID,
|
|
406
|
+
currentMonitorStatusId: OPERATIONAL_STATUS_ID,
|
|
407
|
+
}),
|
|
408
|
+
monitor: fakeMonitor(OPERATIONAL_STATUS_ID),
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
412
|
+
deviceId: DEVICE_ID,
|
|
413
|
+
clearWhenNotMonitorBacked: true,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(update).toHaveBeenCalledTimes(1);
|
|
417
|
+
expect(update.mock.calls[0]![0].data.currentMonitorStatusId).toBeNull();
|
|
418
|
+
expect(rollup).toHaveBeenCalledTimes(1);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("does nothing for a device the update never actually matched", async () => {
|
|
422
|
+
const { update } = mockDeviceAndMonitor({ device: null });
|
|
423
|
+
|
|
424
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
425
|
+
deviceId: DEVICE_ID,
|
|
426
|
+
clearWhenNotMonitorBacked: true,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
expect(update).not.toHaveBeenCalled();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("does nothing for a device row carrying no project", async () => {
|
|
433
|
+
const { update } = mockDeviceAndMonitor({
|
|
434
|
+
device: {
|
|
435
|
+
id: DEVICE_ID,
|
|
436
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
437
|
+
monitorId: MONITOR_ID,
|
|
438
|
+
} as unknown as NetworkDevice,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
442
|
+
deviceId: DEVICE_ID,
|
|
443
|
+
clearWhenNotMonitorBacked: true,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
expect(update).not.toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
/*
|
|
450
|
+
* The columns the method reads. A select that drops one of them makes
|
|
451
|
+
* this silently wrong rather than loudly broken — a missing
|
|
452
|
+
* `monitoringMethod` reads as SNMP and skips every monitor-backed
|
|
453
|
+
* device, which is the bug all over again.
|
|
454
|
+
*/
|
|
455
|
+
it("selects every column its decision depends on", async () => {
|
|
456
|
+
const findDevice: jest.SpyInstance = jest
|
|
457
|
+
.spyOn(NetworkDeviceService, "findOneById")
|
|
458
|
+
.mockResolvedValue(null);
|
|
459
|
+
|
|
460
|
+
await NetworkDeviceService.refreshStampedMonitorStatus({
|
|
461
|
+
deviceId: DEVICE_ID,
|
|
462
|
+
clearWhenNotMonitorBacked: false,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const select: any = findDevice.mock.calls[0]![0].select;
|
|
466
|
+
expect(select.monitoringMethod).toBe(true);
|
|
467
|
+
expect(select.monitorId).toBe(true);
|
|
468
|
+
expect(select.currentMonitorStatusId).toBe(true);
|
|
469
|
+
expect(select.projectId).toBe(true);
|
|
470
|
+
expect(select.siteId).toBe(true);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
/*
|
|
475
|
+
* The hook that turns "the operator pressed Save" into a re-stamp. The
|
|
476
|
+
* dashboard's Device Details card posts `monitoringMethod` and the
|
|
477
|
+
* `monitor` RELATION together; server-side callers write the `monitorId`
|
|
478
|
+
* COLUMN. Both spellings have to fire, or the fix reaches only half the
|
|
479
|
+
* writes (OneUptime/oneuptime#2940 is the same lesson for sites).
|
|
480
|
+
*/
|
|
481
|
+
describe("NetworkDeviceService.onUpdateSuccess re-stamps a changed binding", () => {
|
|
482
|
+
afterEach(() => {
|
|
483
|
+
jest.restoreAllMocks();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
function runOnUpdateSuccess(
|
|
487
|
+
data: Record<string, unknown>,
|
|
488
|
+
updatedItemIds: Array<ObjectID> = [DEVICE_ID],
|
|
489
|
+
): Promise<OnUpdate<NetworkDevice>> {
|
|
490
|
+
const onUpdate: OnUpdate<NetworkDevice> = {
|
|
491
|
+
updateBy: {
|
|
492
|
+
query: {},
|
|
493
|
+
data: data,
|
|
494
|
+
props: { isRoot: true },
|
|
495
|
+
} as unknown as UpdateBy<NetworkDevice>,
|
|
496
|
+
carryForward: null,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
return (NetworkDeviceService as any).onUpdateSuccess(
|
|
500
|
+
onUpdate,
|
|
501
|
+
updatedItemIds,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
it("re-stamps when the monitoring method is written", async () => {
|
|
506
|
+
const refresh: jest.SpyInstance = jest
|
|
507
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
508
|
+
.mockResolvedValue(undefined as never);
|
|
509
|
+
|
|
510
|
+
await runOnUpdateSuccess({
|
|
511
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
515
|
+
expect(refresh.mock.calls[0]![0].deviceId.toString()).toBe(
|
|
516
|
+
DEVICE_ID.toString(),
|
|
517
|
+
);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it("re-stamps when the monitor RELATION is written, as the dashboard posts it", async () => {
|
|
521
|
+
const refresh: jest.SpyInstance = jest
|
|
522
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
523
|
+
.mockResolvedValue(undefined as never);
|
|
524
|
+
|
|
525
|
+
await runOnUpdateSuccess({ monitor: { _id: MONITOR_ID.toString() } });
|
|
526
|
+
|
|
527
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("re-stamps when the monitorId COLUMN is written", async () => {
|
|
531
|
+
const refresh: jest.SpyInstance = jest
|
|
532
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
533
|
+
.mockResolvedValue(undefined as never);
|
|
534
|
+
|
|
535
|
+
await runOnUpdateSuccess({ monitorId: MONITOR_ID });
|
|
536
|
+
|
|
537
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
/*
|
|
541
|
+
* The exact save from the issue: Monitoring Method and Monitor set in one
|
|
542
|
+
* submit, which is how the Device Details card posts them.
|
|
543
|
+
*/
|
|
544
|
+
it("re-stamps the reported save, which writes both at once", async () => {
|
|
545
|
+
const refresh: jest.SpyInstance = jest
|
|
546
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
547
|
+
.mockResolvedValue(undefined as never);
|
|
548
|
+
|
|
549
|
+
await runOnUpdateSuccess({
|
|
550
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
551
|
+
monitor: { _id: MONITOR_ID.toString() },
|
|
552
|
+
deviceRole: "IP phone",
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
556
|
+
expect(refresh.mock.calls[0]![0].clearWhenNotMonitorBacked).toBe(true);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
/*
|
|
560
|
+
* A method write is the only thing allowed to clear an SNMP device's
|
|
561
|
+
* stamp, because it is the only write that can have just moved the
|
|
562
|
+
* device off monitor-backed. Binding alone must not.
|
|
563
|
+
*/
|
|
564
|
+
it("only asks to clear on a method write", async () => {
|
|
565
|
+
const refresh: jest.SpyInstance = jest
|
|
566
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
567
|
+
.mockResolvedValue(undefined as never);
|
|
568
|
+
|
|
569
|
+
await runOnUpdateSuccess({ monitorId: MONITOR_ID });
|
|
570
|
+
|
|
571
|
+
expect(refresh.mock.calls[0]![0].clearWhenNotMonitorBacked).toBe(false);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("re-stamps every device a bulk update touched", async () => {
|
|
575
|
+
const refresh: jest.SpyInstance = jest
|
|
576
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
577
|
+
.mockResolvedValue(undefined as never);
|
|
578
|
+
|
|
579
|
+
await runOnUpdateSuccess(
|
|
580
|
+
{ monitoringMethod: NetworkDeviceMonitoringMethod.Monitor },
|
|
581
|
+
[DEVICE_ID, SECOND_DEVICE_ID],
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
expect(refresh).toHaveBeenCalledTimes(2);
|
|
585
|
+
expect(
|
|
586
|
+
refresh.mock.calls.map((call: Array<any>) => {
|
|
587
|
+
return call[0].deviceId.toString();
|
|
588
|
+
}),
|
|
589
|
+
).toEqual([DEVICE_ID.toString(), SECOND_DEVICE_ID.toString()]);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
/*
|
|
593
|
+
* onUpdateSuccess runs even when the permission-scoped UPDATE matched
|
|
594
|
+
* nothing, and a re-stamp of a device the caller could not touch would
|
|
595
|
+
* be a write they were not allowed to make.
|
|
596
|
+
*/
|
|
597
|
+
it("re-stamps nothing when the update matched no rows", async () => {
|
|
598
|
+
const refresh: jest.SpyInstance = jest
|
|
599
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
600
|
+
.mockResolvedValue(undefined as never);
|
|
601
|
+
|
|
602
|
+
await runOnUpdateSuccess(
|
|
603
|
+
{ monitoringMethod: NetworkDeviceMonitoringMethod.Monitor },
|
|
604
|
+
[],
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
/*
|
|
611
|
+
* The SNMP walk rewrites sysName, lastSeenAt and the interface counts on
|
|
612
|
+
* every single poll of every single device. Re-deriving a stamp on those
|
|
613
|
+
* would put a device read and a monitor read into the hot ingest path
|
|
614
|
+
* for an entire fleet.
|
|
615
|
+
*/
|
|
616
|
+
it("does not re-stamp on a write that leaves the binding alone", async () => {
|
|
617
|
+
const refresh: jest.SpyInstance = jest
|
|
618
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
619
|
+
.mockResolvedValue(undefined as never);
|
|
620
|
+
|
|
621
|
+
await runOnUpdateSuccess({ sysName: "un0661voipcp01", interfacesDown: 2 });
|
|
622
|
+
|
|
623
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
/*
|
|
627
|
+
* Status maintenance is best-effort, exactly like the site maintenance
|
|
628
|
+
* beside it: the device update has already committed, and failing the
|
|
629
|
+
* caller's request over a stamp would turn a cosmetic problem into a
|
|
630
|
+
* failed save.
|
|
631
|
+
*/
|
|
632
|
+
it("never lets a re-stamp failure escape into the update", async () => {
|
|
633
|
+
jest
|
|
634
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
635
|
+
.mockRejectedValue(new Error("monitor lookup exploded") as never);
|
|
636
|
+
|
|
637
|
+
await expect(
|
|
638
|
+
runOnUpdateSuccess({
|
|
639
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
640
|
+
}),
|
|
641
|
+
).resolves.toBeDefined();
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
/*
|
|
645
|
+
* ...and the two halves are independent. A rollup failure in the site
|
|
646
|
+
* maintenance below must not cost the device its stamp, which is why
|
|
647
|
+
* they sit in separate try blocks.
|
|
648
|
+
*/
|
|
649
|
+
it("still re-stamps when the site maintenance beside it fails", async () => {
|
|
650
|
+
const refresh: jest.SpyInstance = jest
|
|
651
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
652
|
+
.mockResolvedValue(undefined as never);
|
|
653
|
+
|
|
654
|
+
jest
|
|
655
|
+
.spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
|
|
656
|
+
.mockRejectedValue(new Error("rollup exploded") as never);
|
|
657
|
+
|
|
658
|
+
await expect(
|
|
659
|
+
runOnUpdateSuccess({
|
|
660
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
661
|
+
siteId: SITE_ID,
|
|
662
|
+
}),
|
|
663
|
+
).resolves.toBeDefined();
|
|
664
|
+
|
|
665
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
/*
|
|
670
|
+
* The create side. A device can arrive monitor-backed and already bound —
|
|
671
|
+
* the "Add Device" form asks for the monitor in the same submit, and the
|
|
672
|
+
* discovery review screen imports ping-only hosts in bulk — so waiting for
|
|
673
|
+
* a later edit to stamp it would leave a freshly created device on
|
|
674
|
+
* "Pending" for exactly the same reason the reported one was.
|
|
675
|
+
*
|
|
676
|
+
* The chain is detached from the create response on purpose (a rule
|
|
677
|
+
* failure must never fail the device write), so these drain the microtask
|
|
678
|
+
* queue the way the rule-chain suite does.
|
|
679
|
+
*/
|
|
680
|
+
describe("NetworkDeviceService.onCreateSuccess stamps a device created bound", () => {
|
|
681
|
+
afterEach(() => {
|
|
682
|
+
jest.restoreAllMocks();
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
function mockRuleChain(): void {
|
|
686
|
+
jest
|
|
687
|
+
.spyOn(NetworkDeviceLabelRuleEngineService, "applyRulesToNetworkDevice")
|
|
688
|
+
.mockResolvedValue(undefined as never);
|
|
689
|
+
jest
|
|
690
|
+
.spyOn(NetworkDeviceOwnerRuleEngineService, "applyRulesToNetworkDevice")
|
|
691
|
+
.mockResolvedValue(undefined as never);
|
|
692
|
+
jest
|
|
693
|
+
.spyOn(NetworkDeviceService, "applySiteAssignmentRulesToDevice")
|
|
694
|
+
.mockResolvedValue(undefined as never);
|
|
695
|
+
jest
|
|
696
|
+
.spyOn(NetworkSiteService, "recomputeRollupForSiteAndAncestors")
|
|
697
|
+
.mockResolvedValue(undefined as never);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async function runOnCreateSuccess(createdItem: NetworkDevice): Promise<void> {
|
|
701
|
+
await (NetworkDeviceService as any).onCreateSuccess(
|
|
702
|
+
{ createBy: { data: {}, props: {} }, carryForward: null },
|
|
703
|
+
createdItem,
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
await new Promise((resolve: (value: unknown) => void) => {
|
|
707
|
+
setTimeout(resolve, 0);
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
it("stamps a device created monitor-backed", async () => {
|
|
712
|
+
mockRuleChain();
|
|
713
|
+
const refresh: jest.SpyInstance = jest
|
|
714
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
715
|
+
.mockResolvedValue(undefined as never);
|
|
716
|
+
|
|
717
|
+
await runOnCreateSuccess(
|
|
718
|
+
fakeDevice({
|
|
719
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
720
|
+
monitorId: MONITOR_ID,
|
|
721
|
+
}),
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
725
|
+
expect(refresh.mock.calls[0]![0].deviceId.toString()).toBe(
|
|
726
|
+
DEVICE_ID.toString(),
|
|
727
|
+
);
|
|
728
|
+
// Nothing to clear on a row that has only just been written.
|
|
729
|
+
expect(refresh.mock.calls[0]![0].clearWhenNotMonitorBacked).toBe(false);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
/*
|
|
733
|
+
* Discovery import creates ping-only hosts with no monitor bound. Those
|
|
734
|
+
* still go through, and the method itself decides there is nothing to
|
|
735
|
+
* stamp — which keeps the "did we call it" decision in one place.
|
|
736
|
+
*/
|
|
737
|
+
it("stamps a monitor-backed device created with nothing bound", async () => {
|
|
738
|
+
mockRuleChain();
|
|
739
|
+
const refresh: jest.SpyInstance = jest
|
|
740
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
741
|
+
.mockResolvedValue(undefined as never);
|
|
742
|
+
|
|
743
|
+
await runOnCreateSuccess(
|
|
744
|
+
fakeDevice({ monitoringMethod: NetworkDeviceMonitoringMethod.Monitor }),
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
/*
|
|
751
|
+
* Every SNMP device ever created goes through this hook, including a
|
|
752
|
+
* whole subnet at a time on discovery import. None of them has anything
|
|
753
|
+
* to stamp, so none of them pays for a device read and a monitor read.
|
|
754
|
+
*/
|
|
755
|
+
it("does not touch an SNMP device", async () => {
|
|
756
|
+
mockRuleChain();
|
|
757
|
+
const refresh: jest.SpyInstance = jest
|
|
758
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
759
|
+
.mockResolvedValue(undefined as never);
|
|
760
|
+
|
|
761
|
+
await runOnCreateSuccess(
|
|
762
|
+
fakeDevice({ monitoringMethod: NetworkDeviceMonitoringMethod.Snmp }),
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it("does not touch a device created with no monitoring method", async () => {
|
|
769
|
+
mockRuleChain();
|
|
770
|
+
const refresh: jest.SpyInstance = jest
|
|
771
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
772
|
+
.mockResolvedValue(undefined as never);
|
|
773
|
+
|
|
774
|
+
await runOnCreateSuccess(fakeDevice({}));
|
|
775
|
+
|
|
776
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
/*
|
|
780
|
+
* Detached, and it has to stay that way: the device row is already
|
|
781
|
+
* committed and its id already returned to the caller by the time this
|
|
782
|
+
* runs, so a monitor lookup that throws must not surface as a failed
|
|
783
|
+
* create.
|
|
784
|
+
*/
|
|
785
|
+
it("never lets a stamp failure escape the create", async () => {
|
|
786
|
+
mockRuleChain();
|
|
787
|
+
jest
|
|
788
|
+
.spyOn(NetworkDeviceService, "refreshStampedMonitorStatus")
|
|
789
|
+
.mockRejectedValue(new Error("monitor lookup exploded") as never);
|
|
790
|
+
|
|
791
|
+
await expect(
|
|
792
|
+
runOnCreateSuccess(
|
|
793
|
+
fakeDevice({
|
|
794
|
+
monitoringMethod: NetworkDeviceMonitoringMethod.Monitor,
|
|
795
|
+
monitorId: MONITOR_ID,
|
|
796
|
+
}),
|
|
797
|
+
),
|
|
798
|
+
).resolves.toBeUndefined();
|
|
799
|
+
});
|
|
800
|
+
});
|