diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 9f0022b..e105178 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -19,7 +19,6 @@ import { ChangesView } from './components/features/ChangesView' import { DataFlowView } from './components/features/DataFlowView' import { SearchView } from './components/features/SearchView' import { DataExplorerView } from './components/features/DataExplorerView' -import { TrinoFederationView } from './components/features/TrinoFederationView' import { DataHubView } from './components/features/DataHubView' import { SshTerminal } from './components/features/SshTerminal' import { TerminalDock } from './components/features/TerminalDock' @@ -145,8 +144,6 @@ export default function App() { ) : cc.mainView === 'dataexplorer' ? ( - ) : cc.mainView === 'trino' ? ( - ) : ( )} diff --git a/ui/src/components/features/DataExplorerView.tsx b/ui/src/components/features/DataExplorerView.tsx index 5a011e0..5df36c6 100644 --- a/ui/src/components/features/DataExplorerView.tsx +++ b/ui/src/components/features/DataExplorerView.tsx @@ -11,9 +11,22 @@ import { Search, Loader2, TrendingUp, - ExternalLink, + BarChart3, + Network, + HardDrive, + ShieldCheck, } from 'lucide-react' import { cn } from '../../lib/utils' +import { TrinoFederationView, type SubTab } from './TrinoFederationView' + +type ExplorerTab = 'business' | SubTab + +const TABS: { id: ExplorerTab; label: string; icon: typeof Users; hint: string }[] = [ + { id: 'business', label: 'Business Overview', icon: BarChart3, hint: 'Customers, orders, workforce, supply chain & telemetry across every source' }, + { id: 'federated', label: 'Federated (Trino)', icon: Network, hint: 'One SQL engine joining PostgreSQL, MySQL & MongoDB live — region scorecard' }, + { id: 'lake', label: 'Hadoop Lake', icon: HardDrive, hint: 'All business data mirrored as external Iceberg tables on HDFS' }, + { id: 'dictionary', label: 'Data Dictionary', icon: ShieldCheck, hint: 'Every table & column with PII / masking status — exactly what the assistant sees' }, +] type Bucket = { key: string; count: number; value?: number } type Business = { @@ -195,6 +208,7 @@ export function DataExplorerView() { const [region, setRegion] = useState('') const [qInput, setQInput] = useState('') const [q, setQ] = useState('') + const [view, setView] = useState('business') const load = useCallback(async () => { setLoading(true) @@ -213,39 +227,63 @@ export function DataExplorerView() { const k = data?.kpis const regions = useMemo(() => data?.regions || [], [data]) + const activeTab = TABS.find((t) => t.id === view) || TABS[0] return (
{/* header */}
-
+

Data Explorer

-

- Your actual business data — customers, orders, employees, supply chain & telemetry across all sources -

-
-
-
{ e.preventDefault(); setQ(qInput.trim()) }} - className="flex items-center gap-1 rounded-md border border-border bg-surface px-2 py-1" - > - - setQInput(e.target.value)} - placeholder="Filter by name, id, region…" - className="w-48 bg-transparent text-[11px] text-foreground outline-none placeholder:text-foreground-faint" - /> - - +

{activeTab.hint}

+ {view === 'business' && ( +
+
{ e.preventDefault(); setQ(qInput.trim()) }} + className="flex items-center gap-1 rounded-md border border-border bg-surface px-2 py-1" + > + + setQInput(e.target.value)} + placeholder="Filter by name, id, region…" + className="w-48 bg-transparent text-[11px] text-foreground outline-none placeholder:text-foreground-faint" + /> + + +
+ )}
+ {/* tab bar */} +
+ {TABS.map(({ id, label, icon: Icon }) => ( + + ))} +
+ + {/* Trino federation / lake / dictionary tabs */} + {view !== 'business' && } + + {/* ───────── BUSINESS OVERVIEW ───────── */} + {view === 'business' && ( + <> {/* region filters */}
Region @@ -318,6 +356,8 @@ export function DataExplorerView() {
+ + )}
) } diff --git a/ui/src/components/features/TrinoFederationView.tsx b/ui/src/components/features/TrinoFederationView.tsx index 65d65a1..4fb76d9 100644 --- a/ui/src/components/features/TrinoFederationView.tsx +++ b/ui/src/components/features/TrinoFederationView.tsx @@ -19,7 +19,7 @@ import { import { cn } from '../../lib/utils' type Bucket = { key: string; count: number; value?: number } -type SubTab = 'federated' | 'lake' | 'dictionary' +export type SubTab = 'federated' | 'lake' | 'dictionary' const COLORS = ['#34d399', '#60a5fa', '#f472b6', '#fbbf24', '#a78bfa', '#22d3ee', '#fb7185'] @@ -117,8 +117,9 @@ function Kpi({ icon: Icon, label, value, sub, accent }: { icon: typeof Users; la ) } -export function TrinoFederationView() { - const [tab, setTab] = useState('federated') +export function TrinoFederationView({ embedded = false, activeTab }: { embedded?: boolean; activeTab?: SubTab } = {}) { + const [tabState, setTab] = useState('federated') + const tab = embedded ? activeTab ?? 'federated' : tabState const [catalogs, setCatalogs] = useState(null) const [marquee, setMarquee] = useState(null) const [lake, setLake] = useState(null) @@ -189,32 +190,8 @@ export function TrinoFederationView() { const m = marquee?.marquee const totals = catalogs?.source_totals || {} - return ( -
-
-
-

- - Trino Federation & Hadoop Lakehouse -

-

- One SQL engine over every source — federated business analytics, mirrored into Hadoop as external Iceberg tables -

-
-
- {([ - { id: 'federated', label: 'Federated', icon: Network }, - { id: 'lake', label: 'Hadoop Lake', icon: HardDrive }, - { id: 'dictionary', label: 'Data Dictionary', icon: ShieldCheck }, - ] as { id: SubTab; label: string; icon: typeof Network }[]).map(({ id, label, icon: Icon }) => ( - - ))} -
-
- + const body = ( + <> {/* ───────── FEDERATED ───────── */} {tab === 'federated' && ( <> @@ -368,6 +345,37 @@ export function TrinoFederationView() { {loading && !catalogs && !lake && !dict && (
)} + + ) + + if (embedded) return body + + return ( +
+
+
+

+ + Trino Federation & Hadoop Lakehouse +

+

+ One SQL engine over every source — federated business analytics, mirrored into Hadoop as external Iceberg tables +

+
+
+ {([ + { id: 'federated', label: 'Federated', icon: Network }, + { id: 'lake', label: 'Hadoop Lake', icon: HardDrive }, + { id: 'dictionary', label: 'Data Dictionary', icon: ShieldCheck }, + ] as { id: SubTab; label: string; icon: typeof Network }[]).map(({ id, label, icon: Icon }) => ( + + ))} +
+
+ {body}
) } diff --git a/ui/src/components/layout/SideNav.tsx b/ui/src/components/layout/SideNav.tsx index 5b9c08e..8070b4b 100644 --- a/ui/src/components/layout/SideNav.tsx +++ b/ui/src/components/layout/SideNav.tsx @@ -1,5 +1,4 @@ -import { useEffect, useState } from 'react' -import { Database, DatabaseZap, HardDrive, Search, LayoutDashboard, MessageSquare, Server, TerminalSquare, Activity, GitBranch, ExternalLink, LineChart, Network } from 'lucide-react' +import { Database, DatabaseZap, HardDrive, Search, LayoutDashboard, MessageSquare, TerminalSquare, Activity, GitBranch, LineChart } from 'lucide-react' import type { GpuStatus, WorkloadData } from '../../types' import type { GpuLiveMetrics } from '../../hooks/useLiveMetrics' import { cn } from '../../lib/utils' @@ -7,7 +6,7 @@ import { viewTabActive, viewTabIdle } from '../../lib/tabActive' import { GpuMatrixPanel } from '../features/GpuMatrixPanel' import { LabHealthPanel } from '../features/LabHealthPanel' -type MainView = 'platform' | 'dataquality' | 'knowledge' | 'storage' | 'hdfs' | 'search' | 'approvals' | 'changes' | 'dataflow' | 'datasources' | 'dataexplorer' | 'trino' +type MainView = 'platform' | 'dataquality' | 'knowledge' | 'storage' | 'hdfs' | 'search' | 'approvals' | 'changes' | 'dataflow' | 'datasources' | 'dataexplorer' type Props = { workload: WorkloadData | null @@ -27,13 +26,12 @@ const VIEWS: { id: MainView; label: string; icon: typeof LayoutDashboard }[] = [ { id: 'platform', label: 'Data Platform', icon: LayoutDashboard }, { id: 'datasources', label: 'Data Hub', icon: Database }, { id: 'dataexplorer', label: 'Data Explorer', icon: LineChart }, - { id: 'trino', label: 'Trino Federation', icon: Network }, { id: 'changes', label: 'Live Changes', icon: Activity }, { id: 'dataflow', label: 'Data Flow', icon: GitBranch }, { id: 'dataquality', label: 'Data Quality', icon: DatabaseZap }, { id: 'knowledge', label: 'Knowledge Chat', icon: MessageSquare }, { id: 'storage', label: 'Object Storage', icon: HardDrive }, - { id: 'search', label: 'Elasticsearch', icon: Search }, + { id: 'search', label: 'Elasticsearch', icon: Search }, ] export function SideNav({ @@ -50,13 +48,6 @@ export function SideNav({ onOpenSsh, }: Props) { const matrixBoost = gpuBoost || mainView === 'knowledge' - const [kibanaUrl, setKibanaUrl] = useState(null) - useEffect(() => { - fetch('/api/search/health') - .then((r) => (r.ok ? r.json() : null)) - .then((j) => { if (j?.kibana?.ui_url) setKibanaUrl(j.kibana.ui_url) }) - .catch(() => {}) - }, []) return (
diff --git a/ui/src/hooks/useCommandCenter.ts b/ui/src/hooks/useCommandCenter.ts index 85db1b7..46f656d 100644 --- a/ui/src/hooks/useCommandCenter.ts +++ b/ui/src/hooks/useCommandCenter.ts @@ -60,7 +60,7 @@ export function useCommandCenter() { const [selectedNode, setSelectedNode] = useState(null) const [nodeDetail, setNodeDetail] = useState(null) const [nodeBusy, setNodeBusy] = useState(false) - const [mainView, setMainView] = useState<'platform' | 'approvals' | 'dataquality' | 'knowledge' | 'storage' | 'hdfs' | 'search' | 'changes' | 'dataflow' | 'datasources' | 'dataexplorer' | 'trino'>('platform') + const [mainView, setMainView] = useState<'platform' | 'approvals' | 'dataquality' | 'knowledge' | 'storage' | 'hdfs' | 'search' | 'changes' | 'dataflow' | 'datasources' | 'dataexplorer'>('platform') const [changes, setChanges] = useState([]) const [genPulse, setGenPulse] = useState(false) const genPulseTimer = useRef | null>(null)