@oneuptime/common 12.0.34 → 13.0.1

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 (952) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/CloudResource.ts +45 -7
  3. package/Models/DatabaseModels/CodeRepository.ts +85 -0
  4. package/Models/DatabaseModels/GlobalConfig.ts +12 -12
  5. package/Models/DatabaseModels/GoogleSecOpsConnection.ts +57 -7
  6. package/Models/DatabaseModels/GoogleSecOpsConnectionRun.ts +263 -0
  7. package/Models/DatabaseModels/Incident.ts +55 -0
  8. package/Models/DatabaseModels/Index.ts +16 -0
  9. package/Models/DatabaseModels/InventoryItem.ts +44 -1
  10. package/Models/DatabaseModels/NetworkDevice.ts +126 -0
  11. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  12. package/Models/DatabaseModels/User.ts +51 -2
  13. package/Models/DatabaseModels/UserNotificationEmailRollupItem.ts +43 -2
  14. package/Models/DatabaseModels/UserWebAuthn.ts +17 -0
  15. package/Models/DatabaseModels/VMwareResource.ts +1149 -0
  16. package/Models/DatabaseModels/VMwareVCenter.ts +1122 -0
  17. package/Models/DatabaseModels/VMwareVCenterFeed.ts +625 -0
  18. package/Models/DatabaseModels/VMwareVCenterLabelRule.ts +514 -0
  19. package/Models/DatabaseModels/VMwareVCenterOwnerRule.ts +596 -0
  20. package/Models/DatabaseModels/VMwareVCenterOwnerTeam.ts +485 -0
  21. package/Models/DatabaseModels/VMwareVCenterOwnerUser.ts +484 -0
  22. package/Models/DatabaseModels/WorkflowVariable.ts +22 -1
  23. package/Server/API/AIAgentDataAPI.ts +79 -4
  24. package/Server/API/AIAgentGitHubAPI.ts +523 -0
  25. package/Server/API/AIChatAPI.ts +20 -1
  26. package/Server/API/BillingAPI.ts +33 -0
  27. package/Server/API/DashboardAPI.ts +10 -0
  28. package/Server/API/GitHubAPI.ts +47 -5
  29. package/Server/API/GoogleSecOpsConnectionAPI.ts +79 -0
  30. package/Server/API/UserWebAuthnAPI.ts +21 -1
  31. package/Server/API/VMwareResourceAPI.ts +143 -0
  32. package/Server/EnvironmentConfig.ts +47 -16
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1791800000000-SplitWebAuthnChallengeByPurpose.ts +68 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1791900000000-AddSeverityAndStateToNotificationEmailRollup.ts +26 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1792000000000-AddVMwareTables.ts +636 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/1792100000000-AddGitHubCommandSettingsToCodeRepository.ts +47 -0
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1792200000000-AddUserWebAuthnPurpose.ts +17 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/1792300000000-AddGoogleSecOpsDiagnostics.ts +83 -0
  40. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  41. package/Server/Infrastructure/Redis.ts +8 -8
  42. package/Server/Infrastructure/Status.ts +10 -4
  43. package/Server/Middleware/IdentityRateLimit.ts +101 -0
  44. package/Server/Services/AIRunService.ts +13 -0
  45. package/Server/Services/BillingPaymentMethodService.ts +99 -28
  46. package/Server/Services/BillingService.ts +287 -20
  47. package/Server/Services/CloudResourceInstanceService.ts +70 -0
  48. package/Server/Services/DatabaseService.ts +65 -13
  49. package/Server/Services/ExceptionAggregationService.ts +1 -0
  50. package/Server/Services/GoogleSecOpsConnectionRunService.ts +10 -0
  51. package/Server/Services/GoogleSecOpsConnectionService.ts +12 -0
  52. package/Server/Services/Index.ts +16 -0
  53. package/Server/Services/InventoryItemService.ts +31 -1
  54. package/Server/Services/LogAggregationService.ts +1 -0
  55. package/Server/Services/MetricAggregationService.ts +1 -0
  56. package/Server/Services/MobilePasskeyLoginService.ts +216 -0
  57. package/Server/Services/MonitorService.ts +51 -1
  58. package/Server/Services/MonitorTemplateService.ts +218 -35
  59. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +481 -42
  60. package/Server/Services/NetworkDeviceAutoImportRuleService.ts +176 -2
  61. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
  62. package/Server/Services/NetworkDeviceService.ts +69 -0
  63. package/Server/Services/OpenTelemetryIngestService.ts +17 -0
  64. package/Server/Services/PayAsYouGoBillingService.ts +446 -0
  65. package/Server/Services/ProjectService.ts +46 -2
  66. package/Server/Services/ServerlessFunctionInstanceService.ts +71 -0
  67. package/Server/Services/TeamMemberService.ts +96 -36
  68. package/Server/Services/TelemetryIngestionKeyService.ts +80 -10
  69. package/Server/Services/TelemetryUsageBillingService.ts +95 -5
  70. package/Server/Services/TraceAggregationService.ts +5 -4
  71. package/Server/Services/UserNotificationRuleService.ts +76 -25
  72. package/Server/Services/UserWebAuthnService.ts +592 -133
  73. package/Server/Services/VMwareResourceService.ts +648 -0
  74. package/Server/Services/VMwareVCenterFeedService.ts +99 -0
  75. package/Server/Services/VMwareVCenterLabelRuleEngineService.ts +240 -0
  76. package/Server/Services/VMwareVCenterLabelRuleService.ts +14 -0
  77. package/Server/Services/VMwareVCenterOwnerRuleEngineService.ts +252 -0
  78. package/Server/Services/VMwareVCenterOwnerRuleService.ts +14 -0
  79. package/Server/Services/VMwareVCenterOwnerTeamService.ts +139 -0
  80. package/Server/Services/VMwareVCenterOwnerUserService.ts +133 -0
  81. package/Server/Services/VMwareVCenterService.ts +629 -0
  82. package/Server/Services/WorkflowVariableService.ts +203 -0
  83. package/Server/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts +17 -0
  84. package/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +31 -1
  85. package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
  86. package/Server/Types/Database/QueryUtil.ts +56 -0
  87. package/Server/Types/Markdown.ts +93 -1
  88. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +7 -0
  89. package/Server/Utils/CodeRepository/GitHub/GitHubAgentTaskTrigger.ts +341 -0
  90. package/Server/Utils/CodeRepository/GitHub/GitHubCommandAuthorizer.ts +274 -0
  91. package/Server/Utils/CodeRepository/GitHub/GitHubCommandParser.ts +375 -0
  92. package/Server/Utils/CodeRepository/GitHub/GitHubConversation.ts +846 -0
  93. package/Server/Utils/CodeRepository/GitHub/GitHubReplyComposer.ts +360 -0
  94. package/Server/Utils/CodeRepository/GitHub/GitHubRunReply.ts +421 -0
  95. package/Server/Utils/CodeRepository/GitHub/GitHubWebhookEvents.ts +140 -0
  96. package/Server/Utils/CodeRepository/GitHub/GitHubWebhookHandler.ts +906 -0
  97. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +225 -17
  98. package/Server/Utils/DataSource/EgressGuard.ts +160 -15
  99. package/Server/Utils/Database/TruncateColumnValue.ts +1 -1
  100. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +12 -1
  101. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +35 -2
  102. package/Server/Utils/EmailRollup/EmailRollupWriter.ts +44 -0
  103. package/Server/Utils/LogRedaction.ts +2 -1
  104. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
  105. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
  106. package/Server/Utils/Monitor/MonitorAlert.ts +1 -1
  107. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +502 -38
  108. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
  109. package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
  110. package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
  111. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
  112. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +26 -4
  113. package/Server/Utils/Monitor/MonitorResourceContext.ts +5 -3
  114. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +14 -0
  115. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
  116. package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
  117. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
  118. package/Server/Utils/Monitor/SeriesResourceLabels.ts +26 -0
  119. package/Server/Utils/Monitor/SeriesResourceLinker.ts +28 -5
  120. package/Server/Utils/ResourceFeed/ResourceFeedUtil.ts +3 -3
  121. package/Server/Utils/SecurityEvent/ConnectorErrorMessage.ts +14 -9
  122. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +116 -84
  123. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +594 -195
  124. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.ts +466 -0
  125. package/Server/Utils/Telemetry/EntityRegistry.ts +16 -1
  126. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +2 -1
  127. package/Server/Utils/Telemetry/TelemetryEntity.ts +264 -10
  128. package/Server/Utils/Telemetry/VMwareSnapshotScan.ts +1352 -0
  129. package/Tests/App/Accounts/LoginPasskey.test.tsx +529 -0
  130. package/Tests/App/Accounts/MobilePasskey.test.tsx +620 -0
  131. package/Tests/App/Accounts/PasswordRequirements.test.tsx +254 -0
  132. package/Tests/App/Accounts/RegisterPassword.test.tsx +553 -0
  133. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
  134. package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
  135. package/Tests/App/Dashboard/AskAiAvailability.test.ts +224 -0
  136. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +432 -0
  137. package/Tests/App/Dashboard/BackupCodes.test.tsx +414 -0
  138. package/Tests/App/Dashboard/BillingPaidUsageFlow.test.tsx +530 -0
  139. package/Tests/App/Dashboard/BillingPaymentMethodForm.test.tsx +195 -0
  140. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +199 -0
  141. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +198 -0
  142. package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +215 -0
  143. package/Tests/App/Dashboard/CloudFleetSummary.test.ts +183 -0
  144. package/Tests/App/Dashboard/CloudFleetSummaryComponent.test.tsx +248 -0
  145. package/Tests/App/Dashboard/CloudResourceTelemetryScope.test.ts +215 -0
  146. package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +3 -0
  147. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +441 -0
  148. package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
  149. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +458 -0
  150. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
  151. package/Tests/App/Dashboard/DashboardToolbarPopupLayering.test.tsx +566 -0
  152. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
  153. package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
  154. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
  155. package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +376 -0
  156. package/Tests/App/Dashboard/DiscoveryScanLiveUpdates.test.tsx +642 -0
  157. package/Tests/App/Dashboard/DiscoveryScanProgress.test.tsx +267 -0
  158. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +69 -231
  159. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
  160. package/Tests/App/Dashboard/GoogleSecOpsConnectionsErrors.test.tsx +244 -0
  161. package/Tests/App/Dashboard/GoogleSecOpsDiagnostics.test.tsx +724 -0
  162. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
  163. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
  164. package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
  165. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
  166. package/Tests/App/Dashboard/MonitorTemplateSyncFields.test.tsx +539 -0
  167. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
  168. package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
  169. package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
  170. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
  171. package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +24 -7
  172. package/Tests/App/Dashboard/OnCallOverridePresentation.test.ts +496 -0
  173. package/Tests/App/Dashboard/OnCallScheduleOverrideClarity.test.tsx +677 -0
  174. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +31 -3
  175. package/Tests/App/Dashboard/PaidUsageConsent.test.tsx +180 -0
  176. package/Tests/App/Dashboard/PasskeySettings.test.tsx +1220 -0
  177. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +176 -275
  178. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +380 -233
  179. package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +3 -23
  180. package/Tests/App/Dashboard/ResourceFeed.test.tsx +1 -0
  181. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  182. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
  183. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
  184. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
  185. package/Tests/App/Dashboard/TwoFactorStatus.test.tsx +260 -0
  186. package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
  187. package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
  188. package/Tests/App/Dashboard/VMwareDashboardWidgets.test.ts +573 -0
  189. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  190. package/Tests/App/Dashboard/WorkflowVariablesTable.test.tsx +768 -0
  191. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
  192. package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
  193. package/Tests/App/StatusPage/StatusPageOverviewLiveAndSearch.test.tsx +532 -40
  194. package/Tests/App/StatusPage/StatusPageResourceSearchBox.test.tsx +19 -2
  195. package/Tests/Fixtures/MobilePasskeyUser.json +8 -0
  196. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +25 -6
  197. package/Tests/Models/DatabaseModels/VMwareModels.test.ts +1134 -0
  198. package/Tests/Models/InventoryItemStatus.test.ts +147 -0
  199. package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
  200. package/Tests/Models/RunnerVersionLabel.test.ts +4 -2
  201. package/Tests/Server/API/AIChatProvidersAIEnabled.test.ts +259 -0
  202. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +31 -2
  203. package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +116 -0
  204. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +158 -1
  205. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +13 -2
  206. package/Tests/Server/API/GitHubWebhookSignature.test.ts +368 -0
  207. package/Tests/Server/API/GoogleSecOpsConnectionAPI.test.ts +248 -0
  208. package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +330 -0
  209. package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +172 -0
  210. package/Tests/Server/API/VMwareResourceAPI.test.ts +546 -0
  211. package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
  212. package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
  213. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
  214. package/Tests/Server/Infrastructure/Postgres/UserNotificationEmailRollupMetadataMigration.test.ts +85 -0
  215. package/Tests/Server/Infrastructure/Postgres/UserNotificationEmailRollupTableMigration.test.ts +34 -3
  216. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
  217. package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
  218. package/Tests/Server/Middleware/PasskeyRateLimit.test.ts +141 -0
  219. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
  220. package/Tests/Server/Services/AutoImportRuleRearmRecentScans.test.ts +343 -0
  221. package/Tests/Server/Services/AutoImportRuleWriteRearmsScans.test.ts +381 -0
  222. package/Tests/Server/Services/BillingPaymentMethodServiceSync.test.ts +175 -0
  223. package/Tests/Server/Services/BillingService.test.ts +26 -11
  224. package/Tests/Server/Services/BillingServiceMeteredPaymentAuthorization.test.ts +245 -0
  225. package/Tests/Server/Services/BillingServicePaymentMethodReads.test.ts +362 -0
  226. package/Tests/Server/Services/CloudResourceInstanceStalePrune.test.ts +188 -0
  227. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
  228. package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
  229. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +16 -2
  230. package/Tests/Server/Services/GoogleSecOpsDiagnosticsModel.test.ts +153 -0
  231. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +13 -0
  232. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
  233. package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +183 -0
  234. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +36 -0
  235. package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +151 -0
  236. package/Tests/Server/Services/MobilePasskeyLoginIntegration.test.ts +231 -0
  237. package/Tests/Server/Services/MobilePasskeyLoginService.test.ts +315 -0
  238. package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +302 -0
  239. package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
  240. package/Tests/Server/Services/MonitorTemplateServiceFieldSync.test.ts +994 -0
  241. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
  242. package/Tests/Server/Services/NetworkDeviceAutoImportRuleService.test.ts +22 -0
  243. package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
  244. package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
  245. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
  246. package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +857 -0
  247. package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +379 -0
  248. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +99 -1
  249. package/Tests/Server/Services/ProjectServiceSubscriptionSeats.test.ts +134 -0
  250. package/Tests/Server/Services/ResourceFeedServices.test.ts +3 -2
  251. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +32 -6
  252. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +363 -175
  253. package/Tests/Server/Services/ServerlessFunctionInstanceStalePrune.test.ts +200 -0
  254. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +3 -0
  255. package/Tests/Server/Services/TeamMemberBillingSeatSync.test.ts +762 -0
  256. package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +3 -0
  257. package/Tests/Server/Services/TelemetryIngestionKeyPolicyResolution.test.ts +72 -0
  258. package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +17 -4
  259. package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +355 -0
  260. package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +41 -4
  261. package/Tests/Server/Services/UserNotificationEmailRollupModels.test.ts +37 -0
  262. package/Tests/Server/Services/UserWebAuthnPasskeyIntegration.test.ts +401 -0
  263. package/Tests/Server/Services/UserWebAuthnPasskeyService.test.ts +769 -0
  264. package/Tests/Server/Services/UserWebAuthnUserVerification.test.ts +649 -0
  265. package/Tests/Server/Services/VMwareResourceService.test.ts +945 -0
  266. package/Tests/Server/Services/VMwareVCenterService.test.ts +722 -0
  267. package/Tests/Server/Services/WorkflowVariableRenameUniqueness.test.ts +842 -0
  268. package/Tests/Server/TestingUtils/PasskeyHttpIntegration.ts +102 -0
  269. package/Tests/Server/TestingUtils/SecurityKey.ts +464 -0
  270. package/Tests/Server/TestingUtils/WebAuthnServiceMocks.ts +60 -0
  271. package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +573 -0
  272. package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
  273. package/Tests/Server/Types/Database/Permissions/ProjectCustomerSupportAccessColumn.test.ts +261 -0
  274. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
  275. package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +95 -0
  276. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
  277. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
  278. package/Tests/Server/Types/Markdown.test.ts +194 -1
  279. package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
  280. package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
  281. package/Tests/Server/Utils/CodeRepository/GitHubAgentTaskTrigger.test.ts +2101 -0
  282. package/Tests/Server/Utils/CodeRepository/GitHubCommandAuthorizer.test.ts +883 -0
  283. package/Tests/Server/Utils/CodeRepository/GitHubCommandParser.test.ts +1474 -0
  284. package/Tests/Server/Utils/CodeRepository/GitHubConversation.test.ts +1526 -0
  285. package/Tests/Server/Utils/CodeRepository/GitHubReplyComposer.test.ts +1389 -0
  286. package/Tests/Server/Utils/CodeRepository/GitHubRunReply.test.ts +1707 -0
  287. package/Tests/Server/Utils/CodeRepository/GitHubWebhookHandler.test.ts +2202 -0
  288. package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
  289. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +559 -0
  290. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +896 -0
  291. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  292. package/Tests/Server/Utils/EmailRollup/EmailRollupMetadataIntegration.test.ts +246 -0
  293. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +309 -4
  294. package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +6 -0
  295. package/Tests/Server/Utils/EmailRollup/EmailRollupWriter.test.ts +185 -0
  296. package/Tests/Server/Utils/EventLoop.test.ts +359 -0
  297. package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
  298. package/Tests/Server/Utils/LogRedaction.test.ts +33 -0
  299. package/Tests/Server/Utils/Monitor/CapturedMetricAttributeUtil.test.ts +2 -1
  300. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
  301. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
  302. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
  303. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
  304. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
  305. package/Tests/Server/Utils/Monitor/Criteria/TelemetryStepMetricAlias.test.ts +10 -0
  306. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +66 -1
  307. package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
  308. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +586 -10
  309. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
  310. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
  311. package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
  312. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
  313. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
  314. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +60 -1
  315. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +60 -0
  316. package/Tests/Server/Utils/Monitor/MonitorRootCauseTargetResolution.test.ts +423 -0
  317. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +6 -0
  318. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
  319. package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
  320. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
  321. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
  322. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +46 -2
  323. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +81 -45
  324. package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.test.ts +711 -0
  325. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClickhouseIntegration.test.ts +223 -0
  326. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +69 -1
  327. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsDiagnostics.test.ts +838 -0
  328. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +241 -28
  329. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsHttpIntegration.test.ts +651 -0
  330. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +11 -0
  331. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +13 -12
  332. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +112 -127
  333. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +12 -18
  334. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +43 -0
  335. package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
  336. package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
  337. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +691 -8
  338. package/Tests/Server/Utils/Telemetry/VMwareSnapshotScan.test.ts +1748 -0
  339. package/Tests/Types/Cloud/CloudPlatform.test.ts +574 -0
  340. package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
  341. package/Tests/Types/JSONFunctions.test.ts +40 -0
  342. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +2 -0
  343. package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +9 -0
  344. package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +1 -0
  345. package/Tests/Types/Monitor/MonitorStepInfraMonitorUtils.test.ts +53 -8
  346. package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +97 -0
  347. package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +1 -0
  348. package/Tests/Types/Monitor/MonitorStepTemplateSyncFields.test.ts +107 -0
  349. package/Tests/Types/Monitor/MonitorTemplateSyncField.test.ts +172 -0
  350. package/Tests/Types/Monitor/MonitorType.test.ts +1 -0
  351. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +2 -0
  352. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationAlertDebuggability.test.ts +1 -0
  353. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +9 -0
  354. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
  355. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
  356. package/Tests/Types/Monitor/SeriesContext/SeriesDebugHints.test.ts +131 -0
  357. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +10 -0
  358. package/Tests/Types/Monitor/VMwareAlertTemplates.test.ts +1608 -0
  359. package/Tests/Types/Monitor/VMwareMetricCatalog.test.ts +488 -0
  360. package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
  361. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
  362. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
  363. package/Tests/Types/OnCallDutyPolicy/OverrideProvenance.test.ts +295 -0
  364. package/Tests/Types/Runner/RunnerLiveStatus.test.ts +2 -2
  365. package/Tests/Types/SignupPassword.test.ts +205 -0
  366. package/Tests/Types/StatusPage/StatusPageLanguage.test.ts +44 -0
  367. package/Tests/Types/SupportedLanguages.test.ts +119 -0
  368. package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +19 -0
  369. package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
  370. package/Tests/UI/ColorPickerBuild.test.ts +86 -0
  371. package/Tests/UI/Components/Breadcrumbs.test.tsx +32 -0
  372. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
  373. package/Tests/UI/Components/Forms/ModelFormPreservesUserInput.test.tsx +937 -0
  374. package/Tests/UI/Components/Forms/ModelFormWriteOnlyColumnSelect.test.tsx +230 -0
  375. package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +3 -4
  376. package/Tests/UI/Components/LabelRuleImportExport.test.tsx +907 -0
  377. package/Tests/UI/Components/ModelTable/ModelTableCreateAfterEdit.test.tsx +514 -0
  378. package/Tests/UI/Components/MonitorGraphs/Uptime.test.tsx +301 -0
  379. package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +2 -1
  380. package/Tests/UI/Components/TooltipLazyContent.test.tsx +373 -0
  381. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +43 -12
  382. package/Tests/UI/ReactDomBuild.test.ts +104 -0
  383. package/Tests/UI/ReactRouterSingletonBuild.test.ts +143 -0
  384. package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
  385. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
  386. package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
  387. package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
  388. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +153 -0
  389. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +30 -0
  390. package/Tests/UI/Utils/DashboardStackingLayers.test.ts +197 -0
  391. package/Tests/UI/Utils/LabelRuleImportExport.test.ts +546 -0
  392. package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +109 -2
  393. package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +7 -2
  394. package/Tests/UI/Utils/UserEmailRegistrationStatus.test.tsx +295 -0
  395. package/Tests/UI/Utils/WebAuthn.test.ts +265 -0
  396. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +5 -0
  397. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
  398. package/Tests/Utils/Dashboard/Components/DashboardVMwareListComponents.test.ts +548 -0
  399. package/Tests/Utils/Dashboard/DashboardResourceListSharedArgs.test.ts +17 -3
  400. package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
  401. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
  402. package/Tests/Utils/LabelRuleImportExport.test.ts +583 -0
  403. package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
  404. package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
  405. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
  406. package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +1 -0
  407. package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
  408. package/Tests/Utils/Monitor/MonitorTemplateSyncUtil.test.ts +590 -0
  409. package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
  410. package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
  411. package/Tests/Utils/RequestFailedDetails.test.ts +325 -0
  412. package/Tests/Utils/Telemetry/CloudInstanceIdentity.test.ts +255 -0
  413. package/Tests/Utils/Telemetry/EntityRelationship.test.ts +167 -0
  414. package/Tests/Utils/WebAuthnTestUtil.ts +79 -0
  415. package/Tests/__mocks__/simplewebauthn.js +33 -4
  416. package/Types/AI/CodeFixTaskContext.ts +122 -0
  417. package/Types/AI/CodeFixTaskType.ts +62 -0
  418. package/Types/Accounts/AccountsLanguage.ts +1 -0
  419. package/Types/AdminDashboard/AdminDashboardLanguage.ts +1 -0
  420. package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
  421. package/Types/Calendar/CalendarEvent.ts +16 -0
  422. package/Types/Cloud/CloudPlatform.ts +372 -0
  423. package/Types/CodeRepository/GitHubCommand.ts +69 -0
  424. package/Types/Dashboard/DashboardComponentType.ts +2 -0
  425. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +2 -0
  426. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  427. package/Types/Dashboard/DashboardComponents/DashboardVMwareHostListComponent.ts +23 -0
  428. package/Types/Dashboard/DashboardComponents/DashboardVMwareVirtualMachineListComponent.ts +30 -0
  429. package/Types/Dashboard/DashboardLanguage.ts +1 -0
  430. package/Types/Dashboard/DashboardTemplates.ts +314 -0
  431. package/Types/Dashboard/DashboardVariable.ts +8 -0
  432. package/Types/Docs/DocsLanguage.ts +1 -0
  433. package/Types/Exception/EgressGuardException.ts +65 -0
  434. package/Types/Icon/IconProp.ts +1 -0
  435. package/Types/JSON.ts +4 -0
  436. package/Types/JSONFunctions.ts +16 -0
  437. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +36 -0
  438. package/Types/Monitor/MonitorCriteriaInstance.ts +1 -0
  439. package/Types/Monitor/MonitorStep.ts +78 -17
  440. package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +1 -0
  441. package/Types/Monitor/MonitorStepVMwareMonitor.ts +127 -0
  442. package/Types/Monitor/MonitorTemplateSyncField.ts +291 -0
  443. package/Types/Monitor/MonitorType.ts +24 -0
  444. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +33 -0
  445. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +2 -0
  446. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +3 -0
  447. package/Types/Monitor/SeriesContext/SeriesDebugHints.ts +158 -0
  448. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +61 -0
  449. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  450. package/Types/Monitor/VMwareAlertTemplates.ts +1070 -0
  451. package/Types/Monitor/VMwareMetricCatalog.ts +914 -0
  452. package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
  453. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +29 -0
  454. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +16 -0
  455. package/Types/Password.ts +84 -5
  456. package/Types/Permission.ts +265 -0
  457. package/Types/Probe/RequestFailedDetails.ts +7 -0
  458. package/Types/Runner/RunnerLiveStatus.ts +1 -1
  459. package/Types/SecurityEvent/GoogleSecOpsDiagnostics.ts +56 -0
  460. package/Types/SerializableObjectDictionary.ts +4 -0
  461. package/Types/StatusPage/StatusPageLanguage.ts +1 -0
  462. package/Types/Telemetry/EntityType.ts +21 -0
  463. package/Types/Telemetry/InventoryLiveness.ts +56 -0
  464. package/Types/Telemetry/ServiceType.ts +1 -0
  465. package/Typings/Index.d.ts +63 -0
  466. package/UI/Components/Breadcrumbs/Breadcrumbs.tsx +2 -2
  467. package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
  468. package/UI/Components/Calendar/Calendar.css +78 -0
  469. package/UI/Components/Calendar/Calendar.tsx +28 -3
  470. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
  471. package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
  472. package/UI/Components/Forms/BasicForm.tsx +44 -2
  473. package/UI/Components/Forms/Fields/ColorPicker.tsx +2 -1
  474. package/UI/Components/Forms/Fields/FormField.tsx +16 -3
  475. package/UI/Components/Forms/ModelForm.tsx +159 -5
  476. package/UI/Components/Forms/Types/Field.ts +8 -1
  477. package/UI/Components/Graphs/DayUptimeGraph.tsx +8 -10
  478. package/UI/Components/Icon/Icon.tsx +16 -0
  479. package/UI/Components/Input/Input.tsx +42 -7
  480. package/UI/Components/LabelRule/ImportLabelRulesModal.tsx +441 -0
  481. package/UI/Components/LabelRule/LabelRuleTable.tsx +142 -0
  482. package/UI/Components/ModelTable/BaseModelTable.tsx +22 -5
  483. package/UI/Components/ModelTable/ModelTable.tsx +14 -6
  484. package/UI/Components/MonitorGraphs/Uptime.tsx +17 -27
  485. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +3 -1
  486. package/UI/Components/Tooltip/Tooltip.tsx +52 -6
  487. package/UI/Utils/DashboardStackingLayers.ts +85 -0
  488. package/UI/Utils/LabelRuleImportExport.ts +310 -0
  489. package/UI/Utils/ModelAPI/ModelAPI.ts +4 -3
  490. package/UI/Utils/TelemetryService.ts +4 -3
  491. package/UI/Utils/UseDashboardGridDnd.ts +4 -1
  492. package/UI/Utils/UserEmailRegistrationStatus.ts +33 -5
  493. package/UI/Utils/WebAuthn.ts +220 -0
  494. package/UI/esbuild-config.js +10 -0
  495. package/Utils/API.ts +34 -0
  496. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
  497. package/Utils/Dashboard/Components/DashboardVMwareHostListComponent.ts +38 -0
  498. package/Utils/Dashboard/Components/DashboardVMwareResourceListShared.ts +70 -0
  499. package/Utils/Dashboard/Components/DashboardVMwareVirtualMachineListComponent.ts +76 -0
  500. package/Utils/Dashboard/Components/Index.ts +16 -0
  501. package/Utils/Dashboard/LabelVariable.ts +152 -0
  502. package/Utils/Dashboard/VariableUrlState.ts +27 -5
  503. package/Utils/LabelRuleImportExport.ts +496 -0
  504. package/Utils/MetricUnitUtil.ts +34 -0
  505. package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
  506. package/Utils/Monitor/MetricValueFormatter.ts +342 -0
  507. package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
  508. package/Utils/Monitor/MonitorTemplateSyncUtil.ts +164 -0
  509. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
  510. package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
  511. package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
  512. package/Utils/NetworkDiscovery/DiscoveryScanStatus.ts +8 -0
  513. package/Utils/Telemetry/CloudInstanceIdentity.ts +94 -0
  514. package/Utils/Telemetry/EntityKey.ts +19 -0
  515. package/Utils/Telemetry/EntityRelationship.ts +19 -0
  516. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  517. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  518. package/build/dist/Models/DatabaseModels/CloudResource.js +44 -7
  519. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  520. package/build/dist/Models/DatabaseModels/CodeRepository.js +87 -0
  521. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  522. package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
  523. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  524. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +60 -7
  525. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -1
  526. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnectionRun.js +287 -0
  527. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnectionRun.js.map +1 -0
  528. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  529. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  530. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  531. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  532. package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
  533. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  534. package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
  535. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  536. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  537. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  538. package/build/dist/Models/DatabaseModels/User.js +57 -4
  539. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  540. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js +47 -2
  541. package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js.map +1 -1
  542. package/build/dist/Models/DatabaseModels/UserWebAuthn.js +18 -0
  543. package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
  544. package/build/dist/Models/DatabaseModels/VMwareResource.js +1201 -0
  545. package/build/dist/Models/DatabaseModels/VMwareResource.js.map +1 -0
  546. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +1149 -0
  547. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -0
  548. package/build/dist/Models/DatabaseModels/VMwareVCenterFeed.js +648 -0
  549. package/build/dist/Models/DatabaseModels/VMwareVCenterFeed.js.map +1 -0
  550. package/build/dist/Models/DatabaseModels/VMwareVCenterLabelRule.js +522 -0
  551. package/build/dist/Models/DatabaseModels/VMwareVCenterLabelRule.js.map +1 -0
  552. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerRule.js +603 -0
  553. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerRule.js.map +1 -0
  554. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerTeam.js +503 -0
  555. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerTeam.js.map +1 -0
  556. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerUser.js +502 -0
  557. package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerUser.js.map +1 -0
  558. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +22 -1
  559. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  560. package/build/dist/Server/API/AIAgentDataAPI.js +63 -3
  561. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  562. package/build/dist/Server/API/AIAgentGitHubAPI.js +363 -0
  563. package/build/dist/Server/API/AIAgentGitHubAPI.js.map +1 -0
  564. package/build/dist/Server/API/AIChatAPI.js +19 -1
  565. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  566. package/build/dist/Server/API/BillingAPI.js +22 -0
  567. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  568. package/build/dist/Server/API/DashboardAPI.js +10 -0
  569. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  570. package/build/dist/Server/API/GitHubAPI.js +33 -5
  571. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  572. package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js +56 -0
  573. package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js.map +1 -0
  574. package/build/dist/Server/API/UserWebAuthnAPI.js +17 -1
  575. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  576. package/build/dist/Server/API/VMwareResourceAPI.js +106 -0
  577. package/build/dist/Server/API/VMwareResourceAPI.js.map +1 -0
  578. package/build/dist/Server/EnvironmentConfig.js +44 -12
  579. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  580. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
  581. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
  582. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791800000000-SplitWebAuthnChallengeByPurpose.js +41 -0
  583. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791800000000-SplitWebAuthnChallengeByPurpose.js.map +1 -0
  584. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791900000000-AddSeverityAndStateToNotificationEmailRollup.js +14 -0
  585. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791900000000-AddSeverityAndStateToNotificationEmailRollup.js.map +1 -0
  586. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792000000000-AddVMwareTables.js +241 -0
  587. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792000000000-AddVMwareTables.js.map +1 -0
  588. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792100000000-AddGitHubCommandSettingsToCodeRepository.js +36 -0
  589. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792100000000-AddGitHubCommandSettingsToCodeRepository.js.map +1 -0
  590. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792200000000-AddUserWebAuthnPurpose.js +12 -0
  591. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792200000000-AddUserWebAuthnPurpose.js.map +1 -0
  592. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792300000000-AddGoogleSecOpsDiagnostics.js +34 -0
  593. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792300000000-AddGoogleSecOpsDiagnostics.js.map +1 -0
  594. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  595. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  596. package/build/dist/Server/Infrastructure/Redis.js +8 -8
  597. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  598. package/build/dist/Server/Infrastructure/Status.js +10 -4
  599. package/build/dist/Server/Infrastructure/Status.js.map +1 -1
  600. package/build/dist/Server/Middleware/IdentityRateLimit.js +80 -0
  601. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  602. package/build/dist/Server/Services/AIRunService.js +14 -1
  603. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  604. package/build/dist/Server/Services/BillingPaymentMethodService.js +71 -21
  605. package/build/dist/Server/Services/BillingPaymentMethodService.js.map +1 -1
  606. package/build/dist/Server/Services/BillingService.js +210 -22
  607. package/build/dist/Server/Services/BillingService.js.map +1 -1
  608. package/build/dist/Server/Services/CloudResourceInstanceService.js +66 -0
  609. package/build/dist/Server/Services/CloudResourceInstanceService.js.map +1 -1
  610. package/build/dist/Server/Services/DatabaseService.js +58 -13
  611. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  612. package/build/dist/Server/Services/ExceptionAggregationService.js +1 -0
  613. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
  614. package/build/dist/Server/Services/GoogleSecOpsConnectionRunService.js +9 -0
  615. package/build/dist/Server/Services/GoogleSecOpsConnectionRunService.js.map +1 -0
  616. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +7 -0
  617. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +1 -1
  618. package/build/dist/Server/Services/Index.js +16 -0
  619. package/build/dist/Server/Services/Index.js.map +1 -1
  620. package/build/dist/Server/Services/InventoryItemService.js +28 -1
  621. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  622. package/build/dist/Server/Services/LogAggregationService.js +1 -0
  623. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  624. package/build/dist/Server/Services/MetricAggregationService.js +1 -0
  625. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  626. package/build/dist/Server/Services/MobilePasskeyLoginService.js +137 -0
  627. package/build/dist/Server/Services/MobilePasskeyLoginService.js.map +1 -0
  628. package/build/dist/Server/Services/MonitorService.js +34 -1
  629. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  630. package/build/dist/Server/Services/MonitorTemplateService.js +167 -30
  631. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  632. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +394 -38
  633. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  634. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +142 -2
  635. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
  636. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
  637. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  638. package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
  639. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  640. package/build/dist/Server/Services/OpenTelemetryIngestService.js +28 -13
  641. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  642. package/build/dist/Server/Services/PayAsYouGoBillingService.js +297 -0
  643. package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -0
  644. package/build/dist/Server/Services/ProjectService.js +38 -2
  645. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  646. package/build/dist/Server/Services/ServerlessFunctionInstanceService.js +66 -0
  647. package/build/dist/Server/Services/ServerlessFunctionInstanceService.js.map +1 -1
  648. package/build/dist/Server/Services/TeamMemberService.js +85 -28
  649. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  650. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +61 -10
  651. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  652. package/build/dist/Server/Services/TelemetryUsageBillingService.js +62 -5
  653. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  654. package/build/dist/Server/Services/TraceAggregationService.js +5 -4
  655. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  656. package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
  657. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  658. package/build/dist/Server/Services/UserWebAuthnService.js +345 -113
  659. package/build/dist/Server/Services/UserWebAuthnService.js.map +1 -1
  660. package/build/dist/Server/Services/VMwareResourceService.js +395 -0
  661. package/build/dist/Server/Services/VMwareResourceService.js.map +1 -0
  662. package/build/dist/Server/Services/VMwareVCenterFeedService.js +89 -0
  663. package/build/dist/Server/Services/VMwareVCenterFeedService.js.map +1 -0
  664. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js +197 -0
  665. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js.map +1 -0
  666. package/build/dist/Server/Services/VMwareVCenterLabelRuleService.js +13 -0
  667. package/build/dist/Server/Services/VMwareVCenterLabelRuleService.js.map +1 -0
  668. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js +214 -0
  669. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js.map +1 -0
  670. package/build/dist/Server/Services/VMwareVCenterOwnerRuleService.js +13 -0
  671. package/build/dist/Server/Services/VMwareVCenterOwnerRuleService.js.map +1 -0
  672. package/build/dist/Server/Services/VMwareVCenterOwnerTeamService.js +129 -0
  673. package/build/dist/Server/Services/VMwareVCenterOwnerTeamService.js.map +1 -0
  674. package/build/dist/Server/Services/VMwareVCenterOwnerUserService.js +122 -0
  675. package/build/dist/Server/Services/VMwareVCenterOwnerUserService.js.map +1 -0
  676. package/build/dist/Server/Services/VMwareVCenterService.js +533 -0
  677. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -0
  678. package/build/dist/Server/Services/WorkflowVariableService.js +169 -0
  679. package/build/dist/Server/Services/WorkflowVariableService.js.map +1 -1
  680. package/build/dist/Server/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.js +12 -1
  681. package/build/dist/Server/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.js.map +1 -1
  682. package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js +25 -2
  683. package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js.map +1 -1
  684. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
  685. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  686. package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
  687. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  688. package/build/dist/Server/Types/Markdown.js +75 -0
  689. package/build/dist/Server/Types/Markdown.js.map +1 -1
  690. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  691. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubAgentTaskTrigger.js +275 -0
  692. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubAgentTaskTrigger.js.map +1 -0
  693. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandAuthorizer.js +226 -0
  694. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandAuthorizer.js.map +1 -0
  695. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandParser.js +297 -0
  696. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandParser.js.map +1 -0
  697. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubConversation.js +582 -0
  698. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubConversation.js.map +1 -0
  699. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubReplyComposer.js +275 -0
  700. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubReplyComposer.js.map +1 -0
  701. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubRunReply.js +365 -0
  702. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubRunReply.js.map +1 -0
  703. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookEvents.js +103 -0
  704. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookEvents.js.map +1 -0
  705. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookHandler.js +661 -0
  706. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookHandler.js.map +1 -0
  707. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +166 -18
  708. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  709. package/build/dist/Server/Utils/DataSource/EgressGuard.js +99 -15
  710. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  711. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +12 -1
  712. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  713. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +20 -2
  714. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
  715. package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js +31 -2
  716. package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js.map +1 -1
  717. package/build/dist/Server/Utils/LogRedaction.js +2 -1
  718. package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
  719. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
  720. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  721. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
  722. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  723. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +1 -1
  724. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +382 -36
  725. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  726. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
  727. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  728. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
  729. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
  730. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
  731. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
  732. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
  733. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  734. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +12 -4
  735. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  736. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +5 -3
  737. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
  738. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +22 -9
  739. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  740. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
  741. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  742. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
  743. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
  744. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
  745. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  746. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +21 -0
  747. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  748. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +21 -6
  749. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  750. package/build/dist/Server/Utils/ResourceFeed/ResourceFeedUtil.js +3 -3
  751. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js +9 -9
  752. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js.map +1 -1
  753. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +92 -71
  754. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
  755. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +428 -143
  756. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
  757. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js +376 -0
  758. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js.map +1 -0
  759. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +14 -1
  760. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  761. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
  762. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +206 -14
  763. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  764. package/build/dist/Server/Utils/Telemetry/VMwareSnapshotScan.js +933 -0
  765. package/build/dist/Server/Utils/Telemetry/VMwareSnapshotScan.js.map +1 -0
  766. package/build/dist/Types/AI/CodeFixTaskContext.js +40 -0
  767. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
  768. package/build/dist/Types/AI/CodeFixTaskType.js +57 -0
  769. package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -1
  770. package/build/dist/Types/Accounts/AccountsLanguage.js +1 -0
  771. package/build/dist/Types/Accounts/AccountsLanguage.js.map +1 -1
  772. package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js +1 -0
  773. package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js.map +1 -1
  774. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
  775. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
  776. package/build/dist/Types/Cloud/CloudPlatform.js +277 -0
  777. package/build/dist/Types/Cloud/CloudPlatform.js.map +1 -0
  778. package/build/dist/Types/CodeRepository/GitHubCommand.js +52 -0
  779. package/build/dist/Types/CodeRepository/GitHubCommand.js.map +1 -0
  780. package/build/dist/Types/Dashboard/DashboardComponentType.js +2 -0
  781. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  782. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +2 -0
  783. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  784. package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareHostListComponent.js +2 -0
  785. package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareHostListComponent.js.map +1 -0
  786. package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareVirtualMachineListComponent.js +2 -0
  787. package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareVirtualMachineListComponent.js.map +1 -0
  788. package/build/dist/Types/Dashboard/DashboardLanguage.js +1 -0
  789. package/build/dist/Types/Dashboard/DashboardLanguage.js.map +1 -1
  790. package/build/dist/Types/Dashboard/DashboardTemplates.js +286 -0
  791. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  792. package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
  793. package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
  794. package/build/dist/Types/Docs/DocsLanguage.js +1 -0
  795. package/build/dist/Types/Docs/DocsLanguage.js.map +1 -1
  796. package/build/dist/Types/Exception/EgressGuardException.js +59 -0
  797. package/build/dist/Types/Exception/EgressGuardException.js.map +1 -0
  798. package/build/dist/Types/Icon/IconProp.js +1 -0
  799. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  800. package/build/dist/Types/JSON.js +1 -0
  801. package/build/dist/Types/JSON.js.map +1 -1
  802. package/build/dist/Types/JSONFunctions.js +13 -0
  803. package/build/dist/Types/JSONFunctions.js.map +1 -1
  804. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +1 -0
  805. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  806. package/build/dist/Types/Monitor/MonitorStep.js +61 -18
  807. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  808. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +1 -0
  809. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -1
  810. package/build/dist/Types/Monitor/MonitorStepVMwareMonitor.js +71 -0
  811. package/build/dist/Types/Monitor/MonitorStepVMwareMonitor.js.map +1 -0
  812. package/build/dist/Types/Monitor/MonitorTemplateSyncField.js +211 -0
  813. package/build/dist/Types/Monitor/MonitorTemplateSyncField.js.map +1 -0
  814. package/build/dist/Types/Monitor/MonitorType.js +23 -0
  815. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  816. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +28 -0
  817. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  818. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +1 -0
  819. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  820. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +2 -0
  821. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  822. package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js +120 -0
  823. package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js.map +1 -1
  824. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +60 -0
  825. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  826. package/build/dist/Types/Monitor/VMwareAlertTemplates.js +775 -0
  827. package/build/dist/Types/Monitor/VMwareAlertTemplates.js.map +1 -0
  828. package/build/dist/Types/Monitor/VMwareMetricCatalog.js +799 -0
  829. package/build/dist/Types/Monitor/VMwareMetricCatalog.js.map +1 -0
  830. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
  831. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  832. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +25 -0
  833. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  834. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +5 -0
  835. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
  836. package/build/dist/Types/Password.js +63 -5
  837. package/build/dist/Types/Password.js.map +1 -1
  838. package/build/dist/Types/Permission.js +234 -0
  839. package/build/dist/Types/Permission.js.map +1 -1
  840. package/build/dist/Types/Probe/RequestFailedDetails.js +7 -0
  841. package/build/dist/Types/Probe/RequestFailedDetails.js.map +1 -1
  842. package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js +2 -0
  843. package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js.map +1 -0
  844. package/build/dist/Types/SerializableObjectDictionary.js +4 -0
  845. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  846. package/build/dist/Types/StatusPage/StatusPageLanguage.js +1 -0
  847. package/build/dist/Types/StatusPage/StatusPageLanguage.js.map +1 -1
  848. package/build/dist/Types/Telemetry/EntityType.js +21 -0
  849. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  850. package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
  851. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
  852. package/build/dist/Types/Telemetry/ServiceType.js +1 -0
  853. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  854. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js +2 -2
  855. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  856. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
  857. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
  858. package/build/dist/UI/Components/Calendar/Calendar.js +24 -4
  859. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  860. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
  861. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  862. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
  863. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
  864. package/build/dist/UI/Components/Forms/BasicForm.js +28 -3
  865. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  866. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +1 -1
  867. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
  868. package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -3
  869. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  870. package/build/dist/UI/Components/Forms/ModelForm.js +89 -6
  871. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  872. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +5 -6
  873. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  874. package/build/dist/UI/Components/Icon/Icon.js +14 -0
  875. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  876. package/build/dist/UI/Components/Input/Input.js +37 -8
  877. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  878. package/build/dist/UI/Components/LabelRule/ImportLabelRulesModal.js +228 -0
  879. package/build/dist/UI/Components/LabelRule/ImportLabelRulesModal.js.map +1 -0
  880. package/build/dist/UI/Components/LabelRule/LabelRuleTable.js +102 -0
  881. package/build/dist/UI/Components/LabelRule/LabelRuleTable.js.map +1 -0
  882. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +15 -2
  883. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  884. package/build/dist/UI/Components/ModelTable/ModelTable.js +13 -5
  885. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  886. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +14 -15
  887. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  888. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +2 -0
  889. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  890. package/build/dist/UI/Components/Tooltip/Tooltip.js +33 -4
  891. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  892. package/build/dist/UI/Utils/DashboardStackingLayers.js +44 -0
  893. package/build/dist/UI/Utils/DashboardStackingLayers.js.map +1 -0
  894. package/build/dist/UI/Utils/LabelRuleImportExport.js +195 -0
  895. package/build/dist/UI/Utils/LabelRuleImportExport.js.map +1 -0
  896. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +4 -4
  897. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  898. package/build/dist/UI/Utils/TelemetryService.js +3 -2
  899. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  900. package/build/dist/UI/Utils/UseDashboardGridDnd.js +2 -1
  901. package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -1
  902. package/build/dist/UI/Utils/UserEmailRegistrationStatus.js +21 -4
  903. package/build/dist/UI/Utils/UserEmailRegistrationStatus.js.map +1 -1
  904. package/build/dist/UI/Utils/WebAuthn.js +121 -0
  905. package/build/dist/UI/Utils/WebAuthn.js.map +1 -0
  906. package/build/dist/Utils/API.js +29 -0
  907. package/build/dist/Utils/API.js.map +1 -1
  908. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
  909. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  910. package/build/dist/Utils/Dashboard/Components/DashboardVMwareHostListComponent.js +33 -0
  911. package/build/dist/Utils/Dashboard/Components/DashboardVMwareHostListComponent.js.map +1 -0
  912. package/build/dist/Utils/Dashboard/Components/DashboardVMwareResourceListShared.js +54 -0
  913. package/build/dist/Utils/Dashboard/Components/DashboardVMwareResourceListShared.js.map +1 -0
  914. package/build/dist/Utils/Dashboard/Components/DashboardVMwareVirtualMachineListComponent.js +60 -0
  915. package/build/dist/Utils/Dashboard/Components/DashboardVMwareVirtualMachineListComponent.js.map +1 -0
  916. package/build/dist/Utils/Dashboard/Components/Index.js +8 -0
  917. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  918. package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
  919. package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
  920. package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
  921. package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
  922. package/build/dist/Utils/LabelRuleImportExport.js +356 -0
  923. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -0
  924. package/build/dist/Utils/MetricUnitUtil.js +26 -0
  925. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  926. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
  927. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
  928. package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
  929. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
  930. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
  931. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
  932. package/build/dist/Utils/Monitor/MonitorTemplateSyncUtil.js +110 -0
  933. package/build/dist/Utils/Monitor/MonitorTemplateSyncUtil.js.map +1 -0
  934. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
  935. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  936. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
  937. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  938. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
  939. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
  940. package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js +6 -0
  941. package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js.map +1 -1
  942. package/build/dist/Utils/Telemetry/CloudInstanceIdentity.js +88 -0
  943. package/build/dist/Utils/Telemetry/CloudInstanceIdentity.js.map +1 -0
  944. package/build/dist/Utils/Telemetry/EntityKey.js +15 -0
  945. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  946. package/build/dist/Utils/Telemetry/EntityRelationship.js +13 -0
  947. package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
  948. package/jest.config.json +2 -0
  949. package/package.json +1 -1
  950. package/test-setup.sh +9 -9
  951. package/tsconfig.json +51 -11
  952. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
@@ -0,0 +1,1755 @@
1
+ import NetworkTopologyUtil, {
2
+ TopologyBuildResult,
3
+ TopologyDeviceInput,
4
+ TopologyEndpointInput,
5
+ TopologyInterfaceInput,
6
+ TopologyManualLinkInput,
7
+ } from "../../../Utils/Monitor/NetworkTopologyUtil";
8
+ import { TopologyDeviceRoleInput } from "../../../Utils/Monitor/NetworkDeviceRoleCatalog";
9
+ import {
10
+ NetworkTopologyEdge,
11
+ NetworkTopologyNode,
12
+ } from "../../../Types/Monitor/SnmpMonitor/NetworkTopology";
13
+
14
+ /*
15
+ * Issue #3489. A ping-only register, handset or kiosk has no LLDP and no
16
+ * CDP, so until now its cable had to be drawn by hand. But the switch it
17
+ * hangs off has already learned its MAC on an access port, and the router
18
+ * has already bound that MAC to an address — both of which the endpoint
19
+ * inventory keeps, one NetworkEndpoint row per learned MAC. The map used
20
+ * to draw that row as a second, anonymous "endpoint" node hanging off the
21
+ * switch while the device's own node floated with no link at all.
22
+ *
23
+ * The adoption pass in buildTopology is what recognises the two as one
24
+ * box: by the MAC the operator declared (or the ARP pass learned), or by
25
+ * the device's address within the site that learned it. This file pins
26
+ * every rule of that pass — what matches, what refuses to, what the cable
27
+ * looks like, which end is the parent, how it merges with a link that is
28
+ * already there, and how the bookkeeping adds up. The builder is pure, so
29
+ * every case is a plain call.
30
+ */
31
+ describe("NetworkTopologyUtil.buildTopology — endpoints that are managed devices (#3489)", () => {
32
+ const now: Date = new Date("2026-07-22T12:00:00Z");
33
+ const fresh: Date = new Date("2026-07-22T11:55:00Z");
34
+ const stale: Date = new Date("2026-07-22T11:30:00Z");
35
+
36
+ const SITE_A: string = "site-a";
37
+ const SITE_B: string = "site-b";
38
+
39
+ const makeDevice: (
40
+ id: string,
41
+ name: string,
42
+ overrides?: Partial<TopologyDeviceInput>,
43
+ ) => TopologyDeviceInput = (
44
+ id: string,
45
+ name: string,
46
+ overrides?: Partial<TopologyDeviceInput>,
47
+ ): TopologyDeviceInput => {
48
+ return {
49
+ id,
50
+ name,
51
+ lastSeenAt: fresh,
52
+ ...overrides,
53
+ };
54
+ };
55
+
56
+ const makeEndpoint: (
57
+ id: string,
58
+ macAddress: string,
59
+ overrides?: Partial<TopologyEndpointInput>,
60
+ ) => TopologyEndpointInput = (
61
+ id: string,
62
+ macAddress: string,
63
+ overrides?: Partial<TopologyEndpointInput>,
64
+ ): TopologyEndpointInput => {
65
+ return {
66
+ id,
67
+ macAddress,
68
+ lastSeenAt: fresh,
69
+ ...overrides,
70
+ };
71
+ };
72
+
73
+ const nodeById: (
74
+ result: TopologyBuildResult,
75
+ id: string,
76
+ ) => NetworkTopologyNode | undefined = (
77
+ result: TopologyBuildResult,
78
+ id: string,
79
+ ): NetworkTopologyNode | undefined => {
80
+ return result.nodes.find((node: NetworkTopologyNode) => {
81
+ return node.id === id;
82
+ });
83
+ };
84
+
85
+ /*
86
+ * Edges are undirected as far as identity goes — which end landed in
87
+ * fromNodeId depends on who reported the link first — so they are looked
88
+ * up by the unordered pair, exactly the way the builder keys them.
89
+ */
90
+ const edgeBetween: (
91
+ result: TopologyBuildResult,
92
+ a: string,
93
+ b: string,
94
+ ) => NetworkTopologyEdge | undefined = (
95
+ result: TopologyBuildResult,
96
+ a: string,
97
+ b: string,
98
+ ): NetworkTopologyEdge | undefined => {
99
+ const wanted: string = [a, b].sort().join("::");
100
+ return result.edges.find((edge: NetworkTopologyEdge) => {
101
+ return [edge.fromNodeId, edge.toNodeId].sort().join("::") === wanted;
102
+ });
103
+ };
104
+
105
+ const endpointNodes: (
106
+ result: TopologyBuildResult,
107
+ ) => Array<NetworkTopologyNode> = (
108
+ result: TopologyBuildResult,
109
+ ): Array<NetworkTopologyNode> => {
110
+ return result.nodes.filter((node: NetworkTopologyNode) => {
111
+ return node.kind === "endpoint";
112
+ });
113
+ };
114
+
115
+ /*
116
+ * The shape the issue describes, reused wherever the test is about
117
+ * something other than the match itself: one access switch, one register
118
+ * nothing walks (hostname is its address, nothing else known), and the
119
+ * row the switch's forwarding table wrote about it once the router's ARP
120
+ * table had bound the MAC to that address.
121
+ */
122
+ const accessSwitch: (
123
+ overrides?: Partial<TopologyDeviceInput>,
124
+ ) => TopologyDeviceInput = (
125
+ overrides?: Partial<TopologyDeviceInput>,
126
+ ): TopologyDeviceInput => {
127
+ return makeDevice("sw", "access-sw-1", {
128
+ deviceRole: "switch",
129
+ siteId: SITE_A,
130
+ ...overrides,
131
+ });
132
+ };
133
+
134
+ const register: (
135
+ overrides?: Partial<TopologyDeviceInput>,
136
+ ) => TopologyDeviceInput = (
137
+ overrides?: Partial<TopologyDeviceInput>,
138
+ ): TopologyDeviceInput => {
139
+ return makeDevice("till", "till-4", {
140
+ hostname: "10.0.0.5",
141
+ siteId: SITE_A,
142
+ ...overrides,
143
+ });
144
+ };
145
+
146
+ const registerRow: (
147
+ overrides?: Partial<TopologyEndpointInput>,
148
+ ) => TopologyEndpointInput = (
149
+ overrides?: Partial<TopologyEndpointInput>,
150
+ ): TopologyEndpointInput => {
151
+ return makeEndpoint("e-till", "aa:bb:cc:00:00:01", {
152
+ ipAddress: "10.0.0.5",
153
+ attachedNetworkDeviceId: "sw",
154
+ attachedInterfaceIndex: 7,
155
+ attachedPortName: "Gi1/0/7",
156
+ siteId: SITE_A,
157
+ ...overrides,
158
+ });
159
+ };
160
+
161
+ describe("recognising a device by its address", () => {
162
+ it("draws the register's cable from the switch that learned its address, and mints no stranger", () => {
163
+ /*
164
+ * The whole of issue #3489 in one picture: the row that used to be an
165
+ * anonymous "endpoint" node is the register, so the fdb evidence
166
+ * draws the register's own cable — the port from the forwarding
167
+ * table, the interface state from the switch's interface row — and
168
+ * nothing else is added to the map.
169
+ */
170
+ const interfaces: Array<TopologyInterfaceInput> = [
171
+ {
172
+ networkDeviceId: "sw",
173
+ interfaceIndex: 7,
174
+ name: "GigabitEthernet1/0/7",
175
+ isOperationallyUp: true,
176
+ utilizationPercent: 3,
177
+ },
178
+ ];
179
+
180
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
181
+ [accessSwitch(), register()],
182
+ now,
183
+ interfaces,
184
+ [registerRow()],
185
+ );
186
+
187
+ expect(nodeById(result, "endpoint:e-till")).toBeUndefined();
188
+ expect(result.nodes).toHaveLength(2);
189
+ expect(result.edges).toHaveLength(1);
190
+
191
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
192
+ expect(edge.fromNodeId).toBe("sw");
193
+ expect(edge.toNodeId).toBe("till");
194
+ expect(edge.protocols).toEqual(["fdb"]);
195
+ // The port the table named, not the interface row's longer spelling.
196
+ expect(edge.fromPort).toBe("Gi1/0/7");
197
+ expect(edge.fromInterface).toEqual({
198
+ interfaceIndex: 7,
199
+ interfaceName: "GigabitEthernet1/0/7",
200
+ isOperationallyUp: true,
201
+ utilizationPercent: 3,
202
+ });
203
+ expect(edge.toPort).toBeUndefined();
204
+ expect(edge.toInterface).toBeUndefined();
205
+ // A register with no role hangs off the switch that learned it.
206
+ expect(edge.parentNodeId).toBe("sw");
207
+
208
+ expect(result.adoptedEndpointCount).toBe(1);
209
+ expect(result.droppedEndpointCount).toBe(0);
210
+ expect(result.endpointsTruncated).toBe(false);
211
+ });
212
+
213
+ it("refuses an address match across sites", () => {
214
+ /*
215
+ * Every branch has a 10.0.0.5. A switch in site B learning a MAC
216
+ * that answers at that address says nothing about the register in
217
+ * site A, so the row stays a stranger and the register keeps
218
+ * floating — which is the honest drawing, not a regression.
219
+ */
220
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
221
+ [accessSwitch({ siteId: SITE_B }), register({ siteId: SITE_A })],
222
+ now,
223
+ [],
224
+ [registerRow({ siteId: SITE_B })],
225
+ );
226
+
227
+ expect(nodeById(result, "endpoint:e-till")).toBeDefined();
228
+ expect(edgeBetween(result, "sw", "till")).toBeUndefined();
229
+ expect(edgeBetween(result, "sw", "endpoint:e-till")).toBeDefined();
230
+ expect(result.edges).toHaveLength(1);
231
+ expect(result.adoptedEndpointCount).toBe(0);
232
+ expect(result.droppedEndpointCount).toBe(0);
233
+ });
234
+
235
+ it("matches project-wide when neither side has a site", () => {
236
+ /*
237
+ * A project that never set sites up still has to get its registers
238
+ * cabled: "no site" is a site of its own, and it is the same one on
239
+ * both sides.
240
+ */
241
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
242
+ [accessSwitch({ siteId: undefined }), register({ siteId: undefined })],
243
+ now,
244
+ [],
245
+ [registerRow({ siteId: undefined })],
246
+ );
247
+
248
+ expect(edgeBetween(result, "sw", "till")).toBeDefined();
249
+ expect(nodeById(result, "endpoint:e-till")).toBeUndefined();
250
+ expect(result.adoptedEndpointCount).toBe(1);
251
+ });
252
+
253
+ it("reads a blank site as no site at all", () => {
254
+ /*
255
+ * The convention NetworkDeviceLinkRuleUtil already uses: null,
256
+ * undefined, "" and whitespace all mean "not in a site". A device
257
+ * whose siteId came through as a padded string must not be stranded
258
+ * in a site of one.
259
+ */
260
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
261
+ [accessSwitch({ siteId: "" }), register({ siteId: " " })],
262
+ now,
263
+ [],
264
+ [registerRow({ siteId: undefined })],
265
+ );
266
+
267
+ expect(edgeBetween(result, "sw", "till")).toBeDefined();
268
+ expect(result.adoptedEndpointCount).toBe(1);
269
+ });
270
+
271
+ it("does not match a device in a site against a row with none, nor the reverse", () => {
272
+ /*
273
+ * Half-configured sites are the dangerous middle: a row with no site
274
+ * would otherwise match the one register in the project that has
275
+ * that address AND no site — and then, once that register was moved
276
+ * into a site, silently stop. Either side missing a site the other
277
+ * has is a different site.
278
+ */
279
+ const deviceSited: TopologyBuildResult =
280
+ NetworkTopologyUtil.buildTopology(
281
+ [accessSwitch({ siteId: undefined }), register({ siteId: SITE_A })],
282
+ now,
283
+ [],
284
+ [registerRow({ siteId: undefined })],
285
+ );
286
+ expect(edgeBetween(deviceSited, "sw", "till")).toBeUndefined();
287
+ expect(nodeById(deviceSited, "endpoint:e-till")).toBeDefined();
288
+ expect(deviceSited.adoptedEndpointCount).toBe(0);
289
+
290
+ const rowSited: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
291
+ [accessSwitch({ siteId: SITE_A }), register({ siteId: undefined })],
292
+ now,
293
+ [],
294
+ [registerRow({ siteId: SITE_A })],
295
+ );
296
+ expect(edgeBetween(rowSited, "sw", "till")).toBeUndefined();
297
+ expect(nodeById(rowSited, "endpoint:e-till")).toBeDefined();
298
+ expect(rowSited.adoptedEndpointCount).toBe(0);
299
+ });
300
+
301
+ it("never reads a device NAME that looks like an address as its address", () => {
302
+ /*
303
+ * The same line matchKeysForDevice draws for neighbour claims. A
304
+ * name is an operator's label; one that happens to be an IP literal
305
+ * is not a claim that the device answers there, and a cable drawn to
306
+ * the wrong box is worse than none.
307
+ */
308
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
309
+ [
310
+ accessSwitch(),
311
+ makeDevice("kps", "10.0.0.5", { hostname: "kps-01", siteId: SITE_A }),
312
+ ],
313
+ now,
314
+ [],
315
+ [registerRow()],
316
+ );
317
+
318
+ expect(edgeBetween(result, "sw", "kps")).toBeUndefined();
319
+ expect(nodeById(result, "endpoint:e-till")).toBeDefined();
320
+ expect(result.adoptedEndpointCount).toBe(0);
321
+ });
322
+
323
+ it("never resolves a DNS hostname, and never treats a non-IPv4 address as one", () => {
324
+ /*
325
+ * Only a literal counts on either side. A lookup in the builder
326
+ * would be a per-node network call whose answer changes between
327
+ * rebuilds; a v6 literal or an out-of-range quad is not something
328
+ * the ARP pass could have bound a MAC to.
329
+ */
330
+ const dnsName: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
331
+ [accessSwitch(), register({ hostname: "till-4.store.local" })],
332
+ now,
333
+ [],
334
+ [registerRow()],
335
+ );
336
+ expect(edgeBetween(dnsName, "sw", "till")).toBeUndefined();
337
+ expect(dnsName.adoptedEndpointCount).toBe(0);
338
+
339
+ const ipv6Row: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
340
+ [accessSwitch(), register()],
341
+ now,
342
+ [],
343
+ [registerRow({ ipAddress: "fe80::1" })],
344
+ );
345
+ expect(edgeBetween(ipv6Row, "sw", "till")).toBeUndefined();
346
+ expect(nodeById(ipv6Row, "endpoint:e-till")).toBeDefined();
347
+ expect(ipv6Row.adoptedEndpointCount).toBe(0);
348
+
349
+ // Both sides say "999.1.1.1"; neither side is an address.
350
+ const badQuad: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
351
+ [accessSwitch(), register({ hostname: "999.1.1.1" })],
352
+ now,
353
+ [],
354
+ [registerRow({ ipAddress: "999.1.1.1" })],
355
+ );
356
+ expect(edgeBetween(badQuad, "sw", "till")).toBeUndefined();
357
+ expect(badQuad.adoptedEndpointCount).toBe(0);
358
+ });
359
+
360
+ it("drops a matching row whose attachment is not on this map, rather than adopting it", () => {
361
+ /*
362
+ * Adoption needs a switch end to draw from. A row attached to a
363
+ * device outside the graph (archived, or in another site's query)
364
+ * or to nothing at all is dropped and counted exactly as it was
365
+ * before — its address matching the register changes nothing,
366
+ * because there is no port to put the register on.
367
+ */
368
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
369
+ [accessSwitch(), register()],
370
+ now,
371
+ [],
372
+ [
373
+ registerRow({ attachedNetworkDeviceId: "ghost-switch" }),
374
+ makeEndpoint("e-loose", "aa:bb:cc:00:00:02", {
375
+ ipAddress: "10.0.0.5",
376
+ siteId: SITE_A,
377
+ }),
378
+ ],
379
+ );
380
+
381
+ expect(result.edges).toHaveLength(0);
382
+ expect(endpointNodes(result)).toHaveLength(0);
383
+ expect(result.droppedEndpointCount).toBe(2);
384
+ expect(result.adoptedEndpointCount).toBe(0);
385
+ });
386
+ });
387
+
388
+ describe("recognising a device by its declared MAC", () => {
389
+ it("matches the declared MAC in any spelling, and in any site", () => {
390
+ /*
391
+ * A MAC is unique per project among endpoints (the table enforces
392
+ * it), so it needs no site scoping — a register declared in site A
393
+ * that a switch in site B learned is still that register, and the
394
+ * map should say so rather than hide a mis-filed device. Spellings
395
+ * vary by who typed it: Cisco dotted, Windows dashed, upper-case.
396
+ */
397
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
398
+ [
399
+ accessSwitch({ siteId: SITE_B }),
400
+ makeDevice("till-1", "till-1", {
401
+ hostname: "till-1.store.local",
402
+ siteId: SITE_A,
403
+ macAddress: "AA-BB-CC-00-00-11",
404
+ }),
405
+ makeDevice("till-2", "till-2", {
406
+ hostname: "till-2.store.local",
407
+ siteId: SITE_A,
408
+ macAddress: "aabb.cc00.0012",
409
+ }),
410
+ makeDevice("till-3", "till-3", {
411
+ hostname: "till-3.store.local",
412
+ siteId: SITE_A,
413
+ macAddress: "AABBCC000013",
414
+ }),
415
+ ],
416
+ now,
417
+ [],
418
+ [
419
+ makeEndpoint("e1", "aa:bb:cc:00:00:11", {
420
+ attachedNetworkDeviceId: "sw",
421
+ attachedPortName: "Gi1/0/1",
422
+ siteId: SITE_B,
423
+ }),
424
+ makeEndpoint("e2", "AA:BB:CC:00:00:12", {
425
+ attachedNetworkDeviceId: "sw",
426
+ attachedPortName: "Gi1/0/2",
427
+ siteId: SITE_B,
428
+ }),
429
+ makeEndpoint("e3", "aa-bb-cc-00-00-13", {
430
+ attachedNetworkDeviceId: "sw",
431
+ attachedPortName: "Gi1/0/3",
432
+ siteId: SITE_B,
433
+ }),
434
+ ],
435
+ );
436
+
437
+ expect(endpointNodes(result)).toHaveLength(0);
438
+ expect(edgeBetween(result, "sw", "till-1")!.fromPort).toBe("Gi1/0/1");
439
+ expect(edgeBetween(result, "sw", "till-2")!.fromPort).toBe("Gi1/0/2");
440
+ expect(edgeBetween(result, "sw", "till-3")!.fromPort).toBe("Gi1/0/3");
441
+ expect(result.edges).toHaveLength(3);
442
+ expect(result.adoptedEndpointCount).toBe(3);
443
+ });
444
+
445
+ it("prefers the MAC over an address that names a different device", () => {
446
+ /*
447
+ * The MAC is the stronger claim and the only one that survives an
448
+ * address change. When a row's MAC is one device and its address is
449
+ * another — the address was re-issued to a new box, say — the MAC
450
+ * decides, and the other device is left alone rather than cabled
451
+ * on hearsay.
452
+ */
453
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
454
+ [
455
+ accessSwitch(),
456
+ makeDevice("by-mac", "kiosk-7", {
457
+ hostname: "kiosk-7.store.local",
458
+ siteId: SITE_A,
459
+ macAddress: "aa:bb:cc:00:00:01",
460
+ }),
461
+ register({ id: "by-ip" }),
462
+ ],
463
+ now,
464
+ [],
465
+ [registerRow()],
466
+ );
467
+
468
+ expect(edgeBetween(result, "sw", "by-mac")).toBeDefined();
469
+ expect(edgeBetween(result, "sw", "by-ip")).toBeUndefined();
470
+ expect(endpointNodes(result)).toHaveLength(0);
471
+ expect(result.adoptedEndpointCount).toBe(1);
472
+ });
473
+
474
+ it("does not adopt on a walked device's interface MACs", () => {
475
+ /*
476
+ * Deliberate, and the reason the declared MAC is a column of its own:
477
+ * a switch's interface MACs turn up in its uplink neighbours'
478
+ * forwarding tables as transit traffic, and adopting those would
479
+ * redraw every switch-to-switch cable LLDP already reports from a
480
+ * weaker source. Only a MAC somebody put there to say "this is the
481
+ * box" is evidence of where the box is.
482
+ */
483
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
484
+ [
485
+ accessSwitch(),
486
+ makeDevice("core", "core-sw-1", {
487
+ deviceRole: "switch",
488
+ siteId: SITE_A,
489
+ macAddresses: ["aa:bb:cc:00:00:30"],
490
+ }),
491
+ ],
492
+ now,
493
+ [],
494
+ [
495
+ makeEndpoint("e-core", "aa:bb:cc:00:00:30", {
496
+ attachedNetworkDeviceId: "sw",
497
+ attachedPortName: "Gi1/0/48",
498
+ siteId: SITE_A,
499
+ }),
500
+ ],
501
+ );
502
+
503
+ expect(edgeBetween(result, "sw", "core")).toBeUndefined();
504
+ expect(nodeById(result, "endpoint:e-core")).toBeDefined();
505
+ expect(result.adoptedEndpointCount).toBe(0);
506
+ });
507
+ });
508
+
509
+ describe("refusing to guess", () => {
510
+ it("adopts nothing when two devices in one site share the address", () => {
511
+ /*
512
+ * The same ambiguity guard the neighbour matcher applies: a key two
513
+ * devices both claim is deleted rather than resolved, because
514
+ * last-writer-wins would cable whichever device happened to be
515
+ * indexed second. The row stays a stranger, which is at least true.
516
+ */
517
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
518
+ [
519
+ accessSwitch(),
520
+ register({ id: "till-a", name: "till-a" }),
521
+ register({ id: "till-b", name: "till-b" }),
522
+ ],
523
+ now,
524
+ [],
525
+ [registerRow()],
526
+ );
527
+
528
+ expect(edgeBetween(result, "sw", "till-a")).toBeUndefined();
529
+ expect(edgeBetween(result, "sw", "till-b")).toBeUndefined();
530
+ expect(nodeById(result, "endpoint:e-till")).toBeDefined();
531
+ expect(result.adoptedEndpointCount).toBe(0);
532
+ });
533
+
534
+ it("adopts nothing when two devices declare the same MAC", () => {
535
+ /*
536
+ * Two rows claiming one MAC is a data-entry error, and the map must
537
+ * not pick a winner for the operator. Neither is cabled; the row is
538
+ * drawn as the stranger it was before.
539
+ */
540
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
541
+ [
542
+ accessSwitch(),
543
+ makeDevice("till-a", "till-a", {
544
+ siteId: SITE_A,
545
+ macAddress: "aa:bb:cc:00:00:01",
546
+ }),
547
+ makeDevice("till-b", "till-b", {
548
+ siteId: SITE_A,
549
+ macAddress: "AA-BB-CC-00-00-01",
550
+ }),
551
+ ],
552
+ now,
553
+ [],
554
+ [registerRow({ ipAddress: undefined })],
555
+ );
556
+
557
+ expect(edgeBetween(result, "sw", "till-a")).toBeUndefined();
558
+ expect(edgeBetween(result, "sw", "till-b")).toBeUndefined();
559
+ expect(nodeById(result, "endpoint:e-till")).toBeDefined();
560
+ expect(result.adoptedEndpointCount).toBe(0);
561
+ });
562
+
563
+ it("is not confused by the same address in two different sites", () => {
564
+ /*
565
+ * The point of scoping by site: one 10.0.0.5 per branch is the
566
+ * normal case, not an ambiguity. Each site's switch cables its own
567
+ * register.
568
+ */
569
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
570
+ [
571
+ accessSwitch({ id: "sw-a", siteId: SITE_A }),
572
+ accessSwitch({ id: "sw-b", siteId: SITE_B }),
573
+ register({ id: "till-a", siteId: SITE_A }),
574
+ register({ id: "till-b", siteId: SITE_B }),
575
+ ],
576
+ now,
577
+ [],
578
+ [
579
+ registerRow({
580
+ id: "e-a",
581
+ macAddress: "aa:bb:cc:00:00:0a",
582
+ attachedNetworkDeviceId: "sw-a",
583
+ siteId: SITE_A,
584
+ }),
585
+ registerRow({
586
+ id: "e-b",
587
+ macAddress: "aa:bb:cc:00:00:0b",
588
+ attachedNetworkDeviceId: "sw-b",
589
+ siteId: SITE_B,
590
+ }),
591
+ ],
592
+ );
593
+
594
+ expect(edgeBetween(result, "sw-a", "till-a")).toBeDefined();
595
+ expect(edgeBetween(result, "sw-b", "till-b")).toBeDefined();
596
+ expect(edgeBetween(result, "sw-a", "till-b")).toBeUndefined();
597
+ expect(edgeBetween(result, "sw-b", "till-a")).toBeUndefined();
598
+ expect(endpointNodes(result)).toHaveLength(0);
599
+ expect(result.adoptedEndpointCount).toBe(2);
600
+ });
601
+ });
602
+
603
+ describe("what the cable looks like", () => {
604
+ it("labels the switch end if<index> when the port is unnamed and no interface row exists", () => {
605
+ /*
606
+ * The same fallback the stranger path has always used, so a device
607
+ * adopted off a switch whose interface walk is off still gets a
608
+ * port label the operator can look up.
609
+ */
610
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
611
+ [accessSwitch(), register()],
612
+ now,
613
+ [],
614
+ [
615
+ registerRow({
616
+ attachedInterfaceIndex: 12,
617
+ attachedPortName: undefined,
618
+ }),
619
+ ],
620
+ );
621
+
622
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
623
+ expect(edge.fromPort).toBe("if12");
624
+ expect(edge.fromInterface).toBeUndefined();
625
+ });
626
+
627
+ it("leaves the port blank when the row knows neither name nor index", () => {
628
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
629
+ [accessSwitch(), register()],
630
+ now,
631
+ [],
632
+ [
633
+ registerRow({
634
+ attachedInterfaceIndex: undefined,
635
+ attachedPortName: undefined,
636
+ }),
637
+ ],
638
+ );
639
+
640
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
641
+ expect(edge.fromPort).toBeUndefined();
642
+ expect(edge.fromInterface).toBeUndefined();
643
+ expect(edge.protocols).toEqual(["fdb"]);
644
+ });
645
+
646
+ it("draws an ARP-only attachment from the router, with the router as parent of a host", () => {
647
+ /*
648
+ * A router with no bridge table still attaches what its ARP cache
649
+ * binds, by interface index alone. That is the same kind of
650
+ * statement a switch's forwarding table makes — the device hangs
651
+ * off this port — so the router is the host's parent.
652
+ */
653
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
654
+ [
655
+ makeDevice("rtr", "rtr-1", { deviceRole: "router", siteId: SITE_A }),
656
+ register({ deviceRole: "host" }),
657
+ ],
658
+ now,
659
+ [],
660
+ [
661
+ registerRow({
662
+ attachedNetworkDeviceId: "rtr",
663
+ attachedInterfaceIndex: 3,
664
+ attachedPortName: undefined,
665
+ }),
666
+ ],
667
+ );
668
+
669
+ const edge: NetworkTopologyEdge = edgeBetween(result, "rtr", "till")!;
670
+ expect(edge.fromNodeId).toBe("rtr");
671
+ expect(edge.toNodeId).toBe("till");
672
+ expect(edge.protocols).toEqual(["fdb"]);
673
+ expect(edge.fromPort).toBe("if3");
674
+ expect(edge.parentNodeId).toBe("rtr");
675
+ expect(endpointNodes(result)).toHaveLength(0);
676
+ expect(result.adoptedEndpointCount).toBe(1);
677
+ });
678
+
679
+ it("adopts a device found in its own tables without drawing a cable to itself", () => {
680
+ /*
681
+ * A router's ARP cache lists its own address. The row is not an
682
+ * endpoint and must not be minted as one, but an edge from a node
683
+ * to itself is not a cable either — so it is counted as adopted and
684
+ * draws nothing.
685
+ */
686
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
687
+ [
688
+ makeDevice("rtr", "rtr-1", {
689
+ deviceRole: "router",
690
+ hostname: "10.0.0.1",
691
+ siteId: SITE_A,
692
+ }),
693
+ ],
694
+ now,
695
+ [],
696
+ [
697
+ makeEndpoint("e-self", "aa:bb:cc:00:00:ff", {
698
+ ipAddress: "10.0.0.1",
699
+ attachedNetworkDeviceId: "rtr",
700
+ attachedInterfaceIndex: 1,
701
+ siteId: SITE_A,
702
+ }),
703
+ ],
704
+ );
705
+
706
+ expect(result.nodes).toHaveLength(1);
707
+ expect(result.edges).toHaveLength(0);
708
+ expect(nodeById(result, "endpoint:e-self")).toBeUndefined();
709
+ expect(result.adoptedEndpointCount).toBe(1);
710
+ expect(result.droppedEndpointCount).toBe(0);
711
+ });
712
+ });
713
+
714
+ describe("what lands on the device node", () => {
715
+ it("stamps the MAC, address and VLAN the tables knew onto the device", () => {
716
+ /*
717
+ * The detail drawer for a ping-only device had nothing to show but
718
+ * a name and an address. The forwarding table knows its MAC and the
719
+ * VLAN it was learned on; the ARP join knows its address. All three
720
+ * land on the node, the MAC normalised so it reads the same as the
721
+ * endpoint rows beside it.
722
+ */
723
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
724
+ [accessSwitch(), register()],
725
+ now,
726
+ [],
727
+ [registerRow({ macAddress: "AA-BB-CC-00-00-10", vlanId: 40 })],
728
+ );
729
+
730
+ const node: NetworkTopologyNode = nodeById(result, "till")!;
731
+ expect(node.macAddress).toBe("aa:bb:cc:00:00:10");
732
+ expect(node.ipAddress).toBe("10.0.0.5");
733
+ expect(node.vlanId).toBe(40);
734
+ // Still the device's own node, not an endpoint dressed as one.
735
+ expect(node.kind).toBe("device");
736
+ expect(node.isManaged).toBe(true);
737
+ });
738
+
739
+ it("carries the declared MAC on the node even when nothing adopts it", () => {
740
+ /*
741
+ * An operator checking why a device floats wants to see what the
742
+ * map was looking for. The declared MAC is shown whether or not a
743
+ * forwarding table has found it yet — normalised, so it can be
744
+ * compared by eye against the switch's table.
745
+ */
746
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
747
+ [register({ macAddress: "AA-BB-CC-00-00-11" })],
748
+ now,
749
+ );
750
+
751
+ const node: NetworkTopologyNode = nodeById(result, "till")!;
752
+ expect(node.macAddress).toBe("aa:bb:cc:00:00:11");
753
+ expect(node.ipAddress).toBeUndefined();
754
+ expect(node.vlanId).toBeUndefined();
755
+ });
756
+
757
+ it("fills gaps only: a declared MAC is not overwritten by the MAC an address match found", () => {
758
+ /*
759
+ * The hardware-swap case: the operator declared the old unit's MAC,
760
+ * the new unit answers at the same address. The row matches by
761
+ * address, and what the operator declared stays on the node — the
762
+ * map reports what it was told, it does not silently correct it.
763
+ */
764
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
765
+ [accessSwitch(), register({ macAddress: "aa:bb:cc:00:00:12" })],
766
+ now,
767
+ [],
768
+ [registerRow({ macAddress: "aa:bb:cc:00:00:99", vlanId: 40 })],
769
+ );
770
+
771
+ const node: NetworkTopologyNode = nodeById(result, "till")!;
772
+ expect(node.macAddress).toBe("aa:bb:cc:00:00:12");
773
+ expect(node.ipAddress).toBe("10.0.0.5");
774
+ expect(node.vlanId).toBe(40);
775
+ expect(edgeBetween(result, "sw", "till")).toBeDefined();
776
+ });
777
+ });
778
+
779
+ describe("which end is the parent", () => {
780
+ /*
781
+ * A forwarding table is not symmetric the way a neighbour protocol is:
782
+ * the switch LEARNED this MAC on an access port, which says the device
783
+ * hangs off it — for a register, a phone, a camera, a server. It says
784
+ * no such thing about a router or a firewall, whose MAC a switch learns
785
+ * on the port that leads UPSTREAM, nor about another switch, where the
786
+ * table cannot tell which of the two is nearer the core. Those are left
787
+ * for the layout to infer, exactly as an LLDP edge is.
788
+ */
789
+ const hangsOffTheSwitch: ReadonlyArray<string | undefined> = [
790
+ undefined,
791
+ "host",
792
+ "phone",
793
+ "printer",
794
+ "camera",
795
+ "server",
796
+ "wirelessAccessPoint",
797
+ ];
798
+ const leftForTheLayout: ReadonlyArray<string> = [
799
+ "router",
800
+ "firewall",
801
+ "loadBalancer",
802
+ "switch",
803
+ ];
804
+
805
+ const parentFor: (
806
+ deviceRole: string | undefined,
807
+ deviceRoles?: ReadonlyArray<TopologyDeviceRoleInput>,
808
+ ) => string | undefined = (
809
+ deviceRole: string | undefined,
810
+ deviceRoles?: ReadonlyArray<TopologyDeviceRoleInput>,
811
+ ): string | undefined => {
812
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
813
+ [accessSwitch(), register({ deviceRole: deviceRole })],
814
+ now,
815
+ [],
816
+ [registerRow()],
817
+ [],
818
+ new Set<string>(),
819
+ deviceRoles || [],
820
+ );
821
+ return edgeBetween(result, "sw", "till")!.parentNodeId;
822
+ };
823
+
824
+ for (const role of hangsOffTheSwitch) {
825
+ it(`makes the switch the parent of a ${role || "role-less"} device`, () => {
826
+ expect(parentFor(role)).toBe("sw");
827
+ });
828
+ }
829
+
830
+ for (const role of leftForTheLayout) {
831
+ it(`declares no parent for a ${role}`, () => {
832
+ expect(parentFor(role)).toBeUndefined();
833
+ });
834
+ }
835
+
836
+ it("reads a custom role's configured tier: access-layer hangs off the switch, core does not", () => {
837
+ /*
838
+ * A project's own roles carry their own answer to "is this a core
839
+ * device", and it is the only answer available — a "PoS Terminal"
840
+ * or an "SD-WAN Edge" is not in the built-in set. The stamp on the
841
+ * node is read ahead of the built-in fallback.
842
+ */
843
+ const roles: Array<TopologyDeviceRoleInput> = [
844
+ {
845
+ id: "role-pos",
846
+ key: "posTerminal",
847
+ name: "PoS Terminal",
848
+ topologyShape: "hexagon",
849
+ isCoreLayer: false,
850
+ isSnmpWalkable: false,
851
+ },
852
+ {
853
+ id: "role-sdwan",
854
+ key: "sdwanEdge",
855
+ name: "SD-WAN Edge",
856
+ topologyShape: "diamond",
857
+ isCoreLayer: true,
858
+ isSnmpWalkable: true,
859
+ },
860
+ ];
861
+
862
+ expect(parentFor("posTerminal", roles)).toBe("sw");
863
+ expect(parentFor("sdwanEdge", roles)).toBeUndefined();
864
+ });
865
+
866
+ it("lets a project's re-tiering of a built-in role override the built-in answer", () => {
867
+ /*
868
+ * The configured flag wins where the project set one, as everywhere
869
+ * else the stamp is read: a project that filed "router" as access
870
+ * layer (a branch CPE that really does hang off the switch) gets
871
+ * the switch as parent, and a "host" it promoted to core does not.
872
+ */
873
+ const routerDemoted: Array<TopologyDeviceRoleInput> = [
874
+ {
875
+ id: "role-router",
876
+ key: "router",
877
+ name: "Router",
878
+ isCoreLayer: false,
879
+ },
880
+ ];
881
+ const hostPromoted: Array<TopologyDeviceRoleInput> = [
882
+ { id: "role-host", key: "host", name: "Host", isCoreLayer: true },
883
+ ];
884
+
885
+ expect(parentFor("router", routerDemoted)).toBe("sw");
886
+ expect(parentFor("host", hostPromoted)).toBeUndefined();
887
+ });
888
+ });
889
+
890
+ describe("merging into a link that already joins the pair", () => {
891
+ const switchInterfaces: Array<TopologyInterfaceInput> = [
892
+ { networkDeviceId: "sw", interfaceIndex: 7, name: "Gi1/0/7" },
893
+ { networkDeviceId: "sw", interfaceIndex: 9, name: "Gi1/0/9" },
894
+ ];
895
+
896
+ it("merges with a hand-drawn link, replacing the typed switch port with the measured one", () => {
897
+ /*
898
+ * The cable the operator drew because nothing could discover it is
899
+ * the same cable the forwarding table now reports, so it is one
900
+ * line carrying both — not the duplicate an operator would read as
901
+ * a second physical link. Measured beats typed on the switch end:
902
+ * the operator's port is a best recollection, the switch's is what
903
+ * it actually learned the MAC on. The operator's name has no
904
+ * measured counterpart and survives; a link nobody gave a hierarchy
905
+ * gains the one the table implies.
906
+ */
907
+ const manualLinks: Array<TopologyManualLinkInput> = [
908
+ {
909
+ fromDeviceId: "sw",
910
+ toDeviceId: "till",
911
+ fromPortName: "Gi1/0/3",
912
+ toPortName: "eth0",
913
+ name: "Till 4 drop",
914
+ },
915
+ ];
916
+
917
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
918
+ [accessSwitch(), register()],
919
+ now,
920
+ switchInterfaces,
921
+ [registerRow()],
922
+ manualLinks,
923
+ );
924
+
925
+ expect(result.edges).toHaveLength(1);
926
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
927
+ expect(edge.protocols).toEqual(["manual", "fdb"]);
928
+ expect(edge.fromNodeId).toBe("sw");
929
+ expect(edge.fromPort).toBe("Gi1/0/7");
930
+ expect(edge.fromInterface?.interfaceIndex).toBe(7);
931
+ expect(edge.toPort).toBe("eth0");
932
+ expect(edge.toInterface).toBeUndefined();
933
+ expect(edge.name).toBe("Till 4 drop");
934
+ expect(edge.parentNodeId).toBe("sw");
935
+ });
936
+
937
+ it("keeps a parent declared on the hand-drawn link over the one the table implies", () => {
938
+ /*
939
+ * A hierarchy somebody DECLARED beats the one inferred from where a
940
+ * MAC was learned — the specific statement beats the general one,
941
+ * the same precedence the manual-link merge already applies against
942
+ * rules. Declared backwards on purpose here, so the two answers
943
+ * differ and the test can tell which one won.
944
+ */
945
+ const manualLinks: Array<TopologyManualLinkInput> = [
946
+ {
947
+ fromDeviceId: "sw",
948
+ toDeviceId: "till",
949
+ parentDeviceId: "till",
950
+ },
951
+ ];
952
+
953
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
954
+ [accessSwitch(), register()],
955
+ now,
956
+ switchInterfaces,
957
+ [registerRow()],
958
+ manualLinks,
959
+ );
960
+
961
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
962
+ expect(edge.protocols).toEqual(["manual", "fdb"]);
963
+ expect(edge.parentNodeId).toBe("till");
964
+ });
965
+
966
+ it("lands the measured port on the switch end when the link was stored device-first", () => {
967
+ /*
968
+ * Which end is fromNodeId depends on how the operator happened to
969
+ * draw the link. The forwarding table's port belongs to the switch
970
+ * whichever end that is — putting "Gi1/0/7" on the register's eth0
971
+ * would be a wrong cable, not a cosmetic slip.
972
+ */
973
+ const manualLinks: Array<TopologyManualLinkInput> = [
974
+ {
975
+ fromDeviceId: "till",
976
+ toDeviceId: "sw",
977
+ fromPortName: "eth0",
978
+ toPortName: "Gi1/0/3",
979
+ name: "Till 4 drop",
980
+ },
981
+ ];
982
+
983
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
984
+ [accessSwitch(), register()],
985
+ now,
986
+ switchInterfaces,
987
+ [registerRow()],
988
+ manualLinks,
989
+ );
990
+
991
+ expect(result.edges).toHaveLength(1);
992
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
993
+ expect(edge.fromNodeId).toBe("till");
994
+ expect(edge.toNodeId).toBe("sw");
995
+ expect(edge.protocols).toEqual(["manual", "fdb"]);
996
+ expect(edge.fromPort).toBe("eth0");
997
+ expect(edge.fromInterface).toBeUndefined();
998
+ expect(edge.toPort).toBe("Gi1/0/7");
999
+ expect(edge.toInterface?.interfaceIndex).toBe(7);
1000
+ expect(edge.name).toBe("Till 4 drop");
1001
+ expect(edge.parentNodeId).toBe("sw");
1002
+ });
1003
+
1004
+ it("merges with an LLDP link, keeping the protocol's port and interface", () => {
1005
+ /*
1006
+ * Against a neighbour protocol both ports are measured, and the
1007
+ * protocol's is kept: it names the port from both ends, where the
1008
+ * table only knows one — and a forwarding table can lag a re-patch
1009
+ * by a walk, which is exactly when the two disagree.
1010
+ */
1011
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1012
+ [
1013
+ accessSwitch({
1014
+ lldpNeighbors: [
1015
+ {
1016
+ localInterfaceIndex: 7,
1017
+ remoteSysName: "till-4",
1018
+ remotePortId: "eth0",
1019
+ },
1020
+ ],
1021
+ }),
1022
+ register({ sysName: "till-4" }),
1023
+ ],
1024
+ now,
1025
+ [
1026
+ {
1027
+ networkDeviceId: "sw",
1028
+ interfaceIndex: 7,
1029
+ name: "Gi1/0/7",
1030
+ utilizationPercent: 12,
1031
+ },
1032
+ { networkDeviceId: "sw", interfaceIndex: 9, name: "Gi1/0/9" },
1033
+ ],
1034
+ [
1035
+ registerRow({
1036
+ attachedInterfaceIndex: 9,
1037
+ attachedPortName: "Gi1/0/9",
1038
+ }),
1039
+ ],
1040
+ );
1041
+
1042
+ expect(result.edges).toHaveLength(1);
1043
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
1044
+ expect(edge.protocols).toEqual(["lldp", "fdb"]);
1045
+ expect(edge.fromNodeId).toBe("sw");
1046
+ expect(edge.fromPort).toBe("Gi1/0/7");
1047
+ expect(edge.fromInterface?.interfaceIndex).toBe(7);
1048
+ expect(edge.fromInterface?.utilizationPercent).toBe(12);
1049
+ expect(edge.toPort).toBe("eth0");
1050
+ // LLDP states no hierarchy, so the table's is free to apply.
1051
+ expect(edge.parentNodeId).toBe("sw");
1052
+ expect(endpointNodes(result)).toHaveLength(0);
1053
+ expect(result.adoptedEndpointCount).toBe(1);
1054
+ });
1055
+ });
1056
+
1057
+ describe("one cable per device: the freshest sighting draws it", () => {
1058
+ /*
1059
+ * The hardware-swap case. The old unit's MAC is still a row in the
1060
+ * endpoint table (nothing prunes it) and is what the ARP pass stored
1061
+ * on the device; the new unit is a fresher row that matches the device
1062
+ * by address. Drawing both would put the register on two ports at
1063
+ * once, one of them a port nothing has been plugged into for a month.
1064
+ */
1065
+ const swappedRegister: () => TopologyDeviceInput =
1066
+ (): TopologyDeviceInput => {
1067
+ return register({ macAddress: "aa:bb:cc:00:00:01" });
1068
+ };
1069
+ const twoSwitches: () => Array<TopologyDeviceInput> =
1070
+ (): Array<TopologyDeviceInput> => {
1071
+ return [accessSwitch({ id: "sw-a" }), accessSwitch({ id: "sw-b" })];
1072
+ };
1073
+
1074
+ it("draws the fresher row and adopts both", () => {
1075
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1076
+ [...twoSwitches(), swappedRegister()],
1077
+ now,
1078
+ [],
1079
+ [
1080
+ makeEndpoint("e-old", "aa:bb:cc:00:00:01", {
1081
+ attachedNetworkDeviceId: "sw-a",
1082
+ attachedPortName: "Gi1/0/3",
1083
+ lastSeenAt: stale,
1084
+ siteId: SITE_A,
1085
+ }),
1086
+ makeEndpoint("e-new", "aa:bb:cc:00:00:02", {
1087
+ ipAddress: "10.0.0.5",
1088
+ attachedNetworkDeviceId: "sw-b",
1089
+ attachedPortName: "Gi1/0/7",
1090
+ lastSeenAt: fresh,
1091
+ siteId: SITE_A,
1092
+ }),
1093
+ ],
1094
+ );
1095
+
1096
+ expect(result.edges).toHaveLength(1);
1097
+ expect(edgeBetween(result, "sw-a", "till")).toBeUndefined();
1098
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw-b", "till")!;
1099
+ expect(edge.fromPort).toBe("Gi1/0/7");
1100
+ expect(endpointNodes(result)).toHaveLength(0);
1101
+ expect(result.adoptedEndpointCount).toBe(2);
1102
+ expect(result.droppedEndpointCount).toBe(0);
1103
+ });
1104
+
1105
+ it("prefers the MAC match at equal freshness", () => {
1106
+ /*
1107
+ * Seen in the same walk, the row that IS the declared MAC is the
1108
+ * stronger claim about where the box is.
1109
+ */
1110
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1111
+ [...twoSwitches(), swappedRegister()],
1112
+ now,
1113
+ [],
1114
+ [
1115
+ makeEndpoint("e-old", "aa:bb:cc:00:00:01", {
1116
+ attachedNetworkDeviceId: "sw-a",
1117
+ attachedPortName: "Gi1/0/3",
1118
+ lastSeenAt: fresh,
1119
+ siteId: SITE_A,
1120
+ }),
1121
+ makeEndpoint("e-new", "aa:bb:cc:00:00:02", {
1122
+ ipAddress: "10.0.0.5",
1123
+ attachedNetworkDeviceId: "sw-b",
1124
+ attachedPortName: "Gi1/0/7",
1125
+ lastSeenAt: fresh,
1126
+ siteId: SITE_A,
1127
+ }),
1128
+ ],
1129
+ );
1130
+
1131
+ expect(result.edges).toHaveLength(1);
1132
+ expect(edgeBetween(result, "sw-b", "till")).toBeUndefined();
1133
+ expect(edgeBetween(result, "sw-a", "till")!.fromPort).toBe("Gi1/0/3");
1134
+ expect(result.adoptedEndpointCount).toBe(2);
1135
+ });
1136
+
1137
+ it("settles a tie on equal timestamps by MAC order, never by input order", () => {
1138
+ /*
1139
+ * Two rows of the same evidence seen at the same moment: the choice
1140
+ * has to come from something the caller cannot vary between
1141
+ * rebuilds, and the MAC order the builder already sorts by is that.
1142
+ * (A row with NO timestamp is not evidence of an address at all -
1143
+ * see the freshness rule - so the tie is on equal times, not none.)
1144
+ */
1145
+ const rows: () => Array<TopologyEndpointInput> =
1146
+ (): Array<TopologyEndpointInput> => {
1147
+ return [
1148
+ makeEndpoint("e-hi", "aa:bb:cc:00:00:09", {
1149
+ ipAddress: "10.0.0.5",
1150
+ attachedNetworkDeviceId: "sw-a",
1151
+ attachedPortName: "Gi1/0/3",
1152
+ lastSeenAt: fresh,
1153
+ siteId: SITE_A,
1154
+ }),
1155
+ makeEndpoint("e-lo", "aa:bb:cc:00:00:01", {
1156
+ ipAddress: "10.0.0.5",
1157
+ attachedNetworkDeviceId: "sw-b",
1158
+ attachedPortName: "Gi1/0/7",
1159
+ lastSeenAt: fresh,
1160
+ siteId: SITE_A,
1161
+ }),
1162
+ ];
1163
+ };
1164
+
1165
+ const forward: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1166
+ [...twoSwitches(), register()],
1167
+ now,
1168
+ [],
1169
+ rows(),
1170
+ );
1171
+ const reversed: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1172
+ [...twoSwitches(), register()],
1173
+ now,
1174
+ [],
1175
+ rows().reverse(),
1176
+ );
1177
+
1178
+ for (const result of [forward, reversed]) {
1179
+ expect(result.edges).toHaveLength(1);
1180
+ expect(edgeBetween(result, "sw-a", "till")).toBeUndefined();
1181
+ expect(edgeBetween(result, "sw-b", "till")!.fromPort).toBe("Gi1/0/7");
1182
+ expect(result.adoptedEndpointCount).toBe(2);
1183
+ }
1184
+ });
1185
+ });
1186
+
1187
+ describe("bookkeeping", () => {
1188
+ it("adds up: rendered + dropped + adopted is every row passed in", () => {
1189
+ /*
1190
+ * The reason adoptedEndpointCount exists. A count of what the
1191
+ * switches learned that no longer matches what the map shows would
1192
+ * read as rows going missing.
1193
+ */
1194
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1195
+ [accessSwitch(), register()],
1196
+ now,
1197
+ [],
1198
+ [
1199
+ registerRow(),
1200
+ makeEndpoint("e-stranger", "aa:bb:cc:00:00:02", {
1201
+ attachedNetworkDeviceId: "sw",
1202
+ siteId: SITE_A,
1203
+ }),
1204
+ makeEndpoint("e-loose", "aa:bb:cc:00:00:03"),
1205
+ ],
1206
+ );
1207
+
1208
+ expect(endpointNodes(result)).toHaveLength(1);
1209
+ expect(result.adoptedEndpointCount).toBe(1);
1210
+ expect(result.droppedEndpointCount).toBe(1);
1211
+ expect(result.edges).toHaveLength(2);
1212
+ expect(edgeBetween(result, "sw", "till")).toBeDefined();
1213
+ expect(edgeBetween(result, "sw", "endpoint:e-stranger")).toBeDefined();
1214
+ });
1215
+
1216
+ it("never spends the endpoint render cap on an adopted row", () => {
1217
+ /*
1218
+ * An adopted row mints no node, so it cannot crowd one out — and it
1219
+ * must not be crowded out either. The register's MAC sorts after
1220
+ * every stranger here, so if adoption were subject to the cap this
1221
+ * is exactly the row that would go missing.
1222
+ */
1223
+ const strangers: (count: number) => Array<TopologyEndpointInput> = (
1224
+ count: number,
1225
+ ): Array<TopologyEndpointInput> => {
1226
+ const rows: Array<TopologyEndpointInput> = [];
1227
+ for (let i: number = 0; i < count; i++) {
1228
+ const hex: string = i.toString(16).padStart(4, "0");
1229
+ rows.push(
1230
+ makeEndpoint(
1231
+ `ep-${i}`,
1232
+ `aa:bb:cc:dd:${hex.substring(0, 2)}:${hex.substring(2, 4)}`,
1233
+ { attachedNetworkDeviceId: "sw", siteId: SITE_A },
1234
+ ),
1235
+ );
1236
+ }
1237
+ return rows;
1238
+ };
1239
+ const adoptedRow: TopologyEndpointInput = registerRow({
1240
+ macAddress: "fe:ff:ff:ff:ff:fe",
1241
+ ipAddress: undefined,
1242
+ });
1243
+ const devices: () => Array<TopologyDeviceInput> =
1244
+ (): Array<TopologyDeviceInput> => {
1245
+ return [
1246
+ accessSwitch(),
1247
+ register({ hostname: undefined, macAddress: "fe:ff:ff:ff:ff:fe" }),
1248
+ ];
1249
+ };
1250
+
1251
+ const atTheCap: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1252
+ devices(),
1253
+ now,
1254
+ [],
1255
+ [...strangers(2000), adoptedRow],
1256
+ );
1257
+ expect(endpointNodes(atTheCap)).toHaveLength(2000);
1258
+ expect(atTheCap.endpointsTruncated).toBe(false);
1259
+ expect(edgeBetween(atTheCap, "sw", "till")).toBeDefined();
1260
+ expect(atTheCap.adoptedEndpointCount).toBe(1);
1261
+
1262
+ const overTheCap: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1263
+ devices(),
1264
+ now,
1265
+ [],
1266
+ [...strangers(2001), adoptedRow],
1267
+ );
1268
+ expect(endpointNodes(overTheCap)).toHaveLength(2000);
1269
+ expect(overTheCap.endpointsTruncated).toBe(true);
1270
+ expect(edgeBetween(overTheCap, "sw", "till")).toBeDefined();
1271
+ expect(overTheCap.adoptedEndpointCount).toBe(1);
1272
+ });
1273
+
1274
+ it("drops the adopted cable with whichever end the project hid, and counts it regardless", () => {
1275
+ /*
1276
+ * Suppression is applied last, on the finished graph, so hiding a
1277
+ * node cannot change how anything resolved: the row is still
1278
+ * adopted (and counted), the line just has nowhere to go.
1279
+ */
1280
+ const switchHidden: TopologyBuildResult =
1281
+ NetworkTopologyUtil.buildTopology(
1282
+ [accessSwitch(), register()],
1283
+ now,
1284
+ [],
1285
+ [registerRow()],
1286
+ [],
1287
+ new Set<string>(["sw"]),
1288
+ );
1289
+ expect(switchHidden.edges).toHaveLength(0);
1290
+ expect(nodeById(switchHidden, "till")).toBeDefined();
1291
+ expect(nodeById(switchHidden, "endpoint:e-till")).toBeUndefined();
1292
+ expect(switchHidden.adoptedEndpointCount).toBe(1);
1293
+ expect(switchHidden.suppressedNodeCount).toBe(1);
1294
+
1295
+ const deviceHidden: TopologyBuildResult =
1296
+ NetworkTopologyUtil.buildTopology(
1297
+ [accessSwitch(), register()],
1298
+ now,
1299
+ [],
1300
+ [registerRow()],
1301
+ [],
1302
+ new Set<string>(["till"]),
1303
+ );
1304
+ expect(deviceHidden.edges).toHaveLength(0);
1305
+ expect(nodeById(deviceHidden, "sw")).toBeDefined();
1306
+ expect(nodeById(deviceHidden, "endpoint:e-till")).toBeUndefined();
1307
+ expect(deviceHidden.adoptedEndpointCount).toBe(1);
1308
+ expect(deviceHidden.suppressedNodeCount).toBe(1);
1309
+ });
1310
+
1311
+ it("builds the same graph whatever order the devices and rows arrive in", () => {
1312
+ /*
1313
+ * Query order is not something a map may depend on: two rebuilds of
1314
+ * one estate must draw one picture, or the operator sees cables
1315
+ * move between refreshes.
1316
+ */
1317
+ const devices: () => Array<TopologyDeviceInput> =
1318
+ (): Array<TopologyDeviceInput> => {
1319
+ return [
1320
+ accessSwitch({ id: "sw-a" }),
1321
+ accessSwitch({ id: "sw-b" }),
1322
+ register({ id: "till-1", name: "till-1" }),
1323
+ makeDevice("phone-1", "handset-1", {
1324
+ deviceRole: "phone",
1325
+ siteId: SITE_A,
1326
+ macAddress: "aa:bb:cc:00:00:22",
1327
+ }),
1328
+ ];
1329
+ };
1330
+ const rows: () => Array<TopologyEndpointInput> =
1331
+ (): Array<TopologyEndpointInput> => {
1332
+ return [
1333
+ registerRow({ id: "e-till", attachedNetworkDeviceId: "sw-a" }),
1334
+ makeEndpoint("e-phone", "AA-BB-CC-00-00-22", {
1335
+ attachedNetworkDeviceId: "sw-b",
1336
+ attachedPortName: "Gi1/0/2",
1337
+ vlanId: 100,
1338
+ siteId: SITE_A,
1339
+ }),
1340
+ makeEndpoint("e-s1", "aa:bb:cc:00:00:31", {
1341
+ attachedNetworkDeviceId: "sw-a",
1342
+ siteId: SITE_A,
1343
+ }),
1344
+ makeEndpoint("e-s2", "aa:bb:cc:00:00:32", {
1345
+ attachedNetworkDeviceId: "sw-b",
1346
+ siteId: SITE_A,
1347
+ }),
1348
+ ];
1349
+ };
1350
+
1351
+ /*
1352
+ * Key order inside an object is insertion order, which a merge
1353
+ * could plausibly vary, so the comparison sorts keys as well as
1354
+ * elements — it is the graph that must be identical, not the
1355
+ * bytes.
1356
+ */
1357
+ const canonical: (value: unknown) => unknown = (
1358
+ value: unknown,
1359
+ ): unknown => {
1360
+ if (Array.isArray(value)) {
1361
+ return value.map(canonical);
1362
+ }
1363
+ if (value && typeof value === "object") {
1364
+ const record: Record<string, unknown> = value as Record<
1365
+ string,
1366
+ unknown
1367
+ >;
1368
+ const sorted: Record<string, unknown> = {};
1369
+ for (const key of Object.keys(record).sort()) {
1370
+ sorted[key] = canonical(record[key]);
1371
+ }
1372
+ return sorted;
1373
+ }
1374
+ return value;
1375
+ };
1376
+ const picture: (result: TopologyBuildResult) => string = (
1377
+ result: TopologyBuildResult,
1378
+ ): string => {
1379
+ const nodes: Array<NetworkTopologyNode> = [...result.nodes].sort(
1380
+ (a: NetworkTopologyNode, b: NetworkTopologyNode) => {
1381
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
1382
+ },
1383
+ );
1384
+ const edges: Array<NetworkTopologyEdge> = [...result.edges].sort(
1385
+ (a: NetworkTopologyEdge, b: NetworkTopologyEdge) => {
1386
+ const aKey: string = [a.fromNodeId, a.toNodeId].sort().join("::");
1387
+ const bKey: string = [b.fromNodeId, b.toNodeId].sort().join("::");
1388
+ return aKey < bKey ? -1 : aKey > bKey ? 1 : 0;
1389
+ },
1390
+ );
1391
+ return JSON.stringify(canonical({ nodes, edges }));
1392
+ };
1393
+
1394
+ const forward: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1395
+ devices(),
1396
+ now,
1397
+ [],
1398
+ rows(),
1399
+ );
1400
+ const reversed: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1401
+ devices().reverse(),
1402
+ now,
1403
+ [],
1404
+ rows().reverse(),
1405
+ );
1406
+
1407
+ expect(picture(reversed)).toBe(picture(forward));
1408
+ // And the picture is the one expected, not two identical mistakes.
1409
+ expect(forward.edges).toHaveLength(4);
1410
+ expect(edgeBetween(forward, "sw-a", "till-1")).toBeDefined();
1411
+ expect(edgeBetween(forward, "sw-b", "phone-1")!.fromPort).toBe("Gi1/0/2");
1412
+ expect(nodeById(forward, "phone-1")!.vlanId).toBe(100);
1413
+ expect(endpointNodes(forward)).toHaveLength(2);
1414
+ expect(forward.adoptedEndpointCount).toBe(2);
1415
+ });
1416
+
1417
+ it("reports zero adopted rows on a legacy call with no endpoints argument", () => {
1418
+ /*
1419
+ * The field is additive: a caller from before endpoints existed
1420
+ * gets a number, not undefined, so a reader that adds the three
1421
+ * counts does not have to guard this one.
1422
+ */
1423
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1424
+ [accessSwitch(), register({ macAddress: "aa:bb:cc:00:00:01" })],
1425
+ now,
1426
+ );
1427
+
1428
+ expect(result.adoptedEndpointCount).toBe(0);
1429
+ expect(result.droppedEndpointCount).toBe(0);
1430
+ expect(result.endpointsTruncated).toBe(false);
1431
+ expect(result.edges).toHaveLength(0);
1432
+ });
1433
+ });
1434
+
1435
+ describe("refusals the review asked for", () => {
1436
+ it("keeps a neighbour protocol's port whole when the table names a different one", () => {
1437
+ /*
1438
+ * Re-patch lag: LLDP still says port 7, the forwarding table already
1439
+ * says port 9. Splicing the two field by field labelled port 7 with
1440
+ * port 9's name and painted it with port 9's state. The protocol's
1441
+ * end stays exactly as reported until the two agree.
1442
+ */
1443
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1444
+ [
1445
+ makeDevice("sw", "access-sw-1", {
1446
+ deviceRole: "switch",
1447
+ lldpNeighbors: [
1448
+ {
1449
+ localInterfaceIndex: 7,
1450
+ remoteSysName: "till-4",
1451
+ remotePortId: "eth0",
1452
+ },
1453
+ ],
1454
+ }),
1455
+ makeDevice("till", "till-4", {
1456
+ sysName: "till-4",
1457
+ hostname: "10.0.0.5",
1458
+ }),
1459
+ ],
1460
+ now,
1461
+ [
1462
+ {
1463
+ networkDeviceId: "sw",
1464
+ interfaceIndex: 9,
1465
+ name: "Gi1/0/9",
1466
+ isOperationallyUp: false,
1467
+ utilizationPercent: 80,
1468
+ },
1469
+ ],
1470
+ [
1471
+ makeEndpoint("row", "aa:bb:cc:00:00:01", {
1472
+ ipAddress: "10.0.0.5",
1473
+ attachedNetworkDeviceId: "sw",
1474
+ attachedInterfaceIndex: 9,
1475
+ }),
1476
+ ],
1477
+ );
1478
+
1479
+ const edge: NetworkTopologyEdge | undefined = edgeBetween(
1480
+ result,
1481
+ "sw",
1482
+ "till",
1483
+ );
1484
+ expect(edge).toBeDefined();
1485
+ expect(edge!.protocols).toEqual(["lldp", "fdb"]);
1486
+ expect(edge!.fromPort).toBe("if7");
1487
+ expect(edge!.fromInterface).toEqual({
1488
+ interfaceIndex: 7,
1489
+ interfaceName: undefined,
1490
+ isOperationallyUp: undefined,
1491
+ isAdministrativelyUp: undefined,
1492
+ utilizationPercent: undefined,
1493
+ inRateMbps: undefined,
1494
+ outRateMbps: undefined,
1495
+ errorsPerSecond: undefined,
1496
+ });
1497
+ expect(edge!.learnedByNodeId).toBe("sw");
1498
+ });
1499
+
1500
+ it("lets the table fill the gaps when both name the same port", () => {
1501
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1502
+ [
1503
+ makeDevice("sw", "access-sw-1", {
1504
+ deviceRole: "switch",
1505
+ lldpNeighbors: [
1506
+ { localInterfaceIndex: 9, remoteSysName: "till-4" },
1507
+ ],
1508
+ }),
1509
+ makeDevice("till", "till-4", {
1510
+ sysName: "till-4",
1511
+ hostname: "10.0.0.5",
1512
+ }),
1513
+ ],
1514
+ now,
1515
+ [
1516
+ {
1517
+ networkDeviceId: "sw",
1518
+ interfaceIndex: 9,
1519
+ name: "Gi1/0/9",
1520
+ isOperationallyUp: true,
1521
+ utilizationPercent: 12,
1522
+ },
1523
+ ],
1524
+ [
1525
+ makeEndpoint("row", "aa:bb:cc:00:00:01", {
1526
+ ipAddress: "10.0.0.5",
1527
+ attachedNetworkDeviceId: "sw",
1528
+ attachedInterfaceIndex: 9,
1529
+ }),
1530
+ ],
1531
+ );
1532
+
1533
+ const edge: NetworkTopologyEdge | undefined = edgeBetween(
1534
+ result,
1535
+ "sw",
1536
+ "till",
1537
+ );
1538
+ expect(edge!.fromPort).toBe("Gi1/0/9");
1539
+ expect(edge!.fromInterface?.interfaceIndex).toBe(9);
1540
+ expect(edge!.fromInterface?.utilizationPercent).toBe(12);
1541
+ });
1542
+
1543
+ it("refuses a declared MAC that a walked device reports as its own interface", () => {
1544
+ /*
1545
+ * The neighbour matcher already deletes a key two devices claim; the
1546
+ * adoption index must not disagree with it and cable a register onto
1547
+ * the port where a switch's own MAC was learned.
1548
+ */
1549
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1550
+ [
1551
+ makeDevice("s2", "dist-sw", {
1552
+ deviceRole: "switch",
1553
+ macAddresses: ["aa:bb:cc:dd:ee:ff"],
1554
+ }),
1555
+ makeDevice("s3", "access-sw", { deviceRole: "switch" }),
1556
+ makeDevice("d", "till-9", {
1557
+ hostname: "10.0.20.1",
1558
+ macAddress: "aa:bb:cc:dd:ee:ff",
1559
+ }),
1560
+ ],
1561
+ now,
1562
+ [],
1563
+ [
1564
+ makeEndpoint("row", "aa:bb:cc:dd:ee:ff", {
1565
+ attachedNetworkDeviceId: "s3",
1566
+ attachedInterfaceIndex: 5,
1567
+ }),
1568
+ ],
1569
+ );
1570
+
1571
+ expect(edgeBetween(result, "s3", "d")).toBeUndefined();
1572
+ expect(nodeById(result, "endpoint:row")).toBeDefined();
1573
+ expect(result.adoptedEndpointCount).toBe(0);
1574
+ });
1575
+
1576
+ it("does not adopt by address from a row nothing has seen for longer than the fresh window", () => {
1577
+ /*
1578
+ * An address is re-leased. A months-old row binding the register's
1579
+ * address to some other box's MAC must not cable the register to
1580
+ * that box's port with a live node's confidence; it stays the stale,
1581
+ * down-coloured endpoint it is.
1582
+ */
1583
+ const monthsAgo: Date = new Date("2026-04-01T00:00:00Z");
1584
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1585
+ [accessSwitch(), register()],
1586
+ now,
1587
+ [],
1588
+ [
1589
+ makeEndpoint("laptop", "de:ad:be:ef:00:01", {
1590
+ ipAddress: "10.0.0.5",
1591
+ siteId: SITE_A,
1592
+ attachedNetworkDeviceId: "sw",
1593
+ attachedInterfaceIndex: 12,
1594
+ lastSeenAt: monthsAgo,
1595
+ }),
1596
+ ],
1597
+ );
1598
+
1599
+ expect(edgeBetween(result, "sw", "till")).toBeUndefined();
1600
+ expect(nodeById(result, "endpoint:laptop")?.status).toBe("down");
1601
+ expect(result.adoptedEndpointCount).toBe(0);
1602
+ expect(nodeById(result, "till")?.macAddress).toBeUndefined();
1603
+ });
1604
+
1605
+ it("still adopts by MAC from a row nothing has seen for a while", () => {
1606
+ // A MAC is not re-leased: it names the box for good, stale or not.
1607
+ const monthsAgo: Date = new Date("2026-04-01T00:00:00Z");
1608
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1609
+ [accessSwitch(), register({ macAddress: "aa:bb:cc:dd:ee:01" })],
1610
+ now,
1611
+ [],
1612
+ [
1613
+ makeEndpoint("row", "aa:bb:cc:dd:ee:01", {
1614
+ attachedNetworkDeviceId: "sw",
1615
+ attachedInterfaceIndex: 12,
1616
+ lastSeenAt: monthsAgo,
1617
+ }),
1618
+ ],
1619
+ );
1620
+
1621
+ expect(edgeBetween(result, "sw", "till")).toBeDefined();
1622
+ expect(result.adoptedEndpointCount).toBe(1);
1623
+ });
1624
+ });
1625
+
1626
+ describe("which end learned", () => {
1627
+ /*
1628
+ * The pair may already be joined the other way round - a hand-drawn
1629
+ * link stored device->switch keeps its ends when the attachment merges
1630
+ * in - so "the switch is the from end" is not something a reader can
1631
+ * rely on. The builder says outright which end's table this came from.
1632
+ */
1633
+ it("stamps the switch as the learner on a fresh learned edge", () => {
1634
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1635
+ [accessSwitch(), register()],
1636
+ now,
1637
+ [],
1638
+ [registerRow()],
1639
+ );
1640
+
1641
+ const edge: NetworkTopologyEdge | undefined = edgeBetween(
1642
+ result,
1643
+ "sw",
1644
+ "till",
1645
+ );
1646
+ expect(edge).toBeDefined();
1647
+ expect(edge!.learnedByNodeId).toBe("sw");
1648
+ expect(edge!.fromNodeId).toBe("sw");
1649
+ });
1650
+
1651
+ it("stamps the switch as the learner when it is the TO end of the link it merged into", () => {
1652
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1653
+ [accessSwitch(), register()],
1654
+ now,
1655
+ [],
1656
+ [registerRow()],
1657
+ [{ fromDeviceId: "till", toDeviceId: "sw", name: "typed in" }],
1658
+ );
1659
+
1660
+ const edge: NetworkTopologyEdge | undefined = edgeBetween(
1661
+ result,
1662
+ "sw",
1663
+ "till",
1664
+ );
1665
+ expect(edge).toBeDefined();
1666
+ expect(edge!.fromNodeId).toBe("till");
1667
+ expect(edge!.toNodeId).toBe("sw");
1668
+ expect(edge!.protocols).toEqual(["manual", "fdb"]);
1669
+ expect(edge!.learnedByNodeId).toBe("sw");
1670
+ // The measured port lands on the switch's end, which is the TO end here.
1671
+ expect(edge!.toPort).toBe(registerRow().attachedPortName);
1672
+ });
1673
+
1674
+ it("stamps the switch as the learner on an endpoint node's edge too", () => {
1675
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1676
+ [accessSwitch()],
1677
+ now,
1678
+ [],
1679
+ [
1680
+ makeEndpoint("stranger", "aa:00:00:00:00:99", {
1681
+ attachedNetworkDeviceId: "sw",
1682
+ attachedInterfaceIndex: 3,
1683
+ }),
1684
+ ],
1685
+ );
1686
+
1687
+ const edge: NetworkTopologyEdge | undefined = edgeBetween(
1688
+ result,
1689
+ "sw",
1690
+ "endpoint:stranger",
1691
+ );
1692
+ expect(edge).toBeDefined();
1693
+ expect(edge!.learnedByNodeId).toBe("sw");
1694
+ });
1695
+ });
1696
+
1697
+ describe("the declared MAC as an LLDP chassis id", () => {
1698
+ it("resolves a chassis id that is the device's declared MAC", () => {
1699
+ /*
1700
+ * A ping-only device has no interface walk to report its MACs from,
1701
+ * so the declared MAC is the only MAC it can answer to when a switch
1702
+ * advertises it as an LLDP chassis id (subtype 4). Without it the
1703
+ * peer is drawn as a stranger beside the device it is.
1704
+ */
1705
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1706
+ [
1707
+ accessSwitch({
1708
+ lldpNeighbors: [
1709
+ {
1710
+ localInterfaceIndex: 7,
1711
+ remoteChassisId: "AA:BB:CC:00:00:20",
1712
+ remotePortId: "eth0",
1713
+ },
1714
+ ],
1715
+ }),
1716
+ register({ hostname: undefined, macAddress: "aa-bb-cc-00-00-20" }),
1717
+ ],
1718
+ now,
1719
+ );
1720
+
1721
+ expect(result.nodes).toHaveLength(2);
1722
+ const edge: NetworkTopologyEdge = edgeBetween(result, "sw", "till")!;
1723
+ expect(edge.protocols).toEqual(["lldp"]);
1724
+ expect(edge.toPort).toBe("eth0");
1725
+ expect(edge.parentNodeId).toBeUndefined();
1726
+ });
1727
+
1728
+ it("still draws a stranger when the device declares no MAC", () => {
1729
+ // The control: the "before" picture, with nothing to match on.
1730
+ const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
1731
+ [
1732
+ accessSwitch({
1733
+ lldpNeighbors: [
1734
+ {
1735
+ localInterfaceIndex: 7,
1736
+ remoteChassisId: "AA:BB:CC:00:00:20",
1737
+ remotePortId: "eth0",
1738
+ },
1739
+ ],
1740
+ }),
1741
+ register({ hostname: undefined }),
1742
+ ],
1743
+ now,
1744
+ );
1745
+
1746
+ expect(result.nodes).toHaveLength(3);
1747
+ expect(edgeBetween(result, "sw", "till")).toBeUndefined();
1748
+ expect(
1749
+ result.nodes.some((node: NetworkTopologyNode) => {
1750
+ return node.kind === "unmanaged";
1751
+ }),
1752
+ ).toBe(true);
1753
+ });
1754
+ });
1755
+ });