feat(ui): Data Sources UI — enterprise browser for all 5 source databases

New "Data Sources UI" tab with per-database Browser (catalog + sample data),
Query Console (SqlWorkbench) and embedded interactive Shell (DbShell via SSH).

Backend:
- Extend sql_console.py with Cassandra (CQL) + Neo4j (Cypher) engines
- Add GET /api/sql/catalog/{engine} and GET /api/sql/sample/{engine}
- ssh_terminal: optional initial_command for auto-launching DB CLIs

Frontend:
- DataSourcesView with 5-DB rail, health dots, Browser/Console/Shell sub-tabs
- DbShell embedded xterm terminal with docker exec CLI per engine
- Deep-link topology DB nodes to Data Sources UI (no SQL dock on platform)
- WorkbenchPanel restricted to agent mode only — frees dashboard space
This commit is contained in:
mo
2026-06-27 15:42:37 +00:00
parent b8a5b10bc4
commit a4c9b60079
10 changed files with 1027 additions and 76 deletions
+18 -5
View File
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useState } from 'react'
import { Database, Loader2, Play, Zap } from 'lucide-react'
import type { SourceEngine } from '../../lib/dataSourceCatalog'
import { cn } from '../../lib/utils'
import { subTabActive, subTabIdle } from '../../lib/tabActive'
@@ -14,7 +15,7 @@ type SqlResult = {
sql?: string
}
type Engine = 'postgres' | 'mysql' | 'mongodb' | 'trino'
type Engine = SourceEngine | 'trino'
type Props = {
engine: Engine
@@ -25,7 +26,7 @@ const ENGINE_META: Record<Engine, { title: string; sub: string; accent: string;
postgres: {
title: 'PostgreSQL SQL Console',
sub: 'DB Vault · 10.0.21.51:5432 · user mo',
accent: 'text-blue-400',
accent: 'text-sky-400',
queryLabel: 'SQL',
},
mysql: {
@@ -40,6 +41,18 @@ const ENGINE_META: Record<Engine, { title: string; sub: string; accent: string;
accent: 'text-emerald-400',
queryLabel: 'Command',
},
cassandra: {
title: 'Cassandra CQL Console',
sub: 'DB Vault · 10.0.21.51:9042 · keyspace telemetry',
accent: 'text-cyan-400',
queryLabel: 'CQL',
},
neo4j: {
title: 'Neo4j Cypher Console',
sub: 'DB Vault · 10.0.21.51:7687 · graph database',
accent: 'text-pink-400',
queryLabel: 'Cypher',
},
trino: {
title: 'Trino SQL Console',
sub: 'Lakehouse · 10.0.21.50:8089 · federated queries',
@@ -51,7 +64,7 @@ const ENGINE_META: Record<Engine, { title: string; sub: string; accent: string;
export function SqlWorkbench({ engine, compact }: Props) {
const meta = ENGINE_META[engine]
const [samples, setSamples] = useState<Sample[]>([])
const [sql, setSql] = useState('SELECT version();')
const [sql, setSql] = useState('')
const [result, setResult] = useState<SqlResult | null>(null)
const [benchmark, setBenchmark] = useState<{
comparison?: { postgres_ms?: number; trino_ms?: number; faster?: string; speedup_factor?: number }
@@ -146,7 +159,7 @@ export function SqlWorkbench({ engine, compact }: Props) {
<div className="flex min-h-0 flex-1 overflow-hidden">
<aside className={cn('flex min-h-0 shrink-0 flex-col border-r border-border/60 p-1.5', compact ? 'w-44' : 'w-52')}>
<p className="mb-0.5 text-[8px] font-semibold uppercase tracking-wider text-foreground-faint">10 demo commands</p>
<p className="mb-0.5 text-[8px] font-semibold uppercase tracking-wider text-foreground-faint">Demo queries</p>
<div className="scrollbar-thin min-h-0 flex-1 space-y-0.5 overflow-y-auto">
{samples.map((s) => (
<button
@@ -167,7 +180,7 @@ export function SqlWorkbench({ engine, compact }: Props) {
onChange={(e) => setSql(e.target.value)}
className={cn(
'shrink-0 resize-y border-b border-border/60 bg-[#0d1117] p-1.5 font-mono text-[10px] text-emerald-100 outline-none focus:ring-1 focus:ring-docker/40',
compact ? 'min-h-[48px] max-h-[96px]' : 'min-h-[56px] max-h-[120px]',
compact ? 'min-h-[48px] max-h-[96px]' : 'min-h-[72px] max-h-[140px]',
)}
spellCheck={false}
placeholder={`${meta.queryLabel}`}