@oneuptime/common 12.0.34 → 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.
Files changed (259) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +12 -12
  2. package/Models/DatabaseModels/InventoryItem.ts +44 -1
  3. package/Models/DatabaseModels/NetworkDevice.ts +126 -0
  4. package/Server/EnvironmentConfig.ts +47 -16
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Infrastructure/Redis.ts +8 -8
  8. package/Server/Infrastructure/Status.ts +10 -4
  9. package/Server/Services/DatabaseService.ts +65 -13
  10. package/Server/Services/InventoryItemService.ts +31 -1
  11. package/Server/Services/MonitorTemplateService.ts +140 -19
  12. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
  13. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
  14. package/Server/Services/NetworkDeviceService.ts +69 -0
  15. package/Server/Services/UserNotificationRuleService.ts +76 -25
  16. package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
  17. package/Server/Types/Database/QueryUtil.ts +56 -0
  18. package/Server/Types/Markdown.ts +93 -1
  19. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
  20. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
  21. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
  25. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
  26. package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
  27. package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
  28. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
  29. package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
  30. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
  31. package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
  32. package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
  33. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
  34. package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
  35. package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
  36. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
  37. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
  38. package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
  39. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
  40. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
  41. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
  42. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
  43. package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
  44. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
  45. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
  46. package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
  47. package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
  48. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
  49. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
  50. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
  51. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
  52. package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
  53. package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
  54. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
  55. package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
  56. package/Tests/Models/InventoryItemStatus.test.ts +147 -0
  57. package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
  58. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
  59. package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
  60. package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
  61. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
  62. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
  63. package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
  64. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
  65. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
  66. package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
  67. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
  68. package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
  69. package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
  70. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
  72. package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
  73. package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
  74. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
  75. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
  76. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
  77. package/Tests/Server/Types/Markdown.test.ts +194 -1
  78. package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
  79. package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
  80. package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
  81. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
  82. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  83. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
  84. package/Tests/Server/Utils/EventLoop.test.ts +359 -0
  85. package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
  86. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
  87. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
  88. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
  90. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
  91. package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
  92. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
  93. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
  94. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
  95. package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
  96. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
  97. package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
  98. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
  99. package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
  100. package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
  101. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
  102. package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
  103. package/Tests/Types/JSONFunctions.test.ts +40 -0
  104. package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
  105. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
  106. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
  107. package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
  108. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
  109. package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
  110. package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
  111. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
  112. package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
  113. package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
  114. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  115. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
  116. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
  117. package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
  118. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
  119. package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
  120. package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
  121. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
  122. package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
  123. package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
  124. package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
  125. package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
  126. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  127. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  128. package/Types/Dashboard/DashboardVariable.ts +8 -0
  129. package/Types/JSON.ts +4 -0
  130. package/Types/JSONFunctions.ts +16 -0
  131. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  132. package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
  133. package/Types/SerializableObjectDictionary.ts +4 -0
  134. package/Types/Telemetry/InventoryLiveness.ts +56 -0
  135. package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
  136. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
  137. package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
  138. package/UI/esbuild-config.js +6 -0
  139. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
  140. package/Utils/Dashboard/LabelVariable.ts +152 -0
  141. package/Utils/Dashboard/VariableUrlState.ts +27 -5
  142. package/Utils/MetricUnitUtil.ts +34 -0
  143. package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
  144. package/Utils/Monitor/MetricValueFormatter.ts +342 -0
  145. package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
  146. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
  147. package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
  148. package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
  149. package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
  150. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
  152. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
  154. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  155. package/build/dist/Server/EnvironmentConfig.js +44 -12
  156. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  157. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
  158. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
  159. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  160. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  161. package/build/dist/Server/Infrastructure/Redis.js +8 -8
  162. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  163. package/build/dist/Server/Infrastructure/Status.js +10 -4
  164. package/build/dist/Server/Infrastructure/Status.js.map +1 -1
  165. package/build/dist/Server/Services/DatabaseService.js +58 -13
  166. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  167. package/build/dist/Server/Services/InventoryItemService.js +28 -1
  168. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  169. package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
  170. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  171. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
  172. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  173. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
  174. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  175. package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
  176. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  177. package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
  178. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  179. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
  180. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  181. package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
  182. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  183. package/build/dist/Server/Types/Markdown.js +75 -0
  184. package/build/dist/Server/Types/Markdown.js.map +1 -1
  185. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
  186. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  187. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
  188. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  189. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
  190. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
  192. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  193. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
  194. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  195. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
  196. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  197. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
  198. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  199. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
  200. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
  201. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
  202. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
  204. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
  206. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
  207. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
  208. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
  210. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  211. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
  212. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  213. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
  214. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
  215. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  216. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  217. package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
  218. package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
  219. package/build/dist/Types/JSON.js +1 -0
  220. package/build/dist/Types/JSON.js.map +1 -1
  221. package/build/dist/Types/JSONFunctions.js +13 -0
  222. package/build/dist/Types/JSONFunctions.js.map +1 -1
  223. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
  224. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  225. package/build/dist/Types/SerializableObjectDictionary.js +4 -0
  226. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  227. package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
  228. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
  229. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
  230. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
  231. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
  232. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  233. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
  234. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
  235. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
  236. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  237. package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
  238. package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
  239. package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
  240. package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
  241. package/build/dist/Utils/MetricUnitUtil.js +26 -0
  242. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  243. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
  244. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
  245. package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
  246. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
  247. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
  248. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
  249. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
  250. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  251. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
  252. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  253. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
  254. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
  255. package/jest.config.json +2 -0
  256. package/package.json +1 -1
  257. package/test-setup.sh +9 -9
  258. package/tsconfig.json +51 -11
  259. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
@@ -21,6 +21,8 @@ import MonitorType from "../../Types/Monitor/MonitorType";
21
21
  import {
22
22
  AutoImportRuleRunResult,
23
23
  MAX_MATCHED_IP_SAMPLE,
24
+ MAX_RUN_FAILURE_REASON_LENGTH,
25
+ MAX_RUN_FAILURE_REASONS,
24
26
  } from "../../Types/NetworkAutomation/RuleRunResult";
25
27
  import AutoImportRuleMatcher, {
26
28
  AutoImportHostEvaluation,
@@ -118,6 +120,26 @@ export const MAX_MONITORS_PER_AUTO_IMPORT_RUN: number = 500;
118
120
  // Scans one manual "Run Now" will read, oldest results last.
119
121
  export const MAX_SCANS_PER_AUTO_IMPORT_RULE_RUN: number = 100;
120
122
 
123
+ /*
124
+ * How many monitor creates may fail IDENTICALLY, back to back with no success
125
+ * between them, before the run stops attempting any more.
126
+ *
127
+ * A monitor create fails for one of two kinds of reason. Per host — this
128
+ * device's name collides, this one raced another writer — where the next host
129
+ * is unaffected and trying it is right. Or systemic: the template's criteria
130
+ * name a monitor status that no longer exists, the plan's monitor limit is
131
+ * reached, the database is refusing writes. Those fail for the FIRST device
132
+ * and for all 500 after it, and the old engine could not tell the difference:
133
+ * it spent the whole per-run monitor budget re-proving one broken thing, then
134
+ * reported "Stopped at the run cap — run again to continue", which is an
135
+ * instruction to do it all again. That is OneUptime/oneuptime#3643.
136
+ *
137
+ * Ten consecutive failures carrying the SAME message, with no create
138
+ * succeeding in between, is not a run of bad luck. Any success resets it, so a
139
+ * genuinely per-host fault never trips this.
140
+ */
141
+ export const MAX_CONSECUTIVE_MONITOR_CREATE_FAILURES: number = 10;
142
+
121
143
  /*
122
144
  * Results older than this are stamped processed WITHOUT importing. A late
123
145
  * result can land on a reaper-Failed scan hours after its sweep ran (the
@@ -161,6 +183,28 @@ export interface ExistingMonitorProvisioningState {
161
183
  autoProvisionedKeys: Set<string>;
162
184
  manuallyMonitoredDeviceIds: Set<string>;
163
185
  attemptedProvisioningKeys: Set<string>;
186
+ /*
187
+ * Monitor template ids this run has already proved it cannot resolve —
188
+ * deleted, moved to another project, or no longer of type Network Device.
189
+ * That is a property of the RULE, settled the first time a host needs the
190
+ * template, and re-deciding it per host costs one wasted monitor budget slot
191
+ * and one log line per device in the estate.
192
+ */
193
+ unresolvableTemplateIds: Set<string>;
194
+ /*
195
+ * Consecutive monitor creates that failed with the same message and no
196
+ * success since — see MAX_CONSECUTIVE_MONITOR_CREATE_FAILURES.
197
+ */
198
+ consecutiveCreateFailures: number;
199
+ lastCreateFailureReason: string;
200
+ /*
201
+ * Whether this run has EVER provisioned a monitor (or lost a race to
202
+ * somebody who did). One success is proof the create pipeline works, which
203
+ * is what disarms the systemic-failure guard for good.
204
+ */
205
+ hasProvisionedAnyMonitor: boolean;
206
+ // Set once this run gives up on provisioning monitors at all.
207
+ isProvisioningHalted: boolean;
164
208
  }
165
209
 
166
210
  export type ExistingMonitorsByProjectId = Map<
@@ -244,6 +288,11 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
244
288
  },
245
289
  props: {
246
290
  isRoot: true,
291
+ /*
292
+ * Keep the stored payload for stampScan; the engine checks live
293
+ * inventory itself before evaluating these hosts.
294
+ */
295
+ ignoreHooks: true,
247
296
  },
248
297
  });
249
298
 
@@ -586,7 +635,8 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
586
635
  scanStubs.pop();
587
636
  }
588
637
 
589
- for (const scanStub of scanStubs) {
638
+ for (let index: number = 0; index < scanStubs.length; index++) {
639
+ const scanStub: NetworkDeviceDiscoveryScan = scanStubs[index]!;
590
640
  const scan: NetworkDeviceDiscoveryScan | null =
591
641
  await NetworkDeviceDiscoveryScanService.findOneBy({
592
642
  query: {
@@ -605,7 +655,8 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
605
655
  discoveredDevices: true,
606
656
  ...AUTO_IMPORT_SCAN_CREDENTIAL_SELECT,
607
657
  },
608
- props: { isRoot: true },
658
+ // Preserve the raw scan rows for the compare-and-set write-back.
659
+ props: { isRoot: true, ignoreHooks: true },
609
660
  });
610
661
 
611
662
  // Re-queued or deleted since the stub query — nothing to evaluate.
@@ -639,8 +690,15 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
639
690
  });
640
691
  }
641
692
 
642
- // The device cap is shared across the whole run, dry or real.
693
+ /*
694
+ * The caps are shared across the whole run, dry or real. Stop OPENING
695
+ * further scans once they are spent — each one is a multi-megabyte
696
+ * jsonb read whose hosts this pass could not act on anyway — but say
697
+ * that scans were left unread, so the pending counters below are
698
+ * reported as a floor rather than as the estate's total.
699
+ */
643
700
  if (result.isTruncated) {
701
+ result.hasUnevaluatedScans = index < scanStubs.length - 1;
644
702
  break;
645
703
  }
646
704
 
@@ -678,18 +736,69 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
678
736
  monitorsSkippedAlreadyExisting: 0,
679
737
  monitorsSkippedUnsupportedHost: 0,
680
738
  monitorsFailed: 0,
739
+ deviceFailureReasons: [],
740
+ monitorFailureReasons: [],
741
+ monitorProvisioningHalted: false,
681
742
  isTruncated: false,
743
+ hostsPendingImport: 0,
744
+ monitorsPendingCreation: 0,
745
+ hasUnevaluatedScans: false,
682
746
  hasMoreScans: false,
683
747
  isDryRun: isDryRun,
684
748
  matchedIpAddressSample: [],
685
749
  };
686
750
  }
687
751
 
752
+ /*
753
+ * Keep the reason a create failed, so the run can say WHY it created
754
+ * nothing instead of pointing at server logs the operator of a self-hosted
755
+ * install may not be able to read. Deduplicated (500 identical failures are
756
+ * one reason) and capped, in both count and length, because this string
757
+ * ends up in a modal.
758
+ */
759
+ private recordFailureReason(reasons: Array<string>, error: unknown): void {
760
+ const message: string = this.describeError(error);
761
+
762
+ if (!message) {
763
+ return;
764
+ }
765
+
766
+ const trimmed: string = message.substring(0, MAX_RUN_FAILURE_REASON_LENGTH);
767
+
768
+ if (reasons.includes(trimmed)) {
769
+ return;
770
+ }
771
+
772
+ if (reasons.length >= MAX_RUN_FAILURE_REASONS) {
773
+ return;
774
+ }
775
+
776
+ reasons.push(trimmed);
777
+ }
778
+
779
+ /*
780
+ * The message an operator should see. Exception extends Error, so one arm
781
+ * covers both; anything else is stringified rather than dropped, because a
782
+ * thrown non-Error is exactly the case where the reason matters most.
783
+ */
784
+ private describeError(error: unknown): string {
785
+ if (error instanceof Error) {
786
+ return (error.message || "").replace(/\s+/g, " ").trim();
787
+ }
788
+
789
+ return String(error).replace(/\s+/g, " ").trim();
790
+ }
791
+
688
792
  private emptyExistingMonitorProvisioningState(): ExistingMonitorProvisioningState {
689
793
  return {
690
794
  autoProvisionedKeys: new Set(),
691
795
  manuallyMonitoredDeviceIds: new Set(),
692
796
  attemptedProvisioningKeys: new Set(),
797
+ unresolvableTemplateIds: new Set(),
798
+ consecutiveCreateFailures: 0,
799
+ lastCreateFailureReason: "",
800
+ hasProvisionedAnyMonitor: false,
801
+ isProvisioningHalted: false,
693
802
  };
694
803
  }
695
804
 
@@ -1108,6 +1217,18 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1108
1217
 
1109
1218
  const createdIpAddresses: Array<string> = [];
1110
1219
 
1220
+ /*
1221
+ * What this pass counted as still-to-do, deduplicated.
1222
+ *
1223
+ * A stored result can list the same address twice (duplicate rows are
1224
+ * normal enough that normalisation preserves them and the import path
1225
+ * treats the second as already-registered), so counting rows rather than
1226
+ * distinct work would report a remainder larger than the work that
1227
+ * actually remains — in a number whose whole purpose is to be trusted.
1228
+ */
1229
+ const pendingImportAddresses: Set<string> = new Set<string>();
1230
+ const pendingProvisioningKeys: Set<string> = new Set<string>();
1231
+
1111
1232
  for (const host of hosts) {
1112
1233
  /*
1113
1234
  * The address becomes the device's varchar(100) hostname, on which
@@ -1180,22 +1301,13 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1180
1301
  let deviceWasCreated: boolean = false;
1181
1302
 
1182
1303
  /*
1183
- * The frozen isAlreadyRegistered flag AND the live set: the flag is a
1184
- * point-in-time answer from the last upload, the set covers devices
1185
- * created since including by this very run, which is what collapses
1186
- * duplicate rows and overlapping scans into one device.
1304
+ * Current inventory is authoritative. The stored isAlreadyRegistered
1305
+ * flag can outlive a deleted device and must not veto re-importing it.
1306
+ * The map also includes devices created during this run, keeping
1307
+ * duplicate rows and overlapping scans idempotent.
1187
1308
  */
1188
- if (host.isAlreadyRegistered || networkDevice) {
1309
+ if (networkDevice) {
1189
1310
  result.hostsSkippedAlreadyRegistered++;
1190
-
1191
- /*
1192
- * A stale scan can claim a deleted device is still registered. With
1193
- * no live row there is neither a safe binding nor enough intent to
1194
- * recreate it from old data, so preserve the established skip.
1195
- */
1196
- if (!networkDevice) {
1197
- continue;
1198
- }
1199
1311
  }
1200
1312
 
1201
1313
  if (result.matchedIpAddressSample.length < MAX_MATCHED_IP_SAMPLE) {
@@ -1206,8 +1318,25 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1206
1318
 
1207
1319
  if (!networkDevice) {
1208
1320
  if (data.attempts.deviceCount >= MAX_DEVICES_PER_AUTO_IMPORT_RUN) {
1321
+ /*
1322
+ * Budget spent — stop WRITING, keep COUNTING. The rest of this
1323
+ * loop is pure matching over a jsonb array that is already in
1324
+ * memory against addresses primeExistingDevices already resolved,
1325
+ * so finishing the scan costs nothing the cap exists to prevent,
1326
+ * and it is the difference between "imported 500" and "imported
1327
+ * 500 of 909, 409 still to go". A run that stopped mid-count
1328
+ * reported the truncated numbers as if they were the estate,
1329
+ * which is what OneUptime issue #3642 experienced as the
1330
+ * remainder being silently dropped.
1331
+ */
1209
1332
  result.isTruncated = true;
1210
- break;
1333
+
1334
+ if (!pendingImportAddresses.has(host.ipAddress)) {
1335
+ pendingImportAddresses.add(host.ipAddress);
1336
+ result.hostsPendingImport++;
1337
+ }
1338
+
1339
+ continue;
1211
1340
  }
1212
1341
 
1213
1342
  if (data.isDryRun) {
@@ -1262,8 +1391,8 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1262
1391
  continue;
1263
1392
  }
1264
1393
 
1265
- const monitorsWereTruncated: boolean = await this.ensureMonitorsForDevice(
1266
- {
1394
+ const monitorsLeftPending: Array<string> =
1395
+ await this.ensureMonitorsForDevice({
1267
1396
  projectId: scan.projectId,
1268
1397
  host: host,
1269
1398
  networkDevice: networkDevice,
@@ -1273,18 +1402,45 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1273
1402
  result: result,
1274
1403
  attempts: data.attempts,
1275
1404
  isDryRun: data.isDryRun,
1276
- },
1277
- );
1405
+ });
1278
1406
 
1279
- if (monitorsWereTruncated) {
1407
+ /*
1408
+ * Same protocol as the device cap above: the monitor budget stops the
1409
+ * creates, not the accounting. It also has its OWN cap, so an estate
1410
+ * that is fully imported but unmonitored — the shape of issue #3642 —
1411
+ * runs out of monitor budget with nothing pending on the device side,
1412
+ * and the operator needs to be told how many devices are still
1413
+ * waiting for a monitor.
1414
+ */
1415
+ for (const templateId of monitorsLeftPending) {
1280
1416
  result.isTruncated = true;
1281
- break;
1417
+
1418
+ const pendingKey: string =
1419
+ NetworkDeviceAutoImportRuleEngineServiceClass.monitorProvisioningKey(
1420
+ networkDevice.id!,
1421
+ templateId,
1422
+ );
1423
+
1424
+ if (!pendingProvisioningKeys.has(pendingKey)) {
1425
+ pendingProvisioningKeys.add(pendingKey);
1426
+ result.monitorsPendingCreation++;
1427
+ }
1282
1428
  }
1283
1429
  }
1284
1430
 
1285
1431
  return createdIpAddresses;
1286
1432
  }
1287
1433
 
1434
+ /*
1435
+ * Reconcile one device against the templates the matched rules selected.
1436
+ *
1437
+ * Returns the template ids the run's budget left UNATTEMPTED — empty when
1438
+ * every requested monitor was settled, whether created, failed, or skipped
1439
+ * because monitoring already existed. A non-empty answer is what makes the
1440
+ * run report itself truncated; the ids (rather than a bare count) are what
1441
+ * let the caller count one device's leftover work once, however many
1442
+ * duplicate rows the scan lists that host under.
1443
+ */
1288
1444
  private async ensureMonitorsForDevice(data: {
1289
1445
  projectId: ObjectID;
1290
1446
  host: DiscoveredNetworkDevice;
@@ -1295,7 +1451,7 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1295
1451
  result: AutoImportRuleRunResult;
1296
1452
  attempts: ImportAttemptBudget;
1297
1453
  isDryRun: boolean;
1298
- }): Promise<boolean> {
1454
+ }): Promise<Array<string>> {
1299
1455
  /*
1300
1456
  * A Network Device monitor is fed by the device's polls, and nothing
1301
1457
  * polls a monitor-backed device — its bound monitor's status IS its
@@ -1312,7 +1468,7 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1312
1468
  )
1313
1469
  ) {
1314
1470
  data.result.monitorsSkippedUnsupportedHost += data.templateIds.length;
1315
- return false;
1471
+ return [];
1316
1472
  }
1317
1473
 
1318
1474
  if (!data.networkDevice.id) {
@@ -1321,7 +1477,7 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1321
1477
  `Auto-import: cannot provision monitor(s) for ${data.host.ipAddress} because the Network Device has no id.`,
1322
1478
  { projectId: data.projectId.toString() } as LogAttributes,
1323
1479
  );
1324
- return false;
1480
+ return [];
1325
1481
  }
1326
1482
 
1327
1483
  const networkDeviceId: ObjectID = data.networkDevice.id;
@@ -1354,25 +1510,76 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1354
1510
  }
1355
1511
 
1356
1512
  if (pendingTemplateIds.length === 0) {
1357
- return false;
1513
+ return [];
1358
1514
  }
1359
1515
 
1516
+ /*
1517
+ * The project-wide snapshot already knows this device is monitored by
1518
+ * hand, and that answer outranks any failure below: a device nobody was
1519
+ * going to provision cannot be a monitor this run failed to create. Read
1520
+ * from memory, so it stays ahead of the systemic verdicts.
1521
+ */
1360
1522
  if (
1361
1523
  data.existingMonitors.manuallyMonitoredDeviceIds.has(
1362
1524
  networkDeviceIdString,
1363
1525
  )
1364
1526
  ) {
1365
1527
  data.result.monitorsSkippedAlreadyExisting += pendingTemplateIds.length;
1366
- return false;
1528
+ return [];
1529
+ }
1530
+
1531
+ /*
1532
+ * Everything below here costs the run something — a monitor budget slot, a
1533
+ * per-device search of the unindexed monitor step payload, a create round
1534
+ * trip. None of it is worth spending on work this run has already proved
1535
+ * cannot succeed, so the two systemic verdicts are settled next: a
1536
+ * template this run could not load, and a create pipeline that has failed
1537
+ * identically for every device it tried.
1538
+ *
1539
+ * Both still COUNT the monitors that are missing. "0 created, and here is
1540
+ * why" is the answer; quietly reporting fewer missing monitors than there
1541
+ * are would trade one confusing dialog for another.
1542
+ */
1543
+ const resolvableTemplateIds: Array<string> = [];
1544
+
1545
+ for (const templateId of pendingTemplateIds) {
1546
+ if (
1547
+ !data.existingMonitors.isProvisioningHalted &&
1548
+ !data.existingMonitors.unresolvableTemplateIds.has(templateId)
1549
+ ) {
1550
+ resolvableTemplateIds.push(templateId);
1551
+ continue;
1552
+ }
1553
+
1554
+ /*
1555
+ * Counted once and then settled. Marking the (device, template) key
1556
+ * attempted is what keeps a host carried by two overlapping scans in the
1557
+ * same run from being counted as two missing monitors — the same
1558
+ * bookkeeping a real attempt does, for the same reason.
1559
+ */
1560
+ data.existingMonitors.attemptedProvisioningKeys.add(
1561
+ NetworkDeviceAutoImportRuleEngineServiceClass.monitorProvisioningKey(
1562
+ networkDeviceId,
1563
+ templateId,
1564
+ ),
1565
+ );
1566
+ data.result.monitorsFailed++;
1567
+ data.result.monitorProvisioningHalted = true;
1568
+ }
1569
+
1570
+ if (resolvableTemplateIds.length === 0) {
1571
+ return [];
1367
1572
  }
1368
1573
 
1369
1574
  /*
1370
1575
  * Do not run the unindexed JSON race-check after this run's monitor-work
1371
1576
  * budget is already exhausted. The device remains unreconciled and the
1372
- * truncated marker protocol brings it back on the next bounded pass.
1577
+ * truncated marker protocol brings it back on the next bounded pass
1578
+ * counted, so the operator is told how many devices are still waiting
1579
+ * rather than left to infer it from a total that stopped early.
1373
1580
  */
1374
1581
  if (data.attempts.monitorCount >= MAX_MONITORS_PER_AUTO_IMPORT_RUN) {
1375
- return true;
1582
+ return resolvableTemplateIds;
1376
1583
  }
1377
1584
 
1378
1585
  if (!data.isDryRun) {
@@ -1388,43 +1595,74 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1388
1595
  networkDeviceIdString,
1389
1596
  )
1390
1597
  ) {
1391
- data.result.monitorsSkippedAlreadyExisting += pendingTemplateIds.length;
1392
- return false;
1598
+ data.result.monitorsSkippedAlreadyExisting +=
1599
+ resolvableTemplateIds.length;
1600
+ return [];
1393
1601
  }
1394
1602
 
1395
- for (const templateId of pendingTemplateIds) {
1603
+ for (let index: number = 0; index < resolvableTemplateIds.length; index++) {
1604
+ const templateId: string = resolvableTemplateIds[index]!;
1396
1605
  const key: string =
1397
1606
  NetworkDeviceAutoImportRuleEngineServiceClass.monitorProvisioningKey(
1398
1607
  networkDeviceId,
1399
1608
  templateId,
1400
1609
  );
1401
1610
 
1611
+ /*
1612
+ * A create earlier in THIS device's loop can be the one that trips the
1613
+ * systemic verdict. The rest of its templates are then in exactly the
1614
+ * position of every device after it.
1615
+ */
1616
+ if (data.existingMonitors.isProvisioningHalted) {
1617
+ data.existingMonitors.attemptedProvisioningKeys.add(key);
1618
+ data.result.monitorsFailed++;
1619
+ data.result.monitorProvisioningHalted = true;
1620
+ continue;
1621
+ }
1622
+
1402
1623
  /* The race-check may have found this key after the initial snapshot. */
1403
1624
  if (data.existingMonitors.autoProvisionedKeys.has(key)) {
1404
1625
  data.result.monitorsSkippedAlreadyExisting++;
1405
1626
  continue;
1406
1627
  }
1407
1628
 
1629
+ /*
1630
+ * Budget spent mid-device. Everything from here on is untouched, so
1631
+ * that is exactly what is left pending for this device.
1632
+ */
1408
1633
  if (data.attempts.monitorCount >= MAX_MONITORS_PER_AUTO_IMPORT_RUN) {
1409
- return true;
1634
+ return resolvableTemplateIds.slice(index);
1410
1635
  }
1411
1636
 
1412
1637
  data.attempts.monitorCount++;
1413
1638
  data.existingMonitors.attemptedProvisioningKeys.add(key);
1414
1639
 
1415
1640
  /*
1416
- * Invalid or concurrently deleted templates are failed attempts too.
1417
- * Count them inside the same cap before looking up the cached template;
1418
- * otherwise one stale rule could walk and log an unbounded estate in a
1419
- * single API request or worker tick.
1641
+ * A template the run could not load is a fault in the RULE, not in this
1642
+ * host: the rule points at a template that has been deleted, moved to
1643
+ * another project, or changed to a different monitor type since the rule
1644
+ * was saved (the rule form validates all three, but only at save time).
1645
+ * It will be just as unloadable for every other device, so the verdict is
1646
+ * recorded once and every later host short-circuits above — instead of
1647
+ * spending the run's whole monitor budget re-proving it, which is what
1648
+ * turned this into "500 monitors failed, stopped at the run cap, run
1649
+ * again to continue" in issue #3643.
1420
1650
  */
1421
1651
  const template: MonitorTemplate | undefined =
1422
1652
  data.monitorTemplates.get(templateId);
1423
1653
 
1424
1654
  if (!template) {
1655
+ data.existingMonitors.unresolvableTemplateIds.add(templateId);
1425
1656
  data.result.monitorsFailed++;
1657
+ data.result.monitorProvisioningHalted = true;
1658
+ this.recordFailureReason(
1659
+ data.result.monitorFailureReasons,
1660
+ new Error(
1661
+ "This rule's Monitor Template could not be loaded. It may have been deleted, moved to another project, or changed to a monitor type other than Network Device. Edit the rule and select a Network Device Monitor Template.",
1662
+ ),
1663
+ );
1426
1664
  logger.error(
1427
- `Auto-import: Network Device monitor template ${templateId} is missing, deleted, or not valid for project ${data.projectId.toString()}.`,
1665
+ `Auto-import: Network Device monitor template ${templateId} is missing, deleted, or not valid for project ${data.projectId.toString()}. No monitors will be provisioned from it for the rest of this run.`,
1428
1666
  { projectId: data.projectId.toString() } as LogAttributes,
1429
1667
  );
1430
1668
  continue;
@@ -1452,6 +1690,7 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1452
1690
 
1453
1691
  data.existingMonitors.autoProvisionedKeys.add(key);
1454
1692
  data.result.monitorsCreated++;
1693
+ this.recordMonitorCreateSuccess(data.existingMonitors);
1455
1694
  } catch (error) {
1456
1695
  /*
1457
1696
  * The partial unique index is the final race backstop. If another
@@ -1483,19 +1722,87 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1483
1722
 
1484
1723
  if (data.existingMonitors.autoProvisionedKeys.has(key)) {
1485
1724
  data.result.monitorsSkippedAlreadyExisting++;
1725
+ /*
1726
+ * Losing this race proves the create pipeline works — somebody
1727
+ * else just used it — so it is not evidence of a systemic fault.
1728
+ */
1729
+ this.recordMonitorCreateSuccess(data.existingMonitors);
1486
1730
  continue;
1487
1731
  }
1488
1732
  }
1489
1733
 
1490
1734
  data.result.monitorsFailed++;
1735
+ this.recordFailureReason(data.result.monitorFailureReasons, error);
1491
1736
  logger.error(
1492
1737
  `Auto-import: could not create monitor from template ${templateId} for Network Device ${networkDeviceId.toString()} (${data.host.ipAddress}): ${error}`,
1493
1738
  { projectId: data.projectId.toString() } as LogAttributes,
1494
1739
  );
1740
+ this.recordMonitorCreateFailure({
1741
+ state: data.existingMonitors,
1742
+ result: data.result,
1743
+ projectId: data.projectId,
1744
+ error: error,
1745
+ });
1495
1746
  }
1496
1747
  }
1497
1748
 
1498
- return false;
1749
+ return [];
1750
+ }
1751
+
1752
+ /*
1753
+ * A create succeeded (or lost a race to another writer, which proves the
1754
+ * same thing): whatever the last failures were, they were not systemic, and
1755
+ * this run has now seen the create pipeline work at least once.
1756
+ */
1757
+ private recordMonitorCreateSuccess(
1758
+ state: ExistingMonitorProvisioningState,
1759
+ ): void {
1760
+ state.consecutiveCreateFailures = 0;
1761
+ state.lastCreateFailureReason = "";
1762
+ state.hasProvisionedAnyMonitor = true;
1763
+ }
1764
+
1765
+ /*
1766
+ * Decide whether one monitor create failure is this host's problem or the
1767
+ * whole run's. The bar is deliberately high, because giving up early on a
1768
+ * per-host fault would silently cost an estate its monitors — the failure
1769
+ * this fix exists to make visible. All three must hold: the run has never
1770
+ * created a monitor, the last MAX_CONSECUTIVE_MONITOR_CREATE_FAILURES
1771
+ * attempts all failed, and they all failed with the SAME message.
1772
+ */
1773
+ private recordMonitorCreateFailure(data: {
1774
+ state: ExistingMonitorProvisioningState;
1775
+ result: AutoImportRuleRunResult;
1776
+ projectId: ObjectID;
1777
+ error: unknown;
1778
+ }): void {
1779
+ const reason: string = this.describeError(data.error);
1780
+
1781
+ if (reason && reason === data.state.lastCreateFailureReason) {
1782
+ data.state.consecutiveCreateFailures++;
1783
+ } else {
1784
+ data.state.lastCreateFailureReason = reason;
1785
+ data.state.consecutiveCreateFailures = 1;
1786
+ }
1787
+
1788
+ if (data.state.hasProvisionedAnyMonitor) {
1789
+ return;
1790
+ }
1791
+
1792
+ if (
1793
+ data.state.consecutiveCreateFailures <
1794
+ MAX_CONSECUTIVE_MONITOR_CREATE_FAILURES
1795
+ ) {
1796
+ return;
1797
+ }
1798
+
1799
+ data.state.isProvisioningHalted = true;
1800
+ data.result.monitorProvisioningHalted = true;
1801
+
1802
+ logger.error(
1803
+ `Auto-import: ${MAX_CONSECUTIVE_MONITOR_CREATE_FAILURES} Network Device monitor creates failed identically for project ${data.projectId.toString()} with none succeeding ("${reason}"). Treating this as a systemic failure and provisioning no further monitors this run.`,
1804
+ { projectId: data.projectId.toString() } as LogAttributes,
1805
+ );
1499
1806
  }
1500
1807
 
1501
1808
  /*
@@ -1580,6 +1887,7 @@ class NetworkDeviceAutoImportRuleEngineServiceClass {
1580
1887
  createdDevice = await attemptCreate(buildFallbackDeviceName(data.host));
1581
1888
  } catch (secondError) {
1582
1889
  data.result.devicesFailed++;
1890
+ this.recordFailureReason(data.result.deviceFailureReasons, secondError);
1583
1891
  logger.error(
1584
1892
  `Auto-import: could not create a device for ${data.host.ipAddress} (scan ${data.scan.id?.toString()}): ${firstError} / retry: ${secondError}`,
1585
1893
  { projectId: data.projectId.toString() } as LogAttributes,