feat: implement backend database management API and frontend service integration
This commit is contained in:
@@ -176,4 +176,15 @@ class SchemaController extends Controller
|
||||
return Response::json(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function tablesMetadata(Request $request, $database)
|
||||
{
|
||||
try {
|
||||
$request->merge(['database' => $database]);
|
||||
$this->initializeDriver($request);
|
||||
return Response::json($this->databaseService->getTablesMetadata($database));
|
||||
} catch (\Exception $e) {
|
||||
return Response::json(['error' => $e->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,4 +138,12 @@ class DatabaseService
|
||||
{
|
||||
return $this->getDriver()->truncateTable($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata for all tables in a database.
|
||||
*/
|
||||
public function getTablesMetadata(string $database): array
|
||||
{
|
||||
return $this->getDriver()->getTablesMetadata($database);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ Route::prefix('schema')->group(function () {
|
||||
Route::get('/databases', [SchemaController::class, 'databases']);
|
||||
Route::get('/tables/{database}', [SchemaController::class, 'tables']);
|
||||
Route::get('/metadata/{database}', [SchemaController::class, 'metadata']);
|
||||
Route::get('/metadata/{database}/tables', [SchemaController::class, 'tablesMetadata']);
|
||||
Route::get('/metadata/{database}/{table}', [SchemaController::class, 'tableMetadata']);
|
||||
Route::post('/truncate/{table}', [SchemaController::class, 'truncate']);
|
||||
Route::get('/{table}', [SchemaController::class, 'schema']);
|
||||
|
||||
@@ -325,6 +325,83 @@ const MainContent: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const DatabaseTablesGrid = ({ database }: { database: string }) => {
|
||||
const [tableRows, setTableRows] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { setActiveTable } = useAppStore();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTablesMeta = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await SchemaService.getTablesMetadata(database);
|
||||
setTableRows(res.data.map((r: any, i: number) => ({ id: r.name || i, ...r })));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchTablesMeta();
|
||||
}, [database]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: 'name',
|
||||
headerName: 'Table Name',
|
||||
flex: 1,
|
||||
minWidth: 200,
|
||||
renderCell: (params) => (
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={<TableChart fontSize="small" />}
|
||||
onClick={() => setActiveTable(params.value)}
|
||||
sx={{ textTransform: 'none', fontWeight: 600, color: 'primary.main' }}
|
||||
>
|
||||
{params.value}
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
{ field: 'engine', headerName: 'Engine', width: 120 },
|
||||
{ field: 'rows', headerName: 'Rows', type: 'number', width: 120 },
|
||||
{
|
||||
field: 'data_length',
|
||||
headerName: 'Data Size',
|
||||
width: 130,
|
||||
valueFormatter: (value) => `${(Number(value) / 1024 / 1024).toFixed(2)} MB`
|
||||
},
|
||||
{
|
||||
field: 'index_length',
|
||||
headerName: 'Index Size',
|
||||
width: 130,
|
||||
valueFormatter: (value) => `${(Number(value) / 1024 / 1024).toFixed(2)} MB`
|
||||
},
|
||||
{ field: 'collation', headerName: 'Collation', width: 180 },
|
||||
{ field: 'comment', headerName: 'Comment', flex: 1, minWidth: 150 },
|
||||
];
|
||||
|
||||
return (
|
||||
<Paper sx={{
|
||||
flexGrow: 1,
|
||||
borderRadius: 3,
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.05)'
|
||||
}}>
|
||||
<DataGrid
|
||||
rows={tableRows}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
density="comfortable"
|
||||
sx={{ border: 'none' }}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1, p: 3, bgcolor: 'background.default', display: 'flex', flexDirection: 'column', width: '100%', minWidth: 0, overflow: 'hidden', gap: 2 }}>
|
||||
{/* Database Navigation Tabs */}
|
||||
@@ -365,9 +442,9 @@ const MainContent: React.FC = () => {
|
||||
{dbTab === 'tables' && (
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', minWidth: 0, overflow: 'hidden' }}>
|
||||
{!activeTable ? (
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 2, opacity: 0.5 }}>
|
||||
<TableChart sx={{ fontSize: 64 }} />
|
||||
<Typography variant="h6">Select a table from the explorer to view data</Typography>
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 800 }}>Database Tables: {activeDatabase}</Typography>
|
||||
<DatabaseTablesGrid database={activeDatabase} />
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -26,6 +26,7 @@ export const SchemaService = {
|
||||
getDatabases: () => api.get('/schema/databases'),
|
||||
getTables: (db: string) => api.get(`/schema/tables/${db}`, { params: { database: db } }),
|
||||
getDatabaseMetadata: (db: string) => api.get(`/schema/metadata/${db}`, { params: { database: db } }),
|
||||
getTablesMetadata: (db: string) => api.get(`/schema/metadata/${db}/tables`, { params: { database: db } }),
|
||||
getTableMetadata: (db: string, table: string) => api.get(`/schema/metadata/${db}/${table}`, { params: { database: db } }),
|
||||
getTableSchema: (table: string, database?: string) => api.get(`/schema/${table}`, { params: { database } }),
|
||||
getTableData: (table: string, params: any) => api.get(`/schema/${table}/data`, { params }),
|
||||
|
||||
Reference in New Issue
Block a user