feat: implement dynamic database management via MySQL driver and API controllers
This commit is contained in:
@@ -109,7 +109,7 @@ class SchemaController extends Controller
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->initializeDriver($request);
|
$this->initializeDriver($request);
|
||||||
$config = $request->only(['host', 'username', 'password', 'database', 'port']);
|
$config = $request->only(['host', 'username', 'password', 'database', 'port', 'table']);
|
||||||
$filePath = $this->databaseService->export($config);
|
$filePath = $this->databaseService->export($config);
|
||||||
|
|
||||||
return Response::download($filePath)->deleteFileAfterSend(true);
|
return Response::download($filePath)->deleteFileAfterSend(true);
|
||||||
@@ -154,4 +154,26 @@ class SchemaController extends Controller
|
|||||||
return Response::json(['error' => $e->getMessage()], 400);
|
return Response::json(['error' => $e->getMessage()], 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tableMetadata(Request $request, $database, $table)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request->merge(['database' => $database]);
|
||||||
|
$this->initializeDriver($request);
|
||||||
|
return Response::json($this->databaseService->getTableMetadata($database, $table));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return Response::json(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function truncate(Request $request, $table)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->initializeDriver($request);
|
||||||
|
$this->databaseService->truncateTable($table);
|
||||||
|
return Response::json(['message' => "Table '{$table}' truncated successfully"]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return Response::json(['error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,10 +114,16 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface
|
|||||||
$host = $config['host'] ?? '127.0.0.1';
|
$host = $config['host'] ?? '127.0.0.1';
|
||||||
$port = $config['port'] ?? '3306';
|
$port = $config['port'] ?? '3306';
|
||||||
$database = $config['database'] ?? '';
|
$database = $config['database'] ?? '';
|
||||||
|
$table = $config['table'] ?? '';
|
||||||
|
|
||||||
// Build command with flags for a complete and resilient export
|
// Build command with flags for a complete and resilient export
|
||||||
$passwordPart = !empty($password) ? "-p" . escapeshellarg($password) : "";
|
$passwordPart = !empty($password) ? "-p" . escapeshellarg($password) : "";
|
||||||
|
|
||||||
|
if (!empty($table) && !empty($database)) {
|
||||||
|
$dbPart = escapeshellarg($database) . " " . escapeshellarg($table);
|
||||||
|
} else {
|
||||||
$dbPart = !empty($database) ? escapeshellarg($database) : "--all-databases";
|
$dbPart = !empty($database) ? escapeshellarg($database) : "--all-databases";
|
||||||
|
}
|
||||||
|
|
||||||
// --single-transaction: for InnoDB tables, ensures consistency without locking
|
// --single-transaction: for InnoDB tables, ensures consistency without locking
|
||||||
// --skip-lock-tables: avoids issues with views/definers that might prevent locking
|
// --skip-lock-tables: avoids issues with views/definers that might prevent locking
|
||||||
@@ -225,4 +231,35 @@ class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface
|
|||||||
$results = $this->query($sql, [$database]);
|
$results = $this->query($sql, [$database]);
|
||||||
return (array) ($results[0] ?? []);
|
return (array) ($results[0] ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTableMetadata(string $database, string $table): array
|
||||||
|
{
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
ENGINE as engine,
|
||||||
|
TABLE_ROWS as rows,
|
||||||
|
DATA_LENGTH as data_length,
|
||||||
|
INDEX_LENGTH as index_length,
|
||||||
|
DATA_FREE as data_free,
|
||||||
|
AUTO_INCREMENT as auto_increment,
|
||||||
|
CREATE_TIME as create_time,
|
||||||
|
UPDATE_TIME as update_time,
|
||||||
|
TABLE_COLLATION as collation,
|
||||||
|
TABLE_COMMENT as comment
|
||||||
|
FROM
|
||||||
|
information_schema.TABLES
|
||||||
|
WHERE
|
||||||
|
TABLE_SCHEMA = ? AND
|
||||||
|
TABLE_NAME = ?
|
||||||
|
";
|
||||||
|
|
||||||
|
$results = $this->query($sql, [$database, $table]);
|
||||||
|
return (array) ($results[0] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function truncateTable(string $table): bool
|
||||||
|
{
|
||||||
|
DB::connection($this->connectionName)->statement("TRUNCATE TABLE `{$table}`");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,4 +122,20 @@ class DatabaseService
|
|||||||
{
|
{
|
||||||
return $this->getDriver()->getDatabaseMetadata($database);
|
return $this->getDriver()->getDatabaseMetadata($database);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table metadata.
|
||||||
|
*/
|
||||||
|
public function getTableMetadata(string $database, string $table): array
|
||||||
|
{
|
||||||
|
return $this->getDriver()->getTableMetadata($database, $table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate a table.
|
||||||
|
*/
|
||||||
|
public function truncateTable(string $table): bool
|
||||||
|
{
|
||||||
|
return $this->getDriver()->truncateTable($table);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ 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}/{table}', [SchemaController::class, 'tableMetadata']);
|
||||||
|
Route::post('/truncate/{table}', [SchemaController::class, 'truncate']);
|
||||||
Route::get('/{table}', [SchemaController::class, 'schema']);
|
Route::get('/{table}', [SchemaController::class, 'schema']);
|
||||||
Route::get('/{table}/data', [SchemaController::class, 'data']);
|
Route::get('/{table}/data', [SchemaController::class, 'data']);
|
||||||
Route::post('/execute', [SchemaController::class, 'execute']);
|
Route::post('/execute', [SchemaController::class, 'execute']);
|
||||||
|
|||||||
@@ -196,29 +196,63 @@ const MainContent: React.FC = () => {
|
|||||||
setDbTab(newValue);
|
setDbTab(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DatabaseOverview = ({ database }: { database: string }) => {
|
const TechnicalOverview = ({ database, table }: { database: string, table: string | null }) => {
|
||||||
const [meta, setMeta] = useState<any>(null);
|
const [meta, setMeta] = useState<any>(null);
|
||||||
const [loadingMeta, setLoadingMeta] = useState(true);
|
const [loadingMeta, setLoadingMeta] = useState(true);
|
||||||
|
const [truncating, setTruncating] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchMeta = useCallback(async () => {
|
||||||
const fetchMeta = async () => {
|
|
||||||
setLoadingMeta(true);
|
setLoadingMeta(true);
|
||||||
try {
|
try {
|
||||||
const res = await SchemaService.getDatabaseMetadata(database);
|
const res = table
|
||||||
|
? await SchemaService.getTableMetadata(database, table)
|
||||||
|
: await SchemaService.getDatabaseMetadata(database);
|
||||||
setMeta(res.data);
|
setMeta(res.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingMeta(false);
|
setLoadingMeta(false);
|
||||||
}
|
}
|
||||||
};
|
}, [database, table]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
fetchMeta();
|
fetchMeta();
|
||||||
}, [database]);
|
}, [fetchMeta]);
|
||||||
|
|
||||||
|
const handleTruncate = async () => {
|
||||||
|
if (!table || !window.confirm(`Are you sure you want to truncate table "${table}"? This will delete all data!`)) return;
|
||||||
|
|
||||||
|
setTruncating(true);
|
||||||
|
try {
|
||||||
|
await SchemaService.truncateTable(table);
|
||||||
|
setErrorInfo({
|
||||||
|
open: true,
|
||||||
|
title: 'Success',
|
||||||
|
message: `Table "${table}" has been truncated.`
|
||||||
|
});
|
||||||
|
fetchMeta();
|
||||||
|
} catch (error: any) {
|
||||||
|
setErrorInfo({
|
||||||
|
open: true,
|
||||||
|
title: 'Truncate Error',
|
||||||
|
message: error.response?.data?.error || error.message
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setTruncating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loadingMeta) return <Box sx={{ display: 'flex', justifyContent: 'center', p: 8 }}><CircularProgress /></Box>;
|
if (loadingMeta) return <Box sx={{ display: 'flex', justifyContent: 'center', p: 8 }}><CircularProgress /></Box>;
|
||||||
if (!meta) return <Alert severity="error">Could not load metadata</Alert>;
|
if (!meta) return <Alert severity="error">Could not load metadata</Alert>;
|
||||||
|
|
||||||
const stats = [
|
const stats = table ? [
|
||||||
|
{ label: 'Engine', value: meta.engine, icon: <Terminal /> },
|
||||||
|
{ label: 'Rows', value: meta.rows, icon: <TableChart /> },
|
||||||
|
{ label: 'Collation', value: meta.collation, icon: <CleaningServices /> },
|
||||||
|
{ label: 'Data Size', value: `${(meta.data_length / 1024 / 1024).toFixed(2)} MB`, icon: <Save /> },
|
||||||
|
{ label: 'Index Size', value: `${(meta.index_length / 1024 / 1024).toFixed(2)} MB`, icon: <History /> },
|
||||||
|
{ label: 'Created', value: meta.create_time, icon: <Info /> },
|
||||||
|
] : [
|
||||||
{ label: 'Character Set', value: meta.charset, icon: <History /> },
|
{ label: 'Character Set', value: meta.charset, icon: <History /> },
|
||||||
{ label: 'Collation', value: meta.collation, icon: <CleaningServices /> },
|
{ label: 'Collation', value: meta.collation, icon: <CleaningServices /> },
|
||||||
{ label: 'Tables', value: meta.table_count, icon: <TableChart /> },
|
{ label: 'Tables', value: meta.table_count, icon: <TableChart /> },
|
||||||
@@ -228,7 +262,23 @@ const MainContent: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 1 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h5" sx={{ fontWeight: 800, mb: 4, letterSpacing: -0.5 }}>Technical Specifications: {database}</Typography>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 800, letterSpacing: -0.5 }}>
|
||||||
|
Technical Specifications: {table ? `${database}.${table}` : database}
|
||||||
|
</Typography>
|
||||||
|
{table && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
startIcon={truncating ? <CircularProgress size={16} color="inherit" /> : <CleaningServices />}
|
||||||
|
onClick={handleTruncate}
|
||||||
|
disabled={truncating}
|
||||||
|
sx={{ borderRadius: 2, textTransform: 'none', fontWeight: 700 }}
|
||||||
|
>
|
||||||
|
Truncate Table
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 3 }}>
|
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 3 }}>
|
||||||
{stats.map((stat, i) => (
|
{stats.map((stat, i) => (
|
||||||
<Paper key={i} sx={{
|
<Paper key={i} sx={{
|
||||||
@@ -247,7 +297,7 @@ const MainContent: React.FC = () => {
|
|||||||
<Typography variant="caption" sx={{ fontWeight: 700, color: 'text.secondary', textTransform: 'uppercase', letterSpacing: 1 }}>{stat.label}</Typography>
|
<Typography variant="caption" sx={{ fontWeight: 700, color: 'text.secondary', textTransform: 'uppercase', letterSpacing: 1 }}>{stat.label}</Typography>
|
||||||
<Box sx={{ color: 'primary.main', opacity: 0.5 }}>{stat.icon}</Box>
|
<Box sx={{ color: 'primary.main', opacity: 0.5 }}>{stat.icon}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h5" sx={{ fontWeight: 800 }}>{stat.value}</Typography>
|
<Typography variant="h5" sx={{ fontWeight: 800 }}>{stat.value || 'N/A'}</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -399,7 +449,7 @@ const MainContent: React.FC = () => {
|
|||||||
{dbTab === 'export' && <TransferContent mode="export" />}
|
{dbTab === 'export' && <TransferContent mode="export" />}
|
||||||
|
|
||||||
{/* Technical Info View */}
|
{/* Technical Info View */}
|
||||||
{dbTab === 'info' && <DatabaseOverview database={activeDatabase} />}
|
{dbTab === 'info' && <TechnicalOverview database={activeDatabase} table={activeTable} />}
|
||||||
|
|
||||||
{/* Custom Alert (Snackbar) */}
|
{/* Custom Alert (Snackbar) */}
|
||||||
<Snackbar open={errorInfo.open} autoHideDuration={10000} onClose={handleCloseError} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
|
<Snackbar open={errorInfo.open} autoHideDuration={10000} onClose={handleCloseError} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ interface TransferContentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
||||||
const { activeDatabase, darkMode } = useAppStore();
|
const { activeDatabase, activeTable, darkMode } = useAppStore();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -37,15 +37,16 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
|||||||
setError(null);
|
setError(null);
|
||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
try {
|
try {
|
||||||
const response = await SchemaService.exportDatabase(activeDatabase || undefined);
|
const response = await SchemaService.exportDatabase(activeDatabase || undefined, activeTable || undefined);
|
||||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
const filename = `backup-${activeDatabase || 'all'}-${new Date().toISOString().split('T')[0]}.sql`;
|
const contextName = activeTable ? `${activeDatabase}-${activeTable}` : (activeDatabase || 'all');
|
||||||
|
const filename = `backup-${contextName}-${new Date().toISOString().split('T')[0]}.sql`;
|
||||||
link.setAttribute('download', filename);
|
link.setAttribute('download', filename);
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
setSuccess('Database exported successfully!');
|
setSuccess(`${activeTable ? 'Table' : 'Database'} exported successfully!`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError('Export failed: ' + (err.response?.data?.error || err.message));
|
setError('Export failed: ' + (err.response?.data?.error || err.message));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -68,9 +69,10 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
if (activeDatabase) formData.append('database', activeDatabase);
|
if (activeDatabase) formData.append('database', activeDatabase);
|
||||||
|
if (activeTable) formData.append('table', activeTable);
|
||||||
|
|
||||||
await SchemaService.importDatabase(formData);
|
await SchemaService.importDatabase(formData);
|
||||||
setSuccess('Database imported successfully!');
|
setSuccess(`${activeTable ? 'Table' : 'Database'} imported successfully!`);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError('Import failed: ' + (err.response?.data?.error || err.message));
|
setError('Import failed: ' + (err.response?.data?.error || err.message));
|
||||||
@@ -83,9 +85,9 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
|||||||
<Box sx={{ flexGrow: 1, p: 4, bgcolor: 'background.default', display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ flexGrow: 1, p: 4, bgcolor: 'background.default', display: 'flex', justifyContent: 'center' }}>
|
||||||
<Stack spacing={4} sx={{ width: '100%', maxWidth: 800 }}>
|
<Stack spacing={4} sx={{ width: '100%', maxWidth: 800 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h4" sx={{ fontWeight: 800, mb: 1 }}>Database Transfer</Typography>
|
<Typography variant="h4" sx={{ fontWeight: 800, mb: 1 }}>{activeTable ? 'Table' : 'Database'} Transfer</Typography>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" color="text.secondary">
|
||||||
Export your database to a SQL file or import an existing SQL dump using <strong>mysqldump</strong>.
|
Export your {activeTable ? 'table' : 'database'} to a SQL file or import an existing SQL dump using <strong>mysqldump</strong>.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -128,9 +130,9 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
|||||||
<Box sx={{ p: 2, borderRadius: '50%', bgcolor: 'primary.main', color: 'white', mb: 1 }}>
|
<Box sx={{ p: 2, borderRadius: '50%', bgcolor: 'primary.main', color: 'white', mb: 1 }}>
|
||||||
<CloudDownload sx={{ fontSize: 40 }} />
|
<CloudDownload sx={{ fontSize: 40 }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>Export Database</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 700 }}>Export {activeTable ? 'Table' : 'Database'}</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Create a full backup of the current database: <strong>{activeDatabase || 'All Databases'}</strong>
|
Create a full backup of the current {activeTable ? 'table' : 'database'}: <strong>{activeTable ? `${activeDatabase}.${activeTable}` : (activeDatabase || 'All Databases')}</strong>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
<Box sx={{ flexGrow: 1 }} />
|
||||||
<Button
|
<Button
|
||||||
@@ -166,9 +168,9 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
|||||||
<Box sx={{ p: 2, borderRadius: '50%', bgcolor: 'secondary.main', color: 'white', mb: 1 }}>
|
<Box sx={{ p: 2, borderRadius: '50%', bgcolor: 'secondary.main', color: 'white', mb: 1 }}>
|
||||||
<CloudUpload sx={{ fontSize: 40 }} />
|
<CloudUpload sx={{ fontSize: 40 }} />
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>Import Database</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 700 }}>Import {activeTable ? 'Table' : 'Database'}</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Upload a .sql file to restore or migrate your data.
|
Upload a .sql file to restore or migrate your {activeTable ? 'table' : 'database'}.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ 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 } }),
|
||||||
|
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 }),
|
||||||
|
truncateTable: (table: string) => api.post(`/schema/truncate/${table}`),
|
||||||
executeQuery: (query: string) => api.post('/schema/execute', { query }),
|
executeQuery: (query: string) => api.post('/schema/execute', { query }),
|
||||||
exportDatabase: (database?: string) => api.post('/schema/export', { database }, { responseType: 'blob' }),
|
exportDatabase: (database?: string, table?: string) => api.post('/schema/export', { database, table }, { responseType: 'blob' }),
|
||||||
importDatabase: (formData: FormData) => api.post('/schema/import', formData, {
|
importDatabase: (formData: FormData) => api.post('/schema/import', formData, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' }
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export const useAppStore = create<AppState>()(
|
|||||||
setDbTab: (tab) => set({ dbTab: tab }),
|
setDbTab: (tab) => set({ dbTab: tab }),
|
||||||
setConnection: (config) => set({ connection: config, connected: true }),
|
setConnection: (config) => set({ connection: config, connected: true }),
|
||||||
clearConnection: () => set({ connection: null, connected: false, activeDatabase: null, activeTable: null }),
|
clearConnection: () => set({ connection: null, connected: false, activeDatabase: null, activeTable: null }),
|
||||||
setActiveDatabase: (db) => set({ activeDatabase: db, activeTable: null, dbTab: 'tables' }),
|
setActiveDatabase: (db) => set({ activeDatabase: db, activeTable: null }),
|
||||||
setActiveTable: (table) => set({ activeTable: table, dbTab: 'tables' }),
|
setActiveTable: (table) => set({ activeTable: table }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'mariavel-storage',
|
name: 'mariavel-storage',
|
||||||
|
|||||||
Reference in New Issue
Block a user