mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-24 13:55:00 +00:00
Add Table Manager and Column Manager UI to admin dashboard
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
19
README.md
19
README.md
@@ -52,6 +52,8 @@ This project is a full-stack web application featuring:
|
||||
- 💎 **Tailwind CSS 4** for styling
|
||||
- 🗄️ **Database CRUD Operations** - Full Create, Read, Update, Delete functionality
|
||||
- 🛠️ **Admin Panel** - Manage tables, columns, and data through a beautiful UI
|
||||
- 📊 **Table Manager** - Create and drop tables with visual column definition
|
||||
- 🔧 **Column Manager** - Add, modify, and drop columns from existing tables
|
||||
- 📊 **SQL Query Interface** - Execute custom queries with safety validation
|
||||
- 🔒 **JWT Authentication** with secure session management
|
||||
- 📦 **DrizzleORM** - Support for PostgreSQL, MySQL, and SQLite
|
||||
@@ -69,6 +71,8 @@ This is a **PostgreSQL database administration panel** that provides:
|
||||
- 🎨 **Modern, beautiful UI** with Material UI components and dark mode support
|
||||
- 🔒 **Secure authentication** with bcrypt password hashing and JWT sessions
|
||||
- 📊 **Database viewing** - Browse tables, view data, and explore schema
|
||||
- 🛠️ **Table management** - Create and drop tables through intuitive UI
|
||||
- 🔧 **Column management** - Add, modify, and drop columns with type selection
|
||||
- 🔍 **SQL query interface** - Execute SELECT queries safely with result display
|
||||
- 🐳 **All-in-one Docker image** - PostgreSQL 15 and admin UI in one container
|
||||
- ⚡ **Production-ready** - Deploy to Caprover, Docker, or any cloud platform
|
||||
@@ -89,9 +93,14 @@ This is a **PostgreSQL database administration panel** that provides:
|
||||
### Database Management
|
||||
- 📊 **View database tables** - Browse all tables with metadata
|
||||
- 📋 **Table data viewer** - View table contents with pagination
|
||||
- 🛠️ **Table Manager** - Create new tables with custom columns and constraints
|
||||
- 🗑️ **Drop tables** - Delete tables with confirmation dialogs
|
||||
- 🔧 **Column Manager** - Add, modify, and drop columns from existing tables
|
||||
- 🎨 **Visual column builder** - Define column types, constraints, and defaults through UI
|
||||
- 🔍 **SQL query interface** - Execute SELECT queries safely
|
||||
- 🔒 **Query validation** - Only SELECT queries allowed for security
|
||||
- 📈 **Row count display** - See result counts instantly
|
||||
- 📐 **Schema inspector** - View table structures and column details
|
||||
|
||||
### Security & Authentication
|
||||
- 🔐 **User/password authentication** - Secure bcrypt password hashing
|
||||
@@ -272,6 +281,8 @@ Access the admin panel at http://localhost:3000/admin/login
|
||||
**Features available in the admin panel**:
|
||||
- 📊 **Table Browser**: View all database tables and their data
|
||||
- ✏️ **CRUD Operations**: Create, edit, and delete records
|
||||
- 🛠️ **Table Manager**: Create new tables with columns, drop existing tables
|
||||
- 🔧 **Column Manager**: Add, modify, and delete columns from tables
|
||||
- 🔍 **SQL Query Interface**: Execute custom SELECT queries
|
||||
- 🛠️ **Schema Inspector**: View table structures, columns, and relationships
|
||||
- 🔐 **Secure Access**: JWT-based authentication with session management
|
||||
@@ -750,13 +761,17 @@ Before deploying to production:
|
||||
|
||||
See [ROADMAP.md](ROADMAP.md) for planned features and improvements.
|
||||
|
||||
**Recently implemented:**
|
||||
- ✅ Table Manager - Create and drop tables with visual column builder
|
||||
- ✅ Column Manager - Add, modify, and drop columns from existing tables
|
||||
- ✅ Schema management interface for table and column operations
|
||||
|
||||
**Upcoming features:**
|
||||
- Full CRUD operations (Create, Update, Delete)
|
||||
- Visual database designer
|
||||
- Multi-database server connections
|
||||
- Advanced query builder
|
||||
- Export data (CSV, JSON, SQL)
|
||||
- Table schema editor
|
||||
- Foreign key relationship management
|
||||
- User management with roles
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -57,9 +57,9 @@ See `src/config/features.json` for the complete feature configuration.
|
||||
- [x] ✅ Create FormDialog component for create/edit operations
|
||||
- [x] ✅ Add ConfirmDialog component for delete confirmations
|
||||
- [x] ✅ Implement table schema inspection API
|
||||
- [ ] Create schema management interface
|
||||
- [ ] Implement table creation/editing UI (API ready, UI pending)
|
||||
- [ ] Add column type management UI (API ready, UI pending)
|
||||
- [x] ✅ Create schema management interface
|
||||
- [x] ✅ Implement table creation/editing UI (API ready, UI implemented)
|
||||
- [x] ✅ Add column type management UI (API ready, UI implemented)
|
||||
- [ ] Add data validation and constraints management
|
||||
- [ ] Build query builder interface
|
||||
- [ ] Add foreign key relationship management
|
||||
|
||||
@@ -7,24 +7,29 @@ import EditIcon from '@mui/icons-material/Edit';
|
||||
import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import TableChartIcon from '@mui/icons-material/TableChart';
|
||||
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
|
||||
import {
|
||||
Alert,
|
||||
AppBar,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Drawer,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Paper,
|
||||
Select,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -83,6 +88,25 @@ export default function AdminDashboard() {
|
||||
const [editingRecord, setEditingRecord] = useState<any>(null);
|
||||
const [deletingRecord, setDeletingRecord] = useState<any>(null);
|
||||
const [formData, setFormData] = useState<any>({});
|
||||
|
||||
// Table Manager states
|
||||
const [openCreateTableDialog, setOpenCreateTableDialog] = useState(false);
|
||||
const [openDropTableDialog, setOpenDropTableDialog] = useState(false);
|
||||
const [newTableName, setNewTableName] = useState('');
|
||||
const [tableColumns, setTableColumns] = useState<any[]>([{ name: '', type: 'VARCHAR', length: 255, nullable: true, primaryKey: false }]);
|
||||
const [tableToDelete, setTableToDelete] = useState('');
|
||||
|
||||
// Column Manager states
|
||||
const [openAddColumnDialog, setOpenAddColumnDialog] = useState(false);
|
||||
const [openModifyColumnDialog, setOpenModifyColumnDialog] = useState(false);
|
||||
const [openDropColumnDialog, setOpenDropColumnDialog] = useState(false);
|
||||
const [selectedTableForColumn, setSelectedTableForColumn] = useState('');
|
||||
const [newColumnName, setNewColumnName] = useState('');
|
||||
const [newColumnType, setNewColumnType] = useState('VARCHAR');
|
||||
const [newColumnNullable, setNewColumnNullable] = useState(true);
|
||||
const [newColumnDefault, setNewColumnDefault] = useState('');
|
||||
const [columnToModify, setColumnToModify] = useState('');
|
||||
const [columnToDelete, setColumnToDelete] = useState('');
|
||||
|
||||
const fetchTables = useCallback(async () => {
|
||||
try {
|
||||
@@ -105,6 +129,31 @@ export default function AdminDashboard() {
|
||||
fetchTables();
|
||||
}, [fetchTables]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTableForColumn && tabValue === 3) {
|
||||
// Fetch schema when a table is selected in Column Manager
|
||||
const fetchSchema = async () => {
|
||||
try {
|
||||
const schemaResponse = await fetch('/api/admin/table-schema', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ tableName: selectedTableForColumn }),
|
||||
});
|
||||
|
||||
if (schemaResponse.ok) {
|
||||
const schemaData = await schemaResponse.json();
|
||||
setTableSchema(schemaData);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch schema:', err);
|
||||
}
|
||||
};
|
||||
fetchSchema();
|
||||
}
|
||||
}, [selectedTableForColumn, tabValue]);
|
||||
|
||||
const handleTableClick = async (tableName: string) => {
|
||||
setSelectedTable(tableName);
|
||||
setLoading(true);
|
||||
@@ -122,7 +171,7 @@ export default function AdminDashboard() {
|
||||
body: JSON.stringify({ tableName }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (!dataResponse.ok) {
|
||||
const data = await dataResponse.json();
|
||||
throw new Error(data.error || 'Query failed');
|
||||
}
|
||||
@@ -195,6 +244,253 @@ export default function AdminDashboard() {
|
||||
}
|
||||
};
|
||||
|
||||
// Table Management Handlers
|
||||
const handleCreateTable = async () => {
|
||||
if (!newTableName.trim()) {
|
||||
setError('Table name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (tableColumns.length === 0 || !tableColumns[0].name) {
|
||||
setError('At least one column is required');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/table-manage', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tableName: newTableName,
|
||||
columns: tableColumns.filter(col => col.name.trim()),
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to create table');
|
||||
}
|
||||
|
||||
setSuccessMessage(data.message);
|
||||
setOpenCreateTableDialog(false);
|
||||
setNewTableName('');
|
||||
setTableColumns([{ name: '', type: 'VARCHAR', length: 255, nullable: true, primaryKey: false }]);
|
||||
await fetchTables();
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropTable = async () => {
|
||||
if (!tableToDelete) {
|
||||
setError('Please select a table to drop');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/table-manage', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ tableName: tableToDelete }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to drop table');
|
||||
}
|
||||
|
||||
setSuccessMessage(data.message);
|
||||
setOpenDropTableDialog(false);
|
||||
setTableToDelete('');
|
||||
if (selectedTable === tableToDelete) {
|
||||
setSelectedTable('');
|
||||
setQueryResult(null);
|
||||
}
|
||||
await fetchTables();
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const addColumnToTable = () => {
|
||||
setTableColumns([...tableColumns, { name: '', type: 'VARCHAR', length: 255, nullable: true, primaryKey: false }]);
|
||||
};
|
||||
|
||||
const updateColumnField = (index: number, field: string, value: any) => {
|
||||
const updated = [...tableColumns];
|
||||
updated[index] = { ...updated[index], [field]: value };
|
||||
setTableColumns(updated);
|
||||
};
|
||||
|
||||
const removeColumn = (index: number) => {
|
||||
if (tableColumns.length > 1) {
|
||||
setTableColumns(tableColumns.filter((_, i) => i !== index));
|
||||
}
|
||||
};
|
||||
|
||||
// Column Management Handlers
|
||||
const handleAddColumn = async () => {
|
||||
if (!selectedTableForColumn || !newColumnName.trim() || !newColumnType) {
|
||||
setError('Table name, column name, and data type are required');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const payload: any = {
|
||||
tableName: selectedTableForColumn,
|
||||
columnName: newColumnName,
|
||||
dataType: newColumnType,
|
||||
nullable: newColumnNullable,
|
||||
};
|
||||
|
||||
if (newColumnDefault) {
|
||||
payload.defaultValue = newColumnDefault;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/admin/column-manage', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to add column');
|
||||
}
|
||||
|
||||
setSuccessMessage(data.message);
|
||||
setOpenAddColumnDialog(false);
|
||||
setNewColumnName('');
|
||||
setNewColumnType('VARCHAR');
|
||||
setNewColumnNullable(true);
|
||||
setNewColumnDefault('');
|
||||
|
||||
// Refresh table schema if viewing the modified table
|
||||
if (selectedTable === selectedTableForColumn) {
|
||||
await handleTableClick(selectedTableForColumn);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModifyColumn = async () => {
|
||||
if (!selectedTableForColumn || !columnToModify) {
|
||||
setError('Table name and column name are required');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/column-manage', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tableName: selectedTableForColumn,
|
||||
columnName: columnToModify,
|
||||
newType: newColumnType,
|
||||
nullable: newColumnNullable,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to modify column');
|
||||
}
|
||||
|
||||
setSuccessMessage(data.message);
|
||||
setOpenModifyColumnDialog(false);
|
||||
setColumnToModify('');
|
||||
setNewColumnType('VARCHAR');
|
||||
setNewColumnNullable(true);
|
||||
|
||||
// Refresh table schema if viewing the modified table
|
||||
if (selectedTable === selectedTableForColumn) {
|
||||
await handleTableClick(selectedTableForColumn);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropColumn = async () => {
|
||||
if (!selectedTableForColumn || !columnToDelete) {
|
||||
setError('Table name and column name are required');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/column-manage', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tableName: selectedTableForColumn,
|
||||
columnName: columnToDelete,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to drop column');
|
||||
}
|
||||
|
||||
setSuccessMessage(data.message);
|
||||
setOpenDropColumnDialog(false);
|
||||
setColumnToDelete('');
|
||||
|
||||
// Refresh table schema if viewing the modified table
|
||||
if (selectedTable === selectedTableForColumn) {
|
||||
await handleTableClick(selectedTableForColumn);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
@@ -243,6 +539,22 @@ export default function AdminDashboard() {
|
||||
<ListItemText primary="SQL Query" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={() => setTabValue(2)}>
|
||||
<ListItemIcon>
|
||||
<TableChartIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Table Manager" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={() => setTabValue(3)}>
|
||||
<ListItemIcon>
|
||||
<ViewColumnIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Column Manager" />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
@@ -313,6 +625,150 @@ export default function AdminDashboard() {
|
||||
</Paper>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabValue} index={2}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Table Manager
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mt: 2, mb: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setOpenCreateTableDialog(true)}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Create Table
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
startIcon={<DeleteIcon />}
|
||||
onClick={() => setOpenDropTableDialog(true)}
|
||||
>
|
||||
Drop Table
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper sx={{ mt: 2 }}>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Existing Tables
|
||||
</Typography>
|
||||
<List>
|
||||
{tables.map(table => (
|
||||
<ListItem key={table.table_name}>
|
||||
<ListItemIcon>
|
||||
<TableChartIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={table.table_name} />
|
||||
</ListItem>
|
||||
))}
|
||||
{tables.length === 0 && (
|
||||
<ListItem>
|
||||
<ListItemText primary="No tables found" />
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Box>
|
||||
</Paper>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabValue} index={3}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Column Manager
|
||||
</Typography>
|
||||
|
||||
<Paper sx={{ p: 2, mt: 2, mb: 2 }}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Select a table to manage its columns:
|
||||
</Typography>
|
||||
<Select
|
||||
fullWidth
|
||||
value={selectedTableForColumn}
|
||||
onChange={e => setSelectedTableForColumn(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select a table</em>
|
||||
</MenuItem>
|
||||
{tables.map(table => (
|
||||
<MenuItem key={table.table_name} value={table.table_name}>
|
||||
{table.table_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Paper>
|
||||
|
||||
{selectedTableForColumn && (
|
||||
<>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setOpenAddColumnDialog(true)}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Add Column
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<EditIcon />}
|
||||
onClick={() => setOpenModifyColumnDialog(true)}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Modify Column
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
startIcon={<DeleteIcon />}
|
||||
onClick={() => setOpenDropColumnDialog(true)}
|
||||
>
|
||||
Drop Column
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{tableSchema && (
|
||||
<Paper sx={{ mt: 2 }}>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Current Columns for {selectedTableForColumn}
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell><strong>Column Name</strong></TableCell>
|
||||
<TableCell><strong>Data Type</strong></TableCell>
|
||||
<TableCell><strong>Nullable</strong></TableCell>
|
||||
<TableCell><strong>Default</strong></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tableSchema.columns?.map((col: any) => (
|
||||
<TableRow key={col.column_name}>
|
||||
<TableCell>{col.column_name}</TableCell>
|
||||
<TableCell>{col.data_type}</TableCell>
|
||||
<TableCell>{col.is_nullable}</TableCell>
|
||||
<TableCell>{col.column_default || 'NULL'}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
{successMessage && (
|
||||
<Alert severity="success" sx={{ mt: 2 }} onClose={() => setSuccessMessage('')}>
|
||||
{successMessage}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mt: 2 }}>
|
||||
{error}
|
||||
@@ -363,6 +819,267 @@ export default function AdminDashboard() {
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Create Table Dialog */}
|
||||
<Dialog open={openCreateTableDialog} onClose={() => setOpenCreateTableDialog(false)} maxWidth="md" fullWidth>
|
||||
<DialogTitle>Create New Table</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Table Name"
|
||||
value={newTableName}
|
||||
onChange={e => setNewTableName(e.target.value)}
|
||||
sx={{ mt: 2, mb: 2 }}
|
||||
/>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Columns:
|
||||
</Typography>
|
||||
{tableColumns.map((col, index) => (
|
||||
<Box key={index} sx={{ mb: 2, p: 2, border: '1px solid #ddd', borderRadius: 1 }}>
|
||||
<TextField
|
||||
label="Column Name"
|
||||
value={col.name}
|
||||
onChange={e => updateColumnField(index, 'name', e.target.value)}
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
<Select
|
||||
value={col.type}
|
||||
onChange={e => updateColumnField(index, 'type', e.target.value)}
|
||||
sx={{ mr: 1, mb: 1, minWidth: 120 }}
|
||||
>
|
||||
<MenuItem value="INTEGER">INTEGER</MenuItem>
|
||||
<MenuItem value="BIGINT">BIGINT</MenuItem>
|
||||
<MenuItem value="SERIAL">SERIAL</MenuItem>
|
||||
<MenuItem value="VARCHAR">VARCHAR</MenuItem>
|
||||
<MenuItem value="TEXT">TEXT</MenuItem>
|
||||
<MenuItem value="BOOLEAN">BOOLEAN</MenuItem>
|
||||
<MenuItem value="TIMESTAMP">TIMESTAMP</MenuItem>
|
||||
<MenuItem value="DATE">DATE</MenuItem>
|
||||
<MenuItem value="JSON">JSON</MenuItem>
|
||||
<MenuItem value="JSONB">JSONB</MenuItem>
|
||||
</Select>
|
||||
{(col.type === 'VARCHAR') && (
|
||||
<TextField
|
||||
label="Length"
|
||||
type="number"
|
||||
value={col.length || 255}
|
||||
onChange={e => updateColumnField(index, 'length', e.target.value)}
|
||||
sx={{ mr: 1, mb: 1, width: 100 }}
|
||||
/>
|
||||
)}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={col.nullable}
|
||||
onChange={e => updateColumnField(index, 'nullable', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Nullable"
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={col.primaryKey}
|
||||
onChange={e => updateColumnField(index, 'primaryKey', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Primary Key"
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
{tableColumns.length > 1 && (
|
||||
<IconButton onClick={() => removeColumn(index)} color="error" size="small">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
<Button startIcon={<AddIcon />} onClick={addColumnToTable} variant="outlined">
|
||||
Add Column
|
||||
</Button>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenCreateTableDialog(false)}>Cancel</Button>
|
||||
<Button onClick={handleCreateTable} variant="contained" disabled={loading}>
|
||||
Create Table
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Drop Table Dialog */}
|
||||
<Dialog open={openDropTableDialog} onClose={() => setOpenDropTableDialog(false)}>
|
||||
<DialogTitle>Drop Table</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2" color="error" gutterBottom>
|
||||
Warning: This will permanently delete the table and all its data!
|
||||
</Typography>
|
||||
<Select
|
||||
fullWidth
|
||||
value={tableToDelete}
|
||||
onChange={e => setTableToDelete(e.target.value)}
|
||||
displayEmpty
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select a table to drop</em>
|
||||
</MenuItem>
|
||||
{tables.map(table => (
|
||||
<MenuItem key={table.table_name} value={table.table_name}>
|
||||
{table.table_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenDropTableDialog(false)}>Cancel</Button>
|
||||
<Button onClick={handleDropTable} color="error" variant="contained" disabled={loading}>
|
||||
Drop Table
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Add Column Dialog */}
|
||||
<Dialog open={openAddColumnDialog} onClose={() => setOpenAddColumnDialog(false)}>
|
||||
<DialogTitle>Add Column to {selectedTableForColumn}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Column Name"
|
||||
value={newColumnName}
|
||||
onChange={e => setNewColumnName(e.target.value)}
|
||||
sx={{ mt: 2, mb: 2 }}
|
||||
/>
|
||||
<Select
|
||||
fullWidth
|
||||
value={newColumnType}
|
||||
onChange={e => setNewColumnType(e.target.value)}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<MenuItem value="INTEGER">INTEGER</MenuItem>
|
||||
<MenuItem value="BIGINT">BIGINT</MenuItem>
|
||||
<MenuItem value="SERIAL">SERIAL</MenuItem>
|
||||
<MenuItem value="VARCHAR">VARCHAR(255)</MenuItem>
|
||||
<MenuItem value="TEXT">TEXT</MenuItem>
|
||||
<MenuItem value="BOOLEAN">BOOLEAN</MenuItem>
|
||||
<MenuItem value="TIMESTAMP">TIMESTAMP</MenuItem>
|
||||
<MenuItem value="DATE">DATE</MenuItem>
|
||||
<MenuItem value="JSON">JSON</MenuItem>
|
||||
<MenuItem value="JSONB">JSONB</MenuItem>
|
||||
</Select>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={newColumnNullable}
|
||||
onChange={e => setNewColumnNullable(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Nullable"
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Default Value (optional)"
|
||||
value={newColumnDefault}
|
||||
onChange={e => setNewColumnDefault(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenAddColumnDialog(false)}>Cancel</Button>
|
||||
<Button onClick={handleAddColumn} variant="contained" disabled={loading}>
|
||||
Add Column
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Modify Column Dialog */}
|
||||
<Dialog open={openModifyColumnDialog} onClose={() => setOpenModifyColumnDialog(false)}>
|
||||
<DialogTitle>Modify Column in {selectedTableForColumn}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Select
|
||||
fullWidth
|
||||
value={columnToModify}
|
||||
onChange={e => setColumnToModify(e.target.value)}
|
||||
displayEmpty
|
||||
sx={{ mt: 2, mb: 2 }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select a column to modify</em>
|
||||
</MenuItem>
|
||||
{tableSchema?.columns?.map((col: any) => (
|
||||
<MenuItem key={col.column_name} value={col.column_name}>
|
||||
{col.column_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{columnToModify && (
|
||||
<>
|
||||
<Select
|
||||
fullWidth
|
||||
value={newColumnType}
|
||||
onChange={e => setNewColumnType(e.target.value)}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<MenuItem value="INTEGER">INTEGER</MenuItem>
|
||||
<MenuItem value="BIGINT">BIGINT</MenuItem>
|
||||
<MenuItem value="VARCHAR">VARCHAR(255)</MenuItem>
|
||||
<MenuItem value="TEXT">TEXT</MenuItem>
|
||||
<MenuItem value="BOOLEAN">BOOLEAN</MenuItem>
|
||||
<MenuItem value="TIMESTAMP">TIMESTAMP</MenuItem>
|
||||
<MenuItem value="DATE">DATE</MenuItem>
|
||||
<MenuItem value="JSON">JSON</MenuItem>
|
||||
<MenuItem value="JSONB">JSONB</MenuItem>
|
||||
</Select>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={newColumnNullable}
|
||||
onChange={e => setNewColumnNullable(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Nullable"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenModifyColumnDialog(false)}>Cancel</Button>
|
||||
<Button onClick={handleModifyColumn} variant="contained" disabled={loading || !columnToModify}>
|
||||
Modify Column
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Drop Column Dialog */}
|
||||
<Dialog open={openDropColumnDialog} onClose={() => setOpenDropColumnDialog(false)}>
|
||||
<DialogTitle>Drop Column from {selectedTableForColumn}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2" color="error" gutterBottom>
|
||||
Warning: This will permanently delete the column and all its data!
|
||||
</Typography>
|
||||
<Select
|
||||
fullWidth
|
||||
value={columnToDelete}
|
||||
onChange={e => setColumnToDelete(e.target.value)}
|
||||
displayEmpty
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
<MenuItem value="">
|
||||
<em>Select a column to drop</em>
|
||||
</MenuItem>
|
||||
{tableSchema?.columns?.map((col: any) => (
|
||||
<MenuItem key={col.column_name} value={col.column_name}>
|
||||
{col.column_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenDropColumnDialog(false)}>Cancel</Button>
|
||||
<Button onClick={handleDropColumn} color="error" variant="contained" disabled={loading || !columnToDelete}>
|
||||
Drop Column
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user