diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..cffe28a --- /dev/null +++ b/favicon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 423a2f5..958a0dd 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,7 @@ PostgreSQL Admin Panel + @@ -162,7 +163,7 @@
- @@ -186,6 +187,9 @@ + + +
@@ -393,36 +397,6 @@ SELECT * FROM users LIMIT 10;"> - - -
@@ -439,7 +413,7 @@ SELECT * FROM users LIMIT 10;"> this.editingColumn = null; this.primaryKey = null; this.tableStructure = []; - this.filters = []; + this.filters = {}; this.sortColumn = ''; this.sortDirection = 'ASC'; this.init(); @@ -606,10 +580,12 @@ SELECT * FROM users LIMIT 10;"> renderTableData(records) { const headers = document.getElementById('tableHeaders'); + const filterInputs = document.getElementById('filterInputs'); const body = document.getElementById('tableBody'); if (records.length === 0) { headers.innerHTML = ''; + filterInputs.innerHTML = ''; body.innerHTML = 'Нет данных'; return; } @@ -622,6 +598,11 @@ SELECT * FROM users LIMIT 10;"> return `${col} ${arrow}`; }).join('') + 'Действия'; + // Generate filter inputs + filterInputs.innerHTML = columns.map(col => { + return ``; + }).join('') + ''; + // Generate rows body.innerHTML = records.map(record => { const pkValue = this.primaryKey ? record[this.primaryKey] : null; @@ -1190,45 +1171,6 @@ SELECT * FROM users LIMIT 10;"> } } - showFiltersModal() { - this.renderFilters(); - document.getElementById('filtersModal').classList.remove('hidden'); - } - - renderFilters() { - const container = document.getElementById('filtersContainer'); - container.innerHTML = this.filters.map((filter, index) => ` -
- - - - -
- `).join(''); - lucide.createIcons(); - } - - addFilter() { - this.filters.push({ column: '', operator: '=', value: '' }); - this.renderFilters(); - } - updateFilterColumn(index, column) { this.filters[index].column = column; this.renderFilters(); // Re-render to update disabled state @@ -1239,23 +1181,28 @@ SELECT * FROM users LIMIT 10;"> this.renderFilters(); // Re-render to update disabled state } - updateFilterValue(index, value) { - this.filters[index].value = value; + toggleFilters() { + const filterRow = document.getElementById('filterRow'); + filterRow.classList.toggle('hidden'); } - removeFilter(index) { - this.filters.splice(index, 1); - this.renderFilters(); + updateFilter(column, value) { + if (value.trim()) { + this.filters[column] = value.trim(); + } else { + delete this.filters[column]; + } + this.currentPage = 1; + this.loadTableData(); } clearFilters() { - this.filters = []; - this.renderFilters(); - } - - applyFilters() { + this.filters = {}; + // Update all input values + document.querySelectorAll('#filterInputs input').forEach(input => { + input.value = ''; + }); this.currentPage = 1; - this.closeModal('filtersModal'); this.loadTableData(); } @@ -1266,7 +1213,7 @@ SELECT * FROM users LIMIT 10;"> // Find primary key const pkColumn = this.tableStructure.find(col => col.is_primary); - this.primaryKey = pkColumn ? pkColumn.name : null; + this.primaryKey = pkColumn ? pkColumn.name : (this.tableStructure.find(col => col.name === 'id') ? 'id' : null); } catch (err) { this.showToast('Ошибка загрузки структуры таблицы', 'error'); } diff --git a/server.js b/server.js index 0ecac32..fa23c48 100644 --- a/server.js +++ b/server.js @@ -179,8 +179,8 @@ app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => { try { let whereClause = ''; - let params = [limit, offset]; - let paramIndex = 3; // $1=limit, $2=offset, $3+ for conditions + let params = []; + let paramIndex = 1; // Search if (search) { @@ -199,68 +199,23 @@ app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => { } // Filters - if (filters.length > 0) { - const filterConditions = filters.map(filter => { - const { column, operator, value } = filter; - let condition = ''; - - switch (operator) { - case '=': - condition = `"${column}" = $${paramIndex}`; - params.push(value); - paramIndex++; - break; - case '!=': - condition = `"${column}" != $${paramIndex}`; - params.push(value); - paramIndex++; - break; - case '>': - condition = `"${column}" > $${paramIndex}`; - params.push(value); - paramIndex++; - break; - case '<': - condition = `"${column}" < $${paramIndex}`; - params.push(value); - paramIndex++; - break; - case '>=': - condition = `"${column}" >= $${paramIndex}`; - params.push(value); - paramIndex++; - break; - case '<=': - condition = `"${column}" <= $${paramIndex}`; - params.push(value); - paramIndex++; - break; - case 'LIKE': - condition = `"${column}" LIKE $${paramIndex}`; - params.push(`%${value}%`); - paramIndex++; - break; - case 'ILIKE': - condition = `"${column}" ILIKE $${paramIndex}`; - params.push(`%${value}%`); - paramIndex++; - break; - case 'IS NULL': - condition = `"${column}" IS NULL`; - break; - case 'IS NOT NULL': - condition = `"${column}" IS NOT NULL`; - break; + if (filters && typeof filters === 'object') { + const filterConditions = Object.entries(filters).map(([column, value]) => { + if (value && value.trim()) { + params.push(`%${value}%`); + paramIndex++; + return `"${column}" ILIKE $${paramIndex - 1}`; } - - return condition; - }).filter(c => c).join(' AND '); + return null; + }).filter(c => c); - whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`; + if (filterConditions.length > 0) { + whereClause = whereClause ? `${whereClause} AND ${filterConditions.join(' AND ')}` : `WHERE ${filterConditions.join(' AND ')}`; + } } // Get total count - const countResult = await pool.query(`SELECT COUNT(*) as total FROM "${tableName}" ${whereClause}`, params.slice(2)); + const countResult = await pool.query(`SELECT COUNT(*) as total FROM "${tableName}" ${whereClause}`, params); const total = parseInt(countResult.rows[0].total); // Build ORDER BY @@ -274,8 +229,8 @@ app.get('/api/tables/:tableName/data', requireAuth, async (req, res) => { SELECT * FROM "${tableName}" ${whereClause} ${orderBy} - LIMIT $1 OFFSET $2 - `, params); + LIMIT $${paramIndex} OFFSET $${paramIndex + 1} + `, [...params, limit, offset]); res.json({ data: result.rows, @@ -358,11 +313,43 @@ app.post('/api/tables/:tableName/records', requireAuth, async (req, res) => { const { tableName } = req.params; const data = req.body; - const columns = Object.keys(data); - const values = Object.values(data); - const placeholders = values.map((_, i) => `$${i + 1}`).join(', '); - try { + // Get table structure to check types + const structureResult = await pool.query(` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = $1 AND table_schema = 'public' + `, [tableName]); + + const structure = structureResult.rows; + + // Filter out empty UUIDs and generate if needed + const filteredData = {}; + for (const [key, value] of Object.entries(data)) { + const colInfo = structure.find(col => col.column_name === key); + if (colInfo && colInfo.data_type === 'uuid') { + if (!value || value.trim() === '') { + // Generate UUID for empty UUID fields + filteredData[key] = require('crypto').randomUUID(); + } else { + // Validate UUID + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + if (uuidRegex.test(value)) { + filteredData[key] = value; + } else { + // Invalid UUID, generate new one + filteredData[key] = require('crypto').randomUUID(); + } + } + } else if (value !== '') { + filteredData[key] = value; + } + } + + const columns = Object.keys(filteredData); + const values = Object.values(filteredData); + const placeholders = values.map((_, i) => `$${i + 1}`).join(', '); + const sql = `INSERT INTO "${tableName}" (${columns.map(c => `"${c}"`).join(', ')}) VALUES (${placeholders}) RETURNING *`; const result = await pool.query(sql, values); res.json({ success: true, data: result.rows[0] });