Skip to content
28 changes: 1 addition & 27 deletions snuba/admin/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snuba admin</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<style>
html, body, #root {margin: 0; height: 100%}
body {
font-family: -apple-system,BlinkMacSystemFont,sans-serif;
-webkit-font-smoothing: antialiased;
}

fieldset {
border: none;
padding: 0;
margin: 0 0 10px 0;
}

/* Hack since we can't put hover/active pseudo selectors in inline styles */
.nav-link:hover {
background-color: #efefef;
}
.nav-link:active {
background-color: #cfcfcf;
}

.nav-link-active:hover {
background-color: #efefef;
}

</style>
<!-- imported css in index.tsx, so esbuild builds a bundle.css next to bundle.js -->
<!-- css imported in index.tsx (bootstrap and static/styles.css) is bundled by esbuild into bundle.css -->
<link rel="stylesheet" href="./static/bundle.css" />
</head>
<body>
Expand Down
4 changes: 2 additions & 2 deletions snuba/admin/static/body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ function Body(props: Props) {
}

const bodyStyle = {
width: "100%",
maxWidth: "calc(100% - 260px)",
flex: 1,
minWidth: 0,
margin: 10,
fontSize: 20,
};
Expand Down
4 changes: 4 additions & 0 deletions snuba/admin/static/common/components/theme.tsx
Original file line number Diff line number Diff line change
@@ -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",
},
});

Expand Down
2 changes: 1 addition & 1 deletion snuba/admin/static/copy_tables/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
90 changes: 70 additions & 20 deletions snuba/admin/static/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {
"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 };
13 changes: 8 additions & 5 deletions snuba/admin/static/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ function Header() {

return (
<header style={headerStyle}>
<img
style={{ height: "100%" }}
src="./static/snuba.svg"
alt="Snuba admin"
/>
<a href="#overview" style={{ height: "100%" }}>
<img
style={{ height: "100%" }}
src="./static/snuba.svg"
alt="Snuba admin"
/>
</a>
<div
style={{
...regionBarStyle,
Expand All @@ -51,6 +53,7 @@ const headerStyle = {
alignItems: "center",
padding: "10px 20px",
justifyContent: "space-between",
borderBottom: `1px solid ${COLORS.NAV_BORDER}`,
};

const adminTextStyle = {
Expand Down
32 changes: 29 additions & 3 deletions snuba/admin/static/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom/client";

import * as Sentry from "@sentry/react";
Expand All @@ -8,14 +8,19 @@ import Body from "SnubaAdmin/body";
import { NAV_ITEMS } from "SnubaAdmin/data";
import Client from "SnubaAdmin/api_client";
import { MantineProvider } from "@mantine/core";
import { COLORS, sloganTiling } from "SnubaAdmin/theme";
import { ShellStateProvider } from "SnubaAdmin/sql_shell/shell_context";

import 'bootstrap/dist/css/bootstrap.min.css';
// Imported after bootstrap so these rules take precedence.
import './styles.css';

const containerStyle = {
display: "flex",
flexDirection: "column" as const,
height: "100%",
backgroundColor: COLORS.PAGE_BG,
color: COLORS.TEXT_DEFAULT,
};

const bodyStyle = {
Expand Down Expand Up @@ -66,6 +71,18 @@ function App() {
}
}

// Pages can link to each other with plain hrefs (e.g. the overview tiles),
// so keep the active tab in sync with the hash.
useEffect(() => {
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;
Expand All @@ -74,10 +91,19 @@ function App() {
return (
<MantineProvider withGlobalStyles withNormalizeCSS>
<ShellStateProvider>
<div style={containerStyle}>
<div
style={{
...containerStyle,
// The slogan tiling is only shown on the home page.
backgroundImage:
activeTab === "overview" ? sloganTiling() : undefined,
}}
>
<Header />
<div style={bodyStyle}>
<Nav active={activeTab} navigate={navigate} api={client} />
{activeTab !== "overview" && (
<Nav active={activeTab} navigate={navigate} api={client} />
)}
{activeTab && <Body active={activeTab} api={client} />}
</div>
</div>
Expand Down
13 changes: 5 additions & 8 deletions snuba/admin/static/nav.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -29,11 +29,7 @@ function Nav(props: NavProps) {
<nav style={navStyle}>
<ul style={ulStyle}>
{NAV_ITEMS.map((item) => {
// Shell pages inherit permissions from their parent pages
const permissionId = item.id === "tracing-shell" ? "tracing"
: item.id === "system-shell" ? "system-queries"
: item.id;
return allowedTools?.includes(permissionId) || allowedTools?.includes("all") ? (
return isToolAllowed(item.id, allowedTools) ? (
item.id === active ? (
<li key={item.id} >
<a style={{ ...linkStyle, ...activeLinkStyle }} className="nav-link-active">
Expand Down Expand Up @@ -61,6 +57,7 @@ function Nav(props: NavProps) {
}

const navStyle: React.CSSProperties = {
backgroundColor: COLORS.NAV_BG,
borderRight: `1px solid ${COLORS.NAV_BORDER}`,
width: 250,
overflowY: "auto",
Expand All @@ -82,8 +79,8 @@ const linkStyle = {

const activeLinkStyle = {
color: COLORS.TEXT_DEFAULT,
backgroundColor: "rgba(59, 130, 246, 0.15)",
borderLeft: "3px solid #3b82f6",
backgroundColor: "rgba(31, 111, 235, 0.12)",
borderLeft: `3px solid ${COLORS.SNUBA_BLUE}`,
fontWeight: "bold" as const,
};

Expand Down
Loading
Loading