@oneuptime/common 12.0.9 → 12.0.11

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 (262) hide show
  1. package/Models/DatabaseModels/NetworkDevice.ts +44 -0
  2. package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
  3. package/Models/DatabaseModels/RunnerJob.ts +17 -2
  4. package/Server/API/LlmProviderAPI.ts +8 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
  8. package/Server/Services/NetworkDeviceLinkService.ts +338 -2
  9. package/Server/Services/NetworkDeviceService.ts +265 -12
  10. package/Server/Services/RunnerJobService.ts +24 -1
  11. package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
  12. package/Server/Types/Database/JSONColumnQuery.ts +42 -4
  13. package/Server/Utils/LLM/LLMService.ts +108 -19
  14. package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
  15. package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
  16. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
  17. package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
  18. package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
  19. package/Server/Utils/SSRFProtection.ts +5 -4
  20. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
  21. package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
  22. package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
  23. package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
  24. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
  25. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
  26. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
  27. package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
  28. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
  29. package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
  30. package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
  31. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
  32. package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
  33. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
  34. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
  35. package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
  36. package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
  37. package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
  38. package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
  39. package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
  40. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
  41. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
  42. package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
  43. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
  44. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
  45. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
  46. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
  47. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
  48. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
  49. package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
  50. package/Tests/Types/API/URLTelScheme.test.ts +261 -0
  51. package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
  52. package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
  53. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
  54. package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
  55. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
  56. package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
  57. package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
  58. package/Tests/UI/Components/Button.test.tsx +279 -0
  59. package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
  60. package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
  61. package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
  62. package/Tests/UI/Components/Icon.test.tsx +57 -0
  63. package/Tests/UI/Components/List.test.tsx +20 -3
  64. package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
  65. package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
  66. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
  67. package/Tests/UI/Components/Pagination.test.tsx +721 -146
  68. package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
  69. package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
  70. package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
  71. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
  72. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
  73. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
  74. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
  75. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
  76. package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
  77. package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
  78. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
  79. package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
  80. package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
  81. package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
  82. package/Types/API/Protocol.ts +9 -0
  83. package/Types/API/URL.ts +286 -20
  84. package/Types/Billing/PayAsYouGoPricing.ts +47 -0
  85. package/Types/CustomField/CustomFieldType.ts +28 -0
  86. package/Types/Monitor/MonitorType.ts +11 -0
  87. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  88. package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
  89. package/Types/Runbook/RunbookStepType.ts +23 -0
  90. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
  91. package/UI/Components/Button/Button.tsx +14 -22
  92. package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
  93. package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
  94. package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
  95. package/UI/Components/Charts/Line/LineChart.tsx +4 -2
  96. package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
  97. package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
  98. package/UI/Components/Dictionary/Dictionary.tsx +4 -39
  99. package/UI/Components/Icon/Icon.tsx +1 -1
  100. package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
  101. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
  102. package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
  103. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
  104. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
  105. package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
  106. package/UI/Components/Pagination/Pagination.tsx +373 -328
  107. package/UI/Components/Pagination/PaginationUtil.ts +263 -0
  108. package/UI/Components/Pagination/Screenshots/README.md +15 -0
  109. package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
  110. package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
  111. package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
  112. package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
  113. package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
  114. package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
  115. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
  116. package/UI/Components/TextArea/TextArea.tsx +3 -0
  117. package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
  118. package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
  119. package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
  120. package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
  121. package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
  122. package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
  123. package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
  124. package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
  125. package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
  126. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
  127. package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
  128. package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
  129. package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
  130. package/Utils/API.ts +243 -36
  131. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
  132. package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
  133. package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
  134. package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
  135. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
  137. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
  139. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  140. package/build/dist/Server/API/LlmProviderAPI.js +9 -1
  141. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  146. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
  147. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  148. package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
  149. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
  150. package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
  151. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  152. package/build/dist/Server/Services/RunnerJobService.js +20 -2
  153. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  154. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
  155. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
  156. package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
  157. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
  158. package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
  159. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  160. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
  161. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  162. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
  163. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  164. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
  165. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  166. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
  167. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  168. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
  169. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
  170. package/build/dist/Server/Utils/SSRFProtection.js +5 -4
  171. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  172. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
  173. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  174. package/build/dist/Types/API/Protocol.js +9 -0
  175. package/build/dist/Types/API/Protocol.js.map +1 -1
  176. package/build/dist/Types/API/URL.js +241 -18
  177. package/build/dist/Types/API/URL.js.map +1 -1
  178. package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
  179. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
  180. package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
  181. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  182. package/build/dist/Types/Monitor/MonitorType.js +10 -0
  183. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  184. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
  185. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
  186. package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
  187. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  188. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  189. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  190. package/build/dist/UI/Components/Button/Button.js +10 -16
  191. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
  193. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
  195. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  196. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
  197. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
  198. package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
  199. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  200. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
  201. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  202. package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
  203. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  204. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  205. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  206. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
  207. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  208. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
  209. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  210. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
  211. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
  212. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  213. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  214. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
  215. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  216. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
  217. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  218. package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
  219. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  220. package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
  221. package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
  222. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
  223. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
  224. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
  225. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
  226. package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
  227. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  228. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
  229. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  230. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
  231. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
  232. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
  233. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
  234. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
  235. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
  236. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
  237. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
  238. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
  239. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
  240. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
  241. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
  242. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
  243. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
  244. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
  245. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
  246. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
  247. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
  248. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
  249. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
  250. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
  251. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  252. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
  253. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
  254. package/build/dist/Utils/API.js +175 -34
  255. package/build/dist/Utils/API.js.map +1 -1
  256. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
  257. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
  258. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
  259. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  260. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
  261. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
  262. package/package.json +1 -1
@@ -353,8 +353,8 @@ function defineInventorySource<TModel extends BaseModel>(
353
353
 
354
354
  /*
355
355
  * Archived rows are excluded from every source that has the concept: the
356
- * explorer is a view of the live estate. IoTDevice and NetworkDevice have no
357
- * archive flag, so their queries are unrestricted.
356
+ * explorer is a view of the live estate. IoTDevice is the only source without
357
+ * an archive flag, so it is the only unrestricted query.
358
358
  */
359
359
  export const INVENTORY_SOURCES: ReadonlyArray<ErasedInventorySource> = [
360
360
  defineInventorySource<NetworkDevice>({
@@ -362,7 +362,7 @@ export const INVENTORY_SOURCES: ReadonlyArray<ErasedInventorySource> = [
362
362
  resourceType: "NetworkDevice",
363
363
  service: NetworkDeviceService,
364
364
  select: { hostname: true },
365
- query: {},
365
+ query: { isArchived: false },
366
366
  describe: (row: NetworkDevice): Dictionary<string> => {
367
367
  return compactAttributes({ "net.device.hostname": row.hostname });
368
368
  },
@@ -469,6 +469,12 @@ export interface InventorySyncResult {
469
469
  created: number;
470
470
  updated: number;
471
471
  deleted: number;
472
+ /**
473
+ * Orphans that were archived rather than deleted because they carried custom
474
+ * field values. Counted apart from `deleted` so the job log distinguishes
475
+ * "the projection went away" from "we kept someone's data".
476
+ */
477
+ archived: number;
472
478
  }
473
479
 
474
480
  /**
@@ -579,15 +585,133 @@ async function mirrorRows(
579
585
  }
580
586
 
581
587
  /**
582
- * Reverse pass: a mirrored row whose owning inventory row is gone is
583
- * deleted. This is what catches cascade deletes, which fire no service hook.
588
+ * Does this row carry anything a person typed?
589
+ *
590
+ * The `customFields` bag is the only user-authored content a mirrored row can
591
+ * hold — every other column on it is rewritten from the owning table on each
592
+ * pass — so it is the whole test for "deleting this loses work".
593
+ *
594
+ * An empty bag counts as nothing: the UI leaves `{}` behind when the last
595
+ * value is cleared, and a row whose fields were all emptied is not one anyone
596
+ * is keeping. A key explicitly set to null or "" is likewise not a value.
597
+ */
598
+ export type HasCustomFieldValuesFunction = (row: InventoryItem) => boolean;
599
+
600
+ export const hasCustomFieldValues: HasCustomFieldValuesFunction = (
601
+ row: InventoryItem,
602
+ ): boolean => {
603
+ const bag: JSONObject | undefined = row.customFields;
604
+
605
+ if (!bag || typeof bag !== "object") {
606
+ return false;
607
+ }
608
+
609
+ return Object.keys(bag).some((key: string) => {
610
+ const value: unknown = (bag as JSONObject)[key];
611
+
612
+ if (value === undefined || value === null || value === "") {
613
+ return false;
614
+ }
615
+
616
+ return !(Array.isArray(value) && value.length === 0);
617
+ });
618
+ };
619
+
620
+ export interface OrphanPartition {
621
+ /** Orphans safe to remove outright — pure projections, nothing typed. */
622
+ deletableIds: Array<ObjectID>;
623
+ /** Orphans that must be kept, because they carry user-authored values. */
624
+ archivableIds: Array<ObjectID>;
625
+ }
626
+
627
+ export type PartitionOrphanRowsFunction = (data: {
628
+ rows: Array<InventoryItem>;
629
+ /** Ids still present in the owning table, from `findLiveIds`. */
630
+ liveResourceIds: Set<string>;
631
+ }) => OrphanPartition;
632
+
633
+ /**
634
+ * The whole decision the reverse pass makes, as a pure function of one page of
635
+ * registry rows and the set of owning-table ids that still exist.
636
+ *
637
+ * Split out from the IO because this is where a mistake destroys data and
638
+ * where a test can actually reach: getting it wrong either hard-deletes rows
639
+ * holding the only copy of someone's serial numbers, or lets the registry
640
+ * accumulate projections of devices that no longer exist.
641
+ */
642
+ export const partitionOrphanRows: PartitionOrphanRowsFunction = (data: {
643
+ rows: Array<InventoryItem>;
644
+ liveResourceIds: Set<string>;
645
+ }): OrphanPartition => {
646
+ const deletableIds: Array<ObjectID> = [];
647
+ const archivableIds: Array<ObjectID> = [];
648
+
649
+ for (const row of data.rows || []) {
650
+ if (!row.id) {
651
+ continue;
652
+ }
653
+
654
+ /*
655
+ * A mirrored row with no pointer cannot be matched back to anything, so
656
+ * it is orphaned by definition.
657
+ */
658
+ const isOrphan: boolean =
659
+ !row.resourceId || !data.liveResourceIds.has(row.resourceId.toString());
660
+
661
+ if (!isOrphan) {
662
+ continue;
663
+ }
664
+
665
+ if (!hasCustomFieldValues(row)) {
666
+ deletableIds.push(row.id);
667
+ continue;
668
+ }
669
+
670
+ /*
671
+ * Already archived on an earlier sweep. Re-archiving would rewrite
672
+ * `archivedAt` on every 15-minute pass, so the timestamp would report the
673
+ * most recent sweep rather than when the device went away.
674
+ */
675
+ if (row.isArchived) {
676
+ continue;
677
+ }
678
+
679
+ archivableIds.push(row.id);
680
+ }
681
+
682
+ return { deletableIds, archivableIds };
683
+ };
684
+
685
+ /**
686
+ * Reverse pass: a mirrored row whose owning inventory row is gone stops being
687
+ * part of the live estate. This is what catches cascade deletes, which fire no
688
+ * service hook.
689
+ *
690
+ * Orphans split two ways, on whether the row holds anything a person typed:
691
+ *
692
+ * - nothing typed: hard-deleted, as before. The row was a pure projection of
693
+ * a table row that no longer exists, so there is nothing to keep.
694
+ *
695
+ * - custom field values present: ARCHIVED, not deleted. Those values are the
696
+ * only copy — serial numbers, warranty dates, owning team — and they live
697
+ * on the registry row rather than on the device, so deleting the projection
698
+ * destroys them with no undo and no audit trail (`hardDeleteBy` bypasses
699
+ * the audit hook). Archiving takes the row out of the default list, which
700
+ * is the visible behaviour the delete was there to produce, and leaves the
701
+ * data recoverable from the Archived tab.
702
+ *
703
+ * The cost of being wrong differs by orders of magnitude between the two, which
704
+ * is why the split is here and not left to an operator's retention policy.
584
705
  */
585
- async function removeOrphans(source: ErasedInventorySource): Promise<number> {
586
- const orphanIds: Array<ObjectID> = [];
706
+ async function removeOrphans(
707
+ source: ErasedInventorySource,
708
+ ): Promise<{ deleted: number; archived: number }> {
709
+ const deletableIds: Array<ObjectID> = [];
710
+ const archivableIds: Array<ObjectID> = [];
587
711
  let skip: number = 0;
588
712
 
589
713
  /*
590
- * Collected across a read-only pass and deleted afterwards. Deleting while
714
+ * Collected across a read-only pass and written afterwards. Writing while
591
715
  * paging would shift subsequent offsets and skip rows.
592
716
  */
593
717
  for (;;) {
@@ -596,7 +720,12 @@ async function removeOrphans(source: ErasedInventorySource): Promise<number> {
596
720
  entityType: source.entityType,
597
721
  source: EntitySource.Inventory,
598
722
  },
599
- select: { _id: true, resourceId: true },
723
+ select: {
724
+ _id: true,
725
+ resourceId: true,
726
+ customFields: true,
727
+ isArchived: true,
728
+ },
600
729
  skip,
601
730
  limit: INVENTORY_SYNC_PAGE_SIZE,
602
731
  props: { isRoot: true },
@@ -614,16 +743,13 @@ async function removeOrphans(source: ErasedInventorySource): Promise<number> {
614
743
  }
615
744
 
616
745
  const liveIds: Set<string> = await source.findLiveIds(resourceIds);
746
+ const partition: OrphanPartition = partitionOrphanRows({
747
+ rows: rows,
748
+ liveResourceIds: liveIds,
749
+ });
617
750
 
618
- for (const row of rows) {
619
- /*
620
- * A mirrored row with no pointer cannot be matched back to anything,
621
- * so it is orphaned by definition.
622
- */
623
- if (!row.resourceId || !liveIds.has(row.resourceId.toString())) {
624
- orphanIds.push(row.id!);
625
- }
626
- }
751
+ deletableIds.push(...partition.deletableIds);
752
+ archivableIds.push(...partition.archivableIds);
627
753
 
628
754
  skip += rows.length;
629
755
 
@@ -632,26 +758,42 @@ async function removeOrphans(source: ErasedInventorySource): Promise<number> {
632
758
  }
633
759
  }
634
760
 
635
- if (orphanIds.length === 0) {
636
- return 0;
761
+ if (deletableIds.length > 0) {
762
+ await InventoryItemService.hardDeleteBy({
763
+ query: {
764
+ _id: QueryHelper.any(
765
+ deletableIds.map((id: ObjectID) => {
766
+ return id.toString();
767
+ }),
768
+ ),
769
+ // Re-asserted so a bug upstream can never widen this into a broad delete.
770
+ source: EntitySource.Inventory,
771
+ },
772
+ limit: deletableIds.length,
773
+ skip: 0,
774
+ props: { isRoot: true },
775
+ });
637
776
  }
638
777
 
639
- await InventoryItemService.hardDeleteBy({
640
- query: {
641
- _id: QueryHelper.any(
642
- orphanIds.map((id: ObjectID) => {
643
- return id.toString();
644
- }),
645
- ),
646
- // Re-asserted so a bug upstream can never widen this into a broad delete.
647
- source: EntitySource.Inventory,
648
- },
649
- limit: orphanIds.length,
650
- skip: 0,
651
- props: { isRoot: true },
652
- });
778
+ if (archivableIds.length > 0) {
779
+ await InventoryItemService.updateBy({
780
+ query: {
781
+ _id: QueryHelper.any(
782
+ archivableIds.map((id: ObjectID) => {
783
+ return id.toString();
784
+ }),
785
+ ),
786
+ // Same reason as the delete above: never widen past mirrored rows.
787
+ source: EntitySource.Inventory,
788
+ },
789
+ data: { isArchived: true },
790
+ limit: archivableIds.length,
791
+ skip: 0,
792
+ props: { isRoot: true },
793
+ });
794
+ }
653
795
 
654
- return orphanIds.length;
796
+ return { deleted: deletableIds.length, archived: archivableIds.length };
655
797
  }
656
798
 
657
799
  /** Reconciles one inventory table into the registry. */
@@ -659,8 +801,8 @@ export async function syncInventorySource(
659
801
  source: ErasedInventorySource,
660
802
  ): Promise<InventorySyncResult> {
661
803
  const { created, updated } = await mirrorRows(source);
662
- const deleted: number = await removeOrphans(source);
663
- return { created, updated, deleted };
804
+ const { deleted, archived } = await removeOrphans(source);
805
+ return { created, updated, deleted, archived };
664
806
  }
665
807
 
666
808
  /**
@@ -669,7 +811,12 @@ export async function syncInventorySource(
669
811
  * should not stop network devices being mirrored.
670
812
  */
671
813
  export async function syncAllInventorySources(): Promise<InventorySyncResult> {
672
- const total: InventorySyncResult = { created: 0, updated: 0, deleted: 0 };
814
+ const total: InventorySyncResult = {
815
+ created: 0,
816
+ updated: 0,
817
+ deleted: 0,
818
+ archived: 0,
819
+ };
673
820
 
674
821
  for (const source of INVENTORY_SOURCES) {
675
822
  try {
@@ -677,6 +824,7 @@ export async function syncAllInventorySources(): Promise<InventorySyncResult> {
677
824
  total.created += result.created;
678
825
  total.updated += result.updated;
679
826
  total.deleted += result.deleted;
827
+ total.archived += result.archived;
680
828
  } catch (err) {
681
829
  logger.error(
682
830
  `InventoryEntityRegistry: sync failed for ${source.resourceType}:`,