update
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { apiAction } from './api.js';
|
||||
import { initUserPanel, initAccountPage } from './ui-user.js';
|
||||
import { initBridgeSetupPage } from './bridge-setup-page.js';
|
||||
import { mountLogoutButton, ensureFloatingLogout } from './ui-auth.js';
|
||||
|
||||
async function ensureAuthenticated() {
|
||||
@@ -24,6 +25,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (!ok) return;
|
||||
initUserPanel();
|
||||
initAccountPage();
|
||||
initBridgeSetupPage();
|
||||
mountLogoutButton('#btn-logout', { redirect: '/login.php' });
|
||||
ensureFloatingLogout({ redirect: '/login.php' });
|
||||
});
|
||||
|
||||
@@ -3,11 +3,15 @@ import { apiAction, toast } from './api.js';
|
||||
const state = {
|
||||
setup: null,
|
||||
loading: false,
|
||||
allTables: [],
|
||||
selectedTables: [],
|
||||
};
|
||||
|
||||
let form;
|
||||
let tablesInput;
|
||||
let tablesPreview;
|
||||
let tablesAllSelect;
|
||||
let tablesSelectedSelect;
|
||||
let tablesAddBtn;
|
||||
let tablesRemoveBtn;
|
||||
let modeInputs;
|
||||
let directFields;
|
||||
let configFields;
|
||||
@@ -15,12 +19,18 @@ let statusLabel;
|
||||
let loadBtn;
|
||||
let configExampleBtn;
|
||||
let configExampleDialog;
|
||||
let bridgeSetupDialog;
|
||||
let openBridgeSetupBtn;
|
||||
let adminLoadBridgeBtn;
|
||||
let closeBridgeSetupBtn;
|
||||
|
||||
export function initBridgeSetupPage() {
|
||||
form = document.getElementById('bridgeSetupForm');
|
||||
if (!form) return;
|
||||
tablesInput = form.elements.tables;
|
||||
tablesPreview = document.getElementById('selectedTables');
|
||||
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');
|
||||
@@ -28,13 +38,33 @@ export function initBridgeSetupPage() {
|
||||
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');
|
||||
adminLoadBridgeBtn = document.getElementById('btn-admin-load-bridge');
|
||||
closeBridgeSetupBtn = document.getElementById('btn-close-bridge-setup');
|
||||
|
||||
form.addEventListener('submit', submitBridgeSetup);
|
||||
tablesInput?.addEventListener('input', () => updateTablesPreview(parseTablesInput()));
|
||||
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();
|
||||
});
|
||||
adminLoadBridgeBtn?.addEventListener('click', (ev) => {
|
||||
if (bridgeSetupDialog?.showModal && !bridgeSetupDialog.open) {
|
||||
bridgeSetupDialog.showModal();
|
||||
}
|
||||
loadTablesFromBridge(ev, { preserveSelection: state.selectedTables.length > 0 });
|
||||
});
|
||||
modeInputs.forEach(input => {
|
||||
input.addEventListener('change', () => applyModeVisibility(input.value));
|
||||
});
|
||||
@@ -82,12 +112,12 @@ async function loadBridgeSetup() {
|
||||
}
|
||||
}
|
||||
|
||||
function fillForm(setup) {
|
||||
function fillForm(setup, options = {}) {
|
||||
const data = normalizeSetupInput(setup);
|
||||
state.setup = data;
|
||||
if (tablesInput) {
|
||||
tablesInput.value = data.tables.join(', ');
|
||||
updateTablesPreview(data.tables);
|
||||
if (!options.skipTables) {
|
||||
state.selectedTables = data.tables.slice();
|
||||
updateTableSelects();
|
||||
}
|
||||
const activeMode = (data.mode || 'direct').toLowerCase();
|
||||
modeInputs.forEach(input => {
|
||||
@@ -135,22 +165,50 @@ function applyModeVisibility(mode) {
|
||||
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 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 updateTablesPreview(list) {
|
||||
if (!tablesPreview) return;
|
||||
function renderSelect(selectEl, list, emptyLabel) {
|
||||
if (!selectEl) return;
|
||||
selectEl.innerHTML = '';
|
||||
if (!list.length) {
|
||||
tablesPreview.innerHTML = '<span class="text-xs text-slate-500">Noch keine Tabellen angegeben.</span>';
|
||||
const opt = document.createElement('option');
|
||||
opt.textContent = emptyLabel;
|
||||
opt.disabled = true;
|
||||
selectEl.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
tablesPreview.innerHTML = list.map(name => `<span class="chip">${escapeHtml(name)}</span>`).join('');
|
||||
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) {
|
||||
@@ -158,7 +216,7 @@ async function submitBridgeSetup(ev) {
|
||||
if (!form) return;
|
||||
const mode = form.querySelector('input[name="db_mode"]:checked')?.value || 'direct';
|
||||
const payload = {
|
||||
tables: parseTablesInput(),
|
||||
tables: normalizeTableNames(state.selectedTables),
|
||||
mode,
|
||||
direct_host: form.direct_host?.value.trim() || '',
|
||||
direct_port: Number(form.direct_port?.value || 0) || 3306,
|
||||
@@ -182,13 +240,17 @@ async function submitBridgeSetup(ev) {
|
||||
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) {
|
||||
async function loadTablesFromBridge(ev, options = {}) {
|
||||
ev?.preventDefault();
|
||||
if (!loadBtn) return;
|
||||
loadBtn.disabled = true;
|
||||
@@ -196,18 +258,32 @@ async function loadTablesFromBridge(ev) {
|
||||
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);
|
||||
const allowedTables = normalizeTableNames(res.setup_hint?.tables ?? fetchedTables);
|
||||
const merged = {
|
||||
...(state.setup || {}),
|
||||
tables: allowedTables,
|
||||
...(res.setup_hint || {}),
|
||||
};
|
||||
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 });
|
||||
}
|
||||
fillForm(merged);
|
||||
|
||||
updateTableSelects();
|
||||
updateStatus(`Tabellen geladen (${fetchedTables.length}).`);
|
||||
toast('Tabellen erfolgreich geladen', true);
|
||||
} catch (err) {
|
||||
@@ -246,15 +322,6 @@ function updateStatus(msg) {
|
||||
statusLabel.textContent = `${msg} (${time})`;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function normalizeSetupInput(input) {
|
||||
const base = defaultSetup();
|
||||
if (!input || typeof input !== 'object') return base;
|
||||
|
||||
@@ -36,6 +36,10 @@ let consoleContainer;
|
||||
let debugStylesInjected = false;
|
||||
let consolePatched = false;
|
||||
const consoleBuffer = [];
|
||||
let adminTablesAllSelect;
|
||||
let adminTablesSelectedSelect;
|
||||
let adminTablesAddBtn;
|
||||
let adminTablesRemoveBtn;
|
||||
|
||||
ensureConsoleCapture();
|
||||
|
||||
@@ -61,6 +65,10 @@ export function initAccountPage() {
|
||||
userForm = document.getElementById('userForm');
|
||||
senderTable = document.getElementById('senderTable');
|
||||
senderForm = document.getElementById('senderForm');
|
||||
adminTablesAllSelect = document.getElementById('adminBridgeTablesAll');
|
||||
adminTablesSelectedSelect = document.getElementById('adminBridgeTablesSelected');
|
||||
adminTablesAddBtn = document.getElementById('adminBridgeTablesAdd');
|
||||
adminTablesRemoveBtn = document.getElementById('adminBridgeTablesRemove');
|
||||
|
||||
document.getElementById('btn-user-add')?.addEventListener('click', () => openUserForm());
|
||||
document.getElementById('userFormCancel')?.addEventListener('click', () => closeUserForm());
|
||||
@@ -98,6 +106,18 @@ export function initAccountPage() {
|
||||
});
|
||||
}
|
||||
|
||||
adminTablesAddBtn?.addEventListener('click', () => {
|
||||
addAdminTables(getSelectedOptions(adminTablesAllSelect));
|
||||
});
|
||||
adminTablesRemoveBtn?.addEventListener('click', () => {
|
||||
removeAdminTables(getSelectedOptions(adminTablesSelectedSelect));
|
||||
});
|
||||
|
||||
window.addEventListener('bridge-setup-updated', (ev) => {
|
||||
const setup = ev?.detail || {};
|
||||
refreshAdminTables(setup.tables || [], state.settings.bridge_tables || []);
|
||||
});
|
||||
|
||||
switchTab(state.currentTab);
|
||||
loadAccountData();
|
||||
updateRoleVisibility();
|
||||
@@ -244,6 +264,7 @@ function fillSettingsForm(settings) {
|
||||
settingsForm.sender_token.value = settings.sender_token || '';
|
||||
settingsForm.external_api_token.value = settings.external_api_token || '';
|
||||
state.rotate = { bridge: false, sender: false, external: false };
|
||||
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
|
||||
}
|
||||
|
||||
async function submitProfileForm(ev) {
|
||||
@@ -281,11 +302,13 @@ async function submitPasswordForm(ev) {
|
||||
|
||||
async function submitSettingsForm(ev) {
|
||||
ev.preventDefault();
|
||||
const bridgeTables = normalizeTableList(state.settings.bridge_tables || []);
|
||||
const data = {
|
||||
bridge_url: settingsForm.bridge_url.value.trim(),
|
||||
bridge_token: settingsForm.bridge_token.value.trim(),
|
||||
sender_token: settingsForm.sender_token.value.trim(),
|
||||
external_api_token: settingsForm.external_api_token.value.trim(),
|
||||
bridge_tables: bridgeTables,
|
||||
rotate_bridge_token: state.rotate.bridge ? 1 : 0,
|
||||
rotate_sender_token: state.rotate.sender ? 1 : 0,
|
||||
rotate_external_token: state.rotate.external ? 1 : 0,
|
||||
@@ -322,6 +345,83 @@ async function downloadFile(type) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTableList(input) {
|
||||
const items = Array.isArray(input) ? input : (typeof input === 'string' ? input.split(/[\s,]+/) : []);
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
items.forEach(entry => {
|
||||
const name = String(entry || '').trim();
|
||||
if (name && !seen.has(name)) {
|
||||
seen.add(name);
|
||||
result.push(name);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function refreshAdminTables(availableTables, selectedTables) {
|
||||
const whitelist = normalizeTableList(availableTables);
|
||||
let selected = normalizeTableList(selectedTables);
|
||||
if (!selected.length) {
|
||||
selected = whitelist.slice();
|
||||
}
|
||||
if (whitelist.length) {
|
||||
selected = selected.filter(name => whitelist.includes(name));
|
||||
}
|
||||
state.settings.bridge_tables = selected;
|
||||
state.settings.bridge_setup = state.settings.bridge_setup || {};
|
||||
state.settings.bridge_setup.tables = whitelist;
|
||||
updateAdminTableSelects(whitelist, selected);
|
||||
}
|
||||
|
||||
function updateAdminTableSelects(availableTables, selectedTables) {
|
||||
const selectedSet = new Set(selectedTables);
|
||||
const available = availableTables.filter(name => !selectedSet.has(name));
|
||||
renderSelect(adminTablesAllSelect, available, 'Keine Tabellen freigegeben.');
|
||||
renderSelect(adminTablesSelectedSelect, selectedTables, '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 addAdminTables(list) {
|
||||
const whitelist = normalizeTableList(state.settings.bridge_setup?.tables || []);
|
||||
if (!whitelist.length) return;
|
||||
const selected = normalizeTableList(state.settings.bridge_tables || []);
|
||||
const merged = normalizeTableList([...selected, ...list]).filter(name => whitelist.includes(name));
|
||||
state.settings.bridge_tables = merged;
|
||||
updateAdminTableSelects(whitelist, merged);
|
||||
}
|
||||
|
||||
function removeAdminTables(list) {
|
||||
const whitelist = normalizeTableList(state.settings.bridge_setup?.tables || []);
|
||||
if (!whitelist.length) return;
|
||||
const removeSet = new Set(list);
|
||||
const next = normalizeTableList(state.settings.bridge_tables || []).filter(name => !removeSet.has(name));
|
||||
state.settings.bridge_tables = next;
|
||||
updateAdminTableSelects(whitelist, next);
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const res = await apiAction('account.users.list', { method: 'GET' });
|
||||
|
||||
Reference in New Issue
Block a user