feat: implement database export/import functionality and table management UI components
This commit is contained in:
@@ -170,7 +170,7 @@ const DatabaseTablesGrid: React.FC<DatabaseTablesGridProps> = ({ database, setNo
|
||||
)}
|
||||
<Paper sx={{
|
||||
flexGrow: 1,
|
||||
borderRadius: 3,
|
||||
borderRadius: 1,
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
@@ -5,7 +5,13 @@ import {
|
||||
Typography,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Alert
|
||||
Alert,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Avatar
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Terminal,
|
||||
@@ -120,28 +126,39 @@ const TechnicalOverview: React.FC<TechnicalOverviewProps> = ({ database, table,
|
||||
confirmLabel="Truncate Now"
|
||||
loading={truncating}
|
||||
/>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 3 }}>
|
||||
{stats.map((stat, i) => (
|
||||
<Paper key={i} sx={{
|
||||
p: 3,
|
||||
borderRadius: 4,
|
||||
<TableContainer component={Paper} sx={{
|
||||
borderRadius: 1,
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
bgcolor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.01)',
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': { borderColor: 'primary.main', transform: 'translateY(-2px)' }
|
||||
bgcolor: 'background.paper',
|
||||
overflow: 'hidden',
|
||||
boxShadow: 'none'
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<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>
|
||||
<Typography variant="h5" sx={{ fontWeight: 800 }}>{stat.value || 'N/A'}</Typography>
|
||||
</Paper>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{stats.map((stat, i) => (
|
||||
<TableRow key={i} sx={{ '&:last-child td, &:last-child th': { border: 0 }, '&:hover': { bgcolor: 'rgba(255,255,255,0.02)' } }}>
|
||||
<TableCell sx={{ width: 64 }}>
|
||||
<Avatar sx={{
|
||||
bgcolor: 'rgba(0,123,255,0.1)',
|
||||
color: 'primary.main',
|
||||
width: 40,
|
||||
height: 40
|
||||
}}>
|
||||
{stat.icon}
|
||||
</Avatar>
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 700, color: 'text.secondary', textTransform: 'uppercase', letterSpacing: 1, fontSize: '0.75rem' }}>
|
||||
{stat.label}
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 800, fontSize: '1.1rem' }}>
|
||||
{stat.value || 'N/A'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</Box>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,13 @@ import {
|
||||
Divider,
|
||||
Alert,
|
||||
LinearProgress,
|
||||
IconButton
|
||||
IconButton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Avatar
|
||||
} from '@mui/material';
|
||||
import {
|
||||
CloudDownload,
|
||||
@@ -111,106 +117,103 @@ const TransferContent: React.FC<TransferContentProps> = ({ mode = 'both' }) => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Stack direction={{ xs: 'column', md: 'row' }} spacing={3}>
|
||||
{/* Export Card */}
|
||||
{(mode === 'export' || mode === 'both') && (
|
||||
<Paper sx={{
|
||||
flex: 1,
|
||||
p: 4,
|
||||
<TableContainer component={Paper} sx={{
|
||||
borderRadius: 4,
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
gap: 2,
|
||||
transition: 'all 0.3s ease',
|
||||
'&:hover': { transform: 'translateY(-4px)', boxShadow: '0 12px 24px rgba(0,0,0,0.1)' }
|
||||
bgcolor: 'background.paper',
|
||||
overflow: 'hidden',
|
||||
boxShadow: 'none'
|
||||
}}>
|
||||
<Box sx={{ p: 2, borderRadius: '50%', bgcolor: 'primary.main', color: 'white', mb: 1 }}>
|
||||
<CloudDownload sx={{ fontSize: 40 }} />
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>Export {activeTable ? 'Table' : 'Database'}</Typography>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{/* Export Row */}
|
||||
{(mode === 'export' || mode === 'both') && (
|
||||
<TableRow sx={{ '&:hover': { bgcolor: 'rgba(255,255,255,0.02)' } }}>
|
||||
<TableCell sx={{ width: 80, verticalAlign: 'top', pt: 3 }}>
|
||||
<Avatar sx={{ bgcolor: 'primary.main', color: 'white', width: 48, height: 48 }}>
|
||||
<CloudDownload />
|
||||
</Avatar>
|
||||
</TableCell>
|
||||
<TableCell sx={{ py: 3 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}>Export {activeTable ? 'Table' : 'Database'}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Create a full backup of the current {activeTable ? 'table' : 'database'}: <strong>{activeTable ? `${activeDatabase}.${activeTable}` : (activeDatabase || 'All Databases')}</strong>
|
||||
Create a full backup of the current {activeTable ? 'table' : 'database'}: <br/>
|
||||
<Box component="span" sx={{ color: 'primary.main', fontWeight: 600 }}>
|
||||
{activeTable ? `${activeDatabase}.${activeTable}` : (activeDatabase || 'All Databases')}
|
||||
</Box>
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ py: 3 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
size="large"
|
||||
startIcon={<CloudDownload />}
|
||||
onClick={handleExport}
|
||||
disabled={loading}
|
||||
sx={{ borderRadius: 2, py: 1.5, fontWeight: 700 }}
|
||||
sx={{ borderRadius: 2, px: 3, py: 1, fontWeight: 700, minWidth: 160 }}
|
||||
>
|
||||
Start Export
|
||||
</Button>
|
||||
</Paper>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
{/* Import Card */}
|
||||
{/* Import Row */}
|
||||
{(mode === 'import' || mode === 'both') && (
|
||||
<Paper sx={{
|
||||
flex: 1,
|
||||
p: 4,
|
||||
borderRadius: 4,
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
gap: 2,
|
||||
transition: 'all 0.3s ease',
|
||||
'&:hover': { transform: 'translateY(-4px)', boxShadow: '0 12px 24px rgba(0,0,0,0.1)' }
|
||||
}}>
|
||||
<Box sx={{ p: 2, borderRadius: '50%', bgcolor: 'secondary.main', color: 'white', mb: 1 }}>
|
||||
<CloudUpload sx={{ fontSize: 40 }} />
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>Import {activeTable ? 'Table' : 'Database'}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<TableRow sx={{ '&:hover': { bgcolor: 'rgba(255,255,255,0.02)' } }}>
|
||||
<TableCell sx={{ width: 80, verticalAlign: 'top', pt: 3 }}>
|
||||
<Avatar sx={{ bgcolor: 'secondary.main', color: 'white', width: 48, height: 48 }}>
|
||||
<CloudUpload />
|
||||
</Avatar>
|
||||
</TableCell>
|
||||
<TableCell sx={{ py: 3 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}>Import {activeTable ? 'Table' : 'Database'}</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Upload a .sql file to restore or migrate your {activeTable ? 'table' : 'database'}.
|
||||
</Typography>
|
||||
|
||||
<Box sx={{
|
||||
width: '100%',
|
||||
p: 2,
|
||||
maxWidth: 400,
|
||||
p: 1.5,
|
||||
border: '2px dashed',
|
||||
borderColor: file ? 'secondary.main' : 'divider',
|
||||
borderRadius: 2,
|
||||
bgcolor: 'rgba(0,0,0,0.02)',
|
||||
cursor: 'pointer',
|
||||
position: 'relative'
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1
|
||||
}}>
|
||||
<input
|
||||
type="file"
|
||||
accept=".sql"
|
||||
onChange={handleFileChange}
|
||||
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', opacity: 0, cursor: 'pointer' }}
|
||||
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', opacity: 0, cursor: 'pointer', zIndex: 1 }}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{file ? file.name : "Click or drag .sql file here"}
|
||||
<CloudUpload fontSize="small" color={file ? "secondary" : "disabled"} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600 }}>
|
||||
{file ? file.name : "Select or drag .sql file here"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ py: 3 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
size="large"
|
||||
color="secondary"
|
||||
startIcon={<CloudUpload />}
|
||||
onClick={handleImport}
|
||||
disabled={loading || !file}
|
||||
sx={{ borderRadius: 2, py: 1.5, fontWeight: 700 }}
|
||||
sx={{ borderRadius: 2, px: 3, py: 1, fontWeight: 700, minWidth: 160 }}
|
||||
>
|
||||
Start Import
|
||||
</Button>
|
||||
</Paper>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</Stack>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{loading && (
|
||||
<Box sx={{ width: '100%' }}>
|
||||
|
||||
Reference in New Issue
Block a user