@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
package/Types/API/URL.ts CHANGED
@@ -9,6 +9,33 @@ import Protocol from "./Protocol";
9
9
  import Route from "./Route";
10
10
  import { FindOperator } from "typeorm";
11
11
 
12
+ /*
13
+ * Schemes whose remainder is NOT an authority: there is no host, no port and
14
+ * no path to parse, so the value is carried verbatim instead of being routed
15
+ * through Hostname. "mailto:" is handled separately because its payload is an
16
+ * Email, which already has its own type.
17
+ */
18
+ const OPAQUE_PROTOCOLS: Array<Protocol> = [Protocol.TEL, Protocol.SMS];
19
+
20
+ /*
21
+ * A tel/sms subscriber per RFC 3966: an optional "+" for a global number, then
22
+ * digits and the visual separators the RFC permits ("-", ".", "(", ")"). At
23
+ * least one digit is required, but it need not come first — "(313)636-1710"
24
+ * opens on a separator and is a number people really write.
25
+ *
26
+ * Deliberately narrow beyond that: the point is to reject anything that could
27
+ * smuggle a path, query, fragment or authority ("/", "?", "#", "@",
28
+ * whitespace) into a value that later gets interpolated into an href.
29
+ */
30
+ const TEL_SUBSCRIBER_REGEX: RegExp = /^\+?[0-9\-.()]*[0-9][0-9\-.()]*$/;
31
+
32
+ /*
33
+ * ";key=value" parameters, e.g. ";phone-context=+1". Values are restricted to
34
+ * RFC 3986 unreserved characters plus the few sub-delims real numbers use.
35
+ */
36
+ const TEL_PARAM_REGEX: RegExp =
37
+ /^[A-Za-z0-9\-._~%]+(?:=[A-Za-z0-9\-._~%+:]*)?$/;
38
+
12
39
  export default class URL extends DatabaseProperty {
13
40
  private _route: Route = new Route();
14
41
  public get route(): Route {
@@ -50,6 +77,75 @@ export default class URL extends DatabaseProperty {
50
77
  this._protocol = v;
51
78
  }
52
79
 
80
+ /*
81
+ * The payload of an opaque scheme — the phone number of a tel:/sms: URL.
82
+ * Empty for every other scheme.
83
+ */
84
+ private _opaqueValue: string = "";
85
+ public get opaqueValue(): string {
86
+ return this._opaqueValue;
87
+ }
88
+ public set opaqueValue(v: string) {
89
+ const value: string = v.trim();
90
+
91
+ if (!URL.isValidOpaqueValue(value)) {
92
+ throw new BadDataException(
93
+ "Phone number " + v + " is not in valid format.",
94
+ );
95
+ }
96
+
97
+ this._opaqueValue = value;
98
+ }
99
+
100
+ /*
101
+ * Set only by the lenient read paths (fromDatabase / fromJSON) when a stored
102
+ * value cannot be parsed. Such a URL keeps the raw string so it still
103
+ * round-trips and still renders, but exposes no hostname and is refused by
104
+ * toDatabase so the bad value can never be written back or newly created.
105
+ */
106
+ private _isMalformed: boolean = false;
107
+ public isMalformed(): boolean {
108
+ return this._isMalformed;
109
+ }
110
+
111
+ private _rawValue: string = "";
112
+
113
+ public static isOpaqueProtocol(protocol: Protocol): boolean {
114
+ return OPAQUE_PROTOCOLS.includes(protocol);
115
+ }
116
+
117
+ /*
118
+ * A tel:/sms: payload: one or more comma-separated numbers (sms: allows
119
+ * several recipients) followed by optional ";key=value" parameters.
120
+ */
121
+ public static isValidOpaqueValue(value: string): boolean {
122
+ const trimmed: string = value.trim();
123
+
124
+ if (!trimmed || trimmed.length > 256) {
125
+ return false;
126
+ }
127
+
128
+ const [subscribers, ...params] = trimmed.split(";");
129
+
130
+ if (!subscribers) {
131
+ return false;
132
+ }
133
+
134
+ const numbers: Array<string> = subscribers.split(",");
135
+
136
+ const everyNumberIsValid: boolean = numbers.every((number: string) => {
137
+ return TEL_SUBSCRIBER_REGEX.test(number);
138
+ });
139
+
140
+ if (!everyNumberIsValid) {
141
+ return false;
142
+ }
143
+
144
+ return params.every((param: string) => {
145
+ return TEL_PARAM_REGEX.test(param);
146
+ });
147
+ }
148
+
53
149
  public constructor(
54
150
  protocol: Protocol,
55
151
  hostname: Hostname | string | Email,
@@ -58,6 +154,21 @@ export default class URL extends DatabaseProperty {
58
154
  ) {
59
155
  super();
60
156
 
157
+ /*
158
+ * Opaque schemes first: their payload is a phone number, and asking
159
+ * Hostname to validate one would either reject it or — as it did before
160
+ * tel: was a known scheme — read "tel:1234567890" as host + port.
161
+ */
162
+ if (URL.isOpaqueProtocol(protocol)) {
163
+ this.protocol = protocol;
164
+ this.opaqueValue =
165
+ hostname instanceof Hostname || hostname instanceof Email
166
+ ? hostname.toString()
167
+ : (hostname as string);
168
+ this.setParamsFromQueryString(queryString);
169
+ return;
170
+ }
171
+
61
172
  if (
62
173
  typeof hostname === Typeof.String &&
63
174
  Email.isValid(hostname as string)
@@ -77,15 +188,21 @@ export default class URL extends DatabaseProperty {
77
188
  this.route = route;
78
189
  }
79
190
 
80
- if (queryString) {
81
- const keyValues: Array<string> = queryString.split("&");
82
- for (const keyValue of keyValues) {
83
- if (keyValue.split("=")[0] && keyValue.split("=")[1]) {
84
- const key: string | undefined = keyValue.split("=")[0];
85
- const value: string | undefined = keyValue.split("=")[1];
86
- if (key && value) {
87
- this._params[key] = value;
88
- }
191
+ this.setParamsFromQueryString(queryString);
192
+ }
193
+
194
+ private setParamsFromQueryString(queryString?: string): void {
195
+ if (!queryString) {
196
+ return;
197
+ }
198
+
199
+ const keyValues: Array<string> = queryString.split("&");
200
+ for (const keyValue of keyValues) {
201
+ if (keyValue.split("=")[0] && keyValue.split("=")[1]) {
202
+ const key: string | undefined = keyValue.split("=")[0];
203
+ const value: string | undefined = keyValue.split("=")[1];
204
+ if (key && value) {
205
+ this._params[key] = value;
89
206
  }
90
207
  }
91
208
  }
@@ -95,7 +212,34 @@ export default class URL extends DatabaseProperty {
95
212
  return this.protocol === Protocol.HTTPS;
96
213
  }
97
214
 
215
+ private queryStringSuffix(): string {
216
+ if (Object.keys(this.params).length === 0) {
217
+ return "";
218
+ }
219
+
220
+ return (
221
+ "?" +
222
+ Object.keys(this.params)
223
+ .map((key: string) => {
224
+ return key + "=" + this.params[key];
225
+ })
226
+ .join("&")
227
+ );
228
+ }
229
+
98
230
  public override toString(): string {
231
+ /*
232
+ * A value that failed to parse on read is echoed back exactly as stored,
233
+ * so a legacy row still renders as the link it always was.
234
+ */
235
+ if (this.isMalformed()) {
236
+ return this._rawValue;
237
+ }
238
+
239
+ if (URL.isOpaqueProtocol(this.protocol)) {
240
+ return `${this.protocol}${this.opaqueValue}${this.queryStringSuffix()}`;
241
+ }
242
+
99
243
  let urlString: string = `${this.protocol}${this.hostname || this.email}`;
100
244
  if (!this.email && !urlString.startsWith("mailto:")) {
101
245
  if (this.route && this.route.toString().startsWith("/")) {
@@ -110,15 +254,7 @@ export default class URL extends DatabaseProperty {
110
254
  urlString += "/" + this.route.toString();
111
255
  }
112
256
 
113
- if (Object.keys(this.params).length > 0) {
114
- urlString += "?";
115
-
116
- for (const key of Object.keys(this.params)) {
117
- urlString += key + "=" + this.params[key] + "&";
118
- }
119
-
120
- urlString = urlString.substring(0, urlString.length - 1); // remove last &
121
- }
257
+ urlString += this.queryStringSuffix();
122
258
  }
123
259
 
124
260
  return urlString;
@@ -128,6 +264,50 @@ export default class URL extends DatabaseProperty {
128
264
  return URL.fromString(url.toString());
129
265
  }
130
266
 
267
+ /*
268
+ * An unrecognised scheme used to be swallowed: the prefix loop simply did
269
+ * not match, the protocol stayed at its https default, and the rest was read
270
+ * as an authority. "tel:3136361710" was stored as "https://tel:3136361710/"
271
+ * that way — a value nobody typed, with a host of "tel", which a later,
272
+ * stricter Hostname then refused to read back.
273
+ *
274
+ * Anything that clearly announces a scheme this type cannot represent is now
275
+ * refused with a message that says so, rather than silently rewritten:
276
+ *
277
+ * - "scheme://..." for any scheme not in the supported list, and
278
+ * - the schemes that are dangerous in an href even without "//".
279
+ *
280
+ * A scheme-less "example.com:8080/hook" is deliberately still allowed: its
281
+ * "example.com:" looks like a scheme to a regex but is a host and port, and
282
+ * callers really do pass it.
283
+ */
284
+ private static rejectUnsupportedScheme(url: string): void {
285
+ const schemeMatch: RegExpMatchArray | null = url
286
+ .trim()
287
+ .match(/^([a-zA-Z][a-zA-Z0-9+.-]*):(\/\/)?/);
288
+
289
+ if (!schemeMatch || !schemeMatch[1]) {
290
+ return;
291
+ }
292
+
293
+ const scheme: string = schemeMatch[1].toLowerCase();
294
+ const hasAuthorityMarker: boolean = Boolean(schemeMatch[2]);
295
+
296
+ const isDangerousInAnHref: boolean = [
297
+ "javascript",
298
+ "data",
299
+ "vbscript",
300
+ "file",
301
+ "blob",
302
+ ].includes(scheme);
303
+
304
+ if (hasAuthorityMarker || isDangerousInAnHref) {
305
+ throw new BadDataException(
306
+ "URL scheme " + scheme + ": is not supported.",
307
+ );
308
+ }
309
+ }
310
+
131
311
  public static fromString(url: string): URL {
132
312
  let protocol: Protocol = Protocol.HTTPS;
133
313
 
@@ -146,18 +326,27 @@ export default class URL extends DatabaseProperty {
146
326
  ["ws://", Protocol.WS],
147
327
  ["mongodb://", Protocol.MONGO_DB],
148
328
  ["mailto:", Protocol.MAIL],
329
+ ["tel:", Protocol.TEL],
330
+ ["sms:", Protocol.SMS],
149
331
  ];
150
332
 
151
333
  const lowerCasedUrl: string = url.toLowerCase();
152
334
 
335
+ let matchedKnownScheme: boolean = false;
336
+
153
337
  for (const [prefix, prefixProtocol] of schemePrefixes) {
154
338
  if (lowerCasedUrl.startsWith(prefix)) {
155
339
  protocol = prefixProtocol;
156
340
  url = url.substring(prefix.length);
341
+ matchedKnownScheme = true;
157
342
  break;
158
343
  }
159
344
  }
160
345
 
346
+ if (!matchedKnownScheme) {
347
+ URL.rejectUnsupportedScheme(url);
348
+ }
349
+
161
350
  if (protocol === Protocol.MAIL) {
162
351
  /*
163
352
  * Hand the bare address to the constructor so it is recognised as an
@@ -170,6 +359,18 @@ export default class URL extends DatabaseProperty {
170
359
  return new URL(protocol, address, undefined, mailQueryString);
171
360
  }
172
361
 
362
+ if (URL.isOpaqueProtocol(protocol)) {
363
+ /*
364
+ * "sms:+15555550123?body=hi" — the number is everything before the
365
+ * query. There is no authority and no path, so nothing here may reach
366
+ * Hostname.
367
+ */
368
+ const number: string = url.split("?")[0] || "";
369
+ const opaqueQueryString: string = url.split("?")[1] || "";
370
+
371
+ return new URL(protocol, number, undefined, opaqueQueryString);
372
+ }
373
+
173
374
  /*
174
375
  * The authority ends at the first "/", "?" or "#". Splitting on "/" alone
175
376
  * left the query and fragment glued to the host for URLs with no path
@@ -194,7 +395,50 @@ export default class URL extends DatabaseProperty {
194
395
  return new URL(protocol, hostname, route, queryString);
195
396
  }
196
397
 
398
+ /*
399
+ * Parses like fromString, but never throws: a value it cannot understand
400
+ * comes back as a malformed URL that preserves the original string.
401
+ *
402
+ * This exists for READ paths only. A single unparseable row must not be able
403
+ * to fail the request that reads it — a status page footer link of
404
+ * "https://tel:1234567890/", stored years earlier by a looser validator,
405
+ * used to 400 the whole status page config endpoint and left the page stuck
406
+ * on "Loading...". Writes stay strict: see toDatabase.
407
+ *
408
+ * A malformed URL exposes NO hostname, so nothing can read a host off it and
409
+ * build a request. The outbound-request guards (SSRFProtection) re-parse the
410
+ * raw string with the WHATWG parser and never trusted this type's parse
411
+ * anyway, so leniency here does not widen them.
412
+ */
413
+ public static fromStringLenient(url: string): URL {
414
+ try {
415
+ return URL.fromString(url);
416
+ } catch {
417
+ return URL.createMalformed(url);
418
+ }
419
+ }
420
+
421
+ private static createMalformed(rawValue: string): URL {
422
+ /*
423
+ * Built through the opaque branch so the constructor never asks Hostname
424
+ * to validate the very value that just failed to parse; the placeholder is
425
+ * then cleared, leaving a URL with no hostname and no opaque value. The
426
+ * protocol reports the same https default fromString falls back to for an
427
+ * unrecognised scheme.
428
+ */
429
+ const url: URL = new URL(Protocol.TEL, "0");
430
+ url._protocol = Protocol.HTTPS;
431
+ url._opaqueValue = "";
432
+ url._isMalformed = true;
433
+ url._rawValue = rawValue;
434
+ return url;
435
+ }
436
+
197
437
  public removeQueryString(): URL {
438
+ if (this.isMalformed()) {
439
+ return this;
440
+ }
441
+
198
442
  return URL.fromString(this.toString().split("?")[0] || "");
199
443
  }
200
444
 
@@ -205,9 +449,14 @@ export default class URL extends DatabaseProperty {
205
449
  };
206
450
  }
207
451
 
452
+ /*
453
+ * Lenient for the same reason fromDatabase is: this is how a URL crosses the
454
+ * wire into the browser. If it threw, moving the server-side failure out of
455
+ * the API would only move the crash into the client that renders the link.
456
+ */
208
457
  public static override fromJSON(json: JSONObject): URL {
209
458
  if (json["_type"] === ObjectType.URL) {
210
- return URL.fromString((json["value"] as string) || "");
459
+ return URL.fromStringLenient((json["value"] as string) || "");
211
460
  }
212
461
 
213
462
  throw new BadDataException("Invalid JSON: " + JSON.stringify(json));
@@ -263,6 +512,12 @@ export default class URL extends DatabaseProperty {
263
512
  return null;
264
513
  }
265
514
 
515
+ /*
516
+ * The write side stays strict. Reads tolerate a legacy value that no longer
517
+ * validates, but nothing may persist one: a malformed URL only ever comes
518
+ * from fromDatabase/fromJSON, so refusing it here stops a bad value being
519
+ * written back on an unrelated update, and stops a client inventing one.
520
+ */
266
521
  protected static override toDatabase(
267
522
  value: URL | FindOperator<URL>,
268
523
  ): string | null {
@@ -271,15 +526,26 @@ export default class URL extends DatabaseProperty {
271
526
  value = URL.fromString(value);
272
527
  }
273
528
 
529
+ if (value instanceof URL && value.isMalformed()) {
530
+ throw new BadDataException(
531
+ "URL " + value.toString() + " is not in valid format.",
532
+ );
533
+ }
534
+
274
535
  return value.toString();
275
536
  }
276
537
 
277
538
  return null;
278
539
  }
279
540
 
541
+ /*
542
+ * Lenient by design — see fromStringLenient. A row that cannot be parsed is
543
+ * returned as a malformed URL rather than throwing, because throwing here
544
+ * fails the entire query that touched the row, not just the one column.
545
+ */
280
546
  protected static override fromDatabase(_value: string): URL | null {
281
547
  if (_value) {
282
- return URL.fromString(_value);
548
+ return URL.fromStringLenient(_value);
283
549
  }
284
550
 
285
551
  return null;
@@ -0,0 +1,47 @@
1
+ /*
2
+ * The public pay-as-you-go rates, as advertised on
3
+ * https://oneuptime.com/pricing.
4
+ *
5
+ * These live in Types (and not next to the metered plans, which are
6
+ * server-only) because two very different places need the same numbers: the
7
+ * server meters and bills against them, and the dashboard has to tell a Free
8
+ * plan user what they are about to be charged before they commit to it. A
9
+ * price quoted in the product that disagrees with the price on the invoice is
10
+ * worse than quoting no price at all, so there is exactly one copy.
11
+ */
12
+
13
+ // Telemetry ingest - logs, traces, metrics and profiles.
14
+ export const TELEMETRY_PRICE_IN_USD_PER_GB: number = 0.1;
15
+
16
+ /*
17
+ * Session replay recordings travel on the same ingestion key as the rest of
18
+ * telemetry but are priced well above it, on purpose - see
19
+ * SessionReplayDataIngestMeteredPlan. Anything that quotes "what this key
20
+ * costs" has to say both numbers, or it is quoting a rate 20x below what a
21
+ * replay-heavy project will actually be invoiced.
22
+ */
23
+ export const SESSION_REPLAY_PRICE_IN_USD_PER_GB: number = 2;
24
+
25
+ // The retention the per-GB telemetry prices above are quoted for.
26
+ export const TELEMETRY_PRICE_RETENTION_IN_DAYS: number = 15;
27
+
28
+ /*
29
+ * Active monitoring. Every monitor except a Manual one is an active monitor -
30
+ * see MonitorTypeHelper.isBilledAsActiveMonitor.
31
+ */
32
+ export const ACTIVE_MONITOR_PRICE_IN_USD_PER_MONTH: number = 1;
33
+
34
+ export const PRICING_PAGE_URL: string = "https://oneuptime.com/pricing";
35
+
36
+ /**
37
+ * Money as the pricing page writes it: `$1` rather than `$1.00`, but `$0.10`
38
+ * rather than `$0.1`. In-app copy that quotes a rate should read exactly like
39
+ * the page the user will check it against.
40
+ */
41
+ export function formatPriceInUSD(amountInUSD: number): string {
42
+ if (Number.isInteger(amountInUSD)) {
43
+ return `$${amountInUSD}`;
44
+ }
45
+
46
+ return `$${amountInUSD.toFixed(2)}`;
47
+ }
@@ -1,9 +1,37 @@
1
+ /*
2
+ * The member VALUES are load-bearing beyond this enum.
3
+ *
4
+ * CustomFieldsDetail passes a definition's `customFieldType` straight through
5
+ * as the `fieldType` of a Detail field and of a form field, so every member
6
+ * here has to name a member of BOTH `FieldType` (Detail) and
7
+ * `FormFieldSchemaType` (Forms) by value. `Boolean` is the standing example:
8
+ * it reaches the form as FormFieldSchemaType.Toggle, which is spelled
9
+ * `Toggle = "Boolean"` for exactly this reason.
10
+ *
11
+ * Adding a member without that correspondence compiles fine and then renders
12
+ * an empty cell and an input the user cannot type into.
13
+ */
1
14
  enum CustomFieldType {
2
15
  Text = "Text",
3
16
  Number = "Number",
4
17
  Boolean = "Boolean",
5
18
  Dropdown = "Dropdown",
6
19
  MultiSelectDropdown = "MultiSelectDropdown",
20
+
21
+ /**
22
+ * A calendar date with no time of day — purchase date, warranty expiry,
23
+ * end-of-life. Stored in the `customFields` jsonb bag as an ISO-8601 string,
24
+ * which is what makes it comparable: ISO-8601 sorts identically as text and
25
+ * as an instant, so the range filters work over a jsonb key without a cast
26
+ * (see JSONColumnQuery.compareText).
27
+ */
28
+ Date = "Date",
29
+
30
+ /**
31
+ * As `Date`, but the time of day is part of the answer — last audited at,
32
+ * decommissioned at.
33
+ */
34
+ DateTime = "DateTime",
7
35
  }
8
36
 
9
37
  export default CustomFieldType;
@@ -170,6 +170,17 @@ export class MonitorTypeHelper {
170
170
  return monitorType === MonitorType.Manual;
171
171
  }
172
172
 
173
+ /*
174
+ * Whether a monitor of this type adds to the project's Active Monitoring
175
+ * bill. Mirrors what the server actually meters -
176
+ * ActiveMonitoringMeteredPlan counts every monitor whose type is not
177
+ * Manual - so the dashboard can warn a Free plan user about a charge using
178
+ * the same rule that produces it. Manual monitors are free and unlimited.
179
+ */
180
+ public static isBilledAsActiveMonitor(monitorType: MonitorType): boolean {
181
+ return !this.isManualMonitor(monitorType);
182
+ }
183
+
173
184
  public static getAllMonitorTypeProps(): Array<MonitorTypeProps> {
174
185
  const monitorTypeProps: Array<MonitorTypeProps> = [
175
186
  {
@@ -156,6 +156,19 @@ export interface NetworkTopologyEdge {
156
156
  monitorState?: "up" | "down" | undefined;
157
157
  // Operator-supplied label, set only on manual links.
158
158
  name?: string | undefined;
159
+ /*
160
+ * Which end of this edge is the parent, when somebody has SAID so —
161
+ * either on the link itself or through a link rule, which is stated in
162
+ * child/parent labels and so knows the answer by construction.
163
+ *
164
+ * Always one of `fromNodeId`/`toNodeId`. Absent on every discovered
165
+ * edge: LLDP and CDP report a cable, not a hierarchy, and the direction
166
+ * a neighbour entry happens to be read from says nothing about which
167
+ * box is upstream. Layouts that draw a tree must therefore treat this as
168
+ * a constraint where it is present and keep inferring where it is not —
169
+ * absent is "not stated", never "these are peers".
170
+ */
171
+ parentNodeId?: string | undefined;
159
172
  }
160
173
 
161
174
  export default interface NetworkTopology {
@@ -0,0 +1,121 @@
1
+ import { JSONObject } from "../JSON";
2
+
3
+ /*
4
+ * What a manual "Run now" of a Network Automation rule did.
5
+ *
6
+ * Both rule kinds fire automatically only when a device is created (and, for
7
+ * site assignment, when its identity changes or on the next poll of a device
8
+ * with no site), so a rule written after an estate was imported never reaches
9
+ * it. "Run now" closes that gap — OneUptime/oneuptime#3191 — and these are the
10
+ * shapes it answers with.
11
+ *
12
+ * They live in Types, not next to the services that produce them, because the
13
+ * dashboard renders the same counters the server computed and neither side
14
+ * should be restating the other's field names.
15
+ */
16
+
17
+ /*
18
+ * One run of a site assignment rule. Every device the run looked at lands in
19
+ * exactly one bucket, which is what lets the UI explain an assignment count of
20
+ * zero: "nothing matched" and "everything that matched is already there" are
21
+ * very different answers, and a single number cannot tell them apart.
22
+ */
23
+ export interface SiteAssignmentRuleRunResult {
24
+ // Devices in the project the run evaluated.
25
+ devicesEvaluated: number;
26
+ // Of those, the ones this rule's criteria matched.
27
+ devicesMatched: number;
28
+ // Matched devices whose site this run actually changed.
29
+ devicesAssigned: number;
30
+ // Matched devices already sitting in this rule's site.
31
+ devicesAlreadyInRuleSite: number;
32
+ /*
33
+ * Matched devices left alone because they are already in some OTHER site
34
+ * and the run was not asked to overwrite existing assignments.
35
+ */
36
+ devicesSkippedAlreadyInAnotherSite: number;
37
+ /*
38
+ * Matched devices a higher-priority rule also matches. Running one rule
39
+ * never does another rule's work, so these are reported rather than moved.
40
+ */
41
+ devicesClaimedByHigherPriorityRule: number;
42
+ // Matched devices whose update threw. Logged server-side, never fatal.
43
+ devicesFailed: number;
44
+ // True when the run stopped at its device cap with devices left over.
45
+ isTruncated: boolean;
46
+ }
47
+
48
+ // One run of a network device label rule.
49
+ export interface LabelRuleRunResult {
50
+ // Devices in the project the run evaluated.
51
+ devicesEvaluated: number;
52
+ // Of those, the ones this rule's criteria matched.
53
+ devicesMatched: number;
54
+ /*
55
+ * Matched devices that gained at least one label. Lower than devicesMatched
56
+ * whenever a device already carries everything the rule attaches —
57
+ * re-running a rule is idempotent, not additive.
58
+ */
59
+ devicesLabeled: number;
60
+ // (device, label) pairs actually inserted.
61
+ labelsAttached: number;
62
+ // Pairs whose insert threw. Logged server-side, never fatal.
63
+ labelsFailed: number;
64
+ // True when the run stopped at its device cap with devices left over.
65
+ isTruncated: boolean;
66
+ }
67
+
68
+ /*
69
+ * A count off the wire. An absent or non-numeric field reads as zero rather
70
+ * than as NaN: a summary line that says "NaN devices" is worse than one that
71
+ * under-reports a counter the server never sent.
72
+ */
73
+ function readCount(json: JSONObject, key: string): number {
74
+ const value: unknown = json[key];
75
+
76
+ if (typeof value !== "number" || !Number.isFinite(value)) {
77
+ return 0;
78
+ }
79
+
80
+ return value;
81
+ }
82
+
83
+ export class RuleRunResultUtil {
84
+ public static parseSiteAssignmentRuleRunResult(
85
+ json: JSONObject | undefined | null,
86
+ ): SiteAssignmentRuleRunResult {
87
+ const source: JSONObject = json || {};
88
+
89
+ return {
90
+ devicesEvaluated: readCount(source, "devicesEvaluated"),
91
+ devicesMatched: readCount(source, "devicesMatched"),
92
+ devicesAssigned: readCount(source, "devicesAssigned"),
93
+ devicesAlreadyInRuleSite: readCount(source, "devicesAlreadyInRuleSite"),
94
+ devicesSkippedAlreadyInAnotherSite: readCount(
95
+ source,
96
+ "devicesSkippedAlreadyInAnotherSite",
97
+ ),
98
+ devicesClaimedByHigherPriorityRule: readCount(
99
+ source,
100
+ "devicesClaimedByHigherPriorityRule",
101
+ ),
102
+ devicesFailed: readCount(source, "devicesFailed"),
103
+ isTruncated: source["isTruncated"] === true,
104
+ };
105
+ }
106
+
107
+ public static parseLabelRuleRunResult(
108
+ json: JSONObject | undefined | null,
109
+ ): LabelRuleRunResult {
110
+ const source: JSONObject = json || {};
111
+
112
+ return {
113
+ devicesEvaluated: readCount(source, "devicesEvaluated"),
114
+ devicesMatched: readCount(source, "devicesMatched"),
115
+ devicesLabeled: readCount(source, "devicesLabeled"),
116
+ labelsAttached: readCount(source, "labelsAttached"),
117
+ labelsFailed: readCount(source, "labelsFailed"),
118
+ isTruncated: source["isTruncated"] === true,
119
+ };
120
+ }
121
+ }
@@ -33,3 +33,26 @@ export const RUNNER_EXECUTED_STEP_TYPES: Array<RunbookStepType> = [
33
33
  export function isRunnerExecutedStepType(type: RunbookStepType): boolean {
34
34
  return RUNNER_EXECUTED_STEP_TYPES.includes(type);
35
35
  }
36
+
37
+ /*
38
+ * Runner-executed step types that act on a system OTHER than the Runner's own
39
+ * host. They are dispatched as structured instructions (RunnerJob.payload)
40
+ * plus a credential the server resolves at claim time — never as a script, so
41
+ * their job rows carry an empty script by design.
42
+ *
43
+ * That is why RunnerJob.script is not a required column: an empty string is
44
+ * falsy, so a required-column check would reject exactly these jobs. The real
45
+ * rule — script types need a script, these need a payload — is per-type and
46
+ * therefore lives in RunnerJobService.enqueue rather than in column metadata.
47
+ *
48
+ * Every runner-executed type not listed here carries a script instead; the
49
+ * two sets are complementary and a test asserts they stay that way.
50
+ */
51
+ export const PAYLOAD_CARRYING_STEP_TYPES: Array<RunbookStepType> = [
52
+ RunbookStepType.SSH,
53
+ RunbookStepType.Kubernetes,
54
+ ];
55
+
56
+ export function isPayloadCarryingStepType(type: RunbookStepType): boolean {
57
+ return PAYLOAD_CARRYING_STEP_TYPES.includes(type);
58
+ }