@oneuptime/common 11.0.0 → 11.0.2

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 (357) hide show
  1. package/Models/DatabaseModels/Alert.ts +110 -0
  2. package/Models/DatabaseModels/CephCluster.ts +964 -0
  3. package/Models/DatabaseModels/CephClusterLabelRule.ts +514 -0
  4. package/Models/DatabaseModels/CephClusterOwnerRule.ts +596 -0
  5. package/Models/DatabaseModels/CephClusterOwnerTeam.ts +487 -0
  6. package/Models/DatabaseModels/CephClusterOwnerUser.ts +486 -0
  7. package/Models/DatabaseModels/CephResource.ts +809 -0
  8. package/Models/DatabaseModels/Host.ts +64 -0
  9. package/Models/DatabaseModels/Incident.ts +110 -0
  10. package/Models/DatabaseModels/Index.ts +24 -0
  11. package/Models/DatabaseModels/ProxmoxCluster.ts +943 -0
  12. package/Models/DatabaseModels/ProxmoxClusterLabelRule.ts +514 -0
  13. package/Models/DatabaseModels/ProxmoxClusterOwnerRule.ts +596 -0
  14. package/Models/DatabaseModels/ProxmoxClusterOwnerTeam.ts +487 -0
  15. package/Models/DatabaseModels/ProxmoxClusterOwnerUser.ts +486 -0
  16. package/Models/DatabaseModels/ProxmoxResource.ts +726 -0
  17. package/Models/DatabaseModels/ScheduledMaintenance.ts +110 -0
  18. package/Server/API/BillingInvoiceAPI.ts +47 -7
  19. package/Server/API/CephResourceAPI.ts +134 -0
  20. package/Server/API/DashboardAPI.ts +46 -0
  21. package/Server/API/ProjectAPI.ts +15 -0
  22. package/Server/API/ProxmoxResourceAPI.ts +132 -0
  23. package/Server/API/ResellerPlanAPI.ts +17 -0
  24. package/Server/Infrastructure/GlobalCache.ts +8 -2
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1781500000000-AddProxmoxAndCephClusterTables.ts +163 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1781600000000-AddProxmoxCephV2Columns.ts +211 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1781600000001-AddProxmoxCephActivityAndRules.ts +590 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1781700000000-AddProxmoxCephV3Columns.ts +64 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  30. package/Server/Infrastructure/Redis.ts +40 -12
  31. package/Server/Services/AnalyticsDatabaseService.ts +1 -1
  32. package/Server/Services/BillingService.ts +109 -21
  33. package/Server/Services/CephClusterLabelRuleEngineService.ts +200 -0
  34. package/Server/Services/CephClusterLabelRuleService.ts +14 -0
  35. package/Server/Services/CephClusterOwnerRuleEngineService.ts +218 -0
  36. package/Server/Services/CephClusterOwnerRuleService.ts +14 -0
  37. package/Server/Services/CephClusterOwnerTeamService.ts +10 -0
  38. package/Server/Services/CephClusterOwnerUserService.ts +10 -0
  39. package/Server/Services/CephClusterService.ts +401 -0
  40. package/Server/Services/CephResourceService.ts +383 -0
  41. package/Server/Services/CloudResourceService.ts +11 -3
  42. package/Server/Services/DockerHostService.ts +11 -3
  43. package/Server/Services/ExceptionAggregationService.ts +2 -0
  44. package/Server/Services/HostService.ts +11 -3
  45. package/Server/Services/Index.ts +24 -0
  46. package/Server/Services/KubernetesClusterService.ts +11 -3
  47. package/Server/Services/LogAggregationService.ts +2 -0
  48. package/Server/Services/MetricAggregationService.ts +2 -0
  49. package/Server/Services/OpenTelemetryIngestService.ts +36 -0
  50. package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +204 -0
  51. package/Server/Services/ProxmoxClusterLabelRuleService.ts +14 -0
  52. package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +222 -0
  53. package/Server/Services/ProxmoxClusterOwnerRuleService.ts +14 -0
  54. package/Server/Services/ProxmoxClusterOwnerTeamService.ts +10 -0
  55. package/Server/Services/ProxmoxClusterOwnerUserService.ts +10 -0
  56. package/Server/Services/ProxmoxClusterService.ts +382 -0
  57. package/Server/Services/ProxmoxResourceService.ts +404 -0
  58. package/Server/Services/RumApplicationService.ts +11 -3
  59. package/Server/Services/ServerlessFunctionService.ts +11 -3
  60. package/Server/Services/TelemetryUsageBillingService.ts +41 -3
  61. package/Server/Services/TraceAggregationService.ts +2 -0
  62. package/Server/Types/AnalyticsDatabase/AggregateBy.ts +8 -23
  63. package/Server/Utils/Monitor/MonitorAlert.ts +45 -0
  64. package/Server/Utils/Monitor/MonitorClusterContext.ts +129 -0
  65. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +344 -4
  66. package/Server/Utils/Monitor/MonitorIncident.ts +130 -7
  67. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +39 -6
  68. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +3 -1
  69. package/Server/Utils/Monitor/SeriesResourceLabels.ts +33 -0
  70. package/Server/Utils/Profiling.ts +37 -2
  71. package/Server/Utils/Telemetry/EntityRegistry.ts +4 -0
  72. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +1096 -0
  73. package/Server/Utils/Telemetry/TelemetryEntity.ts +85 -0
  74. package/Server/Utils/Telemetry.ts +8 -19
  75. package/Tests/Server/API/BillingInvoiceAPI.test.ts +194 -0
  76. package/Tests/Server/API/ProjectAPI.test.ts +91 -0
  77. package/Tests/Server/API/ResellerPlanAPI.test.ts +207 -0
  78. package/Tests/Server/Infrastructure/GlobalCache.test.ts +100 -0
  79. package/Tests/Server/Services/BillingService.test.ts +323 -0
  80. package/Tests/Server/Services/CephResourceService.test.ts +264 -0
  81. package/Tests/Server/Services/ProxmoxResourceService.test.ts +326 -0
  82. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +322 -0
  83. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +13 -0
  84. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +879 -0
  85. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +196 -0
  86. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +1231 -0
  87. package/Tests/Types/Monitor/ProxmoxAlertTemplates.test.ts +732 -0
  88. package/Tests/Utils/ModelImportExport.test.ts +366 -0
  89. package/Tests/Utils/Telemetry/EntityRelationship.test.ts +49 -0
  90. package/Tests/Utils/Telemetry/HeartbeatAvailability.test.ts +423 -0
  91. package/Types/BaseDatabase/AggregationIntervalUtil.ts +74 -0
  92. package/Types/Dashboard/DashboardComponentType.ts +4 -0
  93. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +2 -0
  94. package/Types/Dashboard/DashboardComponents/DashboardCephOsdListComponent.ts +15 -0
  95. package/Types/Dashboard/DashboardComponents/DashboardCephPoolListComponent.ts +14 -0
  96. package/Types/Dashboard/DashboardComponents/DashboardProxmoxGuestListComponent.ts +17 -0
  97. package/Types/Dashboard/DashboardComponents/DashboardProxmoxNodeListComponent.ts +16 -0
  98. package/Types/Dashboard/DashboardTemplates.ts +446 -0
  99. package/Types/Icon/IconProp.ts +2 -0
  100. package/Types/Monitor/CephAlertTemplates.ts +1647 -0
  101. package/Types/Monitor/CephMetricCatalog.ts +409 -0
  102. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +44 -0
  103. package/Types/Monitor/MonitorStep.ts +64 -0
  104. package/Types/Monitor/MonitorStepCephMonitor.ts +57 -0
  105. package/Types/Monitor/MonitorStepProxmoxMonitor.ts +81 -0
  106. package/Types/Monitor/MonitorType.ts +29 -1
  107. package/Types/Monitor/ProxmoxAlertTemplates.ts +899 -0
  108. package/Types/Monitor/ProxmoxMetricCatalog.ts +382 -0
  109. package/Types/Permission.ts +464 -0
  110. package/Types/Telemetry/EntityType.ts +11 -0
  111. package/Types/Telemetry/ServiceType.ts +2 -0
  112. package/UI/Components/Icon/Icon.tsx +84 -0
  113. package/UI/Components/ImportExport/ExportModelCard.tsx +90 -0
  114. package/UI/Components/ImportExport/ImportModelsModal.tsx +239 -0
  115. package/UI/Components/ModelTable/ModelTable.tsx +294 -143
  116. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +9 -5
  117. package/UI/Utils/ModelImportExport.ts +207 -0
  118. package/UI/Utils/Telemetry/Telemetry.ts +16 -21
  119. package/UI/Utils/TelemetryService.ts +7 -3
  120. package/Utils/Dashboard/Components/DashboardCephOsdListComponent.ts +63 -0
  121. package/Utils/Dashboard/Components/DashboardCephPoolListComponent.ts +32 -0
  122. package/Utils/Dashboard/Components/DashboardCephResourceListShared.ts +61 -0
  123. package/Utils/Dashboard/Components/DashboardProxmoxGuestListComponent.ts +69 -0
  124. package/Utils/Dashboard/Components/DashboardProxmoxNodeListComponent.ts +55 -0
  125. package/Utils/Dashboard/Components/DashboardProxmoxResourceListShared.ts +61 -0
  126. package/Utils/Dashboard/Components/Index.ts +28 -0
  127. package/Utils/ModelImportExport.ts +369 -0
  128. package/Utils/Telemetry/EntityKey.ts +35 -0
  129. package/Utils/Telemetry/EntityRelationship.ts +6 -0
  130. package/Utils/Telemetry/HeartbeatAvailability.ts +262 -0
  131. package/build/dist/Models/DatabaseModels/Alert.js +108 -0
  132. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  133. package/build/dist/Models/DatabaseModels/CephCluster.js +992 -0
  134. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -0
  135. package/build/dist/Models/DatabaseModels/CephClusterLabelRule.js +522 -0
  136. package/build/dist/Models/DatabaseModels/CephClusterLabelRule.js.map +1 -0
  137. package/build/dist/Models/DatabaseModels/CephClusterOwnerRule.js +603 -0
  138. package/build/dist/Models/DatabaseModels/CephClusterOwnerRule.js.map +1 -0
  139. package/build/dist/Models/DatabaseModels/CephClusterOwnerTeam.js +503 -0
  140. package/build/dist/Models/DatabaseModels/CephClusterOwnerTeam.js.map +1 -0
  141. package/build/dist/Models/DatabaseModels/CephClusterOwnerUser.js +502 -0
  142. package/build/dist/Models/DatabaseModels/CephClusterOwnerUser.js.map +1 -0
  143. package/build/dist/Models/DatabaseModels/CephResource.js +846 -0
  144. package/build/dist/Models/DatabaseModels/CephResource.js.map +1 -0
  145. package/build/dist/Models/DatabaseModels/Host.js +63 -0
  146. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/Incident.js +108 -0
  148. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/Index.js +24 -0
  150. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +967 -0
  152. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -0
  153. package/build/dist/Models/DatabaseModels/ProxmoxClusterLabelRule.js +522 -0
  154. package/build/dist/Models/DatabaseModels/ProxmoxClusterLabelRule.js.map +1 -0
  155. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerRule.js +603 -0
  156. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerRule.js.map +1 -0
  157. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerTeam.js +503 -0
  158. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerTeam.js.map +1 -0
  159. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerUser.js +502 -0
  160. package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerUser.js.map +1 -0
  161. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +761 -0
  162. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -0
  163. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +108 -0
  164. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  165. package/build/dist/Server/API/BillingInvoiceAPI.js +35 -5
  166. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  167. package/build/dist/Server/API/CephResourceAPI.js +98 -0
  168. package/build/dist/Server/API/CephResourceAPI.js.map +1 -0
  169. package/build/dist/Server/API/DashboardAPI.js +46 -0
  170. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  171. package/build/dist/Server/API/ProjectAPI.js +11 -0
  172. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  173. package/build/dist/Server/API/ProxmoxResourceAPI.js +95 -0
  174. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -0
  175. package/build/dist/Server/API/ResellerPlanAPI.js +17 -3
  176. package/build/dist/Server/API/ResellerPlanAPI.js.map +1 -1
  177. package/build/dist/Server/Infrastructure/GlobalCache.js +7 -2
  178. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781500000000-AddProxmoxAndCephClusterTables.js +76 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781500000000-AddProxmoxAndCephClusterTables.js.map +1 -0
  181. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000000-AddProxmoxCephV2Columns.js +108 -0
  182. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000000-AddProxmoxCephV2Columns.js.map +1 -0
  183. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000001-AddProxmoxCephActivityAndRules.js +253 -0
  184. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000001-AddProxmoxCephActivityAndRules.js.map +1 -0
  185. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781700000000-AddProxmoxCephV3Columns.js +43 -0
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781700000000-AddProxmoxCephV3Columns.js.map +1 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  189. package/build/dist/Server/Infrastructure/Redis.js +31 -8
  190. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  191. package/build/dist/Server/Services/AnalyticsDatabaseService.js +1 -1
  192. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  193. package/build/dist/Server/Services/BillingService.js +85 -23
  194. package/build/dist/Server/Services/BillingService.js.map +1 -1
  195. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +166 -0
  196. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -0
  197. package/build/dist/Server/Services/CephClusterLabelRuleService.js +13 -0
  198. package/build/dist/Server/Services/CephClusterLabelRuleService.js.map +1 -0
  199. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +186 -0
  200. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -0
  201. package/build/dist/Server/Services/CephClusterOwnerRuleService.js +13 -0
  202. package/build/dist/Server/Services/CephClusterOwnerRuleService.js.map +1 -0
  203. package/build/dist/Server/Services/CephClusterOwnerTeamService.js +9 -0
  204. package/build/dist/Server/Services/CephClusterOwnerTeamService.js.map +1 -0
  205. package/build/dist/Server/Services/CephClusterOwnerUserService.js +9 -0
  206. package/build/dist/Server/Services/CephClusterOwnerUserService.js.map +1 -0
  207. package/build/dist/Server/Services/CephClusterService.js +353 -0
  208. package/build/dist/Server/Services/CephClusterService.js.map +1 -0
  209. package/build/dist/Server/Services/CephResourceService.js +257 -0
  210. package/build/dist/Server/Services/CephResourceService.js.map +1 -0
  211. package/build/dist/Server/Services/CloudResourceService.js +10 -2
  212. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  213. package/build/dist/Server/Services/DockerHostService.js +10 -2
  214. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  215. package/build/dist/Server/Services/ExceptionAggregationService.js +2 -0
  216. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
  217. package/build/dist/Server/Services/HostService.js +10 -2
  218. package/build/dist/Server/Services/HostService.js.map +1 -1
  219. package/build/dist/Server/Services/Index.js +24 -0
  220. package/build/dist/Server/Services/Index.js.map +1 -1
  221. package/build/dist/Server/Services/KubernetesClusterService.js +10 -2
  222. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  223. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  224. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  225. package/build/dist/Server/Services/MetricAggregationService.js +2 -0
  226. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  227. package/build/dist/Server/Services/OpenTelemetryIngestService.js +37 -7
  228. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  229. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +166 -0
  230. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -0
  231. package/build/dist/Server/Services/ProxmoxClusterLabelRuleService.js +13 -0
  232. package/build/dist/Server/Services/ProxmoxClusterLabelRuleService.js.map +1 -0
  233. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +186 -0
  234. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -0
  235. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleService.js +13 -0
  236. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleService.js.map +1 -0
  237. package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js +9 -0
  238. package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js.map +1 -0
  239. package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js +9 -0
  240. package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js.map +1 -0
  241. package/build/dist/Server/Services/ProxmoxClusterService.js +337 -0
  242. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -0
  243. package/build/dist/Server/Services/ProxmoxResourceService.js +285 -0
  244. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -0
  245. package/build/dist/Server/Services/RumApplicationService.js +10 -2
  246. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  247. package/build/dist/Server/Services/ServerlessFunctionService.js +10 -2
  248. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  249. package/build/dist/Server/Services/TelemetryUsageBillingService.js +30 -3
  250. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  251. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  252. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  253. package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js +8 -25
  254. package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js.map +1 -1
  255. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +36 -0
  256. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  257. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +90 -0
  258. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +1 -0
  259. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +228 -4
  260. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  261. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +103 -8
  262. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  263. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +23 -6
  264. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  265. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +3 -1
  266. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  267. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +23 -0
  268. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  269. package/build/dist/Server/Utils/Profiling.js +24 -3
  270. package/build/dist/Server/Utils/Profiling.js.map +1 -1
  271. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -0
  272. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  273. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +854 -0
  274. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -0
  275. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +62 -0
  276. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  277. package/build/dist/Server/Utils/Telemetry.js +8 -10
  278. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  279. package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js +69 -0
  280. package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js.map +1 -0
  281. package/build/dist/Types/Dashboard/DashboardComponentType.js +4 -0
  282. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  283. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +2 -0
  284. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  285. package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephOsdListComponent.js +2 -0
  286. package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephOsdListComponent.js.map +1 -0
  287. package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephPoolListComponent.js +2 -0
  288. package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephPoolListComponent.js.map +1 -0
  289. package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxGuestListComponent.js +2 -0
  290. package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxGuestListComponent.js.map +1 -0
  291. package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxNodeListComponent.js +2 -0
  292. package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxNodeListComponent.js.map +1 -0
  293. package/build/dist/Types/Dashboard/DashboardTemplates.js +394 -0
  294. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  295. package/build/dist/Types/Icon/IconProp.js +2 -0
  296. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  297. package/build/dist/Types/Monitor/CephAlertTemplates.js +1379 -0
  298. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -0
  299. package/build/dist/Types/Monitor/CephMetricCatalog.js +353 -0
  300. package/build/dist/Types/Monitor/CephMetricCatalog.js.map +1 -0
  301. package/build/dist/Types/Monitor/MonitorStep.js +46 -0
  302. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  303. package/build/dist/Types/Monitor/MonitorStepCephMonitor.js +34 -0
  304. package/build/dist/Types/Monitor/MonitorStepCephMonitor.js.map +1 -0
  305. package/build/dist/Types/Monitor/MonitorStepProxmoxMonitor.js +36 -0
  306. package/build/dist/Types/Monitor/MonitorStepProxmoxMonitor.js.map +1 -0
  307. package/build/dist/Types/Monitor/MonitorType.js +27 -1
  308. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  309. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +743 -0
  310. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -0
  311. package/build/dist/Types/Monitor/ProxmoxMetricCatalog.js +320 -0
  312. package/build/dist/Types/Monitor/ProxmoxMetricCatalog.js.map +1 -0
  313. package/build/dist/Types/Permission.js +408 -0
  314. package/build/dist/Types/Permission.js.map +1 -1
  315. package/build/dist/Types/Telemetry/EntityType.js +11 -0
  316. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  317. package/build/dist/Types/Telemetry/ServiceType.js +2 -0
  318. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  319. package/build/dist/UI/Components/Icon/Icon.js +33 -0
  320. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  321. package/build/dist/UI/Components/ImportExport/ExportModelCard.js +50 -0
  322. package/build/dist/UI/Components/ImportExport/ExportModelCard.js.map +1 -0
  323. package/build/dist/UI/Components/ImportExport/ImportModelsModal.js +115 -0
  324. package/build/dist/UI/Components/ImportExport/ImportModelsModal.js.map +1 -0
  325. package/build/dist/UI/Components/ModelTable/ModelTable.js +166 -74
  326. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  327. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +5 -1
  328. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  329. package/build/dist/UI/Utils/ModelImportExport.js +142 -0
  330. package/build/dist/UI/Utils/ModelImportExport.js.map +1 -0
  331. package/build/dist/UI/Utils/Telemetry/Telemetry.js +11 -10
  332. package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
  333. package/build/dist/UI/Utils/TelemetryService.js +5 -2
  334. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  335. package/build/dist/Utils/Dashboard/Components/DashboardCephOsdListComponent.js +50 -0
  336. package/build/dist/Utils/Dashboard/Components/DashboardCephOsdListComponent.js.map +1 -0
  337. package/build/dist/Utils/Dashboard/Components/DashboardCephPoolListComponent.js +27 -0
  338. package/build/dist/Utils/Dashboard/Components/DashboardCephPoolListComponent.js.map +1 -0
  339. package/build/dist/Utils/Dashboard/Components/DashboardCephResourceListShared.js +46 -0
  340. package/build/dist/Utils/Dashboard/Components/DashboardCephResourceListShared.js.map +1 -0
  341. package/build/dist/Utils/Dashboard/Components/DashboardProxmoxGuestListComponent.js +55 -0
  342. package/build/dist/Utils/Dashboard/Components/DashboardProxmoxGuestListComponent.js.map +1 -0
  343. package/build/dist/Utils/Dashboard/Components/DashboardProxmoxNodeListComponent.js +42 -0
  344. package/build/dist/Utils/Dashboard/Components/DashboardProxmoxNodeListComponent.js.map +1 -0
  345. package/build/dist/Utils/Dashboard/Components/DashboardProxmoxResourceListShared.js +46 -0
  346. package/build/dist/Utils/Dashboard/Components/DashboardProxmoxResourceListShared.js.map +1 -0
  347. package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
  348. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  349. package/build/dist/Utils/ModelImportExport.js +257 -0
  350. package/build/dist/Utils/ModelImportExport.js.map +1 -0
  351. package/build/dist/Utils/Telemetry/EntityKey.js +27 -0
  352. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  353. package/build/dist/Utils/Telemetry/EntityRelationship.js +3 -0
  354. package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
  355. package/build/dist/Utils/Telemetry/HeartbeatAvailability.js +174 -0
  356. package/build/dist/Utils/Telemetry/HeartbeatAvailability.js.map +1 -0
  357. package/package.json +29 -21
@@ -0,0 +1,100 @@
1
+ import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
2
+ import Redis from "../../../Server/Infrastructure/Redis";
3
+ import OneUptimeDate from "../../../Types/Date";
4
+ import DatabaseNotConnectedException from "../../../Types/Exception/DatabaseNotConnectedException";
5
+
6
+ jest.mock("../../../Server/Infrastructure/Redis", () => {
7
+ return {
8
+ __esModule: true,
9
+ default: {
10
+ getClient: jest.fn(),
11
+ isConnected: jest.fn(),
12
+ },
13
+ };
14
+ });
15
+
16
+ type MockClient = {
17
+ set: jest.Mock;
18
+ expire: jest.Mock;
19
+ get: jest.Mock;
20
+ };
21
+
22
+ describe("GlobalCache.setString", () => {
23
+ let client: MockClient;
24
+
25
+ beforeEach(() => {
26
+ client = {
27
+ set: jest.fn().mockResolvedValue("OK"),
28
+ expire: jest.fn().mockResolvedValue(1),
29
+ get: jest.fn(),
30
+ };
31
+ (Redis.getClient as jest.Mock).mockReturnValue(client);
32
+ (Redis.isConnected as jest.Mock).mockReturnValue(true);
33
+ });
34
+
35
+ afterEach(() => {
36
+ jest.clearAllMocks();
37
+ });
38
+
39
+ /*
40
+ * The TTL must be applied atomically with the SET. A separate
41
+ * SET + EXPIRE pair can crash in between and leave a key that
42
+ * never expires — for fence/throttle keys (e.g. the OTel ingest
43
+ * maintenance fence) that permanently suppresses the work the
44
+ * key gates.
45
+ */
46
+ test("sets the value and TTL in one atomic SET ... EX call", async () => {
47
+ await GlobalCache.setString("ns", "key", "value", {
48
+ expiresInSeconds: 60,
49
+ });
50
+
51
+ expect(client.set).toHaveBeenCalledTimes(1);
52
+ expect(client.set).toHaveBeenCalledWith("ns-key", "value", "EX", 60);
53
+ expect(client.expire).not.toHaveBeenCalled();
54
+ });
55
+
56
+ test("defaults the TTL to 30 days when no option is passed", async () => {
57
+ await GlobalCache.setString("ns", "key", "value");
58
+
59
+ expect(client.set).toHaveBeenCalledTimes(1);
60
+ expect(client.set).toHaveBeenCalledWith(
61
+ "ns-key",
62
+ "value",
63
+ "EX",
64
+ OneUptimeDate.getSecondsInDays(30),
65
+ );
66
+ expect(client.expire).not.toHaveBeenCalled();
67
+ });
68
+
69
+ test("throws when the cache is not connected", async () => {
70
+ (Redis.isConnected as jest.Mock).mockReturnValue(false);
71
+
72
+ await expect(GlobalCache.setString("ns", "key", "value")).rejects.toThrow(
73
+ DatabaseNotConnectedException,
74
+ );
75
+ expect(client.set).not.toHaveBeenCalled();
76
+ });
77
+
78
+ test("setStringArray and setJSON funnel through the atomic setString", async () => {
79
+ await GlobalCache.setStringArray("ns", "arr", ["a", "b"], {
80
+ expiresInSeconds: 120,
81
+ });
82
+ await GlobalCache.setJSON("ns", "obj", { a: 1 }, { expiresInSeconds: 180 });
83
+
84
+ expect(client.set).toHaveBeenNthCalledWith(
85
+ 1,
86
+ "ns-arr",
87
+ JSON.stringify(["a", "b"]),
88
+ "EX",
89
+ 120,
90
+ );
91
+ expect(client.set).toHaveBeenNthCalledWith(
92
+ 2,
93
+ "ns-obj",
94
+ expect.any(String),
95
+ "EX",
96
+ 180,
97
+ );
98
+ expect(client.expire).not.toHaveBeenCalled();
99
+ });
100
+ });
@@ -1057,6 +1057,111 @@ describe("BillingService", () => {
1057
1057
  });
1058
1058
  });
1059
1059
 
1060
+ it("should mark the customer's default payment method and order it first", async () => {
1061
+ const mockPaymentMethodsResponse: {
1062
+ data: Array<Stripe.PaymentMethod>;
1063
+ } = {
1064
+ data: [
1065
+ {
1066
+ id: "pm_123",
1067
+ type: "card",
1068
+ // @ts-expect-error - Simplified mock card object for testing without all required Stripe card properties
1069
+ card: { last4: "4242", brand: "mastercard" },
1070
+ },
1071
+ {
1072
+ id: "pm_456",
1073
+ type: "card",
1074
+ // @ts-expect-error - Simplified mock card object for testing without all required Stripe card properties
1075
+ card: { last4: "4343", brand: "mastercard" },
1076
+ },
1077
+ ],
1078
+ };
1079
+ mockStripe.paymentMethods.list = getJestMockFunction()
1080
+ .mockResolvedValueOnce(mockPaymentMethodsResponse)
1081
+ .mockResolvedValue({ data: [] });
1082
+
1083
+ const customerWithDefault: Stripe.Customer = getStripeCustomer(
1084
+ customer.id.toString(),
1085
+ );
1086
+ customerWithDefault.invoice_settings.default_payment_method = "pm_456";
1087
+ mockStripe.customers.retrieve =
1088
+ getJestMockFunction().mockResolvedValue(customerWithDefault);
1089
+
1090
+ const paymentMethods: PaymentMethod[] =
1091
+ await billingService.getPaymentMethods(customerId);
1092
+
1093
+ expect(paymentMethods).toHaveLength(2);
1094
+ expect(paymentMethods[0]?.id).toBe("pm_456");
1095
+ expect(paymentMethods[0]?.isDefault).toBe(true);
1096
+ expect(paymentMethods[1]?.id).toBe("pm_123");
1097
+ expect(paymentMethods[1]?.isDefault).toBe(false);
1098
+ expect(mockStripe.customers.update).not.toHaveBeenCalled();
1099
+ });
1100
+
1101
+ it("should not mark any payment method as default when the default is not in the list", async () => {
1102
+ const mockPaymentMethodsResponse: {
1103
+ data: Array<Stripe.PaymentMethod>;
1104
+ } = {
1105
+ data: [
1106
+ {
1107
+ id: "pm_123",
1108
+ type: "card",
1109
+ // @ts-expect-error - Simplified mock card object for testing without all required Stripe card properties
1110
+ card: { last4: "4242", brand: "mastercard" },
1111
+ },
1112
+ ],
1113
+ };
1114
+ mockStripe.paymentMethods.list = getJestMockFunction()
1115
+ .mockResolvedValueOnce(mockPaymentMethodsResponse)
1116
+ .mockResolvedValue({ data: [] });
1117
+
1118
+ const customerWithDetachedDefault: Stripe.Customer = getStripeCustomer(
1119
+ customer.id.toString(),
1120
+ );
1121
+ customerWithDetachedDefault.invoice_settings.default_payment_method =
1122
+ "pm_detached";
1123
+ mockStripe.customers.retrieve = getJestMockFunction().mockResolvedValue(
1124
+ customerWithDetachedDefault,
1125
+ );
1126
+
1127
+ const paymentMethods: PaymentMethod[] =
1128
+ await billingService.getPaymentMethods(customerId);
1129
+
1130
+ expect(paymentMethods).toHaveLength(1);
1131
+ expect(paymentMethods[0]?.isDefault).toBe(false);
1132
+ expect(mockStripe.customers.update).not.toHaveBeenCalled();
1133
+ });
1134
+
1135
+ it("should set the first payment method as default when none is set", async () => {
1136
+ const mockPaymentMethodsResponse: {
1137
+ data: Array<Stripe.PaymentMethod>;
1138
+ } = {
1139
+ data: [
1140
+ {
1141
+ id: "pm_123",
1142
+ type: "card",
1143
+ // @ts-expect-error - Simplified mock card object for testing without all required Stripe card properties
1144
+ card: { last4: "4242", brand: "mastercard" },
1145
+ },
1146
+ ],
1147
+ };
1148
+ mockStripe.paymentMethods.list = getJestMockFunction()
1149
+ .mockResolvedValueOnce(mockPaymentMethodsResponse)
1150
+ .mockResolvedValue({ data: [] });
1151
+ mockStripe.customers.retrieve =
1152
+ getJestMockFunction().mockResolvedValue(mockCustomer);
1153
+
1154
+ const paymentMethods: PaymentMethod[] =
1155
+ await billingService.getPaymentMethods(customerId);
1156
+
1157
+ expect(paymentMethods[0]?.isDefault).toBe(true);
1158
+ expect(mockStripe.customers.update).toHaveBeenCalledWith(customerId, {
1159
+ invoice_settings: {
1160
+ default_payment_method: "pm_123",
1161
+ },
1162
+ });
1163
+ });
1164
+
1060
1165
  it("should return an empty array if no payment methods are present", async () => {
1061
1166
  const mockEmptyPaymentMethodsResponse: {
1062
1167
  data: Array<PaymentMethod>;
@@ -1440,6 +1545,224 @@ describe("BillingService", () => {
1440
1545
  },
1441
1546
  );
1442
1547
  });
1548
+
1549
+ const mockTwoPaymentMethodsResponse: PaymentMethodsResponse = {
1550
+ data: [
1551
+ {
1552
+ id: "pm_123",
1553
+ type: "card",
1554
+ last4Digits: "4242",
1555
+ isDefault: false,
1556
+ },
1557
+ {
1558
+ id: "pm_456",
1559
+ type: "card",
1560
+ last4Digits: "4343",
1561
+ isDefault: false,
1562
+ },
1563
+ ],
1564
+ };
1565
+
1566
+ type GetCardDeclinedErrorFunction = () => Error;
1567
+
1568
+ const getCardDeclinedError: GetCardDeclinedErrorFunction = (): Error => {
1569
+ return Object.assign(new Error("Your card was declined."), {
1570
+ type: "StripeCardError",
1571
+ code: "card_declined",
1572
+ });
1573
+ };
1574
+
1575
+ it("should fall back to the next payment method when the first is declined", async () => {
1576
+ mockStripe.paymentMethods.list = getJestMockFunction()
1577
+ .mockResolvedValueOnce(mockTwoPaymentMethodsResponse)
1578
+ .mockResolvedValue({ data: [] });
1579
+ mockStripe.customers.retrieve =
1580
+ getJestMockFunction().mockResolvedValue(mockCustomer);
1581
+
1582
+ const mockPaidInvoice: Stripe.Invoice = getStripeInvoice();
1583
+ mockStripe.invoices.pay = getJestMockFunction()
1584
+ .mockRejectedValueOnce(getCardDeclinedError())
1585
+ .mockResolvedValue(mockPaidInvoice);
1586
+
1587
+ const paidInvoice: Invoice = await billingService.payInvoice(
1588
+ customerId,
1589
+ mockPaidInvoice.id || "",
1590
+ );
1591
+
1592
+ expect(paidInvoice.id).toBe(mockPaidInvoice.id);
1593
+ expect(mockStripe.invoices.pay).toHaveBeenCalledTimes(2);
1594
+ expect(mockStripe.invoices.pay).toHaveBeenNthCalledWith(
1595
+ 1,
1596
+ mockPaidInvoice.id,
1597
+ {
1598
+ payment_method: "pm_123",
1599
+ },
1600
+ );
1601
+ expect(mockStripe.invoices.pay).toHaveBeenNthCalledWith(
1602
+ 2,
1603
+ mockPaidInvoice.id,
1604
+ {
1605
+ payment_method: "pm_456",
1606
+ },
1607
+ );
1608
+ });
1609
+
1610
+ it("should charge the customer's default payment method first", async () => {
1611
+ mockStripe.paymentMethods.list = getJestMockFunction()
1612
+ .mockResolvedValueOnce(mockTwoPaymentMethodsResponse)
1613
+ .mockResolvedValue({ data: [] });
1614
+
1615
+ const customerWithDefault: Stripe.Customer = getStripeCustomer(
1616
+ customer.id.toString(),
1617
+ );
1618
+ customerWithDefault.invoice_settings.default_payment_method = "pm_456";
1619
+ mockStripe.customers.retrieve =
1620
+ getJestMockFunction().mockResolvedValue(customerWithDefault);
1621
+
1622
+ const mockPaidInvoice: Stripe.Invoice = getStripeInvoice();
1623
+ mockStripe.invoices.pay =
1624
+ getJestMockFunction().mockResolvedValue(mockPaidInvoice);
1625
+
1626
+ await billingService.payInvoice(customerId, mockPaidInvoice.id || "");
1627
+
1628
+ expect(mockStripe.invoices.pay).toHaveBeenCalledTimes(1);
1629
+ expect(mockStripe.invoices.pay).toHaveBeenCalledWith(
1630
+ mockPaidInvoice.id,
1631
+ {
1632
+ payment_method: "pm_456",
1633
+ },
1634
+ );
1635
+ });
1636
+
1637
+ it("should throw the last error when all payment methods are declined", async () => {
1638
+ mockStripe.paymentMethods.list = getJestMockFunction()
1639
+ .mockResolvedValueOnce(mockTwoPaymentMethodsResponse)
1640
+ .mockResolvedValue({ data: [] });
1641
+ mockStripe.customers.retrieve =
1642
+ getJestMockFunction().mockResolvedValue(mockCustomer);
1643
+
1644
+ const lastError: Error = Object.assign(
1645
+ new Error("Insufficient funds."),
1646
+ {
1647
+ type: "StripeCardError",
1648
+ code: "card_declined",
1649
+ },
1650
+ );
1651
+ mockStripe.invoices.pay = getJestMockFunction()
1652
+ .mockRejectedValueOnce(getCardDeclinedError())
1653
+ .mockRejectedValueOnce(lastError);
1654
+
1655
+ await expect(
1656
+ billingService.payInvoice(customerId, invoiceId),
1657
+ ).rejects.toThrow(lastError);
1658
+ expect(mockStripe.invoices.pay).toHaveBeenCalledTimes(2);
1659
+ });
1660
+
1661
+ it("should not try other payment methods on errors unrelated to the payment method", async () => {
1662
+ mockStripe.paymentMethods.list = getJestMockFunction()
1663
+ .mockResolvedValueOnce(mockTwoPaymentMethodsResponse)
1664
+ .mockResolvedValue({ data: [] });
1665
+ mockStripe.customers.retrieve =
1666
+ getJestMockFunction().mockResolvedValue(mockCustomer);
1667
+
1668
+ const invoiceStateError: Error = Object.assign(
1669
+ new Error("Invoice is already paid."),
1670
+ {
1671
+ type: "StripeInvalidRequestError",
1672
+ code: "invoice_already_paid",
1673
+ },
1674
+ );
1675
+ mockStripe.invoices.pay =
1676
+ getJestMockFunction().mockRejectedValue(invoiceStateError);
1677
+
1678
+ await expect(
1679
+ billingService.payInvoice(customerId, invoiceId),
1680
+ ).rejects.toThrow(invoiceStateError);
1681
+ expect(mockStripe.invoices.pay).toHaveBeenCalledTimes(1);
1682
+ });
1683
+
1684
+ it("should fall back to the next payment method on payment method and bank account errors", async () => {
1685
+ mockStripe.paymentMethods.list = getJestMockFunction()
1686
+ .mockResolvedValueOnce(mockTwoPaymentMethodsResponse)
1687
+ .mockResolvedValue({ data: [] });
1688
+ mockStripe.customers.retrieve =
1689
+ getJestMockFunction().mockResolvedValue(mockCustomer);
1690
+
1691
+ const bankAccountError: Error = Object.assign(
1692
+ new Error("This bank account cannot be used."),
1693
+ {
1694
+ type: "StripeInvalidRequestError",
1695
+ code: "bank_account_unusable",
1696
+ },
1697
+ );
1698
+
1699
+ const mockPaidInvoice: Stripe.Invoice = getStripeInvoice();
1700
+ mockStripe.invoices.pay = getJestMockFunction()
1701
+ .mockRejectedValueOnce(bankAccountError)
1702
+ .mockResolvedValue(mockPaidInvoice);
1703
+
1704
+ const paidInvoice: Invoice = await billingService.payInvoice(
1705
+ customerId,
1706
+ mockPaidInvoice.id || "",
1707
+ );
1708
+
1709
+ expect(paidInvoice.id).toBe(mockPaidInvoice.id);
1710
+ expect(mockStripe.invoices.pay).toHaveBeenCalledTimes(2);
1711
+ });
1712
+
1713
+ it("should not try other payment methods when authentication is required", async () => {
1714
+ /*
1715
+ * the interactive 3DS flow in BillingInvoiceAPI must surface
1716
+ * authentication for the default payment method instead.
1717
+ */
1718
+ mockStripe.paymentMethods.list = getJestMockFunction()
1719
+ .mockResolvedValueOnce(mockTwoPaymentMethodsResponse)
1720
+ .mockResolvedValue({ data: [] });
1721
+ mockStripe.customers.retrieve =
1722
+ getJestMockFunction().mockResolvedValue(mockCustomer);
1723
+
1724
+ const requiresActionError: Error = Object.assign(
1725
+ new Error("This payment requires additional authentication."),
1726
+ {
1727
+ type: "StripeInvalidRequestError",
1728
+ code: "invoice_payment_intent_requires_action",
1729
+ },
1730
+ );
1731
+ mockStripe.invoices.pay =
1732
+ getJestMockFunction().mockRejectedValue(requiresActionError);
1733
+
1734
+ await expect(
1735
+ billingService.payInvoice(customerId, invoiceId),
1736
+ ).rejects.toThrow(requiresActionError);
1737
+ expect(mockStripe.invoices.pay).toHaveBeenCalledTimes(1);
1738
+ });
1739
+
1740
+ it("should try at most three payment methods", async () => {
1741
+ const mockFourPaymentMethodsResponse: PaymentMethodsResponse = {
1742
+ data: ["pm_1", "pm_2", "pm_3", "pm_4"].map((id: string) => {
1743
+ return {
1744
+ id,
1745
+ type: "card",
1746
+ last4Digits: "4242",
1747
+ isDefault: false,
1748
+ };
1749
+ }),
1750
+ };
1751
+ mockStripe.paymentMethods.list = getJestMockFunction()
1752
+ .mockResolvedValueOnce(mockFourPaymentMethodsResponse)
1753
+ .mockResolvedValue({ data: [] });
1754
+ mockStripe.customers.retrieve =
1755
+ getJestMockFunction().mockResolvedValue(mockCustomer);
1756
+
1757
+ mockStripe.invoices.pay = getJestMockFunction().mockRejectedValue(
1758
+ getCardDeclinedError(),
1759
+ );
1760
+
1761
+ await expect(
1762
+ billingService.payInvoice(customerId, invoiceId),
1763
+ ).rejects.toThrow("Your card was declined.");
1764
+ expect(mockStripe.invoices.pay).toHaveBeenCalledTimes(3);
1765
+ });
1443
1766
  });
1444
1767
  });
1445
1768
  });
@@ -0,0 +1,264 @@
1
+ import CephResourceService, {
2
+ ParsedCephResource,
3
+ CephResourceLatestMetric,
4
+ } from "../../../Server/Services/CephResourceService";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+
7
+ /*
8
+ * WI-21: the CephResource inventory write path — same mocked-query-
9
+ * runner shape as the ProxmoxResourceService tests. Locks in the
10
+ * COALESCE-per-column upsert (a batch lacking a *_metadata series must
11
+ * not blank hostname/deviceClass/daemonVersion), the lastSeenAt
12
+ * dominance guard, exact parameter ordering, and the bigint-as-string
13
+ * convention on the latest-metric mirror.
14
+ */
15
+
16
+ const PROJECT_ID: ObjectID = ObjectID.generate();
17
+ const CLUSTER_ID: ObjectID = ObjectID.generate();
18
+
19
+ type QueryCall = [string, Array<unknown>];
20
+
21
+ function mockQueryRunner(result: unknown = []): jest.Mock {
22
+ const query: jest.Mock = jest.fn().mockResolvedValue(result);
23
+ jest
24
+ .spyOn(CephResourceService, "getRepository")
25
+ .mockReturnValue({ manager: { query } } as any);
26
+ return query;
27
+ }
28
+
29
+ function parsedResource(
30
+ overrides: Partial<ParsedCephResource> = {},
31
+ ): ParsedCephResource {
32
+ return {
33
+ kind: "Osd",
34
+ externalId: "osd.3",
35
+ name: null,
36
+ hostname: "ceph-node-1",
37
+ daemonVersion: "ceph version 18.2.2 reef",
38
+ deviceClass: "ssd",
39
+ isUp: true,
40
+ isIn: true,
41
+ inQuorum: null,
42
+ lastSeenAt: new Date("2026-06-13T00:00:00.000Z"),
43
+ ...overrides,
44
+ };
45
+ }
46
+
47
+ afterEach(() => {
48
+ jest.restoreAllMocks();
49
+ });
50
+
51
+ describe("CephResourceService.bulkUpsert", () => {
52
+ test("does nothing for an empty batch", async () => {
53
+ const query: jest.Mock = mockQueryRunner();
54
+ await CephResourceService.bulkUpsert({
55
+ projectId: PROJECT_ID,
56
+ cephClusterId: CLUSTER_ID,
57
+ resources: [],
58
+ });
59
+ expect(query).not.toHaveBeenCalled();
60
+ });
61
+
62
+ test("emits an ON CONFLICT upsert with COALESCE per identity/status column", async () => {
63
+ const query: jest.Mock = mockQueryRunner();
64
+ await CephResourceService.bulkUpsert({
65
+ projectId: PROJECT_ID,
66
+ cephClusterId: CLUSTER_ID,
67
+ resources: [parsedResource()],
68
+ });
69
+
70
+ expect(query).toHaveBeenCalledTimes(1);
71
+ const [sql] = query.mock.calls[0] as QueryCall;
72
+
73
+ expect(sql).toContain('INSERT INTO "CephResource"');
74
+ expect(sql).toContain(
75
+ 'ON CONFLICT ("projectId", "cephClusterId", "kind", "externalId")',
76
+ );
77
+
78
+ for (const column of [
79
+ "name",
80
+ "hostname",
81
+ "daemonVersion",
82
+ "deviceClass",
83
+ "isUp",
84
+ "isIn",
85
+ "inQuorum",
86
+ ]) {
87
+ expect(sql).toContain(
88
+ `"${column}" = COALESCE(EXCLUDED."${column}", "CephResource"."${column}")`,
89
+ );
90
+ }
91
+
92
+ expect(sql).toContain('"lastSeenAt" = EXCLUDED."lastSeenAt"');
93
+ expect(sql).toContain(
94
+ 'WHERE EXCLUDED."lastSeenAt" >= "CephResource"."lastSeenAt"',
95
+ );
96
+ });
97
+
98
+ test("parameter tuple matches the INSERT column order exactly", async () => {
99
+ const query: jest.Mock = mockQueryRunner();
100
+ const lastSeenAt: Date = new Date("2026-06-13T00:00:00.000Z");
101
+ await CephResourceService.bulkUpsert({
102
+ projectId: PROJECT_ID,
103
+ cephClusterId: CLUSTER_ID,
104
+ resources: [parsedResource({ lastSeenAt })],
105
+ });
106
+
107
+ const [sql, params] = query.mock.calls[0] as QueryCall;
108
+
109
+ // 13 columns per row: a 1-row batch carries exactly $1..$13.
110
+ expect(params).toHaveLength(13);
111
+ expect(sql).toContain(
112
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
113
+ );
114
+ expect(params).toEqual([
115
+ PROJECT_ID.toString(),
116
+ CLUSTER_ID.toString(),
117
+ "Osd",
118
+ "osd.3",
119
+ null, // name — pool-only identity, NULL for daemons
120
+ "ceph-node-1",
121
+ "ceph version 18.2.2 reef",
122
+ "ssd",
123
+ true, // isUp
124
+ true, // isIn
125
+ null, // inQuorum — mon-only, NULL for OSDs
126
+ lastSeenAt,
127
+ 0, // version
128
+ ]);
129
+ });
130
+
131
+ test("chunks batches of more than 500 rows into multiple statements", async () => {
132
+ const query: jest.Mock = mockQueryRunner();
133
+ const resources: Array<ParsedCephResource> = [];
134
+ for (let i: number = 0; i < 501; i++) {
135
+ resources.push(parsedResource({ externalId: `osd.${i}` }));
136
+ }
137
+
138
+ await CephResourceService.bulkUpsert({
139
+ projectId: PROJECT_ID,
140
+ cephClusterId: CLUSTER_ID,
141
+ resources,
142
+ });
143
+
144
+ expect(query).toHaveBeenCalledTimes(2);
145
+ const [, firstParams] = query.mock.calls[0] as QueryCall;
146
+ const [, secondParams] = query.mock.calls[1] as QueryCall;
147
+ expect(firstParams).toHaveLength(500 * 13);
148
+ expect(secondParams).toHaveLength(13);
149
+ });
150
+ });
151
+
152
+ describe("CephResourceService.bulkUpdateLatestMetrics", () => {
153
+ function latestMetric(
154
+ overrides: Partial<CephResourceLatestMetric> = {},
155
+ ): CephResourceLatestMetric {
156
+ return {
157
+ kind: "Osd",
158
+ externalId: "osd.3",
159
+ statBytes: 4000000000,
160
+ statBytesUsed: 1000000000,
161
+ applyLatencyMs: 12.5,
162
+ commitLatencyMs: 10,
163
+ pgCount: 96.7,
164
+ storedBytes: null,
165
+ maxAvailBytes: null,
166
+ objects: null,
167
+ readOpsCounter: null,
168
+ writeOpsCounter: null,
169
+ observedAt: new Date("2026-06-13T00:00:00.000Z"),
170
+ ...overrides,
171
+ };
172
+ }
173
+
174
+ test("COALESCEs every mirror column and guards on metricsUpdatedAt", async () => {
175
+ const query: jest.Mock = mockQueryRunner();
176
+ await CephResourceService.bulkUpdateLatestMetrics({
177
+ projectId: PROJECT_ID,
178
+ cephClusterId: CLUSTER_ID,
179
+ metrics: [latestMetric()],
180
+ });
181
+
182
+ const [sql] = query.mock.calls[0] as QueryCall;
183
+ expect(sql).toContain('UPDATE "CephResource"');
184
+ for (const column of [
185
+ "statBytes",
186
+ "statBytesUsed",
187
+ "applyLatencyMs",
188
+ "commitLatencyMs",
189
+ "pgCount",
190
+ "storedBytes",
191
+ "maxAvailBytes",
192
+ "objects",
193
+ "readOpsCounter",
194
+ "writeOpsCounter",
195
+ ]) {
196
+ expect(sql).toContain(
197
+ `"${column}" = COALESCE(v."${column}", c."${column}")`,
198
+ );
199
+ }
200
+ expect(sql).toContain(
201
+ '(c."metricsUpdatedAt" IS NULL OR v."observedAt" >= c."metricsUpdatedAt")',
202
+ );
203
+ });
204
+
205
+ test("bigints ride as strings, integers truncate, missing series stay NULL", async () => {
206
+ const query: jest.Mock = mockQueryRunner();
207
+ await CephResourceService.bulkUpdateLatestMetrics({
208
+ projectId: PROJECT_ID,
209
+ cephClusterId: CLUSTER_ID,
210
+ metrics: [latestMetric()],
211
+ });
212
+
213
+ const [, params] = query.mock.calls[0] as QueryCall;
214
+ /*
215
+ * Params: projectId, clusterId, then per row (kind, externalId,
216
+ * statBytes, statBytesUsed, applyLatencyMs, commitLatencyMs,
217
+ * pgCount, storedBytes, maxAvailBytes, objects, readOpsCounter,
218
+ * writeOpsCounter, observedAt).
219
+ */
220
+ expect(params).toHaveLength(2 + 13);
221
+ expect(params[4]).toBe("4000000000"); // statBytes as string
222
+ expect(params[6]).toBe(12.5); // latency stays numeric
223
+ expect(params[8]).toBe(96); // pgCount truncated
224
+ expect(params[9]).toBeNull(); // storedBytes — OSD row, not a pool
225
+ expect(params[12]).toBeNull(); // readOpsCounter
226
+ });
227
+ });
228
+
229
+ describe("CephResourceService.deleteStaleForCluster", () => {
230
+ test("normalizes the postgres [rows, affected] DELETE result", async () => {
231
+ mockQueryRunner([[], 3]);
232
+ const affected: number = await CephResourceService.deleteStaleForCluster({
233
+ cephClusterId: CLUSTER_ID,
234
+ olderThan: new Date("2026-06-13T00:00:00.000Z"),
235
+ });
236
+ expect(affected).toBe(3);
237
+ });
238
+ });
239
+
240
+ describe("CephResourceService.getStaleThresholdMinutes", () => {
241
+ const ENV_KEY: string = "CEPH_INVENTORY_STALE_MINUTES";
242
+ let savedValue: string | undefined;
243
+
244
+ beforeEach(() => {
245
+ savedValue = process.env[ENV_KEY];
246
+ });
247
+
248
+ afterEach(() => {
249
+ if (savedValue === undefined) {
250
+ delete process.env[ENV_KEY];
251
+ } else {
252
+ process.env[ENV_KEY] = savedValue;
253
+ }
254
+ });
255
+
256
+ test("defaults to 15 minutes and clamps bad overrides", () => {
257
+ delete process.env[ENV_KEY];
258
+ expect(CephResourceService.getStaleThresholdMinutes()).toBe(15);
259
+ process.env[ENV_KEY] = "45";
260
+ expect(CephResourceService.getStaleThresholdMinutes()).toBe(45);
261
+ process.env[ENV_KEY] = "1";
262
+ expect(CephResourceService.getStaleThresholdMinutes()).toBe(15);
263
+ });
264
+ });