let currentSchema = null; let currentTable = null; let currentMeta = null; let table = null; let enterHandler = null; // Простая обёртка для fetch JSON async function api(url, method = 'GET', body) { const opts = { method, headers: { 'Content-Type': 'application/json' } }; if (body) opts.body = JSON.stringify(body); const res = await fetch(url, opts); if (!res.ok) { const txt = await res.text(); throw new Error(`HTTP ${res.status}: ${txt}`); } return res.json(); } // Логин document.getElementById('loginBtn').addEventListener('click', async () => { console.log('=== LOGIN BUTTON CLICKED ==='); const user = document.getElementById('loginUser').value.trim(); const pass = document.getElementById('loginPass').value; const statusEl = document.getElementById('loginStatus'); if (!user || !pass) { statusEl.textContent = 'Введите логин и пароль'; statusEl.style.color = 'red'; return; } statusEl.textContent = 'Проверяем подключение...'; statusEl.style.color = ''; try { console.log('Отправляем /api/login:', { user, pass: '***' }); const res = await api('/api/login', 'POST', { user, pass }); console.log('Login ответ:', res); if (res.ok) { statusEl.textContent = '✓ Авторизация успешна'; statusEl.style.color = 'green'; await loadTree(); } else { statusEl.textContent = 'Ошибка: ' + (res.error || 'Неизвестная ошибка'); statusEl.style.color = 'red'; } } catch (e) { console.error('Login ошибка:', e); statusEl.textContent = 'Ошибка подключения: ' + e.message; statusEl.style.color = 'red'; } }); // loadTree async function loadTree() { console.log('=== LOAD TREE ВЫЗВАН ==='); const treeEl = document.getElementById('tree'); treeEl.style.color = ''; treeEl.innerHTML = 'Загрузка...'; try { const tree = await api('/api/tree'); console.log('Дерево получено:', tree); treeEl.innerHTML = ''; tree.forEach(schema => { const schemaEl = document.createElement('div'); schemaEl.className = 'schema'; schemaEl.textContent = schema.name; schemaEl.style.cursor = 'pointer'; schemaEl.style.padding = '4px'; schemaEl.style.fontWeight = 'bold'; treeEl.appendChild(schemaEl); (schema.tables || []).forEach(tbl => { const tableEl = document.createElement('div'); tableEl.className = 'table'; tableEl.textContent = ` ${tbl}`; tableEl.style.cursor = 'pointer'; tableEl.style.paddingLeft = '12px'; tableEl.style.padding = '2px'; tableEl.addEventListener('click', () => selectTable(schema.name, tbl)); treeEl.appendChild(tableEl); }); }); } catch (e) { console.error('loadTree ошибка:', e); treeEl.innerHTML = 'Ошибка загрузки: ' + e.message; treeEl.style.color = 'red'; } } let lastEditedRow = null; async function selectTable(schema, tableName) { currentSchema = schema; currentTable = tableName; lastEditedRow = null; console.log('=== SELECT TABLE ===', { schema, tableName }); if (enterHandler) { document.removeEventListener('keydown', enterHandler); enterHandler = null; } currentMeta = await api( `/api/table/meta?schema=${encodeURIComponent(schema)}&table=${encodeURIComponent(tableName)}` ); const columns = currentMeta.columns.map(col => ({ title: col.COLUMN_NAME, field: col.COLUMN_NAME, editor: "input", headerFilter: "input" })); if (table) { table.destroy(); table = null; } table = new Tabulator("#table", { selectableRows: 1, columns: columns, layout: "fitColumns", resizableColumnFit: true, pagination: true, paginationMode: "remote", paginationSize: 50, paginationSizeSelector: [25, 50, 100, 200], filterMode: "remote", sortMode: "remote", ajaxURL: "/api/table/data", ajaxConfig: "POST", ajaxContentType: "json", ajaxParams: function () { const headerFilters = this.getHeaderFilters ? this.getHeaderFilters() : []; const filters = (headerFilters || []).map(f => ({ field: f.field, value: f.value })).filter(f => f.value !== null && f.value !== ''); const sorters = this.getSorters ? this.getSorters() : []; const sort = (sorters && sorters.length > 0) ? { field: sorters[0].field, dir: sorters[0].dir } : null; const params = { schema: currentSchema, table: currentTable, filters: filters, sort: sort, columns: currentMeta.columns, page: this.getPage ? this.getPage() : 1, pageSize: this.getPageSize ? this.getPageSize() : 50 }; console.log('📤 Запрос к серверу:', { page: params.page, pageSize: params.pageSize, filters: params.filters, sort: params.sort }); return params; }, ajaxResponse: function (url, params, response) { console.log('📥 Ответ от сервера:', { page: response.current_page, last_page: response.last_page, total: response.total, rows: response.data ? response.data.length : 0 }); return { last_page: response.last_page || 1, data: response.data || [] }; }, rowClick: function(e, row) { row.toggleSelect(); }, cellEdited: function(cell) { const row = cell.getRow(); lastEditedRow = row.getData(); row.getElement().style.backgroundColor = '#fffae6'; console.log('Ячейка отредактирована:', cell.getField(), '→', cell.getValue()); }, headerFilterLiveFilterDelay: 800, dataFiltering: function(filters) { const activeFilters = filters.filter(f => f.value); console.log('🔍 Активные фильтры:', activeFilters.map(f => `${f.field}=${f.value}`)); }, dataFiltered: function(filters, rows) { console.log('✅ Фильтрация применена, записей на странице:', rows.length); }, pageLoaded: function(pageno) { console.log('📄 Загружена страница:', pageno); } }); enterHandler = async function(e) { if (e.key === 'Enter' && lastEditedRow && currentSchema && currentTable) { e.preventDefault(); console.log('Enter нажат, сохраняем строку:', lastEditedRow); try { const res = await api('/api/table/update', 'POST', { schema: currentSchema, table: currentTable, row: lastEditedRow }); console.log('Сохранено:', res); lastEditedRow = null; table.getRows().forEach(r => { r.getElement().style.backgroundColor = ''; }); await table.replaceData(); } catch (err) { console.error('Ошибка сохранения:', err); alert('Ошибка: ' + err.message); } } }; document.addEventListener('keydown', enterHandler); } // CRUD кнопки document.getElementById('btnInsert').addEventListener('click', async () => { if (!currentSchema || !currentTable || !currentMeta) { alert('Сначала выберите таблицу'); return; } const rowData = {}; const fkFields = []; currentMeta.columns.forEach(col => { const name = col.COLUMN_NAME; if (col.IS_AUTO_INCREMENT) return; if (col.HAS_DEFAULT && col.COLUMN_DEFAULT !== null) { if (String(col.COLUMN_DEFAULT).toUpperCase().includes('CURRENT_TIMESTAMP')) return; rowData[name] = col.COLUMN_DEFAULT; return; } if (col.IS_FOREIGN_KEY) { fkFields.push({ name: name, ref_schema: col.FOREIGN_KEY.ref_schema, ref_table: col.FOREIGN_KEY.ref_table, ref_column: col.FOREIGN_KEY.ref_column, required: col.IS_REQUIRED }); if (!col.IS_REQUIRED) { rowData[name] = null; } return; } if (col.IS_REQUIRED) { if (col.EDITOR_TYPE === 'number') rowData[name] = 0; else if (col.EDITOR_TYPE === 'datetime' || col.EDITOR_TYPE === 'date') rowData[name] = new Date().toISOString().slice(0, 10); else if (col.EDITOR_TYPE === 'time') rowData[name] = '00:00:00'; else rowData[name] = ''; } }); // ✅ Если есть обязательные FK - показываем модальное окно для выбора const requiredFKs = fkFields.filter(f => f.required); if (requiredFKs.length > 0) { try { const fkValues = await promptForForeignKeys(requiredFKs); if (!fkValues) { // Пользователь отменил return; } // Добавляем выбранные FK значения Object.assign(rowData, fkValues); } catch (err) { console.error('Ошибка получения FK значений:', err); alert('Ошибка: ' + err.message); return; } } try { await api('/api/table/insert', 'POST', { schema: currentSchema, table: currentTable, row: rowData }); await table.replaceData(); alert('✓ Строка успешно создана'); } catch (e) { console.error('Ошибка вставки:', e); alert('Ошибка вставки: ' + e.message); } }); // ✅ Функция для выбора FK значений async function promptForForeignKeys(fkFields) { // Создаём модальное окно const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; `; const dialog = document.createElement('div'); dialog.style.cssText = ` background: white; padding: 20px; border-radius: 8px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; `; let html = '