This commit is contained in:
2026-01-12 00:55:44 +01:00
parent 25934aa32b
commit 685e356980
3 changed files with 135 additions and 181 deletions

View File

@@ -12,12 +12,11 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
Diese Angaben werden nur verwendet, um die <strong>emailtemplate_bridge.php</strong> zu generieren. Das EmailTemplate-System selbst behält Zugriff auf alle Tabellen; die hier definierten Whitelists greifen ausschließlich in der Bridge-Datei. Diese Angaben werden nur verwendet, um die <strong>emailtemplate_bridge.php</strong> zu generieren. Das EmailTemplate-System selbst behält Zugriff auf alle Tabellen; die hier definierten Whitelists greifen ausschließlich in der Bridge-Datei.
</p> </p>
<div class="text-sm text-slate-600 mb-4 space-y-2"> <div class="text-sm text-slate-600 mb-4 space-y-2">
<p><strong>Voraussetzung:</strong> Bridge-URL + Token in den <a class="underline" href="<?= htmlspecialchars($appBaseUrl . '/admin/settings.php') ?>">Einstellungen</a> hinterlegen.</p> <p><strong>Moegliche Wege zur DB-Anbindung:</strong></p>
<p><strong>Import-Varianten:</strong></p>
<ul class="list-disc ps-5"> <ul class="list-disc ps-5">
<li>Tabellen/Spalten direkt ueber die Bridge-URL laden (Schema).</li> <li>Bridge-URL + Token laden Tabellen/Spalten (Standard, nur Schema).</li>
<li>DB-Settings ueber die Bridge-URL laden und damit Tabellen/Spalten abfragen.</li> <li>Bridge kann zusaetzlich DB-Settings mitsenden (Host, Port, User, Passwort).</li>
<li>Fixe DB-Settings eingeben und damit Tabellen/Spalten abfragen.</li> <li>DB-Settings hier direkt eintragen (wird in die Bridge-Datei geschrieben).</li>
</ul> </ul>
</div> </div>
<form id="bridgeSetupForm" class="space-y-4"> <form id="bridgeSetupForm" class="space-y-4">
@@ -33,25 +32,13 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
<p class="text-xs text-slate-500 mb-3">Die Auswahl bestimmt, welche Werte in die Bridge-Datei geschrieben werden.</p> <p class="text-xs text-slate-500 mb-3">Die Auswahl bestimmt, welche Werte in die Bridge-Datei geschrieben werden.</p>
<div class="flex flex-wrap gap-4 text-sm text-slate-600 mb-3"> <div class="flex flex-wrap gap-4 text-sm text-slate-600 mb-3">
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<input type="radio" name="db_mode" value="bridge" checked> Konfiguration von URL laden <input type="radio" name="db_mode" value="direct" checked> Direkte Angaben (Host, DB, Benutzer …)
</label> </label>
<label class="inline-flex items-center gap-2"> <label class="inline-flex items-center gap-2">
<input type="radio" name="db_mode" value="direct"> Eingabe fixer Datenbank-Einstellungen <input type="radio" name="db_mode" value="config"> Aus bestehender Konfigurationsdatei laden
</label> </label>
</div> </div>
<div id="bridgeFields" class="space-y-2">
<label class="block text-sm text-slate-600">Import-Art</label>
<label class="inline-flex items-center gap-2 text-sm text-slate-600">
<input type="radio" name="bridge_import" value="schema" checked> Tabellen und Spalten importieren
</label>
<p class="text-xs text-slate-500">Liest die aktuelle DB-Struktur direkt von der Bridge-Datei.</p>
<label class="inline-flex items-center gap-2 text-sm text-slate-600">
<input type="radio" name="bridge_import" value="settings"> Datenbankeinstellungen importieren
</label>
<p class="text-xs text-slate-500">Liest DB-Settings ueber die Bridge-URL und nutzt sie zum Abfragen der Tabellen/Spalten.</p>
</div>
<div id="directFields" class="grid md:grid-cols-2 gap-3"> <div id="directFields" class="grid md:grid-cols-2 gap-3">
<label class="block text-sm text-slate-600">Server / Host <label class="block text-sm text-slate-600">Server / Host
<input type="text" name="direct_host" class="input mt-1" placeholder="127.0.0.1"> <input type="text" name="direct_host" class="input mt-1" placeholder="127.0.0.1">
@@ -72,11 +59,46 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
<input type="text" name="direct_password" class="input mt-1" placeholder="••••"> <input type="text" name="direct_password" class="input mt-1" placeholder="••••">
</label> </label>
</div> </div>
<div id="configFields" class="hidden space-y-3">
<div>
<label class="block text-sm text-slate-600">Pfad zur Konfigurationsdatei</label>
<input type="text" name="config_file" class="input mt-1" placeholder="../config/database.php">
<p class="text-xs text-slate-500 mt-1">Relativ zur Bridge-Datei oder absolut. Die Datei sollte ein Array oder Objekt mit den Zugangsdaten liefern.</p>
<button type="button" id="btn-config-example" class="btn mt-2 text-xs" data-role="admin">Beispiel: Array-Mapping</button>
</div>
<div>
<label class="block text-sm text-slate-600">Basis-Pfad im Array (optional)</label>
<input type="text" name="config_base" class="input mt-1" placeholder="database.connections.mysql">
<p class="text-xs text-slate-500 mt-1">Dot-Notation, um in verschachtelte Arrays zu springen.</p>
</div>
<div class="grid md:grid-cols-2 gap-3">
<label class="block text-sm text-slate-600">Host-Key
<input type="text" name="config_host_key" class="input mt-1" placeholder="host">
</label>
<label class="block text-sm text-slate-600">Port-Key
<input type="text" name="config_port_key" class="input mt-1" placeholder="port">
</label>
<label class="block text-sm text-slate-600">DB-Name-Key
<input type="text" name="config_database_key" class="input mt-1" placeholder="database">
</label>
<label class="block text-sm text-slate-600">Charset-Key
<input type="text" name="config_charset_key" class="input mt-1" placeholder="charset">
</label>
<label class="block text-sm text-slate-600">User-Key
<input type="text" name="config_user_key" class="input mt-1" placeholder="username">
</label>
<label class="block text-sm text-slate-600">Passwort-Key
<input type="text" name="config_password_key" class="input mt-1" placeholder="password">
</label>
</div>
<p class="text-xs text-slate-500">Alle Keys nutzen Dot-Notation relativ zum Basis-Pfad.</p>
</div>
</fieldset> </fieldset>
<div class="flex flex-wrap gap-2 items-center"> <div class="flex flex-wrap gap-2 items-center">
<button type="button" id="btn-load-remote" class="btn" data-role="admin">Tabellen/Spalten laden</button> <button type="button" id="btn-load-remote" class="btn" data-role="admin">Tabellen vom Bridge-Endpunkt laden</button>
<span class="text-xs text-slate-500">Nutzt die Bridge-URL/Token oder die direkten DB-Settings (je nach Auswahl).</span> <span class="text-xs text-slate-500">Nutzt Bridge-URL/Token aus den Einstellungen und uebernimmt optional DB-Settings aus der Bridge-Datei.</span>
<button type="submit" class="btn">Bridge-Setup speichern</button> <button type="submit" class="btn">Bridge-Setup speichern</button>
</div> </div>
<div id="setupStatus" class="text-xs text-slate-500">Noch nicht gespeichert.</div> <div id="setupStatus" class="text-xs text-slate-500">Noch nicht gespeichert.</div>
@@ -86,6 +108,39 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
<div id="toast-root"></div> <div id="toast-root"></div>
<dialog id="configExampleDialog" class="rounded-xl max-w-2xl w-[90vw]">
<form method="dialog" class="space-y-3">
<h3 class="text-lg font-semibold">Beispiel: Mapping einer Config-Datei</h3>
<p class="text-sm text-slate-600">Angenommen, deine <code>../config/database.php</code> liefert folgendes Array:</p>
<pre class="bg-slate-900 text-slate-100 text-xs p-3 rounded-lg overflow-auto"><?php echo htmlspecialchars(<<<'PHP'
<?php
return [
'database' => [
'connections' => [
'default' => [
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'kunden_db',
'username' => 'dbuser',
'password' => 'secret',
'charset' => 'utf8mb4',
],
],
],
];
PHP, ENT_QUOTES); ?></pre>
<p class="text-sm text-slate-600">Dann trägst du ein:</p>
<ul class="text-sm text-slate-700 list-disc ps-5">
<li><strong>Pfad zur Konfigurationsdatei:</strong> <code>../config/database.php</code></li>
<li><strong>Basis-Pfad:</strong> <code>database.connections.default</code></li>
<li><strong>Host-/Port-/DB-/User-/Pass-/Charset-Key:</strong> jeweils <code>host</code>, <code>port</code>, <code>database</code>, <code>username</code>, <code>password</code>, <code>charset</code></li>
</ul>
<p class="text-sm text-slate-600">Die Bridge liest dann automatisch die Werte aus diesem Array und baut daraus den DSN.</p>
<div class="text-right">
<button type="submit" class="btn">Verstanden</button>
</div>
</form>
</dialog>
<?php <?php
tpl_add_script(app_asset_url('/assets/js/toast.js')); tpl_add_script(app_asset_url('/assets/js/toast.js'));
tpl_add_script(app_asset_url('/assets/js/bridge-setup.js'), 'footer', false, false, '', null, true); tpl_add_script(app_asset_url('/assets/js/bridge-setup.js'), 'footer', false, false, '', null, true);

View File

@@ -9,27 +9,32 @@ let form;
let tablesInput; let tablesInput;
let tablesPreview; let tablesPreview;
let modeInputs; let modeInputs;
let importInputs;
let directFields; let directFields;
let configFields;
let statusLabel; let statusLabel;
let loadBtn; let loadBtn;
let bridgeFields; let configExampleBtn;
let configExampleDialog;
export function initBridgeSetupPage() { export function initBridgeSetupPage() {
form = document.getElementById('bridgeSetupForm'); form = document.getElementById('bridgeSetupForm');
if (!form) return; if (!form) return;
tablesInput = form.elements.tables; tablesInput = form.elements.tables;
tablesPreview = document.getElementById('selectedTables'); tablesPreview = document.getElementById('selectedTables');
bridgeFields = document.getElementById('bridgeFields');
directFields = document.getElementById('directFields'); directFields = document.getElementById('directFields');
configFields = document.getElementById('configFields');
statusLabel = document.getElementById('setupStatus'); statusLabel = document.getElementById('setupStatus');
loadBtn = document.getElementById('btn-load-remote'); 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"]')); modeInputs = Array.from(form.querySelectorAll('input[name="db_mode"]'));
importInputs = Array.from(form.querySelectorAll('input[name="bridge_import"]'));
form.addEventListener('submit', submitBridgeSetup); form.addEventListener('submit', submitBridgeSetup);
tablesInput?.addEventListener('input', () => updateTablesPreview(parseTablesInput())); tablesInput?.addEventListener('input', () => updateTablesPreview(parseTablesInput()));
loadBtn?.addEventListener('click', loadTablesFromBridge); loadBtn?.addEventListener('click', loadTablesFromBridge);
configExampleBtn?.addEventListener('click', () => {
if (configExampleDialog?.showModal) configExampleDialog.showModal();
});
modeInputs.forEach(input => { modeInputs.forEach(input => {
input.addEventListener('change', () => applyModeVisibility(input.value)); input.addEventListener('change', () => applyModeVisibility(input.value));
}); });
@@ -40,8 +45,7 @@ export function initBridgeSetupPage() {
function defaultSetup() { function defaultSetup() {
return { return {
tables: [], tables: [],
mode: 'bridge', mode: 'direct',
import_type: 'schema',
direct: { direct: {
host: '', host: '',
port: 3306, port: 3306,
@@ -50,6 +54,16 @@ function defaultSetup() {
password: '', password: '',
charset: 'utf8mb4', charset: 'utf8mb4',
}, },
config: {
file: '',
base: '',
host_key: '',
port_key: '',
database_key: '',
user_key: '',
password_key: '',
charset_key: '',
},
}; };
} }
@@ -81,13 +95,6 @@ function fillForm(setup) {
}); });
applyModeVisibility(activeMode); applyModeVisibility(activeMode);
if (importInputs.length) {
const importType = (data.import_type || 'schema').toLowerCase();
importInputs.forEach(input => {
input.checked = input.value === importType;
});
}
if (directFields) { if (directFields) {
const directMap = { const directMap = {
direct_host: data.direct.host || '', direct_host: data.direct.host || '',
@@ -103,12 +110,29 @@ function fillForm(setup) {
}); });
} }
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) { function applyModeVisibility(mode) {
const showDirect = mode === 'direct'; const direct = mode === 'config' ? 'add' : 'remove';
directFields?.classList[showDirect ? 'remove' : 'add']('hidden'); const config = mode === 'config' ? 'remove' : 'add';
bridgeFields?.classList[showDirect ? 'add' : 'remove']('hidden'); directFields?.classList[direct]('hidden');
configFields?.classList[config]('hidden');
} }
function parseTablesInput() { function parseTablesInput() {
@@ -133,17 +157,23 @@ async function submitBridgeSetup(ev) {
ev.preventDefault(); ev.preventDefault();
if (!form) return; if (!form) return;
const mode = form.querySelector('input[name="db_mode"]:checked')?.value || 'direct'; const mode = form.querySelector('input[name="db_mode"]:checked')?.value || 'direct';
const importType = form.querySelector('input[name="bridge_import"]:checked')?.value || 'schema';
const payload = { const payload = {
tables: parseTablesInput(), tables: parseTablesInput(),
mode, mode,
import_type: importType,
direct_host: form.direct_host?.value.trim() || '', direct_host: form.direct_host?.value.trim() || '',
direct_port: Number(form.direct_port?.value || 0) || 3306, direct_port: Number(form.direct_port?.value || 0) || 3306,
direct_database: form.direct_database?.value.trim() || '', direct_database: form.direct_database?.value.trim() || '',
direct_charset: form.direct_charset?.value.trim() || 'utf8mb4', direct_charset: form.direct_charset?.value.trim() || 'utf8mb4',
direct_user: form.direct_user?.value.trim() || '', direct_user: form.direct_user?.value.trim() || '',
direct_password: form.direct_password?.value || '', 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 { try {
@@ -163,26 +193,20 @@ async function loadTablesFromBridge(ev) {
if (!loadBtn) return; if (!loadBtn) return;
loadBtn.disabled = true; loadBtn.disabled = true;
try { try {
const mode = form?.querySelector('input[name="db_mode"]:checked')?.value || 'bridge'; const res = await apiAction('account.bridge.test', { method: 'POST', data: {} });
const importType = form?.querySelector('input[name="bridge_import"]:checked')?.value || 'schema';
const payload = {
mode,
import_type: importType,
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 || '',
};
const res = await apiAction('account.bridge.test', { method: 'POST', data: payload });
if (!res?.ok) throw new Error(res?.error || 'Bridge konnte nicht abgefragt werden'); if (!res?.ok) throw new Error(res?.error || 'Bridge konnte nicht abgefragt werden');
const fetchedTables = normalizeTableNames(res.tables); const fetchedTables = normalizeTableNames(res.tables);
const allowedTables = normalizeTableNames(res.setup_hint?.tables ?? fetchedTables); const allowedTables = normalizeTableNames(res.setup_hint?.tables ?? fetchedTables);
const merged = { const merged = {
...(state.setup || {}), ...(state.setup || {}),
tables: allowedTables, tables: allowedTables,
...(res.setup_hint || {}),
}; };
if (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); fillForm(merged);
updateStatus(`Tabellen geladen (${fetchedTables.length}).`); updateStatus(`Tabellen geladen (${fetchedTables.length}).`);
toast('Tabellen erfolgreich geladen', true); toast('Tabellen erfolgreich geladen', true);
@@ -235,12 +259,11 @@ function normalizeSetupInput(input) {
const base = defaultSetup(); const base = defaultSetup();
if (!input || typeof input !== 'object') return base; if (!input || typeof input !== 'object') return base;
const mode = (input.mode || base.mode).toLowerCase(); const mode = (input.mode || base.mode).toLowerCase();
const validMode = mode === 'direct' ? 'direct' : 'bridge'; const validMode = mode === 'config' ? 'config' : 'direct';
const importType = (input.import_type || base.import_type).toLowerCase();
const validImport = importType === 'settings' ? 'settings' : 'schema';
const tables = normalizeTableNames(input.tables || base.tables); const tables = normalizeTableNames(input.tables || base.tables);
const direct = { ...base.direct, ...(input.direct || {}) }; const direct = { ...base.direct, ...(input.direct || {}) };
direct.port = Number(direct.port || 3306) || 3306; direct.port = Number(direct.port || 3306) || 3306;
direct.charset = direct.charset || 'utf8mb4'; direct.charset = direct.charset || 'utf8mb4';
return { tables, mode: validMode, import_type: validImport, direct }; const config = { ...base.config, ...(input.config || {}) };
return { tables, mode: validMode, direct, config };
} }

View File

@@ -1455,7 +1455,6 @@ class ApiKernel
private function handlePlaceholderSchema(): void private function handlePlaceholderSchema(): void
{ {
$user = $this->requireAuth();
$user = $this->requireAuth(); $user = $this->requireAuth();
$this->ensureRole($user, ['owner', 'admin']); $this->ensureRole($user, ['owner', 'admin']);
$customerId = (int)($user['customer_id'] ?? 0); $customerId = (int)($user['customer_id'] ?? 0);
@@ -1534,79 +1533,6 @@ class ApiKernel
return $decoded; return $decoded;
} }
private function fetchSchemaFromDirect(array $direct, array $tablesAllow = []): array
{
$host = trim((string)($direct['host'] ?? ''));
$dbName = trim((string)($direct['database'] ?? ''));
$user = trim((string)($direct['user'] ?? ''));
$pass = (string)($direct['password'] ?? '');
$charset = trim((string)($direct['charset'] ?? 'utf8mb4')) ?: 'utf8mb4';
$port = (int)($direct['port'] ?? 3306);
if ($host === '' || $dbName === '' || $user === '') {
throw new RuntimeException('DB settings missing');
}
$dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset={$charset}";
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$whitelist = [];
foreach ($tablesAllow as $tbl) {
if (is_string($tbl) && $tbl !== '') {
$whitelist[strtolower($tbl)] = true;
}
}
$tablesStmt = $pdo->query('SHOW FULL TABLES');
$tables = [];
while ($row = $tablesStmt->fetch(PDO::FETCH_NUM)) {
$tableName = $row[0] ?? null;
if (!$tableName) {
continue;
}
if ($whitelist && empty($whitelist[strtolower($tableName)])) {
continue;
}
$columnsStmt = $pdo->prepare(
'SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = :schema AND TABLE_NAME = :table
ORDER BY ORDINAL_POSITION'
);
$columnsStmt->execute([
':schema' => $dbName,
':table' => $tableName,
]);
$columns = [];
foreach ($columnsStmt as $col) {
$columns[] = [
'name' => $col['COLUMN_NAME'],
'type' => $col['DATA_TYPE'],
'nullable' => ($col['IS_NULLABLE'] === 'YES'),
'default' => $col['COLUMN_DEFAULT'],
'key' => $col['COLUMN_KEY'],
'extra' => $col['EXTRA'],
'placeholder' => strtoupper($tableName) . '__' . strtoupper($col['COLUMN_NAME']),
];
}
$tables[] = [
'name' => $tableName,
'columns' => $columns,
];
}
return [
'ok' => true,
'tables' => $tables,
'fetched' => date(DATE_ATOM),
];
}
private function placeholderCachePath(string $url, string $token): string private function placeholderCachePath(string $url, string $token): string
{ {
$hash = md5($url . '|' . $token); $hash = md5($url . '|' . $token);
@@ -2145,33 +2071,6 @@ class ApiKernel
$user = $this->requireAuth(); $user = $this->requireAuth();
$this->ensureRole($user, ['owner', 'admin']); $this->ensureRole($user, ['owner', 'admin']);
$customerId = (int)($user['customer_id'] ?? 0); $customerId = (int)($user['customer_id'] ?? 0);
$setup = $this->getBridgeSetupData($customerId);
$mode = strtolower((string)($this->in['mode'] ?? $this->in['db_mode'] ?? ($setup['mode'] ?? 'bridge')));
$importType = strtolower((string)($this->in['import_type'] ?? $this->in['bridge_import'] ?? ($setup['import_type'] ?? 'schema')));
$tablesAllow = $this->normalizeBridgeTables($this->in['tables'] ?? ($setup['tables'] ?? []));
if ($mode === 'direct') {
$direct = [
'host' => trim((string)($this->in['direct_host'] ?? ($setup['direct']['host'] ?? ''))),
'port' => (int)($this->in['direct_port'] ?? ($setup['direct']['port'] ?? 3306)),
'database' => trim((string)($this->in['direct_database'] ?? ($setup['direct']['database'] ?? ''))),
'user' => trim((string)($this->in['direct_user'] ?? ($setup['direct']['user'] ?? ''))),
'password' => (string)($this->in['direct_password'] ?? ($setup['direct']['password'] ?? '')),
'charset' => trim((string)($this->in['direct_charset'] ?? ($setup['direct']['charset'] ?? 'utf8mb4'))) ?: 'utf8mb4',
];
try {
$schema = $this->fetchSchemaFromDirect($direct, $tablesAllow);
} catch (Throwable $e) {
$this->fail('DB request failed', $e->getMessage(), 502);
return;
}
$this->respond([
'ok' => true,
'tables' => $schema['tables'] ?? [],
'fetched' => $schema['fetched'] ?? date(DATE_ATOM),
]);
}
$bridgeUrl = trim((string)($this->in['bridge_url'] ?? '')); $bridgeUrl = trim((string)($this->in['bridge_url'] ?? ''));
$bridgeToken = trim((string)($this->in['bridge_token'] ?? '')); $bridgeToken = trim((string)($this->in['bridge_token'] ?? ''));
if ($bridgeUrl === '' || $bridgeToken === '') { if ($bridgeUrl === '' || $bridgeToken === '') {
@@ -2189,28 +2088,6 @@ class ApiKernel
return; return;
} }
if ($importType === 'settings') {
$hint = is_array($schema['setup_hint'] ?? null) ? $schema['setup_hint'] : [];
$directHint = is_array($hint['direct'] ?? null) ? $hint['direct'] : [];
$direct = [
'host' => trim((string)($directHint['host'] ?? '')),
'port' => (int)($directHint['port'] ?? 3306),
'database' => trim((string)($directHint['database'] ?? '')),
'user' => trim((string)($directHint['user'] ?? '')),
'password' => (string)($directHint['password'] ?? ''),
'charset' => trim((string)($directHint['charset'] ?? 'utf8mb4')) ?: 'utf8mb4',
];
if ($direct['host'] === '' || $direct['database'] === '' || $direct['user'] === '') {
$this->fail('Bridge lieferte keine DB-Settings', null, 422);
}
try {
$schema = $this->fetchSchemaFromDirect($direct, $tablesAllow);
} catch (Throwable $e) {
$this->fail('DB request failed', $e->getMessage(), 502);
return;
}
}
$this->respond([ $this->respond([
'ok' => true, 'ok' => true,
'tables' => $schema['tables'] ?? [], 'tables' => $schema['tables'] ?? [],
@@ -2401,8 +2278,7 @@ class ApiKernel
{ {
return [ return [
'tables' => [], 'tables' => [],
'mode' => 'bridge', 'mode' => 'direct',
'import_type' => 'schema',
'direct' => [ 'direct' => [
'host' => '', 'host' => '',
'port' => 3306, 'port' => 3306,