This commit is contained in:
2026-03-18 17:57:59 +07:00
parent ad117fe837
commit ead769e9d1
3 changed files with 252 additions and 28 deletions

View File

@@ -257,6 +257,11 @@ SELECT * FROM users LIMIT 10;"></textarea>
</button>
</div>
<div class="p-6 overflow-y-auto flex-1">
<div class="mb-4">
<label class="block text-sm font-medium text-slate-700 mb-1">Папка</label>
<input type="text" id="newTableFolder" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder="frontend">
<p class="text-xs text-slate-500 mt-1">Если указано, имя таблицы будет создано как <code>папка__имя</code>. Оставьте пустым для создания в корне.</p>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-slate-700 mb-1">Название таблицы</label>
<input type="text" id="newTableName" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder="users">
@@ -510,6 +515,8 @@ SELECT * FROM users LIMIT 10;"></textarea>
if (data.success) {
this.currentUser = {
username,
role: data.role,
permissions: data.permissions,
dbInfo: data.dbInfo
};
localStorage.setItem('pg_admin_session', JSON.stringify(this.currentUser));
@@ -545,6 +552,37 @@ SELECT * FROM users LIMIT 10;"></textarea>
this.loadTables();
}
getPermissions() {
return this.currentUser?.permissions || { folders: null, canCreate: false, canEdit: false, canDelete: false };
}
getCurrentTableFolder() {
if (!this.currentTable) return null;
const parts = this.currentTable.split('__');
return parts.length > 1 ? parts[0] : 'default';
}
canCreate() {
const perms = this.getPermissions();
return perms.canCreate;
}
canEditTable() {
const perms = this.getPermissions();
const folder = this.getCurrentTableFolder();
if (!perms.canEdit) return false;
if (!perms.folders) return true;
return perms.folders.includes(folder);
}
canDeleteTable() {
const perms = this.getPermissions();
const folder = this.getCurrentTableFolder();
if (!perms.canDelete) return false;
if (!perms.folders) return true;
return perms.folders.includes(folder);
}
// Data Loading
async loadTables() {
try {
@@ -563,20 +601,38 @@ SELECT * FROM users LIMIT 10;"></textarea>
renderTableList() {
const container = document.getElementById('tableList');
const search = document.getElementById('tableSearch').value.toLowerCase();
container.innerHTML = this.tables
.filter(t => t.name.toLowerCase().includes(search))
.map(table => `
const filtered = this.tables.filter(t => t.name.toLowerCase().includes(search));
const grouped = filtered.reduce((acc, table) => {
const parts = table.name.split('__');
const folder = parts.length > 1 ? parts[0] : 'default';
if (!acc[folder]) acc[folder] = [];
acc[folder].push(table);
return acc;
}, {});
const folderOrder = Object.keys(grouped).sort();
container.innerHTML = folderOrder.map(folder => {
const label = folder === 'default' ? 'Общие' : folder;
const tablesHtml = grouped[folder].map(table => `
<div onclick="app.selectTable('${table.name}')"
class="sidebar-item px-4 py-3 cursor-pointer border-l-2 ${this.currentTable === table.name ? 'border-blue-500 bg-slate-800/50 text-white' : 'border-transparent hover:text-white'} transition-all flex items-center justify-between group">
<div class="flex items-center gap-3">
<i data-lucide="table-2" class="w-4 h-4 opacity-70"></i>
<span class="text-sm font-medium">${table.name}</span>
<span class="text-sm font-medium">${table.name.replace(`${folder}__`, '')}</span>
</div>
<span class="text-xs opacity-50 group-hover:opacity-100">${table.rows}</span>
</div>
`).join('');
return `
<div class="mb-2">
<div class="px-4 py-2 text-xs text-slate-400 uppercase tracking-wider font-semibold">${label}</div>
${tablesHtml}
</div>
`;
}).join('');
lucide.createIcons();
}
@@ -594,6 +650,12 @@ SELECT * FROM users LIMIT 10;"></textarea>
document.getElementById('emptyState').classList.add('hidden');
document.getElementById('sqlPanel').classList.add('hidden');
document.getElementById('dataGrid').classList.remove('hidden');
// Update action buttons based on permissions
const addBtn = document.querySelector('#tableActions button[onclick="app.showAddRecordModal()"]');
const deleteTableBtn = document.querySelector('#tableActions button[onclick="app.deleteTable()"]');
if (addBtn) addBtn.style.display = this.canEditTable() ? '' : 'none';
if (deleteTableBtn) deleteTableBtn.style.display = this.canDeleteTable() ? '' : 'none';
// Load table structure to get primary key
await this.loadTableStructure();
@@ -658,10 +720,12 @@ SELECT * FROM users LIMIT 10;"></textarea>
}
return `<td class="p-3 border-b border-slate-100">${displayValue}</td>`;
}).join('');
const canEdit = this.canEditTable();
const canDelete = this.canDeleteTable();
const actions = pkValue ? `
<td class="p-3 border-b border-slate-100">
<button onclick="app.editRecord('${pkValue}')" class="text-blue-600 hover:underline mr-2">Редактировать</button>
<button onclick="app.deleteRecord('${pkValue}')" class="text-red-600 hover:underline">Удалить</button>
${canEdit ? `<button onclick="app.editRecord('${pkValue}')" class="text-blue-600 hover:underline mr-2">Редактировать</button>` : ''}
${canDelete ? `<button onclick="app.deleteRecord('${pkValue}')" class="text-red-600 hover:underline">Удалить</button>` : ''}
</td>
` : '<td class="p-3 border-b border-slate-100">-</td>';
@@ -986,6 +1050,7 @@ SELECT * FROM users LIMIT 10;"></textarea>
}
showCreateTableModal() {
document.getElementById('newTableFolder').value = '';
document.getElementById('newTableName').value = '';
document.getElementById('columnsContainer').innerHTML = '';
this.addColumnField(); // Add one default column
@@ -1026,12 +1091,15 @@ SELECT * FROM users LIMIT 10;"></textarea>
}
createTable() {
const name = document.getElementById('newTableName').value;
const folder = document.getElementById('newTableFolder').value.trim();
const name = document.getElementById('newTableName').value.trim();
if (!name) {
this.showToast('Введите название таблицы', 'error');
return;
}
const tableName = folder ? `${folder}__${name}` : name;
const columnElements = document.querySelectorAll('#columnsContainer > div');
const columns = Array.from(columnElements).map(div => {
const inputs = div.querySelectorAll('input, select');
@@ -1051,7 +1119,7 @@ SELECT * FROM users LIMIT 10;"></textarea>
fetch('/api/tables', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, columns })
body: JSON.stringify({ name: tableName, columns })
})
.then(response => response.json())
.then(data => {