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 @@
-
@@ -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] });