feat: implement database management service layer with MySQL driver and API controllers

This commit is contained in:
Ümit Tunç
2026-04-28 20:16:28 +03:00
parent b5282df56f
commit 2e529bb61c
7 changed files with 153 additions and 15 deletions
+111 -15
View File
@@ -1,8 +1,9 @@
import React, { useEffect, useState, useMemo } from 'react';
import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, Divider, CircularProgress, Stack, TextField, InputAdornment } from '@mui/material';
import { Storage, Search, FilterList, ChevronRight, ExpandMore, TableChart, Folder } from '@mui/icons-material';
import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, Divider, CircularProgress, Stack, TextField, InputAdornment, IconButton, Tooltip, Dialog, DialogTitle, DialogContent, DialogActions, Button, Alert, Snackbar, AlertTitle } from '@mui/material';
import { Storage, Search, FilterList, ChevronRight, ExpandMore, TableChart, Folder, Add } from '@mui/icons-material';
import { useAppStore } from '../store/useAppStore';
import { SchemaService } from '../services/api';
import type { MainNotification } from '../types/database';
const Sidebar: React.FC = () => {
const { activeDatabase, setActiveDatabase, setActiveTable, activeTable } = useAppStore();
@@ -16,18 +17,30 @@ const Sidebar: React.FC = () => {
const [tablesLoading, setTablesLoading] = useState(false);
const [openNewDbDialog, setOpenNewDbDialog] = useState(false);
const [newDbName, setNewDbName] = useState('');
const [isCreatingDb, setIsCreatingDb] = useState(false);
const [notification, setNotification] = useState<MainNotification>({
open: false,
message: '',
title: '',
severity: 'success'
});
const fetchDatabases = async () => {
setLoading(true);
try {
const response = await SchemaService.getDatabases();
setDatabases(response.data);
} catch (error) {
console.error('Failed to fetch databases', error);
} finally {
setLoading(false);
}
};
// Fetch databases on mount
useEffect(() => {
const fetchDatabases = async () => {
try {
const response = await SchemaService.getDatabases();
setDatabases(response.data);
} catch (error) {
console.error('Failed to fetch databases', error);
} finally {
setLoading(false);
}
};
fetchDatabases();
}, []);
@@ -86,11 +99,45 @@ const Sidebar: React.FC = () => {
return tables.filter(table => table.toLowerCase().includes(tableSearch.toLowerCase()));
}, [tables, tableSearch]);
const handleCreateDatabase = async () => {
if (!newDbName.trim()) return;
setIsCreatingDb(true);
try {
await SchemaService.createDatabase(newDbName);
setNotification({
open: true,
title: 'Success',
message: `Database '${newDbName}' created successfully.`,
severity: 'success'
});
setOpenNewDbDialog(false);
setNewDbName('');
fetchDatabases();
} catch (error: any) {
setNotification({
open: true,
title: 'Error',
message: error.response?.data?.error || error.message,
severity: 'error'
});
} finally {
setIsCreatingDb(false);
}
};
return (
<Box sx={{ width: 280, borderRight: 1, borderColor: 'divider', display: 'flex', flexDirection: 'column', bgcolor: 'background.paper', zIndex: 10 }}>
<Box sx={{ p: 2, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Storage color="primary" sx={{ fontSize: 24 }} />
<Typography variant="h6" sx={{ fontWeight: 800, fontSize: '1.1rem' }}>Explorer</Typography>
<Box sx={{ p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Storage color="primary" sx={{ fontSize: 24 }} />
<Typography variant="h6" sx={{ fontWeight: 800, fontSize: '1.1rem' }}>Explorer</Typography>
</Box>
<Tooltip title="New Database">
<IconButton size="small" onClick={() => setOpenNewDbDialog(true)} sx={{ color: 'primary.main' }}>
<Add fontSize="small" />
</IconButton>
</Tooltip>
</Box>
<Box sx={{ px: 2, pb: 1 }}>
@@ -229,6 +276,55 @@ const Sidebar: React.FC = () => {
</Typography>
</Stack>
</Box>
{/* New Database Dialog */}
<Dialog open={openNewDbDialog} onClose={() => setOpenNewDbDialog(false)} maxWidth="xs" fullWidth>
<DialogTitle sx={{ fontWeight: 800 }}>Create New Database</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Database Name"
type="text"
fullWidth
variant="outlined"
value={newDbName}
onChange={(e) => setNewDbName(e.target.value)}
disabled={isCreatingDb}
onKeyDown={(e) => {
if (e.key === 'Enter') handleCreateDatabase();
}}
/>
</DialogContent>
<DialogActions sx={{ p: 2, pt: 0 }}>
<Button onClick={() => setOpenNewDbDialog(false)} disabled={isCreatingDb}>Cancel</Button>
<Button
onClick={handleCreateDatabase}
variant="contained"
disabled={isCreatingDb || !newDbName.trim()}
>
{isCreatingDb ? <CircularProgress size={24} /> : 'Create'}
</Button>
</DialogActions>
</Dialog>
{/* Local Notification */}
<Snackbar
open={notification.open}
autoHideDuration={4000}
onClose={() => setNotification({ ...notification, open: false })}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
>
<Alert
severity={notification.severity}
variant="filled"
onClose={() => setNotification({ ...notification, open: false })}
sx={{ width: '100%', borderRadius: 2 }}
>
<AlertTitle sx={{ fontWeight: 700 }}>{notification.title}</AlertTitle>
{notification.message}
</Alert>
</Snackbar>
</Box>
);
};
+1
View File
@@ -24,6 +24,7 @@ export default api;
export const SchemaService = {
getDatabases: () => api.get('/schema/databases'),
createDatabase: (name: string, charset?: string, collation?: string) => api.post('/schema/databases', { name, charset, collation }),
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 } }),