@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,128 @@
1
+ import { JSONObject, JSONValue } from "../JSON";
2
+ import { TableColumnMetadata } from "./TableColumn";
3
+ import TableColumnType from "./TableColumnType";
4
+
5
+ /*
6
+ * Column types whose in-memory representation is a plain JavaScript number.
7
+ *
8
+ * Deliberately excludes the types that merely *look* numeric — `Port`,
9
+ * `Version`, `Date` — because coercing those would destroy them.
10
+ *
11
+ * The declared type is not a perfect proxy for the in-memory one: an SMTP
12
+ * port is declared `Number` but typed `Port`, and a usage figure is declared
13
+ * `Number` but typed `Decimal`. Coercing a string to a number is safe for
14
+ * those only because each of their transformers accepts a raw number —
15
+ * which is a property their tests pin, not an accident.
16
+ */
17
+ const NUMERIC_TABLE_COLUMN_TYPES: Set<TableColumnType> =
18
+ new Set<TableColumnType>([
19
+ TableColumnType.Number,
20
+ TableColumnType.SmallNumber,
21
+ TableColumnType.BigNumber,
22
+ TableColumnType.PositiveNumber,
23
+ TableColumnType.SmallPositiveNumber,
24
+ TableColumnType.BigPositiveNumber,
25
+ ]);
26
+
27
+ export function isNumericTableColumnType(
28
+ type: TableColumnType | undefined | null,
29
+ ): boolean {
30
+ if (!type) {
31
+ return false;
32
+ }
33
+
34
+ return NUMERIC_TABLE_COLUMN_TYPES.has(type);
35
+ }
36
+
37
+ /*
38
+ * Normalize a JSON value destined for a number column.
39
+ *
40
+ * The dashboard's number form field is an `<input type="number">`, and a DOM
41
+ * input's value is a *string* — there is no numeric input event in HTML. So
42
+ * "10" typed into "Sample Percentage" reached the API as the JSON string
43
+ * "10", and any server-side check spelled `typeof value === "number"`
44
+ * rejected a perfectly valid form submission:
45
+ *
46
+ * BadDataException: Sample percentage is required when the action is
47
+ * "Sample" ... between 1 and 99.
48
+ *
49
+ * Postgres silently coerces '10' to 10 on the way in, which is why this had
50
+ * gone unnoticed on every other number field in the product: the string only
51
+ * becomes visible when something reads the value before it is stored.
52
+ *
53
+ * Normalizing here — where the column's declared type is known, on both the
54
+ * browser side (ModelForm -> BaseModel.fromJSON) and the server side
55
+ * (BaseAPI -> BaseModel.fromJSON) — means a number column holds a number by
56
+ * the time any hook, validator or query sees it.
57
+ *
58
+ * Only strings that parse cleanly to a finite number are converted. A blank
59
+ * string is left alone rather than turned into `0` (`Number("")` is 0, which
60
+ * would silently write a real value for a field the user cleared) or into
61
+ * `null` (which would defeat a column DEFAULT the same way the bigint
62
+ * transformer used to). Garbage like "abc" is left alone too, so validation
63
+ * rejects it with a message about the field instead of this function
64
+ * inventing a value.
65
+ */
66
+ export function coerceNumericColumnValue(value: unknown): unknown {
67
+ if (typeof value !== "string") {
68
+ return value;
69
+ }
70
+
71
+ const trimmed: string = value.trim();
72
+
73
+ if (trimmed.length === 0) {
74
+ return value;
75
+ }
76
+
77
+ const parsed: number = Number(trimmed);
78
+
79
+ if (!Number.isFinite(parsed)) {
80
+ return value;
81
+ }
82
+
83
+ return parsed;
84
+ }
85
+
86
+ /*
87
+ * The minimum a model has to offer for its number columns to be found.
88
+ * Every DatabaseBaseModel satisfies it; asking for only this keeps the
89
+ * coercion usable from the API layer without dragging the model class in.
90
+ */
91
+ export interface ColumnMetadataSource {
92
+ getTableColumnMetadata: (columnName: string) => TableColumnMetadata;
93
+ }
94
+
95
+ /*
96
+ * Coerce the number columns of a loose JSON patch, in place.
97
+ *
98
+ * `BaseModel.fromJSON` does this on its way to building a model, but an
99
+ * update never builds one — `BaseAPI.updateItem` goes from the request body
100
+ * straight to a partial entity — so without this a PATCH and a POST
101
+ * disagree about whether "10" is a number. That matters because the
102
+ * save-time hooks read these values before the database gets a chance to
103
+ * coerce them.
104
+ *
105
+ * Values that are not strings are left exactly as they are, including the
106
+ * `() => string` raw SQL expressions a PartialEntity may carry.
107
+ */
108
+ export function coerceNumericColumnsInJSON(
109
+ json: JSONObject,
110
+ model: ColumnMetadataSource,
111
+ ): JSONObject {
112
+ for (const key of Object.keys(json)) {
113
+ const tableColumnMetadata: TableColumnMetadata =
114
+ model.getTableColumnMetadata(key);
115
+
116
+ if (!tableColumnMetadata) {
117
+ continue;
118
+ }
119
+
120
+ if (!isNumericTableColumnType(tableColumnMetadata.type)) {
121
+ continue;
122
+ }
123
+
124
+ json[key] = coerceNumericColumnValue(json[key]) as JSONValue;
125
+ }
126
+
127
+ return json;
128
+ }
@@ -16,6 +16,17 @@ export interface TableColumnMetadata {
16
16
  unique?: boolean;
17
17
  computed?: boolean;
18
18
  hashed?: boolean;
19
+ /*
20
+ * Name of the sibling column that holds this column's per-record salt.
21
+ *
22
+ * When set, the write path generates a fresh random salt on every write of
23
+ * this column, stores it in the named column, and mixes it into the hash.
24
+ * Two records with the same plaintext then hash to different values, so one
25
+ * precomputed table (or one cracked value) buys an attacker nothing
26
+ * anywhere else. The salt column must be nullable — rows written before the
27
+ * salt existed have none and verify against the legacy unsalted scheme.
28
+ */
29
+ hashSaltColumn?: string;
19
30
  encrypted?: boolean;
20
31
  manyToOneRelationColumn?: string;
21
32
  type: TableColumnType;
@@ -0,0 +1,48 @@
1
+ import { IndexOptions } from "typeorm";
2
+
3
+ /*
4
+ * Options for an index whose definition TypeORM cannot express, and which must
5
+ * therefore be owned by a migration rather than by the schema builder.
6
+ *
7
+ * WHY THIS EXISTS
8
+ *
9
+ * TypeORM's schema builder matches database indexes to entity metadata BY NAME
10
+ * (RdbmsSchemaBuilder.dropOldIndices: `metadata.indices.find((i) => i.name ===
11
+ * tableIndex.name)`) and DROPS every index it cannot find a match for. An index
12
+ * created only in a migration is invisible to entity metadata, so the next
13
+ * `npm run generate-postgres-migration` emits a DROP for it — silently, mixed
14
+ * in with whatever change was actually being made.
15
+ *
16
+ * That is not hypothetical. An audit of every hand-named index ever created by
17
+ * a migration against a fully-migrated database found ten that had been deleted
18
+ * this way and never restored, including the unique guards on six `slug`
19
+ * columns and the LOWER(name) index serving the telemetry ingest hot path.
20
+ * Migration 1775735059360 is a representative example: two unrelated column
21
+ * default changes, plus a DROP of an expression index, plus a down() that
22
+ * "restores" it without the expression.
23
+ *
24
+ * HOW THIS FIXES IT
25
+ *
26
+ * Declaring the index on the entity BY NAME with these options makes the schema
27
+ * builder skip it in both directions: `dropOldIndices` returns early on
28
+ * `synchronize === false`, and `createNewIndices` only considers indexes with
29
+ * `synchronize === true`. The migration stays the single source of truth for
30
+ * the real definition, and the declaration exists purely so TypeORM leaves it
31
+ * alone.
32
+ *
33
+ * The columns passed alongside these options are documentation — the schema
34
+ * builder never reads them for an unsynchronized index. Where the real
35
+ * definition is partial or an expression, say so in a comment at the call site.
36
+ *
37
+ * THE CAST
38
+ *
39
+ * `synchronize` is a real, supported option: the @Index decorator forwards it
40
+ * into IndexMetadataArgs (typeorm/decorator/Index.js), IndexMetadataArgs
41
+ * declares it, and RdbmsSchemaBuilder reads it. Only the public IndexOptions
42
+ * type omits it — a gap in TypeORM's own typings rather than a misuse.
43
+ */
44
+ const UNSYNCHRONIZED_INDEX: IndexOptions = {
45
+ synchronize: false,
46
+ } as IndexOptions;
47
+
48
+ export default UNSYNCHRONIZED_INDEX;
package/Types/Decimal.ts CHANGED
@@ -35,14 +35,26 @@ export default class Decimal extends DatabaseProperty {
35
35
  return this.value.toString();
36
36
  }
37
37
 
38
+ /*
39
+ * A decimal column is declared `TableColumnType.Number`, so what reaches
40
+ * the transformer may be a Decimal, a string, or a plain number depending
41
+ * on how far the request body got through deserialization.
42
+ *
43
+ * Both raw forms are handled before the truthiness check, because `0` is a
44
+ * perfectly good decimal and the old `if (value)` sent it to `null` — on a
45
+ * NOT NULL column that is a constraint violation for a value the caller
46
+ * supplied.
47
+ */
38
48
  protected static override toDatabase(
39
49
  value: Decimal | FindOperator<Decimal>,
40
50
  ): string | null {
41
- if (value) {
42
- if (typeof value === "string") {
43
- value = new Decimal(value);
44
- }
51
+ const rawValue: unknown = value;
52
+
53
+ if (typeof rawValue === "string" || typeof rawValue === "number") {
54
+ return new Decimal(rawValue).toString();
55
+ }
45
56
 
57
+ if (value) {
46
58
  return value.toString();
47
59
  }
48
60
 
@@ -7,6 +7,29 @@ import ObjectID from "./ObjectID";
7
7
  import CryptoJS from "crypto-js";
8
8
  import { FindOperator } from "typeorm";
9
9
 
10
+ /*
11
+ * SHA-256 hashing for values that are ALREADY high-entropy: session refresh
12
+ * tokens, email verification tokens, reset tokens, permission fingerprints.
13
+ * Those are random values the server minted, so there is nothing to guess and
14
+ * a fast hash is the right tool — it also keeps them searchable by hash,
15
+ * which is how they are looked up.
16
+ *
17
+ * User passwords are NOT hashed here any more. A password is low-entropy and
18
+ * guessable, so it goes through scrypt in Common/Server/Utils/PasswordHash,
19
+ * which is deliberately slow and memory-hard. What remains in this class is
20
+ * the two SHA-256 schemes passwords used to be stored under, kept so those
21
+ * rows still verify (and are then upgraded) at their owner's next login:
22
+ *
23
+ * no salt `SHA256(secret + value)`, from before per-user salts existed.
24
+ * salted `SHA256("v2:" + salt + ":" + secret + ":" + value)`.
25
+ *
26
+ * Neither records its scheme in the digest — it is a bare hex SHA-256 either
27
+ * way — so they are told apart by whether the row carries a salt. The "v2"
28
+ * prefix and the separators exist for domain separation: without them a
29
+ * crafted salt could make a salted input collide with an unsalted one.
30
+ */
31
+ const SALTED_HASH_SCHEME_VERSION: string = "v2";
32
+
10
33
  export default class HashedString extends DatabaseProperty {
11
34
  private isHashed: boolean = false;
12
35
 
@@ -65,7 +88,62 @@ export default class HashedString extends DatabaseProperty {
65
88
  return this.isHashed;
66
89
  }
67
90
 
68
- public async hashValue(encryptionSecret: ObjectID | null): Promise<string> {
91
+ /**
92
+ * Adopt an already-computed digest.
93
+ *
94
+ * For hashes this class does not compute itself — user passwords go
95
+ * through scrypt in Common/Server/Utils/PasswordHash, which is server-only
96
+ * because this type is also bundled into the browser. The guard is the
97
+ * same one hashValue() carries: a value is hashed once and never re-hashed
98
+ * over the top of itself.
99
+ */
100
+ public setHashedValue(hashedValue: string): void {
101
+ if (this.isHashed) {
102
+ throw new BadOperationException("Value is already hashed");
103
+ }
104
+
105
+ this.isHashed = true;
106
+ this.value = hashedValue;
107
+ }
108
+
109
+ /**
110
+ * The exact byte string fed to SHA-256.
111
+ *
112
+ * The unsalted branch MUST stay byte-for-byte identical forever: every
113
+ * password hashed before per-user salts existed is verified through it.
114
+ */
115
+ private static buildValueToHash(
116
+ value: string,
117
+ encryptionSecret: ObjectID | null,
118
+ salt: string | null,
119
+ ): string {
120
+ if (!salt) {
121
+ return (encryptionSecret || "") + value;
122
+ }
123
+
124
+ return `${SALTED_HASH_SCHEME_VERSION}:${salt}:${
125
+ encryptionSecret || ""
126
+ }:${value}`;
127
+ }
128
+
129
+ public static computeHash(
130
+ value: string,
131
+ encryptionSecret: ObjectID | null,
132
+ salt?: string | null,
133
+ ): string {
134
+ if (!value) {
135
+ return "";
136
+ }
137
+
138
+ return CryptoJS.SHA256(
139
+ HashedString.buildValueToHash(value, encryptionSecret, salt || null),
140
+ ).toString();
141
+ }
142
+
143
+ public async hashValue(
144
+ encryptionSecret: ObjectID | null,
145
+ salt?: string | null,
146
+ ): Promise<string> {
69
147
  if (!this.value) {
70
148
  return "";
71
149
  }
@@ -74,19 +152,76 @@ export default class HashedString extends DatabaseProperty {
74
152
  throw new BadOperationException("Value is already hashed");
75
153
  }
76
154
 
77
- const valueToHash: string = (encryptionSecret || "") + this.value;
78
155
  this.isHashed = true;
79
156
 
80
- this.value = CryptoJS.SHA256(valueToHash).toString();
157
+ this.value = HashedString.computeHash(
158
+ this.value,
159
+ encryptionSecret,
160
+ salt || null,
161
+ );
81
162
  return this.value;
82
163
  }
83
164
 
84
165
  public static async hashValue(
85
166
  value: string,
86
167
  encryptionSecret: ObjectID | null,
168
+ salt?: string | null,
87
169
  ): Promise<string> {
88
170
  const hashstring: HashedString = new HashedString(value, false);
89
- return await hashstring.hashValue(encryptionSecret);
171
+ return await hashstring.hashValue(encryptionSecret, salt || null);
172
+ }
173
+
174
+ /**
175
+ * Compare two already-hashed values without leaking, through timing, how
176
+ * many leading characters matched. Comparing digests with `===` lets an
177
+ * attacker who can measure response time recover a stored hash one
178
+ * character at a time; recovering the hash is what makes offline cracking
179
+ * possible in the first place.
180
+ *
181
+ * Lengths are compared up front. Digests here are always the same length,
182
+ * so an early exit only happens on malformed input, where the length is
183
+ * not the secret.
184
+ */
185
+ public static isEqual(a: string, b: string): boolean {
186
+ if (a.length !== b.length) {
187
+ return false;
188
+ }
189
+
190
+ let difference: number = 0;
191
+
192
+ for (let i: number = 0; i < a.length; i++) {
193
+ difference |= a.charCodeAt(i) ^ b.charCodeAt(i);
194
+ }
195
+
196
+ return difference === 0;
197
+ }
198
+
199
+ /**
200
+ * Verify a plaintext value against a stored hash.
201
+ *
202
+ * Pass the salt stored alongside the hash. Pass null/undefined for records
203
+ * written before per-user salts existed — those verify against the legacy
204
+ * unsalted scheme, and the caller should re-hash them with a fresh salt
205
+ * once the value is known to be correct.
206
+ */
207
+ public static async verifyValue(data: {
208
+ plainValue: string;
209
+ hashedValue: string;
210
+ encryptionSecret: ObjectID | null;
211
+ salt?: string | null | undefined;
212
+ }): Promise<boolean> {
213
+ if (!data.plainValue || !data.hashedValue) {
214
+ return false;
215
+ }
216
+
217
+ return HashedString.isEqual(
218
+ HashedString.computeHash(
219
+ data.plainValue,
220
+ data.encryptionSecret,
221
+ data.salt || null,
222
+ ),
223
+ data.hashedValue,
224
+ );
90
225
  }
91
226
 
92
227
  protected static override fromDatabase(_value: string): HashedString | null {
@@ -484,6 +484,12 @@ enum Permission {
484
484
  EditStatusPageLabelRule = "EditStatusPageLabelRule",
485
485
  ReadStatusPageLabelRule = "ReadStatusPageLabelRule",
486
486
 
487
+ // Status Page Monitor Rule Permissions
488
+ CreateStatusPageMonitorRule = "CreateStatusPageMonitorRule",
489
+ DeleteStatusPageMonitorRule = "DeleteStatusPageMonitorRule",
490
+ EditStatusPageMonitorRule = "EditStatusPageMonitorRule",
491
+ ReadStatusPageMonitorRule = "ReadStatusPageMonitorRule",
492
+
487
493
  // Host Owner Rule Permissions
488
494
  CreateHostOwnerRule = "CreateHostOwnerRule",
489
495
  DeleteHostOwnerRule = "DeleteHostOwnerRule",
@@ -11104,6 +11110,48 @@ export class PermissionHelper {
11104
11110
  group: PermissionGroup.StatusPage,
11105
11111
  },
11106
11112
 
11113
+ // Status Page Monitor Rule Permissions
11114
+ {
11115
+ permission: Permission.CreateStatusPageMonitorRule,
11116
+ title: "Create Status Page Monitor Rule",
11117
+ description:
11118
+ "This permission can create Status Page Monitor Rules in this project.",
11119
+ isAssignableToTenant: true,
11120
+ isAccessControlPermission: false,
11121
+ isRolePermission: false,
11122
+ group: PermissionGroup.StatusPage,
11123
+ },
11124
+ {
11125
+ permission: Permission.DeleteStatusPageMonitorRule,
11126
+ title: "Delete Status Page Monitor Rule",
11127
+ description:
11128
+ "This permission can delete Status Page Monitor Rules of this project.",
11129
+ isAssignableToTenant: true,
11130
+ isAccessControlPermission: false,
11131
+ isRolePermission: false,
11132
+ group: PermissionGroup.StatusPage,
11133
+ },
11134
+ {
11135
+ permission: Permission.EditStatusPageMonitorRule,
11136
+ title: "Edit Status Page Monitor Rule",
11137
+ description:
11138
+ "This permission can edit Status Page Monitor Rules of this project.",
11139
+ isAssignableToTenant: true,
11140
+ isAccessControlPermission: false,
11141
+ isRolePermission: false,
11142
+ group: PermissionGroup.StatusPage,
11143
+ },
11144
+ {
11145
+ permission: Permission.ReadStatusPageMonitorRule,
11146
+ title: "Read Status Page Monitor Rule",
11147
+ description:
11148
+ "This permission can read Status Page Monitor Rules of this project.",
11149
+ isAssignableToTenant: true,
11150
+ isAccessControlPermission: false,
11151
+ isRolePermission: false,
11152
+ group: PermissionGroup.StatusPage,
11153
+ },
11154
+
11107
11155
  // Host Owner Rule Permissions
11108
11156
  {
11109
11157
  permission: Permission.CreateHostOwnerRule,
package/Types/Port.ts CHANGED
@@ -59,17 +59,29 @@ export default class Port extends DatabaseProperty {
59
59
  this.port = new PositiveNumber(port);
60
60
  }
61
61
 
62
+ /*
63
+ * A port column is declared `TableColumnType.Number`, so a raw number is
64
+ * as legitimate an incoming value as a raw string — the SMTP forms send
65
+ * one or the other depending on how far the value got through
66
+ * deserialization. Dropping a number on the floor here wrote NULL over a
67
+ * port the user had just typed, with no error anywhere.
68
+ */
62
69
  public static override toDatabase(
63
70
  value: Port | FindOperator<Port>,
64
71
  ): number | null {
65
- if (typeof value === "string") {
66
- value = new Port(value);
72
+ /*
73
+ * The declared parameter type stays Port-shaped so this still overrides
74
+ * DatabaseProperty.toDatabase, but a raw string or number genuinely
75
+ * arrives here — see the note above — so widen at the check instead.
76
+ */
77
+ const rawValue: unknown = value;
78
+
79
+ if (typeof rawValue === "string" || typeof rawValue === "number") {
80
+ value = new Port(rawValue);
67
81
  }
68
82
 
69
83
  if (value instanceof Port) {
70
84
  return value.toNumber();
71
- } else if (typeof value === "string") {
72
- return parseInt(value);
73
85
  }
74
86
 
75
87
  return null;
@@ -51,14 +51,41 @@ export interface ComponentProps {
51
51
  customFieldType: DatabaseBaseModelType;
52
52
  projectId: ObjectID;
53
53
  name: string;
54
- isEditable?: boolean;
54
+ isEditable?: boolean | undefined;
55
+ /*
56
+ * For call sites that mount this card unconditionally - the resource
57
+ * overview pages - where most projects have never defined a custom field.
58
+ *
59
+ * With this set the card renders NOTHING until it knows the project has at
60
+ * least one field of this type: no loader flash and no "no custom fields
61
+ * have been added" placeholder occupying a slot on a page the operator
62
+ * opened to look at something else.
63
+ *
64
+ * It also hides on a load failure, because reading the schema is gated on
65
+ * both a permission and a billing plan. Without that, every incident a
66
+ * project below the custom-fields plan opens would carry an error card it
67
+ * can do nothing about. The dedicated Custom Fields page mounts this same
68
+ * component WITHOUT the flag, so anyone who goes looking still sees the
69
+ * real reason.
70
+ */
71
+ hideIfEmpty?: boolean | undefined;
55
72
  }
56
73
 
57
74
  const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
58
75
  props: ComponentProps,
59
76
  ): ReactElement => {
60
77
  const [schemaList, setSchemaList] = useState<Array<BaseModel>>([]);
61
- const [error, setError] = useState<string>("");
78
+ /*
79
+ * Read failures and write failures are kept apart because hideIfEmpty
80
+ * treats them oppositely. Failing to READ the schema is something an
81
+ * overview page swallows - the project may simply not be on a plan that
82
+ * includes custom fields, and there is nothing the operator can do about it
83
+ * on an incident page. Failing to WRITE is the operator's own edit coming
84
+ * back rejected, and must be reported wherever it happens. Sharing one slot
85
+ * meant a rejected save made the whole card disappear.
86
+ */
87
+ const [loadError, setLoadError] = useState<string>("");
88
+ const [saveError, setSaveError] = useState<string>("");
62
89
  const [isLoading, setIsLoading] = useState<boolean>(false);
63
90
  const [model, setModel] = useState<BaseModel | null>(null);
64
91
  const [showModelForm, setShowModelForm] = useState<boolean>(false);
@@ -67,6 +94,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
67
94
  try {
68
95
  // load schema.
69
96
  setIsLoading(true);
97
+ setLoadError("");
70
98
 
71
99
  const schemaList: ListResult<BaseModel> =
72
100
  await ModelAPI.getList<BaseModel>({
@@ -99,7 +127,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
99
127
  setIsLoading(false);
100
128
  } catch (err) {
101
129
  setIsLoading(false);
102
- setError(API.getFriendlyMessage(err));
130
+ setLoadError(API.getFriendlyMessage(err));
103
131
  }
104
132
  };
105
133
 
@@ -109,6 +137,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
109
137
  try {
110
138
  // load schema.
111
139
  setIsLoading(true);
140
+ setSaveError("");
112
141
  setShowModelForm(false);
113
142
 
114
143
  await ModelAPI.updateById({
@@ -122,7 +151,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
122
151
  await onLoad();
123
152
  } catch (err) {
124
153
  setIsLoading(false);
125
- setError(API.getFriendlyMessage(err));
154
+ setSaveError(API.getFriendlyMessage(err));
126
155
  }
127
156
  };
128
157
 
@@ -132,12 +161,45 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
132
161
 
133
162
  const isEditable: boolean = props.isEditable !== false;
134
163
 
164
+ /*
165
+ * schemaList is empty both before the first load finishes and when the
166
+ * project genuinely has no fields of this type, which is exactly when an
167
+ * overview page should show nothing - so one check covers both.
168
+ *
169
+ * `!model` covers the record itself coming back empty. On a page that is
170
+ * plainly displaying that record, a card announcing "Item not found" is a
171
+ * contradiction; showing nothing is the honest version.
172
+ *
173
+ * Deliberately NOT saveError: a rejected save has to stay on screen, and it
174
+ * leaves schemaList and model intact, so the card keeps rendering.
175
+ */
176
+ if (
177
+ props.hideIfEmpty &&
178
+ (Boolean(loadError) || schemaList.length === 0 || !model)
179
+ ) {
180
+ return <></>;
181
+ }
182
+
183
+ /*
184
+ * There has to be something to edit before the button is worth offering:
185
+ * the modal reads its initial values off the record and its inputs off the
186
+ * schema, so opening it mid-load threw on a null record, and opening it
187
+ * with no fields defined produced a form with nothing in it.
188
+ *
189
+ * A failed LOAD needs no term of its own - it leaves schemaList empty and
190
+ * model null, which the checks below already cover. A failed SAVE must not
191
+ * take the button away, or the error message is telling the operator to
192
+ * retry something they can no longer reach.
193
+ */
194
+ const canEdit: boolean =
195
+ isEditable && !isLoading && schemaList.length > 0 && Boolean(model);
196
+
135
197
  return (
136
198
  <Card
137
199
  title={props.title}
138
200
  description={props.description}
139
201
  buttons={
140
- isEditable
202
+ canEdit
141
203
  ? [
142
204
  {
143
205
  title: "Edit Fields",
@@ -152,14 +214,26 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
152
214
  }
153
215
  >
154
216
  <div className="border-t border-gray-200 px-4 py-5 sm:px-6 -m-6 -mt-2">
155
- {isLoading && !error && <ComponentLoader />}
156
- {!isLoading && !error && schemaList.length === 0 && (
217
+ {isLoading && !loadError && <ComponentLoader />}
218
+ {!isLoading && !loadError && schemaList.length === 0 && (
157
219
  <ErrorMessage message="No custom fields have been added for this resource. You may add custom fields in Project Settings." />
158
220
  )}
159
- {error && <ErrorMessage message={error} />}
160
- {!model && <ErrorMessage message={"Item not found"} />}
221
+ {loadError && <ErrorMessage message={loadError} />}
222
+ {/*
223
+ * Above the values, which are still the ones on the record: the save
224
+ * did not happen, so what is on screen below is what is stored.
225
+ */}
226
+ {saveError && <ErrorMessage message={saveError} />}
227
+ {/*
228
+ * Only once the fetch has settled: model is null on the very first
229
+ * render too, and announcing "Item not found" while the request is
230
+ * still in flight reads as a broken record rather than a pending one.
231
+ */}
232
+ {!isLoading && !loadError && schemaList.length > 0 && !model && (
233
+ <ErrorMessage message={"Item not found"} />
234
+ )}
161
235
 
162
- {!isLoading && !error && model && (
236
+ {!isLoading && !loadError && schemaList.length > 0 && model && (
163
237
  <Detail
164
238
  id={props.name}
165
239
  item={(model as any)["customFields"] || {}}
@@ -194,7 +268,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
194
268
  await onSave(data).catch();
195
269
  }}
196
270
  formProps={{
197
- initialValues: (model as any)["customFields"] || {},
271
+ initialValues: (model as any)?.["customFields"] || {},
198
272
  fields: schemaList.map((schemaItem: BaseModel) => {
199
273
  const isDropdown: boolean =
200
274
  (schemaItem as any).customFieldType ===