diff --git a/adminer/adminer.css b/adminer/adminer.css index daecd2a..7c9bdbf 100644 --- a/adminer/adminer.css +++ b/adminer/adminer.css @@ -1,113 +1,353 @@ -/* Adminer Mini-Admin UI – angelehnt an style.css (Glassmorphism + Accent) */ -:root{ - --bg:#0b1020; - --text:#e5e7eb; - --muted:#94a3b8; - --card:rgba(255,255,255,.06); - --cardBorder:rgba(255,255,255,.10); - --accent:#6366f1; - --accent2:#22d3ee; - --danger:#ef4444; - --success:#22c55e; +/* ===== ADMINER – gleicher Look wie style.css ===== */ + +/* ── RESET & BASE ─────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --bg: #0f0f13; + --surface: #1a1a24; + --surface-hover: #22222f; + --border: rgba(255,255,255,0.07); + --text: #e8e8f0; + --text-muted: #8888a8; + --radius: 16px; + --transition: 0.25s ease; + --accent: #6366f1; } -*{box-sizing:border-box} -html,body{height:100%} -body{ - margin:0; - font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif; - background:radial-gradient(1200px 600px at 20% 10%, rgba(99,102,241,.25), transparent 60%), - radial-gradient(900px 600px at 80% 30%, rgba(34,211,238,.18), transparent 60%), - radial-gradient(900px 700px at 40% 90%, rgba(168,85,247,.14), transparent 60%), - var(--bg); - color:var(--text); +html { scroll-behavior: smooth; overflow-x: hidden; } + +body { + background-color: var(--bg); + color: var(--text); + font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + padding: 2rem 1rem 4rem; + position: relative; + overflow-x: hidden; + overflow-y: auto; } -.wrap{max-width:1100px;margin:34px auto;padding:0 16px} - -.top{ - display:flex; - align-items:center; - justify-content:space-between; - gap:12px; - margin-bottom:16px; +/* ── BACKGROUND ───────────────────────────────── */ +@keyframes bgShift { + 0% { opacity:1; transform:scale(1) translate(0,0); } + 33% { opacity:.85; transform:scale(1.15) translate(2%,3%); } + 66% { opacity:.9; transform:scale(1.08) translate(-3%,-2%); } + 100% { opacity:1; transform:scale(1) translate(0,0); } } -h1{font-size:28px;letter-spacing:.2px;margin:0} - -a{color:#93c5fd;text-decoration:none} -a:hover{text-decoration:underline} - -.pill{ - font-size:12px; - background:rgba(255,255,255,.10); - border:1px solid rgba(255,255,255,.14); - padding:5px 10px; - border-radius:999px; +.background-blur { + position:fixed; inset:-25%; z-index:-1; pointer-events:none; + background: + radial-gradient(ellipse 60% 40% at 20% 10%, rgba(99,102,241,.18), transparent), + radial-gradient(ellipse 50% 40% at 80% 80%, rgba(14,165,233,.13), transparent), + radial-gradient(ellipse 40% 30% at 60% 40%, rgba(168,85,247,.08), transparent); + animation: bgShift 12s ease-in-out infinite; } -.card{ - background:var(--card); - border:1px solid var(--cardBorder); - border-radius:16px; - padding:16px; - backdrop-filter: blur(14px); - box-shadow:0 18px 40px rgba(0,0,0,.35); +#particle-canvas { + position:fixed; inset:0; z-index:-1; pointer-events:none; opacity:.55; } -.grid{display:grid;grid-template-columns:320px 1fr;gap:16px} -@media (max-width: 900px){.grid{grid-template-columns:1fr}} - -.muted{color:var(--muted)} - -label{display:block;color:#cbd5e1;font-weight:600} - -input,textarea,select{ - width:100%; - padding:10px 12px; - margin-top:6px; - border-radius:12px; - background:rgba(0,0,0,.35); - border:1px solid rgba(255,255,255,.14); - color:var(--text); - outline:none; +/* ── ORBS ─────────────────────────────────────── */ +.orb { + position:fixed; border-radius:50%; pointer-events:none; z-index:-1; + filter:blur(70px); opacity:0; + animation:orbFloat var(--dur,18s) ease-in-out infinite var(--delay,0s); } -input:focus,textarea:focus,select:focus{border-color:rgba(99,102,241,.75);box-shadow:0 0 0 3px rgba(99,102,241,.20)} - -hr{border:0;border-top:1px solid rgba(255,255,255,.10);margin:16px 0} - -.btn{ - display:inline-flex; - align-items:center; - justify-content:center; - gap:8px; - padding:10px 12px; - border-radius:12px; - border:1px solid rgba(255,255,255,.16); - cursor:pointer; - background:linear-gradient(135deg, rgba(99,102,241,.95), rgba(34,211,238,.55)); - color:white; - text-decoration:none; - transition:transform .12s ease, filter .12s ease; -} -.btn:hover{filter:brightness(1.06)} -.btn:active{transform:translateY(1px)} -.btn.secondary{ - background:rgba(255,255,255,.08); +.orb-1 { width:320px;height:320px; background:radial-gradient(circle,rgba(99,102,241,.25),transparent 70%); top:-80px;left:-80px; --dur:20s;--delay:0s; } +.orb-2 { width:260px;height:260px; background:radial-gradient(circle,rgba(14,165,233,.2), transparent 70%); bottom:5%;right:-60px; --dur:17s;--delay:-6s; } +.orb-3 { width:200px;height:200px; background:radial-gradient(circle,rgba(168,85,247,.18),transparent 70%); top:45%;left:-40px; --dur:23s;--delay:-11s;} +.orb-4 { width:180px;height:180px; background:radial-gradient(circle,rgba(236,72,153,.15), transparent 70%); top:20%;right:-30px; --dur:19s;--delay:-4s; } +@keyframes orbFloat { + 0% {opacity:0; transform:translate(0,0) scale(1); } + 10% {opacity:1;} + 50% {opacity:1; transform:translate(20px,25px) scale(1.08); } + 90% {opacity:1;} + 100%{opacity:0; transform:translate(0,0) scale(1); } } -.notice{padding:10px 12px;border-radius:12px;border:1px solid rgba(255,255,255,.14)} -.notice.ok{background:rgba(34,197,94,.12);border-color:rgba(34,197,94,.25);color:#bbf7d0} -.notice.err{background:rgba(239,68,68,.10);border-color:rgba(239,68,68,.25);color:#fecaca} +/* Grid lines */ +body::before { + content:''; position:fixed; inset:0; z-index:-1; pointer-events:none; + background-image: + linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px); + background-size:60px 60px; + mask-image:radial-gradient(ellipse 80% 80% at 50% 50%, black 30%, transparent 100%); + -webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%, black 30%, transparent 100%); +} -.table{width:100%;border-collapse:collapse;overflow:hidden;border-radius:14px} -.table th,.table td{padding:9px 10px;border-bottom:1px solid rgba(255,255,255,.10);vertical-align:top} -.table th{color:#cbd5e1;text-align:left;font-weight:700;background:rgba(255,255,255,.04)} -.code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace} +/* ── LAYOUT ───────────────────────────────────── */ +.wrap { + width: 100%; + max-width: 900px; +} -.list{list-style:none;margin:0;padding:0} -.list li{margin:6px 0} -.list a{display:block;padding:8px 10px;border-radius:12px} -.list a:hover{background:rgba(255,255,255,.06)} -.list a.active{background:rgba(99,102,241,.16);border:1px solid rgba(99,102,241,.25)} +/* ── ANIMATIONS ───────────────────────────────── */ +@keyframes fadeDown { + from { opacity:0; transform:translateY(-20px); } + to { opacity:1; transform:translateY(0); } +} +@keyframes fadeUp { + from { opacity:0; transform:translateY(24px); } + to { opacity:1; transform:translateY(0); } +} +@keyframes shimmerText { + from { background-position:0% center; } + to { background-position:200% center; } +} +/* ── HEADER ───────────────────────────────────── */ +.admin-header { + text-align:center; + margin-bottom: 2.5rem; + margin-top: 1.5rem; + animation: fadeDown .7s ease both; +} + +@keyframes avatarPulse { + 0%,100% { box-shadow:0 0 0 4px rgba(99,102,241,.2),0 0 20px rgba(99,102,241,.1); } + 50% { box-shadow:0 0 0 8px rgba(99,102,241,.12),0 0 40px rgba(99,102,241,.2); } +} + +.admin-avatar { + width:72px; height:72px; border-radius:50%; + background:linear-gradient(135deg,#6366f1,#0ea5e9); + display:flex; align-items:center; justify-content:center; + font-size:1.6rem; font-weight:700; + margin:0 auto 1rem; + animation:avatarPulse 3s ease-in-out infinite; + transition:transform .3s ease; + cursor:default; user-select:none; +} +.admin-avatar:hover { transform:scale(1.08) rotate(6deg); } + +.admin-title { + font-size: clamp(1.5rem,3.5vw,2.2rem); + font-weight:700; + background:linear-gradient(90deg,#e8e8f0 0%,#6366f1 40%,#0ea5e9 60%,#e8e8f0 100%); + background-size:200% auto; + -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; + letter-spacing:-.5px; + animation:shimmerText 5s linear infinite; +} + +.admin-subtitle { + color:var(--text-muted); + margin-top:.4rem; + font-size:.95rem; + animation: fadeDown .9s ease .3s both; +} + +/* ── PILL / TAG ───────────────────────────────── */ +.pill { + display:inline-block; + font-size:.72rem; font-weight:600; + text-transform:uppercase; letter-spacing:1px; + color:var(--text-muted); + background:rgba(255,255,255,.06); + border:1px solid var(--border); + padding:3px 10px; border-radius:999px; + vertical-align:middle; margin-left:8px; +} + +/* ── CARD ─────────────────────────────────────── */ +.card { + background:var(--surface); + border:1px solid var(--border); + border-radius:var(--radius); + padding:1.4rem 1.6rem; + position:relative; + overflow:hidden; + animation: fadeUp .5s ease both; +} +.card::before { + content:''; position:absolute; inset:0; border-radius:var(--radius); + opacity:0; transition:opacity var(--transition); + background:linear-gradient(135deg,rgba(99,102,241,.07),transparent 60%); + pointer-events:none; +} +.card:hover::before { opacity:1; } + +/* ── GRID LAYOUT ──────────────────────────────── */ +.admin-grid { + display:grid; + grid-template-columns:240px 1fr; + gap:16px; + align-items:start; +} +@media(max-width:700px){ .admin-grid{ grid-template-columns:1fr; } } + +/* ── TYPOGRAPHY ───────────────────────────────── */ +h2 { + font-size:1rem; font-weight:700; + color:var(--text-muted); + text-transform:uppercase; letter-spacing:1.2px; + margin-bottom:.75rem; + padding-bottom:.5rem; + border-bottom:1px solid var(--border); +} +h3 { + font-size:.95rem; font-weight:600; color:var(--text-muted); + text-transform:uppercase; letter-spacing:1px; + margin-bottom:.6rem; +} +a { color:#93c5fd; text-decoration:none; } +a:hover { text-decoration:underline; } +.muted { color:var(--text-muted); font-size:.88rem; } +code { font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:.85em; + background:rgba(255,255,255,.06); border:1px solid var(--border); padding:1px 5px; border-radius:5px; } + +hr { border:0; border-top:1px solid var(--border); margin:1.2rem 0; } + +/* ── FORM ─────────────────────────────────────── */ +label { + display:block; + font-size:.82rem; font-weight:600; color:var(--text-muted); + text-transform:uppercase; letter-spacing:.8px; + margin-bottom:.35rem; +} +input, textarea, select { + width:100%; padding:10px 14px; + border-radius:12px; + background:rgba(255,255,255,.04); + border:1px solid var(--border); + color:var(--text); + font-family:inherit; font-size:.95rem; + outline:none; + transition:border-color var(--transition), box-shadow var(--transition); +} +input:focus, textarea:focus, select:focus { + border-color:rgba(99,102,241,.7); + box-shadow:0 0 0 3px rgba(99,102,241,.15); +} +textarea { font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; resize:vertical; } + +.field { margin-bottom:1rem; } + +/* ── BUTTONS ──────────────────────────────────── */ +.btn { + display:inline-flex; align-items:center; justify-content:center; gap:6px; + padding:10px 18px; border-radius:var(--radius); + border:1px solid rgba(255,255,255,.12); + cursor:pointer; font-size:.9rem; font-weight:600; letter-spacing:.2px; + background:linear-gradient(135deg,rgba(99,102,241,.9),rgba(14,165,233,.65)); + color:#fff; + text-decoration:none; + transition:transform var(--transition), filter var(--transition), box-shadow var(--transition); + box-shadow:0 4px 18px rgba(99,102,241,.25); + white-space:nowrap; +} +.btn:hover { filter:brightness(1.1); transform:translateY(-1px); text-decoration:none; } +.btn:active { transform:translateY(1px); } + +.btn-sm { padding:7px 12px; font-size:.82rem; border-radius:10px; } + +.btn-ghost { + background:rgba(255,255,255,.06); + border:1px solid var(--border); + box-shadow:none; +} +.btn-ghost:hover { background:rgba(255,255,255,.1); filter:none; } + +/* ── TABS ─────────────────────────────────────── */ +.tabs { display:flex; gap:6px; margin-bottom:1.4rem; } +.tab { + padding:8px 18px; border-radius:10px; font-size:.88rem; font-weight:600; + border:1px solid var(--border); + background:rgba(255,255,255,.04); color:var(--text-muted); + text-decoration:none; transition:background var(--transition), color var(--transition), border-color var(--transition); + cursor:pointer; +} +.tab:hover { background:rgba(255,255,255,.08); color:var(--text); text-decoration:none; } +.tab.active { + background:rgba(99,102,241,.18); border-color:rgba(99,102,241,.4); + color:#a5b4fc; +} + +/* ── NOTICE / ALERTS ──────────────────────────── */ +.notice { + padding:10px 14px; border-radius:12px; font-size:.88rem; + border:1px solid var(--border); margin-bottom:1rem; +} +.notice-ok { background:rgba(34,197,94,.1); border-color:rgba(34,197,94,.25); color:#86efac; } +.notice-err { background:rgba(239,68,68,.09); border-color:rgba(239,68,68,.25); color:#fca5a5; } + +/* ── NAV LIST (Tabellenliste) ─────────────────── */ +.nav-list { list-style:none; } +.nav-list li { margin:3px 0; } +.nav-list a { + display:block; padding:8px 12px; border-radius:12px; + color:var(--text-muted); font-size:.88rem; + border:1px solid transparent; + transition:background var(--transition), color var(--transition), border-color var(--transition); +} +.nav-list a:hover { background:var(--surface-hover); color:var(--text); text-decoration:none; } +.nav-list a.active { + background:rgba(99,102,241,.12); + border-color:rgba(99,102,241,.25); + color:#a5b4fc; +} + +/* ── TABLE ────────────────────────────────────── */ +.db-table { + width:100%; border-collapse:collapse; font-size:.85rem; + overflow:hidden; +} +.db-table thead th { + padding:9px 10px; text-align:left; + font-size:.75rem; font-weight:700; text-transform:uppercase; letter-spacing:.8px; + color:var(--text-muted); + background:rgba(255,255,255,.03); + border-bottom:1px solid var(--border); +} +.db-table tbody td { + padding:9px 10px; + border-bottom:1px solid var(--border); + vertical-align:top; + font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; + font-size:.82rem; + color:var(--text); + word-break:break-word; + max-width:280px; + overflow:hidden; text-overflow:ellipsis; white-space:nowrap; +} +.db-table tbody tr:hover td { background:rgba(255,255,255,.025); } +.db-table tbody tr:last-child td { border-bottom:none; } +.null-val { color:var(--text-muted); font-style:italic; font-size:.8rem; } + +/* ── PAGINATION ───────────────────────────────── */ +.pagination { display:flex; gap:8px; margin-top:12px; align-items:center; } + +/* ── TOP BAR ──────────────────────────────────── */ +.top-bar { + display:flex; align-items:center; justify-content:space-between; gap:12px; + margin-bottom:1.4rem; + flex-wrap:wrap; +} +.top-bar-left { display:flex; align-items:center; gap:10px; } +.top-bar-actions { display:flex; gap:8px; flex-wrap:wrap; } + +/* ── LOGIN CARD CENTERED ──────────────────────── */ +.login-wrap { + width:100%; max-width:420px; + margin:0 auto; + animation: fadeUp .5s ease both; +} + +/* ── BACK LINK ────────────────────────────────── */ +.back-link { + display:inline-flex; align-items:center; gap:5px; + color:var(--text-muted); font-size:.85rem; + margin-bottom:1.2rem; +} +.back-link:hover { color:var(--text); text-decoration:none; } + +@media(max-width:480px) { + body { padding:1.2rem .75rem 3rem; } + .admin-grid { grid-template-columns:1fr; } +} diff --git a/adminer/index.php b/adminer/index.php index e51669d..29672d1 100644 --- a/adminer/index.php +++ b/adminer/index.php @@ -10,131 +10,117 @@ adminer_app_session_start(); try { adminer_app_bootstrap(); } catch (Throwable $e) { - admin_layout('DB-Verwaltung', '

DB-Verwaltung

' . h($e->getMessage()) . '
'); + admin_layout('DB-Verwaltung', '
' . h($e->getMessage()) . '
', 'Fehler beim Start'); exit; } -$appPage = (string)($_GET['page'] ?? 'login'); // login|register -$appAction = (string)($_GET['auth'] ?? ''); -if ($appAction === 'logout') { +// App-Logout +if ((string)($_GET['auth'] ?? '') === 'logout') { adminer_app_logout(); header('Location: /adminer', true, 302); exit; } -// Handle app login +$appPage = (string)($_GET['page'] ?? 'login'); $appError = null; +$appRegError = null; +$appRegOk = null; + +// Login POST 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); + $res = adminer_app_try_login((string)($_POST['username'] ?? ''), (string)($_POST['password'] ?? '')); if (!empty($res['ok'])) { header('Location: /adminer', true, 302); exit; } $appError = (string)($res['error'] ?? 'Login fehlgeschlagen.'); - $appPage = 'login'; + $appPage = 'login'; } -// Handle registration -$appRegError = null; -$appRegOk = null; +// Register POST 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); + $res = adminer_app_try_register( + (string)($_POST['username'] ?? ''), + (string)($_POST['password'] ?? ''), + (string)($_POST['password2'] ?? '') + ); if (!empty($res['ok'])) { - // Auto-login after register - $loginRes = adminer_app_try_login($u, $p1); - if (!empty($loginRes['ok'])) { + $lr = adminer_app_try_login((string)($_POST['username'] ?? ''), (string)($_POST['password'] ?? '')); + if (!empty($lr['ok'])) { header('Location: /adminer', true, 302); exit; } - $appRegOk = 'Registrierung erfolgreich. Bitte einloggen.'; - $appPage = 'login'; + $appRegOk = 'Konto erstellt! Bitte einloggen.'; + $appPage = 'login'; } else { $appRegError = (string)($res['error'] ?? 'Registrierung fehlgeschlagen.'); - $appPage = 'register'; + $appPage = 'register'; } } +// ── LOGIN / REGISTER SEITE ──────────────────────────────────────────────── if (!adminer_app_is_logged_in()) { - $canRegister = adminer_app_allow_register(); + $canReg = adminer_app_allow_register(); + $isReg = ($appPage === 'register'); - $body = '

DB-Verwaltung

Login
'; - $body .= '
'; + $body = '
'; // Tabs - $body .= '
' - . 'Login' - . ($canRegister ? 'Registrieren' : '') - . '
'; + $body .= '
'; + $body .= 'Login'; + if ($canReg) $body .= 'Registrieren'; + $body .= '
'; - if ($appRegOk) { - $body .= '
' . h($appRegOk) . '

'; - } + $body .= '
'; - if ($appPage === 'register') { - if (!$canRegister) { - $body .= '
Registrierung ist deaktiviert.
'; - } else { - if ($appRegError) $body .= '
' . h($appRegError) . '

'; + if ($appRegOk) $body .= '
' . h($appRegOk) . '
'; + if ($appError) $body .= '
' . h($appError) . '
'; + if ($appRegError)$body .= '
' . h($appRegError) . '
'; - $body .= '

Registrieren

'; - $body .= '
' - . '' - . '

' - . '

' - . '

' - . '' - . '
'; - - $body .= '
Dein Account wird in FabianWebsite.adminer_users gespeichert.
'; - } + if ($isReg && $canReg) { + // ── Registrierungsformular ────────────────────────────────────── + $body .= '
' + . '' + . '
' + . '
' + . '
' + . '' + . '
'; + $body .= '

Bereits ein Konto? Login

'; } else { - if ($appError) $body .= '
' . h($appError) . '

'; - - $body .= '

Login

'; + // ── Login-Formular ────────────────────────────────────────────── $body .= '
' . '' - . '

' - . '

' - . '' + . '
' + . '
' + . '' . '
'; - - if ($canRegister) { - $body .= '
Noch kein Account? Registrieren
'; - } + if ($canReg) $body .= '

Noch kein Konto? Registrieren

'; } + $body .= '
'; $body .= '
'; - admin_layout('DB-Verwaltung', $body); + admin_layout('DB-Verwaltung', $body, $isReg ? 'Neues Konto erstellen' : 'Bitte einloggen'); exit; } +// ── DB-VERBINDUNGS-LOGIN ────────────────────────────────────────────────── require_once __DIR__ . '/auth.php'; - admin_session_start(); -$action = (string)($_GET['a'] ?? ''); -if ($action === 'logout') { +if ((string)($_GET['a'] ?? '') === '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'] = []; -} +if (!isset($_SESSION['db_admin_select'])) $_SESSION['db_admin_select'] = []; $selectError = null; -$selectMsg = null; +$selectMsg = null; +// Probe: Datenbanken laden if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'probe') { $host = trim((string)($_POST['host'] ?? '')); $port = (int)($_POST['port'] ?? 3306); @@ -145,269 +131,240 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') == $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 = new PDO(sprintf('mysql:host=%s;port=%d;charset=utf8mb4', $host, $port), $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.'; + $_SESSION['db_admin_select'] = compact('host', 'port', 'user', 'pass', 'dbs'); + $selectMsg = 'Datenbanken geladen – bitte unten eine auswählen.'; } catch (Throwable $e) { - $selectError = 'Konnte Datenbanken nicht laden: ' . $e->getMessage(); + $selectError = 'Fehler: ' . $e->getMessage(); } } } -// Login (Step 2) -$error = null; +// DB-Login +$dbError = 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); + $res = admin_try_login( + trim((string)($_POST['host'] ?? '')), + (int)($_POST['port'] ?? 3306), + trim((string)($_POST['user'] ?? '')), + (string)($_POST['pass'] ?? ''), + trim((string)($_POST['db'] ?? '')) + ); if ($res['ok']) { header('Location: /adminer', true, 302); exit; } - $error = (string)($res['error'] ?? 'Login fehlgeschlagen.'); + $dbError = (string)($res['error'] ?? 'Login fehlgeschlagen.'); } -$defaults = admin_default_creds(); +$defaults = admin_default_creds(); $selectState = is_array($_SESSION['db_admin_select']) ? $_SESSION['db_admin_select'] : []; +$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'] : []; -// 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'] : ''; + $body = '
'; - $dbList = isset($selectState['dbs']) && is_array($selectState['dbs']) ? $selectState['dbs'] : []; + if ($selectMsg) $body .= '
' . h($selectMsg) . '
'; + if ($selectError)$body .= '
' . h($selectError) . '
'; + if ($dbError) $body .= '
' . h($dbError) . '
'; - $body = "

DB-Verwaltung

Mini-Admin
"; - $body .= "
"; - $body .= "

Zuerst Verbindungsdaten prüfen (ohne DB), dann bekommst du eine Datenbank-Auswahl. Defaults kommen aus .env.

"; + // Step 1 + $body .= '

1 · Server verbinden

'; + $body .= '
' + . '' + . '
' + . '
' + . '
' + . '
' + . '' + . '
'; - if ($selectMsg) $body .= '
' . h($selectMsg) . '

'; - if ($selectError) $body .= '
' . h($selectError) . '

'; - if ($error) $body .= '
' . h($error) . '

'; + $body .= '
'; - // Step 1: Probe - $body .= "

1) Verbindung testen & Datenbanken laden

"; - $body .= "
"; - $body .= ""; - $body .= "
"; - $body .= "
"; - $body .= "

"; - $body .= "

"; - $body .= "

"; - $body .= "

"; - $body .= ""; - $body .= "
"; - - $body .= "
"; - $body .= "

Hinweise

"; - $body .= "
    "; - $body .= "
  • Der Zugriff ist durch den App-Login geschützt.
  • "; - $body .= "
  • Für produktive Nutzung zusätzlich mit IP-Allowlist kombinieren.
  • "; - $body .= "
"; - $body .= "
"; - $body .= "
"; - $body .= "
"; - - // Step 2: Login - $body .= "
"; - $body .= "

2) Login in Datenbank

"; - - $body .= "
"; - $body .= ""; - $body .= "
"; - $body .= "
"; - $body .= "

"; - $body .= "

"; - $body .= "

"; - $body .= "

"; + // Step 2 + $body .= '

2 · Datenbank auswählen & einloggen

'; + $body .= '' + . '' + . '' + . '' + . '' + . '' + . '
'; if (!empty($dbList)) { - $body .= "

"; + $body .= ''; } else { - $body .= "

"; - $body .= "
Tipp: Erst oben \"Datenbanken laden\" klicken für Vorschläge.
"; + $body .= ''; } - $body .= ""; - $body .= "
"; - $body .= "
"; - $body .= ""; + $body .= '
' + . '' + . ''; - $body .= "
"; + $body .= '
'; - admin_layout('DB-Verwaltung', $body); + admin_layout('DB-Verwaltung', $body, 'Datenbankverbindung'); exit; } -// Logged-in area +// ── DB-VERWALTUNG (eingeloggt) ──────────────────────────────────────────── try { - $pdo = admin_pdo(); - - $table = (string)($_GET['t'] ?? ''); - $page = max(1, (int)($_GET['p'] ?? 1)); - $limit = 50; + $pdo = admin_pdo(); + $table = (string)($_GET['t'] ?? ''); + $page = max(1, (int)($_GET['p'] ?? 1)); + $limit = 50; $offset = ($page - 1) * $limit; - $msg = null; + $msg = null; $queryResultHtml = ''; + // SQL Query ausführen 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.']; + $msg = ['ok' => false, 'text' => 'Nur ein Statement ausführen (kein zweites Semikolon).']; } else { try { $stmt = $pdo->query($sql); if ($stmt instanceof PDOStatement) { $rows = $stmt->fetchAll(); - $queryResultHtml .= '

Ergebnis

'; - $queryResultHtml .= admin_render_table($rows); - $msg = ['type' => 'ok', 'text' => 'Query ausgeführt.']; + $queryResultHtml = '

Ergebnis

' . admin_render_table($rows); + $msg = ['ok' => true, 'text' => 'Query ausgeführt (' . count($rows) . ' Zeilen).']; } else { - $msg = ['type' => 'ok', 'text' => 'Statement ausgeführt.']; + $msg = ['ok' => true, 'text' => 'Statement ausgeführt.']; } } catch (Throwable $e) { - $msg = ['type' => 'err', 'text' => 'Fehler: ' . $e->getMessage()]; + $msg = ['ok' => false, 'text' => $e->getMessage()]; } } } } - // Build left nav tables $tables = $pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_NUM); - $body = '
' - . '

DB-Verwaltung

eingeloggt
' - . '
' - . 'App-Logout' - . 'DB-Logout' + // ── TOP BAR ────────────────────────────────────────────────────────── + $dbName = (string)($_SESSION['db_admin']['db'] ?? ''); + $uname = (string)($_SESSION['adminer_app']['username'] ?? ''); + + $body = '
' + . '
' + . '' . h($dbName) . '' + . ($uname ? '' . h($uname) . '' : '') + . '
' + . '
' + . 'DB-Logout' + . 'Account-Logout' . '
' . '
'; - $body .= '
'; - - $body .= '
' - . '

Tabellen

' - . '
Klick zum Anzeigen
'; + // ── GRID: TABELLENLISTE + CONTENT ───────────────────────────────────── + $body .= '
'; + // Linke Spalte: Tabellenliste + $body .= '
'; + $body .= '

Tabellen

'; if (empty($tables)) { - $body .= '
Keine Tabellen gefunden.
'; + $body .= '

Keine Tabellen gefunden.

'; } else { - $body .= '
    '; + $body .= ''; } $body .= '
'; - $body .= '
'; + // Rechte Spalte: Browse + Query + $body .= '
'; + // Notices if ($msg) { - $cls = $msg['type'] === 'ok' ? 'notice ok' : 'notice err'; - $body .= '
' . h($msg['text']) . '

'; + $cls = $msg['ok'] ? 'notice-ok' : 'notice-err'; + $body .= '
' . h($msg['text']) . '
'; } - // Browse table + // Browse if ($table !== '') { - // naive identifier quoting for MySQL if (!preg_match('/^[A-Za-z0-9_]+$/', $table)) { - $body .= '
Ungültiger Tabellenname.
'; + $body .= '
Ungültiger Tabellenname.
'; } else { - $body .= '

Tabelle: ' . h($table) . '

'; $stmt = $pdo->query('SELECT * FROM `' . $table . '` LIMIT ' . (int)$limit . ' OFFSET ' . (int)$offset); $rows = $stmt->fetchAll(); - $body .= admin_render_table($rows); - $body .= '
'; - if ($page > 1) { - $body .= '← Zurück'; - } - $body .= 'Weiter →'; + $body .= '
'; + $body .= '

' . h($table) . '

'; + $body .= admin_render_table($rows); + $body .= ''; $body .= '
'; } - - $body .= '
'; } - // Query box - $body .= '

SQL Query

'; + // SQL Query Box + $body .= '
'; + $body .= '

SQL Query

'; $body .= '
' . '' - . '' - . '
' + . '
' + . '' . '
'; - $body .= $queryResultHtml; + $body .= '
'; - $body .= '
'; // card - $body .= '
'; // grid + $body .= '
'; // right col + $body .= '
'; // admin-grid + + admin_layout('DB-Verwaltung', $body, h($dbName)); - admin_layout('DB-Verwaltung', $body); } catch (Throwable $e) { - // Session invalid, force re-login admin_logout(); - admin_layout('DB-Verwaltung', '

DB-Verwaltung

' . h($e->getMessage()) . '

Zum Login
'); + admin_layout('DB-Verwaltung', + '
' . h($e->getMessage()) . '
' + . '

Zurück zum Login

', + 'Fehler' + ); } function admin_render_table(array $rows): string { - if (empty($rows)) { - return '
(keine Zeilen)
'; - } + if (empty($rows)) return '

(keine Zeilen)

'; $cols = array_keys((array)$rows[0]); - $html = ''; - foreach ($cols as $c) { - $html .= ''; - } + $html = '
' . h((string)$c) . '
'; + foreach ($cols as $c) $html .= ''; $html .= ''; - foreach ($rows as $r) { $html .= ''; foreach ($cols as $c) { $v = $r[$c] ?? null; - if ($v === null) { - $cell = 'NULL'; - } else { - $s = (string)$v; - $cell = strlen($s) > 500 ? h(substr($s, 0, 500)) . '…' : h($s); - } + $cell = $v === null + ? 'NULL' + : (strlen((string)$v) > 300 ? h(substr((string)$v, 0, 300)) . '…' : h((string)$v)); $html .= ''; } $html .= ''; } - - $html .= '
' . h((string)$c) . '
' . $cell . '
'; + $html .= '
'; return $html; } diff --git a/adminer/views.php b/adminer/views.php index 0f89449..fa2b1e2 100644 --- a/adminer/views.php +++ b/adminer/views.php @@ -6,16 +6,72 @@ function h($s) return htmlspecialchars((string)$s, ENT_QUOTES); } -function admin_layout($title, $bodyHtml) +function admin_layout($title, $bodyHtml, $subtitle = 'Datenbankverwaltung') { - echo "\n"; - echo "\n\n"; - echo "\n"; - echo "\n"; - echo '' . h($title) . "\n"; - echo "\n"; - echo "\n\n"; - echo "
\n"; - echo $bodyHtml; - echo "
\n\n"; + ?> + + + + + + <?= h($title) ?> – Fabian Schieder + + + + + +
+
+
+
+
+ +
+ +
+
FS
+
+
+
+ + + + + +
+ + + + + +