From c07ef4196ed0e7be8d08e9d93e0897ce705ca705 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 02:53:50 +0000 Subject: [PATCH] Add Table Manager and Column Manager UI to admin dashboard Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- README.md | 19 +- ROADMAP.md | 6 +- src/app/admin/dashboard/page.tsx | 719 ++++++++++++++++++++++++++++++- 3 files changed, 738 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ac93e5f..f7faac8 100644 --- a/README.md +++ b/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 diff --git a/ROADMAP.md b/ROADMAP.md index b67913d..11cbb4b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 diff --git a/src/app/admin/dashboard/page.tsx b/src/app/admin/dashboard/page.tsx index 275d9ea..3661bb4 100644 --- a/src/app/admin/dashboard/page.tsx +++ b/src/app/admin/dashboard/page.tsx @@ -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(null); const [deletingRecord, setDeletingRecord] = useState(null); const [formData, setFormData] = useState({}); + + // Table Manager states + const [openCreateTableDialog, setOpenCreateTableDialog] = useState(false); + const [openDropTableDialog, setOpenDropTableDialog] = useState(false); + const [newTableName, setNewTableName] = useState(''); + const [tableColumns, setTableColumns] = useState([{ 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 ( @@ -243,6 +539,22 @@ export default function AdminDashboard() { + + setTabValue(2)}> + + + + + + + + setTabValue(3)}> + + + + + + @@ -313,6 +625,150 @@ export default function AdminDashboard() { + + + Table Manager + + + + + + + + + + + Existing Tables + + + {tables.map(table => ( + + + + + + + ))} + {tables.length === 0 && ( + + + + )} + + + + + + + + Column Manager + + + + + Select a table to manage its columns: + + + + + {selectedTableForColumn && ( + <> + + + + + + + {tableSchema && ( + + + + Current Columns for {selectedTableForColumn} + + + + + + Column Name + Data Type + Nullable + Default + + + + {tableSchema.columns?.map((col: any) => ( + + {col.column_name} + {col.data_type} + {col.is_nullable} + {col.column_default || 'NULL'} + + ))} + +
+
+
+
+ )} + + )} +
+ + {successMessage && ( + setSuccessMessage('')}> + {successMessage} + + )} + {error && ( {error} @@ -363,6 +819,267 @@ export default function AdminDashboard() { )} + + {/* Create Table Dialog */} + setOpenCreateTableDialog(false)} maxWidth="md" fullWidth> + Create New Table + + setNewTableName(e.target.value)} + sx={{ mt: 2, mb: 2 }} + /> + + Columns: + + {tableColumns.map((col, index) => ( + + updateColumnField(index, 'name', e.target.value)} + sx={{ mr: 1, mb: 1 }} + /> + + {(col.type === 'VARCHAR') && ( + updateColumnField(index, 'length', e.target.value)} + sx={{ mr: 1, mb: 1, width: 100 }} + /> + )} + updateColumnField(index, 'nullable', e.target.checked)} + /> + } + label="Nullable" + sx={{ mr: 1 }} + /> + updateColumnField(index, 'primaryKey', e.target.checked)} + /> + } + label="Primary Key" + sx={{ mr: 1 }} + /> + {tableColumns.length > 1 && ( + removeColumn(index)} color="error" size="small"> + + + )} + + ))} + + + + + + + + + {/* Drop Table Dialog */} + setOpenDropTableDialog(false)}> + Drop Table + + + Warning: This will permanently delete the table and all its data! + + + + + + + + + + {/* Add Column Dialog */} + setOpenAddColumnDialog(false)}> + Add Column to {selectedTableForColumn} + + setNewColumnName(e.target.value)} + sx={{ mt: 2, mb: 2 }} + /> + + setNewColumnNullable(e.target.checked)} + /> + } + label="Nullable" + sx={{ mb: 2 }} + /> + setNewColumnDefault(e.target.value)} + /> + + + + + + + + {/* Modify Column Dialog */} + setOpenModifyColumnDialog(false)}> + Modify Column in {selectedTableForColumn} + + + {columnToModify && ( + <> + + setNewColumnNullable(e.target.checked)} + /> + } + label="Nullable" + /> + + )} + + + + + + + + {/* Drop Column Dialog */} + setOpenDropColumnDialog(false)}> + Drop Column from {selectedTableForColumn} + + + Warning: This will permanently delete the column and all its data! + + + + + + + +
);