Website-fabianschieder/adminer/index.php

414 lines
16 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/user_auth.php';
require_once __DIR__ . '/views.php';
adminer_app_session_start();
// Bootstrap users table (+ optional seed)
try {
adminer_app_bootstrap();
} catch (Throwable $e) {
admin_layout('DB-Verwaltung', '<div class="top"><h1>DB-Verwaltung</h1></div><div class="card"><div class="err">' . h($e->getMessage()) . '</div></div>');
exit;
}
$appPage = (string)($_GET['page'] ?? 'login'); // login|register
$appAction = (string)($_GET['auth'] ?? '');
if ($appAction === 'logout') {
adminer_app_logout();
header('Location: /adminer', true, 302);
exit;
}
// Handle app login
$appError = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'app_login') {
$u = (string)($_POST['username'] ?? '');
$p = (string)($_POST['password'] ?? '');
$res = adminer_app_try_login($u, $p);
if (!empty($res['ok'])) {
header('Location: /adminer', true, 302);
exit;
}
$appError = (string)($res['error'] ?? 'Login fehlgeschlagen.');
$appPage = 'login';
}
// Handle registration
$appRegError = null;
$appRegOk = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'app_register') {
$u = (string)($_POST['username'] ?? '');
$p1 = (string)($_POST['password'] ?? '');
$p2 = (string)($_POST['password2'] ?? '');
$res = adminer_app_try_register($u, $p1, $p2);
if (!empty($res['ok'])) {
// Auto-login after register
$loginRes = adminer_app_try_login($u, $p1);
if (!empty($loginRes['ok'])) {
header('Location: /adminer', true, 302);
exit;
}
$appRegOk = 'Registrierung erfolgreich. Bitte einloggen.';
$appPage = 'login';
} else {
$appRegError = (string)($res['error'] ?? 'Registrierung fehlgeschlagen.');
$appPage = 'register';
}
}
if (!adminer_app_is_logged_in()) {
$canRegister = adminer_app_allow_register();
$body = '<div class="top"><h1>DB-Verwaltung</h1><span class="pill">Login</span></div>';
$body .= '<div class="card">';
// Tabs
$body .= '<div style="display:flex;gap:10px;margin-bottom:14px">'
. '<a class="btn secondary" href="/adminer?page=login" style="text-decoration:none">Login</a>'
. ($canRegister ? '<a class="btn secondary" href="/adminer?page=register" style="text-decoration:none">Registrieren</a>' : '')
. '</div>';
if ($appRegOk) {
$body .= '<div class="ok">' . h($appRegOk) . '</div><br>';
}
if ($appPage === 'register') {
if (!$canRegister) {
$body .= '<div class="err">Registrierung ist deaktiviert.</div>';
} else {
if ($appRegError) $body .= '<div class="err">' . h($appRegError) . '</div><br>';
$body .= '<h3>Registrieren</h3>';
$body .= '<form method="post">'
. '<input type="hidden" name="action" value="app_register">'
. '<label>Benutzername<br><input name="username" autocomplete="username"></label><br><br>'
. '<label>Passwort<br><input name="password" type="password" autocomplete="new-password"></label><br><br>'
. '<label>Passwort wiederholen<br><input name="password2" type="password" autocomplete="new-password"></label><br><br>'
. '<button class="btn" type="submit">Account erstellen</button>'
. '</form>';
$body .= '<div class="muted" style="margin-top:12px">Dein Account wird in <code>FabianWebsite.adminer_users</code> gespeichert.</div>';
}
} else {
if ($appError) $body .= '<div class="err">' . h($appError) . '</div><br>';
$body .= '<h3>Login</h3>';
$body .= '<form method="post">'
. '<input type="hidden" name="action" value="app_login">'
. '<label>Benutzername<br><input name="username" autocomplete="username"></label><br><br>'
. '<label>Passwort<br><input name="password" type="password" autocomplete="current-password"></label><br><br>'
. '<button class="btn" type="submit">Anmelden</button>'
. '</form>';
if ($canRegister) {
$body .= '<div class="muted" style="margin-top:12px">Noch kein Account? <a href="/adminer?page=register">Registrieren</a></div>';
}
}
$body .= '</div>';
admin_layout('DB-Verwaltung', $body);
exit;
}
require_once __DIR__ . '/auth.php';
admin_session_start();
$action = (string)($_GET['a'] ?? '');
if ($action === 'logout') {
admin_logout();
header('Location: /adminer', true, 302);
exit;
}
// ── DB-Auswahl (Step 1) ───────────────────────────────────────────────────
// Wir speichern Host/Port/User/Pass kurz in der Session, um die DB-Liste zu holen.
if (!isset($_SESSION['db_admin_select'])) {
$_SESSION['db_admin_select'] = [];
}
$selectError = null;
$selectMsg = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'probe') {
$host = trim((string)($_POST['host'] ?? ''));
$port = (int)($_POST['port'] ?? 3306);
$user = trim((string)($_POST['user'] ?? ''));
$pass = (string)($_POST['pass'] ?? '');
if ($host === '' || $port <= 0 || $user === '') {
$selectError = 'Bitte Host, Port und Benutzer angeben.';
} else {
try {
$dsn = sprintf('mysql:host=%s;port=%d;charset=utf8mb4', $host, $port);
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$dbs = $pdo->query('SHOW DATABASES')->fetchAll(PDO::FETCH_COLUMN, 0);
$_SESSION['db_admin_select'] = [
'host' => $host,
'port' => $port,
'user' => $user,
'pass' => $pass,
'dbs' => $dbs,
];
$selectMsg = 'Datenbanken geladen.';
} catch (Throwable $e) {
$selectError = 'Konnte Datenbanken nicht laden: ' . $e->getMessage();
}
}
}
// Login (Step 2)
$error = 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']) {
header('Location: /adminer', true, 302);
exit;
}
$error = (string)($res['error'] ?? 'Login fehlgeschlagen.');
}
$defaults = admin_default_creds();
$selectState = is_array($_SESSION['db_admin_select']) ? $_SESSION['db_admin_select'] : [];
// UI when not logged in
if (!admin_is_logged_in()) {
$prefHost = isset($selectState['host']) ? (string)$selectState['host'] : (string)$defaults['host'];
$prefPort = isset($selectState['port']) ? (int)$selectState['port'] : (int)$defaults['port'];
$prefUser = isset($selectState['user']) ? (string)$selectState['user'] : (string)$defaults['user'];
$prefPass = isset($selectState['pass']) ? (string)$selectState['pass'] : '';
$dbList = isset($selectState['dbs']) && is_array($selectState['dbs']) ? $selectState['dbs'] : [];
$body = "<div class=\"top\"><h1>DB-Verwaltung</h1><span class=\"pill\">Mini-Admin</span></div>";
$body .= "<div class=\"card\">";
$body .= "<p class=\"muted\">Zuerst Verbindungsdaten prüfen (ohne DB), dann bekommst du eine Datenbank-Auswahl. Defaults kommen aus <code>.env</code>.</p>";
if ($selectMsg) $body .= '<div class="ok">' . h($selectMsg) . '</div><br>';
if ($selectError) $body .= '<div class="err">' . h($selectError) . '</div><br>';
if ($error) $body .= '<div class="err">' . h($error) . '</div><br>';
// Step 1: Probe
$body .= "<h3>1) Verbindung testen & Datenbanken laden</h3>";
$body .= "<form method=\"post\">";
$body .= "<input type=\"hidden\" name=\"action\" value=\"probe\">";
$body .= "<div class=\"grid\">";
$body .= "<div>";
$body .= "<label>Host<br><input name=\"host\" value=\"" . h($prefHost) . "\"></label><br><br>";
$body .= "<label>Port<br><input name=\"port\" type=\"number\" value=\"" . h((string)$prefPort) . "\"></label><br><br>";
$body .= "<label>Benutzer<br><input name=\"user\" value=\"" . h($prefUser) . "\"></label><br><br>";
$body .= "<label>Passwort<br><input name=\"pass\" type=\"password\" value=\"" . h($prefPass) . "\"></label><br><br>";
$body .= "<button class=\"btn secondary\" type=\"submit\">Datenbanken laden</button>";
$body .= "</div>";
$body .= "<div>";
$body .= "<h3>Hinweise</h3>";
$body .= "<ul class=\"muted\">";
$body .= "<li>Der Zugriff ist durch den App-Login geschützt.</li>";
$body .= "<li>Für produktive Nutzung zusätzlich mit IP-Allowlist kombinieren.</li>";
$body .= "</ul>";
$body .= "</div>";
$body .= "</div>";
$body .= "</form>";
// Step 2: Login
$body .= "<hr style=\"border:0;border-top:1px solid rgba(255,255,255,.10);margin:16px 0\">";
$body .= "<h3>2) Login in Datenbank</h3>";
$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($prefHost) . "\"></label><br><br>";
$body .= "<label>Port<br><input name=\"port\" type=\"number\" value=\"" . h((string)$prefPort) . "\"></label><br><br>";
$body .= "<label>Benutzer<br><input name=\"user\" value=\"" . h($prefUser) . "\"></label><br><br>";
$body .= "<label>Passwort<br><input name=\"pass\" type=\"password\" value=\"" . h($prefPass) . "\"></label><br><br>";
if (!empty($dbList)) {
$body .= "<label>Datenbank<br><select name=\"db\" style=\"width:100%;box-sizing:border-box;background:rgba(0,0,0,.35);color:#e5e7eb;border:1px solid rgba(255,255,255,.14);border-radius:10px;padding:10px\">";
$selectedDb = (string)$defaults['db'];
foreach ($dbList as $dbName) {
$dbName = (string)$dbName;
$sel = ($dbName === $selectedDb) ? ' selected' : '';
$body .= '<option value="' . h($dbName) . '"' . $sel . '>' . h($dbName) . '</option>';
}
$body .= "</select></label><br><br>";
} else {
$body .= "<label>Datenbank<br><input name=\"db\" value=\"" . h((string)$defaults['db']) . "\" placeholder=\"z.B. mydb\"></label><br><br>";
$body .= "<div class=\"muted\" style=\"margin-top:-8px;margin-bottom:12px\">Tipp: Erst oben \"Datenbanken laden\" klicken für Vorschläge.</div>";
}
$body .= "<button class=\"btn\" type=\"submit\">Login</button>";
$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 style="display:flex;gap:10px">'
. '<a class="btn secondary" href="/adminer?auth=logout">App-Logout</a>'
. '<a class="btn secondary" href="/adminer?a=logout">DB-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;
}