@oneuptime/common 12.0.1 → 12.0.3

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 (385) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  2. package/Models/DatabaseModels/Host.ts +12 -4
  3. package/Models/DatabaseModels/Index.ts +2 -0
  4. package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
  5. package/Models/DatabaseModels/KubernetesResource.ts +8 -7
  6. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  7. package/Models/DatabaseModels/PodmanResource.ts +6 -6
  8. package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
  9. package/Models/DatabaseModels/StatusPage.ts +95 -0
  10. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  11. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  12. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  13. package/Server/API/BaseAPI.ts +13 -2
  14. package/Server/API/DashboardAPI.ts +39 -0
  15. package/Server/API/TeamMemberAPI.ts +85 -0
  16. package/Server/EnvironmentConfig.ts +31 -1
  17. package/Server/Infrastructure/GlobalCache.ts +115 -0
  18. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  25. package/Server/Middleware/ProjectAuthorization.ts +66 -4
  26. package/Server/Services/AlertStateTimelineService.ts +20 -0
  27. package/Server/Services/BillingService.ts +600 -51
  28. package/Server/Services/CephClusterService.ts +27 -45
  29. package/Server/Services/CephResourceService.ts +43 -4
  30. package/Server/Services/CloudResourceService.ts +24 -42
  31. package/Server/Services/DatabaseService.ts +198 -6
  32. package/Server/Services/DockerHostService.ts +20 -38
  33. package/Server/Services/DockerResourceService.ts +62 -8
  34. package/Server/Services/DockerSwarmClusterService.ts +28 -46
  35. package/Server/Services/DockerSwarmResourceService.ts +53 -4
  36. package/Server/Services/HostService.ts +47 -57
  37. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  38. package/Server/Services/Index.ts +2 -0
  39. package/Server/Services/IoTDeviceService.ts +1 -17
  40. package/Server/Services/IoTFleetService.ts +20 -38
  41. package/Server/Services/KubernetesClusterService.ts +18 -36
  42. package/Server/Services/KubernetesContainerService.ts +50 -9
  43. package/Server/Services/KubernetesResourceService.ts +53 -7
  44. package/Server/Services/LabelService.ts +153 -1
  45. package/Server/Services/MetricTypeService.ts +84 -0
  46. package/Server/Services/MonitorService.ts +146 -14
  47. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  48. package/Server/Services/MonitorTemplateService.ts +65 -0
  49. package/Server/Services/PodmanHostService.ts +20 -38
  50. package/Server/Services/PodmanResourceService.ts +62 -8
  51. package/Server/Services/ProjectService.ts +165 -35
  52. package/Server/Services/ProxmoxClusterService.ts +24 -42
  53. package/Server/Services/ProxmoxResourceService.ts +43 -4
  54. package/Server/Services/RumApplicationService.ts +20 -38
  55. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  56. package/Server/Services/ServerlessFunctionService.ts +25 -43
  57. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
  58. package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
  59. package/Server/Services/ServiceService.ts +299 -73
  60. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  61. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  62. package/Server/Services/StatusPageService.ts +95 -36
  63. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
  64. package/Server/Services/StatusPageSubscriberService.ts +3 -0
  65. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  66. package/Server/Services/TelemetryEntityService.ts +1 -0
  67. package/Server/Types/Database/JSONColumnQuery.ts +621 -0
  68. package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
  69. package/Server/Types/Database/QueryHelper.ts +17 -32
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
  72. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  73. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  74. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  75. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -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/UsersTableGroupsByPerson.test.tsx +448 -0
  82. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  83. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  84. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  85. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  86. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
  87. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  88. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
  89. package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
  90. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  91. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
  92. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
  93. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
  94. package/Tests/Server/Services/BillingService.test.ts +78 -3
  95. package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
  96. package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
  97. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  98. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  99. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
  100. package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
  101. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +658 -0
  102. package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
  103. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
  104. package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
  105. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  106. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  107. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  108. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
  109. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
  110. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +636 -0
  111. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
  112. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
  113. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  114. package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -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/StatusPageSubscriberReport.test.ts +29 -13
  119. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
  120. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
  121. package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
  122. package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
  123. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  124. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  125. package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  128. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  129. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  130. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  131. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  132. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  133. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  134. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  135. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  136. package/Tests/Types/Decimal.test.ts +37 -0
  137. package/Tests/Types/Events/Recurring.test.ts +157 -0
  138. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  139. package/Tests/Types/Port.test.ts +38 -0
  140. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  141. package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
  142. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
  143. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  144. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  145. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
  146. package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
  147. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  148. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  149. package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
  150. package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
  151. package/Types/CustomField/CustomFieldDefinition.ts +24 -0
  152. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  153. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  154. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  155. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  156. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  157. package/Types/Database/DatabaseProperty.ts +26 -0
  158. package/Types/Database/NumericColumnValue.ts +128 -0
  159. package/Types/Date.ts +112 -0
  160. package/Types/Decimal.ts +16 -4
  161. package/Types/Events/Recurring.ts +99 -1
  162. package/Types/Permission.ts +48 -0
  163. package/Types/Port.ts +16 -4
  164. package/Types/StatusPage/StatusPageReport.ts +11 -0
  165. package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
  166. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  167. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  168. package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
  169. package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
  170. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
  171. package/UI/Utils/TeamMembersByUser.ts +302 -0
  172. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  173. package/Utils/Dashboard/Components/Index.ts +7 -0
  174. package/Utils/StatusPage/ReportPeriod.ts +236 -0
  175. package/Utils/Telemetry/HostIpAddresses.ts +98 -0
  176. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  177. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  178. package/build/dist/Models/DatabaseModels/Host.js +12 -4
  179. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  180. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  181. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  182. package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
  183. package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
  184. package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
  185. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  187. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
  189. package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
  191. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
  193. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  195. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  196. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  197. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  199. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  200. package/build/dist/Server/API/BaseAPI.js +11 -1
  201. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  202. package/build/dist/Server/API/DashboardAPI.js +39 -0
  203. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  204. package/build/dist/Server/API/TeamMemberAPI.js +57 -2
  205. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  206. package/build/dist/Server/EnvironmentConfig.js +27 -1
  207. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  208. package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
  209. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  210. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  211. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  212. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
  213. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
  214. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
  215. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
  216. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
  217. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
  218. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
  219. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
  220. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  221. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  224. package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
  225. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  226. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  227. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  228. package/build/dist/Server/Services/BillingService.js +466 -36
  229. package/build/dist/Server/Services/BillingService.js.map +1 -1
  230. package/build/dist/Server/Services/CephClusterService.js +26 -39
  231. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  232. package/build/dist/Server/Services/CephResourceService.js +28 -3
  233. package/build/dist/Server/Services/CephResourceService.js.map +1 -1
  234. package/build/dist/Server/Services/CloudResourceService.js +23 -36
  235. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  236. package/build/dist/Server/Services/DatabaseService.js +155 -2
  237. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  238. package/build/dist/Server/Services/DockerHostService.js +19 -32
  239. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  240. package/build/dist/Server/Services/DockerResourceService.js +34 -4
  241. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  242. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -40
  243. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  244. package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
  245. package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
  246. package/build/dist/Server/Services/HostService.js +58 -64
  247. package/build/dist/Server/Services/HostService.js.map +1 -1
  248. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  249. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  250. package/build/dist/Server/Services/Index.js +2 -0
  251. package/build/dist/Server/Services/Index.js.map +1 -1
  252. package/build/dist/Server/Services/IoTDeviceService.js +1 -8
  253. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  254. package/build/dist/Server/Services/IoTFleetService.js +19 -32
  255. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  256. package/build/dist/Server/Services/KubernetesClusterService.js +17 -30
  257. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  258. package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
  259. package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
  260. package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
  261. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  262. package/build/dist/Server/Services/LabelService.js +127 -0
  263. package/build/dist/Server/Services/LabelService.js.map +1 -1
  264. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  265. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  266. package/build/dist/Server/Services/MonitorService.js +128 -9
  267. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  268. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  269. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  270. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  271. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  272. package/build/dist/Server/Services/PodmanHostService.js +19 -32
  273. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  274. package/build/dist/Server/Services/PodmanResourceService.js +34 -4
  275. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  276. package/build/dist/Server/Services/ProjectService.js +135 -21
  277. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  278. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -36
  279. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  280. package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
  281. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  282. package/build/dist/Server/Services/RumApplicationService.js +19 -32
  283. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  284. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  285. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  286. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -37
  287. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  288. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
  289. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
  290. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
  291. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  292. package/build/dist/Server/Services/ServiceService.js +236 -61
  293. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  294. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  295. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  296. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  297. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  298. package/build/dist/Server/Services/StatusPageService.js +69 -46
  299. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  300. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
  301. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  302. package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
  303. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  305. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  306. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  307. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  308. package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
  309. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
  310. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
  311. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
  312. package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
  313. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  314. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  315. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  316. package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
  317. package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
  318. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  320. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  322. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  326. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  327. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  328. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  329. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  330. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  331. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  332. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  333. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  334. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  335. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  336. package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
  337. package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
  338. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  340. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  342. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  344. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  346. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  348. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  349. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  350. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  351. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  352. package/build/dist/Types/Date.js +68 -0
  353. package/build/dist/Types/Date.js.map +1 -1
  354. package/build/dist/Types/Decimal.js +14 -3
  355. package/build/dist/Types/Decimal.js.map +1 -1
  356. package/build/dist/Types/Events/Recurring.js +63 -0
  357. package/build/dist/Types/Events/Recurring.js.map +1 -1
  358. package/build/dist/Types/Permission.js +42 -0
  359. package/build/dist/Types/Permission.js.map +1 -1
  360. package/build/dist/Types/Port.js +15 -5
  361. package/build/dist/Types/Port.js.map +1 -1
  362. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
  363. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
  364. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  365. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  366. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  367. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  368. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  369. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
  370. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  371. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
  372. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
  373. package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
  374. package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
  375. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  376. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  377. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  378. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  379. package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
  380. package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
  381. package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
  382. package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
  383. package/jest.config.json +2 -0
  384. package/package.json +1 -1
  385. package/tsconfig.json +12 -1
@@ -5,6 +5,7 @@ import Model from "../../Models/DatabaseModels/CephCluster";
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";
@@ -181,7 +182,6 @@ export class Service extends DatabaseService<Model> {
181
182
  capacityUsedPercent?: number | undefined;
182
183
  },
183
184
  ): Promise<void> {
184
- const cacheKey: string = clusterId.toString();
185
185
  const extrasFingerprint: string = crypto
186
186
  .createHash("sha1")
187
187
  .update(
@@ -200,83 +200,65 @@ export class Service extends DatabaseService<Model> {
200
200
  )
201
201
  .digest("hex");
202
202
 
203
- let cached: string | null = null;
204
- try {
205
- cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
206
- } catch {
207
- /*
208
- * Cache unavailable — fail open and refresh lastSeenAt anyway. A
209
- * cache error must never skip the DB write below, otherwise the
210
- * resource is wrongly marked "disconnected" while telemetry is
211
- * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
212
- */
213
- cached = null;
214
- }
215
-
216
- if (cached === extrasFingerprint) {
217
- return; // same data was written recently
218
- }
219
-
220
- try {
221
- await GlobalCache.setString(
222
- LAST_SEEN_CACHE_NAMESPACE,
223
- cacheKey,
224
- extrasFingerprint,
225
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
226
- );
227
- } catch {
228
- // Best-effort throttle write; proceed with the DB update regardless.
229
- }
230
-
231
203
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
232
- const data: any = {
204
+ const liveness: any = {
233
205
  lastSeenAt: OneUptimeDate.getCurrentDate(),
234
206
  otelCollectorStatus: "connected",
235
207
  };
236
208
 
209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
+ const metadata: any = {};
211
+
237
212
  if (extra?.cephVersion) {
238
- data.cephVersion = extra.cephVersion;
213
+ metadata.cephVersion = extra.cephVersion;
239
214
  }
240
215
  if (extra?.fsid) {
241
- data.fsid = extra.fsid;
216
+ metadata.fsid = extra.fsid;
242
217
  }
243
218
  if (extra?.agentVersion) {
244
- data.agentVersion = extra.agentVersion;
219
+ metadata.agentVersion = extra.agentVersion;
245
220
  }
246
221
  /*
247
222
  * Counts and health: 0 is a legitimate value (healthStatus 0 = OK,
248
223
  * osdUpCount 0 = every OSD down) — gate on undefined, not falsiness.
249
224
  */
250
225
  if (extra?.monCount !== undefined) {
251
- data.monCount = extra.monCount;
226
+ metadata.monCount = extra.monCount;
252
227
  }
253
228
  if (extra?.osdCount !== undefined) {
254
- data.osdCount = extra.osdCount;
229
+ metadata.osdCount = extra.osdCount;
255
230
  }
256
231
  if (extra?.osdUpCount !== undefined) {
257
- data.osdUpCount = extra.osdUpCount;
232
+ metadata.osdUpCount = extra.osdUpCount;
258
233
  }
259
234
  if (extra?.osdInCount !== undefined) {
260
- data.osdInCount = extra.osdInCount;
235
+ metadata.osdInCount = extra.osdInCount;
261
236
  }
262
237
  if (extra?.poolCount !== undefined) {
263
- data.poolCount = extra.poolCount;
238
+ metadata.poolCount = extra.poolCount;
264
239
  }
265
240
  if (extra?.healthStatus !== undefined) {
266
- data.healthStatus = extra.healthStatus;
241
+ metadata.healthStatus = extra.healthStatus;
267
242
  }
268
243
  if (extra?.capacityUsedPercent !== undefined) {
269
- data.capacityUsedPercent = extra.capacityUsedPercent;
244
+ metadata.capacityUsedPercent = extra.capacityUsedPercent;
270
245
  }
271
246
 
272
247
  /*
273
- * Heartbeat write: a single-statement UPDATE with no hooks and no
274
- * `version` bump, avoiding the hot-row Postgres lock convoy that the
275
- * full updateOneById pipeline causes. See ServiceService.updateLastSeen.
248
+ * One gated, non-blocking heartbeat write. The gates, the fail-open /
249
+ * fail-closed split and the liveness-only fallback all live in
250
+ * ResourceHeartbeat see there for why this row's throttle used to
251
+ * provide no throttling at all.
276
252
  */
277
- await this.updateColumnsByIdWithoutHooks({
253
+ await ResourceHeartbeat.write({
254
+ service: this,
278
255
  id: clusterId,
279
- data: data,
256
+ cacheNamespace: LAST_SEEN_CACHE_NAMESPACE,
257
+ throttleInSeconds: LAST_SEEN_THROTTLE_SECONDS,
258
+ liveness: liveness,
259
+ metadata: metadata,
260
+ fingerprint: extrasFingerprint,
261
+ describe: `ceph cluster ${clusterId.toString()}`,
280
262
  });
281
263
  }
282
264
 
@@ -1,8 +1,10 @@
1
1
  import DatabaseService from "./DatabaseService";
2
2
  import Model from "../../Models/DatabaseModels/CephResource";
3
3
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
4
+ import ColumnLength from "../../Types/Database/ColumnLength";
4
5
  import ObjectID from "../../Types/ObjectID";
5
6
  import OneUptimeDate from "../../Types/Date";
7
+ import { truncateShortText } from "../Utils/Database/TruncateColumnValue";
6
8
  import logger from "../Utils/Logger";
7
9
 
8
10
  /*
@@ -86,6 +88,26 @@ const UPSERT_COLUMNS: Array<string> = [
86
88
  "version",
87
89
  ];
88
90
 
91
+ /*
92
+ * CephResource's text columns are all ShortText (100 chars). The bulk
93
+ * paths below go through manager.query(), which skips the length
94
+ * validation DatabaseService applies to ordinary writes, so a single
95
+ * oversized value aborts the whole 500-row INSERT chunk it rides in.
96
+ * Clamp per value instead, so one pathological daemon can never drop
97
+ * the rest of the scrape.
98
+ */
99
+ function sanitizeResource(r: ParsedCephResource): ParsedCephResource {
100
+ return {
101
+ ...r,
102
+ kind: truncateShortText(r.kind),
103
+ externalId: truncateShortText(r.externalId),
104
+ name: truncateShortText(r.name),
105
+ hostname: truncateShortText(r.hostname),
106
+ daemonVersion: truncateShortText(r.daemonVersion),
107
+ deviceClass: truncateShortText(r.deviceClass),
108
+ };
109
+ }
110
+
89
111
  export class Service extends DatabaseService<Model> {
90
112
  public constructor() {
91
113
  super(Model);
@@ -112,9 +134,21 @@ export class Service extends DatabaseService<Model> {
112
134
  return;
113
135
  }
114
136
 
137
+ const resources: Array<ParsedCephResource> = data.resources.map(
138
+ (r: ParsedCephResource) => {
139
+ const sanitized: ParsedCephResource = sanitizeResource(r);
140
+ if (sanitized.externalId !== r.externalId) {
141
+ logger.warn(
142
+ `CephResource externalId exceeds ${ColumnLength.ShortText} chars; truncated to "${sanitized.externalId}" (cluster ${data.cephClusterId.toString()}).`,
143
+ );
144
+ }
145
+ return sanitized;
146
+ },
147
+ );
148
+
115
149
  // Chunk to keep individual statement parameter counts reasonable.
116
- for (let i: number = 0; i < data.resources.length; i += UPSERT_BATCH_SIZE) {
117
- const chunk: Array<ParsedCephResource> = data.resources.slice(
150
+ for (let i: number = 0; i < resources.length; i += UPSERT_BATCH_SIZE) {
151
+ const chunk: Array<ParsedCephResource> = resources.slice(
118
152
  i,
119
153
  i + UPSERT_BATCH_SIZE,
120
154
  );
@@ -218,9 +252,14 @@ export class Service extends DatabaseService<Model> {
218
252
  valueFragments.push(
219
253
  `($${paramIndex++}, $${paramIndex++}, $${paramIndex++}::bigint, $${paramIndex++}::bigint, $${paramIndex++}::numeric, $${paramIndex++}::numeric, $${paramIndex++}::integer, $${paramIndex++}::bigint, $${paramIndex++}::bigint, $${paramIndex++}::bigint, $${paramIndex++}::bigint, $${paramIndex++}::bigint, $${paramIndex++}::timestamptz)`,
220
254
  );
255
+ /*
256
+ * The identity clamps MUST match bulkUpsert's, or this mirror
257
+ * UPDATE would silently miss the row the upsert just wrote
258
+ * under the truncated externalId.
259
+ */
221
260
  params.push(
222
- m.kind,
223
- m.externalId,
261
+ truncateShortText(m.kind),
262
+ truncateShortText(m.externalId),
224
263
  bigintOrNull(m.statBytes),
225
264
  bigintOrNull(m.statBytesUsed),
226
265
  m.applyLatencyMs !== null && m.applyLatencyMs !== undefined
@@ -2,6 +2,7 @@ import DatabaseService from "./DatabaseService";
2
2
  import Model from "../../Models/DatabaseModels/CloudResource";
3
3
  import Label from "../../Models/DatabaseModels/Label";
4
4
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
5
+ import ResourceHeartbeat from "../Utils/Telemetry/ResourceHeartbeat";
5
6
  import ObjectID from "../../Types/ObjectID";
6
7
  import QueryHelper from "../Types/Database/QueryHelper";
7
8
  import OneUptimeDate from "../../Types/Date";
@@ -154,7 +155,6 @@ export class Service extends DatabaseService<Model> {
154
155
  runtimeVersion?: string | undefined;
155
156
  },
156
157
  ): Promise<void> {
157
- const cacheKey: string = cloudResourceId.toString();
158
158
  const extrasFingerprint: string = crypto
159
159
  .createHash("sha1")
160
160
  .update(
@@ -170,70 +170,52 @@ export class Service extends DatabaseService<Model> {
170
170
  )
171
171
  .digest("hex");
172
172
 
173
- let cached: string | null = null;
174
- try {
175
- cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
176
- } catch {
177
- /*
178
- * Cache unavailable — fail open and refresh lastSeenAt anyway. A
179
- * cache error must never skip the DB write below, otherwise the
180
- * resource is wrongly marked "disconnected" while telemetry is
181
- * still flowing. Mirrors shouldRunMaintenance's fail-open stance.
182
- */
183
- cached = null;
184
- }
185
-
186
- if (cached === extrasFingerprint) {
187
- return; // same data was written recently
188
- }
189
-
190
- try {
191
- await GlobalCache.setString(
192
- LAST_SEEN_CACHE_NAMESPACE,
193
- cacheKey,
194
- extrasFingerprint,
195
- { expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS },
196
- );
197
- } catch {
198
- // Best-effort throttle write; proceed with the DB update regardless.
199
- }
200
-
201
173
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
- const data: any = {
174
+ const liveness: any = {
203
175
  lastSeenAt: OneUptimeDate.getCurrentDate(),
204
176
  otelCollectorStatus: "connected",
205
177
  };
206
178
 
179
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
+ const metadata: any = {};
181
+
207
182
  if (extra?.agentVersion) {
208
- data.agentVersion = extra.agentVersion;
183
+ metadata.agentVersion = extra.agentVersion;
209
184
  }
210
185
  if (extra?.cloudPlatform) {
211
- data.cloudPlatform = extra.cloudPlatform;
186
+ metadata.cloudPlatform = extra.cloudPlatform;
212
187
  }
213
188
  if (extra?.cloudProvider) {
214
- data.cloudProvider = extra.cloudProvider;
189
+ metadata.cloudProvider = extra.cloudProvider;
215
190
  }
216
191
  if (extra?.cloudRegion) {
217
- data.cloudRegion = extra.cloudRegion;
192
+ metadata.cloudRegion = extra.cloudRegion;
218
193
  }
219
194
  if (extra?.cloudAccountId) {
220
- data.cloudAccountId = extra.cloudAccountId;
195
+ metadata.cloudAccountId = extra.cloudAccountId;
221
196
  }
222
197
  if (extra?.runtimeName) {
223
- data.runtimeName = extra.runtimeName;
198
+ metadata.runtimeName = extra.runtimeName;
224
199
  }
225
200
  if (extra?.runtimeVersion) {
226
- data.runtimeVersion = extra.runtimeVersion;
201
+ metadata.runtimeVersion = extra.runtimeVersion;
227
202
  }
228
203
 
229
204
  /*
230
- * Heartbeat write: a single-statement UPDATE with no hooks and no
231
- * `version` bump, avoiding the hot-row Postgres lock convoy that the
232
- * full updateOneById pipeline causes. See ServiceService.updateLastSeen.
205
+ * One gated, non-blocking heartbeat write. The gates, the fail-open /
206
+ * fail-closed split and the liveness-only fallback all live in
207
+ * ResourceHeartbeat see there for why this row's throttle used to
208
+ * provide no throttling at all.
233
209
  */
234
- await this.updateColumnsByIdWithoutHooks({
210
+ await ResourceHeartbeat.write({
211
+ service: this,
235
212
  id: cloudResourceId,
236
- data: data,
213
+ cacheNamespace: LAST_SEEN_CACHE_NAMESPACE,
214
+ throttleInSeconds: LAST_SEEN_THROTTLE_SECONDS,
215
+ liveness: liveness,
216
+ metadata: metadata,
217
+ fingerprint: extrasFingerprint,
218
+ describe: `cloud resource ${cloudResourceId.toString()}`,
237
219
  });
238
220
  }
239
221
 
@@ -1046,6 +1046,61 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
1046
1046
  return data;
1047
1047
  }
1048
1048
 
1049
+ /*
1050
+ * Clamp string values to the max length their column declares, instead of
1051
+ * rejecting the whole write the way checkMaxLengthOfFields does.
1052
+ *
1053
+ * For machine-stamped metadata harvested from OpenTelemetry resource
1054
+ * attributes there is no user to show a validation error to, and the
1055
+ * write these values ride along with also carries liveness columns
1056
+ * (lastSeenAt / otelCollectorStatus). Letting one oversized optional
1057
+ * attribute abort that statement strands a healthy resource as
1058
+ * "disconnected" — the failure mode in issue #3006. A clipped display
1059
+ * string is strictly better than a lost heartbeat.
1060
+ *
1061
+ * Only string values on columns with a declared max length are touched;
1062
+ * `text` columns (VeryLongText and friends) declare none and pass
1063
+ * through untouched, as do numbers, dates and ObjectIDs.
1064
+ *
1065
+ * The raw, hook-free write paths (updateColumnsByIdWithoutHooks and
1066
+ * atomicAddToColumnsByIdWithoutHooks) call this themselves, so callers do
1067
+ * NOT need to — and must not rely on being able to skip it by writing a
1068
+ * new raw path of their own. It is NOT a substitute for sizing a column
1069
+ * correctly: if a field is legitimately long, widen the column.
1070
+ */
1071
+ public truncateStringColumnsToMaxLength(
1072
+ data: Record<string, unknown>,
1073
+ ): Record<string, unknown> {
1074
+ for (const column of Object.keys(data)) {
1075
+ const value: unknown = data[column];
1076
+
1077
+ if (typeof value !== "string" || value.length === 0) {
1078
+ continue;
1079
+ }
1080
+
1081
+ /*
1082
+ * Returns undefined for an unmapped column name despite its type —
1083
+ * leave those alone and let the write path complain about them.
1084
+ */
1085
+ const metadata: TableColumnMetadata | undefined =
1086
+ this.model.getTableColumnMetadata(column);
1087
+
1088
+ if (!metadata?.type) {
1089
+ continue;
1090
+ }
1091
+
1092
+ const maxLength: number | undefined = getMaxLengthFromTableColumnType(
1093
+ metadata.type,
1094
+ );
1095
+
1096
+ if (maxLength !== undefined && value.length > maxLength) {
1097
+ data[column] = value.substring(0, maxLength);
1098
+ }
1099
+ }
1100
+
1101
+ return data;
1102
+ }
1103
+
1049
1104
  private async checkTotalItemsBy(
1050
1105
  createdBy: CreateBy<TBaseModel>,
1051
1106
  ): Promise<void> {
@@ -2248,6 +2303,17 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
2248
2303
  * the caller), and every value is bound as a parameter, so this is not an
2249
2304
  * injection surface. Use ONLY for trusted, internal, side-effect-free
2250
2305
  * column writes where the full pipeline is pure overhead.
2306
+ *
2307
+ * Oversized strings are CLAMPED here (truncateStringColumnsToMaxLength)
2308
+ * rather than rejected. Skipping the hooks also skips
2309
+ * checkMaxLengthOfFields, so before this an over-long value reached
2310
+ * Postgres raw and failed the ENTIRE statement — including whatever else
2311
+ * rode along with it. On the telemetry liveness writes that is fatal: one
2312
+ * over-long OpenTelemetry resource attribute took `lastSeenAt` down with
2313
+ * it and markDisconnected* stranded a healthy resource as "disconnected"
2314
+ * 15 minutes later while its data kept arriving (issue #3006). Clamping
2315
+ * in here, at the choke point, is what makes that unforgettable — every
2316
+ * caller of this path gets it, including ones not written yet.
2251
2317
  */
2252
2318
  @CaptureSpan()
2253
2319
  public async updateColumnsByIdWithoutHooks(input: {
@@ -2273,9 +2339,16 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
2273
2339
  const setClauses: Array<string> = [];
2274
2340
  const params: Array<unknown> = [];
2275
2341
 
2276
- for (const [propertyName, value] of Object.entries(
2277
- input.data as ObjectLiteral,
2278
- )) {
2342
+ /*
2343
+ * Clamp on a shallow COPY: callers must not have their own object
2344
+ * mutated behind their back (a liveness-only retry, for instance,
2345
+ * rebuilds its payload from the same source values).
2346
+ */
2347
+ const data: ObjectLiteral = this.truncateStringColumnsToMaxLength({
2348
+ ...(input.data as ObjectLiteral),
2349
+ });
2350
+
2351
+ for (const [propertyName, value] of Object.entries(data)) {
2279
2352
  const column: ColumnMetadata | undefined =
2280
2353
  metadata.findColumnWithPropertyName(propertyName);
2281
2354
  if (!column) {
@@ -2327,6 +2400,117 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
2327
2400
  await repository.manager.query(sql, params);
2328
2401
  }
2329
2402
 
2403
+ /*
2404
+ * Same single-statement, hook-free, no-version-bump write as
2405
+ * `updateColumnsByIdWithoutHooks`, except it YIELDS instead of queueing:
2406
+ * if another transaction already holds the row's lock, this write is
2407
+ * abandoned and the method returns false. It never waits.
2408
+ *
2409
+ * Why this exists. A single-statement UPDATE holds its row lock for a very
2410
+ * short time, but "short" does not mean "safe" once the arrival rate on ONE
2411
+ * row exceeds the service rate. Postgres queues lock waiters strictly, so
2412
+ * every waiter pins a backend for the sum of everyone ahead of it — the
2413
+ * classic convoy. In the outage this was written for, ~25K concurrent
2414
+ * ingest jobs across 100 worker pods funnelled onto ~2,300 Service rows and
2415
+ * produced 1,017 active connections with 892 of them parked on row locks;
2416
+ * the tail had been waiting 3.7 hours. Nothing about that is fixable by
2417
+ * making the statement faster, and no Postgres tier changes it: the queue
2418
+ * is the bug.
2419
+ *
2420
+ * `FOR UPDATE SKIP LOCKED` inverts it. A contended writer does zero work
2421
+ * and returns immediately, so the number of backends blocked on any single
2422
+ * row is capped at ONE no matter how many workers arrive together. The
2423
+ * convoy cannot form — and cannot form even if every upstream throttle
2424
+ * fails open at once (Redis down, fence bug, cache flush). That is the
2425
+ * point: this is the structural backstop underneath the throttles, not a
2426
+ * second copy of them.
2427
+ *
2428
+ * Use ONLY where losing the write is harmless because a concurrent writer
2429
+ * is writing the same thing — liveness timestamps and other idempotent
2430
+ * bookkeeping, whose contended value is by construction the value the
2431
+ * winner is already storing. It is the WRONG primitive for a write carrying
2432
+ * information the winner does not have (a changed attribute); for those the
2433
+ * caller must re-attempt, and `false` is the signal to do it. Returning a
2434
+ * boolean rather than swallowing the skip is deliberate: a silent drop here
2435
+ * would be indistinguishable from a successful write at the call site,
2436
+ * which is exactly how a "reliable" heartbeat quietly stops beating.
2437
+ *
2438
+ * Returns true when the row was updated, false when it was locked (or no
2439
+ * longer exists).
2440
+ */
2441
+ @CaptureSpan()
2442
+ public async updateColumnsByIdIfUnlockedWithoutHooks(input: {
2443
+ id: ObjectID;
2444
+ data: PartialEntity<TBaseModel>;
2445
+ skipUpdateDateColumn?: boolean;
2446
+ }): Promise<boolean> {
2447
+ if (!input.id) {
2448
+ throw new BadDataException("id is required");
2449
+ }
2450
+
2451
+ const repository: Repository<TBaseModel> = this.getRepository();
2452
+ const metadata: EntityMetadata = repository.metadata;
2453
+ const driver: Driver = repository.manager.connection.driver;
2454
+
2455
+ const setClauses: Array<string> = [];
2456
+ const params: Array<unknown> = [];
2457
+
2458
+ // Clamp on a shallow COPY — see updateColumnsByIdWithoutHooks.
2459
+ const data: ObjectLiteral = this.truncateStringColumnsToMaxLength({
2460
+ ...(input.data as ObjectLiteral),
2461
+ });
2462
+
2463
+ for (const [propertyName, value] of Object.entries(data)) {
2464
+ const column: ColumnMetadata | undefined =
2465
+ metadata.findColumnWithPropertyName(propertyName);
2466
+ if (!column) {
2467
+ throw new BadDataException(
2468
+ `updateColumnsByIdIfUnlockedWithoutHooks: unknown column "${propertyName}" on "${metadata.tableName}"`,
2469
+ );
2470
+ }
2471
+ if (typeof value === "function") {
2472
+ throw new BadDataException(
2473
+ `updateColumnsByIdIfUnlockedWithoutHooks: SQL-expression values are not supported (column "${propertyName}"); pass a literal value.`,
2474
+ );
2475
+ }
2476
+ params.push(driver.preparePersistentValue(value, column));
2477
+ setClauses.push(`"${column.databaseName}" = $${params.length}`);
2478
+ }
2479
+
2480
+ if (setClauses.length === 0) {
2481
+ return false;
2482
+ }
2483
+
2484
+ if (metadata.updateDateColumn && !input.skipUpdateDateColumn) {
2485
+ setClauses.push(
2486
+ `"${metadata.updateDateColumn.databaseName}" = CURRENT_TIMESTAMP`,
2487
+ );
2488
+ }
2489
+
2490
+ const primaryColumnName: string =
2491
+ metadata.primaryColumns[0]?.databaseName || "_id";
2492
+ params.push(input.id.toString());
2493
+
2494
+ /*
2495
+ * The lock is taken by the sub-SELECT and inherited by the UPDATE within
2496
+ * the same implicit transaction, so the outer write never blocks either.
2497
+ * RETURNING is how the affected-row count comes back: TypeORM's `query()`
2498
+ * surfaces the driver's rows and a bare UPDATE returns none, so without it
2499
+ * a skipped write is indistinguishable from an applied one.
2500
+ */
2501
+ const sql: string = `UPDATE "${metadata.tableName}" SET ${setClauses.join(
2502
+ ", ",
2503
+ )} WHERE "${primaryColumnName}" IN (SELECT "${primaryColumnName}" FROM "${
2504
+ metadata.tableName
2505
+ }" WHERE "${primaryColumnName}" = $${
2506
+ params.length
2507
+ } FOR UPDATE SKIP LOCKED) RETURNING "${primaryColumnName}"`;
2508
+
2509
+ const result: unknown = await repository.manager.query(sql, params);
2510
+
2511
+ return Array.isArray(result) && result.length > 0;
2512
+ }
2513
+
2330
2514
  /*
2331
2515
  * Atomically add to numeric columns and set literal columns for one row,
2332
2516
  * in a SINGLE auto-committed UPDATE, with no hooks and no `version` bump.
@@ -2347,6 +2531,11 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
2347
2531
  * caller) and every value is bound as a parameter, so this is not an
2348
2532
  * injection surface. Use ONLY for trusted, internal, side-effect-free
2349
2533
  * column writes.
2534
+ *
2535
+ * `set` values are clamped to their column widths for the same reason
2536
+ * updateColumnsByIdWithoutHooks clamps: this path also skips
2537
+ * checkMaxLengthOfFields, and an over-long string here would abort the
2538
+ * counter increments riding in the same statement.
2350
2539
  */
2351
2540
  @CaptureSpan()
2352
2541
  public async atomicAddToColumnsByIdWithoutHooks(input: {
@@ -2393,9 +2582,12 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
2393
2582
  setClauses.push(`${quoted} = COALESCE(${quoted}, 0) + $${params.length}`);
2394
2583
  }
2395
2584
 
2396
- for (const [propertyName, value] of Object.entries(
2397
- (input.set || {}) as ObjectLiteral,
2398
- )) {
2585
+ // Shallow copy never mutate the caller's object. See the clamp note above.
2586
+ const set: ObjectLiteral = this.truncateStringColumnsToMaxLength({
2587
+ ...((input.set || {}) as ObjectLiteral),
2588
+ });
2589
+
2590
+ for (const [propertyName, value] of Object.entries(set)) {
2399
2591
  if (typeof value === "function") {
2400
2592
  throw new BadDataException(
2401
2593
  `atomicAddToColumnsByIdWithoutHooks: SQL-expression values are not supported (column "${propertyName}"); pass a literal value.`,
@@ -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,58 +192,40 @@ 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
- const data: any = {
196
+ const liveness: any = {
225
197
  lastSeenAt: OneUptimeDate.getCurrentDate(),
226
198
  otelCollectorStatus: "connected",
227
199
  };
228
200
 
201
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
+ const metadata: any = {};
203
+
229
204
  if (extra?.osType) {
230
- data.osType = extra.osType;
205
+ metadata.osType = extra.osType;
231
206
  }
232
207
  if (extra?.osVersion) {
233
- data.osVersion = extra.osVersion;
208
+ metadata.osVersion = extra.osVersion;
234
209
  }
235
210
  if (extra?.agentVersion) {
236
- data.agentVersion = extra.agentVersion;
211
+ metadata.agentVersion = extra.agentVersion;
237
212
  }
238
213
 
239
214
  /*
240
- * Heartbeat write: a single-statement UPDATE with no hooks and no
241
- * `version` bump, avoiding the hot-row Postgres lock convoy that the
242
- * full updateOneById pipeline causes. See ServiceService.updateLastSeen.
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.
243
219
  */
244
- await this.updateColumnsByIdWithoutHooks({
220
+ await ResourceHeartbeat.write({
221
+ service: this,
245
222
  id: hostId,
246
- data: data,
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()}`,
247
229
  });
248
230
  }
249
231