up
This commit is contained in:
3
public/admin/bridge.php
Normal file
3
public/admin/bridge.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '../config/fileload.php';
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '../partials/landingpage/admin/bridge.php';
|
||||
242
public/assets/js/bridge-setup-page.js
Normal file
242
public/assets/js/bridge-setup-page.js
Normal file
@@ -0,0 +1,242 @@
|
||||
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;
|
||||
|
||||
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');
|
||||
modeInputs = Array.from(form.querySelectorAll('input[name="db_mode"]'));
|
||||
|
||||
form.addEventListener('submit', submitBridgeSetup);
|
||||
tablesInput?.addEventListener('input', () => updateTablesPreview(parseTablesInput()));
|
||||
loadBtn?.addEventListener('click', loadTablesFromBridge);
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
30
public/assets/js/bridge-setup.js
Normal file
30
public/assets/js/bridge-setup.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { apiAction } from './api.js';
|
||||
import { initUserPanel } from './ui-user.js';
|
||||
import { mountLogoutButton, ensureFloatingLogout } from './ui-auth.js';
|
||||
import { initBridgeSetupPage } from './bridge-setup-page.js';
|
||||
|
||||
async function ensureAuthenticated() {
|
||||
try {
|
||||
const me = await apiAction('auth.me', { method: 'GET' });
|
||||
if (!me?.ok || !me?.user) {
|
||||
if (!window.DISABLE_AUTH_REDIRECT) {
|
||||
window.location.href = '/login.php';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
window.__currentUser = me.user;
|
||||
document.documentElement.classList.remove('auth-pending');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const ok = await ensureAuthenticated();
|
||||
if (!ok) return;
|
||||
initUserPanel();
|
||||
initBridgeSetupPage();
|
||||
mountLogoutButton('#btn-logout', { redirect: '/login.php' });
|
||||
ensureFloatingLogout({ redirect: '/login.php' });
|
||||
});
|
||||
@@ -7,7 +7,6 @@ const state = {
|
||||
userMap: new Map(),
|
||||
senders: [],
|
||||
senderMap: new Map(),
|
||||
bridgeTables: [],
|
||||
currentTab: 'profile',
|
||||
loading: false,
|
||||
};
|
||||
@@ -25,8 +24,6 @@ let teamTable;
|
||||
let userForm;
|
||||
let senderTable;
|
||||
let senderForm;
|
||||
let bridgePreview;
|
||||
let validateBridgeBtn;
|
||||
let menuInitialized = false;
|
||||
let menuOpen = false;
|
||||
let debugButton;
|
||||
@@ -64,8 +61,6 @@ export function initAccountPage() {
|
||||
userForm = document.getElementById('userForm');
|
||||
senderTable = document.getElementById('senderTable');
|
||||
senderForm = document.getElementById('senderForm');
|
||||
bridgePreview = document.getElementById('bridgeTablesPreview');
|
||||
validateBridgeBtn = document.getElementById('btn-validate-bridge');
|
||||
|
||||
document.getElementById('btn-user-add')?.addEventListener('click', () => openUserForm());
|
||||
document.getElementById('userFormCancel')?.addEventListener('click', () => closeUserForm());
|
||||
@@ -80,8 +75,6 @@ export function initAccountPage() {
|
||||
settingsForm?.addEventListener('submit', submitSettingsForm);
|
||||
teamTable?.addEventListener('click', handleTeamTableClick);
|
||||
senderTable?.addEventListener('click', handleSenderTableClick);
|
||||
validateBridgeBtn?.addEventListener('click', validateBridgeSettings);
|
||||
|
||||
document.querySelectorAll('[data-user-tab]').forEach(btn => {
|
||||
btn.addEventListener('click', () => switchTab(btn.getAttribute('data-user-tab')));
|
||||
});
|
||||
@@ -250,11 +243,6 @@ function fillSettingsForm(settings) {
|
||||
settingsForm.bridge_token.value = settings.bridge_token || '';
|
||||
settingsForm.sender_token.value = settings.sender_token || '';
|
||||
settingsForm.external_api_token.value = settings.external_api_token || '';
|
||||
const tables = normalizeTableNames(settings.bridge_tables);
|
||||
if (settingsForm.bridge_tables) {
|
||||
settingsForm.bridge_tables.value = tables.join(', ');
|
||||
}
|
||||
applyBridgePreview(tables);
|
||||
state.rotate = { bridge: false, sender: false, external: false };
|
||||
}
|
||||
|
||||
@@ -301,7 +289,6 @@ async function submitSettingsForm(ev) {
|
||||
rotate_bridge_token: state.rotate.bridge ? 1 : 0,
|
||||
rotate_sender_token: state.rotate.sender ? 1 : 0,
|
||||
rotate_external_token: state.rotate.external ? 1 : 0,
|
||||
bridge_tables: parseBridgeTablesInput(),
|
||||
};
|
||||
try {
|
||||
const res = await apiAction('account.settings.update', { method: 'POST', data });
|
||||
@@ -335,72 +322,6 @@ async function downloadFile(type) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseBridgeTablesInput() {
|
||||
if (!settingsForm) return [];
|
||||
const raw = settingsForm.bridge_tables?.value || '';
|
||||
return raw
|
||||
.split(/[\s,]+/)
|
||||
.map(part => part.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function applyBridgePreview(tables) {
|
||||
state.bridgeTables = normalizeTableNames(tables);
|
||||
if (!bridgePreview) return;
|
||||
if (!state.bridgeTables.length) {
|
||||
bridgePreview.innerHTML = '<span class="text-xs text-slate-500">Keine Einschränkung – alle Tabellen erlaubt.</span>';
|
||||
return;
|
||||
}
|
||||
bridgePreview.innerHTML = state.bridgeTables.map(name => `<span class="chip">${escapeHtml(name)}</span>`).join('');
|
||||
}
|
||||
|
||||
async function validateBridgeSettings(ev) {
|
||||
ev?.preventDefault();
|
||||
if (!settingsForm) return;
|
||||
const data = {
|
||||
bridge_url: settingsForm.bridge_url.value.trim(),
|
||||
bridge_token: settingsForm.bridge_token.value.trim(),
|
||||
};
|
||||
if (!data.bridge_url || !data.bridge_token) {
|
||||
toast('Bitte Bridge-URL und Token angeben', false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await apiAction('account.bridge.test', { method: 'POST', data });
|
||||
if (!res?.ok) throw new Error(res?.error || 'Prüfung fehlgeschlagen');
|
||||
const tables = normalizeTableNames(res.tables);
|
||||
applyBridgePreview(tables);
|
||||
if (settingsForm.bridge_tables) {
|
||||
settingsForm.bridge_tables.value = tables.join(', ');
|
||||
}
|
||||
toast('Bridge erfolgreich geprüft', true);
|
||||
} catch (err) {
|
||||
toast(err.message || 'Prüfung fehlgeschlagen', false);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTableNames(list) {
|
||||
if (!Array.isArray(list)) return [];
|
||||
const seen = new Set();
|
||||
const result = [];
|
||||
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;
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const res = await apiAction('account.users.list', { method: 'GET' });
|
||||
|
||||
Reference in New Issue
Block a user