@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
@@ -0,0 +1,122 @@
1
+ import URL from "../../../Types/API/URL";
2
+ import Dictionary from "../../../Types/Dictionary";
3
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
4
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
5
+ import { JSONObject } from "../../../Types/JSON";
6
+ import { RuleRunResultUtil } from "../../../Types/NetworkAutomation/RuleRunResult";
7
+ import RuleRunSummary from "../../../Utils/NetworkAutomation/RuleRunSummary";
8
+ import { APP_API_URL } from "../../Config";
9
+ import API from "../API/API";
10
+
11
+ /*
12
+ * Client for the two Network Automation "Run now" endpoints.
13
+ *
14
+ * Both rule kinds fire automatically only when a device is created (and, for
15
+ * site assignment, when its identity changes or on the next poll of a device
16
+ * with no site), so a rule written after an estate was imported never reaches
17
+ * it. These endpoints apply one rule to the devices that already exist —
18
+ * OneUptime/oneuptime#3191.
19
+ *
20
+ * The two differ only in the route they post to and in whether the run can
21
+ * overwrite something a person chose, so they share one client rather than
22
+ * two that drift.
23
+ */
24
+
25
+ export enum NetworkRuleKind {
26
+ SiteAssignment = "SiteAssignment",
27
+ DeviceLabel = "DeviceLabel",
28
+ }
29
+
30
+ const RULE_RUN_ROUTE: Record<NetworkRuleKind, string> = {
31
+ [NetworkRuleKind.SiteAssignment]: "/network-site-assignment-rule",
32
+ [NetworkRuleKind.DeviceLabel]: "/network-device-label-rule",
33
+ };
34
+
35
+ /*
36
+ * What the caller shows. A failed run is not an exception here: the modal
37
+ * renders the same sentence either way, only in the error slot.
38
+ */
39
+ export interface NetworkRuleRunOutcome {
40
+ isSuccess: boolean;
41
+ message: string;
42
+ }
43
+
44
+ export default class RunNetworkRule {
45
+ public static getRunRoute(data: {
46
+ ruleKind: NetworkRuleKind;
47
+ ruleId: string;
48
+ }): string {
49
+ return `${RULE_RUN_ROUTE[data.ruleKind]}/${data.ruleId}/run`;
50
+ }
51
+
52
+ public static async run(data: {
53
+ ruleKind: NetworkRuleKind;
54
+ ruleId: string;
55
+ /*
56
+ * Site assignment only, and only ever sent as a real boolean — the
57
+ * endpoint accepts nothing else, so a run cannot move devices somebody
58
+ * placed by hand through a stray truthy value.
59
+ */
60
+ reassignDevicesAlreadyInASite?: boolean | undefined;
61
+ headers?: Dictionary<string> | undefined;
62
+ }): Promise<NetworkRuleRunOutcome> {
63
+ /*
64
+ * Without this the request would go to `/…//run`, which matches no route
65
+ * and comes back as an unhelpful 404 rather than as the real problem.
66
+ */
67
+ if (!data.ruleId) {
68
+ return {
69
+ isSuccess: false,
70
+ message: "This rule has no id, so it cannot be run.",
71
+ };
72
+ }
73
+
74
+ const isSiteAssignment: boolean =
75
+ data.ruleKind === NetworkRuleKind.SiteAssignment;
76
+
77
+ try {
78
+ const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
79
+ await API.post<JSONObject>({
80
+ url: URL.fromString(APP_API_URL.toString()).addRoute(
81
+ RunNetworkRule.getRunRoute({
82
+ ruleKind: data.ruleKind,
83
+ ruleId: data.ruleId,
84
+ }),
85
+ ),
86
+ data: isSiteAssignment
87
+ ? {
88
+ reassignDevicesAlreadyInASite: Boolean(
89
+ data.reassignDevicesAlreadyInASite,
90
+ ),
91
+ }
92
+ : {},
93
+ ...(data.headers ? { headers: data.headers } : {}),
94
+ });
95
+
96
+ if (response.isFailure()) {
97
+ return {
98
+ isSuccess: false,
99
+ message: API.getFriendlyMessage(response),
100
+ };
101
+ }
102
+
103
+ const body: JSONObject = (response.data || {}) as JSONObject;
104
+
105
+ return {
106
+ isSuccess: true,
107
+ message: isSiteAssignment
108
+ ? RuleRunSummary.describeSiteAssignmentRun(
109
+ RuleRunResultUtil.parseSiteAssignmentRuleRunResult(body),
110
+ )
111
+ : RuleRunSummary.describeLabelRun(
112
+ RuleRunResultUtil.parseLabelRuleRunResult(body),
113
+ ),
114
+ };
115
+ } catch (err) {
116
+ return {
117
+ isSuccess: false,
118
+ message: API.getFriendlyMessage(err),
119
+ };
120
+ }
121
+ }
122
+ }
package/Utils/API.ts CHANGED
@@ -51,6 +51,28 @@ export interface RequestOutcome {
51
51
  export interface RequestOptions {
52
52
  retries?: number | undefined;
53
53
  exponentialBackoff?: boolean | undefined;
54
+ /*
55
+ * Ceiling on any single backoff sleep. Doubling is unbounded by
56
+ * definition, so without a cap a double-digit retry budget spends nearly
57
+ * all of its wall clock asleep — the tenth wait alone is over seventeen
58
+ * minutes. Defaults to DEFAULT_MAX_BACKOFF_IN_MS, which is far above what
59
+ * a handful of retries can reach, so short retry budgets are unaffected.
60
+ */
61
+ maxBackoffInMs?: number | undefined;
62
+ /*
63
+ * Spend retries only on failures that repeating could plausibly fix —
64
+ * see isRetryableError. Off by default: callers that have always retried
65
+ * every error keep doing so until they opt in.
66
+ */
67
+ retryOnlyOnRetryableErrors?: boolean | undefined;
68
+ /*
69
+ * Wall-clock budget for the whole call, every attempt and backoff
70
+ * included. No attempt is started that the budget cannot pay for, so N
71
+ * retries of a request that times out can no longer take N x timeout and
72
+ * outlive whatever is waiting on the answer. It does not abort an attempt
73
+ * already in flight — the per-attempt `timeout` is what bounds that.
74
+ */
75
+ totalTimeoutInMs?: number | undefined;
54
76
  timeout?: number | undefined;
55
77
  doNotFollowRedirects?: boolean | undefined;
56
78
  // Per-request proxy agent support (Probe supplies these instead of mutating global axios defaults)
@@ -97,6 +119,25 @@ export interface AuthRetryContext {
97
119
  }
98
120
 
99
121
  export default class API {
122
+ /*
123
+ * Backstop ceiling for a single backoff sleep when the caller names none.
124
+ * Set well above what a small retry budget can reach (three retries top out
125
+ * at eight seconds), so it only ever bites on the long budgets that need it.
126
+ */
127
+ public static readonly DEFAULT_MAX_BACKOFF_IN_MS: number = 60 * 1000;
128
+
129
+ /*
130
+ * 4xx responses are the server saying the request itself is wrong, and
131
+ * resending it byte-for-byte gets the same answer — except for these three,
132
+ * which say "not now" rather than "not ever".
133
+ */
134
+ private static readonly RETRYABLE_CLIENT_ERROR_STATUS_CODES: Set<number> =
135
+ new Set([
136
+ 408, // Request Timeout
137
+ 425, // Too Early
138
+ 429, // Too Many Requests
139
+ ]);
140
+
100
141
  private _protocol: Protocol = Protocol.HTTPS;
101
142
  public get protocol(): Protocol {
102
143
  return this._protocol;
@@ -417,57 +458,88 @@ export default class API {
417
458
  finalBody = new URLSearchParams(data as Dictionary<string>);
418
459
  }
419
460
 
420
- let currentRetry: number = 0;
421
- const maxRetries: number = options?.retries || 0;
461
+ /*
462
+ * Negative budgets are clamped rather than trusted: `while (0 <= -1)`
463
+ * used to skip the request entirely and report the misleading "No
464
+ * response received from server" for a request never sent.
465
+ */
466
+ const maxRetries: number = Math.max(0, options?.retries || 0);
422
467
  const exponentialBackoff: boolean = options?.exponentialBackoff || false;
423
468
 
424
- let result: AxiosResponse | null = null;
469
+ // Identical on every attempt, so build it once.
470
+ const axiosOptions: AxiosRequestConfig = {
471
+ method: method,
472
+ url: url.toString(),
473
+ headers: finalHeaders,
474
+ data: finalBody,
475
+ };
425
476
 
426
- while (currentRetry <= maxRetries) {
427
- currentRetry++;
428
- attempts++;
429
- try {
430
- const axiosOptions: AxiosRequestConfig = {
431
- method: method,
432
- url: url.toString(),
433
- headers: finalHeaders,
434
- data: finalBody,
435
- };
477
+ if (options?.timeout) {
478
+ axiosOptions.timeout = options.timeout;
479
+ }
436
480
 
437
- if (options?.timeout) {
438
- axiosOptions.timeout = options.timeout;
439
- }
481
+ if (options?.doNotFollowRedirects) {
482
+ axiosOptions.maxRedirects = 0;
483
+ }
440
484
 
441
- if (options?.doNotFollowRedirects) {
442
- axiosOptions.maxRedirects = 0;
443
- }
485
+ // Attach proxy agents per request if provided (avoids global side-effects)
486
+ if (options?.httpAgent) {
487
+ axiosOptions.httpAgent = options.httpAgent;
488
+ }
489
+ if (options?.httpsAgent) {
490
+ axiosOptions.httpsAgent = options.httpsAgent;
491
+ }
444
492
 
445
- // Attach proxy agents per request if provided (avoids global side-effects)
446
- if (options?.httpAgent) {
447
- (axiosOptions as AxiosRequestConfig).httpAgent = options.httpAgent;
448
- }
449
- if (options?.httpsAgent) {
450
- (axiosOptions as AxiosRequestConfig).httpsAgent =
451
- options.httpsAgent;
452
- }
493
+ if (options?.onUploadProgress) {
494
+ axiosOptions.onUploadProgress = options.onUploadProgress;
495
+ }
453
496
 
454
- if (options?.onUploadProgress) {
455
- axiosOptions.onUploadProgress = options.onUploadProgress;
456
- }
497
+ let result: AxiosResponse | null = null;
457
498
 
499
+ for (let attempt: number = 0; attempt <= maxRetries; attempt++) {
500
+ attempts++;
501
+
502
+ try {
458
503
  result = await axios(axiosOptions);
459
504
 
460
505
  break;
461
506
  } catch (e) {
462
- if (currentRetry <= maxRetries) {
463
- if (exponentialBackoff) {
464
- await Sleep.sleep(2 ** currentRetry * 1000);
465
- }
507
+ if (attempt >= maxRetries) {
508
+ throw e;
509
+ }
510
+
511
+ /*
512
+ * A rejected request stays rejected. Failing now surfaces the
513
+ * provider's own explanation immediately instead of after a full
514
+ * ladder of backoffs that cannot change the answer.
515
+ */
516
+ if (options?.retryOnlyOnRetryableErrors && !API.isRetryableError(e)) {
517
+ throw e;
518
+ }
466
519
 
467
- continue;
468
- } else {
520
+ const delayInMs: number = API.getRetryDelayInMs({
521
+ attempt: attempt,
522
+ error: e,
523
+ exponentialBackoff: exponentialBackoff,
524
+ maxBackoffInMs: options?.maxBackoffInMs,
525
+ });
526
+
527
+ /*
528
+ * Check the budget against the point the next attempt would START,
529
+ * not the point we are at now: sleeping out the remainder and then
530
+ * giving up would waste the wait for nothing.
531
+ */
532
+ if (
533
+ options?.totalTimeoutInMs !== undefined &&
534
+ Date.now() - requestStartedAtInMs + delayInMs >=
535
+ options.totalTimeoutInMs
536
+ ) {
469
537
  throw e;
470
538
  }
539
+
540
+ if (delayInMs > 0) {
541
+ await Sleep.sleep(delayInMs);
542
+ }
471
543
  }
472
544
  }
473
545
 
@@ -620,6 +692,141 @@ export default class API {
620
692
  }
621
693
  }
622
694
 
695
+ /**
696
+ * Is repeating this request worth an attempt?
697
+ *
698
+ * Retries exist for failures the network or the server may not repeat: a
699
+ * dropped connection, a timeout, an overloaded backend, a rate limit that
700
+ * lapses. Everything else — a malformed body, a bad key, an unknown route —
701
+ * fails identically every time, and retrying it only delays the error the
702
+ * operator needs to read.
703
+ */
704
+ public static isRetryableError(error: unknown): boolean {
705
+ if (!axios.isAxiosError(error)) {
706
+ /*
707
+ * Not a transport failure at all: the request could not even be built,
708
+ * or an interceptor threw. Nothing about resending changes that.
709
+ */
710
+ return false;
711
+ }
712
+
713
+ const axiosError: AxiosError = error;
714
+
715
+ // The caller pulled the plug on purpose.
716
+ if (axiosError.code === "ERR_CANCELED") {
717
+ return false;
718
+ }
719
+
720
+ const statusCode: number | undefined = axiosError.response?.status;
721
+
722
+ /*
723
+ * No response at all — timeout, DNS failure, refused or reset connection.
724
+ * These are exactly what a retry is for.
725
+ */
726
+ if (statusCode === undefined) {
727
+ return true;
728
+ }
729
+
730
+ // The server broke, not the request.
731
+ if (statusCode >= 500) {
732
+ return true;
733
+ }
734
+
735
+ return API.RETRYABLE_CLIENT_ERROR_STATUS_CODES.has(statusCode);
736
+ }
737
+
738
+ /**
739
+ * How long to wait before the attempt that follows `attempt` (0-based).
740
+ */
741
+ private static getRetryDelayInMs(data: {
742
+ attempt: number;
743
+ error: unknown;
744
+ exponentialBackoff: boolean;
745
+ maxBackoffInMs?: number | undefined;
746
+ }): number {
747
+ const ceilingInMs: number =
748
+ data.maxBackoffInMs ?? API.DEFAULT_MAX_BACKOFF_IN_MS;
749
+
750
+ /*
751
+ * A server that says when to come back knows better than any formula we
752
+ * could pick — coming back early just spends another attempt on the same
753
+ * 429. Still capped: the caller's budget is what bounds the total wait,
754
+ * and an unbounded Retry-After would hand that control to the server.
755
+ */
756
+ const retryAfterInMs: number | undefined = API.getRetryAfterInMs(
757
+ data.error,
758
+ );
759
+
760
+ if (retryAfterInMs !== undefined) {
761
+ return Math.min(retryAfterInMs, ceilingInMs);
762
+ }
763
+
764
+ if (!data.exponentialBackoff) {
765
+ return 0;
766
+ }
767
+
768
+ const backoffInMs: number = Math.min(
769
+ 2 ** (data.attempt + 1) * 1000,
770
+ ceilingInMs,
771
+ );
772
+
773
+ /*
774
+ * Equal jitter. Everything that failed together — every chat turn behind
775
+ * one provider outage, every probe behind one flapping host — otherwise
776
+ * comes back at the very same millisecond and knocks the recovering
777
+ * server straight back over. Half the delay stays fixed so a retry is
778
+ * never effectively immediate, and half is spread at random.
779
+ */
780
+ return Math.round(backoffInMs / 2 + Math.random() * (backoffInMs / 2));
781
+ }
782
+
783
+ /**
784
+ * Read a Retry-After header off a failed response. Returns undefined unless
785
+ * the header is present and names a sane, non-negative wait.
786
+ */
787
+ private static getRetryAfterInMs(error: unknown): number | undefined {
788
+ if (!axios.isAxiosError(error)) {
789
+ return undefined;
790
+ }
791
+
792
+ const headers: unknown = error.response?.headers;
793
+
794
+ if (!headers || typeof headers !== "object") {
795
+ return undefined;
796
+ }
797
+
798
+ const rawHeader: unknown =
799
+ (headers as Dictionary<unknown>)["retry-after"] ??
800
+ (headers as Dictionary<unknown>)["Retry-After"];
801
+
802
+ if (rawHeader === undefined || rawHeader === null) {
803
+ return undefined;
804
+ }
805
+
806
+ const headerValue: string = String(rawHeader).trim();
807
+
808
+ if (!headerValue) {
809
+ return undefined;
810
+ }
811
+
812
+ // Form 1: delta-seconds.
813
+ const deltaSecondsRegex: RegExp = /^\d+$/;
814
+
815
+ if (deltaSecondsRegex.test(headerValue)) {
816
+ return Number(headerValue) * 1000;
817
+ }
818
+
819
+ // Form 2: an HTTP-date to wait until.
820
+ const retryAtInMs: number = Date.parse(headerValue);
821
+
822
+ if (Number.isNaN(retryAtInMs)) {
823
+ return undefined;
824
+ }
825
+
826
+ // A date already in the past means "retry now", not "retry in the past".
827
+ return Math.max(0, retryAtInMs - Date.now());
828
+ }
829
+
623
830
  private static getErrorResponse(error: AxiosError): HTTPErrorResponse {
624
831
  if (error.response) {
625
832
  return new HTTPErrorResponse(
@@ -531,3 +531,48 @@ export function labelForDeviceRole(
531
531
  }
532
532
  return DEVICE_ROLE_LABELS[role] || DEVICE_ROLE_LABELS.unknown;
533
533
  }
534
+
535
+ /**
536
+ * Read an operator's stored role override off a NetworkDevice row.
537
+ *
538
+ * The classifier above is evidence-driven, and its evidence is SNMP: a
539
+ * device nothing walks — a ping-only device imported by discovery, say —
540
+ * offers it a hostname and nothing else, so it comes back "unknown" and
541
+ * stays there forever. `NetworkDevice.deviceRole` is the operator saying
542
+ * what the box actually is, and it is the only statement about a role that
543
+ * is not an inference, so it outranks everything the classifier can find.
544
+ *
545
+ * Undefined means "no override" — NOT "unknown". The two are different
546
+ * answers and the difference decides whether the classifier gets to run at
547
+ * all, which is why an unrecognised or empty column reads as undefined
548
+ * rather than falling back to a role. "unknown" is itself refused as an
549
+ * override for the same reason: storing it would silently disable the
550
+ * classifier on a device the operator was only declining to classify.
551
+ */
552
+ export function parseDeviceRoleOverride(
553
+ value: string | undefined | null,
554
+ ): NetworkTopologyDeviceRole | undefined {
555
+ const normalized: string = (value || "").trim().toLowerCase();
556
+ if (!normalized) {
557
+ return undefined;
558
+ }
559
+ return DEVICE_ROLES_IN_LEGEND_ORDER.find(
560
+ (role: NetworkTopologyDeviceRole) => {
561
+ return role.toLowerCase() === normalized;
562
+ },
563
+ );
564
+ }
565
+
566
+ /**
567
+ * The role to draw a managed device with: the operator's override when
568
+ * there is one, otherwise whatever the evidence supports.
569
+ *
570
+ * One function so the topology builder, the forms and the tests cannot
571
+ * drift about which of the two wins.
572
+ */
573
+ export function resolveDeviceRole(
574
+ roleOverride: string | undefined | null,
575
+ signals: DeviceRoleSignals,
576
+ ): NetworkTopologyDeviceRole {
577
+ return parseDeviceRoleOverride(roleOverride) ?? classifyDeviceRole(signals);
578
+ }
@@ -11,6 +11,7 @@ import { normalizeMac } from "./EndpointAttachmentUtil";
11
11
  import {
12
12
  classifyDeviceRole,
13
13
  classifyEndpointRole,
14
+ resolveDeviceRole,
14
15
  } from "./NetworkDeviceRoleUtil";
15
16
 
16
17
  export interface TopologyDeviceInput {
@@ -29,6 +30,14 @@ export interface TopologyDeviceInput {
29
30
  */
30
31
  sysDescr?: string | undefined;
31
32
  sysObjectId?: string | undefined;
33
+ /*
34
+ * The operator's own answer to what this device is, when they gave one.
35
+ * Outranks the classifier below, because it is the only statement about
36
+ * the role that is not an inference — and on a device nothing walks it
37
+ * is the ONLY statement available at all, the classifier having nothing
38
+ * but a hostname to go on.
39
+ */
40
+ deviceRole?: string | undefined;
32
41
  /*
33
42
  * ENTITY-MIB chassis serial. Not rendered — it is here because LLDP
34
43
  * chassis-id subtype 7 ("locally assigned") is very often exactly this
@@ -109,6 +118,13 @@ export interface TopologyManualLinkInput {
109
118
  toPortName?: string | undefined;
110
119
  // Health from a bound Monitor, already reduced by the caller.
111
120
  monitorStatus?: "up" | "down" | undefined;
121
+ /*
122
+ * Whichever end the operator declared the parent, when they declared
123
+ * one. Must be `fromDeviceId` or `toDeviceId`; anything else is dropped
124
+ * rather than drawn, because a parent that is not on the link cannot be
125
+ * rendered as one and a wrong hierarchy is worse than an inferred one.
126
+ */
127
+ parentDeviceId?: string | undefined;
112
128
  }
113
129
 
114
130
  /*
@@ -232,7 +248,10 @@ export default class NetworkTopologyUtil {
232
248
  * alias are one unmanaged node, however many switches saw it. Edges are
233
249
  * undirected and deduplicated — a link reported from both ends, or by
234
250
  * both protocols, appears once with the union of what each report knew
235
- * (ports, protocols, per-end interface state). When `interfaces` rows are
251
+ * (ports, protocols, per-end interface state). Undirected in the sense
252
+ * that neither end is privileged by how the edge was discovered: an edge
253
+ * carries a direction only where an operator declared one, as
254
+ * `parentNodeId`. When `interfaces` rows are
236
255
  * provided, each edge end whose interface is identifiable carries its
237
256
  * operational state (up/down, utilization, rates). When `endpoints` rows
238
257
  * are provided, each one that attaches to a device in the graph becomes an
@@ -306,7 +325,7 @@ export default class NetworkTopologyUtil {
306
325
  name: device.name,
307
326
  isManaged: true,
308
327
  kind: "device",
309
- role: classifyDeviceRole({
328
+ role: resolveDeviceRole(device.deviceRole, {
310
329
  sysDescr: device.sysDescr,
311
330
  sysObjectId: device.sysObjectId,
312
331
  vendor: device.vendor,
@@ -520,6 +539,18 @@ export default class NetworkTopologyUtil {
520
539
  continue;
521
540
  }
522
541
 
542
+ /*
543
+ * A declared parent is only meaningful if it is one of the two ends.
544
+ * The service refuses anything else at write time, so this is a
545
+ * second line rather than the only one — it also covers rule-derived
546
+ * links, which never pass through that service.
547
+ */
548
+ const declaredParentId: string | undefined =
549
+ link.parentDeviceId === link.fromDeviceId ||
550
+ link.parentDeviceId === link.toDeviceId
551
+ ? link.parentDeviceId
552
+ : undefined;
553
+
523
554
  const edgeKey: string = [link.fromDeviceId, link.toDeviceId]
524
555
  .sort()
525
556
  .join("::");
@@ -534,6 +565,7 @@ export default class NetworkTopologyUtil {
534
565
  protocols: ["manual"],
535
566
  monitorState: link.monitorStatus,
536
567
  name: link.name,
568
+ parentNodeId: declaredParentId,
537
569
  });
538
570
  continue;
539
571
  }
@@ -557,6 +589,14 @@ export default class NetworkTopologyUtil {
557
589
  }
558
590
  existing.name = existing.name || link.name;
559
591
  existing.monitorState = existing.monitorState ?? link.monitorStatus;
592
+ /*
593
+ * First declaration wins, matching the ordering the caller already
594
+ * relies on: explicit links are merged before rule-derived ones, so
595
+ * a hierarchy somebody set on the link itself beats a rule that
596
+ * happens to cover the same pair. The specific statement beats the
597
+ * general one — the same precedence the ports and the name use.
598
+ */
599
+ existing.parentNodeId = existing.parentNodeId || declaredParentId;
560
600
  }
561
601
 
562
602
  const edges: Array<NetworkTopologyEdge> = Array.from(edgeByKey.values());