@oneuptime/common 11.6.1 → 11.7.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.
Files changed (420) hide show
  1. package/Models/AnalyticsModels/Index.ts +4 -0
  2. package/Models/AnalyticsModels/KubernetesCostAllocation.ts +842 -0
  3. package/Models/AnalyticsModels/NetworkFlow.ts +54 -0
  4. package/Models/AnalyticsModels/SloHistory.ts +243 -0
  5. package/Models/DatabaseModels/Index.ts +20 -0
  6. package/Models/DatabaseModels/NetworkDevice.ts +453 -6
  7. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +7 -6
  8. package/Models/DatabaseModels/NetworkEndpoint.ts +753 -0
  9. package/Models/DatabaseModels/NetworkSite.ts +1155 -0
  10. package/Models/DatabaseModels/NetworkSiteAssignmentRule.ts +517 -0
  11. package/Models/DatabaseModels/NetworkSiteLink.ts +611 -0
  12. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +520 -0
  13. package/Models/DatabaseModels/NetworkSiteType.ts +499 -0
  14. package/Models/DatabaseModels/ServiceLevelObjective.ts +1115 -0
  15. package/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.ts +762 -0
  16. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.ts +444 -0
  17. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.ts +443 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1784757142154-AddNetworkSiteHierarchyAndEndpoints.ts +359 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1784897317860-AddNetworkSiteAlerting.ts +48 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1784970388777-AddNetworkDevicePollingColumns.ts +65 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1784986826214-AddNetworkSiteTypeTable.ts +88 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1784987015619-AddServiceLevelObjective.ts +391 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1785066759532-MigrationName.ts +30 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  25. package/Server/Services/AnalyticsDatabaseService.ts +25 -9
  26. package/Server/Services/Index.ts +24 -0
  27. package/Server/Services/KubernetesCostAllocationService.ts +14 -0
  28. package/Server/Services/MetricService.ts +21 -2
  29. package/Server/Services/MonitorService.ts +22 -0
  30. package/Server/Services/NetworkDeviceService.ts +438 -2
  31. package/Server/Services/NetworkEndpointService.ts +414 -0
  32. package/Server/Services/NetworkSiteAssignmentRuleService.ts +98 -0
  33. package/Server/Services/NetworkSiteLinkService.ts +10 -0
  34. package/Server/Services/NetworkSiteService.ts +1254 -0
  35. package/Server/Services/NetworkSiteStatusTimelineService.ts +10 -0
  36. package/Server/Services/NetworkSiteTypeService.ts +10 -0
  37. package/Server/Services/ProjectService.ts +122 -2
  38. package/Server/Services/ServiceLevelObjectiveBurnRateRuleService.ts +457 -0
  39. package/Server/Services/ServiceLevelObjectiveOwnerTeamService.ts +10 -0
  40. package/Server/Services/ServiceLevelObjectiveOwnerUserService.ts +10 -0
  41. package/Server/Services/ServiceLevelObjectiveService.ts +685 -0
  42. package/Server/Services/SloHistoryService.ts +101 -0
  43. package/Server/Services/UserNotificationSettingService.ts +37 -0
  44. package/Server/Types/AnalyticsDatabase/AggregateBy.ts +9 -0
  45. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +15 -0
  46. package/Server/Utils/AnalyticsDatabase/Statement.ts +39 -3
  47. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +87 -48
  48. package/Server/Utils/Memory.ts +9 -1
  49. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +190 -12
  50. package/Server/Utils/Monitor/Data/OuiVendors.json +6920 -0
  51. package/Server/Utils/Monitor/DnsResolutionCache.ts +129 -0
  52. package/Server/Utils/Monitor/MonitorResource.ts +4 -30
  53. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +214 -39
  54. package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +343 -0
  55. package/Server/Utils/Monitor/NetworkDeviceWalkUtil.ts +251 -0
  56. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +176 -142
  57. package/Server/Utils/Monitor/OuiLookupUtil.ts +49 -0
  58. package/Server/Utils/Monitor/SnmpInterfaceRateUtil.ts +17 -13
  59. package/Server/Utils/WhatsAppTemplateUtil.ts +7 -3
  60. package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +135 -0
  61. package/Tests/Models/NetworkSiteHierarchy.test.ts +134 -0
  62. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +239 -0
  63. package/Tests/Server/Services/KubernetesCostAllocationAggregate.test.ts +338 -0
  64. package/Tests/Server/Services/MetricServiceAggregate.test.ts +142 -0
  65. package/Tests/Server/Services/NetworkDeviceSiteAssignment.test.ts +484 -0
  66. package/Tests/Server/Services/NetworkEndpointServiceUpsert.test.ts +686 -0
  67. package/Tests/Server/Services/NetworkSiteAssignmentRuleService.test.ts +185 -0
  68. package/Tests/Server/Services/NetworkSiteService.test.ts +1354 -0
  69. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleService.test.ts +1313 -0
  70. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +1988 -0
  71. package/Tests/Server/Utils/AnalyticsDatabase/Statement.test.ts +22 -0
  72. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +404 -0
  73. package/Tests/Server/Utils/Encryption.test.ts +70 -0
  74. package/Tests/Server/Utils/FileAttachmentMarkdownUtil.test.ts +225 -0
  75. package/Tests/Server/Utils/JsonWebToken.test.ts +175 -0
  76. package/Tests/Server/Utils/LocalFile.test.ts +258 -0
  77. package/Tests/Server/Utils/Memory.test.ts +213 -0
  78. package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +30 -0
  79. package/Tests/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.test.ts +385 -0
  80. package/Tests/Server/Utils/Monitor/DnsResolutionCache.test.ts +194 -0
  81. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +155 -0
  82. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +600 -0
  83. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +151 -124
  84. package/Tests/Server/Utils/Monitor/SnmpInterfaceRateUtil.test.ts +496 -0
  85. package/Tests/Server/Utils/MonitorCriteriaDataExtractor.test.ts +175 -0
  86. package/Tests/Server/Utils/MonitorCriteriaMessageFormatter.test.ts +282 -0
  87. package/Tests/Server/Utils/OuiLookupUtil.test.ts +59 -0
  88. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +117 -0
  89. package/Tests/Server/Utils/StackTraceParser.test.ts +370 -0
  90. package/Tests/Server/Utils/Stream.test.ts +87 -0
  91. package/Tests/Server/Utils/ValidateGlobalProviderProjectTeams.test.ts +221 -0
  92. package/Tests/Server/Utils/WhatsAppTemplateUtil.test.ts +199 -0
  93. package/Tests/Types/Database/CompareOperatorWireSerialization.test.ts +33 -0
  94. package/Tests/Types/Email.test.ts +198 -0
  95. package/Tests/Types/Kubernetes/KubernetesObjectParser.test.ts +177 -0
  96. package/Tests/Types/Log/LogQueryToFilter.test.ts +111 -0
  97. package/Tests/Types/Monitor/MonitorCriteria.test.ts +155 -0
  98. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +465 -0
  99. package/Tests/Types/Monitor/MonitorStep.test.ts +183 -0
  100. package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +141 -1
  101. package/Tests/Types/Monitor/MonitorSteps.test.ts +183 -0
  102. package/Tests/Types/Monitor/MonitorType.test.ts +59 -1
  103. package/Tests/Types/Monitor/NetworkDeviceAlertPack.test.ts +97 -0
  104. package/Tests/Types/Monitor/SnmpEndpointDiscovery.test.ts +151 -0
  105. package/Tests/Types/Monitor/SnmpVendorTemplate.test.ts +110 -6
  106. package/Tests/Types/NetworkSite/DefaultNetworkSiteType.test.ts +129 -0
  107. package/Tests/Types/SerializableObjectDictionary.test.ts +166 -0
  108. package/Tests/Types/Time/RangeStartAndEndDateTime.test.ts +98 -0
  109. package/Tests/Types/WebsiteRequest.test.ts +230 -0
  110. package/Tests/UI/Components/EntityFilterRoundTrip.test.tsx +103 -0
  111. package/Tests/UI/Components/ModelTable/BaseModelTableUrlState.test.tsx +612 -0
  112. package/Tests/UI/Components/ModelTable/FilterDataToQuery.test.ts +275 -0
  113. package/Tests/UI/Utils/LogExport.test.ts +159 -0
  114. package/Tests/UI/Utils/Navigation.test.ts +183 -0
  115. package/Tests/UI/Utils/TableFilterUrlState.test.ts +539 -0
  116. package/Tests/UI/Utils/TableViewUrlState.test.ts +272 -0
  117. package/Tests/Utils/Base64.test.ts +51 -0
  118. package/Tests/Utils/Boolean.test.ts +76 -0
  119. package/Tests/Utils/Crypto.test.ts +48 -0
  120. package/Tests/Utils/Dashboard/DashboardSloComponent.test.ts +259 -0
  121. package/Tests/Utils/Dashboard/ModelQueryVariableInterpolation.test.ts +162 -0
  122. package/Tests/Utils/Enum.test.ts +54 -0
  123. package/Tests/Utils/IpCanonicalUtil.test.ts +85 -0
  124. package/Tests/Utils/Memory.test.ts +44 -0
  125. package/Tests/Utils/Metrics/MetricSeriesFingerprint.test.ts +160 -0
  126. package/Tests/Utils/Monitor/EndpointAttachmentUtil.test.ts +836 -0
  127. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +218 -0
  128. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +447 -0
  129. package/Tests/Utils/NetworkSite/CidrMatchUtil.test.ts +440 -0
  130. package/Tests/Utils/NetworkSite/MaterializedPathUtil.test.ts +205 -0
  131. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +221 -0
  132. package/Tests/Utils/NetworkSite/SiteUptimeUtil.test.ts +283 -0
  133. package/Tests/Utils/ObjectUtil.test.ts +30 -0
  134. package/Tests/Utils/Slo/SloBurnRateRuleState.test.ts +124 -0
  135. package/Tests/Utils/Slo/SloDuration.test.ts +148 -0
  136. package/Tests/Utils/Slo/SloHealth.test.ts +334 -0
  137. package/Tests/Utils/Slo/SloStatusColor.test.ts +89 -0
  138. package/Tests/Utils/Slo/SloUtil.test.ts +1980 -0
  139. package/Tests/Utils/Slo/SloWidgetFormat.test.ts +605 -0
  140. package/Tests/Utils/TechStack.test.ts +50 -0
  141. package/Tests/Utils/Traces/CriticalPath.test.ts +178 -0
  142. package/Types/AnalyticsDatabase/AnalyticsTableName.ts +10 -0
  143. package/Types/BaseDatabase/EqualTo.ts +9 -1
  144. package/Types/BaseDatabase/EqualToOrNull.ts +9 -1
  145. package/Types/BaseDatabase/NotEqual.ts +9 -1
  146. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  147. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  148. package/Types/Dashboard/DashboardComponents/DashboardSloComponent.ts +42 -0
  149. package/Types/Dashboard/DashboardTemplates.ts +214 -0
  150. package/Types/Email/EmailTemplateType.ts +2 -0
  151. package/Types/Kubernetes/KubernetesCostIngest.ts +79 -0
  152. package/Types/Monitor/CriteriaFilter.ts +9 -0
  153. package/Types/Monitor/MonitorStep.ts +5 -8
  154. package/Types/Monitor/MonitorStepNetworkDeviceMonitor.ts +27 -5
  155. package/Types/Monitor/MonitorType.ts +15 -5
  156. package/Types/Monitor/SnmpMonitor/ArpEntry.ts +14 -0
  157. package/Types/Monitor/SnmpMonitor/FdbEntry.ts +18 -0
  158. package/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.ts +10 -3
  159. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +31 -5
  160. package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +12 -0
  161. package/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.ts +231 -3
  162. package/Types/NetworkSite/DefaultNetworkSiteType.ts +20 -0
  163. package/Types/NotificationSetting/NotificationSettingEventType.ts +4 -0
  164. package/Types/Permission.ts +449 -0
  165. package/Types/ServiceLevelObjective/SliType.ts +6 -0
  166. package/Types/ServiceLevelObjective/SloMultiMonitorMode.ts +6 -0
  167. package/Types/ServiceLevelObjective/SloStatus.ts +9 -0
  168. package/Types/ServiceLevelObjective/SloWindowType.ts +6 -0
  169. package/Types/Telemetry/ServiceType.ts +1 -0
  170. package/UI/Components/Filters/EntityFilter.tsx +16 -0
  171. package/UI/Components/Filters/FilterViewer.tsx +22 -5
  172. package/UI/Components/List/List.tsx +8 -0
  173. package/UI/Components/ModelTable/BaseModelTable.tsx +309 -106
  174. package/UI/Components/ModelTable/FilterDataToQuery.ts +172 -0
  175. package/UI/Components/Navbar/NavBar.tsx +16 -2
  176. package/UI/Components/Navbar/NavBarMenuModal.tsx +3 -3
  177. package/UI/Utils/Navigation.ts +10 -0
  178. package/UI/Utils/TableFilterUrlState.ts +204 -17
  179. package/UI/Utils/TableViewUrlState.ts +165 -0
  180. package/Utils/Dashboard/Components/DashboardSloComponent.ts +114 -0
  181. package/Utils/Dashboard/Components/Index.ts +7 -0
  182. package/Utils/IpCanonicalUtil.ts +134 -0
  183. package/Utils/Monitor/EndpointAttachmentUtil.ts +508 -0
  184. package/Utils/Monitor/NetworkTopologyUtil.ts +157 -5
  185. package/Utils/NetworkSite/CidrMatchUtil.ts +296 -0
  186. package/Utils/NetworkSite/MaterializedPathUtil.ts +115 -0
  187. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +97 -0
  188. package/Utils/NetworkSite/SiteUptimeUtil.ts +111 -0
  189. package/Utils/Slo/SloBurnRateRuleState.ts +91 -0
  190. package/Utils/Slo/SloDuration.ts +165 -0
  191. package/Utils/Slo/SloEvaluation.ts +22 -0
  192. package/Utils/Slo/SloHealth.ts +302 -0
  193. package/Utils/Slo/SloStatusColor.ts +54 -0
  194. package/Utils/Slo/SloUtil.ts +637 -0
  195. package/Utils/Slo/SloWidgetFormat.ts +383 -0
  196. package/build/dist/Models/AnalyticsModels/Index.js +4 -0
  197. package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
  198. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +713 -0
  199. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -0
  200. package/build/dist/Models/AnalyticsModels/NetworkFlow.js +44 -0
  201. package/build/dist/Models/AnalyticsModels/NetworkFlow.js.map +1 -1
  202. package/build/dist/Models/AnalyticsModels/SloHistory.js +218 -0
  203. package/build/dist/Models/AnalyticsModels/SloHistory.js.map +1 -0
  204. package/build/dist/Models/DatabaseModels/Index.js +20 -0
  205. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/NetworkDevice.js +458 -6
  207. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +0 -6
  209. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/NetworkEndpoint.js +779 -0
  211. package/build/dist/Models/DatabaseModels/NetworkEndpoint.js.map +1 -0
  212. package/build/dist/Models/DatabaseModels/NetworkSite.js +1180 -0
  213. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -0
  214. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js +534 -0
  215. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js.map +1 -0
  216. package/build/dist/Models/DatabaseModels/NetworkSiteLink.js +629 -0
  217. package/build/dist/Models/DatabaseModels/NetworkSiteLink.js.map +1 -0
  218. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +538 -0
  219. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -0
  220. package/build/dist/Models/DatabaseModels/NetworkSiteType.js +522 -0
  221. package/build/dist/Models/DatabaseModels/NetworkSiteType.js.map +1 -0
  222. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +1134 -0
  223. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -0
  224. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js +769 -0
  225. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js.map +1 -0
  226. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js +460 -0
  227. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js.map +1 -0
  228. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js +459 -0
  229. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js.map +1 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784757142154-AddNetworkSiteHierarchyAndEndpoints.js +130 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784757142154-AddNetworkSiteHierarchyAndEndpoints.js.map +1 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784897317860-AddNetworkSiteAlerting.js +31 -0
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784897317860-AddNetworkSiteAlerting.js.map +1 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784970388777-AddNetworkDevicePollingColumns.js +36 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784970388777-AddNetworkDevicePollingColumns.js.map +1 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784986826214-AddNetworkSiteTypeTable.js +49 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784986826214-AddNetworkSiteTypeTable.js.map +1 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784987015619-AddServiceLevelObjective.js +140 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784987015619-AddServiceLevelObjective.js.map +1 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785066759532-MigrationName.js +21 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785066759532-MigrationName.js.map +1 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  244. package/build/dist/Server/Services/AnalyticsDatabaseService.js +23 -6
  245. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  246. package/build/dist/Server/Services/Index.js +24 -0
  247. package/build/dist/Server/Services/Index.js.map +1 -1
  248. package/build/dist/Server/Services/KubernetesCostAllocationService.js +12 -0
  249. package/build/dist/Server/Services/KubernetesCostAllocationService.js.map +1 -0
  250. package/build/dist/Server/Services/MetricService.js +21 -3
  251. package/build/dist/Server/Services/MetricService.js.map +1 -1
  252. package/build/dist/Server/Services/MonitorService.js +19 -0
  253. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  254. package/build/dist/Server/Services/NetworkDeviceService.js +351 -1
  255. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  256. package/build/dist/Server/Services/NetworkEndpointService.js +319 -0
  257. package/build/dist/Server/Services/NetworkEndpointService.js.map +1 -0
  258. package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js +90 -0
  259. package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js.map +1 -0
  260. package/build/dist/Server/Services/NetworkSiteLinkService.js +9 -0
  261. package/build/dist/Server/Services/NetworkSiteLinkService.js.map +1 -0
  262. package/build/dist/Server/Services/NetworkSiteService.js +1038 -0
  263. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -0
  264. package/build/dist/Server/Services/NetworkSiteStatusTimelineService.js +9 -0
  265. package/build/dist/Server/Services/NetworkSiteStatusTimelineService.js.map +1 -0
  266. package/build/dist/Server/Services/NetworkSiteTypeService.js +9 -0
  267. package/build/dist/Server/Services/NetworkSiteTypeService.js.map +1 -0
  268. package/build/dist/Server/Services/ProjectService.js +98 -2
  269. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  270. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js +358 -0
  271. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js.map +1 -0
  272. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerTeamService.js +9 -0
  273. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerTeamService.js.map +1 -0
  274. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerUserService.js +9 -0
  275. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerUserService.js.map +1 -0
  276. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +588 -0
  277. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -0
  278. package/build/dist/Server/Services/SloHistoryService.js +77 -0
  279. package/build/dist/Server/Services/SloHistoryService.js.map +1 -0
  280. package/build/dist/Server/Services/UserNotificationSettingService.js +20 -0
  281. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  282. package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js +9 -0
  283. package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js.map +1 -1
  284. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +15 -0
  285. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  286. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +31 -0
  287. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  288. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +76 -48
  289. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  290. package/build/dist/Server/Utils/Memory.js +9 -1
  291. package/build/dist/Server/Utils/Memory.js.map +1 -1
  292. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +132 -10
  293. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  294. package/build/dist/Server/Utils/Monitor/Data/OuiVendors.json +6920 -0
  295. package/build/dist/Server/Utils/Monitor/DnsResolutionCache.js +77 -0
  296. package/build/dist/Server/Utils/Monitor/DnsResolutionCache.js.map +1 -0
  297. package/build/dist/Server/Utils/Monitor/MonitorResource.js +21 -41
  298. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  299. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +152 -40
  300. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  301. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +262 -0
  302. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -0
  303. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js +201 -0
  304. package/build/dist/Server/Utils/Monitor/NetworkDeviceWalkUtil.js.map +1 -0
  305. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +152 -120
  306. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  307. package/build/dist/Server/Utils/Monitor/OuiLookupUtil.js +44 -0
  308. package/build/dist/Server/Utils/Monitor/OuiLookupUtil.js.map +1 -0
  309. package/build/dist/Server/Utils/Monitor/SnmpInterfaceRateUtil.js +10 -10
  310. package/build/dist/Server/Utils/Monitor/SnmpInterfaceRateUtil.js.map +1 -1
  311. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +5 -0
  312. package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
  313. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +10 -0
  314. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
  315. package/build/dist/Types/BaseDatabase/EqualTo.js +9 -1
  316. package/build/dist/Types/BaseDatabase/EqualTo.js.map +1 -1
  317. package/build/dist/Types/BaseDatabase/EqualToOrNull.js +9 -1
  318. package/build/dist/Types/BaseDatabase/EqualToOrNull.js.map +1 -1
  319. package/build/dist/Types/BaseDatabase/NotEqual.js +9 -1
  320. package/build/dist/Types/BaseDatabase/NotEqual.js.map +1 -1
  321. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  322. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  323. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  324. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  325. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSloComponent.js +23 -0
  326. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSloComponent.js.map +1 -0
  327. package/build/dist/Types/Dashboard/DashboardTemplates.js +205 -0
  328. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  329. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  330. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  331. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js +21 -0
  332. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -0
  333. package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
  334. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  335. package/build/dist/Types/Monitor/MonitorStep.js +5 -5
  336. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  337. package/build/dist/Types/Monitor/MonitorStepNetworkDeviceMonitor.js +8 -0
  338. package/build/dist/Types/Monitor/MonitorStepNetworkDeviceMonitor.js.map +1 -1
  339. package/build/dist/Types/Monitor/MonitorType.js +15 -5
  340. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  341. package/build/dist/Types/Monitor/SnmpMonitor/ArpEntry.js +2 -0
  342. package/build/dist/Types/Monitor/SnmpMonitor/ArpEntry.js.map +1 -0
  343. package/build/dist/Types/Monitor/SnmpMonitor/FdbEntry.js +2 -0
  344. package/build/dist/Types/Monitor/SnmpMonitor/FdbEntry.js.map +1 -0
  345. package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js +9 -3
  346. package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js.map +1 -1
  347. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js +217 -3
  348. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js.map +1 -1
  349. package/build/dist/Types/NetworkSite/DefaultNetworkSiteType.js +21 -0
  350. package/build/dist/Types/NetworkSite/DefaultNetworkSiteType.js.map +1 -0
  351. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
  352. package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
  353. package/build/dist/Types/Permission.js +409 -0
  354. package/build/dist/Types/Permission.js.map +1 -1
  355. package/build/dist/Types/ServiceLevelObjective/SliType.js +7 -0
  356. package/build/dist/Types/ServiceLevelObjective/SliType.js.map +1 -0
  357. package/build/dist/Types/ServiceLevelObjective/SloMultiMonitorMode.js +7 -0
  358. package/build/dist/Types/ServiceLevelObjective/SloMultiMonitorMode.js.map +1 -0
  359. package/build/dist/Types/ServiceLevelObjective/SloStatus.js +10 -0
  360. package/build/dist/Types/ServiceLevelObjective/SloStatus.js.map +1 -0
  361. package/build/dist/Types/ServiceLevelObjective/SloWindowType.js +7 -0
  362. package/build/dist/Types/ServiceLevelObjective/SloWindowType.js.map +1 -0
  363. package/build/dist/Types/Telemetry/ServiceType.js +1 -0
  364. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  365. package/build/dist/UI/Components/Filters/EntityFilter.js +15 -0
  366. package/build/dist/UI/Components/Filters/EntityFilter.js.map +1 -1
  367. package/build/dist/UI/Components/Filters/FilterViewer.js +19 -4
  368. package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
  369. package/build/dist/UI/Components/List/List.js +1 -1
  370. package/build/dist/UI/Components/List/List.js.map +1 -1
  371. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +235 -89
  372. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  373. package/build/dist/UI/Components/ModelTable/FilterDataToQuery.js +96 -0
  374. package/build/dist/UI/Components/ModelTable/FilterDataToQuery.js.map +1 -0
  375. package/build/dist/UI/Components/Navbar/NavBar.js +14 -2
  376. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  377. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +3 -2
  378. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  379. package/build/dist/UI/Utils/Navigation.js +9 -0
  380. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  381. package/build/dist/UI/Utils/TableFilterUrlState.js +159 -15
  382. package/build/dist/UI/Utils/TableFilterUrlState.js.map +1 -1
  383. package/build/dist/UI/Utils/TableViewUrlState.js +103 -0
  384. package/build/dist/UI/Utils/TableViewUrlState.js.map +1 -0
  385. package/build/dist/Utils/Dashboard/Components/DashboardSloComponent.js +93 -0
  386. package/build/dist/Utils/Dashboard/Components/DashboardSloComponent.js.map +1 -0
  387. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  388. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  389. package/build/dist/Utils/IpCanonicalUtil.js +110 -0
  390. package/build/dist/Utils/IpCanonicalUtil.js.map +1 -0
  391. package/build/dist/Utils/Monitor/EndpointAttachmentUtil.js +311 -0
  392. package/build/dist/Utils/Monitor/EndpointAttachmentUtil.js.map +1 -0
  393. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +98 -5
  394. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  395. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js +222 -0
  396. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js.map +1 -0
  397. package/build/dist/Utils/NetworkSite/MaterializedPathUtil.js +89 -0
  398. package/build/dist/Utils/NetworkSite/MaterializedPathUtil.js.map +1 -0
  399. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +60 -0
  400. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -0
  401. package/build/dist/Utils/NetworkSite/SiteUptimeUtil.js +76 -0
  402. package/build/dist/Utils/NetworkSite/SiteUptimeUtil.js.map +1 -0
  403. package/build/dist/Utils/Slo/SloBurnRateRuleState.js +35 -0
  404. package/build/dist/Utils/Slo/SloBurnRateRuleState.js.map +1 -0
  405. package/build/dist/Utils/Slo/SloDuration.js +87 -0
  406. package/build/dist/Utils/Slo/SloDuration.js.map +1 -0
  407. package/build/dist/Utils/Slo/SloEvaluation.js +21 -0
  408. package/build/dist/Utils/Slo/SloEvaluation.js.map +1 -0
  409. package/build/dist/Utils/Slo/SloHealth.js +179 -0
  410. package/build/dist/Utils/Slo/SloHealth.js.map +1 -0
  411. package/build/dist/Utils/Slo/SloStatusColor.js +18 -0
  412. package/build/dist/Utils/Slo/SloStatusColor.js.map +1 -0
  413. package/build/dist/Utils/Slo/SloUtil.js +393 -0
  414. package/build/dist/Utils/Slo/SloUtil.js.map +1 -0
  415. package/build/dist/Utils/Slo/SloWidgetFormat.js +174 -0
  416. package/build/dist/Utils/Slo/SloWidgetFormat.js.map +1 -0
  417. package/package.json +2 -2
  418. package/Types/Dashboard/DashboardComponents/Index.ts +0 -7
  419. package/build/dist/Types/Dashboard/DashboardComponents/Index.js +0 -8
  420. package/build/dist/Types/Dashboard/DashboardComponents/Index.js.map +0 -1
@@ -0,0 +1,686 @@
1
+ import NetworkEndpointService from "../../../Server/Services/NetworkEndpointService";
2
+ import NetworkEndpoint from "../../../Models/DatabaseModels/NetworkEndpoint";
3
+ import ObjectID from "../../../Types/ObjectID";
4
+
5
+ /*
6
+ * Wiring tests for upsertDiscoveredEndpoints: the precedence DECISIONS live
7
+ * in EndpointAttachmentUtil.decideUpsert (tested exhaustively in
8
+ * Tests/Utils/Monitor/EndpointAttachmentUtil.test.ts); here we pin how the
9
+ * service applies them.
10
+ *
11
+ * This path runs INLINE on probe ingest for L2 domains with thousands of
12
+ * MACs, so the shape of the write is the contract: one batched findBy, then
13
+ * at most one INSERT per 500 new MACs and one UPDATE per 500 changed MACs —
14
+ * never one round trip per MAC. Endpoints that have not moved since the
15
+ * last poll must produce no write at all. The service builds raw
16
+ * parameterized SQL against the TypeORM manager, so these tests mock the
17
+ * query runner (no Postgres) and assert on the statements and parameters.
18
+ */
19
+
20
+ const PROJECT_ID: ObjectID = new ObjectID(
21
+ "11111111-1111-4111-8111-111111111111",
22
+ );
23
+ const SWITCH_ID: ObjectID = new ObjectID(
24
+ "22222222-2222-4222-8222-222222222222",
25
+ );
26
+ const OTHER_SWITCH_ID: ObjectID = new ObjectID(
27
+ "33333333-3333-4333-8333-333333333333",
28
+ );
29
+ const SITE_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
30
+ const ROW_ID: ObjectID = new ObjectID("55555555-5555-4555-8555-555555555555");
31
+
32
+ const NOW: Date = new Date("2026-07-22T12:00:00Z");
33
+ const EARLIER: Date = new Date("2026-07-01T00:00:00Z");
34
+ // Inside the 5 minute lastSeenAt refresh threshold.
35
+ const ONE_MINUTE_AGO: Date = new Date("2026-07-22T11:59:00Z");
36
+ // Past the 5 minute refresh threshold.
37
+ const TEN_MINUTES_AGO: Date = new Date("2026-07-22T11:50:00Z");
38
+
39
+ // A MAC whose OUI is in the bundled registry slice (Cisco 00:00:0c).
40
+ const CISCO_MAC: string = "00:00:0c:aa:bb:01";
41
+
42
+ const INSERT_PARAMS_PER_ROW: number = 12;
43
+ const UPDATE_PARAMS_PER_ROW: number = 9;
44
+
45
+ type QueryCall = [string, Array<unknown>];
46
+
47
+ describe("NetworkEndpointService.upsertDiscoveredEndpoints", () => {
48
+ let findBySpy: jest.SpyInstance;
49
+ let querySpy: jest.Mock;
50
+
51
+ beforeEach(() => {
52
+ findBySpy = jest
53
+ .spyOn(NetworkEndpointService, "findBy")
54
+ .mockResolvedValue([]);
55
+ querySpy = jest.fn().mockResolvedValue([]);
56
+ jest
57
+ .spyOn(NetworkEndpointService, "getRepository")
58
+ .mockReturnValue({ manager: { query: querySpy } } as any);
59
+ });
60
+
61
+ afterEach(() => {
62
+ jest.restoreAllMocks();
63
+ });
64
+
65
+ const callsMatching: (fragment: string) => Array<QueryCall> = (
66
+ fragment: string,
67
+ ): Array<QueryCall> => {
68
+ return (querySpy.mock.calls as Array<QueryCall>).filter(
69
+ (call: QueryCall) => {
70
+ return call[0].includes(fragment);
71
+ },
72
+ );
73
+ };
74
+
75
+ const insertCalls: () => Array<QueryCall> = (): Array<QueryCall> => {
76
+ return callsMatching('INSERT INTO "NetworkEndpoint"');
77
+ };
78
+
79
+ const updateCalls: () => Array<QueryCall> = (): Array<QueryCall> => {
80
+ return callsMatching('UPDATE "NetworkEndpoint"');
81
+ };
82
+
83
+ const makeExistingRow: (data: {
84
+ macAddress: string;
85
+ attachedNetworkDeviceId?: ObjectID | undefined;
86
+ attachedInterfaceIndex?: number | undefined;
87
+ attachedPortName?: string | undefined;
88
+ vlanId?: number | undefined;
89
+ ipAddress?: string | undefined;
90
+ siteId?: ObjectID | undefined;
91
+ firstSeenAt?: Date | undefined;
92
+ lastSeenAt?: Date | undefined;
93
+ }) => NetworkEndpoint = (data: {
94
+ macAddress: string;
95
+ attachedNetworkDeviceId?: ObjectID | undefined;
96
+ attachedInterfaceIndex?: number | undefined;
97
+ attachedPortName?: string | undefined;
98
+ vlanId?: number | undefined;
99
+ ipAddress?: string | undefined;
100
+ siteId?: ObjectID | undefined;
101
+ firstSeenAt?: Date | undefined;
102
+ lastSeenAt?: Date | undefined;
103
+ }): NetworkEndpoint => {
104
+ const row: NetworkEndpoint = new NetworkEndpoint();
105
+ row.id = ROW_ID;
106
+ row.macAddress = data.macAddress;
107
+ if (data.attachedNetworkDeviceId) {
108
+ row.attachedNetworkDeviceId = data.attachedNetworkDeviceId;
109
+ }
110
+ if (data.attachedInterfaceIndex !== undefined) {
111
+ row.attachedInterfaceIndex = data.attachedInterfaceIndex;
112
+ }
113
+ if (data.attachedPortName) {
114
+ row.attachedPortName = data.attachedPortName;
115
+ }
116
+ if (data.vlanId !== undefined) {
117
+ row.vlanId = data.vlanId;
118
+ }
119
+ if (data.ipAddress) {
120
+ row.ipAddress = data.ipAddress;
121
+ }
122
+ if (data.siteId) {
123
+ row.siteId = data.siteId;
124
+ }
125
+ if (data.firstSeenAt) {
126
+ row.firstSeenAt = data.firstSeenAt;
127
+ }
128
+ if (data.lastSeenAt) {
129
+ row.lastSeenAt = data.lastSeenAt;
130
+ }
131
+ return row;
132
+ };
133
+
134
+ it("does not touch the database when there is nothing to upsert", async () => {
135
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
136
+ projectId: PROJECT_ID,
137
+ deviceId: SWITCH_ID,
138
+ deviceSiteId: SITE_ID,
139
+ attachments: [],
140
+ ipBindings: [],
141
+ now: NOW,
142
+ });
143
+
144
+ expect(findBySpy).not.toHaveBeenCalled();
145
+ expect(querySpy).not.toHaveBeenCalled();
146
+ });
147
+
148
+ it("creates a new row from an FDB attachment with vendor, site, and timestamps", async () => {
149
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
150
+ projectId: PROJECT_ID,
151
+ deviceId: SWITCH_ID,
152
+ deviceSiteId: SITE_ID,
153
+ attachments: [
154
+ {
155
+ macAddress: CISCO_MAC,
156
+ attachedInterfaceIndex: 7,
157
+ attachedPortName: "Gi0/7",
158
+ vlanId: 12,
159
+ },
160
+ ],
161
+ ipBindings: [],
162
+ now: NOW,
163
+ });
164
+
165
+ expect(findBySpy).toHaveBeenCalledTimes(1);
166
+ expect(updateCalls()).toHaveLength(0);
167
+ expect(insertCalls()).toHaveLength(1);
168
+
169
+ const params: Array<unknown> = insertCalls()[0]![1];
170
+ expect(params).toEqual([
171
+ PROJECT_ID.toString(),
172
+ CISCO_MAC,
173
+ null, // ipAddress — the FDB knows none
174
+ "Cisco Systems, Inc",
175
+ SWITCH_ID.toString(),
176
+ 7,
177
+ "Gi0/7",
178
+ 12,
179
+ SITE_ID.toString(),
180
+ NOW, // firstSeenAt
181
+ NOW, // lastSeenAt
182
+ 0, // version
183
+ ]);
184
+ });
185
+
186
+ it("creates an ARP-only row attached to the router interface", async () => {
187
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
188
+ projectId: PROJECT_ID,
189
+ deviceId: SWITCH_ID,
190
+ attachments: [],
191
+ ipBindings: [
192
+ {
193
+ macAddress: "aa:bb:cc:dd:ee:01",
194
+ ipAddress: "10.0.0.31",
195
+ routerInterfaceIndex: 3,
196
+ },
197
+ ],
198
+ now: NOW,
199
+ });
200
+
201
+ expect(insertCalls()).toHaveLength(1);
202
+ const params: Array<unknown> = insertCalls()[0]![1];
203
+ expect(params).toEqual([
204
+ PROJECT_ID.toString(),
205
+ "aa:bb:cc:dd:ee:01",
206
+ "10.0.0.31",
207
+ null, // aa:bb:cc is not in the filtered registry
208
+ SWITCH_ID.toString(),
209
+ 3, // the router's own interface
210
+ null, // no port name
211
+ null, // no vlan
212
+ null, // no deviceSiteId passed — none stamped
213
+ NOW,
214
+ NOW,
215
+ 0,
216
+ ]);
217
+ });
218
+
219
+ it("merges an FDB attachment and an ARP binding for the same MAC into one create", async () => {
220
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
221
+ projectId: PROJECT_ID,
222
+ deviceId: SWITCH_ID,
223
+ deviceSiteId: SITE_ID,
224
+ attachments: [
225
+ {
226
+ macAddress: CISCO_MAC,
227
+ attachedInterfaceIndex: 7,
228
+ attachedPortName: "Gi0/7",
229
+ },
230
+ ],
231
+ ipBindings: [
232
+ {
233
+ macAddress: CISCO_MAC,
234
+ ipAddress: "10.0.0.31",
235
+ routerInterfaceIndex: 99,
236
+ },
237
+ ],
238
+ now: NOW,
239
+ });
240
+
241
+ expect(insertCalls()).toHaveLength(1);
242
+ const params: Array<unknown> = insertCalls()[0]![1];
243
+ expect(params).toHaveLength(INSERT_PARAMS_PER_ROW);
244
+ // The FDB attachment wins the port; the ARP binding contributes the IP.
245
+ expect(params[5]).toBe(7);
246
+ expect(params[2]).toBe("10.0.0.31");
247
+ });
248
+
249
+ it("infers the partial unique index so soft-deleted rows never collide", async () => {
250
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
251
+ projectId: PROJECT_ID,
252
+ deviceId: SWITCH_ID,
253
+ attachments: [
254
+ { macAddress: "aa:bb:cc:dd:ee:01", attachedInterfaceIndex: 1 },
255
+ ],
256
+ ipBindings: [],
257
+ now: NOW,
258
+ });
259
+
260
+ const sql: string = insertCalls()[0]![0];
261
+ expect(sql).toContain(
262
+ 'ON CONFLICT ("projectId", "macAddress") WHERE "deletedAt" IS NULL',
263
+ );
264
+ // vendor is create-only (user-correctable) — never in the conflict branch.
265
+ expect(sql.split("DO UPDATE SET")[1]).not.toContain('"vendor"');
266
+ });
267
+
268
+ it("updates an existing row when the FDB moves it to another port, keeping firstSeenAt", async () => {
269
+ findBySpy.mockResolvedValue([
270
+ makeExistingRow({
271
+ macAddress: CISCO_MAC,
272
+ attachedNetworkDeviceId: OTHER_SWITCH_ID,
273
+ attachedInterfaceIndex: 2,
274
+ firstSeenAt: EARLIER,
275
+ lastSeenAt: ONE_MINUTE_AGO,
276
+ }),
277
+ ]);
278
+
279
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
280
+ projectId: PROJECT_ID,
281
+ deviceId: SWITCH_ID,
282
+ deviceSiteId: SITE_ID,
283
+ attachments: [
284
+ {
285
+ macAddress: CISCO_MAC,
286
+ attachedInterfaceIndex: 7,
287
+ attachedPortName: "Gi0/7",
288
+ vlanId: 12,
289
+ },
290
+ ],
291
+ ipBindings: [],
292
+ now: NOW,
293
+ });
294
+
295
+ expect(insertCalls()).toHaveLength(0);
296
+ expect(updateCalls()).toHaveLength(1);
297
+
298
+ const params: Array<unknown> = updateCalls()[0]![1];
299
+ expect(params).toEqual([
300
+ PROJECT_ID.toString(), // $1, shared
301
+ CISCO_MAC,
302
+ true, // setAttachment
303
+ SWITCH_ID.toString(),
304
+ 7,
305
+ "Gi0/7",
306
+ 12,
307
+ SITE_ID.toString(),
308
+ null, // no IP observed
309
+ NOW, // lastSeenAt
310
+ ]);
311
+
312
+ const sql: string = updateCalls()[0]![0];
313
+ // firstSeenAt is never rewritten when the row already has one.
314
+ expect(sql).toContain(
315
+ '"firstSeenAt" = COALESCE(e."firstSeenAt", v."lastSeenAt")',
316
+ );
317
+ // Soft-deleted rows are never resurrected by the bulk update.
318
+ expect(sql).toContain('e."deletedAt" IS NULL');
319
+ });
320
+
321
+ it("lets ARP refresh the IP but not steal an attachment owned elsewhere", async () => {
322
+ findBySpy.mockResolvedValue([
323
+ makeExistingRow({
324
+ macAddress: "aa:bb:cc:dd:ee:01",
325
+ attachedNetworkDeviceId: OTHER_SWITCH_ID,
326
+ attachedInterfaceIndex: 2,
327
+ ipAddress: "10.0.0.5",
328
+ firstSeenAt: EARLIER,
329
+ lastSeenAt: ONE_MINUTE_AGO,
330
+ }),
331
+ ]);
332
+
333
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
334
+ projectId: PROJECT_ID,
335
+ deviceId: SWITCH_ID,
336
+ deviceSiteId: SITE_ID,
337
+ attachments: [],
338
+ ipBindings: [
339
+ {
340
+ macAddress: "aa:bb:cc:dd:ee:01",
341
+ ipAddress: "10.0.0.99",
342
+ routerInterfaceIndex: 3,
343
+ },
344
+ ],
345
+ now: NOW,
346
+ });
347
+
348
+ expect(updateCalls()).toHaveLength(1);
349
+ const params: Array<unknown> = updateCalls()[0]![1];
350
+ expect(params).toEqual([
351
+ PROJECT_ID.toString(),
352
+ "aa:bb:cc:dd:ee:01",
353
+ false, // setAttachment — the FDB owner keeps the port
354
+ null, // deviceId not written
355
+ null,
356
+ null,
357
+ null,
358
+ null, // siteId not written either
359
+ "10.0.0.99", // but the IP still refreshes
360
+ NOW,
361
+ ]);
362
+ });
363
+
364
+ it("backfills firstSeenAt on rows that predate first-seen tracking", async () => {
365
+ findBySpy.mockResolvedValue([
366
+ makeExistingRow({
367
+ macAddress: "aa:bb:cc:dd:ee:01",
368
+ attachedNetworkDeviceId: SWITCH_ID,
369
+ attachedInterfaceIndex: 2,
370
+ lastSeenAt: ONE_MINUTE_AGO,
371
+ // No firstSeenAt.
372
+ }),
373
+ ]);
374
+
375
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
376
+ projectId: PROJECT_ID,
377
+ deviceId: SWITCH_ID,
378
+ attachments: [
379
+ { macAddress: "aa:bb:cc:dd:ee:01", attachedInterfaceIndex: 2 },
380
+ ],
381
+ ipBindings: [],
382
+ now: NOW,
383
+ });
384
+
385
+ /*
386
+ * Nothing else changed and lastSeenAt is fresh, but the missing
387
+ * firstSeenAt still forces the write.
388
+ */
389
+ expect(updateCalls()).toHaveLength(1);
390
+ expect(updateCalls()[0]![1][9]).toBe(NOW);
391
+ });
392
+
393
+ describe("unchanged-since-last-poll short circuit", () => {
394
+ const stableRow: () => NetworkEndpoint = (): NetworkEndpoint => {
395
+ return makeExistingRow({
396
+ macAddress: CISCO_MAC,
397
+ attachedNetworkDeviceId: SWITCH_ID,
398
+ attachedInterfaceIndex: 7,
399
+ attachedPortName: "Gi0/7",
400
+ vlanId: 12,
401
+ siteId: SITE_ID,
402
+ firstSeenAt: EARLIER,
403
+ lastSeenAt: ONE_MINUTE_AGO,
404
+ });
405
+ };
406
+
407
+ const stableWalk: (now: Date) => Promise<void> = async (
408
+ now: Date,
409
+ ): Promise<void> => {
410
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
411
+ projectId: PROJECT_ID,
412
+ deviceId: SWITCH_ID,
413
+ deviceSiteId: SITE_ID,
414
+ attachments: [
415
+ {
416
+ macAddress: CISCO_MAC,
417
+ attachedInterfaceIndex: 7,
418
+ attachedPortName: "Gi0/7",
419
+ vlanId: 12,
420
+ },
421
+ ],
422
+ ipBindings: [],
423
+ now: now,
424
+ });
425
+ };
426
+
427
+ it("writes nothing for an endpoint that has not moved since the last poll", async () => {
428
+ findBySpy.mockResolvedValue([stableRow()]);
429
+
430
+ await stableWalk(NOW);
431
+
432
+ // One SELECT, zero writes.
433
+ expect(findBySpy).toHaveBeenCalledTimes(1);
434
+ expect(querySpy).not.toHaveBeenCalled();
435
+ });
436
+
437
+ it("still refreshes lastSeenAt once it ages past the threshold", async () => {
438
+ const row: NetworkEndpoint = stableRow();
439
+ row.lastSeenAt = TEN_MINUTES_AGO;
440
+ findBySpy.mockResolvedValue([row]);
441
+
442
+ await stableWalk(NOW);
443
+
444
+ expect(updateCalls()).toHaveLength(1);
445
+ expect(updateCalls()[0]![1][9]).toBe(NOW);
446
+ });
447
+
448
+ it("writes when the endpoint moves to a different port", async () => {
449
+ findBySpy.mockResolvedValue([stableRow()]);
450
+
451
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
452
+ projectId: PROJECT_ID,
453
+ deviceId: SWITCH_ID,
454
+ deviceSiteId: SITE_ID,
455
+ attachments: [
456
+ {
457
+ macAddress: CISCO_MAC,
458
+ attachedInterfaceIndex: 8,
459
+ attachedPortName: "Gi0/8",
460
+ vlanId: 12,
461
+ },
462
+ ],
463
+ ipBindings: [],
464
+ now: NOW,
465
+ });
466
+
467
+ expect(updateCalls()).toHaveLength(1);
468
+ expect(updateCalls()[0]![1][4]).toBe(8);
469
+ expect(updateCalls()[0]![1][5]).toBe("Gi0/8");
470
+ });
471
+
472
+ it("writes when the endpoint moves to a different switch", async () => {
473
+ findBySpy.mockResolvedValue([stableRow()]);
474
+
475
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
476
+ projectId: PROJECT_ID,
477
+ deviceId: OTHER_SWITCH_ID,
478
+ deviceSiteId: SITE_ID,
479
+ attachments: [
480
+ {
481
+ macAddress: CISCO_MAC,
482
+ attachedInterfaceIndex: 7,
483
+ attachedPortName: "Gi0/7",
484
+ vlanId: 12,
485
+ },
486
+ ],
487
+ ipBindings: [],
488
+ now: NOW,
489
+ });
490
+
491
+ expect(updateCalls()).toHaveLength(1);
492
+ expect(updateCalls()[0]![1][3]).toBe(OTHER_SWITCH_ID.toString());
493
+ });
494
+
495
+ it("writes when the endpoint's IP changes", async () => {
496
+ const row: NetworkEndpoint = stableRow();
497
+ row.ipAddress = "10.0.0.5";
498
+ findBySpy.mockResolvedValue([row]);
499
+
500
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
501
+ projectId: PROJECT_ID,
502
+ deviceId: SWITCH_ID,
503
+ deviceSiteId: SITE_ID,
504
+ attachments: [
505
+ {
506
+ macAddress: CISCO_MAC,
507
+ attachedInterfaceIndex: 7,
508
+ attachedPortName: "Gi0/7",
509
+ vlanId: 12,
510
+ },
511
+ ],
512
+ ipBindings: [
513
+ {
514
+ macAddress: CISCO_MAC,
515
+ ipAddress: "10.0.0.6",
516
+ routerInterfaceIndex: 3,
517
+ },
518
+ ],
519
+ now: NOW,
520
+ });
521
+
522
+ expect(updateCalls()).toHaveLength(1);
523
+ expect(updateCalls()[0]![1][8]).toBe("10.0.0.6");
524
+ });
525
+
526
+ it("writes when the device moved to a different site", async () => {
527
+ findBySpy.mockResolvedValue([stableRow()]);
528
+
529
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
530
+ projectId: PROJECT_ID,
531
+ deviceId: SWITCH_ID,
532
+ deviceSiteId: OTHER_SWITCH_ID, // any different site id
533
+ attachments: [
534
+ {
535
+ macAddress: CISCO_MAC,
536
+ attachedInterfaceIndex: 7,
537
+ attachedPortName: "Gi0/7",
538
+ vlanId: 12,
539
+ },
540
+ ],
541
+ ipBindings: [],
542
+ now: NOW,
543
+ });
544
+
545
+ expect(updateCalls()).toHaveLength(1);
546
+ expect(updateCalls()[0]![1][7]).toBe(OTHER_SWITCH_ID.toString());
547
+ });
548
+ });
549
+
550
+ describe("write volume", () => {
551
+ const macFor: (index: number) => string = (index: number): string => {
552
+ const hex: string = index.toString(16).padStart(4, "0");
553
+ return `aa:bb:cc:dd:${hex.substring(0, 2)}:${hex.substring(2, 4)}`;
554
+ };
555
+
556
+ it("batches one findBy for all observed MACs, scoped to the project", async () => {
557
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
558
+ projectId: PROJECT_ID,
559
+ deviceId: SWITCH_ID,
560
+ attachments: [
561
+ { macAddress: "aa:bb:cc:dd:ee:01", attachedInterfaceIndex: 1 },
562
+ { macAddress: "aa:bb:cc:dd:ee:02", attachedInterfaceIndex: 2 },
563
+ ],
564
+ ipBindings: [
565
+ {
566
+ macAddress: "aa:bb:cc:dd:ee:03",
567
+ ipAddress: "10.0.0.3",
568
+ routerInterfaceIndex: 9,
569
+ },
570
+ ],
571
+ now: NOW,
572
+ });
573
+
574
+ expect(findBySpy).toHaveBeenCalledTimes(1);
575
+ const findByArg: { query: Record<string, unknown> } = findBySpy.mock
576
+ .calls[0]![0]! as unknown as { query: Record<string, unknown> };
577
+ expect(findByArg.query["projectId"]).toBe(PROJECT_ID);
578
+
579
+ // All three MACs ride in ONE insert, not three.
580
+ expect(insertCalls()).toHaveLength(1);
581
+ expect(insertCalls()[0]![1]).toHaveLength(3 * INSERT_PARAMS_PER_ROW);
582
+ });
583
+
584
+ it("writes 600 brand new MACs in 2 statements, not 600", async () => {
585
+ const attachments: Array<{
586
+ macAddress: string;
587
+ attachedInterfaceIndex: number;
588
+ }> = [];
589
+ for (let i: number = 0; i < 600; i++) {
590
+ attachments.push({
591
+ macAddress: macFor(i),
592
+ attachedInterfaceIndex: i % 48,
593
+ });
594
+ }
595
+
596
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
597
+ projectId: PROJECT_ID,
598
+ deviceId: SWITCH_ID,
599
+ attachments: attachments,
600
+ ipBindings: [],
601
+ now: NOW,
602
+ });
603
+
604
+ // ceil(600 / 500) === 2, and nothing else.
605
+ expect(querySpy).toHaveBeenCalledTimes(2);
606
+ expect(insertCalls()).toHaveLength(2);
607
+ expect(insertCalls()[0]![1]).toHaveLength(500 * INSERT_PARAMS_PER_ROW);
608
+ expect(insertCalls()[1]![1]).toHaveLength(100 * INSERT_PARAMS_PER_ROW);
609
+ });
610
+
611
+ it("issues one SELECT and zero writes for a stable 600-MAC switch", async () => {
612
+ const attachments: Array<{
613
+ macAddress: string;
614
+ attachedInterfaceIndex: number;
615
+ }> = [];
616
+ const rows: Array<NetworkEndpoint> = [];
617
+ for (let i: number = 0; i < 600; i++) {
618
+ attachments.push({
619
+ macAddress: macFor(i),
620
+ attachedInterfaceIndex: i % 48,
621
+ });
622
+ rows.push(
623
+ makeExistingRow({
624
+ macAddress: macFor(i),
625
+ attachedNetworkDeviceId: SWITCH_ID,
626
+ attachedInterfaceIndex: i % 48,
627
+ firstSeenAt: EARLIER,
628
+ lastSeenAt: ONE_MINUTE_AGO,
629
+ }),
630
+ );
631
+ }
632
+ findBySpy.mockResolvedValue(rows);
633
+
634
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
635
+ projectId: PROJECT_ID,
636
+ deviceId: SWITCH_ID,
637
+ attachments: attachments,
638
+ ipBindings: [],
639
+ now: NOW,
640
+ });
641
+
642
+ expect(findBySpy).toHaveBeenCalledTimes(1);
643
+ expect(querySpy).not.toHaveBeenCalled();
644
+ });
645
+
646
+ it("splits mixed creates and updates into exactly one statement each", async () => {
647
+ const attachments: Array<{
648
+ macAddress: string;
649
+ attachedInterfaceIndex: number;
650
+ }> = [];
651
+ const rows: Array<NetworkEndpoint> = [];
652
+ for (let i: number = 0; i < 10; i++) {
653
+ // Every MAC is observed on port 99 this poll.
654
+ attachments.push({ macAddress: macFor(i), attachedInterfaceIndex: 99 });
655
+ if (i < 4) {
656
+ // The first four already exist, on a different port -> updates.
657
+ rows.push(
658
+ makeExistingRow({
659
+ macAddress: macFor(i),
660
+ attachedNetworkDeviceId: SWITCH_ID,
661
+ attachedInterfaceIndex: 1,
662
+ firstSeenAt: EARLIER,
663
+ lastSeenAt: ONE_MINUTE_AGO,
664
+ }),
665
+ );
666
+ }
667
+ }
668
+ findBySpy.mockResolvedValue(rows);
669
+
670
+ await NetworkEndpointService.upsertDiscoveredEndpoints({
671
+ projectId: PROJECT_ID,
672
+ deviceId: SWITCH_ID,
673
+ attachments: attachments,
674
+ ipBindings: [],
675
+ now: NOW,
676
+ });
677
+
678
+ expect(querySpy).toHaveBeenCalledTimes(2);
679
+ expect(insertCalls()).toHaveLength(1);
680
+ expect(updateCalls()).toHaveLength(1);
681
+ expect(insertCalls()[0]![1]).toHaveLength(6 * INSERT_PARAMS_PER_ROW);
682
+ // +1 for the shared projectId parameter.
683
+ expect(updateCalls()[0]![1]).toHaveLength(4 * UPDATE_PARAMS_PER_ROW + 1);
684
+ });
685
+ });
686
+ });