Files
emailtemplate.it/public/assets/js/bridge-setup-page.js
2025-12-09 00:36:05 +01:00

347 lines
11 KiB
JavaScript

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 = '<span class="text-xs text-slate-500">Noch keine Tabellen angegeben.</span>';
return;
}
tablesPreview.innerHTML = list.map(name => `<span class="chip">${escapeHtml(name)}</span>`).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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
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] : '';
}