@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,86 @@
1
+ import React, { useEffect, useRef } from "react";
2
+
3
+ export interface LiveLogsRefreshOptions {
4
+ /** Whether the user has switched live mode on. */
5
+ isLive: boolean;
6
+ /**
7
+ * Whether the current view can be refreshed in place. Live tailing only
8
+ * makes sense on the first page sorted newest-first — on page 3, or sorted
9
+ * by severity, "the newest logs" is not what the viewer is showing.
10
+ */
11
+ isEligible: boolean;
12
+ /** How often to poll while live mode is on. */
13
+ intervalInMs: number;
14
+ /** Pull the newest page of logs. */
15
+ refreshLogs: () => void;
16
+ /**
17
+ * Re-run the histogram query. The chart is driven by its own aggregation
18
+ * request rather than by the rows in the table, so it goes stale unless it
19
+ * is refreshed on the same beat as the list.
20
+ */
21
+ refreshHistogram: () => void;
22
+ }
23
+
24
+ export type UseLiveLogsRefreshFunction = (
25
+ options: LiveLogsRefreshOptions,
26
+ ) => void;
27
+
28
+ /**
29
+ * Drives the polling loop behind the log viewer's live mode.
30
+ *
31
+ * Every tick refreshes the log list *and* the histogram together. That
32
+ * pairing is the point of this hook: the two come from different endpoints,
33
+ * and refreshing only the list leaves a chart that stops moving while rows
34
+ * keep arriving underneath it.
35
+ *
36
+ * The callbacks are read through refs, so a parent that re-renders (which it
37
+ * does on every poll, as results land) does not tear down and re-arm the
38
+ * timer — that would reset the cadence and fire an extra immediate poll each
39
+ * time. Only the gating flags and the interval restart the loop.
40
+ */
41
+ const useLiveLogsRefresh: UseLiveLogsRefreshFunction = (
42
+ options: LiveLogsRefreshOptions,
43
+ ): void => {
44
+ const { isLive, isEligible, intervalInMs } = options;
45
+
46
+ const refreshLogsRef: React.MutableRefObject<() => void> = useRef<() => void>(
47
+ options.refreshLogs,
48
+ );
49
+ const refreshHistogramRef: React.MutableRefObject<() => void> = useRef<
50
+ () => void
51
+ >(options.refreshHistogram);
52
+
53
+ /*
54
+ * Declared before the polling effect so the refs are already current by the
55
+ * time that effect (re-)runs — effects fire in declaration order.
56
+ */
57
+ useEffect(() => {
58
+ refreshLogsRef.current = options.refreshLogs;
59
+ refreshHistogramRef.current = options.refreshHistogram;
60
+ });
61
+
62
+ useEffect(() => {
63
+ if (!isLive || !isEligible) {
64
+ return;
65
+ }
66
+
67
+ const poll: () => void = (): void => {
68
+ refreshLogsRef.current();
69
+ refreshHistogramRef.current();
70
+ };
71
+
72
+ // Switching live mode on should show fresh data immediately, not one tick later.
73
+ poll();
74
+
75
+ const intervalId: ReturnType<typeof setInterval> = setInterval(
76
+ poll,
77
+ intervalInMs,
78
+ );
79
+
80
+ return () => {
81
+ clearInterval(intervalId);
82
+ };
83
+ }, [isLive, isEligible, intervalInMs]);
84
+ };
85
+
86
+ export default useLiveLogsRefresh;
@@ -0,0 +1,113 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import { HistogramBucket } from "./types";
3
+
4
+ export interface LogsHistogramRefreshOptions {
5
+ /**
6
+ * A background refresh: no loader, no blanking on failure, and no stacking
7
+ * if the previous one has not come back yet. This is what live mode uses.
8
+ */
9
+ silent?: boolean | undefined;
10
+ }
11
+
12
+ export interface LogsHistogramState {
13
+ buckets: Array<HistogramBucket>;
14
+ isLoading: boolean;
15
+ refresh: (options?: LogsHistogramRefreshOptions) => Promise<void>;
16
+ }
17
+
18
+ export type FetchHistogramBucketsFunction = () => Promise<
19
+ Array<HistogramBucket>
20
+ >;
21
+
22
+ export type UseLogsHistogramFunction = (
23
+ fetchBuckets: FetchHistogramBucketsFunction,
24
+ ) => LogsHistogramState;
25
+
26
+ /**
27
+ * Owns the log histogram's data, loading flag and refresh loop.
28
+ *
29
+ * The chart is fed by its own aggregation query rather than by the rows in
30
+ * the table, so it needs refreshing in its own right — including on every
31
+ * live-mode tick. A poll is a *silent* refresh, which is what keeps live mode
32
+ * from looking broken:
33
+ *
34
+ * - it does not raise the loading flag, so the chart does not flash a loader
35
+ * every few seconds;
36
+ * - it keeps the buckets already on screen if the request fails, so one bad
37
+ * response does not blank the chart;
38
+ * - it is dropped while an earlier poll is still running, so a slow
39
+ * aggregation cannot pile requests up behind it.
40
+ *
41
+ * `fetchBuckets` doubles as the query identity: hand over a new callback (a
42
+ * new time range, filter or scope) and the histogram reloads.
43
+ */
44
+ const useLogsHistogram: UseLogsHistogramFunction = (
45
+ fetchBuckets: FetchHistogramBucketsFunction,
46
+ ): LogsHistogramState => {
47
+ const [buckets, setBuckets] = useState<Array<HistogramBucket>>([]);
48
+ const [isLoading, setIsLoading] = useState<boolean>(false);
49
+
50
+ const silentRequestInFlight: React.MutableRefObject<boolean> =
51
+ useRef<boolean>(false);
52
+
53
+ /*
54
+ * The query the viewer is on right now. A request that comes back after the
55
+ * reader has moved to another time range or filter is dropped rather than
56
+ * painted over the window they are actually looking at.
57
+ */
58
+ const currentQuery: React.MutableRefObject<FetchHistogramBucketsFunction> =
59
+ useRef<FetchHistogramBucketsFunction>(fetchBuckets);
60
+
61
+ useEffect(() => {
62
+ currentQuery.current = fetchBuckets;
63
+ }, [fetchBuckets]);
64
+
65
+ const refresh: (options?: LogsHistogramRefreshOptions) => Promise<void> =
66
+ useCallback(
67
+ async (options: LogsHistogramRefreshOptions = {}): Promise<void> => {
68
+ const isSilent: boolean = options.silent === true;
69
+
70
+ if (isSilent) {
71
+ if (silentRequestInFlight.current) {
72
+ return;
73
+ }
74
+
75
+ silentRequestInFlight.current = true;
76
+ } else {
77
+ setIsLoading(true);
78
+ }
79
+
80
+ try {
81
+ const nextBuckets: Array<HistogramBucket> = await fetchBuckets();
82
+
83
+ if (currentQuery.current === fetchBuckets) {
84
+ setBuckets(nextBuckets);
85
+ }
86
+ } catch {
87
+ // The histogram is non-critical; degrade rather than fail the page.
88
+ if (!isSilent && currentQuery.current === fetchBuckets) {
89
+ setBuckets([]);
90
+ }
91
+ } finally {
92
+ if (isSilent) {
93
+ silentRequestInFlight.current = false;
94
+ } else if (currentQuery.current === fetchBuckets) {
95
+ setIsLoading(false);
96
+ }
97
+ }
98
+ },
99
+ [fetchBuckets],
100
+ );
101
+
102
+ useEffect(() => {
103
+ void refresh();
104
+ }, [refresh]);
105
+
106
+ return {
107
+ buckets: buckets,
108
+ isLoading: isLoading,
109
+ refresh: refresh,
110
+ };
111
+ };
112
+
113
+ export default useLogsHistogram;
@@ -13,6 +13,7 @@ import NavBarMenuModal from "./NavBarMenuModal";
13
13
  import Button, { ButtonStyleType } from "../Button/Button";
14
14
  import Navigation from "../../Utils/Navigation";
15
15
  import useComponentOutsideClick from "../../Types/UseComponentOutsideClick";
16
+ import useTranslateValue from "../../Utils/Translation";
16
17
  import Icon, { ThickProp } from "../Icon/Icon";
17
18
 
18
19
  export interface NavItem {
@@ -54,7 +55,7 @@ export interface ComponentProps {
54
55
  items?: NavItem[];
55
56
  rightElement?: NavItem;
56
57
  moreMenuItems?: MoreMenuItem[];
57
- moreMenuTitle?: string; // Title for the more menu (default: "More")
58
+ moreMenuTitle?: string; // Title for the more menu (default: "Products")
58
59
  moreMenuSearchPlaceholder?: string; // Placeholder for the menu search box
59
60
  moreMenuNoResultsText?: string; // Empty-state text when search matches nothing
60
61
  moreMenuKeyboardHint?: string; // Keyboard hint shown in the menu footer
@@ -79,6 +80,7 @@ const MOBILE_MENU_MIN_HEIGHT_IN_PX: number = 160;
79
80
  const Navbar: FunctionComponent<ComponentProps> = (
80
81
  props: ComponentProps,
81
82
  ): ReactElement => {
83
+ const { translateString } = useTranslateValue();
82
84
  const [isMobile, setIsMobile] = useState<boolean>(false);
83
85
  const [isMobileMenuVisible, setIsMobileMenuVisible] =
84
86
  useState<boolean>(false);
@@ -408,7 +410,15 @@ const Navbar: FunctionComponent<ComponentProps> = (
408
410
  className="mr-1.5 h-4 w-4 transition-transform duration-150 group-hover:scale-110 group-hover:text-indigo-600"
409
411
  thick={ThickProp.Thick}
410
412
  />
411
- <span>{props.moreMenuTitle || "Products"}</span>
413
+ {/*
414
+ * Callers pass a plain English literal here (or nothing, and
415
+ * take the default) — run it through the same flat-key lookup
416
+ * the side menu uses so the button is localised like every
417
+ * other label in the nav.
418
+ */}
419
+ <span>
420
+ {translateString(props.moreMenuTitle || "Products")}
421
+ </span>
412
422
  <Icon
413
423
  icon={IconProp.ChevronDown}
414
424
  className={`ml-1.5 h-3 w-3 text-gray-400 transition-transform duration-200 ${
@@ -1,6 +1,7 @@
1
1
  import Icon from "../Icon/Icon";
2
2
  import Link from "../Link/Link";
3
3
  import Navigation from "../../Utils/Navigation";
4
+ import useTranslateValue from "../../Utils/Translation";
4
5
  import IconProp from "../../../Types/Icon/IconProp";
5
6
  import URL from "../../../Types/API/URL";
6
7
  import { isMoreMenuItemActive, type MoreMenuItem } from "./NavBar";
@@ -163,7 +164,18 @@ const NavBarMenuModal: FunctionComponent<ComponentProps> = (
163
164
  Array<HTMLDivElement | null>
164
165
  >([]);
165
166
 
166
- const recentLabel: string = props.recentLabel || "Recent";
167
+ /*
168
+ * Callers hand us plain English literals (or omit them and take the
169
+ * defaults). Run every user-facing string through the same flat-key lookup
170
+ * the rest of the nav uses, so a caller that has not been wired up to i18n
171
+ * still renders localised text instead of English.
172
+ */
173
+ const { translateString } = useTranslateValue();
174
+ const tx: (value: string) => string = (value: string): string => {
175
+ return translateString(value) ?? value;
176
+ };
177
+
178
+ const recentLabel: string = tx(props.recentLabel || "Recent");
167
179
 
168
180
  // Show the OS-appropriate shortcut hint (⌘K on macOS, Ctrl K elsewhere).
169
181
  const shortcutLabel: string = useMemo(() => {
@@ -482,7 +494,7 @@ const NavBarMenuModal: FunctionComponent<ComponentProps> = (
482
494
  ? `navbar-menu-option-${activeIndex}`
483
495
  : undefined
484
496
  }
485
- aria-label={props.searchPlaceholder || "Search products"}
497
+ aria-label={tx(props.searchPlaceholder || "Search products")}
486
498
  autoComplete="off"
487
499
  autoCorrect="off"
488
500
  autoCapitalize="off"
@@ -492,7 +504,7 @@ const NavBarMenuModal: FunctionComponent<ComponentProps> = (
492
504
  setQuery(event.target.value);
493
505
  }}
494
506
  onKeyDown={handleKeyDown}
495
- placeholder={props.searchPlaceholder || "Search…"}
507
+ placeholder={tx(props.searchPlaceholder || "Search…")}
496
508
  className="flex-1 border-0 bg-transparent p-0 text-base text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-0"
497
509
  />
498
510
  {query ? (
@@ -530,7 +542,7 @@ const NavBarMenuModal: FunctionComponent<ComponentProps> = (
530
542
  />
531
543
  </div>
532
544
  <p className="text-sm font-medium text-gray-900">
533
- {props.noResultsText || "No results found."}
545
+ {tx(props.noResultsText || "No results found.")}
534
546
  </p>
535
547
  {query && (
536
548
  <p className="mt-1 text-xs text-gray-500">
@@ -640,20 +652,20 @@ const NavBarMenuModal: FunctionComponent<ComponentProps> = (
640
652
  </div>
641
653
  <div className="min-w-0 text-left">
642
654
  <p className="flex items-center gap-1 text-sm font-medium text-gray-900">
643
- <span className="truncate">{props.footer.title}</span>
655
+ <span className="truncate">{tx(props.footer.title)}</span>
644
656
  <Icon
645
657
  icon={IconProp.ExternalLink}
646
658
  className="h-3 w-3 flex-shrink-0 text-gray-400 opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-visible:opacity-100"
647
659
  />
648
660
  </p>
649
661
  <p className="truncate text-xs text-gray-500">
650
- {props.footer.description}
662
+ {tx(props.footer.description)}
651
663
  </p>
652
664
  </div>
653
665
  </Link>
654
666
  {props.keyboardHint && (
655
667
  <div
656
- aria-label={props.keyboardHint}
668
+ aria-label={tx(props.keyboardHint)}
657
669
  className="hidden flex-shrink-0 items-center gap-2 text-xs text-gray-400 md:flex"
658
670
  >
659
671
  <span className="flex items-center gap-1">
@@ -0,0 +1,257 @@
1
+ import ModelAPI, { ListResult, RequestOptions } from "./ModelAPI";
2
+ import API from "../API/API";
3
+ import { APP_API_URL } from "../../Config";
4
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
5
+ import Team from "../../../Models/DatabaseModels/Team";
6
+ import TeamMember from "../../../Models/DatabaseModels/TeamMember";
7
+ import User from "../../../Models/DatabaseModels/User";
8
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
9
+ import HTTPMethod from "../../../Types/API/HTTPMethod";
10
+ import HTTPResponse from "../../../Types/API/HTTPResponse";
11
+ import URL from "../../../Types/API/URL";
12
+ import Query from "../../../Types/BaseDatabase/Query";
13
+ import Select from "../../../Types/BaseDatabase/Select";
14
+ import Sort from "../../../Types/BaseDatabase/Sort";
15
+ import GroupBy from "../../../Types/BaseDatabase/GroupBy";
16
+ import BadDataException from "../../../Types/Exception/BadDataException";
17
+ import { JSONArray, JSONObject } from "../../../Types/JSON";
18
+ import ObjectID from "../../../Types/ObjectID";
19
+
20
+ /*
21
+ * A drop-in ModelAPI for tables that list TeamMember but mean to list the
22
+ * PROJECTS one user belongs to - the Admin Dashboard's User > Projects page
23
+ * being the one that does.
24
+ *
25
+ * TeamMember rows are (user, team) pairs, so the plain API pages over
26
+ * memberships: a user on three teams of one project occupies three rows and
27
+ * reads as three projects. This API pages over projects instead, by calling the
28
+ * master-admin endpoint that does the grouping server-side
29
+ * (POST /user/:userId/projects). Grouping there rather than in the browser is
30
+ * what makes `count` a number of projects and every page boundary fall between
31
+ * two projects rather than between two of one project's teams.
32
+ *
33
+ * The endpoint owns the shape of a row, so the caller's `select` and `sort` are
34
+ * NOT forwarded - a row is always a project with its teams, always ordered by
35
+ * project name. Adding a column to the table therefore means adding the field
36
+ * to the endpoint's select and to the row it serializes, not just to the
37
+ * column list. Only `skip`/`limit` and the user in the query are the caller's.
38
+ *
39
+ * Deleting is redefined to match what a row now means - see deleteItem.
40
+ */
41
+
42
+ export interface UserProjectExtras {
43
+ // Every team this user belongs to in this project, sorted by name.
44
+ teamsForProject: Array<Team>;
45
+ teamCountForProject: number;
46
+ /*
47
+ * How many of those memberships are still unaccepted invitations. A user can
48
+ * be a fully accepted member of one team of a project and a pending invite on
49
+ * another.
50
+ */
51
+ pendingTeamCountForProject: number;
52
+ // The ids of every membership this row was folded from.
53
+ teamMemberIdsForProject: Array<string>;
54
+ }
55
+
56
+ /*
57
+ * The row is still a TeamMember - the table, its columns and its delete action
58
+ * are all typed against the model - it just stands for a project the user is in
59
+ * rather than for one of their memberships.
60
+ */
61
+ export type UserProjectRow = TeamMember & UserProjectExtras;
62
+
63
+ export default class UserProjectsModelAPI extends ModelAPI {
64
+ public static override async getList<TBaseModel extends BaseModel>(data: {
65
+ modelType: { new (): TBaseModel };
66
+ query: Query<TBaseModel>;
67
+ groupBy?: GroupBy<TBaseModel> | undefined;
68
+ limit: number;
69
+ skip: number;
70
+ select: Select<TBaseModel>;
71
+ sort: Sort<TBaseModel>;
72
+ requestOptions?: RequestOptions | undefined;
73
+ }): Promise<ListResult<TBaseModel>> {
74
+ if (!(new data.modelType() instanceof TeamMember)) {
75
+ /*
76
+ * Not the model this API exists for - a table's entity filter fetching
77
+ * its dropdown options, say. Behave like the plain one, but through
78
+ * `super` so a subclass's getCommonHeaders still applies.
79
+ */
80
+ return super.getList<TBaseModel>(data);
81
+ }
82
+
83
+ const userId: ObjectID | null = this.getUserIdFromQuery(
84
+ data.query as Query<TeamMember>,
85
+ );
86
+
87
+ if (!userId) {
88
+ /*
89
+ * Without a user there is no list to fetch - the endpoint is
90
+ * "the projects of THIS user". Failing loudly beats returning an empty
91
+ * list, which would render as "this user is in no projects".
92
+ */
93
+ throw new BadDataException(
94
+ "A user id is required to list the projects a user belongs to.",
95
+ );
96
+ }
97
+
98
+ const url: URL = URL.fromString(APP_API_URL.toString())
99
+ .addRoute(new User().getCrudApiPath()!)
100
+ .addRoute(`/${userId.toString()}/projects`);
101
+
102
+ const result: HTTPResponse<JSONArray> | HTTPErrorResponse =
103
+ await API.fetch<JSONArray>({
104
+ method: HTTPMethod.POST,
105
+ url: url,
106
+ data: {},
107
+ headers: this.getCommonHeaders(data.requestOptions),
108
+ params: {
109
+ limit: data.limit.toString(),
110
+ skip: data.skip.toString(),
111
+ },
112
+ });
113
+
114
+ if (result instanceof HTTPErrorResponse) {
115
+ throw result;
116
+ }
117
+
118
+ const rows: Array<UserProjectRow> = (result.data as JSONArray).map(
119
+ (item: JSONObject) => {
120
+ return this.toRow(item);
121
+ },
122
+ );
123
+
124
+ return {
125
+ data: rows as unknown as Array<TBaseModel>,
126
+ count: result.count,
127
+ skip: result.skip,
128
+ limit: result.limit,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Removes the user from the project on this row - every one of their team
134
+ * memberships in it, not just the one whose id the row happens to carry.
135
+ *
136
+ * A grouped row stands for a project, so deleting one membership would leave
137
+ * the row on screen (still in the project, via their other teams) and look
138
+ * like the delete silently failed. Removing the user from a single team is
139
+ * done from the memberships table on the same page, where a row is a
140
+ * membership again.
141
+ *
142
+ * This goes through one `remove-from-project` request rather than a DELETE
143
+ * per membership, and that is load-bearing rather than an optimisation. The
144
+ * server refuses to remove the last accepted member of the Owners team, and
145
+ * it refuses per request - so a loop deletes every other membership first and
146
+ * only then reports a failure, leaving the person stripped of the teams the
147
+ * caller was just told they had not lost. The endpoint runs that check across
148
+ * the whole set before deleting anything.
149
+ */
150
+ public static override async deleteItem<TBaseModel extends BaseModel>(data: {
151
+ modelType: { new (): TBaseModel };
152
+ id: ObjectID;
153
+ requestOptions?: RequestOptions | undefined;
154
+ }): Promise<void> {
155
+ if (!(new data.modelType() instanceof TeamMember)) {
156
+ return super.deleteItem<TBaseModel>(data);
157
+ }
158
+
159
+ const rowMembership: TeamMember | null = await this.getItem<TeamMember>({
160
+ modelType: TeamMember,
161
+ id: data.id,
162
+ select: {
163
+ _id: true,
164
+ userId: true,
165
+ projectId: true,
166
+ },
167
+ requestOptions: data.requestOptions,
168
+ });
169
+
170
+ const userId: ObjectID | undefined = rowMembership?.userId;
171
+ const projectId: ObjectID | undefined = rowMembership?.projectId;
172
+
173
+ if (!userId || !projectId) {
174
+ /*
175
+ * The row is gone, or came back without the user or project it stands
176
+ * for. Fall back to the single delete the table asked for rather than
177
+ * guessing which project to empty.
178
+ */
179
+ return super.deleteItem<TBaseModel>(data);
180
+ }
181
+
182
+ const url: URL = URL.fromString(APP_API_URL.toString())
183
+ .addRoute(new User().getCrudApiPath()!)
184
+ .addRoute(`/${userId.toString()}/remove-from-project`);
185
+
186
+ const result: HTTPResponse<JSONObject> | HTTPErrorResponse =
187
+ await API.post<JSONObject>({
188
+ url: url,
189
+ data: {
190
+ projectId: projectId.toString(),
191
+ },
192
+ headers: this.getCommonHeaders(data.requestOptions),
193
+ });
194
+
195
+ if (result instanceof HTTPErrorResponse) {
196
+ throw result;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * The user the table is listing projects for.
202
+ *
203
+ * Accepts the id in the shapes a table query can carry it in: an ObjectID
204
+ * (what the page passes) or a plain string.
205
+ */
206
+ private static getUserIdFromQuery(query: Query<TeamMember>): ObjectID | null {
207
+ const value: unknown = (query as Record<string, unknown>)["userId"];
208
+
209
+ if (value instanceof ObjectID) {
210
+ return value;
211
+ }
212
+
213
+ if (typeof value === "string" && value) {
214
+ return new ObjectID(value);
215
+ }
216
+
217
+ return null;
218
+ }
219
+
220
+ /**
221
+ * One row of the response.
222
+ *
223
+ * The base of the row is a TeamMember and hydrates as one. The aggregate
224
+ * fields are not columns of TeamMember, so BaseModel.fromJSON drops them -
225
+ * they are read off the raw JSON and attached here.
226
+ */
227
+ private static toRow(item: JSONObject): UserProjectRow {
228
+ const row: UserProjectRow = BaseModel.fromJSON(
229
+ item,
230
+ TeamMember,
231
+ ) as UserProjectRow;
232
+
233
+ row.teamsForProject = BaseModel.fromJSONArray(
234
+ (item["teams"] as Array<JSONObject>) || [],
235
+ Team,
236
+ );
237
+ row.teamCountForProject = row.teamsForProject.length;
238
+ row.pendingTeamCountForProject = this.toCount(item["pendingTeamCount"]);
239
+ row.teamMemberIdsForProject = (
240
+ (item["teamMemberIds"] as Array<unknown>) || []
241
+ )
242
+ .map((id: unknown) => {
243
+ return id?.toString() || "";
244
+ })
245
+ .filter((id: string) => {
246
+ return Boolean(id);
247
+ });
248
+
249
+ return row;
250
+ }
251
+
252
+ private static toCount(value: unknown): number {
253
+ const count: number = Number(value);
254
+
255
+ return Number.isFinite(count) && count > 0 ? count : 0;
256
+ }
257
+ }