import { apiAction, toast } from './api.js'; const state = { setup: null, loading: false, allTables: [], selectedTables: [], }; let form; let tablesAllSelect; let tablesSelectedSelect; let tablesAddBtn; let tablesRemoveBtn; let modeInputs; let directFields; let configFields; let statusLabel; let loadBtn; let configExampleBtn; let configExampleDialog; let bridgeSetupDialog; let openBridgeSetupBtn; let closeBridgeSetupBtn; export function initBridgeSetupPage() { form = document.getElementById('bridgeSetupForm'); if (!form) return; tablesAllSelect = document.getElementById('bridgeTablesAll'); tablesSelectedSelect = document.getElementById('bridgeTablesSelected'); tablesAddBtn = document.getElementById('bridgeTablesAdd'); tablesRemoveBtn = document.getElementById('bridgeTablesRemove'); directFields = document.getElementById('directFields'); configFields = document.getElementById('configFields'); statusLabel = document.getElementById('setupStatus'); loadBtn = document.getElementById('btn-load-remote'); configExampleBtn = document.getElementById('btn-config-example'); configExampleDialog = document.getElementById('configExampleDialog'); modeInputs = Array.from(form.querySelectorAll('input[name="db_mode"]')); bridgeSetupDialog = document.getElementById('bridgeSetupDialog'); openBridgeSetupBtn = document.getElementById('btn-open-bridge-setup'); closeBridgeSetupBtn = document.getElementById('btn-close-bridge-setup'); form.addEventListener('submit', submitBridgeSetup); tablesAddBtn?.addEventListener('click', () => addSelectedTables(getSelectedOptions(tablesAllSelect))); tablesRemoveBtn?.addEventListener('click', () => removeSelectedTables(getSelectedOptions(tablesSelectedSelect))); loadBtn?.addEventListener('click', loadTablesFromBridge); configExampleBtn?.addEventListener('click', () => { if (configExampleDialog?.showModal) configExampleDialog.showModal(); }); openBridgeSetupBtn?.addEventListener('click', () => { if (bridgeSetupDialog?.showModal) bridgeSetupDialog.showModal(); if (!state.allTables.length) { loadTablesFromBridge(null, { preserveSelection: state.selectedTables.length > 0 }); } }); closeBridgeSetupBtn?.addEventListener('click', () => { if (bridgeSetupDialog?.open) bridgeSetupDialog.close(); }); modeInputs.forEach(input => { input.addEventListener('change', () => applyModeVisibility(input.value)); }); loadBridgeSetup(); } function defaultSetup() { return { tables: [], mode: 'direct', direct: { host: '', port: 3306, database: '', user: '', password: '', charset: 'utf8mb4', }, config: { file: '', base: '', host_key: '', port_key: '', database_key: '', user_key: '', password_key: '', charset_key: '', }, }; } async function loadBridgeSetup() { state.loading = true; try { const res = await apiAction('account.bridge.setup.get', { method: 'GET' }); if (!res?.ok) throw new Error(res?.error || 'Bridge-Setup konnte nicht geladen werden'); fillForm(res.setup || state.setup || defaultSetup()); updateStatus('Daten geladen.'); } catch (err) { console.error(err); toast(err.message || 'Bridge-Setup konnte nicht geladen werden', false); } finally { state.loading = false; } } function fillForm(setup, options = {}) { const data = normalizeSetupInput(setup); state.setup = data; if (!options.skipTables) { state.selectedTables = data.tables.slice(); updateTableSelects(); } const activeMode = (data.mode || 'direct').toLowerCase(); modeInputs.forEach(input => { input.checked = input.value === activeMode; }); applyModeVisibility(activeMode); if (directFields) { const directMap = { direct_host: data.direct.host || '', direct_port: String(data.direct.port || 3306), direct_database: data.direct.database || '', direct_charset: data.direct.charset || 'utf8mb4', direct_user: data.direct.user || '', direct_password: data.direct.password || '', }; Object.entries(directMap).forEach(([name, value]) => { const input = directFields.querySelector(`[name="${name}"]`); if (input) input.value = value; }); } if (configFields) { const configMap = { config_file: data.config.file || '', config_base: data.config.base || '', config_host_key: data.config.host_key || '', config_port_key: data.config.port_key || '', config_database_key: data.config.database_key || '', config_user_key: data.config.user_key || '', config_password_key: data.config.password_key || '', config_charset_key: data.config.charset_key || '', }; Object.entries(configMap).forEach(([name, value]) => { const input = configFields.querySelector(`[name="${name}"]`); if (input) input.value = value; }); } } function applyModeVisibility(mode) { const direct = mode === 'config' ? 'add' : 'remove'; const config = mode === 'config' ? 'remove' : 'add'; directFields?.classList[direct]('hidden'); configFields?.classList[config]('hidden'); } function updateTableSelects() { const all = state.allTables || []; const selected = state.selectedTables || []; const selectedSet = new Set(selected); const orderedSelected = all.length ? all.filter(name => selectedSet.has(name)) : selected; const available = all.length ? all.filter(name => !selectedSet.has(name)) : []; renderSelect(tablesAllSelect, available, 'Noch keine Tabellen geladen.'); renderSelect(tablesSelectedSelect, orderedSelected, 'Noch keine Tabellen ausgewaehlt.'); } function renderSelect(selectEl, list, emptyLabel) { if (!selectEl) return; selectEl.innerHTML = ''; if (!list.length) { const opt = document.createElement('option'); opt.textContent = emptyLabel; opt.disabled = true; selectEl.appendChild(opt); return; } list.forEach(name => { const opt = document.createElement('option'); opt.value = name; opt.textContent = name; selectEl.appendChild(opt); }); } function getSelectedOptions(selectEl) { if (!selectEl) return []; return Array.from(selectEl.selectedOptions || []).map(opt => opt.value); } function addSelectedTables(list) { if (!list.length) return; state.selectedTables = normalizeTableNames([...state.selectedTables, ...list]); updateTableSelects(); } function removeSelectedTables(list) { if (!list.length) return; const removeSet = new Set(list); state.selectedTables = state.selectedTables.filter(name => !removeSet.has(name)); updateTableSelects(); } async function submitBridgeSetup(ev) { ev.preventDefault(); if (!form) return; const mode = form.querySelector('input[name="db_mode"]:checked')?.value || 'direct'; const payload = { tables: normalizeTableNames(state.selectedTables), mode, direct_host: form.direct_host?.value.trim() || '', direct_port: Number(form.direct_port?.value || 0) || 3306, direct_database: form.direct_database?.value.trim() || '', direct_charset: form.direct_charset?.value.trim() || 'utf8mb4', direct_user: form.direct_user?.value.trim() || '', direct_password: form.direct_password?.value || '', config_file: form.config_file?.value.trim() || '', config_base: form.config_base?.value.trim() || '', config_host_key: form.config_host_key?.value.trim() || '', config_port_key: form.config_port_key?.value.trim() || '', config_database_key: form.config_database_key?.value.trim() || '', config_user_key: form.config_user_key?.value.trim() || '', config_password_key: form.config_password_key?.value.trim() || '', config_charset_key: form.config_charset_key?.value.trim() || '', }; try { const res = await apiAction('account.bridge.setup.save', { method: 'POST', data: payload }); if (!res?.ok) throw new Error(res?.error || 'Bridge-Setup konnte nicht gespeichert werden'); fillForm(res.setup || payload); updateStatus('Bridge-Setup gespeichert.'); toast('Bridge-Setup gespeichert', true); if (bridgeSetupDialog?.open) { bridgeSetupDialog.close(); } window.dispatchEvent(new CustomEvent('bridge-setup-updated', { detail: res.setup || payload })); } catch (err) { console.error(err); toast(err.message || 'Bridge-Setup konnte nicht gespeichert werden', false); } } async function loadTablesFromBridge(ev, options = {}) { ev?.preventDefault(); if (!loadBtn) return; loadBtn.disabled = true; try { const res = await apiAction('account.bridge.test', { method: 'POST', data: {} }); if (!res?.ok) throw new Error(res?.error || 'Bridge konnte nicht abgefragt werden'); const fetchedTables = normalizeTableNames(res.tables); state.allTables = fetchedTables; const preserveSelection = !!options.preserveSelection; const hasSelection = state.selectedTables.length > 0; if (preserveSelection || hasSelection) { state.selectedTables = normalizeTableNames(state.selectedTables); } else { state.selectedTables = fetchedTables.slice(); } if (fetchedTables.length && state.selectedTables.length) { const fetchedSet = new Set(fetchedTables); state.selectedTables = state.selectedTables.filter(name => fetchedSet.has(name)); } if (res.setup_hint) { const merged = { ...(state.setup || {}), ...(res.setup_hint || {}), }; merged.mode = res.setup_hint.mode || merged.mode; merged.direct = { ...(merged.direct || {}), ...(res.setup_hint.direct || {}) }; merged.config = { ...(merged.config || {}), ...(res.setup_hint.config || {}) }; fillForm(merged, { skipTables: true }); } updateTableSelects(); updateStatus(`Tabellen geladen (${fetchedTables.length}).`); toast('Tabellen erfolgreich geladen', true); } catch (err) { console.error(err); toast(err.message || 'Bridge konnte nicht geprüft werden', false); } finally { loadBtn.disabled = false; } } function normalizeTableNames(list) { if (!Array.isArray(list)) return []; const result = []; const seen = new Set(); for (const entry of list) { let name = ''; if (typeof entry === 'string') { name = entry; } else if (entry && typeof entry === 'object') { name = entry.name || entry.table || entry.label || ''; } if (typeof name === 'string') { const trimmed = name.trim(); if (trimmed && !seen.has(trimmed)) { seen.add(trimmed); result.push(trimmed); } } } return result; } function updateStatus(msg) { if (!statusLabel) return; const time = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); statusLabel.textContent = `${msg} (${time})`; } function normalizeSetupInput(input) { const base = defaultSetup(); if (!input || typeof input !== 'object') return base; const mode = (input.mode || base.mode).toLowerCase(); const validMode = mode === 'config' ? 'config' : 'direct'; const tables = normalizeTableNames(input.tables || base.tables); const direct = { ...base.direct, ...(input.direct || {}) }; direct.port = Number(direct.port || 3306) || 3306; direct.charset = direct.charset || 'utf8mb4'; const config = { ...base.config, ...(input.config || {}) }; return { tables, mode: validMode, direct, config }; }