@oneuptime/common 12.0.2 → 12.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/Models/DatabaseModels/CephCluster.ts +14 -0
  2. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  3. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  4. package/Models/DatabaseModels/DockerHost.ts +14 -0
  5. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  6. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  7. package/Models/DatabaseModels/Index.ts +2 -0
  8. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  9. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  10. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  11. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  12. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  13. package/Models/DatabaseModels/Project.ts +16 -2
  14. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  15. package/Models/DatabaseModels/Service.ts +30 -0
  16. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  17. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  18. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  19. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  20. package/Models/DatabaseModels/User.ts +30 -0
  21. package/Server/API/AIAgentDataAPI.ts +31 -0
  22. package/Server/API/BaseAPI.ts +13 -2
  23. package/Server/API/DashboardAPI.ts +458 -12
  24. package/Server/API/GitHubAPI.ts +119 -283
  25. package/Server/API/UserAPI.ts +263 -1
  26. package/Server/EnvironmentConfig.ts +38 -4
  27. package/Server/Infrastructure/GlobalCache.ts +152 -0
  28. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
  36. package/Server/Services/AlertStateTimelineService.ts +20 -0
  37. package/Server/Services/AnalyticsDatabaseService.ts +72 -1
  38. package/Server/Services/CephClusterService.ts +26 -86
  39. package/Server/Services/CloudResourceService.ts +23 -83
  40. package/Server/Services/CodeRepositoryService.ts +105 -2
  41. package/Server/Services/DatabaseService.ts +296 -2
  42. package/Server/Services/DockerHostService.ts +19 -79
  43. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  44. package/Server/Services/HostService.ts +46 -99
  45. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  46. package/Server/Services/Index.ts +2 -0
  47. package/Server/Services/IoTFleetService.ts +19 -79
  48. package/Server/Services/KubernetesClusterService.ts +17 -78
  49. package/Server/Services/LabelService.ts +57 -10
  50. package/Server/Services/MetricTypeService.ts +84 -0
  51. package/Server/Services/MonitorService.ts +89 -14
  52. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  53. package/Server/Services/MonitorTemplateService.ts +65 -0
  54. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  55. package/Server/Services/PodmanHostService.ts +19 -79
  56. package/Server/Services/ProxmoxClusterService.ts +23 -83
  57. package/Server/Services/RumApplicationService.ts +19 -80
  58. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  59. package/Server/Services/ServerlessFunctionService.ts +24 -85
  60. package/Server/Services/ServiceService.ts +299 -73
  61. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  62. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  63. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  64. package/Server/Services/TelemetryEntityService.ts +1 -0
  65. package/Server/Services/UserService.ts +184 -0
  66. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  67. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  68. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  69. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  72. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  73. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  74. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  75. package/Server/Utils/PasswordHash.ts +306 -0
  76. package/Server/Utils/SingleFlight.ts +83 -0
  77. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  78. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  79. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  80. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  81. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  82. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  83. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  84. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  85. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  86. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  87. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  88. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  89. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  90. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  91. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  92. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  93. package/Tests/Server/API/Helpers.ts +6 -1
  94. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  95. package/Tests/Server/Infrastructure/GlobalCache.test.ts +354 -0
  96. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  97. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  98. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  99. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  100. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +5 -0
  101. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  102. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  103. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +870 -0
  104. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  105. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  106. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  107. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  108. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  109. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  110. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  111. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  112. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  113. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  114. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  115. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  116. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  117. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  118. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  119. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  120. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  121. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  122. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  123. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  124. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  125. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  128. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  129. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  130. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  131. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  132. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  133. package/Tests/Server/Utils/PasswordHash.test.ts +555 -0
  134. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  135. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  136. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  137. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  138. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  139. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  140. package/Tests/Types/Decimal.test.ts +37 -0
  141. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  142. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  143. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  144. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  145. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  146. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  147. package/Tests/Types/Port.test.ts +38 -0
  148. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  149. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  150. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  151. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  152. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  153. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  154. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  155. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  156. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  157. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  158. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  159. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  160. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  161. package/Types/Database/ColumnLength.ts +9 -1
  162. package/Types/Database/DatabaseProperty.ts +26 -0
  163. package/Types/Database/NumericColumnValue.ts +128 -0
  164. package/Types/Database/TableColumn.ts +11 -0
  165. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  166. package/Types/Decimal.ts +16 -4
  167. package/Types/HashedString.ts +139 -4
  168. package/Types/Permission.ts +48 -0
  169. package/Types/Port.ts +16 -4
  170. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  171. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  172. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  173. package/UI/Components/Navbar/NavBar.tsx +12 -2
  174. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  175. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  176. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  177. package/Utils/Dashboard/Components/Index.ts +7 -0
  178. package/Utils/TeamMembersByProject.ts +237 -0
  179. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  180. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  182. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  184. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  186. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  188. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  190. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  192. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  193. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  194. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  195. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  196. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  197. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  198. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  199. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  200. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  202. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/Project.js +16 -2
  204. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  206. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  208. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  210. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  211. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  212. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  214. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  216. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/User.js +32 -0
  218. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +21 -2
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/BaseAPI.js +11 -1
  222. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +343 -10
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/GitHubAPI.js +94 -167
  226. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  227. package/build/dist/Server/API/UserAPI.js +187 -2
  228. package/build/dist/Server/API/UserAPI.js.map +1 -1
  229. package/build/dist/Server/EnvironmentConfig.js +34 -4
  230. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/GlobalCache.js +120 -0
  232. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  234. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  249. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  250. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +57 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/CephClusterService.js +26 -76
  254. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  255. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  256. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  257. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  258. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  259. package/build/dist/Server/Services/DatabaseService.js +246 -2
  260. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  261. package/build/dist/Server/Services/DockerHostService.js +19 -69
  262. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  263. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  264. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  265. package/build/dist/Server/Services/HostService.js +58 -102
  266. package/build/dist/Server/Services/HostService.js.map +1 -1
  267. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  268. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  269. package/build/dist/Server/Services/Index.js +2 -0
  270. package/build/dist/Server/Services/Index.js.map +1 -1
  271. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  272. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  273. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  274. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  275. package/build/dist/Server/Services/LabelService.js +42 -7
  276. package/build/dist/Server/Services/LabelService.js.map +1 -1
  277. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  278. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  279. package/build/dist/Server/Services/MonitorService.js +81 -9
  280. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  281. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  282. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  283. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  284. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  285. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  286. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  287. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  288. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  289. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  290. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  291. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  292. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  293. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  294. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  295. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  296. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  297. package/build/dist/Server/Services/ServiceService.js +236 -61
  298. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  299. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  300. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  301. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  302. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  303. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  305. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  306. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  307. package/build/dist/Server/Services/UserService.js +165 -0
  308. package/build/dist/Server/Services/UserService.js.map +1 -1
  309. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  310. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  311. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  312. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  313. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  314. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  315. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  316. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  317. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  318. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  320. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  322. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  326. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  327. package/build/dist/Server/Utils/PasswordHash.js +237 -0
  328. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  329. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  330. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  331. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  332. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  333. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  334. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  335. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  336. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  337. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  338. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  340. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  342. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  344. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  346. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  348. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  349. package/build/dist/Types/Database/ColumnLength.js +9 -1
  350. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  351. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  352. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  353. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  354. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  355. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  356. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  357. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  358. package/build/dist/Types/Decimal.js +14 -3
  359. package/build/dist/Types/Decimal.js.map +1 -1
  360. package/build/dist/Types/HashedString.js +95 -5
  361. package/build/dist/Types/HashedString.js.map +1 -1
  362. package/build/dist/Types/Permission.js +42 -0
  363. package/build/dist/Types/Permission.js.map +1 -1
  364. package/build/dist/Types/Port.js +15 -5
  365. package/build/dist/Types/Port.js.map +1 -1
  366. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  367. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  368. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  369. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  370. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  371. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  372. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  373. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  374. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  375. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  376. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  377. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  378. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  379. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  380. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  381. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  382. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  383. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  384. package/package.json +1 -1
@@ -0,0 +1,306 @@
1
+ import { EncryptionSecret } from "../EnvironmentConfig";
2
+ import BadDataException from "../../Types/Exception/BadDataException";
3
+ import HashedString from "../../Types/HashedString";
4
+ import crypto from "crypto";
5
+
6
+ /*
7
+ * Password hashing for user credentials.
8
+ *
9
+ * WHY THIS IS NOT A PLAIN HASH
10
+ *
11
+ * SHA-256 is designed to be fast, and that is exactly wrong for a password.
12
+ * A commodity GPU computes billions of SHA-256 digests per second, so a
13
+ * leaked User table is a dictionary attack that finishes over lunch — a
14
+ * per-user salt stops the attacker amortising that work across accounts, but
15
+ * it does nothing to slow down any single guess.
16
+ *
17
+ * scrypt is deliberately expensive in BOTH time and memory. The memory cost
18
+ * is the important half: it is what stops an attacker buying a thousandfold
19
+ * speedup with GPUs or custom silicon, because memory bandwidth is the one
20
+ * thing that hardware cannot cheaply parallelise.
21
+ *
22
+ * THE CONSTRUCTION
23
+ *
24
+ * input = HMAC-SHA256(key = EncryptionSecret, message = password)
25
+ * derived = scrypt(input, salt, 32 bytes, { N, r, p })
26
+ *
27
+ * The HMAC step carries the instance-wide EncryptionSecret as a "pepper".
28
+ * The pepper lives in configuration, not in the database, so a dump of
29
+ * Postgres alone is not enough to start guessing — the attacker also needs
30
+ * the deployment's secret. It also normalises the password to a fixed 32
31
+ * bytes before scrypt sees it, which sidesteps every question about very
32
+ * long or oddly-encoded inputs.
33
+ *
34
+ * THE STORED FORMAT
35
+ *
36
+ * scrypt$N=16384,r=8,p=1$<64 hex characters>
37
+ *
38
+ * The hash carries the algorithm and the cost parameters it was produced
39
+ * with. That is what makes raising the cost later a non-event: old hashes
40
+ * keep verifying against the parameters recorded in them, and each user's
41
+ * hash is re-derived at their next successful login. Nothing about a cost
42
+ * bump requires a migration or a forced password reset.
43
+ *
44
+ * The salt is NOT in this string — it lives in the row's own salt column
45
+ * (see TableColumn.hashSaltColumn), which is where the previous scheme put
46
+ * it and where the write path keeps it in step with the hash.
47
+ */
48
+
49
+ /*
50
+ * N is the CPU/memory cost. Memory used is 128 * N * r bytes, so the values
51
+ * below cost 16 MiB and roughly 50ms per hash on current server hardware.
52
+ *
53
+ * That is a deliberate middle: enough to make offline guessing expensive,
54
+ * cheap enough that a burst of logins does not exhaust the libuv thread pool
55
+ * or the box's memory. Raising N is a one-line change — bump it here, and
56
+ * every user is migrated to the new cost as they log in.
57
+ */
58
+ const CURRENT_PARAMS: ScryptParams = {
59
+ N: 16384,
60
+ r: 8,
61
+ p: 1,
62
+ };
63
+
64
+ /** Bytes of derived key. 32 bytes = the 64 hex characters stored. */
65
+ const KEY_LENGTH_IN_BYTES: number = 32;
66
+
67
+ const SCHEME_NAME: string = "scrypt";
68
+
69
+ /*
70
+ * Refuse absurd parameters read back out of the database. Stored values are
71
+ * ours, not an attacker's, but a corrupted row must fail the login rather
72
+ * than ask Node to allocate gigabytes.
73
+ */
74
+ const MAX_N: number = 1048576; // 2^20
75
+ const MAX_R: number = 32;
76
+ const MAX_P: number = 16;
77
+
78
+ export interface ScryptParams {
79
+ N: number;
80
+ r: number;
81
+ p: number;
82
+ }
83
+
84
+ interface ParsedPasswordHash {
85
+ params: ScryptParams;
86
+ derivedKey: string;
87
+ }
88
+
89
+ export default class PasswordHash {
90
+ /**
91
+ * A fresh, cryptographically random salt: 32 bytes as 64 hex characters.
92
+ *
93
+ * A salt is not a secret — its one job is to be UNIQUE per user, so that no
94
+ * precomputed table and no cracked password is ever worth anything against
95
+ * a second account.
96
+ */
97
+ public static generateSalt(): string {
98
+ return crypto.randomBytes(32).toString("hex");
99
+ }
100
+
101
+ /** The `algorithm$params$` prefix that hashes written today carry. */
102
+ public static getCurrentPrefix(): string {
103
+ return `${SCHEME_NAME}$${PasswordHash.encodeParams(CURRENT_PARAMS)}$`;
104
+ }
105
+
106
+ public static getCurrentParams(): ScryptParams {
107
+ return { ...CURRENT_PARAMS };
108
+ }
109
+
110
+ private static encodeParams(params: ScryptParams): string {
111
+ return `N=${params.N},r=${params.r},p=${params.p}`;
112
+ }
113
+
114
+ /**
115
+ * Pull the parameters and derived key back out of a stored value.
116
+ *
117
+ * Returns null for anything that is not one of our scrypt hashes — which
118
+ * includes every password written before this scheme existed. Callers use
119
+ * null to mean "fall back to the legacy comparison", so this must never
120
+ * throw on unrecognised input.
121
+ */
122
+ private static parse(storedValue: string): ParsedPasswordHash | null {
123
+ const parts: Array<string> = storedValue.split("$");
124
+
125
+ if (parts.length !== 3 || parts[0] !== SCHEME_NAME) {
126
+ return null;
127
+ }
128
+
129
+ const match: RegExpMatchArray | null = (parts[1] as string).match(
130
+ /^N=(\d+),r=(\d+),p=(\d+)$/,
131
+ );
132
+
133
+ if (!match) {
134
+ return null;
135
+ }
136
+
137
+ const params: ScryptParams = {
138
+ N: Number(match[1]),
139
+ r: Number(match[2]),
140
+ p: Number(match[3]),
141
+ };
142
+
143
+ const isSane: boolean =
144
+ params.N > 1 &&
145
+ params.N <= MAX_N &&
146
+ (params.N & (params.N - 1)) === 0 && // scrypt requires a power of two
147
+ params.r > 0 &&
148
+ params.r <= MAX_R &&
149
+ params.p > 0 &&
150
+ params.p <= MAX_P;
151
+
152
+ if (!isSane || !parts[2]) {
153
+ return null;
154
+ }
155
+
156
+ return { params: params, derivedKey: parts[2] };
157
+ }
158
+
159
+ /**
160
+ * The pepper step. Keyed with the instance's EncryptionSecret, so the
161
+ * database alone cannot be attacked offline.
162
+ */
163
+ private static applyPepper(plainValue: string): Buffer {
164
+ return crypto
165
+ .createHmac("sha256", EncryptionSecret.toString())
166
+ .update(plainValue, "utf8")
167
+ .digest();
168
+ }
169
+
170
+ private static derive(data: {
171
+ plainValue: string;
172
+ salt: string;
173
+ params: ScryptParams;
174
+ }): Promise<string> {
175
+ const { N, r, p } = data.params;
176
+
177
+ return new Promise<string>(
178
+ (resolve: (value: string) => void, reject: (error: Error) => void) => {
179
+ crypto.scrypt(
180
+ PasswordHash.applyPepper(data.plainValue),
181
+ data.salt,
182
+ KEY_LENGTH_IN_BYTES,
183
+ {
184
+ N: N,
185
+ r: r,
186
+ p: p,
187
+ /*
188
+ * Node rejects the call outright when 128 * N * r exceeds
189
+ * maxmem, and its default is a flat 32 MiB. Scale the ceiling
190
+ * with the parameters instead, so raising N does not silently
191
+ * become "Invalid scrypt params" at runtime.
192
+ */
193
+ maxmem: 128 * N * r * 2,
194
+ },
195
+ (error: Error | null, derivedKey: Buffer): void => {
196
+ if (error) {
197
+ return reject(error);
198
+ }
199
+
200
+ return resolve(derivedKey.toString("hex"));
201
+ },
202
+ );
203
+ },
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Hash a password for storage, at today's cost parameters.
209
+ *
210
+ * The returned string records the algorithm and the parameters used, so it
211
+ * stays verifiable after those parameters change.
212
+ */
213
+ public static async hash(data: {
214
+ plainValue: string;
215
+ salt: string;
216
+ }): Promise<string> {
217
+ if (!data.plainValue) {
218
+ return "";
219
+ }
220
+
221
+ if (!data.salt) {
222
+ throw new BadDataException("A salt is required to hash a password.");
223
+ }
224
+
225
+ const derivedKey: string = await PasswordHash.derive({
226
+ plainValue: data.plainValue,
227
+ salt: data.salt,
228
+ params: CURRENT_PARAMS,
229
+ });
230
+
231
+ return `${PasswordHash.getCurrentPrefix()}${derivedKey}`;
232
+ }
233
+
234
+ /**
235
+ * Check a password against a stored value, whichever scheme wrote it.
236
+ *
237
+ * Three schemes are in circulation and all three have to keep working, or
238
+ * the change that introduced the next one locks people out:
239
+ *
240
+ * scrypt$... this one. Verified at the parameters recorded in the hash.
241
+ * salted SHA `SHA256("v2:" + salt + ":" + secret + ":" + password)`.
242
+ * bare SHA `SHA256(secret + password)`, from before salts existed.
243
+ *
244
+ * The last two are told apart by whether the row has a salt, which is the
245
+ * same rule they were written under. Anything not carrying a recognised
246
+ * scheme prefix is one of them.
247
+ */
248
+ public static async verify(data: {
249
+ plainValue: string;
250
+ storedValue: string;
251
+ salt?: string | null | undefined;
252
+ }): Promise<boolean> {
253
+ if (!data.plainValue || !data.storedValue) {
254
+ return false;
255
+ }
256
+
257
+ const parsed: ParsedPasswordHash | null = PasswordHash.parse(
258
+ data.storedValue,
259
+ );
260
+
261
+ if (!parsed) {
262
+ return await HashedString.verifyValue({
263
+ plainValue: data.plainValue,
264
+ hashedValue: data.storedValue,
265
+ encryptionSecret: EncryptionSecret,
266
+ salt: data.salt || null,
267
+ });
268
+ }
269
+
270
+ /*
271
+ * A scrypt hash is meaningless without the salt it was derived with.
272
+ * Reaching here without one means the caller did not select the salt
273
+ * column, which would otherwise look exactly like a wrong password.
274
+ */
275
+ if (!data.salt) {
276
+ throw new BadDataException(
277
+ "Cannot verify a salted password hash without its salt. Select the salt column alongside the hash.",
278
+ );
279
+ }
280
+
281
+ const derivedKey: string = await PasswordHash.derive({
282
+ plainValue: data.plainValue,
283
+ salt: data.salt,
284
+ params: parsed.params,
285
+ });
286
+
287
+ return HashedString.isEqual(derivedKey, parsed.derivedKey);
288
+ }
289
+
290
+ /**
291
+ * Should this stored value be re-hashed the next time the password is
292
+ * known to be correct?
293
+ *
294
+ * True for every hash that is not exactly what `hash()` writes today: the
295
+ * two SHA-256 schemes that came before, and any scrypt hash carrying
296
+ * parameters that are no longer current. This is the whole upgrade
297
+ * mechanism — raising the cost above is enough to start migrating users.
298
+ */
299
+ public static needsUpgrade(storedValue: string): boolean {
300
+ if (!storedValue) {
301
+ return false;
302
+ }
303
+
304
+ return !storedValue.startsWith(PasswordHash.getCurrentPrefix());
305
+ }
306
+ }
@@ -0,0 +1,83 @@
1
+ /*
2
+ * In-process de-duplication of concurrent identical work.
3
+ *
4
+ * A Redis fence admits one writer per window across the FLEET, but every
5
+ * caller still pays a Redis round trip to discover it lost. That is the wrong
6
+ * unit of work when the duplicate callers are in the same process: an ingest
7
+ * pod running hundreds of concurrent jobs re-resolves the same handful of
8
+ * services on every batch, so hundreds of coroutines independently ask Redis
9
+ * the same question and then throw the answer away.
10
+ *
11
+ * SingleFlight collapses them first. The first caller for a key runs the work;
12
+ * everyone who arrives while it is still in flight awaits the SAME promise and
13
+ * gets the same result. Per pod, N concurrent duplicates cost one Redis round
14
+ * trip and at most one database write instead of N of each — which is what
15
+ * turns fleet-wide write amplification from (pods x jobs) into (pods).
16
+ *
17
+ * Deliberately unbounded in time but not in size: entries live only for the
18
+ * duration of the work itself and are removed in a `finally`, so the map holds
19
+ * exactly the set of operations currently running. There is no eviction policy
20
+ * to get wrong and nothing to leak if work hangs beyond a caller's patience —
21
+ * the entry is released when the underlying promise settles either way.
22
+ *
23
+ * This is a per-process optimisation and NOT a correctness mechanism: nothing
24
+ * here coordinates across pods. It composes with a distributed fence, it does
25
+ * not replace one.
26
+ */
27
+ export default class SingleFlight {
28
+ private static inflight: Map<string, Promise<unknown>> = new Map<
29
+ string,
30
+ Promise<unknown>
31
+ >();
32
+
33
+ /**
34
+ * Run `work` for `key`, or join the run already in flight for it.
35
+ *
36
+ * Followers observe whatever the leader observes, including a rejection.
37
+ * That is intentional: these callers asked for identical work, so they are
38
+ * entitled to identical outcomes, and a follower silently retrying a failure
39
+ * the leader just hit would re-create the stampede this exists to prevent.
40
+ */
41
+ public static async run<T>(key: string, work: () => Promise<T>): Promise<T> {
42
+ const existing: Promise<unknown> | undefined = this.inflight.get(key);
43
+
44
+ if (existing) {
45
+ return (await existing) as T;
46
+ }
47
+
48
+ /*
49
+ * Invoke through a wrapper so a `work` that throws SYNCHRONOUSLY still
50
+ * produces a promise to register and await. Calling it bare would throw
51
+ * past the bookkeeping below and leave the key registered forever.
52
+ */
53
+ const promise: Promise<T> = (async () => {
54
+ return await work();
55
+ })();
56
+
57
+ this.inflight.set(key, promise);
58
+
59
+ try {
60
+ return await promise;
61
+ } finally {
62
+ /*
63
+ * Only clear the entry if it is still ours. A follower cannot install a
64
+ * new one (it awaited instead), but a later leader can, and blindly
65
+ * deleting here would evict that live run and let the next caller start
66
+ * a duplicate of work already underway.
67
+ */
68
+ if (this.inflight.get(key) === promise) {
69
+ this.inflight.delete(key);
70
+ }
71
+ }
72
+ }
73
+
74
+ /** Number of operations currently in flight. For tests and diagnostics. */
75
+ public static inflightCount(): number {
76
+ return this.inflight.size;
77
+ }
78
+
79
+ /** Drop all bookkeeping. For tests only. */
80
+ public static clear(): void {
81
+ this.inflight.clear();
82
+ }
83
+ }
@@ -0,0 +1,46 @@
1
+ import BadDataException from "../../../Types/Exception/BadDataException";
2
+ import RulePatternMatchUtil from "../../../Utils/Rules/RulePatternMatchUtil";
3
+
4
+ /*
5
+ * Write-time validation for the monitor name / description patterns on status
6
+ * page monitor rules.
7
+ *
8
+ * The rule engine accepts a case-insensitive regex or a '*' wildcard glob, the
9
+ * same two syntaxes the network device rules take. Anything else - `api-(01`,
10
+ * `[unclosed` - compiles to nothing and matches nothing, and the engine can
11
+ * only log about it long after the user left the form. Reject it at the write
12
+ * instead, exactly as NetworkDeviceRulePatternValidator does
13
+ * (OneUptime/oneuptime#2940).
14
+ */
15
+ export default class StatusPageMonitorRulePatternValidator {
16
+ public static validate(data: {
17
+ namePattern?: string | null | undefined;
18
+ descriptionPattern?: string | null | undefined;
19
+ }): void {
20
+ StatusPageMonitorRulePatternValidator.validateOne(
21
+ "Monitor Name Pattern",
22
+ data.namePattern,
23
+ );
24
+ StatusPageMonitorRulePatternValidator.validateOne(
25
+ "Monitor Description Pattern",
26
+ data.descriptionPattern,
27
+ );
28
+ }
29
+
30
+ private static validateOne(
31
+ title: string,
32
+ pattern: string | null | undefined,
33
+ ): void {
34
+ if (!pattern) {
35
+ return;
36
+ }
37
+
38
+ if (RulePatternMatchUtil.isSupportedPattern(pattern)) {
39
+ return;
40
+ }
41
+
42
+ throw new BadDataException(
43
+ `${title} "${pattern}" is not a valid regular expression, and contains no '*' wildcard to fall back on, so it would never match a monitor. Use a regex such as api-.* or a wildcard such as *api*.`,
44
+ );
45
+ }
46
+ }
@@ -14,6 +14,7 @@ import {
14
14
  deriveRelationships,
15
15
  EntityRelationshipEdge,
16
16
  } from "../../../Utils/Telemetry/EntityRelationship";
17
+ import crypto from "crypto";
17
18
 
18
19
  /*
19
20
  * Shared entity-registry reconciliation machinery (phases 2 + 5 of the
@@ -87,28 +88,81 @@ export const REGISTRY_PROMOTED_TYPES: ReadonlySet<EntityType> =
87
88
  */
88
89
  const FENCE_NAMESPACE: string = "otel-maintenance-fence";
89
90
  const FENCE_SCOPE: string = "entity-reconcile";
91
+ const ROW_FENCE_SCOPE: string = "entity-reconcile-row";
90
92
  const FENCE_TTL_SECONDS: number = 5 * 60; // 5 minutes
91
93
 
94
+ /*
95
+ * Atomic claim. A read-then-write here admits every worker that reads the
96
+ * absent key in the same instant, which at ingest concurrency is all of them —
97
+ * the defect behind the row-lock convoy documented in
98
+ * DatabaseService.updateColumnsByIdIfUnlockedWithoutHooks. TTLs are jittered
99
+ * so a fleet-wide restart cannot re-synchronise the windows and rebuild the
100
+ * herd on a fixed period.
101
+ */
92
102
  async function shouldReconcile(fenceId: string): Promise<boolean> {
93
103
  try {
94
- const key: string = `${FENCE_SCOPE}:${fenceId}`;
95
- const seen: string | null = await GlobalCache.getString(
104
+ return await GlobalCache.setStringIfNotExists(
96
105
  FENCE_NAMESPACE,
97
- key,
106
+ `${FENCE_SCOPE}:${hashFenceId(fenceId)}`,
107
+ "1",
108
+ { expiresInSeconds: GlobalCache.withJitter(FENCE_TTL_SECONDS) },
98
109
  );
99
- if (seen) {
100
- return false;
101
- }
102
- await GlobalCache.setString(FENCE_NAMESPACE, key, "1", {
103
- expiresInSeconds: FENCE_TTL_SECONDS,
104
- });
110
+ } catch {
111
+ // If the cache is down, default to running the reconcile.
105
112
  return true;
113
+ }
114
+ }
115
+
116
+ /*
117
+ * The set-level fence id concatenates every promoted entity key, so it is
118
+ * unbounded in length — a pod with many entities would otherwise produce a
119
+ * multi-kilobyte Redis key. Hashing keeps it fixed-width without changing which
120
+ * sets collide.
121
+ */
122
+ function hashFenceId(fenceId: string): string {
123
+ return crypto.createHash("sha1").update(fenceId).digest("hex");
124
+ }
125
+
126
+ /*
127
+ * Per-ROW fence, sitting inside the set-level one.
128
+ *
129
+ * The set-level fence keys on (project + the whole promoted entity set), which
130
+ * in practice is unique per POD — a pod's own key is in the set. But the writes
131
+ * it gates are per ROW: the single TelemetryEntity row for a Kubernetes cluster
132
+ * takes one UPDATE per pod in that cluster per window, the namespace row one
133
+ * per pod in the namespace, and so on. Throttle granularity finer than write
134
+ * granularity means the throttle does not bound the writes at all, and the
135
+ * mismatch factor is the pod count — the same shape of defect as the Service
136
+ * heartbeat, one layer down.
137
+ *
138
+ * Claiming per row before the lookup also removes the redundant SELECT, not
139
+ * just the redundant UPDATE.
140
+ */
141
+ async function shouldReconcileRow(rowFenceId: string): Promise<boolean> {
142
+ try {
143
+ return await GlobalCache.setStringIfNotExists(
144
+ FENCE_NAMESPACE,
145
+ `${ROW_FENCE_SCOPE}:${hashFenceId(rowFenceId)}`,
146
+ "1",
147
+ { expiresInSeconds: GlobalCache.withJitter(FENCE_TTL_SECONDS) },
148
+ );
106
149
  } catch {
107
150
  // If the cache is down, default to running the reconcile.
108
151
  return true;
109
152
  }
110
153
  }
111
154
 
155
+ async function releaseRowFence(rowFenceId: string): Promise<void> {
156
+ try {
157
+ await GlobalCache.deleteKey(
158
+ FENCE_NAMESPACE,
159
+ `${ROW_FENCE_SCOPE}:${hashFenceId(rowFenceId)}`,
160
+ );
161
+ } catch {
162
+ // Best effort — losing the release costs one window of staleness.
163
+ }
164
+ }
165
+
112
166
  /*
113
167
  * Over-budget skips happen per row, but the warn log is fenced to once per
114
168
  * (project, entityType) per fence window — an over-budget project would
@@ -119,20 +173,12 @@ export async function shouldWarnEntityBudgetOnce(data: {
119
173
  entityType: EntityType;
120
174
  }): Promise<boolean> {
121
175
  try {
122
- const key: string = `entity-budget-warn:${data.projectId.toString()}:${
123
- data.entityType
124
- }`;
125
- const seen: string | null = await GlobalCache.getString(
176
+ return await GlobalCache.setStringIfNotExists(
126
177
  FENCE_NAMESPACE,
127
- key,
178
+ `entity-budget-warn:${data.projectId.toString()}:${data.entityType}`,
179
+ "1",
180
+ { expiresInSeconds: GlobalCache.withJitter(FENCE_TTL_SECONDS) },
128
181
  );
129
- if (seen) {
130
- return false;
131
- }
132
- await GlobalCache.setString(FENCE_NAMESPACE, key, "1", {
133
- expiresInSeconds: FENCE_TTL_SECONDS,
134
- });
135
- return true;
136
182
  } catch {
137
183
  // If the cache is down, default to warning (visibility over silence).
138
184
  return true;
@@ -230,6 +276,13 @@ export async function reconcileByNaturalKey<
230
276
  lastSeenAt: Date;
231
277
  /** Human-readable row identity for log lines, e.g. "entity host/abc123". */
232
278
  describe: string;
279
+ /**
280
+ * Stable identity of the ROW being reconciled, e.g.
281
+ * `${projectId}:${entityType}:${entityKey}`. Claimed atomically before the
282
+ * lookup so one writer per row per window does the work — see
283
+ * shouldReconcileRow for why the set-level fence above is not enough.
284
+ */
285
+ rowFenceId: string;
233
286
  /** Extra columns to select on the existing row, for `buildUpdate` diffing. */
234
287
  select?: Select<TBaseModel> | undefined;
235
288
  /**
@@ -264,6 +317,10 @@ export async function reconcileByNaturalKey<
264
317
  } as unknown as QueryDeepPartialEntity<TBaseModel>;
265
318
  };
266
319
 
320
+ if (!(await shouldReconcileRow(data.rowFenceId))) {
321
+ return;
322
+ }
323
+
267
324
  const existing: TBaseModel | null = await data.service.findOneBy({
268
325
  query: data.query,
269
326
  select,
@@ -278,15 +335,32 @@ export async function reconcileByNaturalKey<
278
335
  * audit). buildBump returns only plain values — lastSeenAt plus, at most,
279
336
  * the descriptiveAttributes / labels JSON columns — which the primitive
280
337
  * persists via the driver transformer path. See ServiceService.updateLastSeen.
338
+ *
339
+ * SKIP LOCKED: a contended bump yields rather than queueing behind the
340
+ * writer that already holds the row. Registry rows for shared entities (a
341
+ * cluster, a namespace) are written by every pod that reports them, so
342
+ * this is exactly the hot-row shape that convoyed on Service.
281
343
  */
282
- await data.service.updateColumnsByIdWithoutHooks({
283
- id: existing.id!,
284
- data: buildBump(existing),
285
- });
344
+ const wrote: boolean =
345
+ await data.service.updateColumnsByIdIfUnlockedWithoutHooks({
346
+ id: existing.id!,
347
+ data: buildBump(existing),
348
+ });
349
+
350
+ if (!wrote) {
351
+ // Skipped under contention — re-open the window so the next batch retries.
352
+ await releaseRowFence(data.rowFenceId);
353
+ }
286
354
  return;
287
355
  }
288
356
 
289
357
  if (data.beforeCreate && !(await data.beforeCreate())) {
358
+ /*
359
+ * Over budget: release the row fence so the gate is re-evaluated next
360
+ * batch rather than being suppressed for a full window by a decision that
361
+ * wrote nothing.
362
+ */
363
+ await releaseRowFence(data.rowFenceId);
290
364
  return;
291
365
  }
292
366
 
@@ -312,13 +386,20 @@ export async function reconcileByNaturalKey<
312
386
  logger.debug(
313
387
  `EntityRegistry: create raced for ${data.describe} (concurrent insert); bumping lastSeenAt on the winning row.`,
314
388
  );
315
- await data.service.updateColumnsByIdWithoutHooks({
389
+ await data.service.updateColumnsByIdIfUnlockedWithoutHooks({
316
390
  id: winner.id!,
317
391
  data: buildBump(winner),
318
392
  });
319
393
  return;
320
394
  }
321
395
 
396
+ /*
397
+ * The insert itself was invalid. Re-open the row window so the next batch
398
+ * retries rather than being silently suppressed for the TTL — a genuinely
399
+ * broken row should keep surfacing this warning, not go quiet.
400
+ */
401
+ await releaseRowFence(data.rowFenceId);
402
+
322
403
  logger.warn(`EntityRegistry: create failed for ${data.describe}:`);
323
404
  logger.warn(err as Error);
324
405
  }