218 lines
7.8 KiB
PHP
218 lines
7.8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/auth.php';
|
|
require_once __DIR__ . '/views.php';
|
|
|
|
admin_session_start();
|
|
|
|
$action = (string)($_GET['a'] ?? '');
|
|
if ($action === 'logout') {
|
|
admin_logout();
|
|
header('Location: /adminer', true, 302);
|
|
exit;
|
|
}
|
|
|
|
// Login
|
|
$error = null;
|
|
$success = null;
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'login') {
|
|
$host = trim((string)($_POST['host'] ?? ''));
|
|
$port = (int)($_POST['port'] ?? 3306);
|
|
$user = trim((string)($_POST['user'] ?? ''));
|
|
$pass = (string)($_POST['pass'] ?? '');
|
|
$db = trim((string)($_POST['db'] ?? ''));
|
|
|
|
$res = admin_try_login($host, $port, $user, $pass, $db);
|
|
if ($res['ok']) {
|
|
$success = 'Login erfolgreich.';
|
|
header('Location: /adminer', true, 302);
|
|
exit;
|
|
}
|
|
$error = (string)($res['error'] ?? 'Login fehlgeschlagen.');
|
|
}
|
|
|
|
$defaults = admin_default_creds();
|
|
|
|
// UI when not logged in
|
|
if (!admin_is_logged_in()) {
|
|
$body = "<div class=\"top\"><h1>DB-Verwaltung</h1><span class=\"pill\">Mini-Admin</span></div>";
|
|
$body .= "<div class=\"card\">";
|
|
$body .= "<p class=\"muted\">Login-Daten werden nur in der Session gespeichert. Für Defaults wird <code>.env</code> aus dem Projekt-Root gelesen.</p>";
|
|
|
|
if ($error) $body .= '<div class="err">' . h($error) . '</div><br>';
|
|
|
|
$body .= "<form method=\"post\">";
|
|
$body .= "<input type=\"hidden\" name=\"action\" value=\"login\">";
|
|
$body .= "<div class=\"grid\">";
|
|
|
|
$body .= "<div>";
|
|
$body .= "<label>Host<br><input name=\"host\" value=\"" . h((string)$defaults['host']) . "\"></label><br><br>";
|
|
$body .= "<label>Port<br><input name=\"port\" type=\"number\" value=\"" . h((string)$defaults['port']) . "\"></label><br><br>";
|
|
$body .= "<label>Benutzer<br><input name=\"user\" value=\"" . h((string)$defaults['user']) . "\"></label><br><br>";
|
|
$body .= "<label>Passwort<br><input name=\"pass\" type=\"password\" value=\"\"></label><br><br>";
|
|
$body .= "<label>Datenbank<br><input name=\"db\" value=\"" . h((string)$defaults['db']) . "\"></label><br><br>";
|
|
$body .= "<button class=\"btn\" type=\"submit\">Login</button>";
|
|
$body .= "</div>";
|
|
|
|
$body .= "<div>";
|
|
$body .= "<h3>Hinweise</h3>";
|
|
$body .= "<ul class=\"muted\">";
|
|
$body .= "<li>Dieses Tool ist bewusst minimal (Tabellenliste, Browse, SQL Query).</li>";
|
|
$body .= "<li>Für produktive Nutzung bitte zusätzlich absichern (Basic Auth / IP-Allowlist).</li>";
|
|
$body .= "</ul>";
|
|
$body .= "</div>";
|
|
|
|
$body .= "</div>";
|
|
$body .= "</form>";
|
|
$body .= "</div>";
|
|
|
|
admin_layout('DB-Verwaltung', $body);
|
|
exit;
|
|
}
|
|
|
|
// Logged-in area
|
|
try {
|
|
$pdo = admin_pdo();
|
|
|
|
$table = (string)($_GET['t'] ?? '');
|
|
$page = max(1, (int)($_GET['p'] ?? 1));
|
|
$limit = 50;
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
$msg = null;
|
|
$queryResultHtml = '';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'query') {
|
|
$sql = trim((string)($_POST['sql'] ?? ''));
|
|
if ($sql !== '') {
|
|
// Allow multiple statements? No. Keep minimal & safer.
|
|
if (preg_match('/;\s*\S/', $sql)) {
|
|
$msg = ['type' => 'err', 'text' => 'Bitte nur ein Statement ohne zusätzliche Semikolons ausführen.'];
|
|
} else {
|
|
try {
|
|
$stmt = $pdo->query($sql);
|
|
if ($stmt instanceof PDOStatement) {
|
|
$rows = $stmt->fetchAll();
|
|
$queryResultHtml .= '<h3>Ergebnis</h3>';
|
|
$queryResultHtml .= admin_render_table($rows);
|
|
$msg = ['type' => 'ok', 'text' => 'Query ausgeführt.'];
|
|
} else {
|
|
$msg = ['type' => 'ok', 'text' => 'Statement ausgeführt.'];
|
|
}
|
|
} catch (Throwable $e) {
|
|
$msg = ['type' => 'err', 'text' => 'Fehler: ' . $e->getMessage()];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build left nav tables
|
|
$tables = $pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_NUM);
|
|
|
|
$body = '<div class="top">'
|
|
. '<div><h1>DB-Verwaltung</h1><div class="muted">eingeloggt</div></div>'
|
|
. '<div><a class="btn secondary" href="/adminer?a=logout">Logout</a></div>'
|
|
. '</div>';
|
|
|
|
$body .= '<div class="grid">';
|
|
|
|
$body .= '<div class="card">'
|
|
. '<h3>Tabellen</h3>'
|
|
. '<div class="muted" style="margin-bottom:8px">Klick zum Anzeigen</div>';
|
|
|
|
if (empty($tables)) {
|
|
$body .= '<div class="muted">Keine Tabellen gefunden.</div>';
|
|
} else {
|
|
$body .= '<ul style="list-style:none;padding-left:0;margin:0">';
|
|
foreach ($tables as $row) {
|
|
$tname = (string)$row[0];
|
|
$active = ($tname === $table) ? ' style="font-weight:700"' : '';
|
|
$body .= '<li><a' . $active . ' href="/adminer?t=' . rawurlencode($tname) . '">' . h($tname) . '</a></li>';
|
|
}
|
|
$body .= '</ul>';
|
|
}
|
|
$body .= '</div>';
|
|
|
|
$body .= '<div class="card">';
|
|
|
|
if ($msg) {
|
|
$cls = $msg['type'] === 'ok' ? 'ok' : 'err';
|
|
$body .= '<div class="' . $cls . '">' . h($msg['text']) . '</div><br>';
|
|
}
|
|
|
|
// Browse table
|
|
if ($table !== '') {
|
|
// naive identifier quoting for MySQL
|
|
if (!preg_match('/^[A-Za-z0-9_]+$/', $table)) {
|
|
$body .= '<div class="err">Ungültiger Tabellenname.</div>';
|
|
} else {
|
|
$body .= '<h3>Tabelle: <code>' . h($table) . '</code></h3>';
|
|
$stmt = $pdo->query('SELECT * FROM `' . $table . '` LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset);
|
|
$rows = $stmt->fetchAll();
|
|
$body .= admin_render_table($rows);
|
|
|
|
$body .= '<div style="margin-top:10px;display:flex;gap:10px">';
|
|
if ($page > 1) {
|
|
$body .= '<a class="btn secondary" href="/adminer?t=' . rawurlencode($table) . '&p=' . ($page - 1) . '">← Zurück</a>';
|
|
}
|
|
$body .= '<a class="btn secondary" href="/adminer?t=' . rawurlencode($table) . '&p=' . ($page + 1) . '">Weiter →</a>';
|
|
$body .= '</div>';
|
|
}
|
|
|
|
$body .= '<hr style="border:0;border-top:1px solid rgba(255,255,255,.10);margin:16px 0">';
|
|
}
|
|
|
|
// Query box
|
|
$body .= '<h3>SQL Query</h3>';
|
|
$body .= '<form method="post">'
|
|
. '<input type="hidden" name="action" value="query">'
|
|
. '<textarea name="sql" rows="6" spellcheck="false" placeholder="SELECT * FROM ..."></textarea>'
|
|
. '<div style="margin-top:10px"><button class="btn" type="submit">Ausführen</button></div>'
|
|
. '</form>';
|
|
|
|
$body .= $queryResultHtml;
|
|
|
|
$body .= '</div>'; // card
|
|
$body .= '</div>'; // grid
|
|
|
|
admin_layout('DB-Verwaltung', $body);
|
|
} catch (Throwable $e) {
|
|
// Session invalid, force re-login
|
|
admin_logout();
|
|
admin_layout('DB-Verwaltung', '<div class="top"><h1>DB-Verwaltung</h1></div><div class="card"><div class="err">' . h($e->getMessage()) . '</div><br><a class="btn" href="/adminer">Zum Login</a></div>');
|
|
}
|
|
|
|
function admin_render_table(array $rows): string
|
|
{
|
|
if (empty($rows)) {
|
|
return '<div class="muted">(keine Zeilen)</div>';
|
|
}
|
|
|
|
$cols = array_keys((array)$rows[0]);
|
|
$html = '<table><thead><tr>';
|
|
foreach ($cols as $c) {
|
|
$html .= '<th>' . h((string)$c) . '</th>';
|
|
}
|
|
$html .= '</tr></thead><tbody>';
|
|
|
|
foreach ($rows as $r) {
|
|
$html .= '<tr>';
|
|
foreach ($cols as $c) {
|
|
$v = $r[$c] ?? null;
|
|
if ($v === null) {
|
|
$cell = '<span class="muted">NULL</span>';
|
|
} else {
|
|
$s = (string)$v;
|
|
$cell = strlen($s) > 500 ? h(substr($s, 0, 500)) . '…' : h($s);
|
|
}
|
|
$html .= '<td>' . $cell . '</td>';
|
|
}
|
|
$html .= '</tr>';
|
|
}
|
|
|
|
$html .= '</tbody></table>';
|
|
return $html;
|
|
}
|
|
|