builder / components /database-manager /schema-viewer.tsx
Leon4gr45's picture
Upload folder using huggingface_hub
eeb9404 verified
Raw
History Blame Contribute Delete
6.87 kB
'use client';
import React, { useState, useEffect } from 'react';
import { TableInfo } from '@/lib/vfs/types';
import { ChevronRight, ChevronDown, Table2, KeyRound, Loader2, AlertCircle, Eye, EyeOff } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
interface SchemaViewerProps {
deploymentId?: string;
schemaEndpoint?: string;
showSystemTablesToggle?: boolean;
workspaceId?: string;
}
export function SchemaViewer({ deploymentId, schemaEndpoint, showSystemTablesToggle = true, workspaceId }: SchemaViewerProps) {
const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api';
const [tables, setTables] = useState<TableInfo[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [expandedTables, setExpandedTables] = useState<Set<string>>(new Set());
const [showSystemTables, setShowSystemTables] = useState(false);
const endpoint = schemaEndpoint || `${apiBase}/admin/deployments/${deploymentId}/database/schema`;
useEffect(() => {
loadSchema();
}, [endpoint]);
const loadSchema = async () => {
try {
setLoading(true);
setError(null);
const res = await fetch(endpoint);
if (!res.ok) {
const data = await res.json();
throw new Error(data.error || 'Failed to load schema');
}
const data = await res.json();
setTables(data.tables);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load schema');
} finally {
setLoading(false);
}
};
const toggleTable = (tableName: string) => {
setExpandedTables(prev => {
const next = new Set(prev);
if (next.has(tableName)) {
next.delete(tableName);
} else {
next.add(tableName);
}
return next;
});
};
const filteredTables = showSystemTables
? tables
: tables.filter(t => !t.isSystemTable);
if (loading) {
return (
<div className="flex items-center justify-center h-full">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
);
}
if (error) {
return (
<div className="flex flex-col items-center justify-center h-full gap-4">
<AlertCircle className="h-8 w-8 text-destructive" />
<p className="text-sm text-muted-foreground">{error}</p>
<Button variant="outline" onClick={loadSchema}>
Retry
</Button>
</div>
);
}
return (
<div className="h-full flex flex-col">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-medium">Database Tables</h3>
{showSystemTablesToggle && (
<Button
variant="ghost"
size="sm"
onClick={() => setShowSystemTables(!showSystemTables)}
className="text-xs"
>
{showSystemTables ? (
<>
<EyeOff className="h-3.5 w-3.5 mr-1" />
Hide System Tables
</>
) : (
<>
<Eye className="h-3.5 w-3.5 mr-1" />
Show System Tables
</>
)}
</Button>
)}
</div>
<div className="flex-1 overflow-auto border rounded-lg">
{filteredTables.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full p-8 text-center">
<Table2 className="h-8 w-8 text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground">No user tables found</p>
<p className="text-xs text-muted-foreground mt-1">
Create tables using the SQL editor
</p>
</div>
) : (
<div className="divide-y">
{filteredTables.map(table => (
<div key={table.name} className={cn(
"transition-colors",
table.isSystemTable && "bg-muted/30"
)}>
<button
onClick={() => toggleTable(table.name)}
className="w-full flex items-center gap-2 p-3 text-left hover:bg-muted/50 transition-colors"
>
{expandedTables.has(table.name) ? (
<ChevronDown className="h-4 w-4 text-muted-foreground" />
) : (
<ChevronRight className="h-4 w-4 text-muted-foreground" />
)}
<Table2 className="h-4 w-4 text-blue-500" />
<span className="flex-1 font-mono text-sm">{table.name}</span>
<span className="text-xs text-muted-foreground">
{table.rowCount} row{table.rowCount !== 1 ? 's' : ''}
</span>
{table.isSystemTable && (
<span className="text-xs bg-muted px-1.5 py-0.5 rounded">system</span>
)}
</button>
{expandedTables.has(table.name) && (
<div className="bg-muted/20 border-t">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/30">
<th className="text-left p-2 font-medium">Column</th>
<th className="text-left p-2 font-medium">Type</th>
<th className="text-left p-2 font-medium">Nullable</th>
<th className="text-left p-2 font-medium">Default</th>
</tr>
</thead>
<tbody>
{table.columns.map(col => (
<tr key={col.name} className="border-b last:border-0">
<td className="p-2 font-mono flex items-center gap-1.5">
{col.primaryKey && (
<KeyRound className="h-3 w-3 text-yellow-500" />
)}
{col.name}
</td>
<td className="p-2 font-mono text-muted-foreground">
{col.type || 'TEXT'}
</td>
<td className="p-2 text-muted-foreground">
{col.nullable ? 'Yes' : 'No'}
</td>
<td className="p-2 font-mono text-muted-foreground text-xs">
{col.defaultValue || '-'}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
);
}