import { useCallback, useEffect, useState } from 'react'
import {
Network,
Database,
Boxes,
RefreshCw,
Loader2,
DollarSign,
ShoppingCart,
Users,
Activity,
Layers,
ShieldCheck,
ShieldAlert,
Server,
HardDrive,
Play,
} from 'lucide-react'
import { cn } from '../../lib/utils'
type Bucket = { key: string; count: number; value?: number }
export type SubTab = 'federated' | 'lake' | 'dictionary'
const COLORS = ['#34d399', '#60a5fa', '#f472b6', '#fbbf24', '#a78bfa', '#22d3ee', '#fb7185']
function fmtNum(n?: number | string | null) {
if (n == null) return '—'
const v = typeof n === 'number' ? n : Number(n)
if (Number.isNaN(v)) return String(n)
if (Math.abs(v) >= 1e9) return `${(v / 1e9).toFixed(2)}B`
if (Math.abs(v) >= 1e6) return `${(v / 1e6).toFixed(1)}M`
if (Math.abs(v) >= 1e3) return `${(v / 1e3).toFixed(1)}K`
return String(v)
}
const fmtMoney = (n?: number | string | null) => (n == null ? '—' : `€${fmtNum(n)}`)
function Panel({ title, subtitle, icon: Icon, children }: { title: string; subtitle?: string; icon?: typeof Database; children: React.ReactNode }) {
return (
{Icon && }
{title}
{subtitle && {subtitle} }
{children}
)
}
function BarsH({ data, valueKind, colorByIndex }: { data?: Bucket[]; valueKind?: 'money' | 'num'; colorByIndex?: boolean }) {
const d = data || []
const useVal = valueKind != null
const max = Math.max(1, ...d.map((x) => (useVal && x.value != null ? x.value : x.count)))
if (!d.length) return No data
return (
{d.map((x, i) => {
const metric = useVal && x.value != null ? x.value : x.count
const pct = Math.max(2, (metric / max) * 100)
const label = useVal && x.value != null ? (valueKind === 'money' ? fmtMoney(x.value) : fmtNum(x.value)) : fmtNum(x.count)
return (
)
})}
)
}
function Donut({ data }: { data?: Bucket[] }) {
const d = data || []
const total = d.reduce((s, x) => s + x.count, 0) || 1
let acc = 0
const r = 42
const c = 2 * Math.PI * r
if (!d.length) return No data
return (
{d.map((x, i) => {
const dash = (x.count / total) * c
const seg =
acc += dash
return seg
})}
{d.slice(0, 7).map((x, i) => (
{x.key ?? '—'}
{((x.count / total) * 100).toFixed(0)}%
))}
)
}
function Kpi({ icon: Icon, label, value, sub, accent }: { icon: typeof Users; label: string; value: string; sub?: string; accent: string }) {
return (
{label}
{value}
{sub &&
{sub}
}
)
}
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)
const [dict, setDict] = useState(null)
const [loading, setLoading] = useState(false)
const [matRunning, setMatRunning] = useState(false)
const loadFederated = useCallback(async () => {
setLoading(true)
try {
const [c, m] = await Promise.all([
fetch('/api/federated/catalogs').then((r) => (r.ok ? r.json() : null)),
fetch('/api/federated/marquee').then((r) => (r.ok ? r.json() : null)),
])
setCatalogs(c)
setMarquee(m)
} catch { /* */ } finally { setLoading(false) }
}, [])
const loadLake = useCallback(async () => {
setLoading(true)
try {
const r = await fetch('/api/federated/lake')
if (r.ok) setLake(await r.json())
const ms = await fetch('/api/federated/materialize/status').then((x) => (x.ok ? x.json() : null))
setMatRunning(!!ms?.running)
} catch { /* */ } finally { setLoading(false) }
}, [])
const loadDict = useCallback(async () => {
setLoading(true)
try {
const r = await fetch('/api/federated/dictionary')
if (r.ok) setDict(await r.json())
} catch { /* */ } finally { setLoading(false) }
}, [])
useEffect(() => {
if (tab === 'federated') loadFederated()
else if (tab === 'lake') loadLake()
else loadDict()
}, [tab, loadFederated, loadLake, loadDict])
// poll marquee while it is computing
useEffect(() => {
if (tab !== 'federated' || !marquee?.running) return
const t = setTimeout(loadFederated, 5000)
return () => clearTimeout(t)
}, [tab, marquee, loadFederated])
// poll materialize while running
useEffect(() => {
if (tab !== 'lake' || !matRunning) return
const t = setTimeout(loadLake, 6000)
return () => clearTimeout(t)
}, [tab, matRunning, loadLake])
const rebuildLake = async () => {
await fetch('/api/federated/materialize', { method: 'POST' })
setMatRunning(true)
setTimeout(loadLake, 2000)
}
const refreshMarquee = async () => {
await fetch('/api/federated/marquee/refresh', { method: 'POST' })
setTimeout(loadFederated, 1500)
}
const m = marquee?.marquee
const totals = catalogs?.source_totals || {}
const mxRows: any[] = m?.matrix?.rows || []
const mxMax = Math.max(1, ...mxRows.map((r) => Number(r.records) || 0))
const mxCatalogs: string[] = m?.matrix?.catalogs || ['postgres_sales', 'mysql_hr', 'mongodb_supplychain', 'cassandra_telemetry', 'iceberg']
const body = (
<>
{/* ───────── FEDERATED ───────── */}
{tab === 'federated' && (
<>
{(catalogs?.catalogs || []).map((c: any) => (
{c.label}
{c.catalog}
{c.desc}
{c.rows != null && {fmtNum(c.rows)} rows }
))}
A single Trino query fanned out to five engines at once — relational, document, wide-column and the Hadoop lakehouse — no copies, no ETL.
{mxCatalogs.map((c) => (
{c}
))}
{marquee?.running ? : } re-run
{m?.matrix?.sql}
{mxRows.length ? (
Source
Catalog · dataset
Records
Headline metric
{mxRows.map((r, i) => {
const isMoney = /revenue|value/i.test(r.metric_label || '')
const pct = Math.max(3, (Number(r.records) / mxMax) * 100)
const color = COLORS[i % COLORS.length]
return (
{r.source}
{r.catalog} · {r.dataset}
{isMoney ? fmtMoney(r.metric) : fmtNum(r.metric)} {r.metric_label}
)
})}
) : (
{marquee?.running ? <> Federating across all databases…> : 'No result yet — click re-run.'}
)}
{(m?.catalogs || ['postgres_sales', 'mysql_hr', 'mongodb_supplychain']).map((c: string) => (
{c}
))}
{marquee?.running ? : } re-run
{marquee?.sql || m?.sql}
{marquee?.running && !m?.rows?.length ? (
Federating across live sources… (~1–2 min, cached afterwards)
) : m?.rows?.length ? (
Region
Orders
Revenue
HR events
Supply events
{m.rows.map((r: any, i: number) => (
{r.region}
{fmtNum(r.orders)}
{fmtMoney(r.revenue)}
{fmtNum(r.hr_events)}
{fmtNum(r.supply_events)}
))}
{m.generated_at &&
as of {new Date(m.generated_at).toLocaleString()} · joined live across PostgreSQL + MySQL + MongoDB
}
) : (
{m?.error || 'No result yet — click re-run.'}
)}
>
)}
{/* ───────── HADOOP LAKE ───────── */}
{tab === 'lake' && (
<>
All federated business data materialized as external Iceberg tables on HDFS — queried live (fast).
{matRunning ? : }
{matRunning ? 'Materializing…' : 'Rebuild external tables'}
{(lake?.tables || []).map((t: any) => (
{t.table}
{fmtNum(t.rows)}
))}
{!lake?.tables?.length &&
No external tables yet — click “Rebuild external tables”.
}
>
)}
{/* ───────── DICTIONARY ───────── */}
{tab === 'dictionary' && (
<>
This is exactly what the assistant knows about your data — every column, its type, and whether it is masked or visible.
{(dict?.tables || []).map((t: any) => (
{t.engine} · {t.desc}
{t.columns.map((c: any) => (
{c.masked && }
{!c.masked && c.pii && }
{c.name}
))}
))}
>
)}
{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 }) => (
setTab(id)}
className={cn('inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-[11px] font-medium transition-colors', tab === id ? 'bg-docker/15 text-docker' : 'text-foreground-muted hover:bg-surface-overlay')}>
{label}
))}
{body}
)
}