@oneuptime/common 12.0.25 → 12.0.26
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/Index.ts +2 -0
- package/Models/AnalyticsModels/ThreatIntelIndicator.ts +413 -0
- package/Models/DatabaseModels/AlertSeverity.ts +4 -1
- package/Models/DatabaseModels/CephClusterFeed.ts +625 -0
- package/Models/DatabaseModels/CloudResourceFeed.ts +625 -0
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +13 -0
- package/Models/DatabaseModels/DetectionRule.ts +9 -3
- package/Models/DatabaseModels/DockerHostFeed.ts +625 -0
- package/Models/DatabaseModels/DockerSwarmClusterFeed.ts +628 -0
- package/Models/DatabaseModels/GoogleSecOpsConnection.ts +9 -3
- package/Models/DatabaseModels/HostFeed.ts +625 -0
- package/Models/DatabaseModels/IncidentSeverity.ts +4 -1
- package/Models/DatabaseModels/Index.ts +22 -0
- package/Models/DatabaseModels/KubernetesClusterFeed.ts +628 -0
- package/Models/DatabaseModels/Monitor.ts +87 -0
- package/Models/DatabaseModels/NetworkDevice.ts +96 -5
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +92 -6
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +301 -31
- package/Models/DatabaseModels/NetworkSite.ts +120 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
- package/Models/DatabaseModels/PodmanHostFeed.ts +625 -0
- package/Models/DatabaseModels/ProxmoxClusterFeed.ts +626 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +67 -0
- package/Models/DatabaseModels/ServiceFeed.ts +625 -0
- package/Models/DatabaseModels/TelemetrySourceMap.ts +474 -0
- package/Models/DatabaseModels/ThreatIntelFeed.ts +758 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +1 -0
- package/Models/DatabaseModels/UserTotpAuth.ts +1 -0
- package/Models/DatabaseModels/UserWebAuthn.ts +1 -0
- package/Server/API/AlertAPI.ts +23 -1
- package/Server/API/BaseAPI.ts +14 -3
- package/Server/API/IncidentAPI.ts +41 -1
- package/Server/API/IncidentEpisodeAPI.ts +23 -1
- package/Server/API/MicrosoftTeamsAPI.ts +31 -2
- package/Server/API/ScheduledMaintenanceAPI.ts +23 -1
- package/Server/API/SlackAPI.ts +48 -0
- package/Server/API/TelemetryAPI.ts +167 -42
- package/Server/API/UserOnCallLogTimelineAPI.ts +75 -61
- package/Server/API/UserTotpAuthAPI.ts +67 -1
- package/Server/API/UserWebAuthnAPI.ts +44 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787923136162-MigrationName.ts +57 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789600000000-AddAutoProvisionedNetworkDeviceMonitors.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789700000000-AddNetworkScaleIndexes.ts +129 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789800000000-WidenSecurityEventLastErrorColumns.ts +54 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789900000000-AddNetworkSiteRollupPolicyAndMaintenance.ts +64 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790000000000-AddTelemetrySourceMap.ts +64 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790000000001-AddSnmpConfigsToNetworkDeviceDiscoveryScan.ts +72 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790003445000-AddSnmpEnabledToNetworkDeviceDiscoveryScan.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790100000000-AddResourceActivityFeeds.ts +489 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +18 -0
- package/Server/Middleware/IdentityRateLimit.ts +53 -3
- package/Server/Services/AIService.ts +181 -41
- package/Server/Services/CephClusterFeedService.ts +99 -0
- package/Server/Services/CephClusterLabelRuleEngineService.ts +29 -0
- package/Server/Services/CephClusterOwnerRuleEngineService.ts +23 -0
- package/Server/Services/CephClusterOwnerTeamService.ts +129 -0
- package/Server/Services/CephClusterOwnerUserService.ts +123 -0
- package/Server/Services/CephClusterService.ts +223 -2
- package/Server/Services/CloudResourceFeedService.ts +99 -0
- package/Server/Services/CloudResourceLabelRuleEngineService.ts +30 -0
- package/Server/Services/CloudResourceOwnerRuleEngineService.ts +33 -0
- package/Server/Services/CloudResourceOwnerTeamService.ts +129 -0
- package/Server/Services/CloudResourceOwnerUserService.ts +123 -0
- package/Server/Services/CloudResourceService.ts +227 -2
- package/Server/Services/DatabaseService.ts +265 -0
- package/Server/Services/DockerHostFeedService.ts +99 -0
- package/Server/Services/DockerHostLabelRuleEngineService.ts +29 -0
- package/Server/Services/DockerHostOwnerRuleEngineService.ts +23 -0
- package/Server/Services/DockerHostOwnerTeamService.ts +129 -0
- package/Server/Services/DockerHostOwnerUserService.ts +123 -0
- package/Server/Services/DockerHostService.ts +227 -2
- package/Server/Services/DockerSwarmClusterFeedService.ts +102 -0
- package/Server/Services/DockerSwarmClusterLabelRuleEngineService.ts +30 -0
- package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +24 -0
- package/Server/Services/DockerSwarmClusterOwnerTeamService.ts +133 -0
- package/Server/Services/DockerSwarmClusterOwnerUserService.ts +127 -0
- package/Server/Services/DockerSwarmClusterService.ts +229 -2
- package/Server/Services/EnterpriseLicenseService.ts +15 -7
- package/Server/Services/ExceptionAggregationService.ts +12 -2
- package/Server/Services/HostFeedService.ts +99 -0
- package/Server/Services/HostLabelRuleEngineService.ts +29 -0
- package/Server/Services/HostOwnerRuleEngineService.ts +23 -0
- package/Server/Services/HostOwnerTeamService.ts +129 -0
- package/Server/Services/HostOwnerUserService.ts +123 -0
- package/Server/Services/HostService.ts +215 -2
- package/Server/Services/IncidentService.ts +11 -0
- package/Server/Services/Index.ts +24 -0
- package/Server/Services/KubernetesClusterFeedService.ts +99 -0
- package/Server/Services/KubernetesClusterLabelRuleEngineService.ts +30 -0
- package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +24 -0
- package/Server/Services/KubernetesClusterOwnerTeamService.ts +133 -0
- package/Server/Services/KubernetesClusterOwnerUserService.ts +127 -0
- package/Server/Services/KubernetesClusterService.ts +231 -2
- package/Server/Services/LogAggregationService.ts +23 -280
- package/Server/Services/MetricAggregationService.ts +98 -1
- package/Server/Services/MonitorService.ts +177 -13
- package/Server/Services/MonitorTemplateService.ts +111 -0
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +777 -86
- package/Server/Services/NetworkDeviceAutoImportRuleService.ts +199 -16
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +1055 -14
- package/Server/Services/NetworkDeviceService.ts +750 -4
- package/Server/Services/NetworkInterfaceService.ts +415 -0
- package/Server/Services/NetworkSiteService.ts +485 -52
- package/Server/Services/PodmanHostFeedService.ts +99 -0
- package/Server/Services/PodmanHostLabelRuleEngineService.ts +29 -0
- package/Server/Services/PodmanHostOwnerRuleEngineService.ts +23 -0
- package/Server/Services/PodmanHostOwnerTeamService.ts +129 -0
- package/Server/Services/PodmanHostOwnerUserService.ts +123 -0
- package/Server/Services/PodmanHostService.ts +227 -2
- package/Server/Services/ProjectService.ts +16 -10
- package/Server/Services/ProxmoxClusterFeedService.ts +99 -0
- package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +30 -0
- package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +24 -0
- package/Server/Services/ProxmoxClusterOwnerTeamService.ts +131 -0
- package/Server/Services/ProxmoxClusterOwnerUserService.ts +124 -0
- package/Server/Services/ProxmoxClusterService.ts +225 -2
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +61 -0
- package/Server/Services/ServiceFeedService.ts +99 -0
- package/Server/Services/ServiceLabelRuleEngineService.ts +29 -0
- package/Server/Services/ServiceOwnerRuleEngineService.ts +26 -0
- package/Server/Services/ServiceOwnerTeamService.ts +129 -0
- package/Server/Services/ServiceOwnerUserService.ts +123 -0
- package/Server/Services/ServiceService.ts +227 -3
- package/Server/Services/StatusPageSubscriberService.ts +7 -0
- package/Server/Services/TelemetryAttributeService.ts +13 -3
- package/Server/Services/TelemetrySourceMapService.ts +307 -0
- package/Server/Services/ThreatIntelFeedService.ts +222 -0
- package/Server/Services/ThreatIntelIndicatorService.ts +428 -0
- package/Server/Services/TraceAggregationService.ts +101 -11
- package/Server/Services/UserService.ts +13 -6
- package/Server/Services/UserTwoFactorBackupCodeService.ts +127 -0
- package/Server/Services/UserWebhookService.ts +11 -2
- package/Server/Static/Brand/README.md +38 -0
- package/Server/Static/Brand/favicons/apple-touch-icon.png +0 -0
- package/Server/Static/Brand/favicons/favicon-16x16.png +0 -0
- package/Server/Static/Brand/favicons/favicon-32x32.png +0 -0
- package/Server/Static/Brand/favicons/favicon.ico +0 -0
- package/Server/Static/Brand/favicons/safari-pinned-tab.svg +31 -0
- package/Server/Static/Brand/hou-wb.svg +1 -0
- package/Server/Static/Brand/oneuptime-logo.svg +1 -0
- package/Server/Static/Brand/ou-wb.svg +1 -0
- package/Server/Static/Vendor/README.md +4 -0
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +13 -3
- package/Server/Types/Database/AggregateBy.ts +83 -0
- package/Server/Types/Database/AggregateResultUtil.ts +126 -0
- package/Server/Types/Database/JSONColumnQuery.ts +18 -0
- package/Server/Types/Database/QueryHelper.ts +40 -0
- package/Server/Types/Database/QueryUtil.ts +26 -0
- package/Server/Types/Workflow/Components/API/Utils.ts +10 -1
- package/Server/Types/Workflow/Components/JavaScript.ts +8 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +9 -0
- package/Server/Utils/AI/SRE/InvestigationGrader.ts +18 -0
- package/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.ts +422 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +176 -0
- package/Server/Utils/DataSource/HttpFetch.ts +18 -0
- package/Server/Utils/Database/RelationIdUtil.ts +42 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +38 -9
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +34 -3
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +9 -1
- package/Server/Utils/Monitor/Criteria/SyntheticMonitor.ts +28 -10
- package/Server/Utils/Monitor/MonitorAlert.ts +541 -332
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +330 -64
- package/Server/Utils/Monitor/MonitorIncident.ts +640 -434
- package/Server/Utils/Monitor/MonitorResource.ts +226 -49
- package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +46 -3
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +23 -102
- package/Server/Utils/Monitor/PerEntityCriteriaFanOut.ts +329 -0
- package/Server/Utils/NetworkDevice/DeviceHealthAggregation.ts +427 -0
- package/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.ts +310 -0
- package/Server/Utils/OnCallNotificationContext.ts +686 -0
- package/Server/Utils/PrivateNetworkWebhookConfig.ts +549 -0
- package/Server/Utils/ResourceFeed/ResourceFeedUtil.ts +139 -0
- package/Server/Utils/Response.ts +11 -1
- package/Server/Utils/SSRFProtection.ts +230 -60
- package/Server/Utils/SecurityEvent/ConnectorErrorMessage.ts +106 -0
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +84 -284
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +47 -20
- package/Server/Utils/SecurityEvent/SecurityEventAlerting.ts +318 -0
- package/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.ts +199 -0
- package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.ts +252 -0
- package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +739 -0
- package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +533 -0
- package/Server/Utils/StartServer.ts +83 -56
- package/Server/Utils/StatusPageSubscriberWebhook.ts +3 -0
- package/Server/Utils/Telemetry/SourceMapResolver.ts +480 -0
- package/Server/Utils/TwoFactorBackupCodeNotification.ts +106 -0
- package/Server/Utils/VM/VMAPI.ts +3 -0
- package/Server/Utils/VM/VMRunner.ts +27 -1
- package/Server/Utils/VendorAssets.ts +42 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +136 -6
- package/Server/Views/AcknowledgeUserOnCallNotification.ejs +11 -9
- package/Server/Views/Partials/Head.ejs +9 -9
- package/Server/Views/Partials/OnCallNotificationDetails.ejs +48 -0
- package/Server/Views/ViewMessage.ejs +11 -9
- package/Tests/App/Accounts/LoginTwoFactorRecovery.test.tsx +972 -0
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +401 -0
- package/Tests/App/Dashboard/DiscoveryScanEditForm.test.tsx +707 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +1908 -18
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +230 -0
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +216 -0
- package/Tests/App/Dashboard/TopologyAddToMonitoring.test.tsx +192 -0
- package/Tests/App/Dashboard/WorkspaceNotificationRuleConditions.test.tsx +439 -0
- package/Tests/Models/DatabaseModels/DateColumnDeserialization.test.ts +182 -0
- package/Tests/Models/DatabaseModels/RelationColumnModelTypeCoverage.test.ts +82 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +398 -0
- package/Tests/Models/DiscoveryScanNameColumn.test.ts +151 -16
- package/Tests/Models/DiscoveryScanSnmpEnabledColumn.test.ts +593 -0
- package/Tests/Models/NetworkDeviceCreateContract.test.ts +102 -0
- package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +607 -0
- package/Tests/Server/API/AIGenerationRequestBudget.test.ts +355 -0
- package/Tests/Server/API/AIKillSwitchBackstop.test.ts +602 -0
- package/Tests/Server/API/BaseAPIUpdateDateColumns.test.ts +154 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +335 -0
- package/Tests/Server/API/UserOnCallLogTimelineAcknowledgePage.test.ts +611 -0
- package/Tests/Server/API/UserTotpAuthAPI.test.ts +498 -4
- package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +682 -0
- package/Tests/Server/Infrastructure/ExampleDockerfiles.test.ts +595 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +9 -0
- package/Tests/Server/Services/AIServiceProjectAccess.test.ts +6 -0
- package/Tests/Server/Services/AutoImportScanCredentialSelect.test.ts +18 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +590 -0
- package/Tests/Server/Services/DatabaseServiceUpdateWriteRouting.test.ts +12 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +162 -26
- package/Tests/Server/Services/EnterpriseLicenseServiceCreate.test.ts +292 -0
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +119 -12
- package/Tests/Server/Services/LogAggregationAttributeOperators.test.ts +109 -0
- package/Tests/Server/Services/MetricAggregationAttributeOperators.test.ts +170 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +383 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +997 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleService.test.ts +830 -0
- package/Tests/Server/Services/NetworkDeviceAutoMonitorLifecycle.test.ts +528 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +3141 -1
- package/Tests/Server/Services/NetworkDeviceRegisteredHostnames.test.ts +1023 -0
- package/Tests/Server/Services/NetworkDeviceSiteAssignmentRuleSkip.test.ts +728 -0
- package/Tests/Server/Services/NetworkInterfaceServiceUpsert.test.ts +795 -0
- package/Tests/Server/Services/NetworkSiteRollupPolicyAndMaintenance.test.ts +903 -0
- package/Tests/Server/Services/NetworkSiteService.test.ts +165 -24
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +5 -3
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +5 -2
- package/Tests/Server/Services/ResourceCreationFeedWrites.test.ts +304 -0
- package/Tests/Server/Services/ResourceFeedServices.test.ts +269 -0
- package/Tests/Server/Services/ResourceOwnerFeedWrites.test.ts +245 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +792 -0
- package/Tests/Server/Services/TelemetrySourceMapService.test.ts +483 -0
- package/Tests/Server/Services/ThreatIntelFeedService.test.ts +351 -0
- package/Tests/Server/Services/ThreatIntelIndicatorService.test.ts +366 -0
- package/Tests/Server/Services/TraceAggregationAttributeOperators.test.ts +507 -0
- package/Tests/Server/Services/UserTwoFactorBackupCodeGenerateIfNone.test.ts +705 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleOnCallDutyPolicy.test.ts +306 -0
- package/Tests/Server/Types/Database/AggregateResultUtil.test.ts +386 -0
- package/Tests/Server/Types/Database/Permissions/SelectRejectionMessageContract.test.ts +424 -0
- package/Tests/Server/Types/Database/QueryHelperWildcard.test.ts +142 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentPrivateNetwork.test.ts +163 -0
- package/Tests/Server/Types/Workflow/Components/JavaScriptPrivateNetwork.test.ts +124 -0
- package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +5 -0
- package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +39 -1
- package/Tests/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.test.ts +338 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWildcard.test.ts +383 -0
- package/Tests/Server/Utils/Marketing/EmittedMarketingEvent.ts +23 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +51 -0
- package/Tests/Server/Utils/Monitor/Criteria/SyntheticMonitorCriteria.test.ts +259 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPerSeriesFanout.test.ts +557 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +12 -2
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkLogWritePath.test.ts +404 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +8 -2
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +125 -98
- package/Tests/Server/Utils/Monitor/PerEntityCriteriaFanOut.test.ts +444 -0
- package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1112 -0
- package/Tests/Server/Utils/NetworkDevice/DeviceHealthAggregation.test.ts +717 -0
- package/Tests/Server/Utils/NetworkDevice/RulePatternValidator.test.ts +78 -0
- package/Tests/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.test.ts +363 -0
- package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +35 -0
- package/Tests/Server/Utils/OnCallNotificationContext.test.ts +908 -0
- package/Tests/Server/Utils/PrivateNetworkWebhookConfig.test.ts +423 -0
- package/Tests/Server/Utils/ResourceFeed/ResourceFeedUtil.test.ts +191 -0
- package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +597 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +819 -0
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +381 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +417 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +819 -0
- package/Tests/Server/Utils/SecurityEvent/SecurityEventAlerting.test.ts +246 -0
- package/Tests/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.test.ts +282 -0
- package/Tests/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.test.ts +312 -0
- package/Tests/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.test.ts +854 -0
- package/Tests/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.test.ts +437 -0
- package/Tests/Server/Utils/StartServerErrorStatus.test.ts +225 -0
- package/Tests/Server/Utils/StatusPage/MonitorRulePatternValidator.test.ts +62 -0
- package/Tests/Server/Utils/StatusPageSubscriberWebhookPrivateNetwork.test.ts +119 -0
- package/Tests/Server/Utils/Telemetry/SourceMapResolver.test.ts +542 -0
- package/Tests/Server/Utils/TwoFactorBackupCodeNotification.test.ts +272 -0
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +111 -0
- package/Tests/Server/Utils/VendorAssets.test.ts +117 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +101 -4
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstallStateDiagnostics.test.ts +922 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsRosterDiagnosticMessages.test.ts +642 -0
- package/Tests/Server/Views/OnCallAcknowledgePage.test.ts +430 -0
- package/Tests/Types/API/URLQueryString.test.ts +472 -0
- package/Tests/Types/BaseDatabase/Wildcard.test.ts +163 -0
- package/Tests/Types/BaseDatabase/WildcardPattern.test.ts +149 -0
- package/Tests/Types/Database/DateColumnValue.test.ts +265 -0
- package/Tests/Types/DateToIsoStringOrNull.test.ts +67 -0
- package/Tests/Types/Log/LogQueryToFilter.test.ts +218 -43
- package/Tests/Types/Monitor/KubernetesTemplateGroupByKeys.test.ts +225 -0
- package/Tests/Types/Monitor/TemplateGroupByKeys.test.ts +296 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +102 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +145 -0
- package/Tests/Types/NetworkSite/SiteHealthRollupPolicy.test.ts +58 -0
- package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +15 -0
- package/Tests/Types/Telemetry/TelemetrySearchQuery.test.ts +620 -0
- package/Tests/Types/WhatsApp/WhatsAppTemplates.test.ts +110 -0
- package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +76 -6
- package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +16 -4
- package/Tests/Types/Workspace/NotificationRules/OnCallDutyPolicyConditions.test.ts +669 -0
- package/Tests/UI/Components/DictionaryFilterOperatorWildcard.test.ts +175 -0
- package/Tests/UI/Components/IconOpticalSize.test.tsx +377 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +332 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsInstallGuidanceContent.test.tsx +643 -0
- package/Tests/Utils/Monitor/InterfaceInventoryUtil.test.ts +824 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +626 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +718 -0
- package/Tests/Utils/NetworkDevice/DeviceHealthStateUtil.test.ts +59 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +287 -2
- package/Tests/Utils/NetworkDiscovery/DiscoveryImportEligibility.test.ts +73 -0
- package/Tests/Utils/NetworkDiscovery/PingMonitorBuilder.test.ts +317 -0
- package/Tests/Utils/NetworkDiscovery/RescanIntervalUtil.test.ts +262 -0
- package/Tests/Utils/NetworkDiscovery/ScanModeUtil.test.ts +474 -0
- package/Tests/Utils/NetworkDiscovery/SnmpScanConfigUtil.test.ts +2442 -0
- package/Tests/Utils/NetworkSite/SiteMaintenanceUtil.test.ts +188 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +522 -0
- package/Tests/Utils/NetworkSite/SiteUptimeUtil.test.ts +694 -0
- package/Tests/Utils/SecurityEvent/ThreatIntel/StixPatternParser.test.ts +281 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +87 -67
- package/Types/API/URL.ts +45 -10
- package/Types/AnalyticsDatabase/AnalyticsTableName.ts +8 -0
- package/Types/BaseDatabase/NotWildcard.ts +86 -0
- package/Types/BaseDatabase/Wildcard.ts +89 -0
- package/Types/BaseDatabase/WildcardPattern.ts +176 -0
- package/Types/Database/DateColumnValue.ts +184 -0
- package/Types/Database/TableColumn.ts +40 -5
- package/Types/Date.ts +37 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/JSON.ts +8 -0
- package/Types/Log/LogQueryToFilter.ts +167 -129
- package/Types/Monitor/DockerAlertTemplates.ts +43 -12
- package/Types/Monitor/HostAlertTemplates.ts +31 -3
- package/Types/Monitor/KubernetesAlertTemplates.ts +98 -0
- package/Types/Monitor/MonitorStep.ts +49 -0
- package/Types/Monitor/PodmanAlertTemplates.ts +39 -12
- package/Types/Monitor/SnmpMonitor/CdpNeighbor.ts +14 -0
- package/Types/Monitor/SnmpMonitor/LldpNeighbor.ts +11 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +11 -4
- package/Types/NetworkAutomation/RuleRunResult.ts +191 -2
- package/Types/NetworkSite/SiteHealthRollupPolicy.ts +96 -0
- package/Types/Permission.ts +411 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +42 -0
- package/Types/SecurityEvent/ThreatIntelConstants.ts +118 -0
- package/Types/SerializableObjectDictionary.ts +8 -0
- package/Types/Telemetry/SourceMap.ts +56 -0
- package/Types/Telemetry/TelemetrySearchQuery.ts +949 -0
- package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +12 -1
- package/UI/Components/Dictionary/DictionaryFilterOperator.ts +83 -0
- package/UI/Components/Icon/Icon.tsx +28 -1
- package/UI/Components/LogsViewer/components/LogSearchBar.tsx +26 -4
- package/UI/Components/LogsViewer/components/LogSearchHelp.tsx +34 -16
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +15 -2
- package/Utils/Metrics/MetricExplorerUrl.ts +77 -15
- package/Utils/Monitor/InterfaceInventoryUtil.ts +251 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +333 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +249 -28
- package/Utils/NetworkDevice/DeviceHealthStateUtil.ts +14 -6
- package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +2 -0
- package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +49 -18
- package/Utils/NetworkDiscovery/PingMonitorBuilder.ts +215 -0
- package/Utils/NetworkDiscovery/RescanIntervalUtil.ts +148 -0
- package/Utils/NetworkDiscovery/ScanModeUtil.ts +105 -0
- package/Utils/NetworkDiscovery/SnmpScanConfigUtil.ts +827 -0
- package/Utils/NetworkSite/SiteMaintenanceUtil.ts +110 -0
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +350 -19
- package/Utils/NetworkSite/SiteUptimeUtil.ts +422 -21
- package/Utils/SecurityEvent/ThreatIntel/StixPatternParser.ts +383 -0
- package/Utils/Telemetry/CrossSignalScope.ts +37 -39
- package/build/dist/Models/AnalyticsModels/Index.js +2 -0
- package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js +325 -0
- package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertSeverity.js +4 -1
- package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CephClusterFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/CephClusterFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CloudResourceFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/CloudResourceFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +11 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DetectionRule.js +9 -3
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHostFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/DockerHostFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmClusterFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmClusterFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +9 -3
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -1
- package/build/dist/Models/DatabaseModels/HostFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/HostFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js +4 -1
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +22 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesClusterFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/KubernetesClusterFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Monitor.js +81 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +103 -7
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +91 -6
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +276 -31
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSite.js +124 -1
- package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanHostFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/PodmanHostFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +66 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/ServiceFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TelemetrySourceMap.js +496 -0
- package/build/dist/Models/DatabaseModels/TelemetrySourceMap.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js +800 -0
- package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js +1 -0
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js +1 -0
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
- package/build/dist/Server/API/AlertAPI.js +18 -1
- package/build/dist/Server/API/AlertAPI.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +8 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +35 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentEpisodeAPI.js +18 -1
- package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +30 -2
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +18 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +45 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +100 -35
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserOnCallLogTimelineAPI.js +64 -59
- package/build/dist/Server/API/UserOnCallLogTimelineAPI.js.map +1 -1
- package/build/dist/Server/API/UserTotpAuthAPI.js +65 -1
- package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
- package/build/dist/Server/API/UserWebAuthnAPI.js +42 -1
- package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787923136162-MigrationName.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787923136162-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789600000000-AddAutoProvisionedNetworkDeviceMonitors.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789600000000-AddAutoProvisionedNetworkDeviceMonitors.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789700000000-AddNetworkScaleIndexes.js +88 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789700000000-AddNetworkScaleIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789800000000-WidenSecurityEventLastErrorColumns.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789800000000-WidenSecurityEventLastErrorColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789900000000-AddNetworkSiteRollupPolicyAndMaintenance.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789900000000-AddNetworkSiteRollupPolicyAndMaintenance.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000000-AddTelemetrySourceMap.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000000-AddTelemetrySourceMap.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000001-AddSnmpConfigsToNetworkDeviceDiscoveryScan.js +61 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000001-AddSnmpConfigsToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790003445000-AddSnmpEnabledToNetworkDeviceDiscoveryScan.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790003445000-AddSnmpEnabledToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790100000000-AddResourceActivityFeeds.js +179 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790100000000-AddResourceActivityFeeds.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +40 -3
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +177 -30
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterFeedService.js +89 -0
- package/build/dist/Server/Services/CephClusterFeedService.js.map +1 -0
- package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +21 -0
- package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/CephClusterOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterOwnerUserService.js +113 -0
- package/build/dist/Server/Services/CephClusterOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +175 -1
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceFeedService.js +89 -0
- package/build/dist/Server/Services/CloudResourceFeedService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +28 -3
- package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/CloudResourceOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceOwnerUserService.js +113 -0
- package/build/dist/Server/Services/CloudResourceOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +177 -1
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +201 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostFeedService.js +89 -0
- package/build/dist/Server/Services/DockerHostFeedService.js.map +1 -0
- package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +21 -0
- package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/DockerHostOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostOwnerUserService.js +113 -0
- package/build/dist/Server/Services/DockerHostOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +177 -1
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterFeedService.js +90 -0
- package/build/dist/Server/Services/DockerSwarmClusterFeedService.js.map +1 -0
- package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +21 -0
- package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/DockerSwarmClusterOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterOwnerUserService.js +113 -0
- package/build/dist/Server/Services/DockerSwarmClusterOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +175 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +40 -30
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/ExceptionAggregationService.js +8 -2
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
- package/build/dist/Server/Services/HostFeedService.js +89 -0
- package/build/dist/Server/Services/HostFeedService.js.map +1 -0
- package/build/dist/Server/Services/HostLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/HostLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/HostOwnerRuleEngineService.js +21 -0
- package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/HostOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/HostOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/HostOwnerUserService.js +113 -0
- package/build/dist/Server/Services/HostOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +175 -1
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +10 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +24 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterFeedService.js +89 -0
- package/build/dist/Server/Services/KubernetesClusterFeedService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +21 -0
- package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js +113 -0
- package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +177 -1
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +19 -196
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +59 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +115 -13
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +86 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +561 -67
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +130 -16
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +850 -8
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +594 -5
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkInterfaceService.js +306 -0
- package/build/dist/Server/Services/NetworkInterfaceService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +408 -47
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostFeedService.js +89 -0
- package/build/dist/Server/Services/PodmanHostFeedService.js.map +1 -0
- package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +21 -0
- package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/PodmanHostOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostOwnerUserService.js +113 -0
- package/build/dist/Server/Services/PodmanHostOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +177 -1
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +40 -30
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterFeedService.js +89 -0
- package/build/dist/Server/Services/ProxmoxClusterFeedService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +21 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js +113 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +175 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +56 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceFeedService.js +89 -0
- package/build/dist/Server/Services/ServiceFeedService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLabelRuleEngineService.js +27 -3
- package/build/dist/Server/Services/ServiceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +24 -0
- package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceOwnerTeamService.js +120 -0
- package/build/dist/Server/Services/ServiceOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/ServiceOwnerUserService.js +113 -0
- package/build/dist/Server/Services/ServiceOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +173 -2
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +7 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +9 -3
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Services/TelemetrySourceMapService.js +265 -0
- package/build/dist/Server/Services/TelemetrySourceMapService.js.map +1 -0
- package/build/dist/Server/Services/ThreatIntelFeedService.js +157 -0
- package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -0
- package/build/dist/Server/Services/ThreatIntelIndicatorService.js +276 -0
- package/build/dist/Server/Services/ThreatIntelIndicatorService.js.map +1 -0
- package/build/dist/Server/Services/TraceAggregationService.js +62 -6
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +22 -13
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js +120 -0
- package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js.map +1 -1
- package/build/dist/Server/Services/UserWebhookService.js +11 -2
- package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +12 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/AggregateBy.js +2 -0
- package/build/dist/Server/Types/Database/AggregateBy.js.map +1 -0
- package/build/dist/Server/Types/Database/AggregateResultUtil.js +90 -0
- package/build/dist/Server/Types/Database/AggregateResultUtil.js.map +1 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +14 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +42 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +18 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js +10 -1
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +8 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +15 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.js +266 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +134 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +8 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
- package/build/dist/Server/Utils/Database/RelationIdUtil.js +31 -0
- package/build/dist/Server/Utils/Database/RelationIdUtil.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +26 -8
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +42 -17
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +9 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SyntheticMonitor.js +15 -2
- package/build/dist/Server/Utils/Monitor/Criteria/SyntheticMonitor.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +386 -271
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +278 -64
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +469 -350
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +183 -44
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +46 -3
- package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +18 -91
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/PerEntityCriteriaFanOut.js +194 -0
- package/build/dist/Server/Utils/Monitor/PerEntityCriteriaFanOut.js.map +1 -0
- package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js +332 -0
- package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js.map +1 -0
- package/build/dist/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.js +279 -0
- package/build/dist/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.js.map +1 -0
- package/build/dist/Server/Utils/OnCallNotificationContext.js +415 -0
- package/build/dist/Server/Utils/OnCallNotificationContext.js.map +1 -0
- package/build/dist/Server/Utils/PrivateNetworkWebhookConfig.js +424 -0
- package/build/dist/Server/Utils/PrivateNetworkWebhookConfig.js.map +1 -0
- package/build/dist/Server/Utils/ResourceFeed/ResourceFeedUtil.js +108 -0
- package/build/dist/Server/Utils/ResourceFeed/ResourceFeedUtil.js.map +1 -0
- package/build/dist/Server/Utils/Response.js +12 -1
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +160 -58
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js +89 -0
- package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +75 -234
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +41 -16
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventAlerting.js +217 -0
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventAlerting.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.js +103 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.js +168 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +527 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +376 -0
- package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +70 -44
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +3 -0
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js +327 -0
- package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js.map +1 -0
- package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js +84 -0
- package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +10 -1
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +29 -0
- package/build/dist/Server/Utils/VendorAssets.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +121 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/URL.js +40 -10
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +8 -0
- package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
- package/build/dist/Types/BaseDatabase/NotWildcard.js +72 -0
- package/build/dist/Types/BaseDatabase/NotWildcard.js.map +1 -0
- package/build/dist/Types/BaseDatabase/Wildcard.js +75 -0
- package/build/dist/Types/BaseDatabase/Wildcard.js.map +1 -0
- package/build/dist/Types/BaseDatabase/WildcardPattern.js +137 -0
- package/build/dist/Types/BaseDatabase/WildcardPattern.js.map +1 -0
- package/build/dist/Types/Database/DateColumnValue.js +125 -0
- package/build/dist/Types/Database/DateColumnValue.js.map +1 -0
- package/build/dist/Types/Database/TableColumn.js +18 -2
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Date.js +31 -0
- package/build/dist/Types/Date.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/JSON.js +2 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/Log/LogQueryToFilter.js +111 -108
- package/build/dist/Types/Log/LogQueryToFilter.js.map +1 -1
- package/build/dist/Types/Monitor/DockerAlertTemplates.js +40 -16
- package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/HostAlertTemplates.js +29 -7
- package/build/dist/Types/Monitor/HostAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +98 -4
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +40 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js +36 -16
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +90 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/NetworkSite/SiteHealthRollupPolicy.js +84 -0
- package/build/dist/Types/NetworkSite/SiteHealthRollupPolicy.js.map +1 -0
- package/build/dist/Types/Permission.js +354 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +100 -0
- package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -0
- package/build/dist/Types/SerializableObjectDictionary.js +8 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/SourceMap.js +9 -0
- package/build/dist/Types/Telemetry/SourceMap.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetrySearchQuery.js +661 -0
- package/build/dist/Types/Telemetry/TelemetrySearchQuery.js.map +1 -0
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +12 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +67 -0
- package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +28 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +25 -4
- package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogSearchHelp.js +36 -16
- package/build/dist/UI/Components/LogsViewer/components/LogSearchHelp.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +14 -2
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js +54 -9
- package/build/dist/Utils/Metrics/MetricExplorerUrl.js.map +1 -1
- package/build/dist/Utils/Monitor/InterfaceInventoryUtil.js +128 -0
- package/build/dist/Utils/Monitor/InterfaceInventoryUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +190 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +206 -28
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js +14 -7
- package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +34 -18
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js +163 -0
- package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/RescanIntervalUtil.js +99 -0
- package/build/dist/Utils/NetworkDiscovery/RescanIntervalUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanModeUtil.js +85 -0
- package/build/dist/Utils/NetworkDiscovery/ScanModeUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/SnmpScanConfigUtil.js +560 -0
- package/build/dist/Utils/NetworkDiscovery/SnmpScanConfigUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteMaintenanceUtil.js +56 -0
- package/build/dist/Utils/NetworkSite/SiteMaintenanceUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +185 -10
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/NetworkSite/SiteUptimeUtil.js +248 -21
- package/build/dist/Utils/NetworkSite/SiteUptimeUtil.js.map +1 -1
- package/build/dist/Utils/SecurityEvent/ThreatIntel/StixPatternParser.js +255 -0
- package/build/dist/Utils/SecurityEvent/ThreatIntel/StixPatternParser.js.map +1 -0
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +27 -30
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +2 -1
- package/Tests/Types/Log/LogQueryParser.test.ts +0 -186
- package/Types/Log/LogQueryParser.ts +0 -252
- package/build/dist/Types/Log/LogQueryParser.js +0 -200
- package/build/dist/Types/Log/LogQueryParser.js.map +0 -1
|
@@ -34,6 +34,20 @@ type CapturedFormField = {
|
|
|
34
34
|
stepId?: string | undefined;
|
|
35
35
|
required?: boolean | undefined;
|
|
36
36
|
placeholder?: string | undefined;
|
|
37
|
+
/*
|
|
38
|
+
* Captured for BOTH changes, which is why it is typed as the enum rather
|
|
39
|
+
* than as a string.
|
|
40
|
+
*
|
|
41
|
+
* Issue #3458: the SNMP step's single field is a CustomComponent rather than
|
|
42
|
+
* an input — see the SNMP describe blocks below. A field that keeps the key
|
|
43
|
+
* but loses the schema type renders as a text box over a jsonb column, which
|
|
44
|
+
* types cleanly and saves a string.
|
|
45
|
+
*
|
|
46
|
+
* Issue #3445: the scan's METHOD is asked as a Toggle, and the same
|
|
47
|
+
* assertion has to be able to tell the two apart.
|
|
48
|
+
*/
|
|
49
|
+
fieldType?: FormFieldSchemaType | undefined;
|
|
50
|
+
getCustomElement?: unknown;
|
|
37
51
|
validation?:
|
|
38
52
|
| {
|
|
39
53
|
maxLength?: number | undefined;
|
|
@@ -45,11 +59,44 @@ type CapturedFormField = {
|
|
|
45
59
|
| ((values: Record<string, unknown>) => string | null)
|
|
46
60
|
| undefined;
|
|
47
61
|
showIf?: ((values: Record<string, unknown>) => boolean) | undefined;
|
|
62
|
+
/*
|
|
63
|
+
* The rest of a field's configuration, captured for issue #3445. A scan's
|
|
64
|
+
* METHOD is asked as a toggle whose defaultValue decides what an untouched
|
|
65
|
+
* wizard creates, whose onChange clears the credentials the operator has
|
|
66
|
+
* just said they do not want sent, and whose copy is the only place the
|
|
67
|
+
* product explains what an ICMP-only scan can and cannot find. None of that
|
|
68
|
+
* is reachable by rendering, because the table this configuration is handed
|
|
69
|
+
* to is mocked away — so it is read off the props, like everything above.
|
|
70
|
+
*
|
|
71
|
+
* `fieldType` belongs to this group too, and is declared once above.
|
|
72
|
+
*/
|
|
73
|
+
defaultValue?: boolean | string | number | Date | undefined;
|
|
74
|
+
description?: string | undefined;
|
|
75
|
+
sectionTitle?: string | undefined;
|
|
76
|
+
sectionDescription?: string | undefined;
|
|
77
|
+
hideOptionalLabel?: boolean | undefined;
|
|
78
|
+
onChange?:
|
|
79
|
+
| ((
|
|
80
|
+
value: boolean,
|
|
81
|
+
currentFormValues: Record<string, unknown>,
|
|
82
|
+
setNewFormValues: (values: Record<string, unknown>) => void,
|
|
83
|
+
) => void)
|
|
84
|
+
| undefined;
|
|
85
|
+
getFooterElement?:
|
|
86
|
+
| ((values: Record<string, unknown>) => React.ReactElement | undefined)
|
|
87
|
+
| undefined;
|
|
48
88
|
};
|
|
49
89
|
|
|
50
90
|
type CapturedFormStep = {
|
|
51
91
|
id: string;
|
|
52
92
|
title: string;
|
|
93
|
+
/*
|
|
94
|
+
* A step can remove itself, which is how "SNMP Version is required" stopped
|
|
95
|
+
* blocking a scan that sends no SNMP (issue #3445): BasicForm validates only
|
|
96
|
+
* the fields of the step being submitted, and a step that filtered itself
|
|
97
|
+
* out can never be that step.
|
|
98
|
+
*/
|
|
99
|
+
showIf?: ((values: Record<string, unknown>) => boolean) | undefined;
|
|
53
100
|
};
|
|
54
101
|
|
|
55
102
|
/*
|
|
@@ -60,6 +107,19 @@ type CapturedFormStep = {
|
|
|
60
107
|
type CapturedColumn = {
|
|
61
108
|
field?: Record<string, unknown> | undefined;
|
|
62
109
|
title?: string | undefined;
|
|
110
|
+
/*
|
|
111
|
+
* The layout flags are captured too, because "which columns does this table
|
|
112
|
+
* show by default, and which can the viewer switch on" is configuration in
|
|
113
|
+
* exactly the same way the form fields are — and it is decided entirely by
|
|
114
|
+
* these four properties. See the Scan Target describe block at the bottom.
|
|
115
|
+
*/
|
|
116
|
+
id?: string | undefined;
|
|
117
|
+
type?: FieldType | undefined;
|
|
118
|
+
isHiddenByDefault?: boolean | undefined;
|
|
119
|
+
isNotCustomizable?: boolean | undefined;
|
|
120
|
+
isRemovable?: boolean | undefined;
|
|
121
|
+
disableSort?: boolean | undefined;
|
|
122
|
+
noValueMessage?: string | undefined;
|
|
63
123
|
getElement?:
|
|
64
124
|
| ((item: NetworkDeviceDiscoveryScan) => React.ReactElement)
|
|
65
125
|
| undefined;
|
|
@@ -83,6 +143,14 @@ type CapturedTableProps = {
|
|
|
83
143
|
actionButtons?: Array<CapturedActionButton>;
|
|
84
144
|
selectMoreFields?: Record<string, boolean>;
|
|
85
145
|
isEditable?: boolean | undefined;
|
|
146
|
+
/*
|
|
147
|
+
* BaseModelTable gates the entire "Customize Columns" picker on this pair
|
|
148
|
+
* (isColumnCustomizationEnabled). Nothing else in the repo reads this key,
|
|
149
|
+
* so dropping the prop would make every hidden-by-default column on this
|
|
150
|
+
* table permanently unreachable, with no other symptom.
|
|
151
|
+
*/
|
|
152
|
+
userPreferencesKey?: string | undefined;
|
|
153
|
+
disableColumnCustomization?: boolean | undefined;
|
|
86
154
|
};
|
|
87
155
|
|
|
88
156
|
let capturedTableProps: CapturedTableProps | null = null;
|
|
@@ -98,6 +166,27 @@ jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
|
98
166
|
});
|
|
99
167
|
|
|
100
168
|
import DiscoveryPage from "../../../../App/FeatureSet/Dashboard/src/Pages/NetworkDevice/Discovery";
|
|
169
|
+
/*
|
|
170
|
+
* The reveal chain the SNMP step used to declare as showIf callbacks on nine
|
|
171
|
+
* flat fields now lives inside SnmpConfigListEditor, which builds it out of
|
|
172
|
+
* exactly these predicates so it cannot drift from the NetworkDevice forms
|
|
173
|
+
* (issue #3458). They are imported here for the same reason the page itself
|
|
174
|
+
* is: the guarantee moved, so the assertion moves with it rather than being
|
|
175
|
+
* quietly dropped. See "the v3 reveal chain still gates a credential card"
|
|
176
|
+
* below.
|
|
177
|
+
*
|
|
178
|
+
* DEFAULT_SNMP_VERSION is the constant the method toggle RESTORES snmpVersion
|
|
179
|
+
* to when SNMP is switched off. The wizard no longer declares an SNMP Version
|
|
180
|
+
* field to read a defaultValue off, so the expectation is pinned to the shared
|
|
181
|
+
* constant the page itself writes from.
|
|
182
|
+
*/
|
|
183
|
+
import {
|
|
184
|
+
DEFAULT_SNMP_VERSION,
|
|
185
|
+
isSnmpV3,
|
|
186
|
+
isSnmpV3WithAuth,
|
|
187
|
+
isSnmpV3WithPriv,
|
|
188
|
+
SnmpV3RevealSource,
|
|
189
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Pages/NetworkDevice/SnmpConfigFormFields";
|
|
101
190
|
import ProbeUtil from "../../../../App/FeatureSet/Dashboard/src/Utils/Probe";
|
|
102
191
|
import Project from "../../../Models/DatabaseModels/Project";
|
|
103
192
|
import Probe from "../../../Models/DatabaseModels/Probe";
|
|
@@ -105,10 +194,35 @@ import ProjectUtil from "../../../UI/Utils/Project";
|
|
|
105
194
|
import PermissionUtil from "../../../UI/Utils/Permission";
|
|
106
195
|
import Permission from "../../../Types/Permission";
|
|
107
196
|
import ScanTargetUtil from "../../../Utils/NetworkDiscovery/ScanTargetUtil";
|
|
197
|
+
import FormFieldSchemaType from "../../../UI/Components/Forms/Types/FormFieldSchemaType";
|
|
198
|
+
import SnmpSecurityLevel from "../../../Types/Monitor/SnmpMonitor/SnmpSecurityLevel";
|
|
108
199
|
import ScanNameUtil from "../../../Utils/NetworkDiscovery/ScanNameUtil";
|
|
200
|
+
import SnmpScanConfigUtil, {
|
|
201
|
+
DiscoveryScanSnmpConfig,
|
|
202
|
+
MAX_SNMP_CONFIGS_PER_SCAN,
|
|
203
|
+
} from "../../../Utils/NetworkDiscovery/SnmpScanConfigUtil";
|
|
109
204
|
import NetworkDeviceDiscoveryScan from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
|
|
110
205
|
import ObjectID from "../../../Types/ObjectID";
|
|
111
206
|
import Route from "../../../Types/API/Route";
|
|
207
|
+
import FieldType from "../../../UI/Components/Types/FieldType";
|
|
208
|
+
import ModelTableColumn from "../../../UI/Components/ModelTable/Column";
|
|
209
|
+
import ModelTableColumns from "../../../UI/Components/ModelTable/Columns";
|
|
210
|
+
import {
|
|
211
|
+
ColumnPreference,
|
|
212
|
+
applyColumnPreference,
|
|
213
|
+
getColumnIds,
|
|
214
|
+
} from "../../../UI/Components/ModelTable/ColumnPreference";
|
|
215
|
+
import { getSelectFromColumns } from "../../../UI/Components/ModelTable/SelectFromColumns";
|
|
216
|
+
import Select from "../../../Types/BaseDatabase/Select";
|
|
217
|
+
/*
|
|
218
|
+
* The real validator BasicForm runs on every step change, so "a hidden field's
|
|
219
|
+
* rules do not fire" is asserted against the code that decides it rather than
|
|
220
|
+
* against a restatement of it here.
|
|
221
|
+
*/
|
|
222
|
+
import Validation from "../../../UI/Components/Forms/Validation";
|
|
223
|
+
import Fields from "../../../UI/Components/Forms/Types/Fields";
|
|
224
|
+
import FormValues from "../../../UI/Components/Forms/Types/FormValues";
|
|
225
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
112
226
|
|
|
113
227
|
const PROJECT_ID: ObjectID = new ObjectID(
|
|
114
228
|
"11111111-1111-4111-8111-111111111111",
|
|
@@ -151,6 +265,47 @@ function columnNamed(key: string): CapturedColumn {
|
|
|
151
265
|
return column;
|
|
152
266
|
}
|
|
153
267
|
|
|
268
|
+
function columnByTitle(title: string): CapturedColumn {
|
|
269
|
+
const column: CapturedColumn | undefined = capturedTableProps?.columns?.find(
|
|
270
|
+
(tableColumn: CapturedColumn): boolean => {
|
|
271
|
+
return tableColumn.title === title;
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
if (!column) {
|
|
276
|
+
throw new Error(`Discovery scans table column titled "${title}" not found`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return column;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/*
|
|
283
|
+
* The captured columns handed to the real ColumnPreference helpers. The mock
|
|
284
|
+
* stores them as the loose CapturedColumn shape; they are the very objects the
|
|
285
|
+
* page declared, so the layout functions can be run over them directly rather
|
|
286
|
+
* than over a replica that could drift from the page.
|
|
287
|
+
*/
|
|
288
|
+
function declaredColumns(): ModelTableColumns<NetworkDeviceDiscoveryScan> {
|
|
289
|
+
return (capturedTableProps?.columns ||
|
|
290
|
+
[]) as unknown as ModelTableColumns<NetworkDeviceDiscoveryScan>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/*
|
|
294
|
+
* The titles a viewer sees, in order, for a given stored layout — computed by
|
|
295
|
+
* the same function BaseModelTable renders through, so these tests move when
|
|
296
|
+
* the real rule moves.
|
|
297
|
+
*/
|
|
298
|
+
function visibleColumnTitles(
|
|
299
|
+
preference: ColumnPreference | null,
|
|
300
|
+
): Array<string> {
|
|
301
|
+
return applyColumnPreference<NetworkDeviceDiscoveryScan>({
|
|
302
|
+
columns: declaredColumns(),
|
|
303
|
+
preference: preference,
|
|
304
|
+
}).map((column: ModelTableColumn<NetworkDeviceDiscoveryScan>): string => {
|
|
305
|
+
return column.title || "";
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
154
309
|
/*
|
|
155
310
|
* What the Scan column actually puts on screen for a given row. Rendered
|
|
156
311
|
* rather than inspected: the fallback that matters here is a rendering
|
|
@@ -174,6 +329,85 @@ function renderScanCell(scan: Partial<NetworkDeviceDiscoveryScan>): string {
|
|
|
174
329
|
return container.textContent || "";
|
|
175
330
|
}
|
|
176
331
|
|
|
332
|
+
/*
|
|
333
|
+
* What the Recurrence column puts on screen. Rendered rather than inspected,
|
|
334
|
+
* for the same reason the Scan cell is: the thing under test is a sentence an
|
|
335
|
+
* operator reads, not a shape in the element tree.
|
|
336
|
+
*/
|
|
337
|
+
function renderRecurrenceCell(
|
|
338
|
+
scan: Partial<NetworkDeviceDiscoveryScan>,
|
|
339
|
+
): string {
|
|
340
|
+
const column: CapturedColumn = columnNamed("isRecurring");
|
|
341
|
+
|
|
342
|
+
if (!column.getElement) {
|
|
343
|
+
throw new Error("The Recurrence column renders no element");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const { container } = render(
|
|
347
|
+
<MemoryRouter>
|
|
348
|
+
{column.getElement(scan as NetworkDeviceDiscoveryScan)}
|
|
349
|
+
</MemoryRouter>,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
return container.textContent || "";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/*
|
|
356
|
+
* A credential list of one, carrying whatever the operator typed into that
|
|
357
|
+
* card's port box.
|
|
358
|
+
*
|
|
359
|
+
* The port is deliberately `unknown` rather than `number | undefined`: form
|
|
360
|
+
* values are JSONValues, a Number input posts its contents as TEXT, and the
|
|
361
|
+
* whole reason SnmpScanConfigUtil checks the raw value is that "161.5" would
|
|
362
|
+
* otherwise parseInt to 161, clear both bounds, and fail against a port the
|
|
363
|
+
* probe cannot dial. A helper typed to the stored interface could not express
|
|
364
|
+
* the very case under test.
|
|
365
|
+
*/
|
|
366
|
+
function oneSnmpConfigWithPort(port: unknown): Array<Record<string, unknown>> {
|
|
367
|
+
return [
|
|
368
|
+
{
|
|
369
|
+
id: "config-1",
|
|
370
|
+
name: "Access switches",
|
|
371
|
+
snmpVersion: "V2c",
|
|
372
|
+
snmpCommunityString: "public",
|
|
373
|
+
snmpPort: port,
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/*
|
|
379
|
+
* The shape this whole feature exists for: one subnet, three credential sets,
|
|
380
|
+
* tried in order until one answers. Typed as the stored interface so that a
|
|
381
|
+
* change to it that this list no longer satisfies is a compile error here
|
|
382
|
+
* rather than a runtime surprise on somebody's scan.
|
|
383
|
+
*/
|
|
384
|
+
const MIXED_SUBNET_SNMP_CONFIGS: Array<DiscoveryScanSnmpConfig> = [
|
|
385
|
+
{
|
|
386
|
+
id: "11111111-1111-4111-8111-aaaaaaaaaaaa",
|
|
387
|
+
name: "Access switches",
|
|
388
|
+
snmpVersion: "V2c",
|
|
389
|
+
snmpCommunityString: "public",
|
|
390
|
+
snmpPort: 161,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
id: "22222222-2222-4222-8222-bbbbbbbbbbbb",
|
|
394
|
+
name: "Core - v3",
|
|
395
|
+
snmpVersion: "V3",
|
|
396
|
+
snmpV3SecurityLevel: "authPriv",
|
|
397
|
+
snmpV3Username: "discovery",
|
|
398
|
+
snmpV3AuthProtocol: "SHA",
|
|
399
|
+
snmpV3AuthKey: "authentication-key",
|
|
400
|
+
snmpV3PrivProtocol: "AES",
|
|
401
|
+
snmpV3PrivKey: "privacy-key",
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
// No community string at all: the sweep falls back to "public".
|
|
405
|
+
id: "33333333-3333-4333-8333-cccccccccccc",
|
|
406
|
+
name: "Printers",
|
|
407
|
+
snmpVersion: "V1",
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
|
|
177
411
|
function validate(key: string, values: Record<string, unknown>): string | null {
|
|
178
412
|
const field: CapturedFormField = fieldNamed(key);
|
|
179
413
|
|
|
@@ -184,6 +418,231 @@ function validate(key: string, values: Record<string, unknown>): string | null {
|
|
|
184
418
|
return field.customValidation(values);
|
|
185
419
|
}
|
|
186
420
|
|
|
421
|
+
/*
|
|
422
|
+
* The questions the SNMP Credentials step asks, in the order it asks them —
|
|
423
|
+
* ONE of them, since issue #3458 replaced the nine flat fields of
|
|
424
|
+
* getSnmpConfigFormFields with a single CustomComponent bound to the
|
|
425
|
+
* `snmpConfigs` column.
|
|
426
|
+
*
|
|
427
|
+
* Listed by hand rather than derived from the captured props: a list read back
|
|
428
|
+
* out of the thing under test would agree with it by construction, and the
|
|
429
|
+
* claim being made is that EVERY field on this step is gated on the scan's
|
|
430
|
+
* method — including one a later change adds and forgets to route through the
|
|
431
|
+
* gate (issue #3445). "the SNMP step holds exactly the fields gated below" is
|
|
432
|
+
* what fails until a new field is named here, which is what forces it through
|
|
433
|
+
* the gating assertions rather than past them.
|
|
434
|
+
*/
|
|
435
|
+
const GATED_SNMP_STEP_FIELD_KEYS: Array<string> = ["snmpConfigs"];
|
|
436
|
+
|
|
437
|
+
/*
|
|
438
|
+
* The nine flattened SNMP columns the list replaced as QUESTIONS but did not
|
|
439
|
+
* remove from the model: the server mirrors the first credential set onto them
|
|
440
|
+
* so a probe a version behind keeps working (issue #3458). The wizard asks for
|
|
441
|
+
* none of them any more — "the nine flat SNMP fields the list replaced are gone
|
|
442
|
+
* from the wizard" pins that — but the method toggle still clears every one on
|
|
443
|
+
* its way past, so a value that reached the form some other way cannot ride
|
|
444
|
+
* along on a scan that will never send SNMP.
|
|
445
|
+
*/
|
|
446
|
+
const FLATTENED_SNMP_COLUMN_KEYS: Array<string> = [
|
|
447
|
+
"snmpVersion",
|
|
448
|
+
"snmpCommunityString",
|
|
449
|
+
"snmpV3SecurityLevel",
|
|
450
|
+
"snmpV3Username",
|
|
451
|
+
"snmpV3AuthProtocol",
|
|
452
|
+
"snmpV3AuthKey",
|
|
453
|
+
"snmpV3PrivProtocol",
|
|
454
|
+
"snmpV3PrivKey",
|
|
455
|
+
"snmpPort",
|
|
456
|
+
];
|
|
457
|
+
|
|
458
|
+
/*
|
|
459
|
+
* Everything the method toggle has to unsay when an operator turns SNMP off:
|
|
460
|
+
* the credential list the scan really carries, and the nine columns derived
|
|
461
|
+
* from it.
|
|
462
|
+
*/
|
|
463
|
+
const SNMP_KEYS_THE_TOGGLE_MUST_CLEAR: Array<string> = [
|
|
464
|
+
...GATED_SNMP_STEP_FIELD_KEYS,
|
|
465
|
+
...FLATTENED_SNMP_COLUMN_KEYS,
|
|
466
|
+
];
|
|
467
|
+
|
|
468
|
+
/*
|
|
469
|
+
* The form states that have to mean "this scan sends SNMP", listed once and
|
|
470
|
+
* asserted twice — at step level, where an operator sees the effect, and at
|
|
471
|
+
* field level, where it holds even in a form that declares no steps.
|
|
472
|
+
*
|
|
473
|
+
* The first group is the invariant this whole change rests on: ABSENT means
|
|
474
|
+
* SNMP. An untouched form has no value until the toggle's default is applied,
|
|
475
|
+
* a row written before the column existed has none, and a partially-selected
|
|
476
|
+
* row returns null.
|
|
477
|
+
*/
|
|
478
|
+
const VALUES_THAT_MEAN_THE_SCAN_SENDS_SNMP: Array<
|
|
479
|
+
[string, Record<string, unknown>]
|
|
480
|
+
> = [
|
|
481
|
+
["an untouched form", {}],
|
|
482
|
+
["an explicit yes", { isSnmpEnabled: true }],
|
|
483
|
+
["an absent flag", { isSnmpEnabled: undefined }],
|
|
484
|
+
[
|
|
485
|
+
"a null flag, as a partially-selected row returns it",
|
|
486
|
+
{
|
|
487
|
+
isSnmpEnabled: null,
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
/*
|
|
493
|
+
* ...and the read is `!== false` rather than `Boolean(value)` on purpose, so
|
|
494
|
+
* only a real boolean off-switch turns SNMP off. Everything here is a value
|
|
495
|
+
* this form never wrote, and for those the safe reading is the one the product
|
|
496
|
+
* has always had: ask about SNMP. Falling back the other way would let a
|
|
497
|
+
* string "false" out of a JSON body, or a 0 out of a driver that maps booleans
|
|
498
|
+
* to integers, disable SNMP discovery wholesale.
|
|
499
|
+
*/
|
|
500
|
+
const VALUES_THAT_ARE_NOT_A_BOOLEAN_FALSE: Array<
|
|
501
|
+
[string, Record<string, unknown>]
|
|
502
|
+
> = [
|
|
503
|
+
['the string "false"', { isSnmpEnabled: "false" }],
|
|
504
|
+
["a zero", { isSnmpEnabled: 0 }],
|
|
505
|
+
["an empty string", { isSnmpEnabled: "" }],
|
|
506
|
+
];
|
|
507
|
+
|
|
508
|
+
function stepNamed(id: string): CapturedFormStep {
|
|
509
|
+
const step: CapturedFormStep | undefined =
|
|
510
|
+
capturedTableProps?.formSteps?.find(
|
|
511
|
+
(formStep: CapturedFormStep): boolean => {
|
|
512
|
+
return formStep.id === id;
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
if (!step) {
|
|
517
|
+
throw new Error(`Discovery scan wizard step "${id}" not found`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return step;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/*
|
|
524
|
+
* Whether the wizard would render `key` for a form in this state. Throws
|
|
525
|
+
* rather than defaulting to `true` when the field carries no showIf at all:
|
|
526
|
+
* every field on the SNMP step must carry one — that rule IS the method gate
|
|
527
|
+
* (issue #3445) — and a field that lost it is the regression, not a passing
|
|
528
|
+
* test.
|
|
529
|
+
*/
|
|
530
|
+
function isFieldShown(key: string, values: Record<string, unknown>): boolean {
|
|
531
|
+
const field: CapturedFormField = fieldNamed(key);
|
|
532
|
+
|
|
533
|
+
if (!field.showIf) {
|
|
534
|
+
throw new Error(`Field "${key}" declares no showIf, so nothing hides it`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return field.showIf(values);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/*
|
|
541
|
+
* The errors BasicForm would raise for a form in this state, sitting on this
|
|
542
|
+
* step — computed by the very function it validates through
|
|
543
|
+
* (Common/UI/Components/Forms/Validation), over the very fields the page
|
|
544
|
+
* declared.
|
|
545
|
+
*
|
|
546
|
+
* The only thing added to those fields is `name`, derived exactly as
|
|
547
|
+
* BasicForm's own getFieldName derives it: the first key of the field
|
|
548
|
+
* selector. ModelForm hands BasicForm ModelFields and BasicForm stamps the
|
|
549
|
+
* name on before Validation ever sees them, so a captured field without one
|
|
550
|
+
* would fail Validation's "Field name is required." guard for a reason that
|
|
551
|
+
* has nothing to do with this page.
|
|
552
|
+
*
|
|
553
|
+
* Used to assert the half of issue #3445 that no assertion about `required`
|
|
554
|
+
* can reach on its own: a required field's rules DO NOT FIRE while the field
|
|
555
|
+
* is hidden, which is what lets an ICMP-only scan be submitted at all.
|
|
556
|
+
*/
|
|
557
|
+
function validationErrorsOnStep(args: {
|
|
558
|
+
stepId: string;
|
|
559
|
+
values: Record<string, unknown>;
|
|
560
|
+
}): Dictionary<string> {
|
|
561
|
+
const declared: Array<CapturedFormField> =
|
|
562
|
+
capturedTableProps?.formFields || [];
|
|
563
|
+
|
|
564
|
+
const named: Array<Record<string, unknown>> = declared.map(
|
|
565
|
+
(field: CapturedFormField): Record<string, unknown> => {
|
|
566
|
+
return { name: fieldKeyOf(field), ...field };
|
|
567
|
+
},
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
return Validation.validate<NetworkDeviceDiscoveryScan>({
|
|
571
|
+
formFields: named as unknown as Fields<NetworkDeviceDiscoveryScan>,
|
|
572
|
+
values: args.values as unknown as FormValues<NetworkDeviceDiscoveryScan>,
|
|
573
|
+
onValidate: undefined,
|
|
574
|
+
currentFormStepId: args.stepId,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/*
|
|
579
|
+
* Drives the method toggle's onChange the way FormField does — with the new
|
|
580
|
+
* value, the values as they stand, and the setter it is expected to call — and
|
|
581
|
+
* hands back the setter so a test can assert it was NOT called just as easily
|
|
582
|
+
* as it can read what it was called with.
|
|
583
|
+
*/
|
|
584
|
+
function toggleScanMethodTo(
|
|
585
|
+
value: boolean,
|
|
586
|
+
currentFormValues: Record<string, unknown>,
|
|
587
|
+
): jest.Mock<any, any> {
|
|
588
|
+
const field: CapturedFormField = fieldNamed("isSnmpEnabled");
|
|
589
|
+
|
|
590
|
+
if (!field.onChange) {
|
|
591
|
+
throw new Error(
|
|
592
|
+
"The scan method toggle rewrites nothing when it is switched",
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const setNewFormValues: jest.Mock<any, any> = jest.fn() as jest.Mock<
|
|
597
|
+
any,
|
|
598
|
+
any
|
|
599
|
+
>;
|
|
600
|
+
|
|
601
|
+
field.onChange(value, currentFormValues, setNewFormValues);
|
|
602
|
+
|
|
603
|
+
return setNewFormValues;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function valuesAfterTurningSnmpOff(
|
|
607
|
+
currentFormValues: Record<string, unknown>,
|
|
608
|
+
): Record<string, unknown> {
|
|
609
|
+
const setNewFormValues: jest.Mock<any, any> = toggleScanMethodTo(
|
|
610
|
+
false,
|
|
611
|
+
currentFormValues,
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
expect(setNewFormValues).toHaveBeenCalledTimes(1);
|
|
615
|
+
|
|
616
|
+
return setNewFormValues.mock.calls[0][0] as Record<string, unknown>;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/*
|
|
620
|
+
* What the Scan Target field says underneath itself for a given target, or
|
|
621
|
+
* undefined when it says nothing. Rendered rather than inspected for the same
|
|
622
|
+
* reason renderScanCell is: the thousands separator and the singular "1
|
|
623
|
+
* address" are the product here, and reading them off the element tree would
|
|
624
|
+
* pin the markup instead of the sentence.
|
|
625
|
+
*/
|
|
626
|
+
function renderTargetSizeHint(target: unknown): string | undefined {
|
|
627
|
+
const field: CapturedFormField = fieldNamed("cidr");
|
|
628
|
+
|
|
629
|
+
if (!field.getFooterElement) {
|
|
630
|
+
throw new Error("The Scan Target field renders no footer");
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const element: React.ReactElement | undefined = field.getFooterElement({
|
|
634
|
+
cidr: target,
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
if (!element) {
|
|
638
|
+
return undefined;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const { container } = render(element);
|
|
642
|
+
|
|
643
|
+
return container.textContent || "";
|
|
644
|
+
}
|
|
645
|
+
|
|
187
646
|
async function renderPage(): Promise<void> {
|
|
188
647
|
const project: Project = new Project();
|
|
189
648
|
project.id = PROJECT_ID;
|
|
@@ -435,6 +894,198 @@ describe("Rescan Interval is validated on the Schedule step", () => {
|
|
|
435
894
|
});
|
|
436
895
|
});
|
|
437
896
|
|
|
897
|
+
/*
|
|
898
|
+
* The SNMP step (OneUptime issue #3458).
|
|
899
|
+
*
|
|
900
|
+
* It used to spread the nine flat fields of getSnmpConfigFormFields, which
|
|
901
|
+
* gave a scan exactly ONE credential set. Real subnets are not shaped that
|
|
902
|
+
* way — access switches on v2c with one community, the core on v3, printers on
|
|
903
|
+
* the factory default — so such a scan silently missed every device speaking
|
|
904
|
+
* anything else, and the only workaround was one scan per credential.
|
|
905
|
+
*
|
|
906
|
+
* A repeated block cannot be expressed as Fields (a Field is one value at one
|
|
907
|
+
* key), so those nine became ONE CustomComponent field bound to the
|
|
908
|
+
* `snmpConfigs` column, rendering SnmpConfigListEditor. That is a lot of
|
|
909
|
+
* configuration to get subtly wrong in ways nothing else would notice:
|
|
910
|
+
*
|
|
911
|
+
* - keep the key but lose `fieldType`, and the operator gets a plain text
|
|
912
|
+
* box over a jsonb column, which types cleanly and saves a string;
|
|
913
|
+
* - keep both but lose `required`, and a scan can be created carrying no
|
|
914
|
+
* credentials at all;
|
|
915
|
+
* - keep all three but move the field off the snmp step, and the editor's
|
|
916
|
+
* own error message renders under the Schedule step, two clicks away from
|
|
917
|
+
* the cards that caused it — the exact failure shape of issue #3377, which
|
|
918
|
+
* this file exists to keep closed.
|
|
919
|
+
*/
|
|
920
|
+
describe("SNMP credentials are collected as one list, on the SNMP Credentials step", () => {
|
|
921
|
+
beforeEach(() => {
|
|
922
|
+
capturedTableProps = null;
|
|
923
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
924
|
+
jest.spyOn(ProbeUtil, "getAllProbes").mockResolvedValue([] as never);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
afterEach(() => {
|
|
928
|
+
cleanup();
|
|
929
|
+
jest.restoreAllMocks();
|
|
930
|
+
capturedTableProps = null;
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
test("the step asks exactly one question, and it is the credential list", async () => {
|
|
934
|
+
await renderPage();
|
|
935
|
+
|
|
936
|
+
const onTheSnmpStep: Array<string> = (capturedTableProps?.formFields || [])
|
|
937
|
+
.filter((field: CapturedFormField): boolean => {
|
|
938
|
+
return field.stepId === STEP_SNMP;
|
|
939
|
+
})
|
|
940
|
+
.map(fieldKeyOf);
|
|
941
|
+
|
|
942
|
+
expect(onTheSnmpStep).toEqual(["snmpConfigs"]);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
test("it is a repeated editor rather than an input, bound to the scan's own column", async () => {
|
|
946
|
+
await renderPage();
|
|
947
|
+
|
|
948
|
+
const field: CapturedFormField = fieldNamed("snmpConfigs");
|
|
949
|
+
|
|
950
|
+
expect(field.fieldType).toBe(FormFieldSchemaType.CustomComponent);
|
|
951
|
+
expect(typeof field.getCustomElement).toBe("function");
|
|
952
|
+
|
|
953
|
+
/*
|
|
954
|
+
* And the key is a real column on the model, not a name invented for the
|
|
955
|
+
* form. A CustomComponent field writes its value straight into the model
|
|
956
|
+
* payload under this key, so a typo here posts a property the API drops on
|
|
957
|
+
* the floor — the cards are edited, the form saves, and the scan sweeps
|
|
958
|
+
* with the credentials it had before.
|
|
959
|
+
*/
|
|
960
|
+
expect(Object.keys(field.field || {})).toEqual(["snmpConfigs"]);
|
|
961
|
+
expect(new NetworkDeviceDiscoveryScan().hasColumn("snmpConfigs")).toBe(
|
|
962
|
+
true,
|
|
963
|
+
);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
/*
|
|
967
|
+
* Required is not belt-and-braces here. The editor seeds one card and
|
|
968
|
+
* refuses to delete the last, so the only way to an empty list is a value
|
|
969
|
+
* that reaches the form some other way — but an empty list is a scan with no
|
|
970
|
+
* credentials to try, which runs to completion and reports a confident zero.
|
|
971
|
+
*/
|
|
972
|
+
test("the list is required, so a scan cannot be created with nothing to try", async () => {
|
|
973
|
+
await renderPage();
|
|
974
|
+
|
|
975
|
+
expect(fieldNamed("snmpConfigs").required).toBe(true);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
/*
|
|
979
|
+
* The nine flat fields are not merely unused, they are ABSENT. They still
|
|
980
|
+
* exist as columns — the server mirrors the first config onto them so a
|
|
981
|
+
* probe a version behind keeps working — but they are derived now. A leftover
|
|
982
|
+
* `snmpCommunityString` box beside the list would be a box whose value the
|
|
983
|
+
* very next save overwrites from the list, silently.
|
|
984
|
+
*/
|
|
985
|
+
test("the nine flat SNMP fields the list replaced are gone from the wizard", async () => {
|
|
986
|
+
await renderPage();
|
|
987
|
+
|
|
988
|
+
const keys: Array<string> = (capturedTableProps?.formFields || []).map(
|
|
989
|
+
fieldKeyOf,
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
for (const key of [
|
|
993
|
+
"snmpVersion",
|
|
994
|
+
"snmpCommunityString",
|
|
995
|
+
"snmpPort",
|
|
996
|
+
"snmpV3SecurityLevel",
|
|
997
|
+
"snmpV3Username",
|
|
998
|
+
"snmpV3AuthProtocol",
|
|
999
|
+
"snmpV3AuthKey",
|
|
1000
|
+
"snmpV3PrivProtocol",
|
|
1001
|
+
"snmpV3PrivKey",
|
|
1002
|
+
]) {
|
|
1003
|
+
expect(keys).not.toContain(key);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
/*
|
|
1008
|
+
* The gate itself, written the way this file writes the cidr and interval
|
|
1009
|
+
* gates: the rule lives on the field, the field lives on the step, and the
|
|
1010
|
+
* rule really fires — which together are what stop "Next".
|
|
1011
|
+
*
|
|
1012
|
+
* BasicForm validates only the fields belonging to the step being submitted
|
|
1013
|
+
* (the currentFormStepId guard in Common/UI/Components/Forms/Validation), so
|
|
1014
|
+
* a validator that returns a message from a field on THIS step is exactly
|
|
1015
|
+
* what refuses to advance past it. A list that cannot be stored must not be
|
|
1016
|
+
* allowed to walk to the Schedule step and fail on the final submit, as one
|
|
1017
|
+
* combined banner quoting cards the operator can no longer see.
|
|
1018
|
+
*/
|
|
1019
|
+
test("an unstorable list is refused on this step rather than at the end", async () => {
|
|
1020
|
+
await renderPage();
|
|
1021
|
+
|
|
1022
|
+
const field: CapturedFormField = fieldNamed("snmpConfigs");
|
|
1023
|
+
|
|
1024
|
+
expect(field.stepId).toBe(STEP_SNMP);
|
|
1025
|
+
expect(typeof field.customValidation).toBe("function");
|
|
1026
|
+
|
|
1027
|
+
/*
|
|
1028
|
+
* Every message is the one SnmpScanConfigUtil returns, not a second
|
|
1029
|
+
* sentence written for the form — the server validates the write with that
|
|
1030
|
+
* same function, so a list the form accepts is a list the API accepts.
|
|
1031
|
+
*/
|
|
1032
|
+
for (const invalid of [
|
|
1033
|
+
// Every card deleted, which the editor prevents but an API caller can do.
|
|
1034
|
+
[],
|
|
1035
|
+
// More configs than the sweep's time budget allows.
|
|
1036
|
+
new Array(MAX_SNMP_CONFIGS_PER_SCAN + 1).fill({ snmpVersion: "V2c" }),
|
|
1037
|
+
// v3 with nobody to authenticate as.
|
|
1038
|
+
[{ snmpVersion: "V3" }],
|
|
1039
|
+
// Two cards that would be indistinguishable to the import path.
|
|
1040
|
+
[
|
|
1041
|
+
{ id: "same", snmpVersion: "V2c" },
|
|
1042
|
+
{ id: "same", snmpVersion: "V1" },
|
|
1043
|
+
],
|
|
1044
|
+
]) {
|
|
1045
|
+
const message: string | null = validate("snmpConfigs", {
|
|
1046
|
+
snmpConfigs: invalid,
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
expect(message).not.toBeNull();
|
|
1050
|
+
expect(message).toBe(SnmpScanConfigUtil.getValidationError(invalid));
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
test("a real mixed-subnet list walks straight through", async () => {
|
|
1055
|
+
await renderPage();
|
|
1056
|
+
|
|
1057
|
+
expect(
|
|
1058
|
+
validate("snmpConfigs", { snmpConfigs: MIXED_SUBNET_SNMP_CONFIGS }),
|
|
1059
|
+
).toBeNull();
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
/*
|
|
1063
|
+
* An untouched form says nothing. SnmpConfigListEditor reports its seeded
|
|
1064
|
+
* card through onChange in a mount effect, so this is the window before that
|
|
1065
|
+
* effect runs — and FormField hands a CustomComponent `values[name] || ""`,
|
|
1066
|
+
* so the value seen here really can be an empty string rather than a list.
|
|
1067
|
+
* `required` owns emptiness, exactly as it does for every other field on
|
|
1068
|
+
* this page.
|
|
1069
|
+
*/
|
|
1070
|
+
test("an untouched step is left to the field's own required rule", async () => {
|
|
1071
|
+
await renderPage();
|
|
1072
|
+
|
|
1073
|
+
for (const value of [undefined, null, ""]) {
|
|
1074
|
+
expect(validate("snmpConfigs", { snmpConfigs: value })).toBeNull();
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
expect(validate("snmpConfigs", {})).toBeNull();
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
/*
|
|
1082
|
+
* The port rule survived the move to a list — it is now enforced PER CONFIG,
|
|
1083
|
+
* by SnmpScanConfigUtil.getPortValidationError, reached through the
|
|
1084
|
+
* snmpConfigs field's own validator. These are the same values the old
|
|
1085
|
+
* `snmpPort` field's tests asserted, driven through the field that replaced
|
|
1086
|
+
* it, because the rule they pin is the one that matters: a port the probe
|
|
1087
|
+
* cannot dial must be refused on the step it was typed on, not by the INSERT.
|
|
1088
|
+
*/
|
|
438
1089
|
describe("SNMP Port is bounded on the SNMP Credentials step", () => {
|
|
439
1090
|
beforeEach(() => {
|
|
440
1091
|
capturedTableProps = null;
|
|
@@ -451,7 +1102,7 @@ describe("SNMP Port is bounded on the SNMP Credentials step", () => {
|
|
|
451
1102
|
test("carries a validator on the SNMP step", async () => {
|
|
452
1103
|
await renderPage();
|
|
453
1104
|
|
|
454
|
-
const field: CapturedFormField = fieldNamed("
|
|
1105
|
+
const field: CapturedFormField = fieldNamed("snmpConfigs");
|
|
455
1106
|
|
|
456
1107
|
expect(field.stepId).toBe(STEP_SNMP);
|
|
457
1108
|
expect(typeof field.customValidation).toBe("function");
|
|
@@ -460,19 +1111,71 @@ describe("SNMP Port is bounded on the SNMP Credentials step", () => {
|
|
|
460
1111
|
test.each([
|
|
461
1112
|
["zero", 0],
|
|
462
1113
|
["a port past the top of the range", 65536],
|
|
463
|
-
|
|
1114
|
+
/*
|
|
1115
|
+
* As TEXT, which is how it arrives: the port is typed into a Number input
|
|
1116
|
+
* and posted as a string, so a value that parseInt would happily round
|
|
1117
|
+
* down to 161 has to be caught as written.
|
|
1118
|
+
*/
|
|
1119
|
+
["a fractional port", "161.5"],
|
|
464
1120
|
])("%s is rejected right there", async (_label: string, value: unknown) => {
|
|
465
1121
|
await renderPage();
|
|
466
1122
|
|
|
467
|
-
|
|
1123
|
+
const message: string | null = validate("snmpConfigs", {
|
|
1124
|
+
snmpConfigs: oneSnmpConfigWithPort(value),
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
expect(message).not.toBeNull();
|
|
1128
|
+
/*
|
|
1129
|
+
* Word for word the server's own sentence, and it names WHICH card is
|
|
1130
|
+
* wrong — with several credential sets on screen, "the SNMP port must be
|
|
1131
|
+
* between 1 and 65535" on its own is not an actionable message.
|
|
1132
|
+
*/
|
|
1133
|
+
expect(message).toBe(
|
|
1134
|
+
SnmpScanConfigUtil.getPortValidationError(value, "SNMP config 1"),
|
|
1135
|
+
);
|
|
1136
|
+
expect(message).toContain("SNMP config 1");
|
|
468
1137
|
});
|
|
469
1138
|
|
|
470
1139
|
test("the SNMP default is accepted, and an empty box says nothing", async () => {
|
|
471
1140
|
await renderPage();
|
|
472
1141
|
|
|
473
|
-
expect(
|
|
474
|
-
|
|
475
|
-
|
|
1142
|
+
expect(
|
|
1143
|
+
validate("snmpConfigs", { snmpConfigs: oneSnmpConfigWithPort(161) }),
|
|
1144
|
+
).toBeNull();
|
|
1145
|
+
expect(
|
|
1146
|
+
validate("snmpConfigs", { snmpConfigs: oneSnmpConfigWithPort("") }),
|
|
1147
|
+
).toBeNull();
|
|
1148
|
+
/*
|
|
1149
|
+
* Absent entirely — the card the editor seeds. The column defaults to 161,
|
|
1150
|
+
* so a config that never mentions a port is the normal case rather than an
|
|
1151
|
+
* incomplete one.
|
|
1152
|
+
*/
|
|
1153
|
+
expect(
|
|
1154
|
+
validate("snmpConfigs", {
|
|
1155
|
+
snmpConfigs: [{ id: "config-1", snmpVersion: "V2c" }],
|
|
1156
|
+
}),
|
|
1157
|
+
).toBeNull();
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
/*
|
|
1161
|
+
* And the rule reaches past the first card. The list is validated entry by
|
|
1162
|
+
* entry, so a bad port on the fourth credential set is refused as loudly as
|
|
1163
|
+
* one on the first — and says "SNMP config 4", which is the only way to find
|
|
1164
|
+
* it among five identical-looking cards.
|
|
1165
|
+
*/
|
|
1166
|
+
test("a bad port on a later config is named by its position", async () => {
|
|
1167
|
+
await renderPage();
|
|
1168
|
+
|
|
1169
|
+
const message: string | null = validate("snmpConfigs", {
|
|
1170
|
+
snmpConfigs: [
|
|
1171
|
+
{ id: "config-1", snmpVersion: "V2c" },
|
|
1172
|
+
{ id: "config-2", snmpVersion: "V2c" },
|
|
1173
|
+
{ id: "config-3", snmpVersion: "V2c" },
|
|
1174
|
+
{ id: "config-4", snmpVersion: "V2c", snmpPort: 70000 },
|
|
1175
|
+
],
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
expect(message).toContain("SNMP config 4");
|
|
476
1179
|
});
|
|
477
1180
|
});
|
|
478
1181
|
|
|
@@ -641,11 +1344,14 @@ describe("Name identifies the scan in the list", () => {
|
|
|
641
1344
|
});
|
|
642
1345
|
|
|
643
1346
|
/*
|
|
644
|
-
* A
|
|
645
|
-
*
|
|
646
|
-
*
|
|
1347
|
+
* A scan that cannot be corrected is worse than no scan: a name on the wrong
|
|
1348
|
+
* range misleads everyone who reads the list afterwards, a typo'd subnet
|
|
1349
|
+
* sweeps nothing, and a rejected community string finds nothing — and the
|
|
1350
|
+
* only way to fix any of them used to be deleting the scan and its results
|
|
1351
|
+
* and starting again (OneUptime issue #3444). One dialog fixes all of them;
|
|
1352
|
+
* it replaced a Rename dialog that could only fix the first.
|
|
647
1353
|
*/
|
|
648
|
-
test("a scan can be
|
|
1354
|
+
test("a scan can be edited after it was created", async () => {
|
|
649
1355
|
await renderPage();
|
|
650
1356
|
|
|
651
1357
|
const actions: Array<string> = (
|
|
@@ -654,22 +1360,24 @@ describe("Name identifies the scan in the list", () => {
|
|
|
654
1360
|
return button.title || "";
|
|
655
1361
|
});
|
|
656
1362
|
|
|
657
|
-
expect(actions).toContain("
|
|
1363
|
+
expect(actions).toContain("Edit");
|
|
1364
|
+
// Superseded: everything it offered is the first field of Edit.
|
|
1365
|
+
expect(actions).not.toContain("Rename");
|
|
658
1366
|
});
|
|
659
1367
|
|
|
660
1368
|
/*
|
|
661
1369
|
* The table is not editable, so this button is the page's only edit
|
|
662
1370
|
* affordance and nothing else gates it. Offered to a reader, it would open a
|
|
663
|
-
* dialog whose
|
|
664
|
-
*
|
|
1371
|
+
* dialog whose fields ModelForm has already dropped for want of the update
|
|
1372
|
+
* permission — boxes that cannot be saved and do not say why.
|
|
665
1373
|
*/
|
|
666
|
-
test("
|
|
1374
|
+
test("editing is offered only to someone who could save it", async () => {
|
|
667
1375
|
await renderPage();
|
|
668
1376
|
|
|
669
|
-
const
|
|
1377
|
+
const edit: CapturedActionButton | undefined = (
|
|
670
1378
|
capturedTableProps?.actionButtons || []
|
|
671
1379
|
).find((button: CapturedActionButton): boolean => {
|
|
672
|
-
return button.title === "
|
|
1380
|
+
return button.title === "Edit";
|
|
673
1381
|
});
|
|
674
1382
|
|
|
675
1383
|
const scan: NetworkDeviceDiscoveryScan = {
|
|
@@ -680,12 +1388,1194 @@ describe("Name identifies the scan in the list", () => {
|
|
|
680
1388
|
.spyOn(PermissionUtil, "getAllPermissions")
|
|
681
1389
|
.mockReturnValue([Permission.Viewer]);
|
|
682
1390
|
|
|
683
|
-
expect(
|
|
1391
|
+
expect(edit?.isVisible?.(scan)).toBe(false);
|
|
684
1392
|
|
|
685
1393
|
jest
|
|
686
1394
|
.spyOn(PermissionUtil, "getAllPermissions")
|
|
687
1395
|
.mockReturnValue([Permission.ProjectAdmin]);
|
|
688
1396
|
|
|
689
|
-
expect(
|
|
1397
|
+
expect(edit?.isVisible?.(scan)).toBe(true);
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
test("a one-time scan reads as one-time", async () => {
|
|
1401
|
+
await renderPage();
|
|
1402
|
+
|
|
1403
|
+
expect(renderRecurrenceCell({ isRecurring: false })).toContain("One-time");
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
test("a scheduled recurring scan says when it next runs", async () => {
|
|
1407
|
+
await renderPage();
|
|
1408
|
+
|
|
1409
|
+
const cell: string = renderRecurrenceCell({
|
|
1410
|
+
isRecurring: true,
|
|
1411
|
+
rescanIntervalInMinutes: 60,
|
|
1412
|
+
status: "Completed",
|
|
1413
|
+
nextScanAt: new Date(Date.now() + 30 * 60000),
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
expect(cell).toContain("Every 60 min");
|
|
1417
|
+
expect(cell).toContain("Next scan");
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
/*
|
|
1421
|
+
* The half-truth this column used to tell. A recurring scan whose
|
|
1422
|
+
* nextScanAt is NULL is never re-queued — the worker's predicate is
|
|
1423
|
+
* `nextScanAt <= now`, and that is UNKNOWN for NULL — but the cell printed
|
|
1424
|
+
* the cadence and simply omitted the second line, so a scan that would never
|
|
1425
|
+
* run again was indistinguishable from one due in an hour.
|
|
1426
|
+
*/
|
|
1427
|
+
test("a recurring scan with nothing scheduled says so rather than showing a bare cadence", async () => {
|
|
1428
|
+
await renderPage();
|
|
1429
|
+
|
|
1430
|
+
const cell: string = renderRecurrenceCell({
|
|
1431
|
+
isRecurring: true,
|
|
1432
|
+
rescanIntervalInMinutes: 60,
|
|
1433
|
+
status: "Completed",
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
expect(cell).toContain("Every 60 min");
|
|
1437
|
+
expect(cell).toContain("No next scan is scheduled");
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
/*
|
|
1441
|
+
* ...but that is only alarming once the scan has finished. A run that is
|
|
1442
|
+
* queued or under way has its next one scheduled when it reports.
|
|
1443
|
+
*/
|
|
1444
|
+
test("a recurring scan mid-run explains that the next one waits for it", async () => {
|
|
1445
|
+
await renderPage();
|
|
1446
|
+
|
|
1447
|
+
for (const status of ["Pending", "In Progress"]) {
|
|
1448
|
+
const cell: string = renderRecurrenceCell({
|
|
1449
|
+
isRecurring: true,
|
|
1450
|
+
rescanIntervalInMinutes: 60,
|
|
1451
|
+
status: status,
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
expect(cell).toContain("when this run finishes");
|
|
1455
|
+
expect(cell).not.toContain("No next scan is scheduled");
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
/*
|
|
1460
|
+
* The complaint in the issue was that a finished scan could not be given a
|
|
1461
|
+
* schedule. Nothing may hide Edit behind a status: a Completed scan is the
|
|
1462
|
+
* one people most need to change, and an In Progress one abandons its sweep
|
|
1463
|
+
* and starts again with the new settings rather than refusing.
|
|
1464
|
+
*/
|
|
1465
|
+
test("editing is offered whatever state the scan is in", async () => {
|
|
1466
|
+
await renderPage();
|
|
1467
|
+
|
|
1468
|
+
const edit: CapturedActionButton | undefined = (
|
|
1469
|
+
capturedTableProps?.actionButtons || []
|
|
1470
|
+
).find((button: CapturedActionButton): boolean => {
|
|
1471
|
+
return button.title === "Edit";
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
jest
|
|
1475
|
+
.spyOn(PermissionUtil, "getAllPermissions")
|
|
1476
|
+
.mockReturnValue([Permission.ProjectAdmin]);
|
|
1477
|
+
|
|
1478
|
+
for (const status of ["Pending", "In Progress", "Completed", "Failed"]) {
|
|
1479
|
+
const scan: NetworkDeviceDiscoveryScan = {
|
|
1480
|
+
cidr: "10.0.0.0/24",
|
|
1481
|
+
status: status,
|
|
1482
|
+
} as NetworkDeviceDiscoveryScan;
|
|
1483
|
+
|
|
1484
|
+
expect(edit?.isVisible?.(scan)).toBe(true);
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
/*
|
|
1490
|
+
* Issue #3446: "Scan Target column missing from the Discovery Scans table, but
|
|
1491
|
+
* present as a filter option".
|
|
1492
|
+
*
|
|
1493
|
+
* THE ACTUAL HISTORY, because it is easy to get wrong and it decides the shape
|
|
1494
|
+
* of the fix. Up to 12.0.22 this table had a standalone `{ cidr }` "Scan
|
|
1495
|
+
* Target" column and no filters at all. The commit that added scan names
|
|
1496
|
+
* (#3391) folded the target into the composite "Scan" cell AND introduced the
|
|
1497
|
+
* Name / Scan Target filters, in one change — so the filter for the target
|
|
1498
|
+
* appeared in the same release the column for it disappeared. The data was
|
|
1499
|
+
* never lost (the Scan cell shows the target on every row, and 12.0.24 is
|
|
1500
|
+
* byte-identical to this file's page in that respect); what was lost was the
|
|
1501
|
+
* target as a COLUMN: a header saying what the filter says, something to sort
|
|
1502
|
+
* on, and its own CSV heading.
|
|
1503
|
+
*
|
|
1504
|
+
* So the column comes back, switched off. That keeps #3391's default layout
|
|
1505
|
+
* exactly as designed — putting it back visible would print the target twice
|
|
1506
|
+
* in every named row — while making it one tick away in Customize Columns.
|
|
1507
|
+
*
|
|
1508
|
+
* WHAT THIS BLOCK GUARDS, in rough order of how expensive the mistake is:
|
|
1509
|
+
*
|
|
1510
|
+
* - The column silently widening the default table (a dropped
|
|
1511
|
+
* `isHiddenByDefault`), which is the #3391 regression wearing a new hat.
|
|
1512
|
+
* - The recycled id. `getColumnBaseId` derives a column's identity from its
|
|
1513
|
+
* first declared field, which for `{ cidr: true }` is "cidr" — the exact
|
|
1514
|
+
* id the 12.0.22 column had, on this same userPreferencesKey. Stored
|
|
1515
|
+
* layouts live in localStorage, are sanitized on read but never rewritten,
|
|
1516
|
+
* and an id present in `order` OVERRIDES isHiddenByDefault. Ship the
|
|
1517
|
+
* derived id and every operator who arranged this table a few releases ago
|
|
1518
|
+
* silently gets the column back, on, duplicating the Scan cell. The
|
|
1519
|
+
* explicit `id` is the whole fix for that, and it looks like a redundant
|
|
1520
|
+
* line nobody would miss deleting — hence a test.
|
|
1521
|
+
* - The picker being switched off table-wide, which would make a
|
|
1522
|
+
* hidden-by-default column unreachable forever with no other symptom.
|
|
1523
|
+
*
|
|
1524
|
+
* These run against the columns the page really declared (captured through the
|
|
1525
|
+
* ModelTable mock) and through the real ColumnPreference / SelectFromColumns
|
|
1526
|
+
* helpers, so they follow the product rather than restating it.
|
|
1527
|
+
*/
|
|
1528
|
+
describe("Scan Target is reachable as a column, not only as a filter", () => {
|
|
1529
|
+
beforeEach(() => {
|
|
1530
|
+
capturedTableProps = null;
|
|
1531
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
1532
|
+
|
|
1533
|
+
const probe: Probe = new Probe();
|
|
1534
|
+
probe.id = new ObjectID("22222222-2222-4222-8222-222222222222");
|
|
1535
|
+
probe.name = "Datacenter Probe";
|
|
1536
|
+
|
|
1537
|
+
jest.spyOn(ProbeUtil, "getAllProbes").mockResolvedValue([probe] as never);
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
afterEach(() => {
|
|
1541
|
+
cleanup();
|
|
1542
|
+
jest.restoreAllMocks();
|
|
1543
|
+
capturedTableProps = null;
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
test("the table declares a Scan Target column keyed on the target", async () => {
|
|
1547
|
+
await renderPage();
|
|
1548
|
+
|
|
1549
|
+
const column: CapturedColumn = columnByTitle("Scan Target");
|
|
1550
|
+
|
|
1551
|
+
expect(Object.keys(column.field || {})).toEqual(["cidr"]);
|
|
1552
|
+
expect(column.type).toBe(FieldType.Text);
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
/*
|
|
1556
|
+
* The title is not decoration here: it is the entire complaint in #3446.
|
|
1557
|
+
* The filter dialog says "Scan Target", so the column has to say it too, and
|
|
1558
|
+
* both have to mean the same field or the pair is a different lie.
|
|
1559
|
+
*/
|
|
1560
|
+
test("what you can filter by, you can now also show", async () => {
|
|
1561
|
+
await renderPage();
|
|
1562
|
+
|
|
1563
|
+
const filter: CapturedFilter | undefined = (
|
|
1564
|
+
capturedTableProps?.filters || []
|
|
1565
|
+
).find((tableFilter: CapturedFilter): boolean => {
|
|
1566
|
+
return tableFilter.title === "Scan Target";
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
expect(Object.keys(filter?.field || {})).toEqual(["cidr"]);
|
|
1570
|
+
expect(Object.keys(columnByTitle("Scan Target").field || {})).toEqual(
|
|
1571
|
+
Object.keys(filter?.field || {}),
|
|
1572
|
+
);
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
/*
|
|
1576
|
+
* The load-bearing one. #3391 decided this table shows ONE identity column,
|
|
1577
|
+
* and a Scan Target column that arrives switched on quietly reverses that
|
|
1578
|
+
* for every operator at once — the target rendered twice on every named row.
|
|
1579
|
+
* Asserted through applyColumnPreference (what BaseModelTable renders
|
|
1580
|
+
* through) rather than by reading the flag, so it fails if the rule that
|
|
1581
|
+
* honours the flag changes too.
|
|
1582
|
+
*/
|
|
1583
|
+
test("it ships switched off, so the default layout is unchanged", async () => {
|
|
1584
|
+
await renderPage();
|
|
1585
|
+
|
|
1586
|
+
expect(columnByTitle("Scan Target").isHiddenByDefault).toBe(true);
|
|
1587
|
+
|
|
1588
|
+
expect(visibleColumnTitles(null)).toEqual([
|
|
1589
|
+
"Scan",
|
|
1590
|
+
"Probe",
|
|
1591
|
+
"Status",
|
|
1592
|
+
"Responded Hosts",
|
|
1593
|
+
"Recurrence",
|
|
1594
|
+
"Started",
|
|
1595
|
+
]);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
test("a viewer who switches it on gets it, between Scan and Probe", async () => {
|
|
1599
|
+
await renderPage();
|
|
1600
|
+
|
|
1601
|
+
const preference: ColumnPreference = {
|
|
1602
|
+
order: ["scanTarget"],
|
|
1603
|
+
hidden: [],
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
expect(visibleColumnTitles(preference)).toEqual([
|
|
1607
|
+
"Scan",
|
|
1608
|
+
"Scan Target",
|
|
1609
|
+
"Probe",
|
|
1610
|
+
"Status",
|
|
1611
|
+
"Responded Hosts",
|
|
1612
|
+
"Recurrence",
|
|
1613
|
+
"Started",
|
|
1614
|
+
]);
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
/*
|
|
1618
|
+
* The regression test for the recycled id, written as the upgrade it
|
|
1619
|
+
* describes: a layout saved on 12.0.22, when this table's first column was
|
|
1620
|
+
* `{ field: { cidr: true }, title: "Scan Target" }` and therefore had the
|
|
1621
|
+
* derived id "cidr". That entry is still in the viewer's localStorage today.
|
|
1622
|
+
* With an explicit id it stays unknown and keeps being dropped; reuse the
|
|
1623
|
+
* derived one and this viewer is upgraded straight into the duplicated
|
|
1624
|
+
* layout, without ever asking for it.
|
|
1625
|
+
*/
|
|
1626
|
+
test("a layout saved before 12.0.23 does not switch it back on", async () => {
|
|
1627
|
+
await renderPage();
|
|
1628
|
+
|
|
1629
|
+
const staleLayout: ColumnPreference = {
|
|
1630
|
+
order: [
|
|
1631
|
+
"cidr",
|
|
1632
|
+
"probe",
|
|
1633
|
+
"status",
|
|
1634
|
+
"respondedHostCount",
|
|
1635
|
+
"isRecurring",
|
|
1636
|
+
"createdAt",
|
|
1637
|
+
],
|
|
1638
|
+
hidden: [],
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
expect(visibleColumnTitles(staleLayout)).not.toContain("Scan Target");
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
test("the column ids are unique, and the target's is not the recycled one", async () => {
|
|
1645
|
+
await renderPage();
|
|
1646
|
+
|
|
1647
|
+
const ids: Array<string> =
|
|
1648
|
+
getColumnIds<NetworkDeviceDiscoveryScan>(declaredColumns());
|
|
1649
|
+
|
|
1650
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
1651
|
+
expect(ids).not.toContain("cidr");
|
|
1652
|
+
expect(ids).toContain("scanTarget");
|
|
1653
|
+
|
|
1654
|
+
/*
|
|
1655
|
+
* The Scan column's id has to stay "name". It declares `{ name, cidr }`,
|
|
1656
|
+
* and an id derives from the FIRST key — so reordering that pair to
|
|
1657
|
+
* `{ cidr, name }` would collide both columns onto "cidr", trigger
|
|
1658
|
+
* title-slug disambiguation, and invalidate every stored layout for this
|
|
1659
|
+
* table at once.
|
|
1660
|
+
*/
|
|
1661
|
+
expect(ids[0]).toBe("name");
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
/*
|
|
1665
|
+
* A column can be switched off in two very different ways. isHiddenByDefault
|
|
1666
|
+
* means "off, but in the picker"; isNotCustomizable means "not the viewer's
|
|
1667
|
+
* to touch" and makes isHiddenByDefault a no-op. Setting both would produce a
|
|
1668
|
+
* column that lives in neither the table nor the picker — invisible, with
|
|
1669
|
+
* nothing to click.
|
|
1670
|
+
*/
|
|
1671
|
+
test("it is offered in the picker rather than pinned out of it", async () => {
|
|
1672
|
+
await renderPage();
|
|
1673
|
+
|
|
1674
|
+
const column: CapturedColumn = columnByTitle("Scan Target");
|
|
1675
|
+
|
|
1676
|
+
expect(column.isNotCustomizable).toBeFalsy();
|
|
1677
|
+
// Nothing put it there for the viewer to take away again.
|
|
1678
|
+
expect(column.isRemovable).toBeFalsy();
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
/*
|
|
1682
|
+
* BaseModelTable gates the whole picker on `userPreferencesKey` being set
|
|
1683
|
+
* and `disableColumnCustomization` being absent. Neither is referenced
|
|
1684
|
+
* anywhere else, so losing one turns every hidden column on this table into
|
|
1685
|
+
* dead configuration and no other test would notice.
|
|
1686
|
+
*/
|
|
1687
|
+
test("the picker this column depends on is switched on for the table", async () => {
|
|
1688
|
+
await renderPage();
|
|
1689
|
+
|
|
1690
|
+
expect(capturedTableProps?.userPreferencesKey).toBe(
|
|
1691
|
+
"network-device-discovery-scans-table",
|
|
1692
|
+
);
|
|
1693
|
+
expect(capturedTableProps?.disableColumnCustomization).toBeFalsy();
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
/*
|
|
1697
|
+
* Sorting is the second thing the column buys over the subtitle already on
|
|
1698
|
+
* screen, and it is on by default only because `cidr` is a plain text
|
|
1699
|
+
* column — BaseModelTable disables sorting for entity columns. It sorts
|
|
1700
|
+
* lexicographically, which is what every other address-shaped text column in
|
|
1701
|
+
* the product does (Endpoints "IP Address", Assignment Rules "Subnet CIDR").
|
|
1702
|
+
*/
|
|
1703
|
+
test("the target can be sorted on", async () => {
|
|
1704
|
+
await renderPage();
|
|
1705
|
+
|
|
1706
|
+
expect(columnByTitle("Scan Target").disableSort).toBeFalsy();
|
|
1707
|
+
expect(new NetworkDeviceDiscoveryScan().isEntityColumn("cidr")).toBe(false);
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
/*
|
|
1711
|
+
* `cidr` is NOT NULL so this is close to hypothetical, but a bare text cell
|
|
1712
|
+
* renders "" for a missing value and the mobile card drops the whole
|
|
1713
|
+
* labelled block when the value is empty — so the "Scan Target" label itself
|
|
1714
|
+
* would disappear, where every other cell on this table shows an em-dash.
|
|
1715
|
+
*/
|
|
1716
|
+
test("an empty target reads like every other empty cell here", async () => {
|
|
1717
|
+
await renderPage();
|
|
1718
|
+
|
|
1719
|
+
expect(columnByTitle("Scan Target").noValueMessage).toBe("—");
|
|
1720
|
+
});
|
|
1721
|
+
|
|
1722
|
+
/*
|
|
1723
|
+
* Every field any column declares has to be a real column on the model. A
|
|
1724
|
+
* typo here does not blank one cell — getSelectFromColumns throws on an
|
|
1725
|
+
* unknown PRIMARY field, which takes down the whole page.
|
|
1726
|
+
*/
|
|
1727
|
+
test("the target is actually requested from the API", async () => {
|
|
1728
|
+
await renderPage();
|
|
1729
|
+
|
|
1730
|
+
const select: Select<NetworkDeviceDiscoveryScan> =
|
|
1731
|
+
getSelectFromColumns<NetworkDeviceDiscoveryScan>({
|
|
1732
|
+
columns: declaredColumns(),
|
|
1733
|
+
model: new NetworkDeviceDiscoveryScan(),
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
expect(select.cidr).toBe(true);
|
|
1737
|
+
expect(select.name).toBe(true);
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
/*
|
|
1741
|
+
* "Scan" is a strict prefix of "Scan Target", so both surface when someone
|
|
1742
|
+
* types "scan" into the picker's search box. Duplicate titles there would
|
|
1743
|
+
* leave two identical rows and no way to tell which checkbox does what.
|
|
1744
|
+
*/
|
|
1745
|
+
test("no two columns share a title", async () => {
|
|
1746
|
+
await renderPage();
|
|
1747
|
+
|
|
1748
|
+
const titles: Array<string> = (capturedTableProps?.columns || []).map(
|
|
1749
|
+
(column: CapturedColumn): string => {
|
|
1750
|
+
return column.title || "";
|
|
1751
|
+
},
|
|
1752
|
+
);
|
|
1753
|
+
|
|
1754
|
+
expect(new Set(titles).size).toBe(titles.length);
|
|
1755
|
+
});
|
|
1756
|
+
});
|
|
1757
|
+
|
|
1758
|
+
/*
|
|
1759
|
+
* The scan's METHOD (issue #3445).
|
|
1760
|
+
*
|
|
1761
|
+
* A discovery scan has always been a ping sweep followed by an SNMP probe, and
|
|
1762
|
+
* the wizard had no way to say "just tell me what is alive in 10.20.30.0/24".
|
|
1763
|
+
* Worse, it had no way to STOP saying it: SNMP Version is a required Dropdown
|
|
1764
|
+
* on its own step, so an operator who wanted an ICMP-only sweep could not get
|
|
1765
|
+
* past step 2 — "SNMP Version is required" blocked Next on a scan that was
|
|
1766
|
+
* never going to send an SNMP packet.
|
|
1767
|
+
*
|
|
1768
|
+
* The fix does not relax that rule. It removes the QUESTION: the step filters
|
|
1769
|
+
* itself out, and BasicForm validates only the fields of the step being
|
|
1770
|
+
* submitted. Everything below pins the wiring that makes removing a step safe
|
|
1771
|
+
* — where the toggle lives, what an absent flag means, and that each SNMP
|
|
1772
|
+
* field is gated on the method as well as on its own reveal rule.
|
|
1773
|
+
*/
|
|
1774
|
+
describe("The scan method decides whether the wizard asks about SNMP", () => {
|
|
1775
|
+
beforeEach(() => {
|
|
1776
|
+
capturedTableProps = null;
|
|
1777
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
1778
|
+
jest.spyOn(ProbeUtil, "getAllProbes").mockResolvedValue([] as never);
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
afterEach(() => {
|
|
1782
|
+
cleanup();
|
|
1783
|
+
jest.restoreAllMocks();
|
|
1784
|
+
capturedTableProps = null;
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
/*
|
|
1788
|
+
* The single most important placement in this change.
|
|
1789
|
+
*
|
|
1790
|
+
* BasicForm navigates the FILTERED step array — the steps whose showIf
|
|
1791
|
+
* passed — and locates itself in it with a findIndex on currentFormStepId.
|
|
1792
|
+
* Put the toggle on the step it can remove, and switching it off deletes the
|
|
1793
|
+
* step the form believes it is standing on: findIndex returns -1, the
|
|
1794
|
+
* advance branch is skipped, and Next keeps reading "Next" while doing
|
|
1795
|
+
* nothing at all. The wizard deadlocks on the very step whose error message
|
|
1796
|
+
* this issue is named after, which is strictly worse than the bug.
|
|
1797
|
+
*/
|
|
1798
|
+
test("the method toggle sits on the scan-target step, never on the step it hides", async () => {
|
|
1799
|
+
await renderPage();
|
|
1800
|
+
|
|
1801
|
+
const field: CapturedFormField = fieldNamed("isSnmpEnabled");
|
|
1802
|
+
|
|
1803
|
+
expect(field.stepId).toBe(STEP_SCAN_TARGET);
|
|
1804
|
+
expect(field.stepId).not.toBe(STEP_SNMP);
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
/*
|
|
1808
|
+
* And nothing may hide the toggle itself. A showIf here — even one that
|
|
1809
|
+
* reads as harmless, "only once a probe is picked" — is the same deadlock
|
|
1810
|
+
* from the other side: an operator who cannot see the control cannot turn
|
|
1811
|
+
* SNMP back on, and the step stays gone.
|
|
1812
|
+
*/
|
|
1813
|
+
test("the method toggle is never hidden by anything", async () => {
|
|
1814
|
+
await renderPage();
|
|
1815
|
+
|
|
1816
|
+
expect(fieldNamed("isSnmpEnabled").showIf).toBeUndefined();
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
/*
|
|
1820
|
+
* The entire product as it stood before this change. An operator who never
|
|
1821
|
+
* touches the toggle must still get the ping + SNMP scan they have always
|
|
1822
|
+
* got, and the FIELD's default is what puts `isSnmpEnabled: true` in the
|
|
1823
|
+
* submitted body — the column default only speaks for a row the form never
|
|
1824
|
+
* mentioned the column on.
|
|
1825
|
+
*/
|
|
1826
|
+
test("the method toggle defaults to on, so an untouched wizard still creates an SNMP scan", async () => {
|
|
1827
|
+
await renderPage();
|
|
1828
|
+
|
|
1829
|
+
expect(fieldNamed("isSnmpEnabled").defaultValue).toBe(true);
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
/*
|
|
1833
|
+
* FieldLabel appends "(Optional)" to every non-required field, and "Check
|
|
1834
|
+
* SNMP on hosts that answer (Optional)" reads as though the question may be
|
|
1835
|
+
* left unanswered — a toggle is always answered, one way or the other.
|
|
1836
|
+
*/
|
|
1837
|
+
test("the method is asked as a toggle that does not read as optional", async () => {
|
|
1838
|
+
await renderPage();
|
|
1839
|
+
|
|
1840
|
+
const field: CapturedFormField = fieldNamed("isSnmpEnabled");
|
|
1841
|
+
|
|
1842
|
+
expect(field.fieldType).toBe(FormFieldSchemaType.Toggle);
|
|
1843
|
+
expect(field.required).toBe(false);
|
|
1844
|
+
expect(field.hideOptionalLabel).toBe(true);
|
|
1845
|
+
expect(field.sectionTitle).toBe("What to check");
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
/*
|
|
1849
|
+
* The section header opens with what the scan ALREADY does. Without that
|
|
1850
|
+
* sentence, "Check SNMP on hosts that answer" reads as the whole question —
|
|
1851
|
+
* an operator who turns it off has no way to know a ping sweep still happens
|
|
1852
|
+
* and still finds things, which is the entire feature #3445 adds.
|
|
1853
|
+
*/
|
|
1854
|
+
test("the section says the ping sweep happens either way, before asking about SNMP", async () => {
|
|
1855
|
+
await renderPage();
|
|
1856
|
+
|
|
1857
|
+
const sectionDescription: string =
|
|
1858
|
+
fieldNamed("isSnmpEnabled").sectionDescription || "";
|
|
1859
|
+
|
|
1860
|
+
expect(sectionDescription).toContain(
|
|
1861
|
+
"Every scan pings each address in the range to find what is alive",
|
|
1862
|
+
);
|
|
1863
|
+
/*
|
|
1864
|
+
* Name and vendor, NOT model: the sweep reads the SNMP system group
|
|
1865
|
+
* (sysName / sysDescr / sysObjectId), and a device's model arrives later
|
|
1866
|
+
* from the ENTITY-MIB poll. Promising it here would have the wizard
|
|
1867
|
+
* describe something the scan does not do.
|
|
1868
|
+
*/
|
|
1869
|
+
expect(sectionDescription).toContain("name and vendor");
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
/*
|
|
1873
|
+
* Where the toggle sits in the field list, which decides two separate
|
|
1874
|
+
* things.
|
|
1875
|
+
*
|
|
1876
|
+
* BasicForm renders a step's fields in declaration order and `sectionTitle`
|
|
1877
|
+
* draws a header above the field carrying it, so everything from there down
|
|
1878
|
+
* reads as part of that section: a toggle declared before Probe would put
|
|
1879
|
+
* the probe picker under a "What to check" heading it has nothing to do
|
|
1880
|
+
* with. And the question has to be asked before the fields whose existence
|
|
1881
|
+
* it decides — an operator filling the form top to bottom answers "do you
|
|
1882
|
+
* want SNMP?" and only then is asked for credentials.
|
|
1883
|
+
*/
|
|
1884
|
+
test("the toggle is the last question on its step and precedes every field it gates", async () => {
|
|
1885
|
+
await renderPage();
|
|
1886
|
+
|
|
1887
|
+
const declared: Array<string> = (capturedTableProps?.formFields || []).map(
|
|
1888
|
+
fieldKeyOf,
|
|
1889
|
+
);
|
|
1890
|
+
|
|
1891
|
+
const onTheFirstStep: Array<string> = (capturedTableProps?.formFields || [])
|
|
1892
|
+
.filter((field: CapturedFormField): boolean => {
|
|
1893
|
+
return field.stepId === STEP_SCAN_TARGET;
|
|
1894
|
+
})
|
|
1895
|
+
.map(fieldKeyOf);
|
|
1896
|
+
|
|
1897
|
+
expect(onTheFirstStep[onTheFirstStep.length - 1]).toBe("isSnmpEnabled");
|
|
1898
|
+
|
|
1899
|
+
const toggleIndex: number = declared.indexOf("isSnmpEnabled");
|
|
1900
|
+
|
|
1901
|
+
for (const key of GATED_SNMP_STEP_FIELD_KEYS) {
|
|
1902
|
+
expect({
|
|
1903
|
+
field: key,
|
|
1904
|
+
isAskedAfterTheToggle: declared.indexOf(key) > toggleIndex,
|
|
1905
|
+
}).toEqual({ field: key, isAskedAfterTheToggle: true });
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
/*
|
|
1910
|
+
* The copy IS the feature here. An ICMP-only scan finds addresses and
|
|
1911
|
+
* nothing else — no name, no model, no vendor — and everything it imports
|
|
1912
|
+
* arrives as a device with polling off. An operator who is not told that
|
|
1913
|
+
* reads an empty device page as a broken import rather than as the scan they
|
|
1914
|
+
* asked for.
|
|
1915
|
+
*/
|
|
1916
|
+
test("the toggle says what an ICMP-only scan does and does not give you", async () => {
|
|
1917
|
+
await renderPage();
|
|
1918
|
+
|
|
1919
|
+
const description: string = fieldNamed("isSnmpEnabled").description || "";
|
|
1920
|
+
|
|
1921
|
+
expect(description).toContain("ICMP-only");
|
|
1922
|
+
expect(description).toContain("no credentials are asked for");
|
|
1923
|
+
/*
|
|
1924
|
+
* Points at the dialog's own "Create a Ping monitor" option rather than at
|
|
1925
|
+
* hand-binding, because every host an ICMP-only scan finds is a host
|
|
1926
|
+
* without SNMP — which is exactly the set that option covers.
|
|
1927
|
+
*/
|
|
1928
|
+
expect(description).toContain("Ping monitor");
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
test("the SNMP step is removed for a scan that will send no SNMP", async () => {
|
|
1932
|
+
await renderPage();
|
|
1933
|
+
|
|
1934
|
+
expect(stepNamed(STEP_SNMP).showIf?.({ isSnmpEnabled: false })).toBe(false);
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
/*
|
|
1938
|
+
* The invariant this whole change rests on, and the one that fails
|
|
1939
|
+
* silently: ABSENT means SNMP. Read an absent flag as "SNMP is off" and the
|
|
1940
|
+
* wizard quietly stops collecting credentials for every scan in the project
|
|
1941
|
+
* — a regression with no error message anywhere, whose only symptom is
|
|
1942
|
+
* discovery finding addresses instead of devices.
|
|
1943
|
+
*/
|
|
1944
|
+
test.each(VALUES_THAT_MEAN_THE_SCAN_SENDS_SNMP)(
|
|
1945
|
+
"the SNMP step stands for %s",
|
|
1946
|
+
async (_label: string, values: Record<string, unknown>) => {
|
|
1947
|
+
await renderPage();
|
|
1948
|
+
|
|
1949
|
+
expect(stepNamed(STEP_SNMP).showIf?.(values)).toBe(true);
|
|
1950
|
+
},
|
|
1951
|
+
);
|
|
1952
|
+
|
|
1953
|
+
test.each(VALUES_THAT_ARE_NOT_A_BOOLEAN_FALSE)(
|
|
1954
|
+
"%s does not remove the SNMP step",
|
|
1955
|
+
async (_label: string, values: Record<string, unknown>) => {
|
|
1956
|
+
await renderPage();
|
|
1957
|
+
|
|
1958
|
+
expect(stepNamed(STEP_SNMP).showIf?.(values)).toBe(true);
|
|
1959
|
+
},
|
|
1960
|
+
);
|
|
1961
|
+
|
|
1962
|
+
/*
|
|
1963
|
+
* Only the middle one is conditional. A showIf that drifted onto Scan Target
|
|
1964
|
+
* or Schedule would put the form's own step back in reach of being filtered
|
|
1965
|
+
* out from under it — the deadlock described at the top of this file, moved
|
|
1966
|
+
* to a step nobody was thinking about.
|
|
1967
|
+
*/
|
|
1968
|
+
test("only the middle step can remove itself", async () => {
|
|
1969
|
+
await renderPage();
|
|
1970
|
+
|
|
1971
|
+
expect(typeof stepNamed(STEP_SNMP).showIf).toBe("function");
|
|
1972
|
+
expect(stepNamed(STEP_SCAN_TARGET).showIf).toBeUndefined();
|
|
1973
|
+
expect(stepNamed(STEP_SCHEDULE).showIf).toBeUndefined();
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
/*
|
|
1977
|
+
* Worth its own test, because it is the thing the issue title tempts you to
|
|
1978
|
+
* change. #3445 is NOT "the SNMP question should be optional": a scan that
|
|
1979
|
+
* does send SNMP needs credentials to dial with, and relaxing the rule would
|
|
1980
|
+
* trade a blocked wizard for a scan the probe cannot run. What changed is
|
|
1981
|
+
* that the field is no longer on any step the operator is asked to submit.
|
|
1982
|
+
*
|
|
1983
|
+
* Before issue #3458 the required question on this step was SNMP Version —
|
|
1984
|
+
* the Dropdown "SNMP Version is required" names. The nine flat fields are one
|
|
1985
|
+
* CustomComponent editor now, so the required SNMP question is the credential
|
|
1986
|
+
* list: same rule, same step, one field.
|
|
1987
|
+
*/
|
|
1988
|
+
test("the credential list is still required — it is the step that disappears, not the rule", async () => {
|
|
1989
|
+
await renderPage();
|
|
1990
|
+
|
|
1991
|
+
const field: CapturedFormField = fieldNamed("snmpConfigs");
|
|
1992
|
+
|
|
1993
|
+
expect(field.required).toBe(true);
|
|
1994
|
+
expect(field.stepId).toBe(STEP_SNMP);
|
|
1995
|
+
|
|
1996
|
+
/*
|
|
1997
|
+
* The other half of the same sentence: what an ICMP-only scan changes is
|
|
1998
|
+
* that the FIELD is not there to be answered. Either half on its own is
|
|
1999
|
+
* satisfied by a bug — a required field nothing hides is exactly what
|
|
2000
|
+
* blocked an ICMP-only scan, and a hidden field that quietly stopped being
|
|
2001
|
+
* required would let a real SNMP scan be created with nothing to try.
|
|
2002
|
+
*/
|
|
2003
|
+
expect(isFieldShown("snmpConfigs", { isSnmpEnabled: false })).toBe(false);
|
|
2004
|
+
});
|
|
2005
|
+
|
|
2006
|
+
/*
|
|
2007
|
+
* ...and hidden is what makes required harmless, asserted through the code
|
|
2008
|
+
* that decides it rather than through a restatement of it. Validation checks
|
|
2009
|
+
* showIf BEFORE it checks required (Common/UI/Components/Forms/Validation),
|
|
2010
|
+
* so an operator who has turned SNMP off is never told "SNMP Configs is
|
|
2011
|
+
* required" about an editor that is not on screen — that sentence, about a
|
|
2012
|
+
* question they were not asked, IS issue #3445.
|
|
2013
|
+
*/
|
|
2014
|
+
test("a required credential list stops blocking submission once the scan is ICMP-only", async () => {
|
|
2015
|
+
await renderPage();
|
|
2016
|
+
|
|
2017
|
+
const icmpOnly: Dictionary<string> = validationErrorsOnStep({
|
|
2018
|
+
stepId: STEP_SNMP,
|
|
2019
|
+
values: { cidr: "10.20.30.0/24", isSnmpEnabled: false },
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
expect(icmpOnly).toEqual({});
|
|
2023
|
+
|
|
2024
|
+
/*
|
|
2025
|
+
* The same untouched form with SNMP left on, to show the rule is alive and
|
|
2026
|
+
* it really is the method that silences it: the list is demanded, on this
|
|
2027
|
+
* step, before the wizard will move off it.
|
|
2028
|
+
*/
|
|
2029
|
+
const withSnmp: Dictionary<string> = validationErrorsOnStep({
|
|
2030
|
+
stepId: STEP_SNMP,
|
|
2031
|
+
values: { cidr: "10.20.30.0/24", isSnmpEnabled: true },
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
expect(withSnmp["snmpConfigs"]).toContain("required");
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
/*
|
|
2038
|
+
* Belt and braces, deliberately. The step-level showIf is what an operator
|
|
2039
|
+
* sees; this is what holds when there is no step at all — BasicForm renders
|
|
2040
|
+
* every field when a form declares no steps, which is exactly how the Edit
|
|
2041
|
+
* dialog on this same page lays these fields out (issue #3444). A field gated
|
|
2042
|
+
* only by its step would come back, required, in any such form.
|
|
2043
|
+
*
|
|
2044
|
+
* These are the assertions the nine flat fields used to make one by one,
|
|
2045
|
+
* asked of the single field that replaced them (issue #3458) — the
|
|
2046
|
+
* credentials are all in the list now, so hiding the list is what hides them.
|
|
2047
|
+
*/
|
|
2048
|
+
test("the credential list is hidden when the scan sends no SNMP", async () => {
|
|
2049
|
+
await renderPage();
|
|
2050
|
+
|
|
2051
|
+
expect(isFieldShown("snmpConfigs", { isSnmpEnabled: false })).toBe(false);
|
|
2052
|
+
});
|
|
2053
|
+
|
|
2054
|
+
/*
|
|
2055
|
+
* The other direction, and the one the negative test above cannot stand in
|
|
2056
|
+
* for. A gate written as `if (!item.isSnmpEnabled) { return false; }` passes
|
|
2057
|
+
* every hidden-when-off assertion and still hides the editor from an
|
|
2058
|
+
* untouched form, from a row written before the column existed, and from a
|
|
2059
|
+
* partially selected one — the ABSENT-means-SNMP invariant, broken at the
|
|
2060
|
+
* only level that survives a form with no steps.
|
|
2061
|
+
*
|
|
2062
|
+
* The full state table is kept for exactly that reason: the absent case is
|
|
2063
|
+
* the whole of #3445. It is also what "a field with no reveal rule of its own
|
|
2064
|
+
* is shown whenever the method allows it" used to pin — snmpConfigs has no
|
|
2065
|
+
* reveal rule beyond the gate, so the gate's answer IS the field's answer,
|
|
2066
|
+
* for every state below.
|
|
2067
|
+
*/
|
|
2068
|
+
test.each(VALUES_THAT_MEAN_THE_SCAN_SENDS_SNMP)(
|
|
2069
|
+
"the credential list is asked for given %s",
|
|
2070
|
+
async (_label: string, values: Record<string, unknown>) => {
|
|
2071
|
+
await renderPage();
|
|
2072
|
+
|
|
2073
|
+
expect(isFieldShown("snmpConfigs", values)).toBe(true);
|
|
2074
|
+
},
|
|
2075
|
+
);
|
|
2076
|
+
|
|
2077
|
+
test.each(VALUES_THAT_ARE_NOT_A_BOOLEAN_FALSE)(
|
|
2078
|
+
"%s does not hide the credential list",
|
|
2079
|
+
async (_label: string, values: Record<string, unknown>) => {
|
|
2080
|
+
await renderPage();
|
|
2081
|
+
|
|
2082
|
+
expect(isFieldShown("snmpConfigs", values)).toBe(true);
|
|
2083
|
+
},
|
|
2084
|
+
);
|
|
2085
|
+
|
|
2086
|
+
/*
|
|
2087
|
+
* The inventory is the point: a second field added to this step later — a
|
|
2088
|
+
* per-scan timeout, a retry count — reaches it without passing through
|
|
2089
|
+
* anything above, and this test fails until GATED_SNMP_STEP_FIELD_KEYS names
|
|
2090
|
+
* it, which is what forces it through the gating assertions rather than past
|
|
2091
|
+
* them. It is one field today, and every claim made about "the SNMP fields"
|
|
2092
|
+
* is a claim about that one.
|
|
2093
|
+
*/
|
|
2094
|
+
test("the SNMP step holds exactly the fields gated on the scan's method", async () => {
|
|
2095
|
+
await renderPage();
|
|
2096
|
+
|
|
2097
|
+
const onTheSnmpStep: Array<string> = (capturedTableProps?.formFields || [])
|
|
2098
|
+
.filter((field: CapturedFormField): boolean => {
|
|
2099
|
+
return field.stepId === STEP_SNMP;
|
|
2100
|
+
})
|
|
2101
|
+
.map(fieldKeyOf);
|
|
2102
|
+
|
|
2103
|
+
expect(onTheSnmpStep).toEqual(GATED_SNMP_STEP_FIELD_KEYS);
|
|
2104
|
+
|
|
2105
|
+
for (const key of GATED_SNMP_STEP_FIELD_KEYS) {
|
|
2106
|
+
const field: CapturedFormField = fieldNamed(key);
|
|
2107
|
+
|
|
2108
|
+
/*
|
|
2109
|
+
* The repeated editor, required — the shape the gate exists to switch
|
|
2110
|
+
* off. (The wiring describe above owns the fuller claim about what this
|
|
2111
|
+
* field is bound to and what it renders.)
|
|
2112
|
+
*/
|
|
2113
|
+
expect(field.fieldType).toBe(FormFieldSchemaType.CustomComponent);
|
|
2114
|
+
expect(field.required).toBe(true);
|
|
2115
|
+
|
|
2116
|
+
// ...and it really carries a gate of its own, not only a step that hides.
|
|
2117
|
+
expect(typeof field.showIf).toBe("function");
|
|
2118
|
+
expect(isFieldShown(key, { isSnmpEnabled: false })).toBe(false);
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
/*
|
|
2123
|
+
* WHERE THE V3 REVEAL RULES WENT, and why they are still asserted here.
|
|
2124
|
+
*
|
|
2125
|
+
* They used to be showIf callbacks on the nine flat fields of this step, so
|
|
2126
|
+
* "the method gate composes with the v3 reveal rules instead of replacing
|
|
2127
|
+
* them" was a claim about two rules stacked on one field. Issue #3458 turned
|
|
2128
|
+
* those nine fields into one repeated editor, and the reveal chain moved
|
|
2129
|
+
* INSIDE it: SnmpConfigListEditor
|
|
2130
|
+
* (App/FeatureSet/Dashboard/src/Components/NetworkDevice) asks isSnmpV3 /
|
|
2131
|
+
* isSnmpV3WithAuth / isSnmpV3WithPriv per CARD, and imports all three from
|
|
2132
|
+
* Pages/NetworkDevice/SnmpConfigFormFields so that it and the NetworkDevice
|
|
2133
|
+
* forms cannot answer "when do we ask for a privacy key?" differently.
|
|
2134
|
+
*
|
|
2135
|
+
* So the composition is pinned where it now lives — against a config object,
|
|
2136
|
+
* through the very predicates the editor calls — rather than dropped along
|
|
2137
|
+
* with the fields that used to carry it. The guarantee is unchanged: a scan
|
|
2138
|
+
* that sends SNMP is asked for exactly the credentials its version and
|
|
2139
|
+
* security level need, and a scan that sends none is asked for nothing at
|
|
2140
|
+
* all. The two levels compose the same way they always did; only the second
|
|
2141
|
+
* one moved.
|
|
2142
|
+
*
|
|
2143
|
+
* (That the editor really calls these rather than re-deriving them, and that
|
|
2144
|
+
* what it reveals matches what the validator demands, is
|
|
2145
|
+
* App/Tests/Dashboard/SnmpConfigListEditorWiring.test.ts.)
|
|
2146
|
+
*/
|
|
2147
|
+
test("the v3 reveal chain still gates a credential card, where it moved to", async () => {
|
|
2148
|
+
await renderPage();
|
|
2149
|
+
|
|
2150
|
+
const v3AuthPriv: SnmpV3RevealSource = {
|
|
2151
|
+
snmpVersion: "V3",
|
|
2152
|
+
snmpV3SecurityLevel: SnmpSecurityLevel.AuthPriv,
|
|
2153
|
+
};
|
|
2154
|
+
|
|
2155
|
+
expect(isSnmpV3(v3AuthPriv)).toBe(true);
|
|
2156
|
+
expect(isSnmpV3WithAuth(v3AuthPriv)).toBe(true);
|
|
2157
|
+
expect(isSnmpV3WithPriv(v3AuthPriv)).toBe(true);
|
|
2158
|
+
|
|
2159
|
+
// Same security level, a version that has no v3 credentials at all.
|
|
2160
|
+
const v2cAuthPriv: SnmpV3RevealSource = {
|
|
2161
|
+
...v3AuthPriv,
|
|
2162
|
+
snmpVersion: "V2c",
|
|
2163
|
+
};
|
|
2164
|
+
|
|
2165
|
+
expect(isSnmpV3(v2cAuthPriv)).toBe(false);
|
|
2166
|
+
expect(isSnmpV3WithAuth(v2cAuthPriv)).toBe(false);
|
|
2167
|
+
expect(isSnmpV3WithPriv(v2cAuthPriv)).toBe(false);
|
|
2168
|
+
|
|
2169
|
+
// Right version, a security level that sends no privacy material.
|
|
2170
|
+
const v3AuthNoPriv: SnmpV3RevealSource = {
|
|
2171
|
+
...v3AuthPriv,
|
|
2172
|
+
snmpV3SecurityLevel: SnmpSecurityLevel.AuthNoPriv,
|
|
2173
|
+
};
|
|
2174
|
+
|
|
2175
|
+
expect(isSnmpV3(v3AuthNoPriv)).toBe(true);
|
|
2176
|
+
expect(isSnmpV3WithAuth(v3AuthNoPriv)).toBe(true);
|
|
2177
|
+
expect(isSnmpV3WithPriv(v3AuthNoPriv)).toBe(false);
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
/*
|
|
2181
|
+
* ...and the community string still follows the VERSION, not the method. The
|
|
2182
|
+
* editor asks for it on a v1/v2c card and swaps it for the v3 block on a v3
|
|
2183
|
+
* card — `!isSnmpV3(config)` is the whole rule, and it is the same one the
|
|
2184
|
+
* flat field's showIf used to carry.
|
|
2185
|
+
*
|
|
2186
|
+
* The method sits one level above that now: an ICMP-only scan has no cards at
|
|
2187
|
+
* all, because the editor itself is hidden. That is the "the method still
|
|
2188
|
+
* wins over the version when it is off" half of the old test, made at the
|
|
2189
|
+
* level the method is now decided on.
|
|
2190
|
+
*/
|
|
2191
|
+
test("the community string still follows the version, and the method still wins over both", async () => {
|
|
2192
|
+
await renderPage();
|
|
2193
|
+
|
|
2194
|
+
expect(isSnmpV3({ snmpVersion: "V2c" })).toBe(false);
|
|
2195
|
+
expect(isSnmpV3({ snmpVersion: "V3" })).toBe(true);
|
|
2196
|
+
|
|
2197
|
+
/*
|
|
2198
|
+
* The card the editor seeds before the operator picks anything. It is a
|
|
2199
|
+
* v2c card — never the v3 block — which is why an untouched SNMP step asks
|
|
2200
|
+
* for a community string.
|
|
2201
|
+
*/
|
|
2202
|
+
expect(isSnmpV3({})).toBe(false);
|
|
2203
|
+
|
|
2204
|
+
// ...and no card of any version is asked for when the scan sends no SNMP.
|
|
2205
|
+
expect(isFieldShown("snmpConfigs", { isSnmpEnabled: false })).toBe(false);
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2208
|
+
/*
|
|
2209
|
+
* The list has to fetch the method as well as ask for it: the results dialog
|
|
2210
|
+
* describes an ICMP-only scan differently ("hosts answered ping", no SNMP
|
|
2211
|
+
* filter buttons), and a column that is never selected reads as undefined,
|
|
2212
|
+
* which every reader is required to treat as an SNMP scan.
|
|
2213
|
+
*/
|
|
2214
|
+
test("the method is fetched with the list, so the results dialog can read it", async () => {
|
|
2215
|
+
await renderPage();
|
|
2216
|
+
|
|
2217
|
+
expect(capturedTableProps?.selectMoreFields?.["isSnmpEnabled"]).toBe(true);
|
|
2218
|
+
});
|
|
2219
|
+
});
|
|
2220
|
+
|
|
2221
|
+
/*
|
|
2222
|
+
* Turning the toggle off is not only a visibility change — it has to unsay
|
|
2223
|
+
* what the operator already said. ModelForm builds the request body from every
|
|
2224
|
+
* DECLARED field without asking whether it was visible, so a v3 passphrase
|
|
2225
|
+
* typed before they changed their mind is still posted, stored, and sitting on
|
|
2226
|
+
* a scan that will never send it.
|
|
2227
|
+
*/
|
|
2228
|
+
describe("Turning SNMP off clears the credentials the scan will never send", () => {
|
|
2229
|
+
beforeEach(() => {
|
|
2230
|
+
capturedTableProps = null;
|
|
2231
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
2232
|
+
jest.spyOn(ProbeUtil, "getAllProbes").mockResolvedValue([] as never);
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2235
|
+
afterEach(() => {
|
|
2236
|
+
cleanup();
|
|
2237
|
+
jest.restoreAllMocks();
|
|
2238
|
+
capturedTableProps = null;
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
/*
|
|
2242
|
+
* A form the operator has filled in and then changed their mind about.
|
|
2243
|
+
*
|
|
2244
|
+
* `snmpConfigs` is where a scan's community strings and v3 passphrases
|
|
2245
|
+
* actually live since issue #3458 — three credential sets, one of them
|
|
2246
|
+
* carrying an authPriv key pair — and the nine flat columns beside it are the
|
|
2247
|
+
* ones the server mirrors from that list. Both have to be unsaid: the list
|
|
2248
|
+
* because it is what would be stored, the columns because a value that
|
|
2249
|
+
* reached the form some other way must not ride along either.
|
|
2250
|
+
*/
|
|
2251
|
+
const TYPED_IN_CREDENTIALS: Record<string, unknown> = {
|
|
2252
|
+
snmpConfigs: MIXED_SUBNET_SNMP_CONFIGS,
|
|
2253
|
+
snmpVersion: "V3",
|
|
2254
|
+
snmpCommunityString: "public",
|
|
2255
|
+
snmpPort: 1161,
|
|
2256
|
+
snmpV3SecurityLevel: SnmpSecurityLevel.AuthPriv,
|
|
2257
|
+
snmpV3Username: "netops",
|
|
2258
|
+
snmpV3AuthProtocol: "sha",
|
|
2259
|
+
snmpV3AuthKey: "auth-passphrase",
|
|
2260
|
+
snmpV3PrivProtocol: "aes",
|
|
2261
|
+
snmpV3PrivKey: "priv-passphrase",
|
|
2262
|
+
};
|
|
2263
|
+
|
|
2264
|
+
test.each([
|
|
2265
|
+
/*
|
|
2266
|
+
* First, because it is the one that matters now: the credential list is
|
|
2267
|
+
* what a scan actually sends, and ModelForm builds the request body from
|
|
2268
|
+
* every DECLARED field without asking whether it was visible. A list left
|
|
2269
|
+
* in the form is a list stored on a scan that will never dial it.
|
|
2270
|
+
*/
|
|
2271
|
+
"snmpConfigs",
|
|
2272
|
+
"snmpCommunityString",
|
|
2273
|
+
"snmpPort",
|
|
2274
|
+
"snmpV3SecurityLevel",
|
|
2275
|
+
"snmpV3Username",
|
|
2276
|
+
"snmpV3AuthProtocol",
|
|
2277
|
+
"snmpV3AuthKey",
|
|
2278
|
+
"snmpV3PrivProtocol",
|
|
2279
|
+
"snmpV3PrivKey",
|
|
2280
|
+
])("%s is cleared on the way past", async (key: string) => {
|
|
2281
|
+
await renderPage();
|
|
2282
|
+
|
|
2283
|
+
const cleared: Record<string, unknown> = valuesAfterTurningSnmpOff({
|
|
2284
|
+
...TYPED_IN_CREDENTIALS,
|
|
2285
|
+
});
|
|
2286
|
+
|
|
2287
|
+
expect(Object.prototype.hasOwnProperty.call(cleared, key)).toBe(true);
|
|
2288
|
+
expect(cleared[key]).toBeUndefined();
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2291
|
+
/*
|
|
2292
|
+
* The one exception, and the reason it is an exception.
|
|
2293
|
+
*
|
|
2294
|
+
* Clearing snmpVersion would leave the column holding nothing where every
|
|
2295
|
+
* other writer of this model — the NetworkDevice forms, the API — expects a
|
|
2296
|
+
* version. It was a REQUIRED Dropdown on this very step until issue #3458
|
|
2297
|
+
* folded it into the credential list, and emptying it then meant being told
|
|
2298
|
+
* "SNMP Version is required" about a control that visibly read V2c the moment
|
|
2299
|
+
* SNMP went back on: issue #3445's own symptom, reintroduced by the fix for
|
|
2300
|
+
* it. The reset outlived the field, and so does the rule.
|
|
2301
|
+
*
|
|
2302
|
+
* Pinned to DEFAULT_SNMP_VERSION, the constant SnmpConfigFormFields exports
|
|
2303
|
+
* and the page writes from, rather than to a field defaultValue this wizard
|
|
2304
|
+
* no longer declares — one constant, so the two cannot drift.
|
|
2305
|
+
*/
|
|
2306
|
+
test("snmpVersion is reset to the shared default rather than emptied", async () => {
|
|
2307
|
+
await renderPage();
|
|
2308
|
+
|
|
2309
|
+
const cleared: Record<string, unknown> = valuesAfterTurningSnmpOff({
|
|
2310
|
+
...TYPED_IN_CREDENTIALS,
|
|
2311
|
+
});
|
|
2312
|
+
|
|
2313
|
+
expect(cleared["snmpVersion"]).toBe(DEFAULT_SNMP_VERSION);
|
|
2314
|
+
expect(cleared["snmpVersion"]).toBe("V2c");
|
|
2315
|
+
expect(cleared["snmpVersion"]).not.toBeUndefined();
|
|
2316
|
+
expect(cleared["snmpVersion"]).not.toBe("");
|
|
2317
|
+
expect(cleared["snmpVersion"]).not.toBeNull();
|
|
2318
|
+
});
|
|
2319
|
+
|
|
2320
|
+
/*
|
|
2321
|
+
* The sweep across everything the toggle owns rather than key by key:
|
|
2322
|
+
* whatever an individual value's treatment is — emptied, or reset to a
|
|
2323
|
+
* default — nothing the operator typed may survive the switch. The credential
|
|
2324
|
+
* list is in that sweep, because that is where the credentials are (issue
|
|
2325
|
+
* #3458), and so are the nine columns derived from it.
|
|
2326
|
+
*
|
|
2327
|
+
* It does NOT force a new SNMP setting to be handled. Both
|
|
2328
|
+
* SNMP_KEYS_THE_TOGGLE_MUST_CLEAR and TYPED_IN_CREDENTIALS are written by
|
|
2329
|
+
* hand in this file and are blind to a key nobody listed. The test that fails
|
|
2330
|
+
* until a new field is named is "the SNMP step holds exactly the fields gated
|
|
2331
|
+
* on the scan's method".
|
|
2332
|
+
*/
|
|
2333
|
+
test("every SNMP value is either cleared or reset, none is left as typed", async () => {
|
|
2334
|
+
await renderPage();
|
|
2335
|
+
|
|
2336
|
+
const cleared: Record<string, unknown> = valuesAfterTurningSnmpOff({
|
|
2337
|
+
...TYPED_IN_CREDENTIALS,
|
|
2338
|
+
});
|
|
2339
|
+
|
|
2340
|
+
for (const key of SNMP_KEYS_THE_TOGGLE_MUST_CLEAR) {
|
|
2341
|
+
expect(Object.prototype.hasOwnProperty.call(cleared, key)).toBe(true);
|
|
2342
|
+
expect(cleared[key]).not.toBe(TYPED_IN_CREDENTIALS[key]);
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
|
|
2346
|
+
test("the method itself is recorded as off", async () => {
|
|
2347
|
+
await renderPage();
|
|
2348
|
+
|
|
2349
|
+
expect(
|
|
2350
|
+
valuesAfterTurningSnmpOff({ ...TYPED_IN_CREDENTIALS })["isSnmpEnabled"],
|
|
2351
|
+
).toBe(false);
|
|
2352
|
+
});
|
|
2353
|
+
|
|
2354
|
+
/*
|
|
2355
|
+
* FormField calls onChange BEFORE setFieldValue and setFieldValue spreads
|
|
2356
|
+
* from the object handed back here, so anything this drops is dropped for
|
|
2357
|
+
* good — including the three answers that have nothing to do with SNMP.
|
|
2358
|
+
*/
|
|
2359
|
+
test("everything that is not an SNMP setting is left exactly as it was", async () => {
|
|
2360
|
+
await renderPage();
|
|
2361
|
+
|
|
2362
|
+
const cleared: Record<string, unknown> = valuesAfterTurningSnmpOff({
|
|
2363
|
+
name: "Router Discovery - Region 1100",
|
|
2364
|
+
cidr: "10.16-22.0-255.51-66",
|
|
2365
|
+
probe: "22222222-2222-4222-8222-222222222222",
|
|
2366
|
+
isRecurring: true,
|
|
2367
|
+
rescanIntervalInMinutes: 60,
|
|
2368
|
+
...TYPED_IN_CREDENTIALS,
|
|
2369
|
+
});
|
|
2370
|
+
|
|
2371
|
+
expect(cleared["name"]).toBe("Router Discovery - Region 1100");
|
|
2372
|
+
expect(cleared["cidr"]).toBe("10.16-22.0-255.51-66");
|
|
2373
|
+
expect(cleared["probe"]).toBe("22222222-2222-4222-8222-222222222222");
|
|
2374
|
+
expect(cleared["isRecurring"]).toBe(true);
|
|
2375
|
+
expect(cleared["rescanIntervalInMinutes"]).toBe(60);
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
/*
|
|
2379
|
+
* A NEW object, never the caller's. Mutating the values in place and handing
|
|
2380
|
+
* the same reference back is the classic React state bug: the object is
|
|
2381
|
+
* reference-equal to the one already in state, so nothing re-renders and the
|
|
2382
|
+
* credential boxes keep showing what was supposedly cleared.
|
|
2383
|
+
*/
|
|
2384
|
+
test("the operator's values are rewritten into a new object, not mutated", async () => {
|
|
2385
|
+
await renderPage();
|
|
2386
|
+
|
|
2387
|
+
const typed: Record<string, unknown> = { ...TYPED_IN_CREDENTIALS };
|
|
2388
|
+
const cleared: Record<string, unknown> = valuesAfterTurningSnmpOff(typed);
|
|
2389
|
+
|
|
2390
|
+
expect(cleared).not.toBe(typed);
|
|
2391
|
+
expect(typed["snmpV3PrivKey"]).toBe("priv-passphrase");
|
|
2392
|
+
expect(typed["snmpCommunityString"]).toBe("public");
|
|
2393
|
+
/*
|
|
2394
|
+
* The list too, and by reference: the editor holds the same array the form
|
|
2395
|
+
* does, so splicing it here instead of dropping the key would empty the
|
|
2396
|
+
* cards on screen under a component that never asked to be re-rendered.
|
|
2397
|
+
*/
|
|
2398
|
+
expect(typed["snmpConfigs"]).toBe(MIXED_SUBNET_SNMP_CONFIGS);
|
|
2399
|
+
expect(MIXED_SUBNET_SNMP_CONFIGS).toHaveLength(3);
|
|
2400
|
+
});
|
|
2401
|
+
|
|
2402
|
+
/*
|
|
2403
|
+
* A form where nothing was typed yet still has to come out cleared, and what
|
|
2404
|
+
* that buys is FORM state, not wire state. setFieldValue spreads from the
|
|
2405
|
+
* object handed back here, so a key this object does not carry leaves
|
|
2406
|
+
* whatever the form already held in place — including a value typed on an
|
|
2407
|
+
* earlier visit to the step, since the toggle sits BEFORE these fields and
|
|
2408
|
+
* they are reachable again the moment SNMP goes back on.
|
|
2409
|
+
*
|
|
2410
|
+
* On the wire it buys nothing: the values written are `undefined`, and
|
|
2411
|
+
* JSON.stringify drops undefined properties, so the create request carries
|
|
2412
|
+
* no SNMP keys at all. What stops the column defaults filling them back in
|
|
2413
|
+
* is the server hook nulling them, which is asserted in
|
|
2414
|
+
* Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts.
|
|
2415
|
+
*/
|
|
2416
|
+
test("a form with nothing typed in it still comes out with the SNMP keys cleared", async () => {
|
|
2417
|
+
await renderPage();
|
|
2418
|
+
|
|
2419
|
+
const cleared: Record<string, unknown> = valuesAfterTurningSnmpOff({});
|
|
2420
|
+
|
|
2421
|
+
for (const key of SNMP_KEYS_THE_TOGGLE_MUST_CLEAR) {
|
|
2422
|
+
expect(Object.prototype.hasOwnProperty.call(cleared, key)).toBe(true);
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
expect(cleared["snmpConfigs"]).toBeUndefined();
|
|
2426
|
+
expect(cleared["snmpCommunityString"]).toBeUndefined();
|
|
2427
|
+
expect(cleared["snmpVersion"]).toBe(DEFAULT_SNMP_VERSION);
|
|
2428
|
+
});
|
|
2429
|
+
|
|
2430
|
+
/*
|
|
2431
|
+
* Turning it back ON must rewrite nothing. Re-applying a default here would
|
|
2432
|
+
* stamp over credentials the operator had already typed on a previous visit
|
|
2433
|
+
* to the step — and the toggle is on the step BEFORE those fields, so they
|
|
2434
|
+
* are reachable again immediately.
|
|
2435
|
+
*/
|
|
2436
|
+
test("turning SNMP back on rewrites nothing at all", async () => {
|
|
2437
|
+
await renderPage();
|
|
2438
|
+
|
|
2439
|
+
expect(
|
|
2440
|
+
toggleScanMethodTo(true, { ...TYPED_IN_CREDENTIALS }),
|
|
2441
|
+
).not.toHaveBeenCalled();
|
|
2442
|
+
});
|
|
2443
|
+
});
|
|
2444
|
+
|
|
2445
|
+
/*
|
|
2446
|
+
* The size of the sweep, said out loud before it is queued (issue #3445's
|
|
2447
|
+
* sibling complaint: an ICMP-only scan is the one people run over big ranges).
|
|
2448
|
+
*
|
|
2449
|
+
* The most surprising thing about octet-range notation is how much of it there
|
|
2450
|
+
* is — 10.16-22.0-255.51-66 is 28,672 addresses — and nothing on the form said
|
|
2451
|
+
* so until the sweep had already been handed to a probe.
|
|
2452
|
+
*/
|
|
2453
|
+
describe("Scan Target says how big the sweep is while it is being typed", () => {
|
|
2454
|
+
beforeEach(() => {
|
|
2455
|
+
capturedTableProps = null;
|
|
2456
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
2457
|
+
jest.spyOn(ProbeUtil, "getAllProbes").mockResolvedValue([] as never);
|
|
2458
|
+
});
|
|
2459
|
+
|
|
2460
|
+
afterEach(() => {
|
|
2461
|
+
cleanup();
|
|
2462
|
+
jest.restoreAllMocks();
|
|
2463
|
+
capturedTableProps = null;
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
/*
|
|
2467
|
+
* 254 rather than 256: the parser drops the network and broadcast addresses
|
|
2468
|
+
* of any block bigger than a /31, exactly as the probe's expansion does, so
|
|
2469
|
+
* the number on the form is the number of addresses that will actually be
|
|
2470
|
+
* pinged.
|
|
2471
|
+
*/
|
|
2472
|
+
test("a CIDR target renders its address count", async () => {
|
|
2473
|
+
await renderPage();
|
|
2474
|
+
|
|
2475
|
+
expect(renderTargetSizeHint("192.168.1.0/24")).toBe(
|
|
2476
|
+
"This target sweeps 254 addresses.",
|
|
2477
|
+
);
|
|
2478
|
+
});
|
|
2479
|
+
|
|
2480
|
+
test("an octet range renders the much larger count it expands to, with thousands separators", async () => {
|
|
2481
|
+
await renderPage();
|
|
2482
|
+
|
|
2483
|
+
expect(renderTargetSizeHint("10.16-22.0-255.51-66")).toBe(
|
|
2484
|
+
"This target sweeps 28,672 addresses.",
|
|
2485
|
+
);
|
|
2486
|
+
});
|
|
2487
|
+
|
|
2488
|
+
/*
|
|
2489
|
+
* "1 addresses" is the kind of detail that makes a product feel unfinished,
|
|
2490
|
+
* and a single-address target is a completely ordinary thing to type while
|
|
2491
|
+
* checking one host.
|
|
2492
|
+
*/
|
|
2493
|
+
test("a single-address target says 1 address, not 1 addresses", async () => {
|
|
2494
|
+
await renderPage();
|
|
2495
|
+
|
|
2496
|
+
expect(renderTargetSizeHint("10.0.0.5")).toBe(
|
|
2497
|
+
"This target sweeps 1 address.",
|
|
2498
|
+
);
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
/*
|
|
2502
|
+
* The hint is gated on the COUNT, not on validity — which is what makes it
|
|
2503
|
+
* useful in the one case where the size IS the problem. A target over the
|
|
2504
|
+
* ceiling states its size right next to the error explaining why that size
|
|
2505
|
+
* is refused, and the two have to be the SAME number: the hint counts with
|
|
2506
|
+
* ScanTargetUtil.countHosts while the refusal counts inside
|
|
2507
|
+
* getValidationError's own parse, so nothing but these literals stops a form
|
|
2508
|
+
* from reading "sweeps 16,777,216 addresses" above "expands to 16,777,214
|
|
2509
|
+
* addresses, exceeding the 32,768-address scan limit".
|
|
2510
|
+
*/
|
|
2511
|
+
test("a target over the address ceiling states its size, and the refusal quotes the same number", async () => {
|
|
2512
|
+
await renderPage();
|
|
2513
|
+
|
|
2514
|
+
expect(renderTargetSizeHint("10.0.0.0/8")).toBe(
|
|
2515
|
+
"This target sweeps 16,777,214 addresses.",
|
|
2516
|
+
);
|
|
2517
|
+
|
|
2518
|
+
expect(ScanTargetUtil.getValidationError("10.0.0.0/8")).toContain(
|
|
2519
|
+
"expands to 16,777,214 addresses, exceeding the 32,768-address scan limit",
|
|
2520
|
+
);
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2523
|
+
test("a target exactly at the ceiling states the ceiling", async () => {
|
|
2524
|
+
await renderPage();
|
|
2525
|
+
|
|
2526
|
+
const atLimit: string = "10.0.0-127.0-255";
|
|
2527
|
+
|
|
2528
|
+
expect(ScanTargetUtil.countHosts(atLimit)).toBe(
|
|
2529
|
+
ScanTargetUtil.MAX_SCAN_HOSTS,
|
|
2530
|
+
);
|
|
2531
|
+
expect(renderTargetSizeHint(atLimit)).toBe(
|
|
2532
|
+
"This target sweeps 32,768 addresses.",
|
|
2533
|
+
);
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
/*
|
|
2537
|
+
* countHosts returns 0 for anything it cannot parse, and a half-typed target
|
|
2538
|
+
* is unparseable for most of the time it is being typed. Saying nothing is
|
|
2539
|
+
* what keeps the hint from talking over the inline validation message.
|
|
2540
|
+
*/
|
|
2541
|
+
test.each([
|
|
2542
|
+
["an empty box", ""],
|
|
2543
|
+
["a blank box", " "],
|
|
2544
|
+
["an untouched box", undefined],
|
|
2545
|
+
["a cleared box", null],
|
|
2546
|
+
["free text", "not-a-target"],
|
|
2547
|
+
["a half-typed target", "10.0.0."],
|
|
2548
|
+
["a bad prefix", "192.168.1.0/99"],
|
|
2549
|
+
["a reversed range", "10.22-16.0.1"],
|
|
2550
|
+
["an out-of-range octet", "10.0.0.256"],
|
|
2551
|
+
/*
|
|
2552
|
+
* Over the parser's length cap — and deliberately just another unparseable
|
|
2553
|
+
* input here rather than a test OF that cap. The grammar caps a
|
|
2554
|
+
* well-formed target at 31 characters
|
|
2555
|
+
* ("255-255.255-255.255-255.255-255"), so nothing longer than
|
|
2556
|
+
* MAX_TARGET_LENGTH can be well-formed and no assertion about this hint
|
|
2557
|
+
* can tell the length gate apart from the syntax rules. The gate is pinned
|
|
2558
|
+
* where it is observable — by the message text, in "a target past the
|
|
2559
|
+
* parser's length cap is refused on this step" — and its effect on this
|
|
2560
|
+
* hint is pinned from below: lower the cap under 20 and the octet-range
|
|
2561
|
+
* count above stops rendering.
|
|
2562
|
+
*/
|
|
2563
|
+
[
|
|
2564
|
+
"a target longer than the parser will look at",
|
|
2565
|
+
"1".repeat(ScanTargetUtil.MAX_TARGET_LENGTH + 1),
|
|
2566
|
+
],
|
|
2567
|
+
])("%s renders nothing", async (_label: string, target: unknown) => {
|
|
2568
|
+
await renderPage();
|
|
2569
|
+
|
|
2570
|
+
expect(renderTargetSizeHint(target)).toBeUndefined();
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
// The box is trimmed before it is counted, same as it is before it is validated.
|
|
2574
|
+
test("surrounding whitespace does not stop the count", async () => {
|
|
2575
|
+
await renderPage();
|
|
2576
|
+
|
|
2577
|
+
expect(renderTargetSizeHint(" 192.168.1.0/24 ")).toBe(
|
|
2578
|
+
"This target sweeps 254 addresses.",
|
|
2579
|
+
);
|
|
690
2580
|
});
|
|
691
2581
|
});
|