diff --git a/snuba/admin/dist/index.html b/snuba/admin/dist/index.html index e4efa0c6301..a39a843f287 100644 --- a/snuba/admin/dist/index.html +++ b/snuba/admin/dist/index.html @@ -5,33 +5,7 @@ Snuba admin - - + diff --git a/snuba/admin/static/body.tsx b/snuba/admin/static/body.tsx index 6c7b74bcc93..5b1213c348e 100644 --- a/snuba/admin/static/body.tsx +++ b/snuba/admin/static/body.tsx @@ -20,8 +20,8 @@ function Body(props: Props) { } const bodyStyle = { - width: "100%", - maxWidth: "calc(100% - 260px)", + flex: 1, + minWidth: 0, margin: 10, fontSize: 20, }; diff --git a/snuba/admin/static/common/components/theme.tsx b/snuba/admin/static/common/components/theme.tsx index 8fe75e4e927..dfc05af57ff 100644 --- a/snuba/admin/static/common/components/theme.tsx +++ b/snuba/admin/static/common/components/theme.tsx @@ -1,10 +1,14 @@ import { EditorView } from "@codemirror/view"; import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; import { tags } from "@lezer/highlight"; +import { COLORS } from "SnubaAdmin/theme"; export const theme = EditorView.theme({ "&": { fontSize: "14px", + backgroundColor: COLORS.PANEL_BG, + border: `1px solid ${COLORS.BORDER_GRAY}`, + borderRadius: "4px", }, }); diff --git a/snuba/admin/static/copy_tables/index.tsx b/snuba/admin/static/copy_tables/index.tsx index 50ee032c04a..5d8cff2801a 100644 --- a/snuba/admin/static/copy_tables/index.tsx +++ b/snuba/admin/static/copy_tables/index.tsx @@ -300,7 +300,7 @@ const copyTableResultStyle = { const sectionContainerStyle = { padding: 15, - backgroundColor: COLORS.WHITE, + backgroundColor: COLORS.PANEL_BG, border: `1px solid ${COLORS.BORDER_GRAY}`, borderRadius: 5, marginTop: 0, diff --git a/snuba/admin/static/data.tsx b/snuba/admin/static/data.tsx index f66c0529b61..366c3352d70 100644 --- a/snuba/admin/static/data.tsx +++ b/snuba/admin/static/data.tsx @@ -16,87 +16,137 @@ import RpcEndpoints from "SnubaAdmin/rpc_endpoints"; import EapStats from "SnubaAdmin/eap_stats"; const NAV_ITEMS = [ - { id: "overview", display: "🤿 Snuba Admin", component: Welcome }, { - id: "auto-replacements-bypass-projects", - display: "👻 Replacements", - component: AutoReplacementsBypassProjects, - }, - { - id: "snql-to-sql", - display: "🌐 SnQL to SQL", - component: SnQLToSQL, - }, - { - id: "snuba-explain", - display: "🩺 Snubsplain", - component: SnubaExplain, + id: "overview", + display: "🤿 Snuba Admin", + description: "", + component: Welcome, }, { id: "clusters", display: "🗄️ ClickHouse Clusters", + description: + "Inspect the ClickHouse clusters, nodes and storages in this region.", component: Clusters, }, { id: "system-queries", display: "🏚️ System Queries", + description: + "Run predefined queries against ClickHouse system tables.", component: ClickhouseQueries, }, { id: "system-shell", display: "💻 System Shell", + description: + "Interactive SQL shell for querying ClickHouse system tables.", component: SystemShellPage, }, { id: "copy-tables", display: "📋 Copy Tables", + description: + "Copy table definitions from a source ClickHouse host to other hosts.", component: CopyTables, }, { id: "clickhouse-migrations", display: "🚧 ClickHouse Migrations", + description: + "Check migration status and run or reverse migrations by group.", component: ClickhouseMigrations, }, { id: "tracing", display: "🔎 ClickHouse Tracing", + description: + "Run a query with tracing enabled and inspect ClickHouse's trace logs.", component: TracingQueries, }, { id: "tracing-shell", display: "💻 Tracing Shell", + description: + "Interactive SQL shell that runs queries with ClickHouse tracing.", component: SQLShellPage, }, { id: "rpc-endpoints", display: "🔌 RPC Endpoints", + description: + "Send requests to EAP RPC endpoints and inspect the responses.", component: RpcEndpoints, }, { id: "querylog", display: "🔍 ClickHouse Querylog", + description: + "Search the Snuba querylog for what ran, who ran it and how it performed.", component: QuerylogQueries, }, - { - id: "eap-stats", - display: "📊 EAP Stats", - component: EapStats, - }, { id: "outcomes-analyzer", display: "📈 Outcomes Analyzer", + description: + "Query outcomes to see accepted, filtered and dropped events.", component: OutcomesAnalyzer, }, { id: "production-queries", display: "🔦 Production Queries", + description: + "Run SnQL queries against production data for allowed projects.", component: ProductionQueries, }, { id: "run-custom-jobs", display: "▶️ View/Run Custom Jobs", + description: + "List manual jobs, check their status and run them.", component: ViewCustomJobs, }, + { + id: "snuba-explain", + display: "🩺 Snubsplain", + description: + "Step through how Snuba processes a query, from parsing to the final SQL.", + component: SnubaExplain, + }, + { + id: "snql-to-sql", + display: "🌐 SnQL to SQL", + description: + "Translate a SnQL query into the ClickHouse SQL that Snuba would run.", + component: SnQLToSQL, + }, + { + id: "eap-stats", + display: "📊 EAP Stats", + description: + "Sample EAP data to see item and attribute coverage.", + component: EapStats, + }, + { + id: "auto-replacements-bypass-projects", + display: "👻 Replacements", + description: + "See which projects are currently bypassing the replacements pipeline.", + component: AutoReplacementsBypassProjects, + }, ]; -export { NAV_ITEMS }; +// Shell pages inherit permissions from their parent pages +const PERMISSION_OVERRIDES: Record = { + "tracing-shell": "tracing", + "system-shell": "system-queries", +}; + +function isToolAllowed(itemId: string, allowedTools: string[] | null): boolean { + const permissionId = PERMISSION_OVERRIDES[itemId] ?? itemId; + return ( + allowedTools?.includes(permissionId) || allowedTools?.includes("all") || false + ); +} + +export { NAV_ITEMS, isToolAllowed }; diff --git a/snuba/admin/static/header.tsx b/snuba/admin/static/header.tsx index 6e39d905f38..9203a8fb6aa 100644 --- a/snuba/admin/static/header.tsx +++ b/snuba/admin/static/header.tsx @@ -26,11 +26,13 @@ function Header() { return (
- Snuba admin + + Snuba admin +
{ + function onHashChange() { + try { + setActiveTab(getTab(window.location.hash)); + } catch {} + } + window.addEventListener("hashchange", onHashChange); + return () => window.removeEventListener("hashchange", onHashChange); + }, []); + function navigate(nextTab: string) { setActiveTab(nextTab); window.location.hash = nextTab; @@ -74,10 +91,19 @@ function App() { return ( -
+
-
diff --git a/snuba/admin/static/nav.tsx b/snuba/admin/static/nav.tsx index 3b089077692..d11ec4bb268 100644 --- a/snuba/admin/static/nav.tsx +++ b/snuba/admin/static/nav.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { COLORS } from "SnubaAdmin/theme"; -import { NAV_ITEMS } from "SnubaAdmin/data"; +import { NAV_ITEMS, isToolAllowed } from "SnubaAdmin/data"; import Client from "SnubaAdmin/api_client"; type NavProps = { @@ -29,11 +29,7 @@ function Nav(props: NavProps) {