@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
@@ -11,6 +11,7 @@ import IncidentService from "../../../Server/Services/IncidentService";
11
11
  import KubernetesResourceService from "../../../Server/Services/KubernetesResourceService";
12
12
  import LogService from "../../../Server/Services/LogService";
13
13
  import MonitorService from "../../../Server/Services/MonitorService";
14
+ import NetworkSiteService from "../../../Server/Services/NetworkSiteService";
14
15
  import PodmanHostService from "../../../Server/Services/PodmanHostService";
15
16
  import PodmanResourceService from "../../../Server/Services/PodmanResourceService";
16
17
  import ProxmoxResourceService from "../../../Server/Services/ProxmoxResourceService";
@@ -141,6 +142,30 @@ const RESOURCE_CASES: Array<ResourceCase> = [
141
142
  nonSelectColumn: "monitorSteps",
142
143
  selectKeys: ["_id", "name", "monitorType", "currentMonitorStatus"],
143
144
  },
145
+ {
146
+ /*
147
+ * The Network Map draws a FLAT set of pins, so the hierarchy columns
148
+ * (parentSiteId, materializedPath, depth) are deliberately outside both
149
+ * the allowlist and the select — structure an anonymous viewer would be
150
+ * able to walk but the map never shows them.
151
+ */
152
+ resourceType: "network-site",
153
+ componentType: DashboardComponentType.NetworkMap,
154
+ service: NetworkSiteService,
155
+ kind: null,
156
+ allowedQueryKey: "networkSiteTypeId",
157
+ forbiddenQueryKey: "parentSiteId",
158
+ selectColumn: "name",
159
+ nonSelectColumn: "address",
160
+ selectKeys: [
161
+ "_id",
162
+ "name",
163
+ "networkSiteType",
164
+ "latitude",
165
+ "longitude",
166
+ "currentMonitorStatus",
167
+ ],
168
+ },
144
169
  {
145
170
  resourceType: "host",
146
171
  componentType: DashboardComponentType.HostList,
@@ -465,6 +490,7 @@ const ALL_SERVICES: Array<ListService> = [
465
490
  IncidentService,
466
491
  AlertService,
467
492
  MonitorService,
493
+ NetworkSiteService,
468
494
  HostService,
469
495
  KubernetesResourceService,
470
496
  DockerHostService,
@@ -0,0 +1,598 @@
1
+ import Dashboard from "../../../Models/DatabaseModels/Dashboard";
2
+ import DashboardAPI from "../../../Server/API/DashboardAPI";
3
+ import DashboardService from "../../../Server/Services/DashboardService";
4
+ import MetricService from "../../../Server/Services/MetricService";
5
+ import TelemetryAttributeService from "../../../Server/Services/TelemetryAttributeService";
6
+ import {
7
+ ExpressRequest,
8
+ ExpressResponse,
9
+ NextFunction,
10
+ } from "../../../Server/Utils/Express";
11
+ import {
12
+ DashboardTemplateType,
13
+ getTemplateConfig,
14
+ } from "../../../Types/Dashboard/DashboardTemplates";
15
+ import { DashboardVariableType } from "../../../Types/Dashboard/DashboardVariable";
16
+ import DashboardViewConfig from "../../../Types/Dashboard/DashboardViewConfig";
17
+ import { JSONObject } from "../../../Types/JSON";
18
+ import ObjectID from "../../../Types/ObjectID";
19
+ import { mockRouter } from "./Helpers";
20
+ import {
21
+ beforeAll,
22
+ beforeEach,
23
+ afterEach,
24
+ describe,
25
+ expect,
26
+ it,
27
+ } from "@jest/globals";
28
+
29
+ jest.mock("../../../Server/Utils/Express", () => {
30
+ return {
31
+ getRouter: () => {
32
+ return mockRouter;
33
+ },
34
+ };
35
+ });
36
+
37
+ jest.mock("../../../Server/Utils/Response", () => {
38
+ return {
39
+ sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => {
40
+ return args;
41
+ }),
42
+ sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => {
43
+ return args;
44
+ }),
45
+ sendEmptySuccessResponse: jest.fn(),
46
+ sendEntityResponse: jest.fn().mockImplementation((...args: []) => {
47
+ return args;
48
+ }),
49
+ sendErrorResponse: jest.fn().mockImplementation((...args: []) => {
50
+ return args;
51
+ }),
52
+ };
53
+ });
54
+
55
+ const ATTRIBUTE_VALUES_ROUTE: string =
56
+ "/dashboard/attribute-values/:dashboardId";
57
+ const METRICS_AGGREGATE_ROUTE: string =
58
+ "/dashboard/metrics-aggregate/:dashboardId";
59
+
60
+ /*
61
+ * Every template a user can actually create a dashboard from. Blank has no
62
+ * config at all, so it is excluded rather than special-cased.
63
+ */
64
+ const TEMPLATE_TYPES: Array<DashboardTemplateType> = Object.values(
65
+ DashboardTemplateType,
66
+ ).filter((type: DashboardTemplateType) => {
67
+ return type !== DashboardTemplateType.Blank;
68
+ });
69
+
70
+ /*
71
+ * Small, deliberately independent readers over a stored config. These
72
+ * duplicate a little of what the server's allowlist walkers do on purpose:
73
+ * if the walkers stop reaching a shape the templates actually ship, these
74
+ * still find the key and the request they build starts failing.
75
+ */
76
+ type CollectFunction = (config: unknown) => Array<string>;
77
+
78
+ const collectVariableAttributeKeys: CollectFunction = (
79
+ config: unknown,
80
+ ): Array<string> => {
81
+ const variables: unknown =
82
+ config && typeof config === "object"
83
+ ? (config as Record<string, unknown>)["variables"]
84
+ : undefined;
85
+
86
+ if (!Array.isArray(variables)) {
87
+ return [];
88
+ }
89
+
90
+ return variables
91
+ .filter((variable: unknown) => {
92
+ return (
93
+ variable &&
94
+ typeof variable === "object" &&
95
+ (variable as Record<string, unknown>)["type"] ===
96
+ DashboardVariableType.TelemetryAttribute &&
97
+ typeof (variable as Record<string, unknown>)["attributeKey"] ===
98
+ "string"
99
+ );
100
+ })
101
+ .map((variable: unknown) => {
102
+ return (variable as Record<string, unknown>)["attributeKey"] as string;
103
+ });
104
+ };
105
+
106
+ type WalkCollectFunction = (config: unknown, field: string) => Array<string>;
107
+
108
+ const collectStringsUnderKey: WalkCollectFunction = (
109
+ config: unknown,
110
+ field: string,
111
+ ): Array<string> => {
112
+ const found: Array<string> = [];
113
+
114
+ const walk: (node: unknown) => void = (node: unknown): void => {
115
+ if (!node || typeof node !== "object") {
116
+ return;
117
+ }
118
+
119
+ if (Array.isArray(node)) {
120
+ for (const item of node) {
121
+ walk(item);
122
+ }
123
+ return;
124
+ }
125
+
126
+ const obj: Record<string, unknown> = node as Record<string, unknown>;
127
+ const value: unknown = obj[field];
128
+
129
+ if (typeof value === "string" && value.length > 0) {
130
+ found.push(value);
131
+ }
132
+
133
+ if (Array.isArray(value)) {
134
+ for (const item of value) {
135
+ if (typeof item === "string" && item.length > 0) {
136
+ found.push(item);
137
+ }
138
+ }
139
+ }
140
+
141
+ for (const key of Object.keys(obj)) {
142
+ walk(obj[key]);
143
+ }
144
+ };
145
+
146
+ walk(config);
147
+
148
+ return Array.from(new Set(found));
149
+ };
150
+
151
+ describe("public dashboard allowlists vs the shipped templates", () => {
152
+ let dashboardId: ObjectID;
153
+ let projectId: ObjectID;
154
+ let dashboard: Dashboard;
155
+ let mockResponse: ExpressResponse;
156
+ let nextFunction: NextFunction;
157
+
158
+ beforeAll(() => {
159
+ mockRouter.routes.length = 0;
160
+ new DashboardAPI();
161
+ });
162
+
163
+ type LoadTemplateFunction = (type: DashboardTemplateType) => JSONObject;
164
+
165
+ /*
166
+ * Round-trip the template through JSON the way the JSONB column does, so
167
+ * ObjectID instances become strings and the handler sees exactly the shape
168
+ * a real stored dashboard has.
169
+ */
170
+ const loadTemplate: LoadTemplateFunction = (
171
+ type: DashboardTemplateType,
172
+ ): JSONObject => {
173
+ const config: DashboardViewConfig | null = getTemplateConfig(type);
174
+
175
+ expect(config).not.toBeNull();
176
+
177
+ return JSON.parse(JSON.stringify(config)) as JSONObject;
178
+ };
179
+
180
+ type CallAttributeValuesFunction = (attributeKey: string) => Promise<void>;
181
+
182
+ const callAttributeValues: CallAttributeValuesFunction = async (
183
+ attributeKey: string,
184
+ ): Promise<void> => {
185
+ const request: ExpressRequest = {
186
+ params: { dashboardId: dashboardId.toString() },
187
+ body: { attributeKey: attributeKey },
188
+ query: {},
189
+ cookies: {},
190
+ headers: {},
191
+ socket: {},
192
+ ips: [],
193
+ } as unknown as ExpressRequest;
194
+
195
+ await mockRouter
196
+ .match("post", ATTRIBUTE_VALUES_ROUTE)
197
+ .handlerFunction(request, mockResponse, nextFunction);
198
+ };
199
+
200
+ type CallAggregateFunction = (overrides: JSONObject) => Promise<void>;
201
+
202
+ const callAggregate: CallAggregateFunction = async (
203
+ overrides: JSONObject,
204
+ ): Promise<void> => {
205
+ const request: ExpressRequest = {
206
+ params: { dashboardId: dashboardId.toString() },
207
+ body: {
208
+ aggregateBy: {
209
+ aggregateColumnName: "value",
210
+ aggregationType: "Avg",
211
+ aggregationTimestampColumnName: "time",
212
+ startTimestamp: "2026-01-01T00:00:00.000Z",
213
+ endTimestamp: "2026-01-02T00:00:00.000Z",
214
+ limit: 100,
215
+ skip: 0,
216
+ ...overrides,
217
+ },
218
+ },
219
+ query: {},
220
+ cookies: {},
221
+ headers: {},
222
+ socket: {},
223
+ ips: [],
224
+ } as unknown as ExpressRequest;
225
+
226
+ await mockRouter
227
+ .match("post", METRICS_AGGREGATE_ROUTE)
228
+ .handlerFunction(request, mockResponse, nextFunction);
229
+ };
230
+
231
+ type ExpectNoErrorFunction = (context: string) => void;
232
+
233
+ const expectNoError: ExpectNoErrorFunction = (context: string): void => {
234
+ const calls: Array<Array<unknown>> = (nextFunction as jest.Mock).mock
235
+ .calls as Array<Array<unknown>>;
236
+
237
+ if (calls.length > 0) {
238
+ const error: unknown = calls[0]![0];
239
+
240
+ throw new Error(
241
+ `${context} was refused: ${(error as Error)?.message || String(error)}`,
242
+ );
243
+ }
244
+ };
245
+
246
+ beforeEach(() => {
247
+ jest.clearAllMocks();
248
+
249
+ dashboardId = ObjectID.generate();
250
+ projectId = ObjectID.generate();
251
+
252
+ dashboard = new Dashboard();
253
+ dashboard.id = dashboardId;
254
+ dashboard.projectId = projectId;
255
+ dashboard.isPublicDashboard = true;
256
+ dashboard.enableMasterPassword = false;
257
+
258
+ jest.spyOn(DashboardService, "findOneById").mockResolvedValue(dashboard);
259
+ jest
260
+ .spyOn(TelemetryAttributeService, "fetchAttributeValues")
261
+ .mockResolvedValue([]);
262
+ jest.spyOn(MetricService, "aggregateBy").mockResolvedValue({ data: [] });
263
+
264
+ mockResponse = {
265
+ cookie: jest.fn(),
266
+ send: jest.fn(),
267
+ json: jest.fn(),
268
+ status: jest.fn().mockReturnThis(),
269
+ } as unknown as ExpressResponse;
270
+
271
+ nextFunction = jest.fn();
272
+ });
273
+
274
+ afterEach(() => {
275
+ jest.restoreAllMocks();
276
+ });
277
+
278
+ /*
279
+ * The suite above is only meaningful if the templates actually carry the
280
+ * shapes it replays. Pin that here so a template refactor that drops all
281
+ * variables (or all metrics) turns this red instead of quietly making
282
+ * every case above pass on an empty loop.
283
+ */
284
+ describe("fixture invariants", () => {
285
+ it("still ships templates with telemetry-attribute variables and charted metrics", async () => {
286
+ const kubernetes: JSONObject = loadTemplate(
287
+ DashboardTemplateType.Kubernetes,
288
+ );
289
+
290
+ expect(
291
+ collectVariableAttributeKeys(kubernetes).length,
292
+ ).toBeGreaterThanOrEqual(1);
293
+ expect(
294
+ collectStringsUnderKey(kubernetes, "metricName").length,
295
+ ).toBeGreaterThanOrEqual(1);
296
+
297
+ const totalVariableKeys: number = TEMPLATE_TYPES.reduce(
298
+ (total: number, type: DashboardTemplateType): number => {
299
+ return (
300
+ total + collectVariableAttributeKeys(loadTemplate(type)).length
301
+ );
302
+ },
303
+ 0,
304
+ );
305
+
306
+ const totalGroupByKeys: number = TEMPLATE_TYPES.reduce(
307
+ (total: number, type: DashboardTemplateType): number => {
308
+ return (
309
+ total +
310
+ collectStringsUnderKey(loadTemplate(type), "groupByAttributeKeys")
311
+ .length
312
+ );
313
+ },
314
+ 0,
315
+ );
316
+
317
+ expect(totalVariableKeys).toBeGreaterThanOrEqual(1);
318
+ expect(totalGroupByKeys).toBeGreaterThanOrEqual(1);
319
+ });
320
+ });
321
+
322
+ describe("every shipped template still works when published", () => {
323
+ it.each(TEMPLATE_TYPES)(
324
+ "serves the %s template's own variables, metrics and groupings",
325
+ async (templateType: DashboardTemplateType) => {
326
+ const config: JSONObject = loadTemplate(templateType);
327
+
328
+ dashboard.dashboardViewConfig =
329
+ config as unknown as DashboardViewConfig;
330
+
331
+ const variableKeys: Array<string> =
332
+ collectVariableAttributeKeys(config);
333
+ const metricNames: Array<string> = collectStringsUnderKey(
334
+ config,
335
+ "metricName",
336
+ );
337
+ const groupByKeys: Array<string> = collectStringsUnderKey(
338
+ config,
339
+ "groupByAttributeKeys",
340
+ );
341
+
342
+ let requests: number = 0;
343
+
344
+ // 1. Every variable dropdown must still be able to load its options.
345
+ for (const attributeKey of variableKeys) {
346
+ jest.clearAllMocks();
347
+
348
+ await callAttributeValues(attributeKey);
349
+
350
+ expectNoError(`${templateType} variable "${attributeKey}"`);
351
+ expect(
352
+ TelemetryAttributeService.fetchAttributeValues,
353
+ ).toHaveBeenCalledTimes(1);
354
+ requests++;
355
+ }
356
+
357
+ // 2. Every charted metric must still aggregate.
358
+ for (const metricName of metricNames) {
359
+ jest.clearAllMocks();
360
+
361
+ await callAggregate({ query: { name: metricName } });
362
+
363
+ expectNoError(`${templateType} metric "${metricName}"`);
364
+ expect(MetricService.aggregateBy).toHaveBeenCalledTimes(1);
365
+ requests++;
366
+ }
367
+
368
+ /*
369
+ * 3. Every configured grouping must still be accepted, on a metric
370
+ * the same dashboard charts.
371
+ */
372
+ if (metricNames.length > 0) {
373
+ for (const groupByKey of groupByKeys) {
374
+ jest.clearAllMocks();
375
+
376
+ await callAggregate({
377
+ query: { name: metricNames[0] as string },
378
+ groupByAttributeKeys: [groupByKey],
379
+ });
380
+
381
+ expectNoError(`${templateType} grouping "${groupByKey}"`);
382
+ expect(MetricService.aggregateBy).toHaveBeenCalledTimes(1);
383
+ requests++;
384
+ }
385
+
386
+ /*
387
+ * 4. The interpolation case: once a viewer picks a value from a
388
+ * template variable, the client sends it as an attribute filter
389
+ * on a key the widgets never stored themselves.
390
+ */
391
+ for (const attributeKey of variableKeys) {
392
+ jest.clearAllMocks();
393
+
394
+ await callAggregate({
395
+ query: {
396
+ name: metricNames[0] as string,
397
+ attributes: { [attributeKey]: "selected-value" },
398
+ },
399
+ });
400
+
401
+ expectNoError(
402
+ `${templateType} interpolated filter "${attributeKey}"`,
403
+ );
404
+ expect(MetricService.aggregateBy).toHaveBeenCalledTimes(1);
405
+ requests++;
406
+ }
407
+ }
408
+
409
+ // The template must actually have exercised something.
410
+ expect(requests).toBeGreaterThan(0);
411
+ },
412
+ );
413
+ });
414
+
415
+ describe("templates do not become readers for anything else", () => {
416
+ it("refuses a sensitive attribute key on every template", async () => {
417
+ for (const templateType of TEMPLATE_TYPES) {
418
+ const config: JSONObject = loadTemplate(templateType);
419
+
420
+ dashboard.dashboardViewConfig =
421
+ config as unknown as DashboardViewConfig;
422
+
423
+ for (const attributeKey of ["user.email", "db.statement"]) {
424
+ jest.clearAllMocks();
425
+
426
+ await callAttributeValues(attributeKey);
427
+
428
+ expect(nextFunction).toHaveBeenCalled();
429
+ expect(
430
+ TelemetryAttributeService.fetchAttributeValues,
431
+ ).not.toHaveBeenCalled();
432
+ }
433
+ }
434
+ });
435
+
436
+ it("refuses an uncharted metric on every template", async () => {
437
+ for (const templateType of TEMPLATE_TYPES) {
438
+ const config: JSONObject = loadTemplate(templateType);
439
+
440
+ dashboard.dashboardViewConfig =
441
+ config as unknown as DashboardViewConfig;
442
+
443
+ jest.clearAllMocks();
444
+
445
+ await callAggregate({ query: { name: "billing.revenue.total" } });
446
+
447
+ expect(nextFunction).toHaveBeenCalled();
448
+ expect(MetricService.aggregateBy).not.toHaveBeenCalled();
449
+ }
450
+ });
451
+
452
+ it("refuses an unconfigured grouping on every template that charts a metric", async () => {
453
+ for (const templateType of TEMPLATE_TYPES) {
454
+ const config: JSONObject = loadTemplate(templateType);
455
+ const metricNames: Array<string> = collectStringsUnderKey(
456
+ config,
457
+ "metricName",
458
+ );
459
+
460
+ if (metricNames.length === 0) {
461
+ continue;
462
+ }
463
+
464
+ dashboard.dashboardViewConfig =
465
+ config as unknown as DashboardViewConfig;
466
+
467
+ jest.clearAllMocks();
468
+
469
+ await callAggregate({
470
+ query: { name: metricNames[0] as string },
471
+ groupByAttributeKeys: ["enduser.id"],
472
+ });
473
+
474
+ expect(nextFunction).toHaveBeenCalled();
475
+ expect(MetricService.aggregateBy).not.toHaveBeenCalled();
476
+ }
477
+ });
478
+
479
+ it("refuses an attribute filter on a key no widget or variable uses", async () => {
480
+ for (const templateType of TEMPLATE_TYPES) {
481
+ const config: JSONObject = loadTemplate(templateType);
482
+ const metricNames: Array<string> = collectStringsUnderKey(
483
+ config,
484
+ "metricName",
485
+ );
486
+
487
+ if (metricNames.length === 0) {
488
+ continue;
489
+ }
490
+
491
+ dashboard.dashboardViewConfig =
492
+ config as unknown as DashboardViewConfig;
493
+
494
+ jest.clearAllMocks();
495
+
496
+ await callAggregate({
497
+ query: {
498
+ name: metricNames[0] as string,
499
+ attributes: { "user.email": { _type: "StartsWith", value: "a" } },
500
+ },
501
+ });
502
+
503
+ expect(nextFunction).toHaveBeenCalled();
504
+ expect(MetricService.aggregateBy).not.toHaveBeenCalled();
505
+ }
506
+ });
507
+ });
508
+
509
+ describe("cross-route isolation", () => {
510
+ /*
511
+ * The two routes read two different parts of the config. A key that
512
+ * unlocks one must not unlock the other.
513
+ */
514
+ const CHART_METRIC: string = "system.cpu.utilization";
515
+ const VARIABLE_KEY: string = "resource.k8s.cluster.name";
516
+ const GROUP_BY_KEY: string = "resource.host.name";
517
+
518
+ type SetMixedDashboardFunction = () => void;
519
+
520
+ const setMixedDashboard: SetMixedDashboardFunction = (): void => {
521
+ dashboard.dashboardViewConfig = {
522
+ _type: "DashboardViewConfig",
523
+ heightInDashboardUnits: 24,
524
+ variables: [
525
+ {
526
+ id: ObjectID.generate().toString(),
527
+ name: "cluster",
528
+ label: "Cluster",
529
+ type: DashboardVariableType.TelemetryAttribute,
530
+ attributeKey: VARIABLE_KEY,
531
+ },
532
+ ],
533
+ components: [
534
+ {
535
+ componentId: ObjectID.generate().toString(),
536
+ componentType: "Chart",
537
+ arguments: {
538
+ metricQueryConfigs: [
539
+ {
540
+ metricQueryData: {
541
+ filterData: { metricName: CHART_METRIC },
542
+ groupByAttributeKeys: [GROUP_BY_KEY],
543
+ },
544
+ },
545
+ ],
546
+ },
547
+ },
548
+ ],
549
+ } as unknown as DashboardViewConfig;
550
+ };
551
+
552
+ it("does not let a chart's group-by key become a readable variable attribute", async () => {
553
+ setMixedDashboard();
554
+
555
+ await callAttributeValues(GROUP_BY_KEY);
556
+
557
+ expect(nextFunction).toHaveBeenCalled();
558
+ expect(
559
+ TelemetryAttributeService.fetchAttributeValues,
560
+ ).not.toHaveBeenCalled();
561
+ });
562
+
563
+ it("does not let a variable's attribute key become a group-by dimension", async () => {
564
+ setMixedDashboard();
565
+
566
+ await callAggregate({
567
+ query: { name: CHART_METRIC },
568
+ groupByAttributeKeys: [VARIABLE_KEY],
569
+ });
570
+
571
+ expect(nextFunction).toHaveBeenCalled();
572
+ expect(MetricService.aggregateBy).not.toHaveBeenCalled();
573
+ });
574
+
575
+ it("serves the variable key on attribute-values (positive control)", async () => {
576
+ setMixedDashboard();
577
+
578
+ await callAttributeValues(VARIABLE_KEY);
579
+
580
+ expectNoError("variable key");
581
+ expect(
582
+ TelemetryAttributeService.fetchAttributeValues,
583
+ ).toHaveBeenCalledTimes(1);
584
+ });
585
+
586
+ it("serves the chart's group-by key on metrics-aggregate (positive control)", async () => {
587
+ setMixedDashboard();
588
+
589
+ await callAggregate({
590
+ query: { name: CHART_METRIC },
591
+ groupByAttributeKeys: [GROUP_BY_KEY],
592
+ });
593
+
594
+ expectNoError("chart group-by key");
595
+ expect(MetricService.aggregateBy).toHaveBeenCalledTimes(1);
596
+ });
597
+ });
598
+ });