@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
@@ -533,6 +533,70 @@ export default class TelemetryEntity {
533
533
  return { entityType: EntityType.KubernetesDeployment, id };
534
534
  },
535
535
 
536
+ // proxmox.cluster — proxmox.cluster.name only (see proxmoxClusterIdentity).
537
+ (attrs: EntityAttributes) => {
538
+ const id: Dictionary<string> | null =
539
+ TelemetryEntity.proxmoxClusterIdentity(attrs);
540
+ return id ? { entityType: EntityType.ProxmoxCluster, id } : null;
541
+ },
542
+
543
+ // proxmox.node — cluster + proxmox.node.name.
544
+ (attrs: EntityAttributes) => {
545
+ const nodeName: string | null = TelemetryEntity.str(
546
+ attrs,
547
+ "proxmox.node.name",
548
+ );
549
+ if (!nodeName) {
550
+ return null;
551
+ }
552
+ const id: Dictionary<string> = {
553
+ ...(TelemetryEntity.proxmoxClusterIdentity(attrs) || {}),
554
+ "proxmox.node.name": nodeName,
555
+ };
556
+ return { entityType: EntityType.ProxmoxNode, id };
557
+ },
558
+
559
+ /*
560
+ * proxmox.guest — cluster + proxmox.guest.vmid. The node name is
561
+ * deliberately NOT part of guest identity: vmids are cluster-unique
562
+ * and a live migration moves a guest between nodes without changing
563
+ * what it is, so folding the node in would fork the key on every
564
+ * migration. Guest name/type are descriptive (a guest can be renamed).
565
+ */
566
+ (attrs: EntityAttributes) => {
567
+ const vmid: string | null = TelemetryEntity.str(
568
+ attrs,
569
+ "proxmox.guest.vmid",
570
+ );
571
+ if (!vmid) {
572
+ return null;
573
+ }
574
+ const id: Dictionary<string> = {
575
+ ...(TelemetryEntity.proxmoxClusterIdentity(attrs) || {}),
576
+ "proxmox.guest.vmid": vmid,
577
+ };
578
+ return { entityType: EntityType.ProxmoxGuest, id };
579
+ },
580
+
581
+ /*
582
+ * ceph.cluster — ceph.cluster.name only. `ceph.cluster.fsid` is
583
+ * descriptive, not identity: the typed Postgres row (CephCluster) and
584
+ * the read side (`EntityKey.keyForCephCluster`) are name-based, and
585
+ * the fsid is only optionally stamped by the agent.
586
+ */
587
+ (attrs: EntityAttributes) => {
588
+ const name: string | null = TelemetryEntity.str(
589
+ attrs,
590
+ "ceph.cluster.name",
591
+ );
592
+ return name
593
+ ? {
594
+ entityType: EntityType.CephCluster,
595
+ id: { "ceph.cluster.name": name },
596
+ }
597
+ : null;
598
+ },
599
+
536
600
  /*
537
601
  * container — container.id. High-churn: flows as a membership key but
538
602
  * is membership-only by default (not promoted to a registry row
@@ -652,6 +716,8 @@ export default class TelemetryEntity {
652
716
  "container.image.tag",
653
717
  "container.image.tags",
654
718
  ],
719
+ [EntityType.ProxmoxGuest]: ["proxmox.guest.name", "proxmox.guest.type"],
720
+ [EntityType.CephCluster]: ["ceph.cluster.fsid"],
655
721
  };
656
722
 
657
723
  private static descriptiveAttributesFor(
@@ -739,6 +805,25 @@ export default class TelemetryEntity {
739
805
  return null;
740
806
  }
741
807
 
808
+ /*
809
+ * Proxmox cluster identity — proxmox.cluster.name only, mirroring
810
+ * k8sClusterIdentity above: the typed Postgres row (ProxmoxCluster) and
811
+ * the read side (`EntityKey.keyForProxmoxCluster`) are name-based, and
812
+ * the attribute is the user-configured join key our agent stamps on
813
+ * every resource (see Internal/Roadmap/ProxmoxCephProducts.md §1). This
814
+ * identity is also folded into the composite proxmox node/guest
815
+ * identities, which must stay name-based with it.
816
+ */
817
+ private static proxmoxClusterIdentity(
818
+ attrs: EntityAttributes,
819
+ ): Dictionary<string> | null {
820
+ const name: string | null = this.str(attrs, "proxmox.cluster.name");
821
+ if (name) {
822
+ return { "proxmox.cluster.name": name };
823
+ }
824
+ return null;
825
+ }
826
+
742
827
  /** Copy `key` from attrs into `id` (canonicalized) when scalar & present. */
743
828
  private static addIfPresent(
744
829
  id: Dictionary<string>,
@@ -24,16 +24,14 @@ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
24
24
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
25
25
  import { AWSXRayIdGenerator } from "@opentelemetry/id-generator-aws-xray";
26
26
  import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base";
27
- import { Resource } from "@opentelemetry/resources";
27
+ import { Resource, resourceFromAttributes } from "@opentelemetry/resources";
28
28
  import {
29
29
  BatchLogRecordProcessor,
30
30
  LoggerProvider,
31
31
  LogRecordProcessor,
32
32
  type LoggerProviderConfig,
33
33
  } from "@opentelemetry/sdk-logs";
34
- import type { Resource as LogsResource } from "@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources/build/src/Resource";
35
34
  import {
36
- Aggregation,
37
35
  MeterProvider,
38
36
  PeriodicExportingMetricReader,
39
37
  } from "@opentelemetry/sdk-metrics";
@@ -56,7 +54,7 @@ import GracefulShutdown, { ShutdownPriority } from "./GracefulShutdown";
56
54
  import ContextSpanProcessor from "./Telemetry/ContextSpanProcessor";
57
55
  import RuntimeMetrics from "./Telemetry/RuntimeMetrics";
58
56
 
59
- type ResourceWithRawAttributes = LogsResource & {
57
+ type ResourceWithRawAttributes = Resource & {
60
58
  getRawAttributes?: () => Array<[string, AttributeValue | undefined]>;
61
59
  };
62
60
 
@@ -165,7 +163,7 @@ export default class Telemetry {
165
163
  }
166
164
 
167
165
  public static getResource(data: { serviceName: string }): Resource {
168
- return new Resource({
166
+ return resourceFromAttributes({
169
167
  [ATTR_SERVICE_NAME]: data.serviceName,
170
168
  [ATTR_SERVICE_VERSION]: AppVersion,
171
169
  ["deployment.environment"]: Env,
@@ -203,20 +201,11 @@ export default class Telemetry {
203
201
  compression: CompressionAlgorithm.GZIP,
204
202
  }) as unknown as PushMetricExporter;
205
203
 
206
- // Force an SDK-side aggregation selector that matches the modern metrics API.
207
- if (
208
- typeof (metricExporter as { selectAggregation?: unknown })
209
- .selectAggregation === "function"
210
- ) {
211
- (
212
- metricExporter as unknown as {
213
- selectAggregation: (..._args: Array<unknown>) => Aggregation;
214
- }
215
- ).selectAggregation = () => {
216
- return Aggregation.Default();
217
- };
218
- }
219
-
204
+ /*
205
+ * No aggregation-selector shim is needed anymore: the OTLP metric
206
+ * exporter and the sdk-metrics package now come from the same release
207
+ * line, so the exporter's default selector already matches the SDK.
208
+ */
220
209
  this.metricReader = new PeriodicExportingMetricReader({
221
210
  exporter: metricExporter,
222
211
  });
@@ -0,0 +1,194 @@
1
+ import BillingInvoiceAPI from "../../../Server/API/BillingInvoiceAPI";
2
+ import BillingInvoiceService from "../../../Server/Services/BillingInvoiceService";
3
+ import BillingService from "../../../Server/Services/BillingService";
4
+ import ProjectService from "../../../Server/Services/ProjectService";
5
+ import {
6
+ NextFunction,
7
+ OneUptimeRequest,
8
+ OneUptimeResponse,
9
+ } from "../../../Server/Utils/Express";
10
+ import Response from "../../../Server/Utils/Response";
11
+ import { mockRouter } from "./Helpers";
12
+ import { describe, expect, it } from "@jest/globals";
13
+ import BadDataException from "../../../Types/Exception/BadDataException";
14
+ import ObjectID from "../../../Types/ObjectID";
15
+ import Permission, { UserPermission } from "../../../Types/Permission";
16
+ import BillingInvoice from "../../../Models/DatabaseModels/BillingInvoice";
17
+ import Project from "../../../Models/DatabaseModels/Project";
18
+
19
+ jest.mock("../../../Server/Utils/Express", () => {
20
+ return {
21
+ getRouter: () => {
22
+ return mockRouter;
23
+ },
24
+ };
25
+ });
26
+
27
+ jest.mock("../../../Server/Utils/Response", () => {
28
+ return {
29
+ sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => {
30
+ return args;
31
+ }),
32
+ sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => {
33
+ return args;
34
+ }),
35
+ sendEmptySuccessResponse: jest.fn(),
36
+ sendEntityResponse: jest.fn().mockImplementation((...args: []) => {
37
+ return args;
38
+ }),
39
+ sendErrorResponse: jest.fn().mockImplementation((...args: []) => {
40
+ return args;
41
+ }),
42
+ };
43
+ });
44
+
45
+ jest.mock("../../../Server/EnvironmentConfig", () => {
46
+ return {
47
+ ...jest.requireActual("../../../Server/EnvironmentConfig"),
48
+ IsBillingEnabled: true,
49
+ };
50
+ });
51
+
52
+ jest.mock("../../../Server/Services/BillingInvoiceService");
53
+ jest.mock("../../../Server/Services/BillingService");
54
+ jest.mock("../../../Server/Services/ProjectService");
55
+
56
+ describe("BillingInvoiceAPI", () => {
57
+ let mockRequest: OneUptimeRequest;
58
+ let mockResponse: OneUptimeResponse;
59
+ let nextFunction: NextFunction;
60
+
61
+ const projectId: ObjectID = ObjectID.generate();
62
+ const projectCustomerId: string = "cus_own_project";
63
+ const invoiceId: string = "in_123";
64
+
65
+ let project: Project;
66
+
67
+ beforeEach(() => {
68
+ new BillingInvoiceAPI();
69
+
70
+ project = new Project();
71
+ project.id = projectId;
72
+ project.paymentProviderCustomerId = projectCustomerId;
73
+ project.paymentProviderSubscriptionId = "sub_123";
74
+
75
+ jest
76
+ .spyOn(BillingInvoiceAPI.prototype, "getPermissionsForTenant")
77
+ .mockResolvedValue([
78
+ {
79
+ permission: Permission.EditInvoices,
80
+ } as UserPermission,
81
+ ]);
82
+
83
+ ProjectService.findOneById = jest.fn().mockResolvedValue(project);
84
+ BillingInvoiceService.findOneBy = jest
85
+ .fn()
86
+ .mockResolvedValue(new BillingInvoice());
87
+ BillingInvoiceService.updateOneBy = jest.fn().mockResolvedValue(undefined);
88
+ BillingInvoiceService.refreshSubscriptionStatus = jest
89
+ .fn()
90
+ .mockResolvedValue(undefined);
91
+ BillingService.payInvoice = jest.fn().mockResolvedValue({
92
+ id: invoiceId,
93
+ status: "paid",
94
+ });
95
+
96
+ mockRequest = {
97
+ tenantId: projectId,
98
+ body: {
99
+ data: {
100
+ paymentProviderInvoiceId: invoiceId,
101
+ paymentProviderCustomerId: projectCustomerId,
102
+ },
103
+ },
104
+ } as unknown as OneUptimeRequest;
105
+ mockResponse = {
106
+ send: jest.fn(),
107
+ json: jest.fn(),
108
+ status: jest.fn().mockReturnThis(),
109
+ } as unknown as OneUptimeResponse;
110
+ nextFunction = jest.fn();
111
+ });
112
+
113
+ afterEach(() => {
114
+ jest.restoreAllMocks();
115
+ });
116
+
117
+ describe("POST /billing-invoices/pay", () => {
118
+ it("should reject a customer id that does not belong to the project", async () => {
119
+ mockRequest.body["data"] = {
120
+ paymentProviderInvoiceId: invoiceId,
121
+ paymentProviderCustomerId: "cus_victim_tenant",
122
+ };
123
+
124
+ await mockRouter
125
+ .match("post", "/billing-invoices/pay")
126
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
127
+
128
+ expect(nextFunction).toHaveBeenCalledWith(
129
+ new BadDataException("Customer ID does not belong to this project"),
130
+ );
131
+ expect(BillingService.payInvoice).not.toHaveBeenCalled();
132
+ expect(BillingInvoiceService.updateOneBy).not.toHaveBeenCalled();
133
+ });
134
+
135
+ it("should reject an invoice that does not belong to the project", async () => {
136
+ BillingInvoiceService.findOneBy = jest.fn().mockResolvedValue(null);
137
+
138
+ await mockRouter
139
+ .match("post", "/billing-invoices/pay")
140
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
141
+
142
+ expect(BillingInvoiceService.findOneBy).toHaveBeenCalledWith(
143
+ expect.objectContaining({
144
+ query: {
145
+ projectId: projectId,
146
+ paymentProviderInvoiceId: invoiceId,
147
+ },
148
+ /*
149
+ * ignoreHooks is required: BillingInvoiceService.onBeforeFind
150
+ * throws without props.tenantId and would re-sync all invoices
151
+ * from Stripe on every pay attempt.
152
+ */
153
+ props: expect.objectContaining({
154
+ ignoreHooks: true,
155
+ }),
156
+ }),
157
+ );
158
+ expect(nextFunction).toHaveBeenCalledWith(
159
+ new BadDataException("Invoice not found for this project"),
160
+ );
161
+ expect(BillingService.payInvoice).not.toHaveBeenCalled();
162
+ expect(BillingInvoiceService.updateOneBy).not.toHaveBeenCalled();
163
+ });
164
+
165
+ it("should pay with the project's own customer id and scope the status update to the project", async () => {
166
+ // body omits the customer id — the project's own customer is charged.
167
+ mockRequest.body["data"] = {
168
+ paymentProviderInvoiceId: invoiceId,
169
+ };
170
+
171
+ await mockRouter
172
+ .match("post", "/billing-invoices/pay")
173
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
174
+
175
+ expect(nextFunction).not.toHaveBeenCalled();
176
+ expect(BillingService.payInvoice).toHaveBeenCalledWith(
177
+ projectCustomerId,
178
+ invoiceId,
179
+ );
180
+ expect(BillingInvoiceService.updateOneBy).toHaveBeenCalledWith(
181
+ expect.objectContaining({
182
+ query: {
183
+ projectId: projectId,
184
+ paymentProviderInvoiceId: invoiceId,
185
+ },
186
+ }),
187
+ );
188
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
189
+ mockRequest,
190
+ mockResponse,
191
+ );
192
+ });
193
+ });
194
+ });
@@ -1,4 +1,6 @@
1
1
  import ProjectAPI from "../../../Server/API/ProjectAPI";
2
+ import BillingService from "../../../Server/Services/BillingService";
3
+ import ProjectService from "../../../Server/Services/ProjectService";
2
4
  import TeamMemberService from "../../../Server/Services/TeamMemberService";
3
5
  import {
4
6
  NextFunction,
@@ -15,6 +17,17 @@ import ObjectID from "../../../Types/ObjectID";
15
17
  import PositiveNumber from "../../../Types/PositiveNumber";
16
18
  import Project from "../../../Models/DatabaseModels/Project";
17
19
  import TeamMember from "../../../Models/DatabaseModels/TeamMember";
20
+ import BadDataException from "../../../Types/Exception/BadDataException";
21
+ import Permission, { UserPermission } from "../../../Types/Permission";
22
+
23
+ jest.mock("../../../Server/EnvironmentConfig", () => {
24
+ return {
25
+ ...jest.requireActual("../../../Server/EnvironmentConfig"),
26
+ IsBillingEnabled: true,
27
+ };
28
+ });
29
+
30
+ jest.mock("../../../Server/Services/BillingService");
18
31
 
19
32
  jest.mock("../../../Server/Utils/Express", () => {
20
33
  return {
@@ -216,4 +229,82 @@ describe("ProjectAPI", () => {
216
229
  expect(nextFunction).toHaveBeenCalledWith(authError);
217
230
  });
218
231
  });
232
+
233
+ describe("PUT /project/:id/change-plan", () => {
234
+ const planId: string = "plan_123";
235
+
236
+ const tenantMismatchError: BadDataException = new BadDataException(
237
+ "Project ID in the URL does not match the project the request is authenticated for",
238
+ );
239
+
240
+ beforeEach(() => {
241
+ ProjectService.findOneById = jest.fn().mockResolvedValue({
242
+ paymentProviderCustomerId: "cus_123",
243
+ } as Project);
244
+ ProjectService.changePlan = jest.fn().mockResolvedValue(undefined);
245
+ BillingService.hasPaymentMethods = jest.fn().mockResolvedValue(true);
246
+ });
247
+
248
+ it("should reject when the URL project id does not match the authenticated tenant", async () => {
249
+ const victimProjectId: ObjectID = ObjectID.generate();
250
+ const attackerProjectId: ObjectID = ObjectID.generate();
251
+
252
+ mockRequest.params = { id: victimProjectId.toString() };
253
+ mockRequest.tenantId = attackerProjectId;
254
+ mockRequest.body = { data: { paymentProviderPlanId: planId } };
255
+
256
+ await mockRouter
257
+ .match("put", "/project/:id/change-plan")
258
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
259
+
260
+ expect(nextFunction).toHaveBeenCalledWith(tenantMismatchError);
261
+ expect(ProjectService.changePlan).not.toHaveBeenCalled();
262
+ // the foreign project must not even be looked up.
263
+ expect(ProjectService.findOneById).not.toHaveBeenCalled();
264
+ });
265
+
266
+ it("should reject when the request has no authenticated tenant", async () => {
267
+ const projectId: ObjectID = ObjectID.generate();
268
+
269
+ mockRequest.params = { id: projectId.toString() };
270
+ mockRequest.body = { data: { paymentProviderPlanId: planId } };
271
+
272
+ await mockRouter
273
+ .match("put", "/project/:id/change-plan")
274
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
275
+
276
+ expect(nextFunction).toHaveBeenCalledWith(tenantMismatchError);
277
+ expect(ProjectService.changePlan).not.toHaveBeenCalled();
278
+ });
279
+
280
+ it("should change the plan of the authenticated tenant's own project", async () => {
281
+ const projectId: ObjectID = ObjectID.generate();
282
+
283
+ mockRequest.params = { id: projectId.toString() };
284
+ mockRequest.tenantId = projectId;
285
+ mockRequest.body = { data: { paymentProviderPlanId: planId } };
286
+
287
+ jest
288
+ .spyOn(ProjectAPI.prototype, "getPermissionsForTenant")
289
+ .mockResolvedValue([
290
+ {
291
+ permission: Permission.ProjectOwner,
292
+ } as UserPermission,
293
+ ]);
294
+
295
+ await mockRouter
296
+ .match("put", "/project/:id/change-plan")
297
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
298
+
299
+ expect(nextFunction).not.toHaveBeenCalled();
300
+ expect(ProjectService.changePlan).toHaveBeenCalledWith({
301
+ projectId: projectId,
302
+ paymentProviderPlanId: planId,
303
+ });
304
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
305
+ mockRequest,
306
+ mockResponse,
307
+ );
308
+ });
309
+ });
219
310
  });
@@ -0,0 +1,207 @@
1
+ import ResellerPlanAPI from "../../../Server/API/ResellerPlanAPI";
2
+ import ProjectService from "../../../Server/Services/ProjectService";
3
+ import ResellerPlanService from "../../../Server/Services/ResellerPlanService";
4
+ import {
5
+ NextFunction,
6
+ OneUptimeRequest,
7
+ OneUptimeResponse,
8
+ } from "../../../Server/Utils/Express";
9
+ import Response from "../../../Server/Utils/Response";
10
+ import { mockRouter } from "./Helpers";
11
+ import { describe, expect, it } from "@jest/globals";
12
+ import BadDataException from "../../../Types/Exception/BadDataException";
13
+ import ObjectID from "../../../Types/ObjectID";
14
+ import Project from "../../../Models/DatabaseModels/Project";
15
+ import ResellerPlan from "../../../Models/DatabaseModels/ResellerPlan";
16
+
17
+ jest.mock("../../../Server/Utils/Express", () => {
18
+ return {
19
+ getRouter: () => {
20
+ return mockRouter;
21
+ },
22
+ };
23
+ });
24
+
25
+ jest.mock("../../../Server/Utils/Response", () => {
26
+ return {
27
+ sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => {
28
+ return args;
29
+ }),
30
+ sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => {
31
+ return args;
32
+ }),
33
+ sendEmptySuccessResponse: jest.fn(),
34
+ sendEntityResponse: jest.fn().mockImplementation((...args: []) => {
35
+ return args;
36
+ }),
37
+ sendErrorResponse: jest.fn().mockImplementation((...args: []) => {
38
+ return args;
39
+ }),
40
+ };
41
+ });
42
+
43
+ jest.mock("../../../Server/Services/ResellerPlanService");
44
+ jest.mock("../../../Server/Services/ProjectService");
45
+ jest.mock("../../../Server/Services/BillingService");
46
+ jest.mock("../../../Server/Services/PromoCodeService");
47
+
48
+ describe("ResellerPlanAPI", () => {
49
+ let mockRequest: OneUptimeRequest;
50
+ let mockResponse: OneUptimeResponse;
51
+ let nextFunction: NextFunction;
52
+
53
+ const resellerExternalId: string = "reseller_a";
54
+ const resellerObjectId: ObjectID = ObjectID.generate();
55
+ const resellerPlanObjectId: ObjectID = ObjectID.generate();
56
+ const projectObjectId: ObjectID = ObjectID.generate();
57
+ const licenseKey: string = "license_123";
58
+
59
+ const mockResellerPlan: ResellerPlan = {
60
+ id: resellerPlanObjectId,
61
+ planId: "plan_basic",
62
+ reseller: {
63
+ id: resellerObjectId,
64
+ resellerId: resellerExternalId,
65
+ },
66
+ monitorLimit: 10,
67
+ teamMemberLimit: 5,
68
+ } as unknown as ResellerPlan;
69
+
70
+ beforeEach(() => {
71
+ new ResellerPlanAPI();
72
+
73
+ ResellerPlanService.findOneBy = jest
74
+ .fn()
75
+ .mockResolvedValue(mockResellerPlan);
76
+ ProjectService.findOneBy = jest.fn().mockResolvedValue(null);
77
+ ProjectService.updateOneById = jest.fn().mockResolvedValue(undefined);
78
+ ProjectService.deleteOneBy = jest.fn().mockResolvedValue(undefined);
79
+
80
+ mockRequest = {
81
+ params: { resellerId: resellerExternalId },
82
+ bearerTokenData: { resellerId: resellerExternalId },
83
+ body: {
84
+ action: "enhance_tier",
85
+ plan_id: "plan_basic",
86
+ uuid: licenseKey,
87
+ activation_email: "customer@example.com",
88
+ },
89
+ } as unknown as OneUptimeRequest;
90
+ mockResponse = {
91
+ send: jest.fn(),
92
+ json: jest.fn(),
93
+ status: jest.fn().mockReturnThis(),
94
+ } as unknown as OneUptimeResponse;
95
+ nextFunction = jest.fn();
96
+ });
97
+
98
+ afterEach(() => {
99
+ jest.restoreAllMocks();
100
+ });
101
+
102
+ describe("POST /reseller-plan/action/:resellerId", () => {
103
+ it("should scope the project lookup to the authenticated reseller on tier change", async () => {
104
+ await mockRouter
105
+ .match("post", "/reseller-plan/action/:resellerId")
106
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
107
+
108
+ expect(ProjectService.findOneBy).toHaveBeenCalledWith(
109
+ expect.objectContaining({
110
+ query: {
111
+ resellerLicenseId: licenseKey,
112
+ resellerId: resellerObjectId,
113
+ },
114
+ }),
115
+ );
116
+ // license key belongs to another reseller -> scoped lookup finds nothing.
117
+ expect(nextFunction).toHaveBeenCalledWith(
118
+ new BadDataException("Project not found with this license key"),
119
+ );
120
+ expect(ProjectService.updateOneById).not.toHaveBeenCalled();
121
+ });
122
+
123
+ it("should not delete another reseller's project on refund", async () => {
124
+ mockRequest.body["action"] = "refund";
125
+
126
+ await mockRouter
127
+ .match("post", "/reseller-plan/action/:resellerId")
128
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
129
+
130
+ expect(ProjectService.findOneBy).toHaveBeenCalledWith(
131
+ expect.objectContaining({
132
+ query: {
133
+ resellerLicenseId: licenseKey,
134
+ resellerId: resellerObjectId,
135
+ },
136
+ }),
137
+ );
138
+ expect(ProjectService.deleteOneBy).not.toHaveBeenCalled();
139
+ // refund is idempotent: respond as refunded without deleting anything.
140
+ expect(Response.sendJsonObjectResponse).toHaveBeenCalledWith(
141
+ mockRequest,
142
+ mockResponse,
143
+ {
144
+ message: "product refunded",
145
+ },
146
+ );
147
+ });
148
+
149
+ it("should update limits for the owning reseller's project", async () => {
150
+ ProjectService.findOneBy = jest.fn().mockResolvedValue({
151
+ id: projectObjectId,
152
+ } as Project);
153
+
154
+ await mockRouter
155
+ .match("post", "/reseller-plan/action/:resellerId")
156
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
157
+
158
+ expect(nextFunction).not.toHaveBeenCalled();
159
+ expect(ProjectService.updateOneById).toHaveBeenCalledWith(
160
+ expect.objectContaining({
161
+ id: projectObjectId,
162
+ data: {
163
+ activeMonitorsLimit: 10,
164
+ seatLimit: 5,
165
+ resellerPlanId: resellerPlanObjectId,
166
+ },
167
+ }),
168
+ );
169
+ expect(Response.sendJsonObjectResponse).toHaveBeenCalledWith(
170
+ mockRequest,
171
+ mockResponse,
172
+ {
173
+ message: "product enhanced",
174
+ },
175
+ );
176
+ });
177
+
178
+ it("should delete the owning reseller's project on refund with a reseller-scoped query", async () => {
179
+ mockRequest.body["action"] = "refund";
180
+ ProjectService.findOneBy = jest.fn().mockResolvedValue({
181
+ id: projectObjectId,
182
+ } as Project);
183
+
184
+ await mockRouter
185
+ .match("post", "/reseller-plan/action/:resellerId")
186
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
187
+
188
+ expect(nextFunction).not.toHaveBeenCalled();
189
+ expect(ProjectService.deleteOneBy).toHaveBeenCalledWith(
190
+ expect.objectContaining({
191
+ query: {
192
+ resellerLicenseId: licenseKey,
193
+ resellerId: resellerObjectId,
194
+ _id: projectObjectId.toString(),
195
+ },
196
+ }),
197
+ );
198
+ expect(Response.sendJsonObjectResponse).toHaveBeenCalledWith(
199
+ mockRequest,
200
+ mockResponse,
201
+ {
202
+ message: "product refunded",
203
+ },
204
+ );
205
+ });
206
+ });
207
+ });