@oneuptime/common 12.0.5 → 12.0.6

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 (528) hide show
  1. package/Models/DatabaseModels/AIRun.ts +33 -0
  2. package/Models/DatabaseModels/DataSource.ts +744 -0
  3. package/Models/DatabaseModels/Index.ts +3 -0
  4. package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
  5. package/Models/DatabaseModels/User.ts +2 -0
  6. package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
  8. package/Models/DatabaseModels/UserSession.ts +2 -0
  9. package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
  10. package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
  11. package/Server/API/AIInsightAPI.ts +47 -4
  12. package/Server/API/AIInvestigationAPI.ts +25 -0
  13. package/Server/API/DashboardAPI.ts +176 -492
  14. package/Server/API/DataSourceAPI.ts +268 -0
  15. package/Server/API/StatusPageAPI.ts +11 -1
  16. package/Server/API/UserAPI.ts +119 -0
  17. package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  22. package/Server/Infrastructure/Queue.ts +26 -0
  23. package/Server/Infrastructure/QueueWorker.ts +17 -0
  24. package/Server/Middleware/TelemetryIngest.ts +8 -3
  25. package/Server/Services/AIInsightService.ts +132 -12
  26. package/Server/Services/AIRunService.ts +34 -0
  27. package/Server/Services/AIService.ts +17 -2
  28. package/Server/Services/AlertService.ts +38 -32
  29. package/Server/Services/AnalyticsDatabaseService.ts +8 -4
  30. package/Server/Services/DataSourceService.ts +200 -0
  31. package/Server/Services/HostService.ts +92 -0
  32. package/Server/Services/IncidentService.ts +45 -0
  33. package/Server/Services/Index.ts +2 -0
  34. package/Server/Services/MonitorService.ts +49 -0
  35. package/Server/Services/MonitorStatusService.ts +166 -0
  36. package/Server/Services/OpenTelemetryIngestService.ts +255 -184
  37. package/Server/Services/RumSessionPinService.ts +7 -21
  38. package/Server/Services/StatusPageSubscriberService.ts +13 -0
  39. package/Server/Services/UserService.ts +280 -9
  40. package/Server/Static/Vendor/README.md +62 -0
  41. package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
  42. package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
  43. package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
  44. package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
  45. package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
  46. package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
  47. package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
  48. package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
  49. package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
  50. package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
  51. package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
  52. package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
  53. package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
  54. package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
  55. package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
  56. package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
  57. package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
  58. package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
  59. package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
  60. package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
  61. package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
  62. package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
  63. package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
  64. package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
  65. package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
  66. package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
  67. package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
  68. package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
  69. package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
  70. package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
  71. package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
  72. package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
  73. package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
  74. package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
  75. package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
  76. package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
  77. package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
  78. package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
  79. package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
  80. package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
  81. package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
  82. package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
  83. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +2 -2
  84. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
  85. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
  86. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
  87. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
  88. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
  89. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
  90. package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
  91. package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
  92. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
  93. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
  94. package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
  95. package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
  96. package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
  97. package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
  98. package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
  99. package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
  100. package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
  101. package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
  102. package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
  103. package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
  104. package/Server/Utils/DataSource/EgressGuard.ts +446 -0
  105. package/Server/Utils/DataSource/HttpFetch.ts +221 -0
  106. package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
  107. package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
  108. package/Server/Utils/DataSource/Types.ts +93 -0
  109. package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
  110. package/Server/Utils/InProcessMemo.ts +141 -0
  111. package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
  112. package/Server/Utils/Monitor/MonitorMetricUtil.ts +58 -0
  113. package/Server/Utils/Monitor/MonitorResource.ts +14 -0
  114. package/Server/Utils/Response.ts +12 -1
  115. package/Server/Utils/SSRFProtection.ts +237 -0
  116. package/Server/Utils/StartServer.ts +8 -0
  117. package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
  118. package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
  119. package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
  120. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
  121. package/Server/Utils/Telemetry/Telemetry.ts +112 -1
  122. package/Server/Utils/Telemetry.ts +104 -3
  123. package/Server/Utils/VendorAssets.ts +165 -0
  124. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
  125. package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
  126. package/Server/Views/Partials/Head.ejs +21 -7
  127. package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
  128. package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
  129. package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
  130. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
  131. package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
  132. package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
  133. package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
  134. package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
  135. package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
  136. package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
  137. package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
  138. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
  139. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
  140. package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
  141. package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
  142. package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
  143. package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
  144. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
  145. package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
  146. package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
  147. package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
  148. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
  149. package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
  150. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
  151. package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
  152. package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
  153. package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
  154. package/Tests/Server/Services/AIInsightService.test.ts +266 -11
  155. package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
  156. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +4 -0
  157. package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
  158. package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
  159. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
  160. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
  161. package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
  162. package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
  163. package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
  164. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
  165. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
  166. package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
  167. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
  168. package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
  169. package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
  170. package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
  171. package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
  172. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +2 -2
  173. package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
  174. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
  175. package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
  176. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
  177. package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
  178. package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
  179. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
  180. package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
  181. package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
  182. package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
  183. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
  184. package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
  185. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
  186. package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
  187. package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
  188. package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
  189. package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
  190. package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
  191. package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
  192. package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
  193. package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
  194. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
  195. package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
  196. package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
  197. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
  198. package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
  199. package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
  200. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
  201. package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
  202. package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
  203. package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
  204. package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
  205. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
  206. package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
  207. package/Tests/Server/Utils/Telemetry.test.ts +87 -0
  208. package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
  209. package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
  210. package/Tests/Server/Views/OfflineRender.test.ts +297 -0
  211. package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
  212. package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
  213. package/Tests/Types/Password.test.ts +363 -0
  214. package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
  215. package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
  216. package/Tests/UI/Components/Modal.test.tsx +281 -1
  217. package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
  218. package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
  219. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
  220. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  221. package/Tests/Utils/Analytics.test.ts +23 -0
  222. package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
  223. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
  224. package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
  225. package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
  226. package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
  227. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
  228. package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
  229. package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
  230. package/Types/AI/AIInsightEvidence.ts +6 -0
  231. package/Types/Analytics/RevenueEvent.ts +31 -0
  232. package/Types/Dashboard/DashboardComponentType.ts +31 -0
  233. package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
  234. package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
  235. package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
  236. package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
  237. package/Types/DataSource/DataSourceLimits.ts +62 -0
  238. package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
  239. package/Types/DataSource/DataSourceTableResult.ts +38 -0
  240. package/Types/DataSource/DataSourceType.ts +213 -0
  241. package/Types/Date.ts +43 -3
  242. package/Types/Password.ts +64 -0
  243. package/Types/Permission.ts +46 -0
  244. package/Types/UserAuthenticationStatus.ts +34 -0
  245. package/UI/Components/Button/Button.tsx +1 -1
  246. package/UI/Components/ErrorBoundary.tsx +459 -108
  247. package/UI/Components/Icon/Icon.tsx +10 -9
  248. package/UI/Components/Modal/ConfirmModal.tsx +6 -1
  249. package/UI/Components/Modal/Modal.tsx +267 -35
  250. package/UI/Components/Modal/ModalFooter.tsx +6 -1
  251. package/UI/Components/MoreMenu/MoreMenu.tsx +19 -4
  252. package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
  253. package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
  254. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
  255. package/UI/Components/StatusPage/ResourceList.tsx +570 -0
  256. package/UI/Styles/Theme.css +18 -0
  257. package/UI/Utils/API/RequestOptions.ts +7 -0
  258. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
  259. package/UI/Utils/ModelAPI/ModelAPI.ts +1 -0
  260. package/UI/Utils/TestDataSource.ts +61 -0
  261. package/UI/esbuild-config.js +45 -0
  262. package/Utils/Analytics.ts +15 -0
  263. package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
  264. package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
  265. package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
  266. package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
  267. package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
  268. package/Utils/Dashboard/Components/Index.ts +28 -0
  269. package/Utils/DataSource/DataSourceQueryText.ts +94 -0
  270. package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
  271. package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
  272. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
  273. package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
  274. package/Utils/StatusPage/ResourceExplorer.ts +853 -0
  275. package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
  276. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  277. package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
  278. package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
  279. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  280. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
  282. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/User.js +2 -0
  284. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
  286. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
  288. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
  290. package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
  292. package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
  294. package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
  295. package/build/dist/Server/API/AIInsightAPI.js +34 -4
  296. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  297. package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
  298. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  299. package/build/dist/Server/API/DashboardAPI.js +119 -433
  300. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  301. package/build/dist/Server/API/DataSourceAPI.js +173 -0
  302. package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
  303. package/build/dist/Server/API/StatusPageAPI.js +11 -1
  304. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  305. package/build/dist/Server/API/UserAPI.js +94 -2
  306. package/build/dist/Server/API/UserAPI.js.map +1 -1
  307. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +49 -0
  308. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  309. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
  310. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
  311. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
  312. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
  313. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
  314. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
  315. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  316. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  317. package/build/dist/Server/Infrastructure/Queue.js +27 -0
  318. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  319. package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
  320. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  321. package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
  322. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  323. package/build/dist/Server/Services/AIInsightService.js +110 -10
  324. package/build/dist/Server/Services/AIInsightService.js.map +1 -1
  325. package/build/dist/Server/Services/AIRunService.js +34 -0
  326. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  327. package/build/dist/Server/Services/AIService.js +16 -2
  328. package/build/dist/Server/Services/AIService.js.map +1 -1
  329. package/build/dist/Server/Services/AlertService.js +28 -35
  330. package/build/dist/Server/Services/AlertService.js.map +1 -1
  331. package/build/dist/Server/Services/AnalyticsDatabaseService.js +8 -4
  332. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  333. package/build/dist/Server/Services/DataSourceService.js +159 -0
  334. package/build/dist/Server/Services/DataSourceService.js.map +1 -0
  335. package/build/dist/Server/Services/HostService.js +74 -0
  336. package/build/dist/Server/Services/HostService.js.map +1 -1
  337. package/build/dist/Server/Services/IncidentService.js +35 -24
  338. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  339. package/build/dist/Server/Services/Index.js +2 -0
  340. package/build/dist/Server/Services/Index.js.map +1 -1
  341. package/build/dist/Server/Services/MonitorService.js +45 -0
  342. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  343. package/build/dist/Server/Services/MonitorStatusService.js +129 -0
  344. package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
  345. package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
  346. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  347. package/build/dist/Server/Services/RumSessionPinService.js +7 -15
  348. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  349. package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
  350. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  351. package/build/dist/Server/Services/UserService.js +241 -10
  352. package/build/dist/Server/Services/UserService.js.map +1 -1
  353. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
  354. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
  356. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
  358. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
  360. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  361. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +2 -2
  362. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  363. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
  364. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  365. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
  366. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
  368. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  369. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
  370. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
  371. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
  372. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  373. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
  374. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  375. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
  376. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
  377. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
  378. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
  379. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
  380. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
  381. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
  382. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
  383. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
  384. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
  385. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
  386. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
  387. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
  388. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
  389. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
  390. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
  391. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
  392. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
  393. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
  394. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
  395. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
  396. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
  397. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
  398. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
  399. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
  400. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
  401. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
  402. package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
  403. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
  404. package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
  405. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
  406. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
  407. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
  408. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
  409. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
  410. package/build/dist/Server/Utils/DataSource/Types.js +2 -0
  411. package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
  412. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
  413. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
  414. package/build/dist/Server/Utils/InProcessMemo.js +113 -0
  415. package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
  416. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
  417. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
  418. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +34 -0
  419. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  420. package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
  421. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  422. package/build/dist/Server/Utils/Response.js +9 -1
  423. package/build/dist/Server/Utils/Response.js.map +1 -1
  424. package/build/dist/Server/Utils/SSRFProtection.js +192 -0
  425. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
  426. package/build/dist/Server/Utils/StartServer.js +7 -0
  427. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  428. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
  429. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
  430. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
  431. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
  432. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
  433. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
  434. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
  435. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
  436. package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
  437. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  438. package/build/dist/Server/Utils/Telemetry.js +93 -6
  439. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  440. package/build/dist/Server/Utils/VendorAssets.js +121 -0
  441. package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
  442. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
  443. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  444. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
  445. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  446. package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
  447. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
  448. package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
  449. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  450. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
  451. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
  452. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
  453. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
  454. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
  455. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
  456. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
  457. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
  458. package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
  459. package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
  460. package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
  461. package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
  462. package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
  463. package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
  464. package/build/dist/Types/DataSource/DataSourceType.js +183 -0
  465. package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
  466. package/build/dist/Types/Date.js +38 -3
  467. package/build/dist/Types/Date.js.map +1 -1
  468. package/build/dist/Types/Password.js +56 -0
  469. package/build/dist/Types/Password.js.map +1 -0
  470. package/build/dist/Types/Permission.js +42 -0
  471. package/build/dist/Types/Permission.js.map +1 -1
  472. package/build/dist/Types/UserAuthenticationStatus.js +2 -0
  473. package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
  474. package/build/dist/UI/Components/ErrorBoundary.js +388 -72
  475. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  476. package/build/dist/UI/Components/Icon/Icon.js +10 -4
  477. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  478. package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
  479. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  480. package/build/dist/UI/Components/Modal/Modal.js +178 -19
  481. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  482. package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
  483. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  484. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +9 -6
  485. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  486. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
  487. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  488. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
  489. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
  490. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
  491. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
  492. package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
  493. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
  494. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
  495. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  496. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +3 -8
  497. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  498. package/build/dist/UI/Utils/TestDataSource.js +39 -0
  499. package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
  500. package/build/dist/Utils/Analytics.js +4 -0
  501. package/build/dist/Utils/Analytics.js.map +1 -1
  502. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
  503. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
  504. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
  505. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
  506. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
  507. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
  508. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
  509. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
  510. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
  511. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
  512. package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
  513. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  514. package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
  515. package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
  516. package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
  517. package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
  518. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
  519. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
  520. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
  521. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
  522. package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
  523. package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
  524. package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
  525. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
  526. package/jest.config.json +1 -0
  527. package/package.json +5 -1
  528. package/tsconfig.json +12 -10
@@ -0,0 +1,808 @@
1
+ import StatusPageGroup from "../../../Models/DatabaseModels/StatusPageGroup";
2
+ import IconProp from "../../../Types/Icon/IconProp";
3
+ import StatusPageGroupViewMode from "../../../Types/StatusPage/StatusPageGroupViewMode";
4
+ import StatusPageResourceExplorerUtil, {
5
+ StatusPageResourceCountIndex,
6
+ StatusPageResourceNavigatorRow,
7
+ StatusPageResourceSelection,
8
+ StatusPageResourceSelectionType,
9
+ } from "../../../Utils/StatusPage/ResourceExplorer";
10
+ import Button, { ButtonSize, ButtonStyleType } from "../Button/Button";
11
+ import Icon from "../Icon/Icon";
12
+ import MoreMenu from "../MoreMenu/MoreMenu";
13
+ import MoreMenuItem from "../MoreMenu/MoreMenuItem";
14
+ import Tooltip from "../Tooltip/Tooltip";
15
+ import React, {
16
+ Fragment,
17
+ FunctionComponent,
18
+ ReactElement,
19
+ useRef,
20
+ } from "react";
21
+
22
+ export interface ComponentProps {
23
+ /* In render order, collapsed subtrees already removed, counts attached. */
24
+ rows: Array<StatusPageResourceNavigatorRow>;
25
+ countIndex: StatusPageResourceCountIndex;
26
+ selection: StatusPageResourceSelection;
27
+ onSelect: (selection: StatusPageResourceSelection) => void;
28
+ onToggleExpand: (statusPageGroupId: string) => void;
29
+
30
+ /* How many rows the cap is holding back, and how to ask for more. */
31
+ hiddenRowCount: number;
32
+ onShowMore: () => void;
33
+
34
+ searchText: string;
35
+
36
+ /*
37
+ * Managing the hierarchy, which used to be a separate page. Every one of
38
+ * these is optional so a screen that only browses can leave them out.
39
+ */
40
+ isCreateable?: boolean | undefined;
41
+ isEditable?: boolean | undefined;
42
+ isDeleteable?: boolean | undefined;
43
+
44
+ /* Creating a group at the top level, from the section header. */
45
+ onCreateGroup?: (() => void) | undefined;
46
+ onAddSubGroup?: ((statusPageGroup: StatusPageGroup) => void) | undefined;
47
+ onEditGroup?: ((statusPageGroup: StatusPageGroup) => void) | undefined;
48
+ onDeleteGroup?: ((statusPageGroup: StatusPageGroup) => void) | undefined;
49
+ onMoveGroupUp?: ((statusPageGroup: StatusPageGroup) => void) | undefined;
50
+ onMoveGroupDown?: ((statusPageGroup: StatusPageGroup) => void) | undefined;
51
+ onShowGroupId?: ((statusPageGroup: StatusPageGroup) => void) | undefined;
52
+
53
+ /*
54
+ * The row whose write is in flight. Its actions are disabled so a second
55
+ * click cannot queue a move on top of one that has not landed yet - the
56
+ * server renumbers siblings on every reorder, so two in flight at once
57
+ * resolve against a hierarchy neither of them saw.
58
+ */
59
+ busyGroupId?: string | null | undefined;
60
+ }
61
+
62
+ /*
63
+ * The left hand side of the Resources explorer: the group hierarchy, as
64
+ * somewhere to go AND as the thing being built.
65
+ *
66
+ * Groups used to be managed on a page of their own, which meant the two halves
67
+ * of one job - "make a section" and "put monitors in it" - lived behind
68
+ * different links, and building a status page was a loop of navigating between
69
+ * them. They are one screen now: the tree here is what an operator selects to
70
+ * fill, and the same rows are where a group is created, renamed, moved and
71
+ * deleted.
72
+ *
73
+ * Three controls per row, and they do different things. The chevron opens the
74
+ * group's sub groups; the row itself selects the group, which is what puts its
75
+ * monitors in the pane on the right; the cluster that appears on hover acts on
76
+ * the group. Keeping the first two apart is what lets an operator look inside a
77
+ * branch without losing the group they were working in.
78
+ */
79
+
80
+ /*
81
+ * An indent column is exactly as wide as a chevron, and its rail runs down the
82
+ * middle of it, so every rail lands on the centre line of the chevron of the
83
+ * group it descends from, at every depth.
84
+ */
85
+ const INDENT_COLUMN_CLASS_NAME: string = "relative w-5 flex-shrink-0";
86
+ const RAIL_CLASS_NAME: string = "absolute inset-y-0 left-2.5 w-px bg-gray-200";
87
+ /* The vertical centre of a row: py-2 (8px) plus half of its 20px line. */
88
+ const CONNECTOR_OFFSET_CLASS_NAME: string = "top-[1.125rem]";
89
+
90
+ /*
91
+ * Every control in a row's hover cluster is the same square with the same glyph
92
+ * size, hover and focus ring, because they read as one cluster and any drift
93
+ * between them is visible at a glance.
94
+ *
95
+ * Native buttons rather than <Button buttonStyle={ICON} />: the overflow trigger
96
+ * has to be one - MoreMenu only enhances a custom trigger in place when it
97
+ * already is a native button - and ButtonStyleType.ICON cannot be talked into
98
+ * matching it from a className, because its padding, its 20px glyph and its
99
+ * indigo-500 offset focus ring are all baked into the style.
100
+ */
101
+ const ROW_ACTION_CLASS_NAME: string =
102
+ "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 disabled:pointer-events-none disabled:opacity-40";
103
+
104
+ const ROW_SELECTOR: string =
105
+ "[data-testid='status-page-resource-navigator-row']";
106
+ const SELECT_SELECTOR: string =
107
+ "[data-testid='status-page-resource-navigator-select']";
108
+
109
+ const ResourceGroupNavigator: FunctionComponent<ComponentProps> = (
110
+ props: ComponentProps,
111
+ ): ReactElement => {
112
+ const isUngroupedSelected: boolean =
113
+ props.selection.type === StatusPageResourceSelectionType.Ungrouped;
114
+
115
+ /*
116
+ * Whether a row has anything to act on it with. Every one of these is a
117
+ * property of the screen rather than of a row, so it is worked out once - and
118
+ * the count needs to know, because it is drawn where the cluster lands.
119
+ */
120
+ const hasRowActions: boolean = Boolean(
121
+ (props.isCreateable && props.onAddSubGroup) ||
122
+ (props.isEditable && (props.onEditGroup || props.onMoveGroupUp)) ||
123
+ props.onShowGroupId ||
124
+ (props.isDeleteable && props.onDeleteGroup),
125
+ );
126
+
127
+ const treeRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
128
+ null,
129
+ ) as React.RefObject<HTMLDivElement>;
130
+
131
+ type OnTreeKeyDownFunction = (
132
+ event: React.KeyboardEvent<HTMLElement>,
133
+ ) => void;
134
+
135
+ /*
136
+ * The arrow keys everybody already knows from a file tree: up and down walk
137
+ * the rows that are on screen, right opens a branch and then steps into it,
138
+ * left closes the branch you are in and then climbs out of it.
139
+ *
140
+ * Without this the only way through a large hierarchy from the keyboard is
141
+ * Tab, which stops on every chevron and every hover action on the way past -
142
+ * three stops per row, and no way to skip a subtree at all.
143
+ */
144
+ const onTreeKeyDown: OnTreeKeyDownFunction = (
145
+ event: React.KeyboardEvent<HTMLElement>,
146
+ ): void => {
147
+ const navigationKeys: Array<string> = [
148
+ "ArrowDown",
149
+ "ArrowUp",
150
+ "ArrowRight",
151
+ "ArrowLeft",
152
+ "Home",
153
+ "End",
154
+ ];
155
+
156
+ if (!navigationKeys.includes(event.key)) {
157
+ return;
158
+ }
159
+
160
+ const rowElements: Array<HTMLElement> = Array.from(
161
+ treeRef.current?.querySelectorAll<HTMLElement>(ROW_SELECTOR) || [],
162
+ );
163
+
164
+ const activeElement: Element | null = document.activeElement;
165
+
166
+ const currentIndex: number = rowElements.findIndex(
167
+ (rowElement: HTMLElement) => {
168
+ return (
169
+ activeElement instanceof Node && rowElement.contains(activeElement)
170
+ );
171
+ },
172
+ );
173
+
174
+ if (currentIndex < 0) {
175
+ return;
176
+ }
177
+
178
+ const row: StatusPageResourceNavigatorRow | undefined =
179
+ props.rows[currentIndex];
180
+
181
+ if (!row) {
182
+ return;
183
+ }
184
+
185
+ type FocusRowFunction = (index: number) => void;
186
+
187
+ const focusRow: FocusRowFunction = (index: number): void => {
188
+ const target: HTMLElement | undefined =
189
+ rowElements[Math.max(0, Math.min(rowElements.length - 1, index))];
190
+
191
+ target?.querySelector<HTMLElement>(SELECT_SELECTOR)?.focus();
192
+ };
193
+
194
+ /*
195
+ * Claimed before anything is done with it, so the page does not scroll out
196
+ * from under a tree the operator is walking.
197
+ */
198
+ event.preventDefault();
199
+
200
+ if (event.key === "ArrowDown") {
201
+ focusRow(currentIndex + 1);
202
+ return;
203
+ }
204
+
205
+ if (event.key === "ArrowUp") {
206
+ focusRow(currentIndex - 1);
207
+ return;
208
+ }
209
+
210
+ if (event.key === "Home") {
211
+ focusRow(0);
212
+ return;
213
+ }
214
+
215
+ if (event.key === "End") {
216
+ focusRow(rowElements.length - 1);
217
+ return;
218
+ }
219
+
220
+ if (event.key === "ArrowRight") {
221
+ if (!row.hasVisibleSubGroups) {
222
+ return;
223
+ }
224
+
225
+ if (row.isExpanded) {
226
+ /* Already open, so the first child is the next row drawn. */
227
+ focusRow(currentIndex + 1);
228
+ return;
229
+ }
230
+
231
+ props.onToggleExpand(row.id);
232
+ return;
233
+ }
234
+
235
+ /* ArrowLeft: close this branch, or climb to the one that contains it. */
236
+ if (row.hasVisibleSubGroups && row.isExpanded) {
237
+ props.onToggleExpand(row.id);
238
+ return;
239
+ }
240
+
241
+ for (let index: number = currentIndex - 1; index >= 0; index--) {
242
+ if ((props.rows[index]?.depth ?? 0) < row.depth) {
243
+ focusRow(index);
244
+ return;
245
+ }
246
+ }
247
+ };
248
+
249
+ type RenderIndentFunction = (
250
+ row: StatusPageResourceNavigatorRow,
251
+ ) => ReactElement;
252
+
253
+ const renderIndent: RenderIndentFunction = (
254
+ row: StatusPageResourceNavigatorRow,
255
+ ): ReactElement => {
256
+ if (row.depth <= 0) {
257
+ return <></>;
258
+ }
259
+
260
+ return (
261
+ <Fragment>
262
+ {row.ancestorRails.map((doesRailContinue: boolean, column: number) => {
263
+ return (
264
+ <span
265
+ key={`rail-${column}`}
266
+ aria-hidden="true"
267
+ className={INDENT_COLUMN_CLASS_NAME}
268
+ data-testid="status-page-resource-navigator-rail"
269
+ data-rail-continues={doesRailContinue ? "true" : "false"}
270
+ >
271
+ {doesRailContinue ? <span className={RAIL_CLASS_NAME} /> : <></>}
272
+ </span>
273
+ );
274
+ })}
275
+ <span
276
+ aria-hidden="true"
277
+ className={INDENT_COLUMN_CLASS_NAME}
278
+ data-testid="status-page-resource-navigator-connector"
279
+ data-is-last-sibling={row.isLastVisibleSibling ? "true" : "false"}
280
+ >
281
+ <span
282
+ className={`absolute left-2.5 top-0 w-px bg-gray-200 ${
283
+ row.isLastVisibleSibling ? "h-[1.125rem]" : "bottom-0"
284
+ }`}
285
+ />
286
+ <span
287
+ className={`absolute left-2.5 h-px w-2.5 bg-gray-200 ${CONNECTOR_OFFSET_CLASS_NAME}`}
288
+ />
289
+ </span>
290
+ </Fragment>
291
+ );
292
+ };
293
+
294
+ type RenderDisclosureFunction = (
295
+ row: StatusPageResourceNavigatorRow,
296
+ ) => ReactElement;
297
+
298
+ const renderDisclosure: RenderDisclosureFunction = (
299
+ row: StatusPageResourceNavigatorRow,
300
+ ): ReactElement => {
301
+ if (!row.hasVisibleSubGroups) {
302
+ /*
303
+ * A leaf keeps the column so every name in the navigator starts on the
304
+ * same vertical line whether or not its row can be opened.
305
+ */
306
+ return (
307
+ <span
308
+ className="h-5 w-5 flex-shrink-0"
309
+ aria-hidden="true"
310
+ data-testid="status-page-resource-navigator-leaf-spacer"
311
+ />
312
+ );
313
+ }
314
+
315
+ return (
316
+ <button
317
+ type="button"
318
+ aria-label={
319
+ row.isExpanded
320
+ ? `Collapse ${row.name || "group"}`
321
+ : `Expand ${row.name || "group"}`
322
+ }
323
+ aria-expanded={row.isExpanded}
324
+ data-testid="status-page-resource-navigator-disclosure"
325
+ className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400"
326
+ onClick={() => {
327
+ props.onToggleExpand(row.id);
328
+ }}
329
+ >
330
+ <Icon
331
+ icon={row.isExpanded ? IconProp.ChevronDown : IconProp.ChevronRight}
332
+ className="h-4 w-4"
333
+ />
334
+ </button>
335
+ );
336
+ };
337
+
338
+ type RenderCountFunction = (
339
+ row: StatusPageResourceNavigatorRow,
340
+ isSelected: boolean,
341
+ ) => ReactElement;
342
+
343
+ const renderCount: RenderCountFunction = (
344
+ row: StatusPageResourceNavigatorRow,
345
+ isSelected: boolean,
346
+ ): ReactElement => {
347
+ const label: string | null =
348
+ StatusPageResourceExplorerUtil.getNavigatorCountLabel({
349
+ row: row,
350
+ countIndex: props.countIndex,
351
+ });
352
+
353
+ if (label === null) {
354
+ return <></>;
355
+ }
356
+
357
+ return (
358
+ <span
359
+ /*
360
+ * A pill rather than loose digits: a bare number at the end of a name
361
+ * reads as part of the name, which on a status page full of "Region
362
+ * 1000" and "Market 1001" is not a theoretical problem.
363
+ *
364
+ * It gives the row up to the action cluster, which is drawn over this
365
+ * end of the row - `invisible` rather than `hidden` so the name is
366
+ * truncated to the same width either way and cannot reflow under the
367
+ * pointer. The cluster's own background cannot be relied on to cover
368
+ * it: the dark theme maps indigo-50 to a translucent fill, and the
369
+ * digits read straight through it.
370
+ */
371
+ className={`flex-shrink-0 rounded-full px-1.5 py-0.5 text-[11px] font-medium tabular-nums ${
372
+ hasRowActions
373
+ ? "group-hover/nav-row:invisible group-focus-within/nav-row:invisible"
374
+ : ""
375
+ } ${
376
+ isSelected
377
+ ? hasRowActions
378
+ ? "invisible"
379
+ : "bg-indigo-100 text-indigo-700"
380
+ : row.ownResourceCount === 0
381
+ ? "bg-gray-100 text-gray-400"
382
+ : "bg-gray-100 text-gray-500"
383
+ }`}
384
+ title={StatusPageResourceExplorerUtil.getNavigatorCountTooltip({
385
+ row: row,
386
+ countIndex: props.countIndex,
387
+ })}
388
+ data-testid="status-page-resource-navigator-count"
389
+ >
390
+ {label}
391
+ </span>
392
+ );
393
+ };
394
+
395
+ type RenderActionsFunction = (
396
+ row: StatusPageResourceNavigatorRow,
397
+ isSelected: boolean,
398
+ ) => ReactElement;
399
+
400
+ /*
401
+ * The group's own actions. They sit over the right hand end of the row rather
402
+ * than beside the name, so a row is exactly as wide for its name whether or
403
+ * not the pointer is on it - a cluster that pushed the name aside on hover
404
+ * would make every name in a narrow sidebar jump as the pointer travelled
405
+ * down it.
406
+ *
407
+ * bg-inherit is what makes the overlay work: the row underneath is the thing
408
+ * carrying the hover and selected backgrounds, so the cluster is always
409
+ * painted on whatever the row currently is.
410
+ */
411
+ const renderActions: RenderActionsFunction = (
412
+ row: StatusPageResourceNavigatorRow,
413
+ isSelected: boolean,
414
+ ): ReactElement => {
415
+ const isBusy: boolean = props.busyGroupId === row.id;
416
+
417
+ const menuItems: Array<ReactElement> = [];
418
+
419
+ if (props.isEditable && props.onEditGroup) {
420
+ menuItems.push(
421
+ <MoreMenuItem
422
+ key="edit"
423
+ text="Edit group"
424
+ icon={IconProp.Edit}
425
+ isDisabled={isBusy}
426
+ onClick={() => {
427
+ props.onEditGroup?.(row.statusPageGroup);
428
+ }}
429
+ />,
430
+ );
431
+ }
432
+
433
+ if (props.isEditable && props.onMoveGroupUp) {
434
+ menuItems.push(
435
+ <MoreMenuItem
436
+ key="move-up"
437
+ text="Move up"
438
+ icon={IconProp.ArrowUp}
439
+ isDisabled={isBusy || !row.canMoveUp}
440
+ onClick={() => {
441
+ props.onMoveGroupUp?.(row.statusPageGroup);
442
+ }}
443
+ />,
444
+ );
445
+ }
446
+
447
+ if (props.isEditable && props.onMoveGroupDown) {
448
+ menuItems.push(
449
+ <MoreMenuItem
450
+ key="move-down"
451
+ text="Move down"
452
+ icon={IconProp.ArrowDown}
453
+ isDisabled={isBusy || !row.canMoveDown}
454
+ onClick={() => {
455
+ props.onMoveGroupDown?.(row.statusPageGroup);
456
+ }}
457
+ />,
458
+ );
459
+ }
460
+
461
+ if (props.onShowGroupId) {
462
+ menuItems.push(
463
+ <MoreMenuItem
464
+ key="show-id"
465
+ text="Show ID"
466
+ icon={IconProp.Info}
467
+ onClick={() => {
468
+ props.onShowGroupId?.(row.statusPageGroup);
469
+ }}
470
+ />,
471
+ );
472
+ }
473
+
474
+ if (props.isDeleteable && props.onDeleteGroup) {
475
+ menuItems.push(
476
+ <MoreMenuItem
477
+ key="delete"
478
+ text="Delete group"
479
+ icon={IconProp.Trash}
480
+ className="text-red-600 enabled:hover:bg-red-50 enabled:hover:text-red-700"
481
+ iconClassName="text-red-400 group-hover:text-red-500"
482
+ isDisabled={isBusy}
483
+ onClick={() => {
484
+ props.onDeleteGroup?.(row.statusPageGroup);
485
+ }}
486
+ />,
487
+ );
488
+ }
489
+
490
+ const canAddSubGroup: boolean = Boolean(
491
+ props.isCreateable && props.onAddSubGroup,
492
+ );
493
+
494
+ if (!canAddSubGroup && menuItems.length === 0) {
495
+ return <></>;
496
+ }
497
+
498
+ return (
499
+ <div
500
+ /*
501
+ * The selected row keeps its actions on screen. Hover is the discovery
502
+ * affordance, not the only one: a touch screen has no hover at all, and
503
+ * the row an operator is working in is precisely the one they are most
504
+ * likely to want to act on. Its count is the thing given up for it, and
505
+ * the pane beside it is already showing that number.
506
+ */
507
+ className={`absolute inset-y-0 right-0 items-center gap-0.5 rounded-r-md pl-4 pr-1 group-hover/nav-row:flex group-focus-within/nav-row:flex ${
508
+ isSelected ? "flex bg-indigo-50" : "hidden bg-gray-100"
509
+ }`}
510
+ data-testid="status-page-resource-navigator-actions"
511
+ >
512
+ {canAddSubGroup ? (
513
+ <Tooltip text="Add a sub group inside this group">
514
+ <button
515
+ type="button"
516
+ aria-label={`Add a sub group inside ${row.name || "this group"}`}
517
+ data-testid="status-page-resource-navigator-add-sub-group"
518
+ disabled={isBusy}
519
+ className={ROW_ACTION_CLASS_NAME}
520
+ onClick={() => {
521
+ props.onAddSubGroup?.(row.statusPageGroup);
522
+ }}
523
+ >
524
+ <Icon icon={IconProp.FolderPlus} className="h-3.5 w-3.5" />
525
+ </button>
526
+ </Tooltip>
527
+ ) : (
528
+ <></>
529
+ )}
530
+
531
+ {menuItems.length > 0 ? (
532
+ <MoreMenu
533
+ ariaLabel={`More actions for ${row.name || "this group"}`}
534
+ isDisabled={isBusy}
535
+ elementToBeShownInsteadOfButton={
536
+ <button
537
+ type="button"
538
+ data-testid="status-page-resource-navigator-more"
539
+ className={ROW_ACTION_CLASS_NAME}
540
+ >
541
+ <Icon icon={IconProp.EllipsisHorizontal} className="h-4 w-4" />
542
+ </button>
543
+ }
544
+ >
545
+ {menuItems}
546
+ </MoreMenu>
547
+ ) : (
548
+ <></>
549
+ )}
550
+ </div>
551
+ );
552
+ };
553
+
554
+ type RenderRowFunction = (
555
+ row: StatusPageResourceNavigatorRow,
556
+ ) => ReactElement;
557
+
558
+ const renderRow: RenderRowFunction = (
559
+ row: StatusPageResourceNavigatorRow,
560
+ ): ReactElement => {
561
+ const isSelected: boolean =
562
+ props.selection.type === StatusPageResourceSelectionType.Group &&
563
+ props.selection.statusPageGroupId === row.id;
564
+
565
+ const isBusy: boolean = props.busyGroupId === row.id;
566
+
567
+ return (
568
+ <div
569
+ key={row.id}
570
+ role="treeitem"
571
+ aria-level={row.depth + 1}
572
+ aria-posinset={row.visibleSiblingIndex + 1}
573
+ aria-setsize={row.visibleSiblingCount}
574
+ aria-expanded={row.hasVisibleSubGroups ? row.isExpanded : undefined}
575
+ aria-selected={isSelected}
576
+ aria-busy={isBusy ? true : undefined}
577
+ data-testid="status-page-resource-navigator-row"
578
+ data-group-id={row.id}
579
+ data-depth={row.depth}
580
+ data-selected={isSelected ? "true" : "false"}
581
+ /*
582
+ * A grid group lays its resources out as a matrix rather than a list,
583
+ * which is the difference between "add a monitor" and "add a monitor to
584
+ * a cell" - so the row says which it is, in its glyph and here.
585
+ */
586
+ data-view-mode={row.viewMode || StatusPageGroupViewMode.List}
587
+ className={`group/nav-row relative flex items-stretch rounded-lg transition-colors ${
588
+ isSelected
589
+ ? "bg-indigo-50"
590
+ : "hover:bg-gray-100 focus-within:bg-gray-100"
591
+ } ${isBusy ? "opacity-60" : ""}`}
592
+ >
593
+ {renderIndent(row)}
594
+
595
+ <div className="flex min-w-0 flex-1 items-center gap-1">
596
+ {renderDisclosure(row)}
597
+
598
+ <button
599
+ type="button"
600
+ data-testid="status-page-resource-navigator-select"
601
+ className={`flex min-w-0 flex-1 items-center gap-2 rounded-lg py-2 pl-1 pr-2 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 ${
602
+ isSelected ? "text-indigo-700" : "text-gray-700"
603
+ }`}
604
+ onClick={() => {
605
+ props.onSelect({
606
+ type: StatusPageResourceSelectionType.Group,
607
+ statusPageGroupId: row.id,
608
+ });
609
+ }}
610
+ >
611
+ <Icon
612
+ icon={
613
+ row.viewMode === StatusPageGroupViewMode.Grid
614
+ ? IconProp.Grid
615
+ : row.hasSubGroups && row.isExpanded
616
+ ? IconProp.FolderOpen
617
+ : IconProp.Folder
618
+ }
619
+ className={`h-4 w-4 flex-shrink-0 ${
620
+ isSelected
621
+ ? "text-indigo-500"
622
+ : row.hasSubGroups
623
+ ? "text-gray-400"
624
+ : "text-gray-300"
625
+ }`}
626
+ />
627
+
628
+ <span
629
+ className={`min-w-0 flex-1 truncate text-sm ${
630
+ isSelected ? "font-semibold" : "font-medium"
631
+ } ${
632
+ /*
633
+ * A row that is only on screen to give a match its place in the
634
+ * hierarchy is context, not a result.
635
+ */
636
+ row.isSearchMatch ? "" : "text-gray-400"
637
+ }`}
638
+ title={row.name || "Untitled group"}
639
+ data-testid="status-page-resource-navigator-name"
640
+ >
641
+ {row.name || "Untitled group"}
642
+ </span>
643
+
644
+ {renderCount(row, isSelected)}
645
+ </button>
646
+ </div>
647
+
648
+ {renderActions(row, isSelected)}
649
+ </div>
650
+ );
651
+ };
652
+
653
+ type RenderUngroupedRowFunction = () => ReactElement;
654
+
655
+ /*
656
+ * The resources that belong to no group. "Top of page" rather than
657
+ * "Uncategorized": on the public status page these are drawn first, above
658
+ * every group, so where they appear is the useful thing to know about them -
659
+ * and it is a place an operator can picture, which "uncategorized" is not.
660
+ */
661
+ const renderUngroupedRow: RenderUngroupedRowFunction = (): ReactElement => {
662
+ return (
663
+ <button
664
+ type="button"
665
+ data-testid="status-page-resource-navigator-ungrouped"
666
+ title="Resources that are not in any group. Visitors see these first, above every group."
667
+ aria-current={isUngroupedSelected ? "true" : undefined}
668
+ className={`flex w-full items-center gap-2 rounded-lg py-2 pl-2 pr-2 text-left transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 ${
669
+ isUngroupedSelected
670
+ ? "bg-indigo-50 text-indigo-700"
671
+ : "text-gray-700 hover:bg-gray-100"
672
+ }`}
673
+ onClick={() => {
674
+ props.onSelect({
675
+ type: StatusPageResourceSelectionType.Ungrouped,
676
+ statusPageGroupId: null,
677
+ });
678
+ }}
679
+ >
680
+ <Icon
681
+ icon={IconProp.List}
682
+ className={`h-4 w-4 flex-shrink-0 ${
683
+ isUngroupedSelected ? "text-indigo-500" : "text-gray-400"
684
+ }`}
685
+ />
686
+ <span
687
+ className={`min-w-0 flex-1 truncate text-sm ${
688
+ isUngroupedSelected ? "font-semibold" : "font-medium"
689
+ }`}
690
+ >
691
+ Top of page
692
+ </span>
693
+ {props.countIndex.isComplete ? (
694
+ <span
695
+ className={`flex-shrink-0 rounded-full px-1.5 py-0.5 text-[11px] font-medium tabular-nums ${
696
+ isUngroupedSelected
697
+ ? "bg-indigo-100 text-indigo-700"
698
+ : props.countIndex.ungroupedCount === 0
699
+ ? "bg-gray-100 text-gray-400"
700
+ : "bg-gray-100 text-gray-500"
701
+ }`}
702
+ title={`${props.countIndex.ungroupedCount.toLocaleString()} ${
703
+ props.countIndex.ungroupedCount === 1 ? "resource" : "resources"
704
+ } that are not in any group`}
705
+ data-testid="status-page-resource-navigator-ungrouped-count"
706
+ >
707
+ {props.countIndex.ungroupedCount.toLocaleString()}
708
+ </span>
709
+ ) : (
710
+ <></>
711
+ )}
712
+ </button>
713
+ );
714
+ };
715
+
716
+ type RenderGroupsFunction = () => ReactElement;
717
+
718
+ const renderGroups: RenderGroupsFunction = (): ReactElement => {
719
+ if (props.rows.length === 0) {
720
+ return (
721
+ <p
722
+ className="rounded-lg border border-dashed border-gray-300 px-3 py-3 text-xs text-gray-500"
723
+ role="status"
724
+ data-testid="status-page-resource-navigator-empty"
725
+ >
726
+ {props.searchText
727
+ ? `No groups match “${props.searchText}”.`
728
+ : "No groups yet. Groups split a longer status page into sections, and they can be nested."}
729
+ </p>
730
+ );
731
+ }
732
+
733
+ return (
734
+ <Fragment>
735
+ <div
736
+ ref={treeRef}
737
+ role="tree"
738
+ aria-label="Status page groups"
739
+ data-testid="status-page-resource-navigator-tree"
740
+ className="space-y-0.5"
741
+ onKeyDown={onTreeKeyDown}
742
+ >
743
+ {props.rows.map((row: StatusPageResourceNavigatorRow) => {
744
+ return renderRow(row);
745
+ })}
746
+ </div>
747
+
748
+ {props.hiddenRowCount > 0 ? (
749
+ <div
750
+ className="px-1 pt-2 [&_button]:md:ml-0"
751
+ data-testid="status-page-resource-navigator-show-more"
752
+ >
753
+ <Button
754
+ title={`Show ${Math.min(
755
+ props.hiddenRowCount,
756
+ StatusPageResourceExplorerUtil.NavigatorRowsPerPage,
757
+ ).toLocaleString()} more of ${props.hiddenRowCount.toLocaleString()}`}
758
+ icon={IconProp.ChevronDown}
759
+ buttonSize={ButtonSize.Small}
760
+ buttonStyle={ButtonStyleType.NORMAL}
761
+ onClick={props.onShowMore}
762
+ />
763
+ </div>
764
+ ) : (
765
+ <></>
766
+ )}
767
+ </Fragment>
768
+ );
769
+ };
770
+
771
+ return (
772
+ <div data-testid="status-page-resource-navigator">
773
+ {renderUngroupedRow()}
774
+
775
+ <div className="mt-3 border-t border-gray-200 pt-3">
776
+ {/*
777
+ * The section header is where a new group belongs: beside the list it
778
+ * will join, rather than in a page header two panes away from it.
779
+ */}
780
+ <div className="flex items-center justify-between gap-2 px-1.5 pb-1.5">
781
+ <p className="text-[11px] font-semibold uppercase tracking-wide text-gray-500">
782
+ Groups
783
+ </p>
784
+
785
+ {props.isCreateable && props.onCreateGroup ? (
786
+ <Tooltip text="Create a top level group">
787
+ <button
788
+ type="button"
789
+ aria-label="Create a top level group"
790
+ data-testid="status-page-resource-navigator-create-group"
791
+ className={ROW_ACTION_CLASS_NAME}
792
+ onClick={props.onCreateGroup}
793
+ >
794
+ <Icon icon={IconProp.Add} className="h-4 w-4" />
795
+ </button>
796
+ </Tooltip>
797
+ ) : (
798
+ <></>
799
+ )}
800
+ </div>
801
+
802
+ {renderGroups()}
803
+ </div>
804
+ </div>
805
+ );
806
+ };
807
+
808
+ export default ResourceGroupNavigator;