@oneuptime/common 12.0.33 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/AnalyticsModels/RumSession.ts +117 -1
- package/Models/AnalyticsModels/RumSessionChunk.ts +13 -0
- package/Models/DatabaseModels/AlertCustomField.ts +77 -3
- package/Models/DatabaseModels/ApiKeyPermission.ts +3 -27
- package/Models/DatabaseModels/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/IncidentCustomField.ts +77 -3
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/InventoryItemCustomField.ts +77 -3
- package/Models/DatabaseModels/MonitorCustomField.ts +77 -3
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +77 -3
- package/Models/DatabaseModels/RumApplication.ts +6 -6
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +77 -3
- package/Models/DatabaseModels/StatusPageCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamMember.ts +0 -9
- package/Models/DatabaseModels/TeamMemberCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamPermission.ts +3 -24
- package/Models/DatabaseModels/TelemetryIngestionKey.ts +260 -0
- package/Models/DatabaseModels/UserTelegram.ts +0 -4
- package/Scripts/benchmark-fanin-capacity.js +181 -0
- package/Server/API/TelemetryAPI.ts +1284 -164
- package/Server/API/UserNotificationSettingAPI.ts +55 -0
- package/Server/API/UserTelegramAPI.ts +48 -4
- package/Server/EnvironmentConfig.ts +82 -18
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.ts +139 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.ts +121 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Infrastructure/QueueWorker.ts +20 -14
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Semaphore.ts +2 -0
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Middleware/TelemetryIngest.ts +453 -7
- package/Server/Services/AccessTokenService.ts +1 -0
- package/Server/Services/AlertCustomFieldService.ts +98 -0
- package/Server/Services/AlertService.ts +37 -0
- package/Server/Services/ApiKeyPermissionService.ts +461 -29
- package/Server/Services/CustomFieldMappingService.ts +879 -0
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/GlobalConfigService.ts +194 -0
- package/Server/Services/IncidentCustomFieldService.ts +98 -0
- package/Server/Services/IncidentService.ts +35 -0
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MetricService.ts +194 -0
- package/Server/Services/MonitorService.ts +32 -0
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +427 -66
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/ProjectService.ts +22 -0
- package/Server/Services/RoutineEmailSettingsService.ts +73 -0
- package/Server/Services/RumSessionReplayViewService.ts +104 -24
- package/Server/Services/ScheduledMaintenanceCustomFieldService.ts +98 -0
- package/Server/Services/ScheduledMaintenanceService.ts +32 -0
- package/Server/Services/TeamMemberService.ts +78 -0
- package/Server/Services/TeamPermissionService.ts +235 -3
- package/Server/Services/TelemetryIngestionKeyService.ts +722 -19
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Services/UserNotificationSettingService.ts +34 -6
- package/Server/Services/UserTelegramService.ts +326 -5
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryHelper.ts +2 -0
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/APIKey/AccessPermission.ts +5 -2
- package/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.ts +54 -0
- package/Server/Utils/CustomField/CustomFieldMappingRegistry.ts +415 -0
- package/Server/Utils/CustomField/CustomFieldMappingValidator.ts +335 -0
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/DataSource/EgressGuard.ts +177 -8
- package/Server/Utils/EmailRollup/EmailRollupConstants.ts +38 -5
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +67 -5
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/FrontendEnvironment.ts +40 -0
- package/Server/Utils/LogRedaction.ts +20 -0
- package/Server/Utils/Logger.ts +11 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +394 -86
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +30 -4
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorAlert.ts +29 -6
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +312 -52
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/MonitorIncident.ts +22 -6
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +29 -0
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Monitor/SeriesContextEnricher.ts +304 -0
- package/Server/Utils/SSRFProtection.ts +217 -10
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +239 -138
- package/Server/Utils/SessionReplay/SessionReplayHealthCounters.ts +305 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1430 -289
- package/Server/Utils/StartServer.ts +2 -17
- package/Server/Utils/TelegramVerificationToken.ts +185 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/PinServiceName.ts +197 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +38 -16
- package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +92 -0
- package/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.ts +217 -0
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +975 -95
- package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +202 -0
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +404 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +468 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageLastUpdated.test.tsx +263 -0
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/App/StatusPage/StatusPageOverviewLiveAndSearch.test.tsx +753 -0
- package/Tests/App/StatusPage/StatusPageResourceSearchBox.test.tsx +224 -0
- package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +262 -0
- package/Tests/Models/CustomFieldMappingColumns.test.ts +166 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +20 -6
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/ResponsiveVisibility.test.ts +115 -0
- package/Tests/ResponsiveVisibility.ts +183 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +1940 -216
- package/Tests/Server/API/UserNotificationSettingAPI.test.ts +186 -0
- package/Tests/Server/API/UserTelegramAPISecurity.test.ts +231 -0
- package/Tests/Server/EnvironmentConfigFrontendSecurity.test.ts +234 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +241 -0
- package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +41 -0
- package/Tests/Server/Infrastructure/Postgres/SessionReplayRecordEverySessionByDefaultMigration.test.ts +169 -0
- package/Tests/Server/Infrastructure/QueueWorkerTimeout.test.ts +210 -0
- package/Tests/Server/Infrastructure/TelemetryExporterDeploymentConfig.test.ts +130 -0
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -1
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +1163 -0
- package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +5 -3
- package/Tests/Server/Services/AddTelemetryIngestionKeyTypeMigration.test.ts +555 -0
- package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +678 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +276 -28
- package/Tests/Server/Services/CustomFieldDropdownOptionsColumnWidth.test.ts +431 -0
- package/Tests/Server/Services/CustomFieldMappingService.test.ts +850 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +32 -0
- package/Tests/Server/Services/GlobalConfigService.test.ts +414 -1
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MetricRawEntityKeyPrune.test.ts +592 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1351 -74
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Services/RoutineEmailSettingsPostgres.test.ts +329 -0
- package/Tests/Server/Services/RumSessionReplayViewService.test.ts +267 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +9 -0
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +9 -0
- package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +834 -0
- package/Tests/Server/Services/TelemetryIngestionKeyPolicyResolution.test.ts +814 -0
- package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +998 -0
- package/Tests/Server/Services/UserNotificationSettingRollupRouting.test.ts +141 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +18 -10
- package/Tests/Server/Services/UserTelegramVerificationSecurity.test.ts +688 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/AI/SRE/Insights/FixRouting.test.ts +497 -0
- package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +383 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +7 -3
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterConfig.test.ts +315 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingValidator.test.ts +437 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +300 -11
- package/Tests/Server/Utils/EmailRollup/EmailRollupBurstWindow.test.ts +83 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +2 -2
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerPreferences.test.ts +362 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +77 -1
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/FrontendEnvironment.test.ts +190 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +26 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +23 -9
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +590 -0
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestHeaderCriteria.test.ts +236 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +659 -8
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/MonitorTemplateUtilSeriesContext.test.ts +176 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Monitor/SeriesContextEnricher.test.ts +356 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +595 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +198 -1
- package/Tests/Server/Utils/SessionReplay/SessionReplayHealthCounters.test.ts +453 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1378 -0
- package/Tests/Server/Utils/SessionReplayOriginAllowListRefactor.test.ts +420 -0
- package/Tests/Server/Utils/TelegramVerificationToken.test.ts +253 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/PinServiceName.test.ts +666 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriterCapacity.test.ts +458 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +424 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.test.ts +528 -0
- package/Tests/Server/Utils/TelemetryExporterEnvironment.test.ts +56 -0
- package/Tests/Server/Utils/VM/VMRunnerHostBridgeLatency.test.ts +74 -0
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +100 -7
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +1176 -6
- package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +220 -0
- package/Tests/Types/CustomField/CustomFieldValueMapping.test.ts +348 -0
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +300 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +422 -53
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +495 -15
- package/Tests/Types/Monitor/DockerSwarmAlertTemplates.test.ts +191 -45
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +370 -42
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +375 -20
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +636 -42
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +189 -0
- package/Tests/Types/Monitor/KubernetesTemplateGroupByKeys.test.ts +95 -14
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +190 -21
- package/Tests/Types/Monitor/ProxmoxAlertTemplates.test.ts +352 -22
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationAlertDebuggability.test.ts +363 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +93 -18
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +259 -0
- package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +476 -0
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +136 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesDebugHints.test.ts +661 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +507 -0
- package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +270 -0
- package/Tests/Types/Monitor/TemplateGroupByKeys.test.ts +38 -10
- package/Tests/Types/Monitor/Utils/RecommendationCriteriaAssertions.ts +102 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/NotificationSetting/RoutineEmailEvents.test.ts +47 -0
- package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +608 -0
- package/Tests/Types/Rum/SessionReplayCustomEvents.test.ts +434 -0
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/Types/WebsiteRequest.test.ts +285 -3
- package/Tests/UI/Components/Charts/ChartBucketIdentity.test.ts +16 -3
- package/Tests/UI/Components/Charts/ChartTrailingBucketGap.test.ts +691 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +12 -4
- package/Tests/UI/Components/ComponentsModalUsability.test.tsx +518 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailMapping.test.tsx +324 -0
- package/Tests/UI/Components/CustomFields/MapFromCustomFieldInput.test.tsx +176 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +484 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +157 -0
- package/Tests/UI/Components/IconDropdownItem.test.tsx +102 -0
- package/Tests/UI/Components/JSONTablePrototypePollution.test.tsx +117 -0
- package/Tests/UI/Components/KeyboardShortcutsModal.test.tsx +214 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +561 -0
- package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +302 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +28 -12
- package/Tests/UI/Components/ShortcutDialogGuard.test.tsx +74 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSectionAutoExpand.test.tsx +248 -0
- package/Tests/UI/Components/TableCellWrapping.test.tsx +625 -0
- package/Tests/UI/Components/TableLoadingStates.test.tsx +205 -0
- package/Tests/UI/Components/Workflow/NodePlacement.test.ts +148 -0
- package/Tests/UI/Components/Workflow/Workflow.test.tsx +981 -0
- package/Tests/UI/ConfigBrowserTelemetry.test.ts +70 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +1036 -2
- package/Tests/UI/Rum/FidelityNotices.test.ts +187 -0
- package/Tests/UI/Rum/InactivityMap.test.ts +341 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +259 -0
- package/Tests/UI/Rum/RecordingHealthCard.test.tsx +855 -0
- package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +594 -0
- package/Tests/UI/Rum/ReplayCard.test.tsx +451 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +536 -0
- package/Tests/UI/Rum/ReplayEngine.test.ts +2503 -0
- package/Tests/UI/Rum/ReplayEngineTypes.test.ts +171 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +719 -0
- package/Tests/UI/Rum/ReplayLink.test.tsx +119 -0
- package/Tests/UI/Rum/ReplayPinControl.test.tsx +408 -0
- package/Tests/UI/Rum/ReplayPlaybackIntent.test.ts +152 -0
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayRail.test.tsx +1296 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +900 -0
- package/Tests/UI/Rum/ReplayScrubber.test.tsx +763 -147
- package/Tests/UI/Rum/ReplaySignalTypes.test.ts +189 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +1579 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +396 -473
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +918 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayTimeline.test.tsx +696 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +499 -0
- package/Tests/UI/Rum/SessionReplaySearchBar.test.tsx +346 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +545 -0
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +991 -0
- package/Tests/UI/Rum/TargetedCapturePanel.test.tsx +254 -0
- package/Tests/UI/Telemetry/BrowserExporterIsolation.test.ts +71 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/UI/Utils/GlobalKeyboardShortcut.test.ts +344 -0
- package/Tests/Utils/API.test.ts +308 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/HTTPResponseBodyReader.test.ts +323 -0
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryScanStatus.test.ts +117 -0
- package/Tests/Utils/Rum/ChunkMath.test.ts +83 -0
- package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +993 -0
- package/Tests/Utils/Rum/SessionReplayStringMap.test.ts +245 -0
- package/Tests/Utils/StatusPage/ResourceSearch.test.ts +631 -0
- package/Tests/Utils/Telemetry/OriginAllowList.test.ts +633 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +461 -0
- package/Tests/Utils/ValueFormatter.test.ts +148 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/CustomField/CustomFieldMappingCatalog.ts +161 -0
- package/Types/CustomField/CustomFieldMappingSourceResource.ts +19 -0
- package/Types/CustomField/CustomFieldValueMapping.ts +288 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +205 -43
- package/Types/Monitor/CephAlertTemplates.ts +157 -195
- package/Types/Monitor/DockerAlertTemplates.ts +338 -128
- package/Types/Monitor/DockerSwarmAlertTemplates.ts +95 -112
- package/Types/Monitor/HostAlertTemplates.ts +302 -130
- package/Types/Monitor/IotAlertTemplates.ts +112 -104
- package/Types/Monitor/KubernetesAlertTemplates.ts +415 -224
- package/Types/Monitor/KubernetesMetricCatalog.ts +23 -19
- package/Types/Monitor/PodmanAlertTemplates.ts +116 -163
- package/Types/Monitor/ProxmoxAlertTemplates.ts +217 -99
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +98 -6
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +44 -7
- package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +383 -0
- package/Types/Monitor/RumAlertTemplates.ts +46 -7
- package/Types/Monitor/SeriesContext/SeriesDebugHints.ts +596 -0
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +554 -0
- package/Types/Monitor/ServiceAlertTemplates.ts +98 -11
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/Monitor/UptimeHistoryLabels.ts +56 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/NotificationSetting/RoutineEmailEvents.ts +30 -0
- package/Types/Rum/SessionReplay.ts +209 -0
- package/Types/Rum/SessionReplayApi.ts +722 -0
- package/Types/Rum/SessionReplayCaptureTrigger.ts +21 -12
- package/Types/Rum/SessionReplayConsentMode.ts +11 -7
- package/Types/Rum/SessionReplayCustomEvents.ts +549 -0
- package/Types/Rum/SessionReplayHealth.ts +238 -0
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +95 -0
- package/Types/Telemetry/TelemetryIngestionKeyPolicy.ts +75 -0
- package/Types/Telemetry/TelemetryIngestionKeyType.ts +29 -0
- package/Types/WebsiteRequest.ts +85 -3
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/Charts/Area/AreaChart.tsx +13 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +6 -2
- package/UI/Components/Charts/Line/LineChart.tsx +13 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +29 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +22 -3
- package/UI/Components/Charts/Utils/TimeAnnotation.ts +76 -9
- package/UI/Components/Charts/Utils/XAxis.ts +217 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +189 -46
- package/UI/Components/CustomFields/MapFromCustomFieldInput.tsx +216 -0
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/Components/Graphs/DayUptimeGraph.tsx +173 -8
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +22 -278
- package/UI/Components/Graphs/UptimeDaySummary.tsx +327 -0
- package/UI/Components/Header/Header.tsx +25 -9
- package/UI/Components/Header/IconDropdown/IconDropdownItem.tsx +10 -1
- package/UI/Components/Header/IconDropdown/IconDropdownMenu.tsx +6 -1
- package/UI/Components/Header/ProjectPicker/ProjectPicker.tsx +6 -1
- package/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.tsx +6 -1
- package/UI/Components/Icon/Icon.tsx +11 -0
- package/UI/Components/JSONTable/JSONTable.tsx +2 -2
- package/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.tsx +125 -0
- package/UI/Components/KeyboardShortcut/Screenshots/README.md +19 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-command-palette.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog-dark.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-help-menu.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-narrow.png +0 -0
- package/UI/Components/ModelTable/Column.ts +15 -0
- package/UI/Components/Monitor/SeriesDebugCommandsViewer.tsx +64 -0
- package/UI/Components/Monitor/SeriesLabelsViewer.tsx +93 -0
- package/UI/Components/MonitorGraphs/Uptime.tsx +14 -2
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +63 -10
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +44 -0
- package/UI/Components/StatusPage/ResourceGroupSection.tsx +40 -0
- package/UI/Components/Table/CellClassName.ts +81 -0
- package/UI/Components/Table/TableRow.tsx +20 -14
- package/UI/Components/Table/TableSkeletonRows.tsx +9 -9
- package/UI/Components/Table/Types/Column.ts +36 -0
- package/UI/Components/Tooltip/Tooltip.tsx +11 -1
- package/UI/Components/Workflow/ComponentsModal.tsx +60 -39
- package/UI/Components/Workflow/NodePlacement.ts +57 -0
- package/UI/Components/Workflow/Workflow.tsx +47 -18
- package/UI/Config.ts +15 -33
- package/UI/Utils/GlobalKeyboardShortcut.ts +208 -0
- package/UI/Utils/Telemetry/BrowserTelemetryConfig.ts +28 -0
- package/UI/Utils/Telemetry/Telemetry.ts +10 -8
- package/UI/esbuild-config.js +6 -0
- package/Utils/API.ts +129 -3
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/HTTPResponseBodyReader.ts +221 -0
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/Utils/NetworkDiscovery/DiscoveryScanStatus.ts +77 -0
- package/Utils/Rum/ChunkMath.ts +106 -0
- package/Utils/Rum/SessionReplayHealth.ts +732 -0
- package/Utils/Rum/SessionReplayStringMap.ts +226 -0
- package/Utils/Schema/ModelSchema.ts +1 -0
- package/Utils/StatusPage/ResourceSearch.ts +290 -0
- package/Utils/Telemetry/OriginAllowList.ts +355 -0
- package/Utils/Uptime/DayUptimeGraphUtil.ts +205 -0
- package/Utils/ValueFormatter.ts +35 -1
- package/build/dist/Models/AnalyticsModels/RumSession.js +102 -19
- package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +11 -9
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ApiKeyPermission.js +3 -27
- package/build/dist/Models/DatabaseModels/ApiKeyPermission.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +6 -6
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMember.js +0 -9
- package/build/dist/Models/DatabaseModels/TeamMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamPermission.js +3 -24
- package/build/dist/Models/DatabaseModels/TeamPermission.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +267 -0
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTelegram.js +0 -4
- package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +769 -109
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserNotificationSettingAPI.js +35 -0
- package/build/dist/Server/API/UserNotificationSettingAPI.js.map +1 -0
- package/build/dist/Server/API/UserTelegramAPI.js +28 -6
- package/build/dist/Server/API/UserTelegramAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +73 -14
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js +60 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js +53 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js +82 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js +46 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +19 -7
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +1 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +315 -8
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +1 -0
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AlertCustomFieldService.js +93 -0
- package/build/dist/Server/Services/AlertCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +34 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +331 -28
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/CustomFieldMappingService.js +614 -0
- package/build/dist/Server/Services/CustomFieldMappingService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/GlobalConfigService.js +133 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +93 -0
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +32 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +163 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +29 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +349 -64
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RoutineEmailSettingsService.js +69 -0
- package/build/dist/Server/Services/RoutineEmailSettingsService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionReplayViewService.js +86 -24
- package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js +93 -0
- package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +29 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +62 -2
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamPermissionService.js +166 -3
- package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js +562 -18
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +26 -6
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserTelegramService.js +257 -4
- package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +2 -2
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js +27 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js +226 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js +189 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +121 -7
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js +38 -5
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +54 -3
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/FrontendEnvironment.js +33 -0
- package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -0
- package/build/dist/Server/Utils/LogRedaction.js +20 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +10 -0
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +323 -81
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +20 -4
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +29 -6
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +254 -48
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js +237 -0
- package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +136 -9
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +174 -89
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js +223 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +945 -164
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +3 -14
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TelegramVerificationToken.js +129 -0
- package/build/dist/Server/Utils/TelegramVerificationToken.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/PinServiceName.js +163 -0
- package/build/dist/Server/Utils/Telemetry/PinServiceName.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +28 -15
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +64 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js +171 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +780 -69
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +60 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js +20 -0
- package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueMapping.js +141 -0
- package/build/dist/Types/CustomField/CustomFieldValueMapping.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +131 -37
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Monitor/CephAlertTemplates.js +130 -171
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DockerAlertTemplates.js +286 -121
- package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js +84 -105
- package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/HostAlertTemplates.js +250 -122
- package/build/dist/Types/Monitor/HostAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/IotAlertTemplates.js +81 -81
- package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +375 -215
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +19 -19
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js +104 -153
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +156 -91
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +67 -6
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +38 -7
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +261 -0
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -0
- package/build/dist/Types/Monitor/RumAlertTemplates.js +46 -7
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js +422 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js.map +1 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +441 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -0
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js +79 -15
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/UptimeHistoryLabels.js +17 -0
- package/build/dist/Types/Monitor/UptimeHistoryLabels.js.map +1 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js +29 -0
- package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplay.js +117 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayApi.js +181 -0
- package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +21 -12
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayConsentMode.js +11 -7
- package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js +172 -0
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayHealth.js +37 -0
- package/build/dist/Types/Rum/SessionReplayHealth.js.map +1 -0
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +85 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js +15 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js +30 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js.map +1 -0
- package/build/dist/Types/WebsiteRequest.js +55 -3
- package/build/dist/Types/WebsiteRequest.js.map +1 -1
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -2
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +6 -2
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +13 -2
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Types/XAxis/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -3
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +40 -9
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +186 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +89 -8
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js +118 -0
- package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js.map +1 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +112 -7
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -162
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js +181 -0
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -0
- package/build/dist/UI/Components/Header/Header.js +2 -3
- package/build/dist/UI/Components/Header/Header.js.map +1 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js +2 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js.map +1 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js +7 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js.map +1 -1
- package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js +7 -1
- package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js +7 -1
- package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +5 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/JSONTable/JSONTable.js +2 -2
- package/build/dist/UI/Components/JSONTable/JSONTable.js.map +1 -1
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js +35 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js.map +1 -0
- package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js +34 -0
- package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js.map +1 -0
- package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js +49 -0
- package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js.map +1 -0
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +32 -6
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +38 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +22 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -1
- package/build/dist/UI/Components/Table/CellClassName.js +55 -0
- package/build/dist/UI/Components/Table/CellClassName.js.map +1 -0
- package/build/dist/UI/Components/Table/TableRow.js +16 -13
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/Table/TableSkeletonRows.js +9 -8
- package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +25 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +47 -30
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/NodePlacement.js +37 -0
- package/build/dist/UI/Components/Workflow/NodePlacement.js.map +1 -0
- package/build/dist/UI/Components/Workflow/Workflow.js +35 -18
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Config.js +10 -20
- package/build/dist/UI/Config.js.map +1 -1
- package/build/dist/UI/Utils/GlobalKeyboardShortcut.js +138 -0
- package/build/dist/UI/Utils/GlobalKeyboardShortcut.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js +14 -0
- package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/Telemetry.js +7 -5
- package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Utils/API.js +67 -5
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/HTTPResponseBodyReader.js +157 -0
- package/build/dist/Utils/HTTPResponseBodyReader.js.map +1 -0
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js +69 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js.map +1 -0
- package/build/dist/Utils/Rum/ChunkMath.js +76 -0
- package/build/dist/Utils/Rum/ChunkMath.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayHealth.js +534 -0
- package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -0
- package/build/dist/Utils/Rum/SessionReplayStringMap.js +165 -0
- package/build/dist/Utils/Rum/SessionReplayStringMap.js.map +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceSearch.js +200 -0
- package/build/dist/Utils/StatusPage/ResourceSearch.js.map +1 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js +299 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +147 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -0
- package/build/dist/Utils/ValueFormatter.js +32 -1
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
|
@@ -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
|
+
});
|