import { apiAction, toast } from './api.js'; const state = { setup: null, loading: false, }; let form; let tablesInput; let tablesPreview; let modeInputs; let directFields; let configFields; let statusLabel; let loadBtn; let importBtn; let importInput; export function initBridgeSetupPage() { form = document.getElementById('bridgeSetupForm'); if (!form) return; tablesInput = form.elements.tables; tablesPreview = document.getElementById('selectedTables'); directFields = document.getElementById('directFields'); configFields = document.getElementById('configFields'); statusLabel = document.getElementById('setupStatus'); loadBtn = document.getElementById('btn-load-remote'); importBtn = document.getElementById('btn-import-bridge'); importInput = document.getElementById('bridgeImportInput'); modeInputs = Array.from(form.querySelectorAll('input[name="db_mode"]')); form.addEventListener('submit', submitBridgeSetup); tablesInput?.addEventListener('input', () => updateTablesPreview(parseTablesInput())); loadBtn?.addEventListener('click', loadTablesFromBridge); importBtn?.addEventListener('click', () => importInput?.click()); importInput?.addEventListener('change', handleBridgeImport); 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'); state.setup = res.setup || defaultSetup(); fillForm(state.setup); 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) { const data = { ...defaultSetup(), ...(setup || {}) }; if (tablesInput) { tablesInput.value = (data.tables || []).join(', '); updateTablesPreview(parseTablesInput()); } 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; }); } } async function handleBridgeImport(ev) { const file = ev.target?.files?.[0]; if (!file) return; try { const text = await file.text(); const parsed = parseBridgeFile(text); if (!parsed) { toast('Bridge-Datei konnte nicht erkannt werden', false); return; } state.setup = { ...defaultSetup(), ...state.setup, ...parsed }; fillForm(state.setup); updateTablesPreview(parseTablesInput()); updateStatus('Bridge-Datei importiert'); toast('Bridge-Daten übernommen', true); } catch (err) { console.error(err); toast('Bridge-Datei konnte nicht gelesen werden', false); } finally { if (importInput) { importInput.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 parseTablesInput() { if (!tablesInput) return []; return tablesInput.value .split(/[\s,]+/) .map(part => part.trim()) .filter(Boolean) .filter((value, index, arr) => arr.indexOf(value) === index); } function updateTablesPreview(list) { if (!tablesPreview) return; if (!list.length) { tablesPreview.innerHTML = 'Noch keine Tabellen angegeben.'; return; } tablesPreview.innerHTML = list.map(name => `${escapeHtml(name)}`).join(''); } async function submitBridgeSetup(ev) { ev.preventDefault(); if (!form) return; const mode = form.querySelector('input[name="db_mode"]:checked')?.value || 'direct'; const payload = { tables: parseTablesInput(), 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'); state.setup = res.setup || payload; fillForm(state.setup); updateStatus('Bridge-Setup gespeichert.'); toast('Bridge-Setup gespeichert', true); } catch (err) { console.error(err); toast(err.message || 'Bridge-Setup konnte nicht gespeichert werden', false); } } async function loadTablesFromBridge(ev) { 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 tables = normalizeTableNames(res.tables); if (tablesInput) { tablesInput.value = tables.join(', '); updateTablesPreview(tables); } updateStatus(`Tabellen geladen (${tables.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 escapeHtml(str) { return String(str || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function parseBridgeFile(text) { if (typeof text !== 'string' || !text.includes('EmailTemplate Bridge')) { return null; } const result = defaultSetup(); const tablesMatch = text.match(/'tables_allow'\s*=>\s*\[([^\]]*)\]/s); if (tablesMatch) { result.tables = tablesMatch[1] .split(',') .map(entry => entry.replace(/['\s]/g, '').trim()) .filter(Boolean) .filter((value, index, arr) => arr.indexOf(value) === index); } const dsnMatch = text.match(/'dsn'\s*=>\s*'([^']+)'/); const userMatch = text.match(/'user'\s*=>\s*'([^']*)'/); const passMatch = text.match(/'pass'\s*=>\s*'([^']*)'/); if (dsnMatch) { const dsn = dsnMatch[1]; const dsnParts = Object.fromEntries(dsn.split(';').map(part => { const [key, value] = part.split('='); return [key?.trim(), value?.trim()]; })); result.direct.host = dsnParts['mysql:host'] || ''; result.direct.port = Number(dsnParts.port || 3306) || 3306; result.direct.database = dsnParts.dbname || ''; result.direct.charset = dsnParts.charset || 'utf8mb4'; result.mode = 'direct'; } if (userMatch) result.direct.user = userMatch[1]; if (passMatch) result.direct.password = passMatch[1]; const snippetMatch = text.match(/\*\* Bridge DB Setup: automatisch generiertes Mapping \*\/([\s\S]+?)\n\}/); if (snippetMatch) { result.mode = 'config'; const snippet = snippetMatch[1]; result.config.file = parseConfigFileExpression(snippet); result.config.base = parseBridgeBasePath(snippet); result.config.host_key = parseBridgeArrayPath(snippet, 'bridgeDbHost'); result.config.port_key = parseBridgeArrayPath(snippet, 'bridgeDbPort'); result.config.database_key = parseBridgeArrayPath(snippet, 'bridgeDbName'); result.config.user_key = parseBridgeArrayPath(snippet, 'bridgeDbUser'); result.config.password_key = parseBridgeArrayPath(snippet, 'bridgeDbPass'); result.config.charset_key = parseBridgeArrayPath(snippet, 'bridgeDbCharset'); } return result; } function parseConfigFileExpression(snippet) { const match = snippet.match(/\$bridgeConfigFile\s*=\s*(.+);/); if (!match) return ''; const expr = match[1].trim(); const dirMatch = expr.match(/__DIR__\s*\.\s*'([^']+)'/); if (dirMatch) { return dirMatch[1]; } const quoteMatch = expr.match(/'([^']+)'/); return quoteMatch ? quoteMatch[1] : expr.replace(/[';]/g, '').trim(); } function parseBridgeArrayPath(snippet, variableName) { const regex = new RegExp('\\$' + variableName + "[\\s=\\(\\)a-zA-Z0-9]*bridge_array_get\\\\(\\$bridgeConfigData,\\s*'([^']*)'", 'i'); const match = snippet.match(regex); return match ? match[1] : ''; } function parseBridgeBasePath(snippet) { const regex = /bridge_array_get\(\$bridgeConfigSource,\s*'([^']*)',\s*\$bridgeConfigData\)/; const match = snippet.match(regex); return match ? match[1] : ''; }