/** * Database Management Page * Multi-database comparison UI for DuckLake, PostgreSQL, TypeDB, and Oxigraph * * DuckLake is the primary analytics database (server-side DuckDB with lakehouse features) * Falls back to DuckDB-WASM for in-browser queries when server is unavailable */ import { useState } from 'react'; import { useLanguage } from '../contexts/LanguageContext'; import { DuckDBPanel } from '../components/database/DuckDBPanel'; import { DuckLakePanel } from '../components/database/DuckLakePanel'; import { PostgreSQLPanel } from '../components/database/PostgreSQLPanel'; import { TypeDBPanel } from '../components/database/TypeDBPanel'; import { OxigraphPanel } from '../components/database/OxigraphPanel'; import { useDuckDB } from '../hooks/useDuckDB'; import { useDuckLake } from '../hooks/useDuckLake'; import { usePostgreSQL } from '../hooks/usePostgreSQL'; import { useTypeDB } from '../hooks/useTypeDB'; import { useOxigraph } from '../hooks/useOxigraph'; import './Database.css'; // Database types - DuckLake is primary (with DuckDB-WASM fallback) type DatabaseType = 'duckdb' | 'postgres' | 'typedb' | 'oxigraph'; interface DatabaseInfo { id: DatabaseType; name: string; description: { nl: string; en: string }; icon: string; color: string; } // Stats interface for status cards interface DatabaseStats { isConnected: boolean; isLoading: boolean; primaryStat: number; primaryLabel: string; secondaryStat: number; secondaryLabel: string; tertiaryStat?: number; tertiaryLabel?: string; responseTimeMs?: number; } const DATABASES: DatabaseInfo[] = [ { id: 'duckdb', name: 'DuckLake', description: { nl: 'Lakehouse met tijdreizen, ACID-transacties en schema-evolutie', en: 'Lakehouse with time travel, ACID transactions, and schema evolution', }, icon: '🏔️', color: '#FFC107', }, { id: 'postgres', name: 'PostgreSQL', description: { nl: 'Relationele database voor gestructureerde erfgoeddata', en: 'Relational database for structured heritage data', }, icon: '🐘', color: '#336791', }, { id: 'typedb', name: 'TypeDB', description: { nl: 'Kennisgraaf voor complexe erfgoedrelaties', en: 'Knowledge graph for complex heritage relationships', }, icon: '🧠', color: '#6B5CE7', }, { id: 'oxigraph', name: 'Oxigraph', description: { nl: 'SPARQL-triplestore voor Linked Data', en: 'SPARQL triplestore for Linked Data', }, icon: '🔗', color: '#00A86B', }, ]; // Bilingual text object for translations const TEXT = { // Page header pageTitle: { nl: 'Database Vergelijking', en: 'Database Comparison' }, pageSubtitle: { nl: 'Vergelijk en beheer meerdere database-backends voor erfgoeddata', en: 'Compare and manage multiple database backends for heritage data', }, // Database tabs allDatabases: { nl: 'Alle databases', en: 'All Databases' }, // Status indicators connected: { nl: 'Verbonden', en: 'Connected' }, disconnected: { nl: 'Niet verbonden', en: 'Disconnected' }, loading: { nl: 'Laden...', en: 'Loading...' }, // Quick comparison quickComparison: { nl: 'Snelle vergelijking', en: 'Quick Comparison' }, database: { nl: 'Database', en: 'Database' }, status: { nl: 'Status', en: 'Status' }, dataCount: { nl: 'Data', en: 'Data' }, responseTime: { nl: 'Responstijd', en: 'Response Time' }, lastChecked: { nl: 'Laatst gecontroleerd', en: 'Last Checked' }, // Data upload uploadData: { nl: 'Data uploaden', en: 'Upload Data' }, uploadDescription: { nl: 'Upload verrijkte NDE-bestanden naar geselecteerde databases', en: 'Upload enriched NDE files to selected databases', }, // Info databaseTypes: { nl: 'Database types', en: 'Database Types' }, ducklakeDescription: { nl: 'Lakehouse - ACID transacties, tijdreizen, schema-evolutie. Server-side DuckDB met DuckLake extensie.', en: 'Lakehouse - ACID transactions, time travel, schema evolution. Server-side DuckDB with DuckLake extension.', }, relationalDescription: { nl: 'Relational - Gestructureerde data met ACID-garanties', en: 'Relational - Structured data with ACID guarantees', }, graphDescription: { nl: 'Knowledge Graph - Complexe relaties en type-inferentie', en: 'Knowledge Graph - Complex relationships and type inference', }, rdfDescription: { nl: 'RDF Triplestore - Linked Data en SPARQL-queries', en: 'RDF Triplestore - Linked Data and SPARQL queries', }, }; export function Database() { const { language } = useLanguage(); const t = (key: keyof typeof TEXT) => TEXT[key][language]; const [activeDatabase, setActiveDatabase] = useState('all'); return (

{t('pageTitle')}

{t('pageSubtitle')}

{/* Database Type Tabs */} {/* Content based on active tab */}
{activeDatabase === 'all' && } {activeDatabase === 'duckdb' && } {activeDatabase === 'postgres' && } {activeDatabase === 'typedb' && } {activeDatabase === 'oxigraph' && }
{/* Info Section */}

{t('databaseTypes')}

🏔️
DuckLake

{t('ducklakeDescription')}

🐘
PostgreSQL

{t('relationalDescription')}

🧠
TypeDB

{t('graphDescription')}

🔗
Oxigraph

{t('rdfDescription')}

); } /** * All Databases Overview View */ function AllDatabasesView({ language }: { language: 'nl' | 'en' }) { const t = (key: keyof typeof TEXT) => TEXT[key][language]; // Initialize all database hooks const duckdb = useDuckDB(); const ducklake = useDuckLake(); // Server-side DuckDB with lakehouse features const postgres = usePostgreSQL(); const typedb = useTypeDB(); const oxigraph = useOxigraph(); // Unified DuckDB stats: prefer DuckLake (server) if connected, fallback to WASM const duckdbConnected = ducklake.status.isConnected || duckdb.status.isConnected; const duckdbLoading = ducklake.isLoading || duckdb.isLoading; // Use DuckLake stats if available (production data), otherwise use WASM stats const duckdbTables = ducklake.status.isConnected ? (ducklake.stats?.totalTables ?? 0) : (duckdb.stats?.totalTables ?? 0); const duckdbRows = ducklake.status.isConnected ? (ducklake.stats?.totalRows ?? 0) : (duckdb.stats?.totalRows ?? 0); const duckdbSnapshots = ducklake.stats?.totalSnapshots ?? 0; const duckdbResponseTime = ducklake.status.isConnected ? ducklake.status.responseTimeMs : duckdb.status.responseTimeMs; // Create a map of database stats const dbStats: Record = { duckdb: { isConnected: duckdbConnected, isLoading: duckdbLoading, primaryStat: duckdbTables, primaryLabel: language === 'nl' ? 'tabellen' : 'tables', secondaryStat: duckdbRows, secondaryLabel: language === 'nl' ? 'rijen' : 'rows', // Show snapshots if DuckLake is connected (lakehouse feature) tertiaryStat: ducklake.status.isConnected ? duckdbSnapshots : undefined, tertiaryLabel: ducklake.status.isConnected ? 'snapshots' : undefined, responseTimeMs: duckdbResponseTime, }, postgres: { isConnected: postgres.status.isConnected, isLoading: postgres.isLoading, primaryStat: postgres.stats?.totalTables ?? 0, primaryLabel: language === 'nl' ? 'tabellen' : 'tables', secondaryStat: postgres.stats?.totalRows ?? 0, secondaryLabel: language === 'nl' ? 'rijen' : 'rows', responseTimeMs: postgres.status.responseTimeMs, }, typedb: { isConnected: typedb.status.isConnected, isLoading: typedb.isLoading, primaryStat: typedb.stats?.totalEntities ?? 0, primaryLabel: language === 'nl' ? 'entiteiten' : 'entities', secondaryStat: typedb.stats?.totalRelations ?? 0, secondaryLabel: language === 'nl' ? 'relaties' : 'relations', responseTimeMs: typedb.status.responseTimeMs, }, oxigraph: { isConnected: oxigraph.status.isConnected, isLoading: oxigraph.isLoading, primaryStat: oxigraph.stats?.totalTriples ?? 0, primaryLabel: language === 'nl' ? 'triples' : 'triples', secondaryStat: oxigraph.stats?.totalGraphs ?? 0, secondaryLabel: language === 'nl' ? 'grafen' : 'graphs', responseTimeMs: oxigraph.status.responseTimeMs, }, }; return (
{/* Quick Comparison Grid */}

{t('quickComparison')}

{DATABASES.map((db) => ( ))}
{/* Compact panels for each database */}
); } /** * Database Status Card for comparison view */ function DatabaseStatusCard({ db, language, stats, }: { db: DatabaseInfo; language: 'nl' | 'en'; stats: DatabaseStats; }) { // Format large numbers const formatNumber = (n: number) => { if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`; if (n >= 1000) return `${(n / 1000).toFixed(1)}K`; return n.toLocaleString(); }; return (
{db.icon}

{db.name}

{db.description[language]}

{stats.isLoading ? (
{language === 'nl' ? 'Laden...' : 'Loading...'}
) : stats.isConnected ? (
{language === 'nl' ? 'Verbonden' : 'Connected'} {stats.responseTimeMs && ( {stats.responseTimeMs}ms )}
{formatNumber(stats.primaryStat)} {stats.primaryLabel}
{formatNumber(stats.secondaryStat)} {stats.secondaryLabel}
{stats.tertiaryStat !== undefined && stats.tertiaryLabel && (
{formatNumber(stats.tertiaryStat)} {stats.tertiaryLabel}
)}
) : (
{language === 'nl' ? 'Niet verbonden' : 'Disconnected'}
)}
); }