@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
@@ -0,0 +1,658 @@
1
+ import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
2
+ import HostService from "../../../Server/Services/HostService";
3
+ import SingleFlight from "../../../Server/Utils/SingleFlight";
4
+ import Host from "../../../Models/DatabaseModels/Host";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
7
+
8
+ /*
9
+ * Regression suite for issue #3006.
10
+ *
11
+ * HostService.updateLastSeen writes liveness (lastSeenAt +
12
+ * otelCollectorStatus) in the SAME hook-free UPDATE as a dozen optional
13
+ * columns harvested from OpenTelemetry resource attributes. Because that
14
+ * raw path bypasses DatabaseService's length validation, an oversized
15
+ * attribute reached Postgres unchecked and failed the whole statement —
16
+ * so a host with telemetry still arriving stopped advancing lastSeenAt and
17
+ * MarkDisconnectedHosts flipped it to "disconnected" 15 minutes later.
18
+ *
19
+ * Two independent guards are pinned here:
20
+ * 1. collector-supplied strings are clamped to their column widths, so
21
+ * the statement does not fail in the first place, and
22
+ * 2. if the enriched write fails ANYWAY (for any reason at all), the
23
+ * liveness columns are retried on their own. Enrichment is what gets
24
+ * dropped, never the heartbeat.
25
+ *
26
+ * Guard 1 has since moved DOWN a layer: updateColumnsByIdWithoutHooks
27
+ * clamps for every caller, so no service can forget it (see
28
+ * HookFreeWriteLengthClamp.test.ts, which pins that for all eleven
29
+ * telemetry models). The clamp assertions below therefore run the real
30
+ * write path against a faked repository and inspect the values actually
31
+ * BOUND to the SQL — the same end-to-end claim, checked where it is now
32
+ * true rather than where it used to be.
33
+ *
34
+ * Everything external is mocked — no Postgres, no Redis.
35
+ */
36
+
37
+ const HOST_ID: ObjectID = ObjectID.generate();
38
+
39
+ /** Longer than every ShortText column (100) and the old LongText bound (500). */
40
+ const HUGE_IP_LIST: string = Array.from(
41
+ { length: 55 },
42
+ (_unused: unknown, i: number) => {
43
+ return `fe80::42:acff:fe11:${(0x1000 + i).toString(16)}`;
44
+ },
45
+ ).join(", ");
46
+
47
+ type WriteCall = { id: ObjectID; data: Record<string, unknown> };
48
+
49
+ /** The (inline) shape of updateLastSeen's optional metadata argument. */
50
+ type UpdateLastSeenExtra = Parameters<typeof HostService.updateLastSeen>[1];
51
+
52
+ /** The argument to the hook-free UPDATE the service ultimately issues. */
53
+ type ColumnWrite = Parameters<
54
+ typeof HostService.updateColumnsByIdIfUnlockedWithoutHooks
55
+ >[0];
56
+
57
+ let writes: Array<WriteCall> = [];
58
+ let cache: Map<string, string> = new Map<string, string>();
59
+ let deletedCacheKeys: Array<string> = [];
60
+
61
+ /**
62
+ * A faithful in-memory Redis for the two atomic gate primitives: SET NX
63
+ * succeeds only on an absent key, compare-and-claim only on a differing
64
+ * value. Stubbing either to a constant would let this suite agree with a
65
+ * throttle that does not actually throttle — which is the bug that took
66
+ * production down.
67
+ */
68
+ function mockCache(): void {
69
+ jest
70
+ .spyOn(GlobalCache, "setStringIfNotExists")
71
+ .mockImplementation(
72
+ async (namespace: string, key: string, value: string) => {
73
+ const full: string = `${namespace}:${key}`;
74
+ if (cache.has(full)) {
75
+ return false;
76
+ }
77
+ cache.set(full, value);
78
+ return true;
79
+ },
80
+ );
81
+ jest
82
+ .spyOn(GlobalCache, "setStringIfChanged")
83
+ .mockImplementation(
84
+ async (namespace: string, key: string, value: string) => {
85
+ const full: string = `${namespace}:${key}`;
86
+ if (cache.get(full) === value) {
87
+ return false;
88
+ }
89
+ cache.set(full, value);
90
+ return true;
91
+ },
92
+ );
93
+ jest
94
+ .spyOn(GlobalCache, "deleteKey")
95
+ .mockImplementation(async (namespace: string, key: string) => {
96
+ deletedCacheKeys.push(`${namespace}:${key}`);
97
+ cache.delete(`${namespace}:${key}`);
98
+ });
99
+ }
100
+
101
+ /** Re-opens the liveness window, as its TTL would. */
102
+ function expireLivenessWindow(): void {
103
+ cache.delete(`host-last-seen:${HOST_ID.toString()}`);
104
+ SingleFlight.clear();
105
+ }
106
+
107
+ /** Re-opens the enrichment rate-limit window, as its TTL would. */
108
+ function expireEnrichmentWindow(): void {
109
+ cache.delete(`host-last-seen-write-window:${HOST_ID.toString()}`);
110
+ SingleFlight.clear();
111
+ }
112
+
113
+ function recordWrite(input: {
114
+ id: ObjectID;
115
+ data: unknown;
116
+ }): Record<string, unknown> {
117
+ const data: Record<string, unknown> = {
118
+ ...(input.data as Record<string, unknown>),
119
+ };
120
+ writes.push({ id: input.id, data: data });
121
+ return data;
122
+ }
123
+
124
+ /** Records every hook-free UPDATE instead of issuing it. */
125
+ function mockWritesSucceeding(): void {
126
+ jest
127
+ .spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
128
+ .mockImplementation(async (input: ColumnWrite) => {
129
+ recordWrite(input);
130
+ // The SKIP LOCKED primitive resolves true when the row was updated.
131
+ return true;
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Fails any write that carries more than the two liveness columns — the
137
+ * shape of a Postgres rejection caused by one bad optional column.
138
+ */
139
+ function mockEnrichedWriteFailing(
140
+ message: string = `value too long for type character varying(100)`,
141
+ ): void {
142
+ jest
143
+ .spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
144
+ .mockImplementation(async (input: ColumnWrite) => {
145
+ const data: Record<string, unknown> = recordWrite(input);
146
+ if (Object.keys(data).length > 2) {
147
+ throw new Error(message);
148
+ }
149
+ return true;
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Runs the REAL updateColumnsByIdWithoutHooks against a faked repository,
155
+ * recording the values actually BOUND to the generated SQL. The length
156
+ * clamp lives inside that method now, so this is the only way to observe
157
+ * it end-to-end from the service without a database — mocking the method
158
+ * out (as the other blocks do) would mock the clamp out with it.
159
+ */
160
+ function mockRealWritePath(): void {
161
+ const host: Host = new Host();
162
+ let bound: Record<string, unknown> = {};
163
+
164
+ jest.spyOn(HostService, "getRepository").mockReturnValue({
165
+ metadata: {
166
+ tableName: "Host",
167
+ findColumnWithPropertyName: (
168
+ propertyName: string,
169
+ ): { databaseName: string } | undefined => {
170
+ return host.hasColumn(propertyName)
171
+ ? { databaseName: propertyName }
172
+ : undefined;
173
+ },
174
+ updateDateColumn: { databaseName: "updatedAt" },
175
+ primaryColumns: [{ databaseName: "_id" }],
176
+ },
177
+ manager: {
178
+ connection: {
179
+ driver: {
180
+ preparePersistentValue: (
181
+ value: unknown,
182
+ column: { databaseName: string },
183
+ ): unknown => {
184
+ bound[column.databaseName] = value;
185
+ return value;
186
+ },
187
+ },
188
+ },
189
+ query: async (
190
+ _sql: string,
191
+ params: Array<unknown>,
192
+ ): Promise<Array<unknown>> => {
193
+ writes.push({
194
+ id: new ObjectID(String(params[params.length - 1])),
195
+ data: bound,
196
+ });
197
+ bound = {};
198
+ // One affected row: the SKIP LOCKED write reports success via RETURNING.
199
+ return [{ _id: String(params[params.length - 1]) }];
200
+ },
201
+ },
202
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
+ } as any);
204
+ }
205
+
206
+ function lastWrite(): Record<string, unknown> {
207
+ return writes[writes.length - 1]?.data as Record<string, unknown>;
208
+ }
209
+
210
+ describe("HostService.updateLastSeen", () => {
211
+ beforeEach(() => {
212
+ writes = [];
213
+ cache = new Map<string, string>();
214
+ deletedCacheKeys = [];
215
+ SingleFlight.clear();
216
+ mockCache();
217
+ });
218
+
219
+ afterEach(() => {
220
+ jest.restoreAllMocks();
221
+ SingleFlight.clear();
222
+ });
223
+
224
+ describe("liveness columns", () => {
225
+ beforeEach(() => {
226
+ mockWritesSucceeding();
227
+ });
228
+
229
+ test("always writes lastSeenAt and marks the collector connected", async () => {
230
+ await HostService.updateLastSeen(HOST_ID);
231
+
232
+ expect(writes).toHaveLength(1);
233
+ expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
234
+ expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
235
+ });
236
+
237
+ test("writes to the host it was given", async () => {
238
+ await HostService.updateLastSeen(HOST_ID);
239
+ expect(writes[0]?.id.toString()).toBe(HOST_ID.toString());
240
+ });
241
+
242
+ test("skips the write when the same metadata was written recently", async () => {
243
+ await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
244
+ SingleFlight.clear();
245
+ await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
246
+
247
+ expect(writes).toHaveLength(1);
248
+ });
249
+
250
+ /*
251
+ * A changed attribute lands in the NEXT window, not on the very next
252
+ * batch. That narrows the old "writes again immediately" contract on
253
+ * purpose: change detection cannot tell a real update apart from a gauge
254
+ * that moves every scrape — both present as "it changed" — and only one
255
+ * of the two is bounded. Bounding both is what removes the convoy, and
256
+ * the cost is at most one window of staleness on a descriptive column.
257
+ * `lastSeenAt` is unaffected; it rides the separate liveness gate.
258
+ */
259
+ test("a metadata change lands in the next window", async () => {
260
+ await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
261
+ expireLivenessWindow();
262
+ expireEnrichmentWindow();
263
+
264
+ await HostService.updateLastSeen(HOST_ID, { osType: "windows" });
265
+
266
+ expect(writes).toHaveLength(2);
267
+ expect(lastWrite()["osType"]).toBe("windows");
268
+ });
269
+
270
+ test("a metadata change cannot write twice inside one window", async () => {
271
+ await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
272
+ SingleFlight.clear();
273
+ await HostService.updateLastSeen(HOST_ID, { osType: "windows" });
274
+
275
+ expect(writes).toHaveLength(1);
276
+ });
277
+
278
+ /*
279
+ * Per-scrape gauges must NOT participate in the throttle fingerprint.
280
+ *
281
+ * cpuCores, totalMemoryBytes and processCount used to be hashed into it,
282
+ * and `system.processes.count` is partitioned by process.status and
283
+ * summed — so it changes on essentially every scrape. The fingerprint
284
+ * differed every time and the 60s window never engaged, for exactly the
285
+ * hosts producing the most batches. OneUptime's own install guide enables
286
+ * the processes scraper at a 30s interval, so every host following the
287
+ * documentation hit this.
288
+ *
289
+ * They are still WRITTEN (see below) — they just ride along on whatever
290
+ * write the throttle admits instead of forcing one.
291
+ */
292
+ test("a gauge-only change does not defeat the throttle", async () => {
293
+ await HostService.updateLastSeen(HOST_ID, {
294
+ osType: "linux",
295
+ cpuCores: 8,
296
+ totalMemoryBytes: 17179869184,
297
+ processCount: 412,
298
+ });
299
+ expireEnrichmentWindow();
300
+
301
+ await HostService.updateLastSeen(HOST_ID, {
302
+ osType: "linux",
303
+ cpuCores: 8,
304
+ totalMemoryBytes: 17179869184,
305
+ processCount: 413,
306
+ });
307
+
308
+ /*
309
+ * Both gates were re-opened, so the only thing that can suppress the
310
+ * second write is the fingerprint agreeing — which is the point: the
311
+ * gauges must not be part of it.
312
+ */
313
+ expect(writes).toHaveLength(1);
314
+ });
315
+
316
+ test("a churning process count over many scrapes still writes once", async () => {
317
+ for (let scrape: number = 0; scrape < 20; scrape++) {
318
+ expireEnrichmentWindow();
319
+ await HostService.updateLastSeen(HOST_ID, {
320
+ osType: "linux",
321
+ processCount: 400 + scrape,
322
+ });
323
+ }
324
+
325
+ expect(writes).toHaveLength(1);
326
+ });
327
+
328
+ test("a real metadata change alongside a churning gauge still writes", async () => {
329
+ await HostService.updateLastSeen(HOST_ID, {
330
+ osType: "linux",
331
+ processCount: 412,
332
+ });
333
+ expireLivenessWindow();
334
+ expireEnrichmentWindow();
335
+
336
+ await HostService.updateLastSeen(HOST_ID, {
337
+ osType: "windows",
338
+ processCount: 413,
339
+ });
340
+
341
+ expect(writes).toHaveLength(2);
342
+ });
343
+
344
+ test("the gauges are still written when a write happens", async () => {
345
+ await HostService.updateLastSeen(HOST_ID, {
346
+ osType: "linux",
347
+ cpuCores: 8,
348
+ totalMemoryBytes: 17179869184,
349
+ processCount: 412,
350
+ });
351
+
352
+ expect(lastWrite()["cpuCores"]).toBe(8);
353
+ expect(lastWrite()["totalMemoryBytes"]).toBe(17179869184);
354
+ expect(lastWrite()["processCount"]).toBe(412);
355
+ });
356
+ });
357
+
358
+ describe("oversized collector metadata", () => {
359
+ beforeEach(() => {
360
+ // The clamp lives in the write path, so run the real one.
361
+ mockRealWritePath();
362
+ });
363
+
364
+ test("stores a 55-address host.ip list untouched — the column is text now", async () => {
365
+ await HostService.updateLastSeen(HOST_ID, {
366
+ hostIpAddresses: HUGE_IP_LIST,
367
+ });
368
+
369
+ expect(HUGE_IP_LIST.length).toBeGreaterThan(500);
370
+ expect(lastWrite()["hostIpAddresses"]).toBe(HUGE_IP_LIST);
371
+ });
372
+
373
+ test("clamps an oversized ShortText attribute instead of failing the write", async () => {
374
+ const longHostId: string = "i".repeat(400);
375
+
376
+ await HostService.updateLastSeen(HOST_ID, { hostId: longHostId });
377
+
378
+ expect(lastWrite()["hostId"]).toHaveLength(100);
379
+ expect(lastWrite()["hostId"]).toBe("i".repeat(100));
380
+ });
381
+
382
+ test("clamps an oversized LongText attribute to 500", async () => {
383
+ const longOsVersion: string = "v".repeat(900);
384
+
385
+ await HostService.updateLastSeen(HOST_ID, { osVersion: longOsVersion });
386
+
387
+ expect(lastWrite()["osVersion"]).toHaveLength(500);
388
+ });
389
+
390
+ test("clamping never costs the liveness columns", async () => {
391
+ await HostService.updateLastSeen(HOST_ID, {
392
+ hostId: "i".repeat(400),
393
+ hostArch: "a".repeat(400),
394
+ });
395
+
396
+ expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
397
+ expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
398
+ });
399
+
400
+ const CLAMPED_COLUMNS: Array<[string, number]> = [
401
+ ["osType", 100],
402
+ ["hostId", 100],
403
+ ["hostArch", 100],
404
+ ["hostType", 100],
405
+ ["containerRuntime", 100],
406
+ ["agentVersion", 100],
407
+ ["deploymentEnvironment", 100],
408
+ ["runtimeName", 100],
409
+ ["runtimeVersion", 100],
410
+ ["cloudProvider", 100],
411
+ ["cloudPlatform", 100],
412
+ ["cloudRegion", 100],
413
+ ["cloudAccountId", 100],
414
+ ["osVersion", 500],
415
+ ];
416
+
417
+ test.each(CLAMPED_COLUMNS)(
418
+ "%s is clamped to %i characters",
419
+ async (column: string, maxLength: number) => {
420
+ await HostService.updateLastSeen(HOST_ID, {
421
+ [column]: "x".repeat(maxLength + 250),
422
+ } as UpdateLastSeenExtra);
423
+
424
+ expect(lastWrite()[column]).toHaveLength(maxLength);
425
+ },
426
+ );
427
+
428
+ test("values that already fit are passed through byte-for-byte", async () => {
429
+ await HostService.updateLastSeen(HOST_ID, {
430
+ osType: "linux",
431
+ osVersion: "Debian GNU/Linux 12 (bookworm)",
432
+ hostArch: "amd64",
433
+ });
434
+
435
+ expect(lastWrite()["osType"]).toBe("linux");
436
+ expect(lastWrite()["osVersion"]).toBe("Debian GNU/Linux 12 (bookworm)");
437
+ expect(lastWrite()["hostArch"]).toBe("amd64");
438
+ });
439
+
440
+ test("numeric and relation columns are left alone by the clamp", async () => {
441
+ const dockerHostId: ObjectID = ObjectID.generate();
442
+
443
+ await HostService.updateLastSeen(HOST_ID, {
444
+ cpuCores: 64,
445
+ totalMemoryBytes: 274877906944,
446
+ processCount: 1337,
447
+ dockerHostId: dockerHostId,
448
+ });
449
+
450
+ expect(lastWrite()["cpuCores"]).toBe(64);
451
+ expect(lastWrite()["totalMemoryBytes"]).toBe(274877906944);
452
+ expect(lastWrite()["processCount"]).toBe(1337);
453
+ expect(lastWrite()["dockerHostId"]).toBe(dockerHostId);
454
+ });
455
+ });
456
+
457
+ describe("fallback when the enriched write fails anyway", () => {
458
+ beforeEach(() => {
459
+ mockEnrichedWriteFailing();
460
+ });
461
+
462
+ test("does not throw — ingest keeps going", async () => {
463
+ await expect(
464
+ HostService.updateLastSeen(HOST_ID, {
465
+ hostIpAddresses: HUGE_IP_LIST,
466
+ }),
467
+ ).resolves.toBeUndefined();
468
+ });
469
+
470
+ test("retries with liveness only, so lastSeenAt still advances", async () => {
471
+ await HostService.updateLastSeen(HOST_ID, {
472
+ hostIpAddresses: HUGE_IP_LIST,
473
+ });
474
+
475
+ expect(writes).toHaveLength(2);
476
+ expect(Object.keys(lastWrite()).sort()).toEqual([
477
+ "lastSeenAt",
478
+ "otelCollectorStatus",
479
+ ]);
480
+ expect(lastWrite()["otelCollectorStatus"]).toBe("connected");
481
+ expect(lastWrite()["lastSeenAt"]).toBeInstanceOf(Date);
482
+ });
483
+
484
+ /*
485
+ * The enrichment gates were claimed before the write that failed, so
486
+ * without releasing them the next batch would short-circuit and the retry
487
+ * would never happen. The liveness gate is deliberately NOT released: the
488
+ * fallback write below already refreshed lastSeenAt, so re-opening it
489
+ * would buy a second heartbeat inside one window for nothing.
490
+ */
491
+ test("busts the enrichment gates so the next batch is not skipped", async () => {
492
+ await HostService.updateLastSeen(HOST_ID, {
493
+ hostIpAddresses: HUGE_IP_LIST,
494
+ });
495
+
496
+ expect(deletedCacheKeys).toEqual([
497
+ `host-last-seen-fingerprint:${HOST_ID.toString()}`,
498
+ `host-last-seen-write-window:${HOST_ID.toString()}`,
499
+ ]);
500
+ });
501
+
502
+ test("the next batch retries rather than short-circuiting on a cache hit", async () => {
503
+ await HostService.updateLastSeen(HOST_ID, {
504
+ hostIpAddresses: HUGE_IP_LIST,
505
+ });
506
+ writes = [];
507
+
508
+ await HostService.updateLastSeen(HOST_ID, {
509
+ hostIpAddresses: HUGE_IP_LIST,
510
+ });
511
+
512
+ // Enriched attempt + liveness fallback, not a silent no-op.
513
+ expect(writes).toHaveLength(2);
514
+ });
515
+
516
+ test("a failing cache delete does not stop the liveness retry", async () => {
517
+ jest
518
+ .spyOn(GlobalCache, "deleteKey")
519
+ .mockRejectedValue(new Error("redis down"));
520
+
521
+ await HostService.updateLastSeen(HOST_ID, {
522
+ hostIpAddresses: HUGE_IP_LIST,
523
+ });
524
+
525
+ expect(Object.keys(lastWrite()).sort()).toEqual([
526
+ "lastSeenAt",
527
+ "otelCollectorStatus",
528
+ ]);
529
+ });
530
+
531
+ test("surfaces a genuinely broken database instead of hiding it", async () => {
532
+ // Both attempts fail — the caller must find out.
533
+ jest
534
+ .spyOn(HostService, "updateColumnsByIdIfUnlockedWithoutHooks")
535
+ .mockRejectedValue(new Error("connection terminated"));
536
+
537
+ await expect(
538
+ HostService.updateLastSeen(HOST_ID, { osType: "linux" }),
539
+ ).rejects.toThrow("connection terminated");
540
+ });
541
+ });
542
+
543
+ describe("cache outages", () => {
544
+ beforeEach(() => {
545
+ mockWritesSucceeding();
546
+ });
547
+
548
+ test("a read failure still writes — never mark a live host disconnected", async () => {
549
+ jest
550
+ .spyOn(GlobalCache, "getString")
551
+ .mockRejectedValue(new Error("redis down"));
552
+
553
+ await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
554
+
555
+ expect(writes).toHaveLength(1);
556
+ });
557
+
558
+ test("a write failure still writes", async () => {
559
+ jest
560
+ .spyOn(GlobalCache, "setString")
561
+ .mockRejectedValue(new Error("redis down"));
562
+
563
+ await HostService.updateLastSeen(HOST_ID, { osType: "linux" });
564
+
565
+ expect(writes).toHaveLength(1);
566
+ });
567
+ });
568
+ });
569
+
570
+ describe("DatabaseService.truncateStringColumnsToMaxLength", () => {
571
+ test("clamps only what exceeds its column width", () => {
572
+ const data: Record<string, unknown> = {
573
+ osType: "l".repeat(101),
574
+ hostArch: "amd64",
575
+ };
576
+
577
+ HostService.truncateStringColumnsToMaxLength(data);
578
+
579
+ expect(data["osType"]).toHaveLength(100);
580
+ expect(data["hostArch"]).toBe("amd64");
581
+ });
582
+
583
+ test("leaves text columns untouched at any length", () => {
584
+ const huge: string = "1.2.3.4, ".repeat(5000);
585
+ const data: Record<string, unknown> = { hostIpAddresses: huge };
586
+
587
+ HostService.truncateStringColumnsToMaxLength(data);
588
+
589
+ expect(data["hostIpAddresses"]).toBe(huge);
590
+ });
591
+
592
+ test("leaves non-string values untouched", () => {
593
+ const date: Date = new Date();
594
+ const id: ObjectID = ObjectID.generate();
595
+ const data: Record<string, unknown> = {
596
+ lastSeenAt: date,
597
+ cpuCores: 8,
598
+ isArchived: false,
599
+ dockerHostId: id,
600
+ };
601
+
602
+ HostService.truncateStringColumnsToMaxLength(data);
603
+
604
+ expect(data).toEqual({
605
+ lastSeenAt: date,
606
+ cpuCores: 8,
607
+ isArchived: false,
608
+ dockerHostId: id,
609
+ });
610
+ });
611
+
612
+ test("ignores keys that are not columns on the model", () => {
613
+ const data: Record<string, unknown> = { notAColumn: "z".repeat(5000) };
614
+
615
+ HostService.truncateStringColumnsToMaxLength(data);
616
+
617
+ expect(data["notAColumn"]).toHaveLength(5000);
618
+ });
619
+
620
+ test("ignores empty strings", () => {
621
+ const data: Record<string, unknown> = { osType: "" };
622
+
623
+ HostService.truncateStringColumnsToMaxLength(data);
624
+
625
+ expect(data["osType"]).toBe("");
626
+ });
627
+
628
+ test("mutates in place and returns the same object", () => {
629
+ const data: Record<string, unknown> = { osType: "l".repeat(200) };
630
+
631
+ expect(HostService.truncateStringColumnsToMaxLength(data)).toBe(data);
632
+ });
633
+
634
+ test("a value exactly at the limit is not clipped", () => {
635
+ const exact: string = "l".repeat(100);
636
+ const data: Record<string, unknown> = { osType: exact };
637
+
638
+ HostService.truncateStringColumnsToMaxLength(data);
639
+
640
+ expect(data["osType"]).toBe(exact);
641
+ });
642
+
643
+ test("every clamped value stays a prefix of the original", () => {
644
+ const original: string = "abcdefghij".repeat(40);
645
+ const data: Record<string, unknown> = { osType: original };
646
+
647
+ HostService.truncateStringColumnsToMaxLength(data);
648
+
649
+ expect(original.startsWith(data["osType"] as string)).toBe(true);
650
+ });
651
+
652
+ test("Host really does have the columns this suite assumes", () => {
653
+ const host: Host = new Host();
654
+ for (const column of ["osType", "osVersion", "hostIpAddresses"]) {
655
+ expect(host.hasColumn(column)).toBe(true);
656
+ }
657
+ });
658
+ });