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);
|
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);
|
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('/databases', [SchemaController::class, 'databases']);
|
||||||
Route::get('/tables/{database}', [SchemaController::class, 'tables']);
|
Route::get('/tables/{database}', [SchemaController::class, 'tables']);
|
||||||
Route::get('/metadata/{database}', [SchemaController::class, 'metadata']);
|
Route::get('/metadata/{database}', [SchemaController::class, 'metadata']);
|
||||||
|
Route::get('/metadata/{database}/tables', [SchemaController::class, 'tablesMetadata']);
|
||||||
Route::get('/metadata/{database}/{table}', [SchemaController::class, 'tableMetadata']);
|
Route::get('/metadata/{database}/{table}', [SchemaController::class, 'tableMetadata']);
|
||||||
Route::post('/truncate/{table}', [SchemaController::class, 'truncate']);
|
Route::post('/truncate/{table}', [SchemaController::class, 'truncate']);
|
||||||
Route::get('/{table}', [SchemaController::class, 'schema']);
|
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 (
|
return (
|
||||||
<Box sx={{ flexGrow: 1, p: 3, bgcolor: 'background.default', display: 'flex', flexDirection: 'column', width: '100%', minWidth: 0, overflow: 'hidden', gap: 2 }}>
|
<Box sx={{ flexGrow: 1, p: 3, bgcolor: 'background.default', display: 'flex', flexDirection: 'column', width: '100%', minWidth: 0, overflow: 'hidden', gap: 2 }}>
|
||||||
{/* Database Navigation Tabs */}
|
{/* Database Navigation Tabs */}
|
||||||
@@ -365,9 +442,9 @@ const MainContent: React.FC = () => {
|
|||||||
{dbTab === 'tables' && (
|
{dbTab === 'tables' && (
|
||||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', minWidth: 0, overflow: 'hidden' }}>
|
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', minWidth: 0, overflow: 'hidden' }}>
|
||||||
{!activeTable ? (
|
{!activeTable ? (
|
||||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 2, opacity: 0.5 }}>
|
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
<TableChart sx={{ fontSize: 64 }} />
|
<Typography variant="h6" sx={{ fontWeight: 800 }}>Database Tables: {activeDatabase}</Typography>
|
||||||
<Typography variant="h6">Select a table from the explorer to view data</Typography>
|
<DatabaseTablesGrid database={activeDatabase} />
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const SchemaService = {
|
|||||||
getDatabases: () => api.get('/schema/databases'),
|
getDatabases: () => api.get('/schema/databases'),
|
||||||
getTables: (db: string) => api.get(`/schema/tables/${db}`, { params: { database: db } }),
|
getTables: (db: string) => api.get(`/schema/tables/${db}`, { params: { database: db } }),
|
||||||
getDatabaseMetadata: (db: string) => api.get(`/schema/metadata/${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 } }),
|
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 } }),
|
getTableSchema: (table: string, database?: string) => api.get(`/schema/${table}`, { params: { database } }),
|
||||||
getTableData: (table: string, params: any) => api.get(`/schema/${table}/data`, { params }),
|
getTableData: (table: string, params: any) => api.get(`/schema/${table}/data`, { params }),
|
||||||
|
|||||||
Reference in New Issue
Block a user