asdasd
This commit is contained in:
@@ -33,6 +33,7 @@ $bridgeConfig = [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'tables_allow' => [], // optional whitelist: ['customers', 'orders']
|
'tables_allow' => [], // optional whitelist: ['customers', 'orders']
|
||||||
|
'setup_hint' => '__SETUP_HINT__',
|
||||||
];
|
];
|
||||||
|
|
||||||
// {{BRIDGE_DB_SETUP}}
|
// {{BRIDGE_DB_SETUP}}
|
||||||
@@ -199,6 +200,7 @@ while ($row = $tablesStmt->fetch(PDO::FETCH_NUM)) {
|
|||||||
bridgeRespond([
|
bridgeRespond([
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
'tables' => $tables,
|
'tables' => $tables,
|
||||||
|
'setup_hint' => $bridgeConfig['setup_hint'] ?? null,
|
||||||
'fetched' => date(DATE_ATOM),
|
'fetched' => date(DATE_ATOM),
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ $debugRedirect = isset($_GET['debug_redirect']);
|
|||||||
<label class="block text-sm text-slate-600">Pfad zur Konfigurationsdatei</label>
|
<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">
|
<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>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm text-slate-600">Basis-Pfad im Array (optional)</label>
|
<label class="block text-sm text-slate-600">Basis-Pfad im Array (optional)</label>
|
||||||
@@ -153,9 +154,43 @@ $debugRedirect = isset($_GET['debug_redirect']);
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div id="toast-root"></div>
|
<div id="toast-root"></div>
|
||||||
|
|
||||||
<script src="<?= $assetBase ?>/assets/js/toast.js?v=<?= htmlspecialchars($assetVersion, ENT_QUOTES) ?>"></script>
|
<dialog id="configExampleDialog" class="rounded-xl max-w-2xl w-[90vw]">
|
||||||
<script type="module" src="<?= $assetBase ?>/assets/js/bridge-setup.js?v=<?= htmlspecialchars($assetVersion, ENT_QUOTES) ?>"></script>
|
<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>
|
||||||
|
|
||||||
|
<script src="<?= $assetBase ?>/assets/js/toast.js?v=<?= htmlspecialchars($assetVersion, ENT_QUOTES) ?>"></script>
|
||||||
|
<script type="module" src="<?= $assetBase ?>/assets/js/bridge-setup.js?v=<?= htmlspecialchars($assetVersion, ENT_QUOTES) ?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ let statusLabel;
|
|||||||
let loadBtn;
|
let loadBtn;
|
||||||
let importBtn;
|
let importBtn;
|
||||||
let importInput;
|
let importInput;
|
||||||
|
let configExampleBtn;
|
||||||
|
let configExampleDialog;
|
||||||
|
|
||||||
export function initBridgeSetupPage() {
|
export function initBridgeSetupPage() {
|
||||||
form = document.getElementById('bridgeSetupForm');
|
form = document.getElementById('bridgeSetupForm');
|
||||||
@@ -27,6 +29,8 @@ export function initBridgeSetupPage() {
|
|||||||
loadBtn = document.getElementById('btn-load-remote');
|
loadBtn = document.getElementById('btn-load-remote');
|
||||||
importBtn = document.getElementById('btn-import-bridge');
|
importBtn = document.getElementById('btn-import-bridge');
|
||||||
importInput = document.getElementById('bridgeImportInput');
|
importInput = document.getElementById('bridgeImportInput');
|
||||||
|
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"]'));
|
||||||
|
|
||||||
form.addEventListener('submit', submitBridgeSetup);
|
form.addEventListener('submit', submitBridgeSetup);
|
||||||
@@ -34,6 +38,9 @@ export function initBridgeSetupPage() {
|
|||||||
loadBtn?.addEventListener('click', loadTablesFromBridge);
|
loadBtn?.addEventListener('click', loadTablesFromBridge);
|
||||||
importBtn?.addEventListener('click', () => importInput?.click());
|
importBtn?.addEventListener('click', () => importInput?.click());
|
||||||
importInput?.addEventListener('change', handleBridgeImport);
|
importInput?.addEventListener('change', handleBridgeImport);
|
||||||
|
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));
|
||||||
});
|
});
|
||||||
@@ -71,8 +78,7 @@ async function loadBridgeSetup() {
|
|||||||
try {
|
try {
|
||||||
const res = await apiAction('account.bridge.setup.get', { method: 'GET' });
|
const res = await apiAction('account.bridge.setup.get', { method: 'GET' });
|
||||||
if (!res?.ok) throw new Error(res?.error || 'Bridge-Setup konnte nicht geladen werden');
|
if (!res?.ok) throw new Error(res?.error || 'Bridge-Setup konnte nicht geladen werden');
|
||||||
state.setup = res.setup || defaultSetup();
|
fillForm(res.setup || state.setup || defaultSetup());
|
||||||
fillForm(state.setup);
|
|
||||||
updateStatus('Daten geladen.');
|
updateStatus('Daten geladen.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -83,10 +89,11 @@ async function loadBridgeSetup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fillForm(setup) {
|
function fillForm(setup) {
|
||||||
const data = { ...defaultSetup(), ...(setup || {}) };
|
const data = normalizeSetupInput(setup);
|
||||||
|
state.setup = data;
|
||||||
if (tablesInput) {
|
if (tablesInput) {
|
||||||
tablesInput.value = (data.tables || []).join(', ');
|
tablesInput.value = data.tables.join(', ');
|
||||||
updateTablesPreview(parseTablesInput());
|
updateTablesPreview(data.tables);
|
||||||
}
|
}
|
||||||
const activeMode = (data.mode || 'direct').toLowerCase();
|
const activeMode = (data.mode || 'direct').toLowerCase();
|
||||||
modeInputs.forEach(input => {
|
modeInputs.forEach(input => {
|
||||||
@@ -137,9 +144,7 @@ async function handleBridgeImport(ev) {
|
|||||||
toast('Bridge-Datei konnte nicht erkannt werden', false);
|
toast('Bridge-Datei konnte nicht erkannt werden', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.setup = { ...defaultSetup(), ...state.setup, ...parsed };
|
fillForm({ ...state.setup, ...parsed });
|
||||||
fillForm(state.setup);
|
|
||||||
updateTablesPreview(parseTablesInput());
|
|
||||||
updateStatus('Bridge-Datei importiert');
|
updateStatus('Bridge-Datei importiert');
|
||||||
toast('Bridge-Daten übernommen', true);
|
toast('Bridge-Daten übernommen', true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -203,8 +208,7 @@ async function submitBridgeSetup(ev) {
|
|||||||
try {
|
try {
|
||||||
const res = await apiAction('account.bridge.setup.save', { method: 'POST', data: payload });
|
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');
|
if (!res?.ok) throw new Error(res?.error || 'Bridge-Setup konnte nicht gespeichert werden');
|
||||||
state.setup = res.setup || payload;
|
fillForm(res.setup || payload);
|
||||||
fillForm(state.setup);
|
|
||||||
updateStatus('Bridge-Setup gespeichert.');
|
updateStatus('Bridge-Setup gespeichert.');
|
||||||
toast('Bridge-Setup gespeichert', true);
|
toast('Bridge-Setup gespeichert', true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -220,12 +224,20 @@ async function loadTablesFromBridge(ev) {
|
|||||||
try {
|
try {
|
||||||
const res = await apiAction('account.bridge.test', { method: 'POST', data: {} });
|
const res = await apiAction('account.bridge.test', { method: 'POST', data: {} });
|
||||||
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 tables = normalizeTableNames(res.tables);
|
const fetchedTables = normalizeTableNames(res.tables);
|
||||||
if (tablesInput) {
|
const allowedTables = normalizeTableNames(res.setup_hint?.tables ?? fetchedTables);
|
||||||
tablesInput.value = tables.join(', ');
|
const merged = {
|
||||||
updateTablesPreview(tables);
|
...(state.setup || {}),
|
||||||
|
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 || {}) };
|
||||||
}
|
}
|
||||||
updateStatus(`Tabellen geladen (${tables.length}).`);
|
fillForm(merged);
|
||||||
|
updateStatus(`Tabellen geladen (${fetchedTables.length}).`);
|
||||||
toast('Tabellen erfolgreich geladen', true);
|
toast('Tabellen erfolgreich geladen', true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -344,3 +356,16 @@ function parseBridgeBasePath(snippet) {
|
|||||||
const match = snippet.match(regex);
|
const match = snippet.match(regex);
|
||||||
return match ? match[1] : '';
|
return match ? match[1] : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSetupInput(input) {
|
||||||
|
const base = defaultSetup();
|
||||||
|
if (!input || typeof input !== 'object') return base;
|
||||||
|
const mode = (input.mode || base.mode).toLowerCase();
|
||||||
|
const validMode = mode === 'config' ? 'config' : 'direct';
|
||||||
|
const tables = normalizeTableNames(input.tables || base.tables);
|
||||||
|
const direct = { ...base.direct, ...(input.direct || {}) };
|
||||||
|
direct.port = Number(direct.port || 3306) || 3306;
|
||||||
|
direct.charset = direct.charset || 'utf8mb4';
|
||||||
|
const config = { ...base.config, ...(input.config || {}) };
|
||||||
|
return { tables, mode: validMode, direct, config };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1895,6 +1895,7 @@ class ApiKernel
|
|||||||
$this->respond([
|
$this->respond([
|
||||||
'ok' => true,
|
'ok' => true,
|
||||||
'tables' => $schema['tables'] ?? [],
|
'tables' => $schema['tables'] ?? [],
|
||||||
|
'setup_hint' => $schema['setup_hint'] ?? null,
|
||||||
'fetched' => $schema['fetched'] ?? date(DATE_ATOM),
|
'fetched' => $schema['fetched'] ?? date(DATE_ATOM),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -1933,7 +1934,11 @@ class ApiKernel
|
|||||||
private function getBridgeSetupData(int $customerId): array
|
private function getBridgeSetupData(int $customerId): array
|
||||||
{
|
{
|
||||||
$settings = $this->getCustomerSettings($customerId);
|
$settings = $this->getCustomerSettings($customerId);
|
||||||
return $settings['bridge_setup'] ?? $this->defaultBridgeSetup();
|
$setup = $settings['bridge_setup'] ?? $this->defaultBridgeSetup();
|
||||||
|
if ((!$setup['tables'] || !count($setup['tables'])) && !empty($settings['bridge_tables'])) {
|
||||||
|
$setup['tables'] = $this->normalizeBridgeTables($settings['bridge_tables']);
|
||||||
|
}
|
||||||
|
return $setup;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function saveCustomerSettings(int $customerId, array $data): array
|
private function saveCustomerSettings(int $customerId, array $data): array
|
||||||
@@ -2555,6 +2560,15 @@ SQL;
|
|||||||
$content = preg_replace("/'pass'\\s*=>\\s*'[^']*',/", "'pass' => {$passValue},", $content, 1);
|
$content = preg_replace("/'pass'\\s*=>\\s*'[^']*',/", "'pass' => {$passValue},", $content, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$setupHint = [
|
||||||
|
'mode' => $setup['mode'] ?? 'direct',
|
||||||
|
'tables' => $tables,
|
||||||
|
'direct' => $setup['direct'] ?? [],
|
||||||
|
'config' => $setup['config'] ?? [],
|
||||||
|
];
|
||||||
|
$setupExport = $this->exportPhpValue($setupHint);
|
||||||
|
$content = str_replace("'setup_hint' => '__SETUP_HINT__',", "'setup_hint' => {$setupExport},", $content);
|
||||||
|
|
||||||
$snippet = $this->buildBridgeSetupSnippet($setup);
|
$snippet = $this->buildBridgeSetupSnippet($setup);
|
||||||
if (strpos($content, '// {{BRIDGE_DB_SETUP}}') !== false) {
|
if (strpos($content, '// {{BRIDGE_DB_SETUP}}') !== false) {
|
||||||
$content = str_replace('// {{BRIDGE_DB_SETUP}}', $snippet, $content);
|
$content = str_replace('// {{BRIDGE_DB_SETUP}}', $snippet, $content);
|
||||||
@@ -2708,6 +2722,11 @@ SQL;
|
|||||||
return '[' . implode(', ', $escaped) . ']';
|
return '[' . implode(', ', $escaped) . ']';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function exportPhpValue($value): string
|
||||||
|
{
|
||||||
|
return var_export($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
private function bridgeDownloadTemplate(): string
|
private function bridgeDownloadTemplate(): string
|
||||||
{
|
{
|
||||||
return <<<'PHP'
|
return <<<'PHP'
|
||||||
|
|||||||
Reference in New Issue
Block a user