@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,879 @@
1
+ import {
2
+ PVE_SNAPSHOT_METRIC_NAMES,
3
+ CEPH_SNAPSHOT_METRIC_NAMES,
4
+ ProxmoxResourceBufferEntry,
5
+ ProxmoxClusterSnapshotBufferEntry,
6
+ ProxmoxClusterSnapshotExtras,
7
+ CephResourceBufferEntry,
8
+ CephClusterSnapshotBufferEntry,
9
+ CephClusterSnapshotExtras,
10
+ bufferProxmoxSnapshotMetric,
11
+ bufferCephSnapshotMetric,
12
+ computeProxmoxGuestBackedUp,
13
+ deriveProxmoxClusterSnapshotExtras,
14
+ deriveCephClusterSnapshotExtras,
15
+ } from "../../../../Server/Utils/Telemetry/ProxmoxCephSnapshotScan";
16
+ import { JSONObject } from "../../../../Types/JSON";
17
+
18
+ /*
19
+ * WI-21: the Proxmox/Ceph snapshot scan, fed with synthetic OTLP
20
+ * datapoints (the exact JSON shape the prometheus receiver emits
21
+ * through the OTLP decode: asInt/asDouble + timeUnixNano + a raw
22
+ * key/stringValue attribute array). Locks in:
23
+ *
24
+ * - identity first-non-null-wins / status newest-observedAt-wins,
25
+ * - the never-zero-a-count-on-a-partial-batch contract (a count is
26
+ * derived only when the batch carried the matching identity
27
+ * series; same COALESCE-style guard the upsert uses per column),
28
+ * - the WI-24 backup-coverage join (sawBackupInfo gate + the
29
+ * externalId-or-bare-vmid id match),
30
+ * - non-allow-listed metrics never reaching the inventory fold.
31
+ */
32
+
33
+ const CLUSTER: string = "0a1b2c3d-0000-0000-0000-000000000001";
34
+
35
+ // 2023-11-14T22:13:20.000Z — an arbitrary fixed scrape instant.
36
+ const BASE_MS: number = 1700000000000;
37
+
38
+ type LabelMap = Record<string, string>;
39
+
40
+ function toNano(ms: number): string {
41
+ return `${ms}000000`;
42
+ }
43
+
44
+ function datapoint(data: {
45
+ value: number;
46
+ atMs?: number;
47
+ labels?: LabelMap;
48
+ asDouble?: boolean;
49
+ }): JSONObject {
50
+ const attributes: Array<JSONObject> = Object.entries(data.labels || {}).map(
51
+ ([key, value]: [string, string]) => {
52
+ return { key, value: { stringValue: value } };
53
+ },
54
+ );
55
+ return {
56
+ ...(data.asDouble ? { asDouble: data.value } : { asInt: data.value }),
57
+ timeUnixNano: toNano(data.atMs ?? BASE_MS),
58
+ attributes,
59
+ };
60
+ }
61
+
62
+ interface ProxmoxBuffers {
63
+ resourceBuffer: Map<string, Map<string, ProxmoxResourceBufferEntry>>;
64
+ clusterBuffer: Map<string, ProxmoxClusterSnapshotBufferEntry>;
65
+ }
66
+
67
+ function proxmoxBuffers(): ProxmoxBuffers {
68
+ return { resourceBuffer: new Map(), clusterBuffer: new Map() };
69
+ }
70
+
71
+ function feedProxmox(
72
+ buffers: ProxmoxBuffers,
73
+ metricName: string,
74
+ dp: JSONObject,
75
+ ): void {
76
+ bufferProxmoxSnapshotMetric({
77
+ clusterIdStr: CLUSTER,
78
+ metricName,
79
+ datapoint: dp,
80
+ resourceBuffer: buffers.resourceBuffer,
81
+ clusterBuffer: buffers.clusterBuffer,
82
+ });
83
+ }
84
+
85
+ function proxmoxEntries(
86
+ buffers: ProxmoxBuffers,
87
+ ): Array<ProxmoxResourceBufferEntry> {
88
+ return Array.from(buffers.resourceBuffer.get(CLUSTER)?.values() || []);
89
+ }
90
+
91
+ function proxmoxEntry(
92
+ buffers: ProxmoxBuffers,
93
+ kind: string,
94
+ externalId: string,
95
+ ): ProxmoxResourceBufferEntry {
96
+ const entry: ProxmoxResourceBufferEntry | undefined = buffers.resourceBuffer
97
+ .get(CLUSTER)
98
+ ?.get(`${kind}|${externalId}`);
99
+ if (!entry) {
100
+ throw new Error(`expected buffered entry ${kind}|${externalId}`);
101
+ }
102
+ return entry;
103
+ }
104
+
105
+ interface CephBuffers {
106
+ resourceBuffer: Map<string, Map<string, CephResourceBufferEntry>>;
107
+ clusterBuffer: Map<string, CephClusterSnapshotBufferEntry>;
108
+ }
109
+
110
+ function cephBuffers(): CephBuffers {
111
+ return { resourceBuffer: new Map(), clusterBuffer: new Map() };
112
+ }
113
+
114
+ function feedCeph(
115
+ buffers: CephBuffers,
116
+ metricName: string,
117
+ dp: JSONObject,
118
+ ): void {
119
+ bufferCephSnapshotMetric({
120
+ clusterIdStr: CLUSTER,
121
+ metricName,
122
+ datapoint: dp,
123
+ resourceBuffer: buffers.resourceBuffer,
124
+ clusterBuffer: buffers.clusterBuffer,
125
+ });
126
+ }
127
+
128
+ function cephEntries(buffers: CephBuffers): Array<CephResourceBufferEntry> {
129
+ return Array.from(buffers.resourceBuffer.get(CLUSTER)?.values() || []);
130
+ }
131
+
132
+ function cephEntry(
133
+ buffers: CephBuffers,
134
+ kind: string,
135
+ externalId: string,
136
+ ): CephResourceBufferEntry {
137
+ const entry: CephResourceBufferEntry | undefined = buffers.resourceBuffer
138
+ .get(CLUSTER)
139
+ ?.get(`${kind}|${externalId}`);
140
+ if (!entry) {
141
+ throw new Error(`expected buffered entry ${kind}|${externalId}`);
142
+ }
143
+ return entry;
144
+ }
145
+
146
+ describe("ProxmoxCephSnapshotScan - metric allow-lists", () => {
147
+ test("non-snapshot metrics are not allow-listed (the ingest gate)", () => {
148
+ // Perf series go to ClickHouse only — never the Postgres mirror.
149
+ expect(PVE_SNAPSHOT_METRIC_NAMES.has("pve_network_receive_bytes")).toBe(
150
+ false,
151
+ );
152
+ expect(PVE_SNAPSHOT_METRIC_NAMES.has("pve_replication_failed_syncs")).toBe(
153
+ false,
154
+ );
155
+ expect(CEPH_SNAPSHOT_METRIC_NAMES.has("ceph_health_detail")).toBe(false);
156
+ expect(CEPH_SNAPSHOT_METRIC_NAMES.has("ceph_pg_degraded")).toBe(false);
157
+ });
158
+
159
+ test("WI-24 backup-coverage series are allow-listed", () => {
160
+ expect(PVE_SNAPSHOT_METRIC_NAMES.has("pve_not_backed_up_total")).toBe(true);
161
+ expect(PVE_SNAPSHOT_METRIC_NAMES.has("pve_not_backed_up_info")).toBe(true);
162
+ });
163
+
164
+ test("a non-allow-listed metric name never creates an inventory row", () => {
165
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
166
+ /*
167
+ * Defense in depth: even if the Set gate were bypassed, the fold's
168
+ * metric-name switch drops unknown names.
169
+ */
170
+ feedProxmox(
171
+ buffers,
172
+ "pve_network_receive_bytes",
173
+ datapoint({ value: 1234, labels: { id: "qemu/100" } }),
174
+ );
175
+ expect(proxmoxEntries(buffers)).toHaveLength(0);
176
+ });
177
+ });
178
+
179
+ describe("ProxmoxCephSnapshotScan - Proxmox fold", () => {
180
+ test("a full scrape folds nodes, guests and storage with identity + status", () => {
181
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
182
+
183
+ feedProxmox(
184
+ buffers,
185
+ "pve_node_info",
186
+ datapoint({ value: 1, labels: { id: "node/pve1", name: "pve1" } }),
187
+ );
188
+ feedProxmox(
189
+ buffers,
190
+ "pve_up",
191
+ datapoint({
192
+ value: 1,
193
+ labels: { id: "node/pve1", "pve.scope": "node" },
194
+ }),
195
+ );
196
+ feedProxmox(
197
+ buffers,
198
+ "pve_node_info",
199
+ datapoint({ value: 1, labels: { id: "node/pve2", name: "pve2" } }),
200
+ );
201
+ feedProxmox(
202
+ buffers,
203
+ "pve_up",
204
+ datapoint({
205
+ value: 0,
206
+ labels: { id: "node/pve2", "pve.scope": "node" },
207
+ }),
208
+ );
209
+ feedProxmox(
210
+ buffers,
211
+ "pve_guest_info",
212
+ datapoint({
213
+ value: 1,
214
+ labels: { id: "qemu/100", name: "web-vm", node: "pve1" },
215
+ }),
216
+ );
217
+ feedProxmox(
218
+ buffers,
219
+ "pve_storage_info",
220
+ datapoint({
221
+ value: 1,
222
+ labels: { id: "storage/pve1/local", storage: "local", node: "pve1" },
223
+ }),
224
+ );
225
+ feedProxmox(
226
+ buffers,
227
+ "pve_version_info",
228
+ datapoint({ value: 1, labels: { version: "8.2.4" } }),
229
+ );
230
+
231
+ const node1: ProxmoxResourceBufferEntry = proxmoxEntry(
232
+ buffers,
233
+ "Node",
234
+ "node/pve1",
235
+ );
236
+ expect(node1.name).toBe("pve1");
237
+ expect(node1.isUp).toBe(true);
238
+
239
+ const node2: ProxmoxResourceBufferEntry = proxmoxEntry(
240
+ buffers,
241
+ "Node",
242
+ "node/pve2",
243
+ );
244
+ expect(node2.isUp).toBe(false);
245
+
246
+ const guest: ProxmoxResourceBufferEntry = proxmoxEntry(
247
+ buffers,
248
+ "Guest",
249
+ "qemu/100",
250
+ );
251
+ expect(guest.name).toBe("web-vm");
252
+ expect(guest.vmid).toBe(100);
253
+ expect(guest.guestType).toBe("qemu");
254
+ expect(guest.parentNodeName).toBe("pve1");
255
+
256
+ const storage: ProxmoxResourceBufferEntry = proxmoxEntry(
257
+ buffers,
258
+ "Storage",
259
+ "storage/pve1/local",
260
+ );
261
+ expect(storage.name).toBe("local");
262
+ expect(storage.parentNodeName).toBe("pve1");
263
+
264
+ // pve_version_info has no id — cluster-level only, never a row.
265
+ expect(proxmoxEntries(buffers)).toHaveLength(4);
266
+ expect(buffers.clusterBuffer.get(CLUSTER)?.pveVersion).toBe("8.2.4");
267
+ });
268
+
269
+ test("scope falls back to the id prefix when the agent transform is absent", () => {
270
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
271
+ // No pve.scope / pve.type attributes — hand-rolled collector config.
272
+ feedProxmox(
273
+ buffers,
274
+ "pve_up",
275
+ datapoint({ value: 1, labels: { id: "lxc/101" } }),
276
+ );
277
+
278
+ const guest: ProxmoxResourceBufferEntry = proxmoxEntry(
279
+ buffers,
280
+ "Guest",
281
+ "lxc/101",
282
+ );
283
+ expect(guest.guestType).toBe("lxc");
284
+ expect(guest.vmid).toBe(101);
285
+ });
286
+
287
+ test("cluster-scoped and id-less series never create inventory rows", () => {
288
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
289
+ feedProxmox(
290
+ buffers,
291
+ "pve_up",
292
+ datapoint({ value: 1, labels: { id: "cluster/pve" } }),
293
+ );
294
+ feedProxmox(buffers, "pve_up", datapoint({ value: 1 }));
295
+ expect(proxmoxEntries(buffers)).toHaveLength(0);
296
+ });
297
+
298
+ test("identity labels are first-non-null-wins; a later info-less patch never blanks them", () => {
299
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
300
+ feedProxmox(
301
+ buffers,
302
+ "pve_guest_info",
303
+ datapoint({
304
+ value: 1,
305
+ atMs: BASE_MS,
306
+ labels: { id: "qemu/100", name: "web-vm", node: "pve1" },
307
+ }),
308
+ );
309
+ // A NEWER pve_up patch carries no name/node labels.
310
+ feedProxmox(
311
+ buffers,
312
+ "pve_up",
313
+ datapoint({
314
+ value: 1,
315
+ atMs: BASE_MS + 60_000,
316
+ labels: { id: "qemu/100", "pve.scope": "guest" },
317
+ }),
318
+ );
319
+
320
+ const guest: ProxmoxResourceBufferEntry = proxmoxEntry(
321
+ buffers,
322
+ "Guest",
323
+ "qemu/100",
324
+ );
325
+ expect(guest.name).toBe("web-vm");
326
+ expect(guest.parentNodeName).toBe("pve1");
327
+ expect(guest.isUp).toBe(true);
328
+ expect(guest.observedAt).toEqual(new Date(BASE_MS + 60_000));
329
+ });
330
+
331
+ test("status fields are newest-observedAt-wins regardless of arrival order", () => {
332
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
333
+ const labels: LabelMap = { id: "node/pve1", "pve.scope": "node" };
334
+ // Newest point (up) arrives FIRST, then a stale point (down).
335
+ feedProxmox(
336
+ buffers,
337
+ "pve_up",
338
+ datapoint({ value: 1, atMs: BASE_MS + 60_000, labels }),
339
+ );
340
+ feedProxmox(
341
+ buffers,
342
+ "pve_up",
343
+ datapoint({ value: 0, atMs: BASE_MS, labels }),
344
+ );
345
+
346
+ const node: ProxmoxResourceBufferEntry = proxmoxEntry(
347
+ buffers,
348
+ "Node",
349
+ "node/pve1",
350
+ );
351
+ expect(node.isUp).toBe(true);
352
+ expect(node.observedAt).toEqual(new Date(BASE_MS + 60_000));
353
+ });
354
+
355
+ test("pve_ha_state folds only the active enum row; zero rows leave no trace", () => {
356
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
357
+ feedProxmox(
358
+ buffers,
359
+ "pve_ha_state",
360
+ datapoint({
361
+ value: 0,
362
+ labels: { id: "qemu/100", state: "started", "pve.scope": "guest" },
363
+ }),
364
+ );
365
+ // The zero row must not even create an empty patch.
366
+ expect(proxmoxEntries(buffers)).toHaveLength(0);
367
+
368
+ feedProxmox(
369
+ buffers,
370
+ "pve_ha_state",
371
+ datapoint({
372
+ value: 1,
373
+ labels: { id: "qemu/100", state: "error", "pve.scope": "guest" },
374
+ }),
375
+ );
376
+ expect(proxmoxEntry(buffers, "Guest", "qemu/100").haState).toBe("error");
377
+ });
378
+
379
+ test("latest-metric mirror fields fold with truncation and ratio→percent", () => {
380
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
381
+ const labels: LabelMap = { id: "qemu/100", "pve.scope": "guest" };
382
+ feedProxmox(
383
+ buffers,
384
+ "pve_cpu_usage_ratio",
385
+ datapoint({ value: 0.25, asDouble: true, labels }),
386
+ );
387
+ feedProxmox(
388
+ buffers,
389
+ "pve_memory_usage_bytes",
390
+ datapoint({ value: 1024.9, asDouble: true, labels }),
391
+ );
392
+ feedProxmox(
393
+ buffers,
394
+ "pve_memory_size_bytes",
395
+ datapoint({ value: 2048, labels }),
396
+ );
397
+
398
+ const guest: ProxmoxResourceBufferEntry = proxmoxEntry(
399
+ buffers,
400
+ "Guest",
401
+ "qemu/100",
402
+ );
403
+ expect(guest.latestCpuPercent).toBe(25);
404
+ expect(guest.latestMemoryBytes).toBe(1024);
405
+ expect(guest.maxMemoryBytes).toBe(2048);
406
+ /*
407
+ * No disk series in the batch: a qemu guest without the QEMU guest
408
+ * agent stays NULL (never 0) — the upsert COALESCE keeps it NULL.
409
+ */
410
+ expect(guest.latestDiskBytes).toBeNull();
411
+ });
412
+
413
+ test("non-finite and missing values are dropped before folding", () => {
414
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
415
+ feedProxmox(buffers, "pve_up", {
416
+ asDouble: "Infinity",
417
+ timeUnixNano: toNano(BASE_MS),
418
+ attributes: [{ key: "id", value: { stringValue: "node/pve1" } }],
419
+ } as JSONObject);
420
+ feedProxmox(buffers, "pve_up", {
421
+ timeUnixNano: toNano(BASE_MS),
422
+ attributes: [{ key: "id", value: { stringValue: "node/pve1" } }],
423
+ } as JSONObject);
424
+ expect(proxmoxEntries(buffers)).toHaveLength(0);
425
+ });
426
+ });
427
+
428
+ describe("ProxmoxCephSnapshotScan - WI-24 backup coverage", () => {
429
+ function bufferedGuest(
430
+ externalId: string,
431
+ vmid: number | null,
432
+ ): ProxmoxResourceBufferEntry {
433
+ return {
434
+ kind: "Guest",
435
+ externalId,
436
+ name: null,
437
+ vmid,
438
+ guestType: "qemu",
439
+ parentNodeName: null,
440
+ isUp: true,
441
+ haState: null,
442
+ onboot: null,
443
+ uptimeSeconds: null,
444
+ latestCpuPercent: null,
445
+ latestMemoryBytes: null,
446
+ maxMemoryBytes: null,
447
+ latestDiskBytes: null,
448
+ maxDiskBytes: null,
449
+ observedAt: new Date(BASE_MS),
450
+ };
451
+ }
452
+
453
+ test("backup series fold into the cluster buffer, never the inventory", () => {
454
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
455
+ feedProxmox(buffers, "pve_not_backed_up_total", datapoint({ value: 2 }));
456
+ feedProxmox(
457
+ buffers,
458
+ "pve_not_backed_up_info",
459
+ datapoint({ value: 1, labels: { id: "qemu/100" } }),
460
+ );
461
+ feedProxmox(
462
+ buffers,
463
+ "pve_not_backed_up_info",
464
+ datapoint({ value: 1, labels: { id: "101" } }),
465
+ );
466
+
467
+ expect(proxmoxEntries(buffers)).toHaveLength(0);
468
+
469
+ const snap: ProxmoxClusterSnapshotBufferEntry | undefined =
470
+ buffers.clusterBuffer.get(CLUSTER);
471
+ expect(snap?.sawBackupInfo).toBe(true);
472
+ expect(snap?.guestsWithoutBackupCount).toBe(2);
473
+ expect(snap?.notBackedUpIds).toEqual(new Set(["qemu/100", "101"]));
474
+ });
475
+
476
+ test("guests join against the uncovered ids by externalId OR bare vmid", () => {
477
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
478
+ feedProxmox(buffers, "pve_not_backed_up_total", datapoint({ value: 2 }));
479
+ feedProxmox(
480
+ buffers,
481
+ "pve_not_backed_up_info",
482
+ datapoint({ value: 1, labels: { id: "qemu/100" } }),
483
+ );
484
+ feedProxmox(
485
+ buffers,
486
+ "pve_not_backed_up_info",
487
+ datapoint({ value: 1, labels: { id: "101" } }),
488
+ );
489
+ const snap: ProxmoxClusterSnapshotBufferEntry | undefined =
490
+ buffers.clusterBuffer.get(CLUSTER);
491
+
492
+ // Full-id match (qemu/100), bare-vmid match (101), covered (102).
493
+ expect(
494
+ computeProxmoxGuestBackedUp(bufferedGuest("qemu/100", 100), snap),
495
+ ).toBe(false);
496
+ expect(
497
+ computeProxmoxGuestBackedUp(bufferedGuest("lxc/101", 101), snap),
498
+ ).toBe(false);
499
+ expect(
500
+ computeProxmoxGuestBackedUp(bufferedGuest("qemu/102", 102), snap),
501
+ ).toBe(true);
502
+ });
503
+
504
+ test("a batch WITHOUT backup-info output yields null for every guest (COALESCE keeps last-known)", () => {
505
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
506
+ feedProxmox(
507
+ buffers,
508
+ "pve_up",
509
+ datapoint({ value: 1, labels: { id: "qemu/100", "pve.scope": "guest" } }),
510
+ );
511
+ const snap: ProxmoxClusterSnapshotBufferEntry | undefined =
512
+ buffers.clusterBuffer.get(CLUSTER);
513
+
514
+ expect(snap?.sawBackupInfo).toBe(false);
515
+ expect(
516
+ computeProxmoxGuestBackedUp(bufferedGuest("qemu/100", 100), snap),
517
+ ).toBe(null);
518
+ // No snapshot at all (cluster never seen this batch) — also null.
519
+ expect(
520
+ computeProxmoxGuestBackedUp(bufferedGuest("qemu/100", 100), undefined),
521
+ ).toBe(null);
522
+ });
523
+
524
+ test("non-Guest rows never get a backup flag", () => {
525
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
526
+ feedProxmox(buffers, "pve_not_backed_up_total", datapoint({ value: 1 }));
527
+ const snap: ProxmoxClusterSnapshotBufferEntry | undefined =
528
+ buffers.clusterBuffer.get(CLUSTER);
529
+
530
+ const node: ProxmoxResourceBufferEntry = {
531
+ ...bufferedGuest("node/pve1", null),
532
+ kind: "Node",
533
+ guestType: null,
534
+ };
535
+ expect(computeProxmoxGuestBackedUp(node, snap)).toBe(null);
536
+ });
537
+ });
538
+
539
+ describe("ProxmoxCephSnapshotScan - Proxmox cluster extras derive", () => {
540
+ test("a full batch derives every count from the same folded buffer", () => {
541
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
542
+ feedProxmox(
543
+ buffers,
544
+ "pve_node_info",
545
+ datapoint({ value: 1, labels: { id: "node/pve1", name: "pve1" } }),
546
+ );
547
+ feedProxmox(
548
+ buffers,
549
+ "pve_up",
550
+ datapoint({ value: 1, labels: { id: "node/pve1", "pve.scope": "node" } }),
551
+ );
552
+ feedProxmox(
553
+ buffers,
554
+ "pve_node_info",
555
+ datapoint({ value: 1, labels: { id: "node/pve2", name: "pve2" } }),
556
+ );
557
+ feedProxmox(
558
+ buffers,
559
+ "pve_up",
560
+ datapoint({ value: 0, labels: { id: "node/pve2", "pve.scope": "node" } }),
561
+ );
562
+ feedProxmox(
563
+ buffers,
564
+ "pve_guest_info",
565
+ datapoint({ value: 1, labels: { id: "qemu/100", name: "web-vm" } }),
566
+ );
567
+ feedProxmox(
568
+ buffers,
569
+ "pve_storage_info",
570
+ datapoint({
571
+ value: 1,
572
+ labels: { id: "storage/pve1/local", storage: "local" },
573
+ }),
574
+ );
575
+ feedProxmox(
576
+ buffers,
577
+ "pve_version_info",
578
+ datapoint({ value: 1, labels: { version: "8.2.4" } }),
579
+ );
580
+ feedProxmox(buffers, "pve_not_backed_up_total", datapoint({ value: 0 }));
581
+
582
+ const extras: ProxmoxClusterSnapshotExtras =
583
+ deriveProxmoxClusterSnapshotExtras(
584
+ proxmoxEntries(buffers),
585
+ buffers.clusterBuffer.get(CLUSTER),
586
+ );
587
+
588
+ expect(extras).toEqual({
589
+ pveVersion: "8.2.4",
590
+ nodeCount: 2,
591
+ onlineNodeCount: 1,
592
+ guestCount: 1,
593
+ storageCount: 1,
594
+ // 0 is a legitimate value — distinct from "collector silent".
595
+ guestsWithoutBackupCount: 0,
596
+ });
597
+ });
598
+
599
+ test("a partial batch never zeroes a count (no identity series ⇒ key absent)", () => {
600
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
601
+ // Only a perf series made it through (e.g. a pipeline filter).
602
+ feedProxmox(
603
+ buffers,
604
+ "pve_cpu_usage_ratio",
605
+ datapoint({
606
+ value: 0.5,
607
+ asDouble: true,
608
+ labels: { id: "node/pve1", "pve.scope": "node" },
609
+ }),
610
+ );
611
+
612
+ const extras: ProxmoxClusterSnapshotExtras =
613
+ deriveProxmoxClusterSnapshotExtras(
614
+ proxmoxEntries(buffers),
615
+ buffers.clusterBuffer.get(CLUSTER),
616
+ );
617
+
618
+ /*
619
+ * The buffered node row exists, but no saw* flag flipped — so NO
620
+ * count keys appear and the cluster row's existing counts survive
621
+ * (the flush only writes the keys present here).
622
+ */
623
+ expect(extras).toEqual({});
624
+ });
625
+
626
+ test("guest-only batch writes guestCount without touching node counts", () => {
627
+ const buffers: ProxmoxBuffers = proxmoxBuffers();
628
+ feedProxmox(
629
+ buffers,
630
+ "pve_guest_info",
631
+ datapoint({ value: 1, labels: { id: "qemu/100", name: "web-vm" } }),
632
+ );
633
+
634
+ const extras: ProxmoxClusterSnapshotExtras =
635
+ deriveProxmoxClusterSnapshotExtras(
636
+ proxmoxEntries(buffers),
637
+ buffers.clusterBuffer.get(CLUSTER),
638
+ );
639
+
640
+ expect(extras).toEqual({ guestCount: 1 });
641
+ expect(Object.keys(extras)).not.toContain("nodeCount");
642
+ expect(Object.keys(extras)).not.toContain("guestsWithoutBackupCount");
643
+ });
644
+ });
645
+
646
+ describe("ProxmoxCephSnapshotScan - Ceph fold", () => {
647
+ test("daemon series fold by ceph_daemon with kind from the prefix", () => {
648
+ const buffers: CephBuffers = cephBuffers();
649
+ feedCeph(
650
+ buffers,
651
+ "ceph_osd_metadata",
652
+ datapoint({
653
+ value: 1,
654
+ labels: {
655
+ ceph_daemon: "osd.3",
656
+ hostname: "ceph-node-1",
657
+ device_class: "ssd",
658
+ ceph_version: "ceph version 18.2.2 reef",
659
+ },
660
+ }),
661
+ );
662
+ feedCeph(
663
+ buffers,
664
+ "ceph_osd_up",
665
+ datapoint({ value: 1, labels: { ceph_daemon: "osd.3" } }),
666
+ );
667
+ feedCeph(
668
+ buffers,
669
+ "ceph_osd_in",
670
+ datapoint({ value: 0, labels: { ceph_daemon: "osd.3" } }),
671
+ );
672
+ feedCeph(
673
+ buffers,
674
+ "ceph_mon_quorum_status",
675
+ datapoint({ value: 1, labels: { ceph_daemon: "mon.a" } }),
676
+ );
677
+ // Unknown daemon prefix — not an inventory kind.
678
+ feedCeph(
679
+ buffers,
680
+ "ceph_osd_up",
681
+ datapoint({ value: 1, labels: { ceph_daemon: "client.admin" } }),
682
+ );
683
+
684
+ const osd: CephResourceBufferEntry = cephEntry(buffers, "Osd", "osd.3");
685
+ expect(osd.hostname).toBe("ceph-node-1");
686
+ expect(osd.deviceClass).toBe("ssd");
687
+ expect(osd.isUp).toBe(true);
688
+ expect(osd.isIn).toBe(false);
689
+
690
+ const mon: CephResourceBufferEntry = cephEntry(buffers, "Mon", "mon.a");
691
+ expect(mon.inQuorum).toBe(true);
692
+
693
+ expect(cephEntries(buffers)).toHaveLength(2);
694
+ });
695
+
696
+ test("pool series fold by pool_id; the name only ever comes from metadata", () => {
697
+ const buffers: CephBuffers = cephBuffers();
698
+ feedCeph(
699
+ buffers,
700
+ "ceph_pool_stored",
701
+ datapoint({ value: 1000, labels: { pool_id: "2" } }),
702
+ );
703
+ feedCeph(
704
+ buffers,
705
+ "ceph_pool_metadata",
706
+ datapoint({ value: 1, labels: { pool_id: "2", name: "rbd" } }),
707
+ );
708
+ feedCeph(
709
+ buffers,
710
+ "ceph_pool_max_avail",
711
+ datapoint({ value: 9000, labels: { pool_id: "2" } }),
712
+ );
713
+ // Pool series without pool_id are dropped.
714
+ feedCeph(buffers, "ceph_pool_stored", datapoint({ value: 5 }));
715
+
716
+ const pool: CephResourceBufferEntry = cephEntry(buffers, "Pool", "2");
717
+ expect(pool.name).toBe("rbd");
718
+ expect(pool.storedBytes).toBe(1000);
719
+ expect(pool.maxAvailBytes).toBe(9000);
720
+ expect(cephEntries(buffers)).toHaveLength(1);
721
+ });
722
+
723
+ test("cluster health/capacity series never create resource rows", () => {
724
+ const buffers: CephBuffers = cephBuffers();
725
+ feedCeph(buffers, "ceph_health_status", datapoint({ value: 1 }));
726
+ feedCeph(buffers, "ceph_cluster_total_bytes", datapoint({ value: 300 }));
727
+ feedCeph(
728
+ buffers,
729
+ "ceph_cluster_total_used_bytes",
730
+ datapoint({ value: 100 }),
731
+ );
732
+
733
+ expect(cephEntries(buffers)).toHaveLength(0);
734
+ const snap: CephClusterSnapshotBufferEntry | undefined =
735
+ buffers.clusterBuffer.get(CLUSTER);
736
+ expect(snap?.healthStatus).toBe(1);
737
+ expect(snap?.totalBytes).toBe(300);
738
+ expect(snap?.totalUsedBytes).toBe(100);
739
+ });
740
+
741
+ test("identity is first-non-null-wins, status newest-wins (out-of-order)", () => {
742
+ const buffers: CephBuffers = cephBuffers();
743
+ feedCeph(
744
+ buffers,
745
+ "ceph_osd_up",
746
+ datapoint({
747
+ value: 0,
748
+ atMs: BASE_MS + 60_000,
749
+ labels: { ceph_daemon: "osd.3" },
750
+ }),
751
+ );
752
+ feedCeph(
753
+ buffers,
754
+ "ceph_osd_up",
755
+ datapoint({ value: 1, atMs: BASE_MS, labels: { ceph_daemon: "osd.3" } }),
756
+ );
757
+ feedCeph(
758
+ buffers,
759
+ "ceph_osd_metadata",
760
+ datapoint({
761
+ value: 1,
762
+ atMs: BASE_MS,
763
+ labels: { ceph_daemon: "osd.3", hostname: "ceph-node-1" },
764
+ }),
765
+ );
766
+
767
+ const osd: CephResourceBufferEntry = cephEntry(buffers, "Osd", "osd.3");
768
+ // The newest (down) observation wins over the stale up one.
769
+ expect(osd.isUp).toBe(false);
770
+ // Identity from the older metadata series still sticks.
771
+ expect(osd.hostname).toBe("ceph-node-1");
772
+ expect(osd.observedAt).toEqual(new Date(BASE_MS + 60_000));
773
+ });
774
+ });
775
+
776
+ describe("ProxmoxCephSnapshotScan - Ceph cluster extras derive", () => {
777
+ function feedFullCephBatch(buffers: CephBuffers): void {
778
+ for (const mon of ["mon.a", "mon.b", "mon.c"]) {
779
+ feedCeph(
780
+ buffers,
781
+ "ceph_mon_metadata",
782
+ datapoint({
783
+ value: 1,
784
+ labels: {
785
+ ceph_daemon: mon,
786
+ hostname: `host-${mon}`,
787
+ ceph_version:
788
+ mon === "mon.c"
789
+ ? "ceph version 19.2.0 squid"
790
+ : "ceph version 18.2.2 reef",
791
+ },
792
+ }),
793
+ );
794
+ }
795
+ for (const [osd, up, inCluster] of [
796
+ ["osd.0", 1, 1],
797
+ ["osd.1", 1, 0],
798
+ ["osd.2", 0, 1],
799
+ ] as Array<[string, number, number]>) {
800
+ feedCeph(
801
+ buffers,
802
+ "ceph_osd_metadata",
803
+ datapoint({ value: 1, labels: { ceph_daemon: osd } }),
804
+ );
805
+ feedCeph(
806
+ buffers,
807
+ "ceph_osd_up",
808
+ datapoint({ value: up, labels: { ceph_daemon: osd } }),
809
+ );
810
+ feedCeph(
811
+ buffers,
812
+ "ceph_osd_in",
813
+ datapoint({ value: inCluster, labels: { ceph_daemon: osd } }),
814
+ );
815
+ }
816
+ feedCeph(
817
+ buffers,
818
+ "ceph_pool_metadata",
819
+ datapoint({ value: 1, labels: { pool_id: "1", name: "rbd" } }),
820
+ );
821
+ feedCeph(buffers, "ceph_health_status", datapoint({ value: 2 }));
822
+ feedCeph(buffers, "ceph_cluster_total_bytes", datapoint({ value: 3 }));
823
+ feedCeph(buffers, "ceph_cluster_total_used_bytes", datapoint({ value: 1 }));
824
+ }
825
+
826
+ test("a full batch derives counts, health, capacity and the modal version", () => {
827
+ const buffers: CephBuffers = cephBuffers();
828
+ feedFullCephBatch(buffers);
829
+
830
+ const extras: CephClusterSnapshotExtras = deriveCephClusterSnapshotExtras(
831
+ cephEntries(buffers),
832
+ buffers.clusterBuffer.get(CLUSTER),
833
+ );
834
+
835
+ expect(extras).toEqual({
836
+ monCount: 3,
837
+ osdCount: 3,
838
+ osdUpCount: 2,
839
+ osdInCount: 2,
840
+ poolCount: 1,
841
+ healthStatus: 2,
842
+ // 1/3 → rounded to 2 decimals (fingerprint-stability contract).
843
+ capacityUsedPercent: 33.33,
844
+ // 2× reef beats 1× squid.
845
+ cephVersion: "ceph version 18.2.2 reef",
846
+ });
847
+ });
848
+
849
+ test("a partial batch never zeroes a count and skips capacity without totals", () => {
850
+ const buffers: CephBuffers = cephBuffers();
851
+ // Latency mirror only — no identity / health / capacity series.
852
+ feedCeph(
853
+ buffers,
854
+ "ceph_osd_apply_latency_ms",
855
+ datapoint({ value: 12, labels: { ceph_daemon: "osd.0" } }),
856
+ );
857
+ // Used-bytes without total-bytes must not derive a percent.
858
+ feedCeph(buffers, "ceph_cluster_total_used_bytes", datapoint({ value: 1 }));
859
+
860
+ const extras: CephClusterSnapshotExtras = deriveCephClusterSnapshotExtras(
861
+ cephEntries(buffers),
862
+ buffers.clusterBuffer.get(CLUSTER),
863
+ );
864
+
865
+ expect(extras).toEqual({});
866
+ });
867
+
868
+ test("healthStatus 0 (HEALTH_OK) is still written — 0 is a value, not absence", () => {
869
+ const buffers: CephBuffers = cephBuffers();
870
+ feedCeph(buffers, "ceph_health_status", datapoint({ value: 0 }));
871
+
872
+ const extras: CephClusterSnapshotExtras = deriveCephClusterSnapshotExtras(
873
+ cephEntries(buffers),
874
+ buffers.clusterBuffer.get(CLUSTER),
875
+ );
876
+
877
+ expect(extras).toEqual({ healthStatus: 0 });
878
+ });
879
+ });