@oneuptime/common 12.0.25 → 12.0.27

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.
Files changed (856) hide show
  1. package/Models/AnalyticsModels/Index.ts +2 -0
  2. package/Models/AnalyticsModels/ThreatIntelIndicator.ts +413 -0
  3. package/Models/DatabaseModels/AlertSeverity.ts +4 -1
  4. package/Models/DatabaseModels/CephClusterFeed.ts +625 -0
  5. package/Models/DatabaseModels/CloudResourceFeed.ts +625 -0
  6. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +13 -0
  7. package/Models/DatabaseModels/DetectionRule.ts +9 -3
  8. package/Models/DatabaseModels/DockerHostFeed.ts +625 -0
  9. package/Models/DatabaseModels/DockerSwarmClusterFeed.ts +628 -0
  10. package/Models/DatabaseModels/GoogleSecOpsConnection.ts +9 -3
  11. package/Models/DatabaseModels/HostFeed.ts +625 -0
  12. package/Models/DatabaseModels/IncidentSeverity.ts +4 -1
  13. package/Models/DatabaseModels/Index.ts +22 -0
  14. package/Models/DatabaseModels/KubernetesClusterFeed.ts +628 -0
  15. package/Models/DatabaseModels/Monitor.ts +87 -0
  16. package/Models/DatabaseModels/NetworkDevice.ts +96 -5
  17. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +92 -6
  18. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +301 -31
  19. package/Models/DatabaseModels/NetworkSite.ts +120 -0
  20. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  21. package/Models/DatabaseModels/PodmanHostFeed.ts +625 -0
  22. package/Models/DatabaseModels/ProxmoxClusterFeed.ts +626 -0
  23. package/Models/DatabaseModels/ScheduledMaintenance.ts +67 -0
  24. package/Models/DatabaseModels/ServiceFeed.ts +625 -0
  25. package/Models/DatabaseModels/TelemetrySourceMap.ts +474 -0
  26. package/Models/DatabaseModels/ThreatIntelFeed.ts +758 -0
  27. package/Models/DatabaseModels/UserOnCallLog.ts +1 -0
  28. package/Models/DatabaseModels/UserTotpAuth.ts +1 -0
  29. package/Models/DatabaseModels/UserWebAuthn.ts +1 -0
  30. package/Server/API/AlertAPI.ts +23 -1
  31. package/Server/API/BaseAPI.ts +14 -3
  32. package/Server/API/IncidentAPI.ts +41 -1
  33. package/Server/API/IncidentEpisodeAPI.ts +23 -1
  34. package/Server/API/MicrosoftTeamsAPI.ts +31 -2
  35. package/Server/API/ScheduledMaintenanceAPI.ts +23 -1
  36. package/Server/API/SlackAPI.ts +48 -0
  37. package/Server/API/TelemetryAPI.ts +167 -42
  38. package/Server/API/UserOnCallLogTimelineAPI.ts +75 -61
  39. package/Server/API/UserTotpAuthAPI.ts +67 -1
  40. package/Server/API/UserWebAuthnAPI.ts +44 -1
  41. package/Server/Infrastructure/Postgres/SchemaMigrations/1787923136162-MigrationName.ts +57 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1789600000000-AddAutoProvisionedNetworkDeviceMonitors.ts +49 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1789700000000-AddNetworkScaleIndexes.ts +129 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1789800000000-WidenSecurityEventLastErrorColumns.ts +54 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1789900000000-AddNetworkSiteRollupPolicyAndMaintenance.ts +64 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/1790000000000-AddTelemetrySourceMap.ts +64 -0
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1790000000001-AddSnmpConfigsToNetworkDeviceDiscoveryScan.ts +72 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1790003445000-AddSnmpEnabledToNetworkDeviceDiscoveryScan.ts +31 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1790100000000-AddResourceActivityFeeds.ts +489 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +18 -0
  51. package/Server/Middleware/IdentityRateLimit.ts +53 -3
  52. package/Server/Services/AIService.ts +181 -41
  53. package/Server/Services/CephClusterFeedService.ts +99 -0
  54. package/Server/Services/CephClusterLabelRuleEngineService.ts +29 -0
  55. package/Server/Services/CephClusterOwnerRuleEngineService.ts +23 -0
  56. package/Server/Services/CephClusterOwnerTeamService.ts +129 -0
  57. package/Server/Services/CephClusterOwnerUserService.ts +123 -0
  58. package/Server/Services/CephClusterService.ts +223 -2
  59. package/Server/Services/CloudResourceFeedService.ts +99 -0
  60. package/Server/Services/CloudResourceLabelRuleEngineService.ts +30 -0
  61. package/Server/Services/CloudResourceOwnerRuleEngineService.ts +33 -0
  62. package/Server/Services/CloudResourceOwnerTeamService.ts +129 -0
  63. package/Server/Services/CloudResourceOwnerUserService.ts +123 -0
  64. package/Server/Services/CloudResourceService.ts +227 -2
  65. package/Server/Services/DatabaseService.ts +265 -0
  66. package/Server/Services/DockerHostFeedService.ts +99 -0
  67. package/Server/Services/DockerHostLabelRuleEngineService.ts +29 -0
  68. package/Server/Services/DockerHostOwnerRuleEngineService.ts +23 -0
  69. package/Server/Services/DockerHostOwnerTeamService.ts +129 -0
  70. package/Server/Services/DockerHostOwnerUserService.ts +123 -0
  71. package/Server/Services/DockerHostService.ts +227 -2
  72. package/Server/Services/DockerSwarmClusterFeedService.ts +102 -0
  73. package/Server/Services/DockerSwarmClusterLabelRuleEngineService.ts +30 -0
  74. package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +24 -0
  75. package/Server/Services/DockerSwarmClusterOwnerTeamService.ts +133 -0
  76. package/Server/Services/DockerSwarmClusterOwnerUserService.ts +127 -0
  77. package/Server/Services/DockerSwarmClusterService.ts +229 -2
  78. package/Server/Services/EnterpriseLicenseService.ts +15 -7
  79. package/Server/Services/ExceptionAggregationService.ts +12 -2
  80. package/Server/Services/HostFeedService.ts +99 -0
  81. package/Server/Services/HostLabelRuleEngineService.ts +29 -0
  82. package/Server/Services/HostOwnerRuleEngineService.ts +23 -0
  83. package/Server/Services/HostOwnerTeamService.ts +129 -0
  84. package/Server/Services/HostOwnerUserService.ts +123 -0
  85. package/Server/Services/HostService.ts +215 -2
  86. package/Server/Services/IncidentService.ts +11 -0
  87. package/Server/Services/Index.ts +24 -0
  88. package/Server/Services/KubernetesClusterFeedService.ts +99 -0
  89. package/Server/Services/KubernetesClusterLabelRuleEngineService.ts +30 -0
  90. package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +24 -0
  91. package/Server/Services/KubernetesClusterOwnerTeamService.ts +133 -0
  92. package/Server/Services/KubernetesClusterOwnerUserService.ts +127 -0
  93. package/Server/Services/KubernetesClusterService.ts +231 -2
  94. package/Server/Services/LogAggregationService.ts +23 -280
  95. package/Server/Services/MetricAggregationService.ts +98 -1
  96. package/Server/Services/MonitorService.ts +177 -13
  97. package/Server/Services/MonitorTemplateService.ts +111 -0
  98. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +777 -86
  99. package/Server/Services/NetworkDeviceAutoImportRuleService.ts +199 -16
  100. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +1055 -14
  101. package/Server/Services/NetworkDeviceService.ts +750 -4
  102. package/Server/Services/NetworkInterfaceService.ts +415 -0
  103. package/Server/Services/NetworkSiteService.ts +485 -52
  104. package/Server/Services/PodmanHostFeedService.ts +99 -0
  105. package/Server/Services/PodmanHostLabelRuleEngineService.ts +29 -0
  106. package/Server/Services/PodmanHostOwnerRuleEngineService.ts +23 -0
  107. package/Server/Services/PodmanHostOwnerTeamService.ts +129 -0
  108. package/Server/Services/PodmanHostOwnerUserService.ts +123 -0
  109. package/Server/Services/PodmanHostService.ts +227 -2
  110. package/Server/Services/ProjectService.ts +16 -10
  111. package/Server/Services/ProxmoxClusterFeedService.ts +99 -0
  112. package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +30 -0
  113. package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +24 -0
  114. package/Server/Services/ProxmoxClusterOwnerTeamService.ts +131 -0
  115. package/Server/Services/ProxmoxClusterOwnerUserService.ts +124 -0
  116. package/Server/Services/ProxmoxClusterService.ts +225 -2
  117. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +61 -0
  118. package/Server/Services/ServiceFeedService.ts +99 -0
  119. package/Server/Services/ServiceLabelRuleEngineService.ts +29 -0
  120. package/Server/Services/ServiceOwnerRuleEngineService.ts +26 -0
  121. package/Server/Services/ServiceOwnerTeamService.ts +129 -0
  122. package/Server/Services/ServiceOwnerUserService.ts +123 -0
  123. package/Server/Services/ServiceService.ts +227 -3
  124. package/Server/Services/StatusPageSubscriberService.ts +7 -0
  125. package/Server/Services/TelemetryAttributeService.ts +13 -3
  126. package/Server/Services/TelemetrySourceMapService.ts +307 -0
  127. package/Server/Services/ThreatIntelFeedService.ts +222 -0
  128. package/Server/Services/ThreatIntelIndicatorService.ts +428 -0
  129. package/Server/Services/TraceAggregationService.ts +101 -11
  130. package/Server/Services/UserService.ts +13 -6
  131. package/Server/Services/UserTwoFactorBackupCodeService.ts +127 -0
  132. package/Server/Services/UserWebhookService.ts +11 -2
  133. package/Server/Static/Brand/README.md +38 -0
  134. package/Server/Static/Brand/favicons/apple-touch-icon.png +0 -0
  135. package/Server/Static/Brand/favicons/favicon-16x16.png +0 -0
  136. package/Server/Static/Brand/favicons/favicon-32x32.png +0 -0
  137. package/Server/Static/Brand/favicons/favicon.ico +0 -0
  138. package/Server/Static/Brand/favicons/safari-pinned-tab.svg +31 -0
  139. package/Server/Static/Brand/hou-wb.svg +1 -0
  140. package/Server/Static/Brand/oneuptime-logo.svg +1 -0
  141. package/Server/Static/Brand/ou-wb.svg +1 -0
  142. package/Server/Static/Vendor/README.md +4 -0
  143. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +13 -3
  144. package/Server/Types/Database/AggregateBy.ts +83 -0
  145. package/Server/Types/Database/AggregateResultUtil.ts +126 -0
  146. package/Server/Types/Database/JSONColumnQuery.ts +18 -0
  147. package/Server/Types/Database/QueryHelper.ts +40 -0
  148. package/Server/Types/Database/QueryUtil.ts +26 -0
  149. package/Server/Types/Workflow/Components/API/Utils.ts +10 -1
  150. package/Server/Types/Workflow/Components/JavaScript.ts +8 -0
  151. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +9 -0
  152. package/Server/Utils/AI/SRE/InvestigationGrader.ts +18 -0
  153. package/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.ts +422 -0
  154. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +176 -0
  155. package/Server/Utils/DataSource/HttpFetch.ts +18 -0
  156. package/Server/Utils/Database/RelationIdUtil.ts +42 -0
  157. package/Server/Utils/Marketing/MarketingEventUtil.ts +38 -9
  158. package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +34 -3
  159. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +9 -1
  160. package/Server/Utils/Monitor/Criteria/SyntheticMonitor.ts +28 -10
  161. package/Server/Utils/Monitor/MonitorAlert.ts +541 -332
  162. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +330 -64
  163. package/Server/Utils/Monitor/MonitorIncident.ts +640 -434
  164. package/Server/Utils/Monitor/MonitorResource.ts +226 -49
  165. package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +46 -3
  166. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +23 -102
  167. package/Server/Utils/Monitor/PerEntityCriteriaFanOut.ts +329 -0
  168. package/Server/Utils/NetworkDevice/DeviceHealthAggregation.ts +427 -0
  169. package/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.ts +310 -0
  170. package/Server/Utils/OnCallNotificationContext.ts +686 -0
  171. package/Server/Utils/PrivateNetworkWebhookConfig.ts +549 -0
  172. package/Server/Utils/ResourceFeed/ResourceFeedUtil.ts +139 -0
  173. package/Server/Utils/Response.ts +11 -1
  174. package/Server/Utils/SSRFProtection.ts +230 -60
  175. package/Server/Utils/SecurityEvent/ConnectorErrorMessage.ts +106 -0
  176. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +84 -284
  177. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +47 -20
  178. package/Server/Utils/SecurityEvent/SecurityEventAlerting.ts +318 -0
  179. package/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.ts +199 -0
  180. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.ts +252 -0
  181. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +739 -0
  182. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +533 -0
  183. package/Server/Utils/StartServer.ts +83 -56
  184. package/Server/Utils/StatusPageSubscriberWebhook.ts +3 -0
  185. package/Server/Utils/Telemetry/SourceMapResolver.ts +480 -0
  186. package/Server/Utils/TwoFactorBackupCodeNotification.ts +106 -0
  187. package/Server/Utils/VM/VMAPI.ts +3 -0
  188. package/Server/Utils/VM/VMRunner.ts +27 -1
  189. package/Server/Utils/VendorAssets.ts +42 -0
  190. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +136 -6
  191. package/Server/Views/AcknowledgeUserOnCallNotification.ejs +11 -9
  192. package/Server/Views/Partials/Head.ejs +9 -9
  193. package/Server/Views/Partials/OnCallNotificationDetails.ejs +48 -0
  194. package/Server/Views/ViewMessage.ejs +11 -9
  195. package/Tests/App/Accounts/LoginTwoFactorRecovery.test.tsx +972 -0
  196. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +401 -0
  197. package/Tests/App/Dashboard/DiscoveryScanEditForm.test.tsx +707 -0
  198. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +1908 -18
  199. package/Tests/App/Dashboard/ResourceFeed.test.tsx +230 -0
  200. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +216 -0
  201. package/Tests/App/Dashboard/TopologyAddToMonitoring.test.tsx +192 -0
  202. package/Tests/App/Dashboard/WorkspaceNotificationRuleConditions.test.tsx +439 -0
  203. package/Tests/Models/DatabaseModels/DateColumnDeserialization.test.ts +182 -0
  204. package/Tests/Models/DatabaseModels/RelationColumnModelTypeCoverage.test.ts +82 -0
  205. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +398 -0
  206. package/Tests/Models/DiscoveryScanNameColumn.test.ts +151 -16
  207. package/Tests/Models/DiscoveryScanSnmpEnabledColumn.test.ts +593 -0
  208. package/Tests/Models/NetworkDeviceCreateContract.test.ts +102 -0
  209. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +607 -0
  210. package/Tests/Server/API/AIGenerationRequestBudget.test.ts +355 -0
  211. package/Tests/Server/API/AIKillSwitchBackstop.test.ts +602 -0
  212. package/Tests/Server/API/BaseAPIUpdateDateColumns.test.ts +154 -0
  213. package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +335 -0
  214. package/Tests/Server/API/UserOnCallLogTimelineAcknowledgePage.test.ts +611 -0
  215. package/Tests/Server/API/UserTotpAuthAPI.test.ts +498 -4
  216. package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +682 -0
  217. package/Tests/Server/Infrastructure/ExampleDockerfiles.test.ts +595 -0
  218. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +9 -0
  219. package/Tests/Server/Services/AIServiceProjectAccess.test.ts +6 -0
  220. package/Tests/Server/Services/AutoImportScanCredentialSelect.test.ts +18 -0
  221. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +590 -0
  222. package/Tests/Server/Services/DatabaseServiceUpdateWriteRouting.test.ts +12 -0
  223. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +162 -26
  224. package/Tests/Server/Services/EnterpriseLicenseServiceCreate.test.ts +292 -0
  225. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +119 -12
  226. package/Tests/Server/Services/LogAggregationAttributeOperators.test.ts +109 -0
  227. package/Tests/Server/Services/MetricAggregationAttributeOperators.test.ts +170 -0
  228. package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +383 -0
  229. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +997 -3
  230. package/Tests/Server/Services/NetworkDeviceAutoImportRuleService.test.ts +830 -0
  231. package/Tests/Server/Services/NetworkDeviceAutoMonitorLifecycle.test.ts +528 -0
  232. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +3141 -1
  233. package/Tests/Server/Services/NetworkDeviceRegisteredHostnames.test.ts +1023 -0
  234. package/Tests/Server/Services/NetworkDeviceSiteAssignmentRuleSkip.test.ts +728 -0
  235. package/Tests/Server/Services/NetworkInterfaceServiceUpsert.test.ts +795 -0
  236. package/Tests/Server/Services/NetworkSiteRollupPolicyAndMaintenance.test.ts +903 -0
  237. package/Tests/Server/Services/NetworkSiteService.test.ts +165 -24
  238. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +5 -3
  239. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +5 -2
  240. package/Tests/Server/Services/ResourceCreationFeedWrites.test.ts +304 -0
  241. package/Tests/Server/Services/ResourceFeedServices.test.ts +269 -0
  242. package/Tests/Server/Services/ResourceOwnerFeedWrites.test.ts +245 -0
  243. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +792 -0
  244. package/Tests/Server/Services/TelemetrySourceMapService.test.ts +483 -0
  245. package/Tests/Server/Services/ThreatIntelFeedService.test.ts +351 -0
  246. package/Tests/Server/Services/ThreatIntelIndicatorService.test.ts +366 -0
  247. package/Tests/Server/Services/TraceAggregationAttributeOperators.test.ts +507 -0
  248. package/Tests/Server/Services/UserTwoFactorBackupCodeGenerateIfNone.test.ts +705 -0
  249. package/Tests/Server/Services/WorkspaceNotificationRuleOnCallDutyPolicy.test.ts +306 -0
  250. package/Tests/Server/Types/Database/AggregateResultUtil.test.ts +386 -0
  251. package/Tests/Server/Types/Database/Permissions/SelectRejectionMessageContract.test.ts +424 -0
  252. package/Tests/Server/Types/Database/QueryHelperWildcard.test.ts +142 -0
  253. package/Tests/Server/Types/Workflow/Components/ApiComponentPrivateNetwork.test.ts +163 -0
  254. package/Tests/Server/Types/Workflow/Components/JavaScriptPrivateNetwork.test.ts +124 -0
  255. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +5 -0
  256. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +39 -1
  257. package/Tests/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.test.ts +338 -0
  258. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWildcard.test.ts +383 -0
  259. package/Tests/Server/Utils/Marketing/EmittedMarketingEvent.ts +23 -0
  260. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +51 -0
  261. package/Tests/Server/Utils/Monitor/Criteria/SyntheticMonitorCriteria.test.ts +259 -0
  262. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPerSeriesFanout.test.ts +557 -0
  263. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +12 -2
  264. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkLogWritePath.test.ts +404 -0
  265. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +8 -2
  266. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +125 -98
  267. package/Tests/Server/Utils/Monitor/PerEntityCriteriaFanOut.test.ts +444 -0
  268. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1112 -0
  269. package/Tests/Server/Utils/NetworkDevice/DeviceHealthAggregation.test.ts +717 -0
  270. package/Tests/Server/Utils/NetworkDevice/RulePatternValidator.test.ts +78 -0
  271. package/Tests/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.test.ts +363 -0
  272. package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +35 -0
  273. package/Tests/Server/Utils/OnCallNotificationContext.test.ts +908 -0
  274. package/Tests/Server/Utils/PrivateNetworkWebhookConfig.test.ts +423 -0
  275. package/Tests/Server/Utils/ResourceFeed/ResourceFeedUtil.test.ts +191 -0
  276. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +597 -0
  277. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +819 -0
  278. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +381 -0
  279. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +417 -0
  280. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +819 -0
  281. package/Tests/Server/Utils/SecurityEvent/SecurityEventAlerting.test.ts +246 -0
  282. package/Tests/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.test.ts +282 -0
  283. package/Tests/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.test.ts +312 -0
  284. package/Tests/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.test.ts +854 -0
  285. package/Tests/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.test.ts +437 -0
  286. package/Tests/Server/Utils/StartServerErrorStatus.test.ts +225 -0
  287. package/Tests/Server/Utils/StatusPage/MonitorRulePatternValidator.test.ts +62 -0
  288. package/Tests/Server/Utils/StatusPageSubscriberWebhookPrivateNetwork.test.ts +119 -0
  289. package/Tests/Server/Utils/Telemetry/SourceMapResolver.test.ts +542 -0
  290. package/Tests/Server/Utils/TwoFactorBackupCodeNotification.test.ts +272 -0
  291. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +111 -0
  292. package/Tests/Server/Utils/VendorAssets.test.ts +117 -0
  293. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +101 -4
  294. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstallStateDiagnostics.test.ts +922 -0
  295. package/Tests/Server/Utils/Workspace/MicrosoftTeamsRosterDiagnosticMessages.test.ts +642 -0
  296. package/Tests/Server/Views/OnCallAcknowledgePage.test.ts +430 -0
  297. package/Tests/Types/API/HostnameAuthorityParsing.test.ts +1 -0
  298. package/Tests/Types/API/URLMailtoQueryString.test.ts +439 -0
  299. package/Tests/Types/API/URLQueryString.test.ts +481 -0
  300. package/Tests/Types/API/URLRouteQuerySeparation.test.ts +491 -0
  301. package/Tests/Types/BaseDatabase/Wildcard.test.ts +163 -0
  302. package/Tests/Types/BaseDatabase/WildcardPattern.test.ts +149 -0
  303. package/Tests/Types/Database/DateColumnValue.test.ts +265 -0
  304. package/Tests/Types/DateToIsoStringOrNull.test.ts +67 -0
  305. package/Tests/Types/Log/LogQueryToFilter.test.ts +218 -43
  306. package/Tests/Types/Monitor/KubernetesTemplateGroupByKeys.test.ts +225 -0
  307. package/Tests/Types/Monitor/TemplateGroupByKeys.test.ts +296 -0
  308. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +102 -0
  309. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +145 -0
  310. package/Tests/Types/NetworkSite/SiteHealthRollupPolicy.test.ts +58 -0
  311. package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +15 -0
  312. package/Tests/Types/Telemetry/TelemetrySearchQuery.test.ts +620 -0
  313. package/Tests/Types/WhatsApp/WhatsAppTemplates.test.ts +110 -0
  314. package/Tests/Types/Workspace/NotificationRuleConditionUtil.test.ts +76 -6
  315. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +16 -4
  316. package/Tests/Types/Workspace/NotificationRules/OnCallDutyPolicyConditions.test.ts +669 -0
  317. package/Tests/UI/Components/DictionaryFilterOperatorWildcard.test.ts +175 -0
  318. package/Tests/UI/Components/IconOpticalSize.test.tsx +377 -0
  319. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +393 -0
  320. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +248 -0
  321. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +205 -0
  322. package/Tests/UI/Components/UseHistogramZoom.test.tsx +262 -0
  323. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +332 -0
  324. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsInstallGuidanceContent.test.tsx +643 -0
  325. package/Tests/Utils/Monitor/InterfaceInventoryUtil.test.ts +824 -0
  326. package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +626 -0
  327. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +718 -0
  328. package/Tests/Utils/NetworkDevice/DeviceHealthStateUtil.test.ts +59 -0
  329. package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +287 -2
  330. package/Tests/Utils/NetworkDiscovery/DiscoveryImportEligibility.test.ts +73 -0
  331. package/Tests/Utils/NetworkDiscovery/PingMonitorBuilder.test.ts +317 -0
  332. package/Tests/Utils/NetworkDiscovery/RescanIntervalUtil.test.ts +262 -0
  333. package/Tests/Utils/NetworkDiscovery/ScanModeUtil.test.ts +474 -0
  334. package/Tests/Utils/NetworkDiscovery/SnmpScanConfigUtil.test.ts +2442 -0
  335. package/Tests/Utils/NetworkSite/SiteMaintenanceUtil.test.ts +188 -0
  336. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +522 -0
  337. package/Tests/Utils/NetworkSite/SiteUptimeUtil.test.ts +694 -0
  338. package/Tests/Utils/SecurityEvent/ThreatIntel/StixPatternParser.test.ts +281 -0
  339. package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +87 -67
  340. package/Types/API/URL.ts +100 -28
  341. package/Types/AnalyticsDatabase/AnalyticsTableName.ts +8 -0
  342. package/Types/BaseDatabase/NotWildcard.ts +86 -0
  343. package/Types/BaseDatabase/Wildcard.ts +89 -0
  344. package/Types/BaseDatabase/WildcardPattern.ts +176 -0
  345. package/Types/Database/DateColumnValue.ts +184 -0
  346. package/Types/Database/TableColumn.ts +40 -5
  347. package/Types/Date.ts +37 -0
  348. package/Types/Email/EmailTemplateType.ts +1 -0
  349. package/Types/JSON.ts +8 -0
  350. package/Types/Log/LogQueryToFilter.ts +167 -129
  351. package/Types/Monitor/DockerAlertTemplates.ts +43 -12
  352. package/Types/Monitor/HostAlertTemplates.ts +31 -3
  353. package/Types/Monitor/KubernetesAlertTemplates.ts +98 -0
  354. package/Types/Monitor/MonitorStep.ts +49 -0
  355. package/Types/Monitor/PodmanAlertTemplates.ts +39 -12
  356. package/Types/Monitor/SnmpMonitor/CdpNeighbor.ts +14 -0
  357. package/Types/Monitor/SnmpMonitor/LldpNeighbor.ts +11 -0
  358. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +11 -4
  359. package/Types/NetworkAutomation/RuleRunResult.ts +191 -2
  360. package/Types/NetworkSite/SiteHealthRollupPolicy.ts +96 -0
  361. package/Types/Permission.ts +411 -0
  362. package/Types/Probe/ProbeApiIngestResponse.ts +42 -0
  363. package/Types/SecurityEvent/ThreatIntelConstants.ts +118 -0
  364. package/Types/SerializableObjectDictionary.ts +8 -0
  365. package/Types/Telemetry/SourceMap.ts +56 -0
  366. package/Types/Telemetry/TelemetrySearchQuery.ts +949 -0
  367. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +12 -1
  368. package/UI/Components/Charts/Utils/useHistogramZoom.ts +101 -0
  369. package/UI/Components/Dictionary/DictionaryFilterOperator.ts +83 -0
  370. package/UI/Components/Icon/Icon.tsx +28 -1
  371. package/UI/Components/LogsViewer/LogsViewer.tsx +19 -3
  372. package/UI/Components/LogsViewer/components/LogSearchBar.tsx +26 -4
  373. package/UI/Components/LogsViewer/components/LogSearchHelp.tsx +34 -16
  374. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +52 -1
  375. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +18 -2
  376. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +52 -1
  377. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +15 -2
  378. package/Utils/Metrics/MetricExplorerUrl.ts +77 -15
  379. package/Utils/Monitor/InterfaceInventoryUtil.ts +251 -0
  380. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +333 -0
  381. package/Utils/Monitor/NetworkTopologyUtil.ts +249 -28
  382. package/Utils/NetworkDevice/DeviceHealthStateUtil.ts +14 -6
  383. package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +2 -0
  384. package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +49 -18
  385. package/Utils/NetworkDiscovery/PingMonitorBuilder.ts +215 -0
  386. package/Utils/NetworkDiscovery/RescanIntervalUtil.ts +148 -0
  387. package/Utils/NetworkDiscovery/ScanModeUtil.ts +105 -0
  388. package/Utils/NetworkDiscovery/SnmpScanConfigUtil.ts +827 -0
  389. package/Utils/NetworkSite/SiteMaintenanceUtil.ts +110 -0
  390. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +350 -19
  391. package/Utils/NetworkSite/SiteUptimeUtil.ts +422 -21
  392. package/Utils/SecurityEvent/ThreatIntel/StixPatternParser.ts +383 -0
  393. package/Utils/Telemetry/CrossSignalScope.ts +37 -39
  394. package/build/dist/Models/AnalyticsModels/Index.js +2 -0
  395. package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
  396. package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js +325 -0
  397. package/build/dist/Models/AnalyticsModels/ThreatIntelIndicator.js.map +1 -0
  398. package/build/dist/Models/DatabaseModels/AlertSeverity.js +4 -1
  399. package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
  400. package/build/dist/Models/DatabaseModels/CephClusterFeed.js +648 -0
  401. package/build/dist/Models/DatabaseModels/CephClusterFeed.js.map +1 -0
  402. package/build/dist/Models/DatabaseModels/CloudResourceFeed.js +648 -0
  403. package/build/dist/Models/DatabaseModels/CloudResourceFeed.js.map +1 -0
  404. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +11 -0
  405. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  406. package/build/dist/Models/DatabaseModels/DetectionRule.js +9 -3
  407. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  408. package/build/dist/Models/DatabaseModels/DockerHostFeed.js +648 -0
  409. package/build/dist/Models/DatabaseModels/DockerHostFeed.js.map +1 -0
  410. package/build/dist/Models/DatabaseModels/DockerSwarmClusterFeed.js +648 -0
  411. package/build/dist/Models/DatabaseModels/DockerSwarmClusterFeed.js.map +1 -0
  412. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +9 -3
  413. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -1
  414. package/build/dist/Models/DatabaseModels/HostFeed.js +648 -0
  415. package/build/dist/Models/DatabaseModels/HostFeed.js.map +1 -0
  416. package/build/dist/Models/DatabaseModels/IncidentSeverity.js +4 -1
  417. package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
  418. package/build/dist/Models/DatabaseModels/Index.js +22 -0
  419. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  420. package/build/dist/Models/DatabaseModels/KubernetesClusterFeed.js +648 -0
  421. package/build/dist/Models/DatabaseModels/KubernetesClusterFeed.js.map +1 -0
  422. package/build/dist/Models/DatabaseModels/Monitor.js +81 -0
  423. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  424. package/build/dist/Models/DatabaseModels/NetworkDevice.js +103 -7
  425. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  426. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +91 -6
  427. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  428. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +276 -31
  429. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  430. package/build/dist/Models/DatabaseModels/NetworkSite.js +124 -1
  431. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  432. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  433. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  434. package/build/dist/Models/DatabaseModels/PodmanHostFeed.js +648 -0
  435. package/build/dist/Models/DatabaseModels/PodmanHostFeed.js.map +1 -0
  436. package/build/dist/Models/DatabaseModels/ProxmoxClusterFeed.js +648 -0
  437. package/build/dist/Models/DatabaseModels/ProxmoxClusterFeed.js.map +1 -0
  438. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +66 -0
  439. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  440. package/build/dist/Models/DatabaseModels/ServiceFeed.js +648 -0
  441. package/build/dist/Models/DatabaseModels/ServiceFeed.js.map +1 -0
  442. package/build/dist/Models/DatabaseModels/TelemetrySourceMap.js +496 -0
  443. package/build/dist/Models/DatabaseModels/TelemetrySourceMap.js.map +1 -0
  444. package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js +800 -0
  445. package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js.map +1 -0
  446. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -0
  447. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  448. package/build/dist/Models/DatabaseModels/UserTotpAuth.js +1 -0
  449. package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
  450. package/build/dist/Models/DatabaseModels/UserWebAuthn.js +1 -0
  451. package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
  452. package/build/dist/Server/API/AlertAPI.js +18 -1
  453. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  454. package/build/dist/Server/API/BaseAPI.js +8 -1
  455. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  456. package/build/dist/Server/API/IncidentAPI.js +35 -1
  457. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  458. package/build/dist/Server/API/IncidentEpisodeAPI.js +18 -1
  459. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  460. package/build/dist/Server/API/MicrosoftTeamsAPI.js +30 -2
  461. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  462. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +18 -1
  463. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  464. package/build/dist/Server/API/SlackAPI.js +45 -0
  465. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  466. package/build/dist/Server/API/TelemetryAPI.js +100 -35
  467. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  468. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js +64 -59
  469. package/build/dist/Server/API/UserOnCallLogTimelineAPI.js.map +1 -1
  470. package/build/dist/Server/API/UserTotpAuthAPI.js +65 -1
  471. package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
  472. package/build/dist/Server/API/UserWebAuthnAPI.js +42 -1
  473. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  474. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787923136162-MigrationName.js +26 -0
  475. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787923136162-MigrationName.js.map +1 -0
  476. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789600000000-AddAutoProvisionedNetworkDeviceMonitors.js +22 -0
  477. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789600000000-AddAutoProvisionedNetworkDeviceMonitors.js.map +1 -0
  478. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789700000000-AddNetworkScaleIndexes.js +88 -0
  479. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789700000000-AddNetworkScaleIndexes.js.map +1 -0
  480. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789800000000-WidenSecurityEventLastErrorColumns.js +39 -0
  481. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789800000000-WidenSecurityEventLastErrorColumns.js.map +1 -0
  482. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789900000000-AddNetworkSiteRollupPolicyAndMaintenance.js +34 -0
  483. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789900000000-AddNetworkSiteRollupPolicyAndMaintenance.js.map +1 -0
  484. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000000-AddTelemetrySourceMap.js +32 -0
  485. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000000-AddTelemetrySourceMap.js.map +1 -0
  486. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000001-AddSnmpConfigsToNetworkDeviceDiscoveryScan.js +61 -0
  487. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790000000001-AddSnmpConfigsToNetworkDeviceDiscoveryScan.js.map +1 -0
  488. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790003445000-AddSnmpEnabledToNetworkDeviceDiscoveryScan.js +23 -0
  489. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790003445000-AddSnmpEnabledToNetworkDeviceDiscoveryScan.js.map +1 -0
  490. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790100000000-AddResourceActivityFeeds.js +179 -0
  491. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790100000000-AddResourceActivityFeeds.js.map +1 -0
  492. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +18 -0
  493. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  494. package/build/dist/Server/Middleware/IdentityRateLimit.js +40 -3
  495. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  496. package/build/dist/Server/Services/AIService.js +177 -30
  497. package/build/dist/Server/Services/AIService.js.map +1 -1
  498. package/build/dist/Server/Services/CephClusterFeedService.js +89 -0
  499. package/build/dist/Server/Services/CephClusterFeedService.js.map +1 -0
  500. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +27 -3
  501. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -1
  502. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +21 -0
  503. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
  504. package/build/dist/Server/Services/CephClusterOwnerTeamService.js +120 -0
  505. package/build/dist/Server/Services/CephClusterOwnerTeamService.js.map +1 -1
  506. package/build/dist/Server/Services/CephClusterOwnerUserService.js +113 -0
  507. package/build/dist/Server/Services/CephClusterOwnerUserService.js.map +1 -1
  508. package/build/dist/Server/Services/CephClusterService.js +175 -1
  509. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  510. package/build/dist/Server/Services/CloudResourceFeedService.js +89 -0
  511. package/build/dist/Server/Services/CloudResourceFeedService.js.map +1 -0
  512. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +27 -3
  513. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -1
  514. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +28 -3
  515. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
  516. package/build/dist/Server/Services/CloudResourceOwnerTeamService.js +120 -0
  517. package/build/dist/Server/Services/CloudResourceOwnerTeamService.js.map +1 -1
  518. package/build/dist/Server/Services/CloudResourceOwnerUserService.js +113 -0
  519. package/build/dist/Server/Services/CloudResourceOwnerUserService.js.map +1 -1
  520. package/build/dist/Server/Services/CloudResourceService.js +177 -1
  521. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  522. package/build/dist/Server/Services/DatabaseService.js +201 -0
  523. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  524. package/build/dist/Server/Services/DockerHostFeedService.js +89 -0
  525. package/build/dist/Server/Services/DockerHostFeedService.js.map +1 -0
  526. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js +27 -3
  527. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js.map +1 -1
  528. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +21 -0
  529. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
  530. package/build/dist/Server/Services/DockerHostOwnerTeamService.js +120 -0
  531. package/build/dist/Server/Services/DockerHostOwnerTeamService.js.map +1 -1
  532. package/build/dist/Server/Services/DockerHostOwnerUserService.js +113 -0
  533. package/build/dist/Server/Services/DockerHostOwnerUserService.js.map +1 -1
  534. package/build/dist/Server/Services/DockerHostService.js +177 -1
  535. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  536. package/build/dist/Server/Services/DockerSwarmClusterFeedService.js +90 -0
  537. package/build/dist/Server/Services/DockerSwarmClusterFeedService.js.map +1 -0
  538. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js +27 -3
  539. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js.map +1 -1
  540. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +21 -0
  541. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
  542. package/build/dist/Server/Services/DockerSwarmClusterOwnerTeamService.js +120 -0
  543. package/build/dist/Server/Services/DockerSwarmClusterOwnerTeamService.js.map +1 -1
  544. package/build/dist/Server/Services/DockerSwarmClusterOwnerUserService.js +113 -0
  545. package/build/dist/Server/Services/DockerSwarmClusterOwnerUserService.js.map +1 -1
  546. package/build/dist/Server/Services/DockerSwarmClusterService.js +175 -1
  547. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  548. package/build/dist/Server/Services/EnterpriseLicenseService.js +40 -30
  549. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  550. package/build/dist/Server/Services/ExceptionAggregationService.js +8 -2
  551. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
  552. package/build/dist/Server/Services/HostFeedService.js +89 -0
  553. package/build/dist/Server/Services/HostFeedService.js.map +1 -0
  554. package/build/dist/Server/Services/HostLabelRuleEngineService.js +27 -3
  555. package/build/dist/Server/Services/HostLabelRuleEngineService.js.map +1 -1
  556. package/build/dist/Server/Services/HostOwnerRuleEngineService.js +21 -0
  557. package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
  558. package/build/dist/Server/Services/HostOwnerTeamService.js +120 -0
  559. package/build/dist/Server/Services/HostOwnerTeamService.js.map +1 -1
  560. package/build/dist/Server/Services/HostOwnerUserService.js +113 -0
  561. package/build/dist/Server/Services/HostOwnerUserService.js.map +1 -1
  562. package/build/dist/Server/Services/HostService.js +175 -1
  563. package/build/dist/Server/Services/HostService.js.map +1 -1
  564. package/build/dist/Server/Services/IncidentService.js +10 -0
  565. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  566. package/build/dist/Server/Services/Index.js +24 -0
  567. package/build/dist/Server/Services/Index.js.map +1 -1
  568. package/build/dist/Server/Services/KubernetesClusterFeedService.js +89 -0
  569. package/build/dist/Server/Services/KubernetesClusterFeedService.js.map +1 -0
  570. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js +27 -3
  571. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js.map +1 -1
  572. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +21 -0
  573. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
  574. package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js +120 -0
  575. package/build/dist/Server/Services/KubernetesClusterOwnerTeamService.js.map +1 -1
  576. package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js +113 -0
  577. package/build/dist/Server/Services/KubernetesClusterOwnerUserService.js.map +1 -1
  578. package/build/dist/Server/Services/KubernetesClusterService.js +177 -1
  579. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  580. package/build/dist/Server/Services/LogAggregationService.js +19 -196
  581. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  582. package/build/dist/Server/Services/MetricAggregationService.js +59 -0
  583. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  584. package/build/dist/Server/Services/MonitorService.js +115 -13
  585. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  586. package/build/dist/Server/Services/MonitorTemplateService.js +86 -0
  587. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  588. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +561 -67
  589. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  590. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +130 -16
  591. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
  592. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +850 -8
  593. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  594. package/build/dist/Server/Services/NetworkDeviceService.js +594 -5
  595. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  596. package/build/dist/Server/Services/NetworkInterfaceService.js +306 -0
  597. package/build/dist/Server/Services/NetworkInterfaceService.js.map +1 -1
  598. package/build/dist/Server/Services/NetworkSiteService.js +408 -47
  599. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  600. package/build/dist/Server/Services/PodmanHostFeedService.js +89 -0
  601. package/build/dist/Server/Services/PodmanHostFeedService.js.map +1 -0
  602. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js +27 -3
  603. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js.map +1 -1
  604. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +21 -0
  605. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
  606. package/build/dist/Server/Services/PodmanHostOwnerTeamService.js +120 -0
  607. package/build/dist/Server/Services/PodmanHostOwnerTeamService.js.map +1 -1
  608. package/build/dist/Server/Services/PodmanHostOwnerUserService.js +113 -0
  609. package/build/dist/Server/Services/PodmanHostOwnerUserService.js.map +1 -1
  610. package/build/dist/Server/Services/PodmanHostService.js +177 -1
  611. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  612. package/build/dist/Server/Services/ProjectService.js +40 -30
  613. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  614. package/build/dist/Server/Services/ProxmoxClusterFeedService.js +89 -0
  615. package/build/dist/Server/Services/ProxmoxClusterFeedService.js.map +1 -0
  616. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +27 -3
  617. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -1
  618. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +21 -0
  619. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
  620. package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js +120 -0
  621. package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js.map +1 -1
  622. package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js +113 -0
  623. package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js.map +1 -1
  624. package/build/dist/Server/Services/ProxmoxClusterService.js +175 -1
  625. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  626. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +56 -0
  627. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  628. package/build/dist/Server/Services/ServiceFeedService.js +89 -0
  629. package/build/dist/Server/Services/ServiceFeedService.js.map +1 -0
  630. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js +27 -3
  631. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js.map +1 -1
  632. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +24 -0
  633. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
  634. package/build/dist/Server/Services/ServiceOwnerTeamService.js +120 -0
  635. package/build/dist/Server/Services/ServiceOwnerTeamService.js.map +1 -1
  636. package/build/dist/Server/Services/ServiceOwnerUserService.js +113 -0
  637. package/build/dist/Server/Services/ServiceOwnerUserService.js.map +1 -1
  638. package/build/dist/Server/Services/ServiceService.js +173 -2
  639. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  640. package/build/dist/Server/Services/StatusPageSubscriberService.js +7 -0
  641. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  642. package/build/dist/Server/Services/TelemetryAttributeService.js +9 -3
  643. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  644. package/build/dist/Server/Services/TelemetrySourceMapService.js +265 -0
  645. package/build/dist/Server/Services/TelemetrySourceMapService.js.map +1 -0
  646. package/build/dist/Server/Services/ThreatIntelFeedService.js +157 -0
  647. package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -0
  648. package/build/dist/Server/Services/ThreatIntelIndicatorService.js +276 -0
  649. package/build/dist/Server/Services/ThreatIntelIndicatorService.js.map +1 -0
  650. package/build/dist/Server/Services/TraceAggregationService.js +62 -6
  651. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  652. package/build/dist/Server/Services/UserService.js +22 -13
  653. package/build/dist/Server/Services/UserService.js.map +1 -1
  654. package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js +120 -0
  655. package/build/dist/Server/Services/UserTwoFactorBackupCodeService.js.map +1 -1
  656. package/build/dist/Server/Services/UserWebhookService.js +11 -2
  657. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  658. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +12 -1
  659. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  660. package/build/dist/Server/Types/Database/AggregateBy.js +2 -0
  661. package/build/dist/Server/Types/Database/AggregateBy.js.map +1 -0
  662. package/build/dist/Server/Types/Database/AggregateResultUtil.js +90 -0
  663. package/build/dist/Server/Types/Database/AggregateResultUtil.js.map +1 -0
  664. package/build/dist/Server/Types/Database/JSONColumnQuery.js +14 -0
  665. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
  666. package/build/dist/Server/Types/Database/QueryHelper.js +42 -0
  667. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  668. package/build/dist/Server/Types/Database/QueryUtil.js +18 -0
  669. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  670. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +10 -1
  671. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  672. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
  673. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  674. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +8 -0
  675. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  676. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +15 -0
  677. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  678. package/build/dist/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.js +266 -0
  679. package/build/dist/Server/Utils/AnalyticsDatabase/AttributeFilterStatement.js.map +1 -0
  680. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +134 -0
  681. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  682. package/build/dist/Server/Utils/DataSource/HttpFetch.js +8 -0
  683. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
  684. package/build/dist/Server/Utils/Database/RelationIdUtil.js +31 -0
  685. package/build/dist/Server/Utils/Database/RelationIdUtil.js.map +1 -1
  686. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +26 -8
  687. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
  688. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +42 -17
  689. package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
  690. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +9 -1
  691. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  692. package/build/dist/Server/Utils/Monitor/Criteria/SyntheticMonitor.js +15 -2
  693. package/build/dist/Server/Utils/Monitor/Criteria/SyntheticMonitor.js.map +1 -1
  694. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +386 -271
  695. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  696. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +278 -64
  697. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  698. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +469 -350
  699. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  700. package/build/dist/Server/Utils/Monitor/MonitorResource.js +183 -44
  701. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  702. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +46 -3
  703. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -1
  704. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +18 -91
  705. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  706. package/build/dist/Server/Utils/Monitor/PerEntityCriteriaFanOut.js +194 -0
  707. package/build/dist/Server/Utils/Monitor/PerEntityCriteriaFanOut.js.map +1 -0
  708. package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js +332 -0
  709. package/build/dist/Server/Utils/NetworkDevice/DeviceHealthAggregation.js.map +1 -0
  710. package/build/dist/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.js +279 -0
  711. package/build/dist/Server/Utils/NetworkSite/NetworkSiteMaintenanceSuppression.js.map +1 -0
  712. package/build/dist/Server/Utils/OnCallNotificationContext.js +415 -0
  713. package/build/dist/Server/Utils/OnCallNotificationContext.js.map +1 -0
  714. package/build/dist/Server/Utils/PrivateNetworkWebhookConfig.js +424 -0
  715. package/build/dist/Server/Utils/PrivateNetworkWebhookConfig.js.map +1 -0
  716. package/build/dist/Server/Utils/ResourceFeed/ResourceFeedUtil.js +108 -0
  717. package/build/dist/Server/Utils/ResourceFeed/ResourceFeedUtil.js.map +1 -0
  718. package/build/dist/Server/Utils/Response.js +12 -1
  719. package/build/dist/Server/Utils/Response.js.map +1 -1
  720. package/build/dist/Server/Utils/SSRFProtection.js +160 -58
  721. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  722. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js +89 -0
  723. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js.map +1 -0
  724. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +75 -234
  725. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  726. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +41 -16
  727. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  728. package/build/dist/Server/Utils/SecurityEvent/SecurityEventAlerting.js +217 -0
  729. package/build/dist/Server/Utils/SecurityEvent/SecurityEventAlerting.js.map +1 -0
  730. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.js +103 -0
  731. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/TaxiiClient.js.map +1 -0
  732. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.js +168 -0
  733. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher.js.map +1 -0
  734. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +527 -0
  735. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -0
  736. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +376 -0
  737. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -0
  738. package/build/dist/Server/Utils/StartServer.js +70 -44
  739. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  740. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +3 -0
  741. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
  742. package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js +327 -0
  743. package/build/dist/Server/Utils/Telemetry/SourceMapResolver.js.map +1 -0
  744. package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js +84 -0
  745. package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js.map +1 -0
  746. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  747. package/build/dist/Server/Utils/VM/VMRunner.js +10 -1
  748. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  749. package/build/dist/Server/Utils/VendorAssets.js +29 -0
  750. package/build/dist/Server/Utils/VendorAssets.js.map +1 -1
  751. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +121 -6
  752. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  753. package/build/dist/Types/API/URL.js +88 -27
  754. package/build/dist/Types/API/URL.js.map +1 -1
  755. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +8 -0
  756. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
  757. package/build/dist/Types/BaseDatabase/NotWildcard.js +72 -0
  758. package/build/dist/Types/BaseDatabase/NotWildcard.js.map +1 -0
  759. package/build/dist/Types/BaseDatabase/Wildcard.js +75 -0
  760. package/build/dist/Types/BaseDatabase/Wildcard.js.map +1 -0
  761. package/build/dist/Types/BaseDatabase/WildcardPattern.js +137 -0
  762. package/build/dist/Types/BaseDatabase/WildcardPattern.js.map +1 -0
  763. package/build/dist/Types/Database/DateColumnValue.js +125 -0
  764. package/build/dist/Types/Database/DateColumnValue.js.map +1 -0
  765. package/build/dist/Types/Database/TableColumn.js +18 -2
  766. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  767. package/build/dist/Types/Date.js +31 -0
  768. package/build/dist/Types/Date.js.map +1 -1
  769. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  770. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  771. package/build/dist/Types/JSON.js +2 -0
  772. package/build/dist/Types/JSON.js.map +1 -1
  773. package/build/dist/Types/Log/LogQueryToFilter.js +111 -108
  774. package/build/dist/Types/Log/LogQueryToFilter.js.map +1 -1
  775. package/build/dist/Types/Monitor/DockerAlertTemplates.js +40 -16
  776. package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -1
  777. package/build/dist/Types/Monitor/HostAlertTemplates.js +29 -7
  778. package/build/dist/Types/Monitor/HostAlertTemplates.js.map +1 -1
  779. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +98 -4
  780. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  781. package/build/dist/Types/Monitor/MonitorStep.js +40 -0
  782. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  783. package/build/dist/Types/Monitor/PodmanAlertTemplates.js +36 -16
  784. package/build/dist/Types/Monitor/PodmanAlertTemplates.js.map +1 -1
  785. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +90 -0
  786. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  787. package/build/dist/Types/NetworkSite/SiteHealthRollupPolicy.js +84 -0
  788. package/build/dist/Types/NetworkSite/SiteHealthRollupPolicy.js.map +1 -0
  789. package/build/dist/Types/Permission.js +354 -0
  790. package/build/dist/Types/Permission.js.map +1 -1
  791. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +100 -0
  792. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -0
  793. package/build/dist/Types/SerializableObjectDictionary.js +8 -0
  794. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  795. package/build/dist/Types/Telemetry/SourceMap.js +9 -0
  796. package/build/dist/Types/Telemetry/SourceMap.js.map +1 -0
  797. package/build/dist/Types/Telemetry/TelemetrySearchQuery.js +661 -0
  798. package/build/dist/Types/Telemetry/TelemetrySearchQuery.js.map +1 -0
  799. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +12 -1
  800. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  801. package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js +47 -0
  802. package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js.map +1 -0
  803. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js +67 -0
  804. package/build/dist/UI/Components/Dictionary/DictionaryFilterOperator.js.map +1 -1
  805. package/build/dist/UI/Components/Icon/Icon.js +28 -1
  806. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  807. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +15 -3
  808. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  809. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +25 -4
  810. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
  811. package/build/dist/UI/Components/LogsViewer/components/LogSearchHelp.js +36 -16
  812. package/build/dist/UI/Components/LogsViewer/components/LogSearchHelp.js.map +1 -1
  813. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +33 -5
  814. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  815. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +14 -2
  816. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  817. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +33 -5
  818. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  819. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +14 -2
  820. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  821. package/build/dist/Utils/Metrics/MetricExplorerUrl.js +54 -9
  822. package/build/dist/Utils/Metrics/MetricExplorerUrl.js.map +1 -1
  823. package/build/dist/Utils/Monitor/InterfaceInventoryUtil.js +128 -0
  824. package/build/dist/Utils/Monitor/InterfaceInventoryUtil.js.map +1 -0
  825. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +190 -0
  826. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -0
  827. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +206 -28
  828. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  829. package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js +14 -7
  830. package/build/dist/Utils/NetworkDevice/DeviceHealthStateUtil.js.map +1 -1
  831. package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -1
  832. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +34 -18
  833. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
  834. package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js +163 -0
  835. package/build/dist/Utils/NetworkDiscovery/PingMonitorBuilder.js.map +1 -0
  836. package/build/dist/Utils/NetworkDiscovery/RescanIntervalUtil.js +99 -0
  837. package/build/dist/Utils/NetworkDiscovery/RescanIntervalUtil.js.map +1 -0
  838. package/build/dist/Utils/NetworkDiscovery/ScanModeUtil.js +85 -0
  839. package/build/dist/Utils/NetworkDiscovery/ScanModeUtil.js.map +1 -0
  840. package/build/dist/Utils/NetworkDiscovery/SnmpScanConfigUtil.js +560 -0
  841. package/build/dist/Utils/NetworkDiscovery/SnmpScanConfigUtil.js.map +1 -0
  842. package/build/dist/Utils/NetworkSite/SiteMaintenanceUtil.js +56 -0
  843. package/build/dist/Utils/NetworkSite/SiteMaintenanceUtil.js.map +1 -0
  844. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +185 -10
  845. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  846. package/build/dist/Utils/NetworkSite/SiteUptimeUtil.js +248 -21
  847. package/build/dist/Utils/NetworkSite/SiteUptimeUtil.js.map +1 -1
  848. package/build/dist/Utils/SecurityEvent/ThreatIntel/StixPatternParser.js +255 -0
  849. package/build/dist/Utils/SecurityEvent/ThreatIntel/StixPatternParser.js.map +1 -0
  850. package/build/dist/Utils/Telemetry/CrossSignalScope.js +27 -30
  851. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  852. package/package.json +2 -1
  853. package/Tests/Types/Log/LogQueryParser.test.ts +0 -186
  854. package/Types/Log/LogQueryParser.ts +0 -252
  855. package/build/dist/Types/Log/LogQueryParser.js +0 -200
  856. 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("snmpPort");
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
- ["a fractional port", 161.5],
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
- expect(validate("snmpPort", { snmpPort: value })).not.toBeNull();
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(validate("snmpPort", { snmpPort: 161 })).toBeNull();
474
- expect(validate("snmpPort", { snmpPort: "" })).toBeNull();
475
- expect(validate("snmpPort", {})).toBeNull();
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 name that cannot be corrected is worse than no name: "Region 1100" on
645
- * the wrong range misleads everyone who reads the list afterwards, and the
646
- * only other way to fix it would be deleting the scan and its results.
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 renamed after it was created", async () => {
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("Rename");
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 one field ModelForm has already dropped for want of the
664
- * update permission — a box that cannot be saved and does not say why.
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("renaming is offered only to someone who could save it", async () => {
1374
+ test("editing is offered only to someone who could save it", async () => {
667
1375
  await renderPage();
668
1376
 
669
- const rename: CapturedActionButton | undefined = (
1377
+ const edit: CapturedActionButton | undefined = (
670
1378
  capturedTableProps?.actionButtons || []
671
1379
  ).find((button: CapturedActionButton): boolean => {
672
- return button.title === "Rename";
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(rename?.isVisible?.(scan)).toBe(false);
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(rename?.isVisible?.(scan)).toBe(true);
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
  });