@oneuptime/common 12.0.2 → 12.0.4

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 (384) hide show
  1. package/Models/DatabaseModels/CephCluster.ts +14 -0
  2. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  3. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  4. package/Models/DatabaseModels/DockerHost.ts +14 -0
  5. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  6. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  7. package/Models/DatabaseModels/Index.ts +2 -0
  8. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  9. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  10. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  11. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  12. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  13. package/Models/DatabaseModels/Project.ts +16 -2
  14. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  15. package/Models/DatabaseModels/Service.ts +30 -0
  16. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  17. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  18. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  19. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  20. package/Models/DatabaseModels/User.ts +30 -0
  21. package/Server/API/AIAgentDataAPI.ts +31 -0
  22. package/Server/API/BaseAPI.ts +13 -2
  23. package/Server/API/DashboardAPI.ts +458 -12
  24. package/Server/API/GitHubAPI.ts +119 -283
  25. package/Server/API/UserAPI.ts +263 -1
  26. package/Server/EnvironmentConfig.ts +38 -4
  27. package/Server/Infrastructure/GlobalCache.ts +152 -0
  28. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  36. package/Server/Services/AlertStateTimelineService.ts +20 -0
  37. package/Server/Services/AnalyticsDatabaseService.ts +72 -1
  38. package/Server/Services/CephClusterService.ts +26 -86
  39. package/Server/Services/CloudResourceService.ts +23 -83
  40. package/Server/Services/CodeRepositoryService.ts +105 -2
  41. package/Server/Services/DatabaseService.ts +296 -2
  42. package/Server/Services/DockerHostService.ts +19 -79
  43. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  44. package/Server/Services/HostService.ts +46 -99
  45. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  46. package/Server/Services/Index.ts +2 -0
  47. package/Server/Services/IoTFleetService.ts +19 -79
  48. package/Server/Services/KubernetesClusterService.ts +17 -78
  49. package/Server/Services/LabelService.ts +57 -10
  50. package/Server/Services/MetricTypeService.ts +84 -0
  51. package/Server/Services/MonitorService.ts +89 -14
  52. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  53. package/Server/Services/MonitorTemplateService.ts +65 -0
  54. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  55. package/Server/Services/PodmanHostService.ts +19 -79
  56. package/Server/Services/ProxmoxClusterService.ts +23 -83
  57. package/Server/Services/RumApplicationService.ts +19 -80
  58. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  59. package/Server/Services/ServerlessFunctionService.ts +24 -85
  60. package/Server/Services/ServiceService.ts +299 -73
  61. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  62. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  63. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  64. package/Server/Services/TelemetryEntityService.ts +1 -0
  65. package/Server/Services/UserService.ts +184 -0
  66. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  67. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  68. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  69. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  72. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  73. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  74. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  75. package/Server/Utils/PasswordHash.ts +306 -0
  76. package/Server/Utils/SingleFlight.ts +83 -0
  77. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  78. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  79. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  80. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  81. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  82. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  83. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  84. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  85. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  86. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  87. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  88. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  89. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  90. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  91. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  92. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  93. package/Tests/Server/API/Helpers.ts +6 -1
  94. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  95. package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
  96. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  97. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  98. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  99. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  100. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
  101. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  102. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  103. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
  104. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  105. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  106. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  107. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  108. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  109. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  110. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  111. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  112. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  113. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  114. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  115. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  116. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  117. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  118. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  119. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  120. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  121. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  122. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  123. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  124. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  125. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  128. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  129. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  130. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  131. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  132. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  133. package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
  134. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  135. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  136. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  137. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  138. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  139. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  140. package/Tests/Types/Decimal.test.ts +37 -0
  141. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  142. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  143. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  144. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  145. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  146. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  147. package/Tests/Types/Port.test.ts +38 -0
  148. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  149. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  150. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  151. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  152. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  153. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  154. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  155. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  156. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  157. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  158. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  159. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  160. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  161. package/Types/Database/ColumnLength.ts +9 -1
  162. package/Types/Database/DatabaseProperty.ts +26 -0
  163. package/Types/Database/NumericColumnValue.ts +128 -0
  164. package/Types/Database/TableColumn.ts +11 -0
  165. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  166. package/Types/Decimal.ts +16 -4
  167. package/Types/HashedString.ts +139 -4
  168. package/Types/Permission.ts +48 -0
  169. package/Types/Port.ts +16 -4
  170. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  171. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  172. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  173. package/UI/Components/Navbar/NavBar.tsx +12 -2
  174. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  175. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  176. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  177. package/Utils/Dashboard/Components/Index.ts +7 -0
  178. package/Utils/TeamMembersByProject.ts +237 -0
  179. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  180. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  182. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  184. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  186. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  188. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  190. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  192. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  193. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  194. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  195. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  196. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  197. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  198. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  199. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  200. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  202. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/Project.js +16 -2
  204. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  206. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  208. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  210. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  211. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  212. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  214. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  216. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/User.js +32 -0
  218. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/BaseAPI.js +11 -1
  222. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +343 -10
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/GitHubAPI.js +94 -167
  226. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  227. package/build/dist/Server/API/UserAPI.js +187 -2
  228. package/build/dist/Server/API/UserAPI.js.map +1 -1
  229. package/build/dist/Server/EnvironmentConfig.js +34 -4
  230. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/GlobalCache.js +120 -0
  232. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  234. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  249. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  250. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/CephClusterService.js +26 -76
  254. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  255. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  256. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  257. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  258. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  259. package/build/dist/Server/Services/DatabaseService.js +246 -2
  260. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  261. package/build/dist/Server/Services/DockerHostService.js +19 -69
  262. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  263. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  264. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  265. package/build/dist/Server/Services/HostService.js +58 -102
  266. package/build/dist/Server/Services/HostService.js.map +1 -1
  267. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  268. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  269. package/build/dist/Server/Services/Index.js +2 -0
  270. package/build/dist/Server/Services/Index.js.map +1 -1
  271. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  272. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  273. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  274. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  275. package/build/dist/Server/Services/LabelService.js +42 -7
  276. package/build/dist/Server/Services/LabelService.js.map +1 -1
  277. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  278. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  279. package/build/dist/Server/Services/MonitorService.js +81 -9
  280. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  281. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  282. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  283. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  284. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  285. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  286. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  287. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  288. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  289. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  290. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  291. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  292. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  293. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  294. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  295. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  296. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  297. package/build/dist/Server/Services/ServiceService.js +236 -61
  298. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  299. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  300. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  301. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  302. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  303. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  305. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  306. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  307. package/build/dist/Server/Services/UserService.js +165 -0
  308. package/build/dist/Server/Services/UserService.js.map +1 -1
  309. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  310. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  311. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  312. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  313. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  314. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  315. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  316. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  317. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  318. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  320. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  322. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  326. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  327. package/build/dist/Server/Utils/PasswordHash.js +237 -0
  328. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  329. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  330. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  331. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  332. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  333. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  334. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  335. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  336. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  337. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  338. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  340. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  342. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  344. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  346. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  348. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  349. package/build/dist/Types/Database/ColumnLength.js +9 -1
  350. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  351. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  352. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  353. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  354. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  355. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  356. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  357. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  358. package/build/dist/Types/Decimal.js +14 -3
  359. package/build/dist/Types/Decimal.js.map +1 -1
  360. package/build/dist/Types/HashedString.js +95 -5
  361. package/build/dist/Types/HashedString.js.map +1 -1
  362. package/build/dist/Types/Permission.js +42 -0
  363. package/build/dist/Types/Permission.js.map +1 -1
  364. package/build/dist/Types/Port.js +15 -5
  365. package/build/dist/Types/Port.js.map +1 -1
  366. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  367. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  368. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  369. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  370. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  371. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  372. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  373. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  374. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  375. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  376. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  377. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  378. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  379. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  380. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  381. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  382. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  383. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  384. package/package.json +1 -1
@@ -30,6 +30,7 @@ import UpdateByID from "../Types/Database/UpdateByID";
30
30
  import UpdateByIDAndFetch from "../Types/Database/UpdateByIDAndFetch";
31
31
  import UpdateOneBy from "../Types/Database/UpdateOneBy";
32
32
  import Encryption from "../Utils/Encryption";
33
+ import PasswordHash from "../Utils/PasswordHash";
33
34
  import PostgresErrorTranslator from "../Utils/Database/PostgresErrorTranslator";
34
35
  import logger, { LogAttributes } from "../Utils/Logger";
35
36
  import BaseService from "./BaseService";
@@ -321,6 +322,55 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
321
322
  return data;
322
323
  }
323
324
 
325
+ /*
326
+ * Hash one HashedString column in place.
327
+ *
328
+ * Declaring `hashSaltColumn` on a column is what marks it a CREDENTIAL —
329
+ * a low-entropy, human-chosen secret. Those go through scrypt (see
330
+ * PasswordHash): deliberately slow and memory-hard, with a fresh per-record
331
+ * salt minted into the sibling column named by the metadata.
332
+ *
333
+ * Columns without a salt column are high-entropy values the server itself
334
+ * generated — session refresh tokens and the like. They keep the fast
335
+ * SHA-256 hash, which is the right tool for them: there is nothing to guess,
336
+ * and they have to stay searchable by hash because that is how they are
337
+ * looked up.
338
+ *
339
+ * The salt is written onto the SAME payload as the hash, so the pair can
340
+ * never be persisted out of step with each other.
341
+ *
342
+ * The salt column grants nobody create or update rights, so it has to get
343
+ * past the column-permission check some other way on each path. On update
344
+ * it does so by timing — sanitizeCreateOrUpdate runs after the check. On
345
+ * create the check runs after `hash()`, so the salt column relies on being
346
+ * declared `computed: true`, which checkDataColumnPermissions skips on
347
+ * create. A salt column that forgets that flag breaks every non-root
348
+ * create of a row carrying a password.
349
+ */
350
+ private async hashColumnValue(data: {
351
+ payload: TBaseModel | PartialEntity<TBaseModel>;
352
+ columnName: string;
353
+ columnValue: HashedString;
354
+ }): Promise<void> {
355
+ const saltColumnName: string | undefined =
356
+ this.model.getTableColumnMetadata(data.columnName)?.hashSaltColumn;
357
+
358
+ if (!saltColumnName) {
359
+ await data.columnValue.hashValue(EncryptionSecret);
360
+ return;
361
+ }
362
+
363
+ const salt: string = PasswordHash.generateSalt();
364
+ (data.payload as any)[saltColumnName] = salt;
365
+
366
+ data.columnValue.setHashedValue(
367
+ await PasswordHash.hash({
368
+ plainValue: data.columnValue.toString(),
369
+ salt: salt,
370
+ }),
371
+ );
372
+ }
373
+
324
374
  protected async hash(data: TBaseModel): Promise<TBaseModel> {
325
375
  const columns: Columns = data.getHashedColumns();
326
376
 
@@ -329,13 +379,110 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
329
379
  data.hasValue(key) &&
330
380
  !(data.getValue(key) as HashedString).isValueHashed()
331
381
  ) {
332
- await ((data as any)[key] as HashedString).hashValue(EncryptionSecret);
382
+ await this.hashColumnValue({
383
+ payload: data,
384
+ columnName: key,
385
+ columnValue: (data as any)[key] as HashedString,
386
+ });
333
387
  }
334
388
  }
335
389
 
336
390
  return data;
337
391
  }
338
392
 
393
+ /*
394
+ * Check a plaintext secret (a password) against a hashed column on a row
395
+ * that has already been read out of the database, and re-hash the stored
396
+ * value in place when it was not written by today's scheme.
397
+ *
398
+ * The row must have been selected with BOTH the hashed column and its salt
399
+ * column. Without the salt a scrypt hash cannot be checked at all, and an
400
+ * older SHA-256 one would be checked against the wrong scheme — either way
401
+ * a perfectly good password gets rejected. PasswordHash.verify throws
402
+ * rather than silently returning false for the scrypt case, because a
403
+ * missing SELECT is a bug and must not look like a bad password.
404
+ *
405
+ * Why the caller cannot just query by the hash any more: with a per-user
406
+ * salt the hash is not computable until the row's own salt is known, so
407
+ * `WHERE password = <hash>` has nothing to bind. Read the row by its
408
+ * identity (email, id) and verify here.
409
+ *
410
+ * THE UPGRADE. Anything that is not exactly what PasswordHash writes today
411
+ * is re-hashed once the password is known to be correct: the two SHA-256
412
+ * schemes that preceded scrypt, and any scrypt hash whose cost parameters
413
+ * have since been raised. That is what makes raising the cost a one-line
414
+ * change — users migrate as they log in, with no reset and no migration.
415
+ *
416
+ * The write is deliberately hook-free and leaves updatedAt alone: the user
417
+ * did not change their password, the server changed how it stores it.
418
+ * Firing the password-change hooks would revoke every session and mail the
419
+ * user about a change they did not make. A failed upgrade is logged and
420
+ * swallowed — a correct password must not be rejected because a
421
+ * bookkeeping write lost a race.
422
+ */
423
+ @CaptureSpan()
424
+ public async verifyHashedColumnValue(data: {
425
+ item: TBaseModel;
426
+ columnName: string;
427
+ plainValue: string;
428
+ }): Promise<boolean> {
429
+ const storedHash: string | undefined = (data.item as any)[
430
+ data.columnName
431
+ ]?.toString();
432
+
433
+ if (!storedHash || !data.plainValue) {
434
+ return false;
435
+ }
436
+
437
+ const saltColumnName: string | undefined =
438
+ this.model.getTableColumnMetadata(data.columnName)?.hashSaltColumn;
439
+
440
+ const salt: string | null = saltColumnName
441
+ ? ((data.item as any)[saltColumnName] as string | undefined) || null
442
+ : null;
443
+
444
+ const isValid: boolean = await PasswordHash.verify({
445
+ plainValue: data.plainValue,
446
+ storedValue: storedHash,
447
+ salt: salt,
448
+ });
449
+
450
+ if (!isValid) {
451
+ return false;
452
+ }
453
+
454
+ if (
455
+ saltColumnName &&
456
+ data.item.id &&
457
+ PasswordHash.needsUpgrade(storedHash)
458
+ ) {
459
+ try {
460
+ const newSalt: string = PasswordHash.generateSalt();
461
+
462
+ await this.updateColumnsByIdWithoutHooks({
463
+ id: data.item.id,
464
+ data: {
465
+ [data.columnName]: await PasswordHash.hash({
466
+ plainValue: data.plainValue,
467
+ salt: newSalt,
468
+ }),
469
+ [saltColumnName]: newSalt,
470
+ } as unknown as PartialEntity<TBaseModel>,
471
+ skipUpdateDateColumn: true,
472
+ });
473
+ } catch (err) {
474
+ /*
475
+ * `data.item` is deliberately left holding the old hash and old salt,
476
+ * so it stays an internally consistent pair whether or not the write
477
+ * above landed.
478
+ */
479
+ logger.error(err);
480
+ }
481
+ }
482
+
483
+ return true;
484
+ }
485
+
339
486
  protected async decrypt(data: TBaseModel): Promise<TBaseModel> {
340
487
  for (const key of data.getEncryptedColumns().columns) {
341
488
  if (!data.hasValue(key)) {
@@ -552,6 +699,26 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
552
699
  if (this.model.isHashedStringColumn(columnName)) {
553
700
  const columnValue: JSONValue = (data as any)[columnName];
554
701
 
702
+ /*
703
+ * A salted column's hash and its salt are only ever correct as a
704
+ * pair. A write that supplies the column as a bare string skips the
705
+ * hashing branch below, leaving the row's existing salt attached to
706
+ * a value it was not computed from — which locks the account out
707
+ * silently, at the next login, far from whatever wrote it. Refuse
708
+ * instead. Callers pass a HashedString; the write path hashes it.
709
+ */
710
+ if (
711
+ data &&
712
+ columnName &&
713
+ columnValue &&
714
+ !(columnValue instanceof HashedString) &&
715
+ this.model.getTableColumnMetadata(columnName)?.hashSaltColumn
716
+ ) {
717
+ throw new BadDataException(
718
+ `${columnName} is a salted column and must be supplied as a HashedString, not a pre-hashed value.`,
719
+ );
720
+ }
721
+
555
722
  if (
556
723
  data &&
557
724
  columnName &&
@@ -559,7 +726,23 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
559
726
  columnValue instanceof HashedString
560
727
  ) {
561
728
  if (!columnValue.isValueHashed()) {
562
- await columnValue.hashValue(EncryptionSecret);
729
+ /*
730
+ * The update path never runs `hash()`, so this is where an
731
+ * updated password gets both hashed and salted. The salt lands
732
+ * on `data`, which the caller turns into the SET list, so the
733
+ * new hash and the salt it was computed with are written by the
734
+ * same statement.
735
+ *
736
+ * An update matching several rows writes the same salt to all of
737
+ * them, which is correct: they are all being set to the same
738
+ * value by one statement. Per-row salts come from per-row
739
+ * writes.
740
+ */
741
+ await this.hashColumnValue({
742
+ payload: data,
743
+ columnName: columnName,
744
+ columnValue: columnValue,
745
+ });
563
746
  }
564
747
 
565
748
  (data as any)[columnName] = columnValue.toString();
@@ -2400,6 +2583,117 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
2400
2583
  await repository.manager.query(sql, params);
2401
2584
  }
2402
2585
 
2586
+ /*
2587
+ * Same single-statement, hook-free, no-version-bump write as
2588
+ * `updateColumnsByIdWithoutHooks`, except it YIELDS instead of queueing:
2589
+ * if another transaction already holds the row's lock, this write is
2590
+ * abandoned and the method returns false. It never waits.
2591
+ *
2592
+ * Why this exists. A single-statement UPDATE holds its row lock for a very
2593
+ * short time, but "short" does not mean "safe" once the arrival rate on ONE
2594
+ * row exceeds the service rate. Postgres queues lock waiters strictly, so
2595
+ * every waiter pins a backend for the sum of everyone ahead of it — the
2596
+ * classic convoy. In the outage this was written for, ~25K concurrent
2597
+ * ingest jobs across 100 worker pods funnelled onto ~2,300 Service rows and
2598
+ * produced 1,017 active connections with 892 of them parked on row locks;
2599
+ * the tail had been waiting 3.7 hours. Nothing about that is fixable by
2600
+ * making the statement faster, and no Postgres tier changes it: the queue
2601
+ * is the bug.
2602
+ *
2603
+ * `FOR UPDATE SKIP LOCKED` inverts it. A contended writer does zero work
2604
+ * and returns immediately, so the number of backends blocked on any single
2605
+ * row is capped at ONE no matter how many workers arrive together. The
2606
+ * convoy cannot form — and cannot form even if every upstream throttle
2607
+ * fails open at once (Redis down, fence bug, cache flush). That is the
2608
+ * point: this is the structural backstop underneath the throttles, not a
2609
+ * second copy of them.
2610
+ *
2611
+ * Use ONLY where losing the write is harmless because a concurrent writer
2612
+ * is writing the same thing — liveness timestamps and other idempotent
2613
+ * bookkeeping, whose contended value is by construction the value the
2614
+ * winner is already storing. It is the WRONG primitive for a write carrying
2615
+ * information the winner does not have (a changed attribute); for those the
2616
+ * caller must re-attempt, and `false` is the signal to do it. Returning a
2617
+ * boolean rather than swallowing the skip is deliberate: a silent drop here
2618
+ * would be indistinguishable from a successful write at the call site,
2619
+ * which is exactly how a "reliable" heartbeat quietly stops beating.
2620
+ *
2621
+ * Returns true when the row was updated, false when it was locked (or no
2622
+ * longer exists).
2623
+ */
2624
+ @CaptureSpan()
2625
+ public async updateColumnsByIdIfUnlockedWithoutHooks(input: {
2626
+ id: ObjectID;
2627
+ data: PartialEntity<TBaseModel>;
2628
+ skipUpdateDateColumn?: boolean;
2629
+ }): Promise<boolean> {
2630
+ if (!input.id) {
2631
+ throw new BadDataException("id is required");
2632
+ }
2633
+
2634
+ const repository: Repository<TBaseModel> = this.getRepository();
2635
+ const metadata: EntityMetadata = repository.metadata;
2636
+ const driver: Driver = repository.manager.connection.driver;
2637
+
2638
+ const setClauses: Array<string> = [];
2639
+ const params: Array<unknown> = [];
2640
+
2641
+ // Clamp on a shallow COPY — see updateColumnsByIdWithoutHooks.
2642
+ const data: ObjectLiteral = this.truncateStringColumnsToMaxLength({
2643
+ ...(input.data as ObjectLiteral),
2644
+ });
2645
+
2646
+ for (const [propertyName, value] of Object.entries(data)) {
2647
+ const column: ColumnMetadata | undefined =
2648
+ metadata.findColumnWithPropertyName(propertyName);
2649
+ if (!column) {
2650
+ throw new BadDataException(
2651
+ `updateColumnsByIdIfUnlockedWithoutHooks: unknown column "${propertyName}" on "${metadata.tableName}"`,
2652
+ );
2653
+ }
2654
+ if (typeof value === "function") {
2655
+ throw new BadDataException(
2656
+ `updateColumnsByIdIfUnlockedWithoutHooks: SQL-expression values are not supported (column "${propertyName}"); pass a literal value.`,
2657
+ );
2658
+ }
2659
+ params.push(driver.preparePersistentValue(value, column));
2660
+ setClauses.push(`"${column.databaseName}" = $${params.length}`);
2661
+ }
2662
+
2663
+ if (setClauses.length === 0) {
2664
+ return false;
2665
+ }
2666
+
2667
+ if (metadata.updateDateColumn && !input.skipUpdateDateColumn) {
2668
+ setClauses.push(
2669
+ `"${metadata.updateDateColumn.databaseName}" = CURRENT_TIMESTAMP`,
2670
+ );
2671
+ }
2672
+
2673
+ const primaryColumnName: string =
2674
+ metadata.primaryColumns[0]?.databaseName || "_id";
2675
+ params.push(input.id.toString());
2676
+
2677
+ /*
2678
+ * The lock is taken by the sub-SELECT and inherited by the UPDATE within
2679
+ * the same implicit transaction, so the outer write never blocks either.
2680
+ * RETURNING is how the affected-row count comes back: TypeORM's `query()`
2681
+ * surfaces the driver's rows and a bare UPDATE returns none, so without it
2682
+ * a skipped write is indistinguishable from an applied one.
2683
+ */
2684
+ const sql: string = `UPDATE "${metadata.tableName}" SET ${setClauses.join(
2685
+ ", ",
2686
+ )} WHERE "${primaryColumnName}" IN (SELECT "${primaryColumnName}" FROM "${
2687
+ metadata.tableName
2688
+ }" WHERE "${primaryColumnName}" = $${
2689
+ params.length
2690
+ } FOR UPDATE SKIP LOCKED) RETURNING "${primaryColumnName}"`;
2691
+
2692
+ const result: unknown = await repository.manager.query(sql, params);
2693
+
2694
+ return Array.isArray(result) && result.length > 0;
2695
+ }
2696
+
2403
2697
  /*
2404
2698
  * Atomically add to numeric columns and set literal columns for one row,
2405
2699
  * in a SINGLE auto-committed UPDATE, with no hooks and no `version` bump.
@@ -5,6 +5,7 @@ import Model from "../../Models/DatabaseModels/DockerHost";
5
5
  import Label from "../../Models/DatabaseModels/Label";
6
6
  import { OnCreate } from "../Types/Database/Hooks";
7
7
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
8
+ import ResourceHeartbeat from "../Utils/Telemetry/ResourceHeartbeat";
8
9
  import ObjectID from "../../Types/ObjectID";
9
10
  import QueryHelper from "../Types/Database/QueryHelper";
10
11
  import OneUptimeDate from "../../Types/Date";
@@ -180,7 +181,6 @@ export class Service extends DatabaseService<Model> {
180
181
  agentVersion?: string | undefined;
181
182
  },
182
183
  ): Promise<void> {
183
- const cacheKey: string = hostId.toString();
184
184
  const extrasFingerprint: string = crypto
185
185
  .createHash("sha1")
186
186
  .update(
@@ -192,34 +192,6 @@ export class Service extends DatabaseService<Model> {
192
192
  )
193
193
  .digest("hex");
194
194
 
195
- let cached: string | null = null;
196
- try {
197
- cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
198
- } catch {
199
- /*
200
- * Cache unavailable — fail open and refresh lastSeenAt anyway. A
201
- * cache error must never skip the DB write below, otherwise the
202
- * resource is wrongly marked "disconnected" while telemetry is
203
- * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
204
- */
205
- cached = null;
206
- }
207
-
208
- if (cached === extrasFingerprint) {
209
- return; // same data was written recently
210
- }
211
-
212
- try {
213
- await GlobalCache.setString(
214
- LAST_SEEN_CACHE_NAMESPACE,
215
- cacheKey,
216
- extrasFingerprint,
217
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
218
- );
219
- } catch {
220
- // Best-effort throttle write; proceed with the DB update regardless.
221
- }
222
-
223
195
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
196
  const liveness: any = {
225
197
  lastSeenAt: OneUptimeDate.getCurrentDate(),
@@ -227,66 +199,34 @@ export class Service extends DatabaseService<Model> {
227
199
  };
228
200
 
229
201
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
230
- const data: any = { ...liveness };
202
+ const metadata: any = {};
231
203
 
232
204
  if (extra?.osType) {
233
- data.osType = extra.osType;
205
+ metadata.osType = extra.osType;
234
206
  }
235
207
  if (extra?.osVersion) {
236
- data.osVersion = extra.osVersion;
208
+ metadata.osVersion = extra.osVersion;
237
209
  }
238
210
  if (extra?.agentVersion) {
239
- data.agentVersion = extra.agentVersion;
211
+ metadata.agentVersion = extra.agentVersion;
240
212
  }
241
213
 
242
214
  /*
243
- * Heartbeat write: a single-statement UPDATE with no hooks and no
244
- * `version` bump, avoiding the hot-row Postgres lock convoy that the
245
- * full updateOneById pipeline causes. See ServiceService.updateLastSeen.
246
- *
247
- * Collector-supplied strings are clamped to their declared column
248
- * widths inside updateColumnsByIdWithoutHooks — see
249
- * DatabaseService.truncateStringColumnsToMaxLength.
215
+ * One gated, non-blocking heartbeat write. The gates, the fail-open /
216
+ * fail-closed split and the liveness-only fallback all live in
217
+ * ResourceHeartbeat see there for why this row's throttle used to
218
+ * provide no throttling at all.
250
219
  */
251
- try {
252
- await this.updateColumnsByIdWithoutHooks({
253
- id: hostId,
254
- data: data,
255
- });
256
- } catch (err) {
257
- /*
258
- * Liveness must survive bad metadata. The enriched write carries
259
- * `lastSeenAt` and `otelCollectorStatus` alongside optional,
260
- * collector-supplied columns; if any one of them makes Postgres
261
- * reject the statement, lastSeenAt silently stops advancing and
262
- * markDisconnectedHosts flips a perfectly healthy Docker host to
263
- * "disconnected" 15 minutes later — with telemetry still arriving the
264
- * whole time. That was issue #3006 on the Host path (a host.ip list
265
- * longer than the then-varchar(500) column). Retry with liveness only
266
- * so the enrichment is what gets dropped, never the heartbeat.
267
- */
268
- logger.warn(
269
- `DockerHostService.updateLastSeen: metadata write failed for Docker host ${hostId.toString()}, falling back to a liveness-only update: ${
270
- err instanceof Error ? err.message : String(err)
271
- }`,
272
- );
273
-
274
- /*
275
- * The throttle fingerprint was already stored, so without this the
276
- * next batch would short-circuit on a cache hit and skip the retry
277
- * for the rest of the window.
278
- */
279
- try {
280
- await GlobalCache.deleteKey(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
281
- } catch {
282
- // Best-effort; the fallback write below is what actually matters.
283
- }
284
-
285
- await this.updateColumnsByIdWithoutHooks({
286
- id: hostId,
287
- data: liveness,
288
- });
289
- }
220
+ await ResourceHeartbeat.write({
221
+ service: this,
222
+ id: hostId,
223
+ cacheNamespace: LAST_SEEN_CACHE_NAMESPACE,
224
+ throttleInSeconds: LAST_SEEN_THROTTLE_SECONDS,
225
+ liveness: liveness,
226
+ metadata: metadata,
227
+ fingerprint: extrasFingerprint,
228
+ describe: `docker host ${hostId.toString()}`,
229
+ });
290
230
  }
291
231
 
292
232
  /**
@@ -5,6 +5,7 @@ import Model from "../../Models/DatabaseModels/DockerSwarmCluster";
5
5
  import Label from "../../Models/DatabaseModels/Label";
6
6
  import { OnCreate } from "../Types/Database/Hooks";
7
7
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
8
+ import ResourceHeartbeat from "../Utils/Telemetry/ResourceHeartbeat";
8
9
  import ObjectID from "../../Types/ObjectID";
9
10
  import QueryHelper from "../Types/Database/QueryHelper";
10
11
  import OneUptimeDate from "../../Types/Date";
@@ -182,7 +183,6 @@ export class Service extends DatabaseService<Model> {
182
183
  networkCount?: number | undefined;
183
184
  },
184
185
  ): Promise<void> {
185
- const cacheKey: string = clusterId.toString();
186
186
  const extrasFingerprint: string = crypto
187
187
  .createHash("sha1")
188
188
  .update(
@@ -202,34 +202,6 @@ export class Service extends DatabaseService<Model> {
202
202
  )
203
203
  .digest("hex");
204
204
 
205
- let cached: string | null = null;
206
- try {
207
- cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
208
- } catch {
209
- /*
210
- * Cache unavailable — fail open and refresh lastSeenAt anyway. A
211
- * cache error must never skip the DB write below, otherwise the
212
- * resource is wrongly marked "disconnected" while telemetry is
213
- * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
214
- */
215
- cached = null;
216
- }
217
-
218
- if (cached === extrasFingerprint) {
219
- return; // same data was written recently
220
- }
221
-
222
- try {
223
- await GlobalCache.setString(
224
- LAST_SEEN_CACHE_NAMESPACE,
225
- cacheKey,
226
- extrasFingerprint,
227
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
228
- );
229
- } catch {
230
- // Best-effort throttle write; proceed with the DB update regardless.
231
- }
232
-
233
205
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
206
  const liveness: any = {
235
207
  lastSeenAt: OneUptimeDate.getCurrentDate(),
@@ -237,92 +209,59 @@ export class Service extends DatabaseService<Model> {
237
209
  };
238
210
 
239
211
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
- const data: any = { ...liveness };
212
+ const metadata: any = {};
241
213
 
242
214
  if (extra?.dockerVersion) {
243
- data.dockerVersion = extra.dockerVersion;
215
+ metadata.dockerVersion = extra.dockerVersion;
244
216
  }
245
217
  if (extra?.swarmId) {
246
- data.swarmId = extra.swarmId;
218
+ metadata.swarmId = extra.swarmId;
247
219
  }
248
220
  if (extra?.agentVersion) {
249
- data.agentVersion = extra.agentVersion;
221
+ metadata.agentVersion = extra.agentVersion;
250
222
  }
251
223
  // Counts: 0 is a legitimate value — gate on undefined, not falsiness.
252
224
  if (extra?.nodeCount !== undefined) {
253
- data.nodeCount = extra.nodeCount;
225
+ metadata.nodeCount = extra.nodeCount;
254
226
  }
255
227
  if (extra?.readyNodeCount !== undefined) {
256
- data.readyNodeCount = extra.readyNodeCount;
228
+ metadata.readyNodeCount = extra.readyNodeCount;
257
229
  }
258
230
  if (extra?.managerNodeCount !== undefined) {
259
- data.managerNodeCount = extra.managerNodeCount;
231
+ metadata.managerNodeCount = extra.managerNodeCount;
260
232
  }
261
233
  if (extra?.serviceCount !== undefined) {
262
- data.serviceCount = extra.serviceCount;
234
+ metadata.serviceCount = extra.serviceCount;
263
235
  }
264
236
  if (extra?.taskCount !== undefined) {
265
- data.taskCount = extra.taskCount;
237
+ metadata.taskCount = extra.taskCount;
266
238
  }
267
239
  if (extra?.runningTaskCount !== undefined) {
268
- data.runningTaskCount = extra.runningTaskCount;
240
+ metadata.runningTaskCount = extra.runningTaskCount;
269
241
  }
270
242
  if (extra?.stackCount !== undefined) {
271
- data.stackCount = extra.stackCount;
243
+ metadata.stackCount = extra.stackCount;
272
244
  }
273
245
  if (extra?.networkCount !== undefined) {
274
- data.networkCount = extra.networkCount;
246
+ metadata.networkCount = extra.networkCount;
275
247
  }
276
248
 
277
249
  /*
278
- * Heartbeat write: a single-statement UPDATE with no hooks and no
279
- * `version` bump, avoiding the hot-row Postgres lock convoy that the
280
- * full updateOneById pipeline causes. See ServiceService.updateLastSeen.
281
- *
282
- * Collector-supplied strings are clamped to their declared column
283
- * widths inside updateColumnsByIdWithoutHooks — see
284
- * DatabaseService.truncateStringColumnsToMaxLength.
250
+ * One gated, non-blocking heartbeat write. The gates, the fail-open /
251
+ * fail-closed split and the liveness-only fallback all live in
252
+ * ResourceHeartbeat see there for why this row's throttle used to
253
+ * provide no throttling at all.
285
254
  */
286
- try {
287
- await this.updateColumnsByIdWithoutHooks({
288
- id: clusterId,
289
- data: data,
290
- });
291
- } catch (err) {
292
- /*
293
- * Liveness must survive bad metadata. The enriched write carries
294
- * `lastSeenAt` and `otelCollectorStatus` alongside optional,
295
- * collector-supplied columns; if any one of them makes Postgres
296
- * reject the statement, lastSeenAt silently stops advancing and
297
- * markDisconnectedClusters flips a perfectly healthy Docker Swarm
298
- * cluster to "disconnected" 15 minutes later — with telemetry still
299
- * arriving the whole time. That was issue #3006 on the Host path (a
300
- * host.ip list longer than the then-varchar(500) column). Retry with
301
- * liveness only so the enrichment is what gets dropped, never the
302
- * heartbeat.
303
- */
304
- logger.warn(
305
- `DockerSwarmClusterService.updateLastSeen: metadata write failed for Docker Swarm cluster ${clusterId.toString()}, falling back to a liveness-only update: ${
306
- err instanceof Error ? err.message : String(err)
307
- }`,
308
- );
309
-
310
- /*
311
- * The throttle fingerprint was already stored, so without this the
312
- * next batch would short-circuit on a cache hit and skip the retry
313
- * for the rest of the window.
314
- */
315
- try {
316
- await GlobalCache.deleteKey(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
317
- } catch {
318
- // Best-effort; the fallback write below is what actually matters.
319
- }
320
-
321
- await this.updateColumnsByIdWithoutHooks({
322
- id: clusterId,
323
- data: liveness,
324
- });
325
- }
255
+ await ResourceHeartbeat.write({
256
+ service: this,
257
+ id: clusterId,
258
+ cacheNamespace: LAST_SEEN_CACHE_NAMESPACE,
259
+ throttleInSeconds: LAST_SEEN_THROTTLE_SECONDS,
260
+ liveness: liveness,
261
+ metadata: metadata,
262
+ fingerprint: extrasFingerprint,
263
+ describe: `docker swarm cluster ${clusterId.toString()}`,
264
+ });
326
265
  }
327
266
 
328
267
  /**