Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b3b15da
feat(admin): Add terminal-style SQL Shell for ClickHouse tracing
phacops Jan 25, 2026
5a36b61
feat(admin): Add System Shell using shared SQL Shell component
phacops Jan 25, 2026
8ee1fd6
refactor(admin): Rename SQL Shell to Tracing Shell
phacops Jan 25, 2026
7821943
style(admin): Make shell take full available screen height
phacops Jan 25, 2026
87bec79
style(admin): Remove duplicate title from shell pages
phacops Jan 25, 2026
02b1d0e
style(admin): Remove page titles and improve active nav highlighting
phacops Jan 25, 2026
fee2ccc
fix(admin): Shell pages inherit permissions from parent pages
phacops Jan 25, 2026
c836c04
style(admin): Improve shell visual design
phacops Jan 25, 2026
4a88fea
style(admin): reduce padding around SQL shells
phacops Jan 25, 2026
32fd6bc
feat(admin): add TAB autocomplete for storage and host selection
phacops Jan 25, 2026
91a9543
feat(admin): show autocomplete suggestions above input box
phacops Jan 25, 2026
1eaaae5
fix(admin): properly extract error messages from API responses
phacops Jan 25, 2026
aa31318
feat(admin): add standard SQL shell keyboard shortcuts
phacops Jan 25, 2026
1b68435
fix(admin): add defensive checks in result rendering
phacops Jan 25, 2026
973c0e1
fix(admin): add defensive checks to trace output components
phacops Jan 25, 2026
602b29b
fix(admin): handle object errors in ErrorOutput component
phacops Jan 25, 2026
cfe590b
fix(admin): strip stack trace from ClickHouse error messages
phacops Jan 25, 2026
bfd1a4e
feat(admin): add virtualized scrollback for SQL shell terminal
phacops Jan 26, 2026
78408a6
fix(admin): make navigation sidebar scrollable
phacops Jan 26, 2026
463d679
feat(admin): add multi-line input support to SQL shell
phacops Jan 26, 2026
80ccdb9
feat(admin): add SQL syntax highlighting to shell input
phacops Jan 26, 2026
042da22
feat(admin): make SQL shell results compact and collapsible
phacops Jan 26, 2026
9f6f84b
refactor(admin): extract command parser into extensible module
phacops Jan 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions snuba/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"test": "jest"
},
"dependencies": {
"@tanstack/react-virtual": "^3.0.0",
"@codemirror/commands": "^6.6.0",
"@codemirror/lang-sql": "^6.7.0",
"@codemirror/language": "^6.0.0",
Expand Down
5 changes: 2 additions & 3 deletions snuba/admin/static/body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ function Body(props: Props) {

return (
<div style={bodyStyle}>
<div style={{ marginBottom: 20 }}>{activeItem.display}</div>
<activeItem.component {...rest} />
</div>
);
}

const bodyStyle = {
width: "100%",
maxWidth: "calc(100% - 290px)",
margin: 20,
maxWidth: "calc(100% - 260px)",
margin: 10,
fontSize: 20,
};

Expand Down
11 changes: 11 additions & 0 deletions snuba/admin/static/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AuditLog from "SnubaAdmin/runtime_config/auditlog";
import ClickhouseMigrations from "SnubaAdmin/clickhouse_migrations";
import ClickhouseQueries from "SnubaAdmin/clickhouse_queries";
import TracingQueries from "SnubaAdmin/tracing";
import SQLShellPage, { SystemShellPage } from "SnubaAdmin/sql_shell";
import SnQLToSQL from "SnubaAdmin/snql_to_sql";
import Kafka from "SnubaAdmin/kafka";
import QuerylogQueries from "SnubaAdmin/querylog";
Expand Down Expand Up @@ -53,6 +54,11 @@ const NAV_ITEMS = [
display: "🏚️ System Queries",
component: ClickhouseQueries,
},
{
id: "system-shell",
display: "💻 System Shell",
component: SystemShellPage,
},
{
id: "clickhouse-migrations",
display: "🚧 ClickHouse Migrations",
Expand All @@ -63,6 +69,11 @@ const NAV_ITEMS = [
display: "🔎 ClickHouse Tracing",
component: TracingQueries,
},
{
id: "tracing-shell",
display: "💻 Tracing Shell",
component: SQLShellPage,
},
{
id: "rpc-endpoints",
display: "🔌 RPC Endpoints",
Expand Down
2 changes: 2 additions & 0 deletions snuba/admin/static/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const containerStyle = {
const bodyStyle = {
flexGrow: 1,
display: "flex",
minHeight: 0,
overflow: "hidden",
};

let client = Client();
Expand Down
25 changes: 19 additions & 6 deletions snuba/admin/static/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ function Nav(props: NavProps) {
return (
<nav style={navStyle}>
<ul style={ulStyle}>
{NAV_ITEMS.map((item) =>
allowedTools?.includes(item.id) || allowedTools?.includes("all") ? (
{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") ? (
item.id === active ? (
<li key={item.id} >
<a style={{ color: COLORS.TEXT_DEFAULT, ...linkStyle }} className="nav-link-active">
<a style={{ ...linkStyle, ...activeLinkStyle }} className="nav-link-active">
{item.display}
</a>
</li>
Expand All @@ -49,16 +53,18 @@ function Nav(props: NavProps) {
)
) : (
<div key={item.id} />
)
)}
);
})}
</ul>
</nav>
);
}

const navStyle = {
const navStyle: React.CSSProperties = {
borderRight: `1px solid ${COLORS.NAV_BORDER}`,
width: 250,
overflowY: "auto",
flexShrink: 0,
};

const ulStyle = {
Expand All @@ -74,4 +80,11 @@ const linkStyle = {
padding: 20,
};

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

export default Nav;
145 changes: 145 additions & 0 deletions snuba/admin/static/sql_shell/command_parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { ParsedCommand, ShellMode } from "./types";

/**
* Command definition for the shell parser.
* Each command specifies a pattern to match and how to parse it.
*/
interface CommandDefinition {
/** Regex pattern to match the command (case-insensitive) */
pattern: RegExp;
/** Parse the matched input and return a ParsedCommand */
parse: (match: RegExpMatchArray, input: string) => ParsedCommand;
/** Which modes this command is available in (default: both) */
modes?: ShellMode[];
}

/**
* Registry of all shell commands.
* Commands are matched in order, so more specific patterns should come first.
* Add new commands here to extend the shell.
*/
const COMMANDS: CommandDefinition[] = [
// USE <storage> - Select a storage
{
pattern: /^USE\s+(.+)$/i,
parse: (match) => ({
type: "use",
storage: match[1].trim(),
}),
},

// HOST <host:port> - Select target host (system mode only)
{
pattern: /^HOST\s+(.+)$/i,
parse: (match) => {
const hostStr = match[1].trim();
const hostMatch = hostStr.match(/^([^:]+):(\d+)$/);
if (hostMatch) {
return {
type: "host",
host: hostMatch[1],
port: parseInt(hostMatch[2], 10),
};
}
// Default port if not specified
return { type: "host", host: hostStr, port: 9000 };
},
modes: ["system"],
},

// SHOW STORAGES - List available storages
{
pattern: /^SHOW\s+STORAGES$/i,
parse: () => ({ type: "show_storages" }),
},

// SHOW HOSTS - List available hosts (system mode only)
{
pattern: /^SHOW\s+HOSTS$/i,
parse: () => ({ type: "show_hosts" }),
modes: ["system"],
},

// PROFILE ON/OFF - Toggle profile event collection (tracing mode only)
{
pattern: /^PROFILE\s+(ON|OFF)$/i,
parse: (match) => ({
type: "profile",
enabled: match[1].toUpperCase() === "ON",
}),
modes: ["tracing"],
},

// TRACE RAW/FORMATTED - Toggle trace output format (tracing mode only)
{
pattern: /^TRACE\s+(RAW|FORMATTED)$/i,
parse: (match) => ({
type: "trace_mode",
formatted: match[1].toUpperCase() === "FORMATTED",
}),
modes: ["tracing"],
},

// SUDO ON/OFF - Toggle sudo mode (system mode only)
{
pattern: /^SUDO\s+(ON|OFF)$/i,
parse: (match) => ({
type: "sudo",
enabled: match[1].toUpperCase() === "ON",
}),
modes: ["system"],
},

// HELP - Show help message
{
pattern: /^HELP$/i,
parse: () => ({ type: "help" }),
},

// CLEAR - Clear terminal output
{
pattern: /^CLEAR$/i,
parse: () => ({ type: "clear" }),
},
];

/**
* Parse user input into a structured command.
* Commands are matched against the registry in order.
* If no command matches, the input is treated as a SQL query.
*
* @param input - The raw user input
* @param mode - The current shell mode (tracing or system)
* @returns The parsed command
*/
export function parseCommand(input: string, mode: ShellMode): ParsedCommand {
const trimmed = input.trim();

for (const cmd of COMMANDS) {
// Skip commands not available in current mode
if (cmd.modes && !cmd.modes.includes(mode)) {
continue;
}

const match = trimmed.match(cmd.pattern);
if (match) {
return cmd.parse(match, trimmed);
}
}

// Default: treat as SQL query
return { type: "sql", query: trimmed };
}

/**
* Get list of commands available in a given mode.
* Useful for generating help text or autocomplete suggestions.
*
* @param mode - The shell mode
* @returns Array of pattern strings for available commands
*/
export function getAvailableCommands(mode: ShellMode): string[] {
return COMMANDS
.filter((cmd) => !cmd.modes || cmd.modes.includes(mode))
.map((cmd) => cmd.pattern.source.replace(/\^|\$|\\/g, "").replace(/\(\.\+\)/g, "<arg>"));
}
30 changes: 30 additions & 0 deletions snuba/admin/static/sql_shell/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import Client from "SnubaAdmin/api_client";
import SQLShell from "SnubaAdmin/sql_shell/shell";

interface ShellPageProps {
api: Client;
}

const shellWrapperStyle = {
height: "calc(100vh - 75px)",
};

function SQLShellPage({ api }: ShellPageProps) {
return (
<div style={shellWrapperStyle}>
<SQLShell api={api} mode="tracing" />
</div>
);
}

function SystemShellPage({ api }: ShellPageProps) {
return (
<div style={shellWrapperStyle}>
<SQLShell api={api} mode="system" />
</div>
);
}

export default SQLShellPage;
export { SystemShellPage };
Loading
Loading