<?php
/**
 * 大宋 dasong.online / greatsong.cn —— 管理控制台
 * 独立子域名 admin.greatsong.cn，账号体系复用站点账号（且必须在 admins 列表里）
 *
 * 安全设计：
 *   - 登录失败节流（同 IP 15 分钟内 5 次失败即锁）
 *   - Session cookie：Domain=.greatsong.cn / Secure / HttpOnly / SameSite=Lax
 *   - 所有写操作都要 CSRF token + confirm
 *   - 所有操作写审计日志 /var/www/dasong/data/console_audit.log
 *   - shell 只执行固定的命令白名单，不接受任何用户输入拼接
 */

declare(strict_types=1);

const CFG = '/var/www/dasong/config/config.php';
const DATA = '/var/www/dasong/data';

$CONFIG = require CFG;
$ADMINS = $CONFIG['admins'] ?? [];

/* ---------------- 会话 ---------------- */
session_name('DSHCONSOLE');
session_set_cookie_params([
    'lifetime' => 0,
    'path'     => '/',
    'domain'   => '.greatsong.cn',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
session_start();

/* ---------------- 数据库 ---------------- */
function db(): PDO
{
    static $pdo = null;
    global $CONFIG;
    if ($pdo === null) {
        $d = $CONFIG['db'];
        $pdo = new PDO(
            "mysql:host={$d['host']};port={$d['port']};dbname={$d['name']};charset=utf8mb4",
            $d['user'],
            $d['pass'],
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]
        );
    }
    return $pdo;
}
function q(string $sql, array $p = []): PDOStatement
{
    $st = db()->prepare($sql);
    $st->execute($p);
    return $st;
}
function one(string $sql, array $p = [])
{
    $r = q($sql, $p)->fetch();
    return $r === false ? null : $r;
}
function all(string $sql, array $p = []): array
{
    return q($sql, $p)->fetchAll();
}
function scalar(string $sql, array $p = [])
{
    $r = q($sql, $p)->fetch(PDO::FETCH_NUM);
    return $r === false ? null : $r[0];
}

/* ---------------- 工具 ---------------- */
function h($s): string { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function ip(): string { return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; }
function audit(string $what): void
{
    $u = $_SESSION['cuser'] ?? '-';
    @file_put_contents(DATA . '/console_audit.log',
        sprintf("[%s] %-14s %-15s %s\n", date('Y-m-d H:i:s'), $u, ip(), $what),
        FILE_APPEND | LOCK_EX);
}
function csrf(): string
{
    if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16));
    return $_SESSION['csrf'];
}
function check_csrf(): bool
{
    return isset($_POST['csrf'], $_SESSION['csrf']) && hash_equals($_SESSION['csrf'], (string)$_POST['csrf']);
}
function go(string $p, string $msg = ''): void
{
    $url = '?' . http_build_query(array_filter(['p' => $p, 'msg' => $msg]));
    header('Location: ' . $url);
    exit;
}
function sh(string $cmd): string
{
    // 只执行代码里写死的命令，不接受用户输入
    $out = @shell_exec($cmd . ' 2>&1');
    return trim((string)$out);
}
function human(int $n): string
{
    if ($n >= 10000) return round($n / 10000, 1) . '万';
    return (string)$n;
}

/* ---------------- 登录节流 ---------------- */
const THROTTLE_FILE = DATA . '/console_throttle.json';
function throttle_load(): array
{
    $j = @file_get_contents(THROTTLE_FILE);
    $a = $j ? json_decode($j, true) : [];
    return is_array($a) ? $a : [];
}
function throttle_save(array $a): void
{
    @file_put_contents(THROTTLE_FILE, json_encode($a, JSON_UNESCAPED_UNICODE), LOCK_EX);
}
function throttle_check(): int
{
    $a = throttle_load();
    $k = ip();
    $now = time();
    if (!isset($a[$k])) return 0;
    $rec = array_values(array_filter($a[$k], fn($t) => $now - $t < 900));
    if (count($rec) >= 5) return 900 - ($now - $rec[0]);
    return 0;
}
function throttle_hit(): void
{
    $a = throttle_load();
    $a[ip()][] = time();
    // 清理超过 1 天的记录
    foreach ($a as $k => $v) {
        $a[$k] = array_values(array_filter($v, fn($t) => time() - $t < 86400));
        if (!$a[$k]) unset($a[$k]);
    }
    throttle_save($a);
}
function throttle_clear(): void
{
    $a = throttle_load();
    unset($a[ip()]);
    throttle_save($a);
}

/* ---------------- 处理登录 ---------------- */
$loginError = '';
if (($_POST['action'] ?? '') === 'login') {
    $wait = throttle_check();
    if ($wait > 0) {
        $loginError = '尝试过于频繁，请 ' . ceil($wait / 60) . ' 分钟后再试';
    } else {
        $u = trim((string)($_POST['username'] ?? ''));
        $p = (string)($_POST['password'] ?? '');
        $row = one('SELECT id, username, password FROM users WHERE username = ? LIMIT 1', [$u]);
        if ($row && password_verify($p, $row['password']) && in_array($row['username'], $ADMINS, true)) {
            session_regenerate_id(true);
            $_SESSION['cuser'] = $row['username'];
            $_SESSION['cuid'] = (int)$row['id'];
            throttle_clear();
            audit('登录成功');
            go('dash');
        } else {
            throttle_hit();
            $loginError = '账号或密码错误，或该账号没有管理权限';
            audit('登录失败: ' . $u);
        }
    }
}

if (($_GET['do'] ?? '') === 'logout') {
    audit('退出登录');
    $_SESSION = [];
    session_destroy();
    header('Location: /');
    exit;
}

$me = $_SESSION['cuser'] ?? null;
$loggedIn = $me !== null && in_array($me, $ADMINS, true);

/* ================= 未登录：登录页 ================= */
if (!$loggedIn) {
    ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>控制台登录 · 大宋</title>
<style>
  :root{--bg:#15161a;--card:#1d1f25;--bd:#2f333c;--tx:#e8eaf0;--mut:#a4abb8;--ac:#e8a44e;--er:#ff8a8a}
  *{box-sizing:border-box}
  body{margin:0;min-height:100vh;display:grid;place-items:center;background:radial-gradient(900px 500px at 50% -10%,rgba(232,164,78,.16),transparent 60%),var(--bg);color:var(--tx);font:14px/1.6 "PingFang SC","Microsoft YaHei",system-ui,sans-serif}
  .box{width:360px;max-width:92vw;background:var(--card);border:1px solid var(--bd);border-radius:16px;padding:28px 26px}
  h1{margin:0 0 4px;font-size:19px;letter-spacing:.06em}
  .sub{color:var(--mut);font-size:12.5px;margin-bottom:22px}
  label{display:block;font-size:12.5px;color:var(--mut);margin:14px 0 6px}
  input{width:100%;padding:11px 12px;border-radius:9px;border:1px solid var(--bd);background:#15161a;color:var(--tx);font-size:14px;outline:none}
  input:focus{border-color:var(--ac)}
  button{width:100%;margin-top:20px;padding:11px;border-radius:9px;border:0;background:linear-gradient(145deg,#e8a44e,#d98a2b);color:#241a08;font-weight:700;font-size:14.5px;cursor:pointer}
  .err{margin-top:14px;padding:10px 12px;border-radius:9px;background:rgba(255,138,138,.12);border:1px solid rgba(255,138,138,.3);color:var(--er);font-size:13px}
  .tip{margin-top:18px;font-size:11.5px;color:#6f7684;line-height:1.7}
</style>
</head>
<body>
<form class="box" method="post">
  <h1>🏯 大宋 · 控制台</h1>
  <div class="sub">用站点管理员账号登录（goodren）</div>
  <?php if ($loginError): ?><div class="err"><?= h($loginError) ?></div><?php endif; ?>
  <label>账号</label>
  <input name="username" autocomplete="username" autofocus value="<?= h($_POST['username'] ?? '') ?>">
  <label>密码</label>
  <input name="password" type="password" autocomplete="current-password">
  <input type="hidden" name="action" value="login">
  <button type="submit">登录</button>
  <div class="tip">仅限 config 里 admins 列表的账号。<br>失败 5 次将锁定 15 分钟。</div>
</form>
</body>
</html>
    <?php
    exit;
}

/* ================= 已登录：处理写操作 ================= */
$flash = (string)($_GET['msg'] ?? '');
$act = (string)($_POST['action'] ?? '');

if ($act !== '' && $act !== 'login') {
    if (!check_csrf()) {
        go('dash', 'CSRF 校验失败，请重试');
    }
    $confirm = trim((string)($_POST['confirm'] ?? ''));
    try {
        switch ($act) {
            /* ---- 用户 ---- */
            case 'user_reset_pw':
                $id = (int)($_POST['id'] ?? 0);
                $newPw = (string)($_POST['newpw'] ?? '');
                if ($confirm !== 'RESET') throw new RuntimeException('确认词不对（应为 RESET）');
                if (strlen($newPw) < 6) throw new RuntimeException('新密码至少 6 位');
                q('UPDATE users SET password = ? WHERE id = ?', [password_hash($newPw, PASSWORD_DEFAULT), $id]);
                audit("重置密码 user_id=$id");
                go('users', '密码已重置');
            case 'user_delete':
                $id = (int)($_POST['id'] ?? 0);
                if ($confirm !== 'DELETE') throw new RuntimeException('确认词不对（应为 DELETE）');
                if ($id === 1) throw new RuntimeException('拒绝删除主管理员账号（id=1）');
                q('DELETE FROM users WHERE id = ?', [$id]);
                audit("删除用户 user_id=$id");
                go('users', '用户已删除（评论保留）');
            case 'user_exp':
                $id = (int)($_POST['id'] ?? 0);
                $exp = (int)($_POST['exp'] ?? 0);
                q('UPDATE users SET total_experience = ? WHERE id = ?', [$exp, $id]);
                audit("设置经验 user_id=$id exp=$exp");
                go('users', '经验值已更新');

            /* ---- 评论 / 回复 ---- */
            case 'comment_del':
                $id = (int)($_POST['id'] ?? 0);
                $row = one('SELECT * FROM comments WHERE id = ?', [$id]);
                if (!$row) throw new RuntimeException('评论不存在');
                q('INSERT INTO deleted_comments (author, content, timestamp, ip) VALUES (?,?,?,?)',
                  [$row['author'], $row['content'], $row['timestamp'], $row['ip']]);
                q('DELETE FROM comments WHERE id = ?', [$id]);
                audit("删除评论 id=$id 作者={$row['author']}");
                go('content', '评论已删除（转入回收表）');
            case 'comment_flag':
                $id = (int)($_POST['id'] ?? 0);
                q('UPDATE comments SET flagged = 1 - flagged WHERE id = ?', [$id]);
                audit("切换评论标记 id=$id");
                go('content', '标记已切换');
            case 'reply_del':
                $id = (int)($_POST['id'] ?? 0);
                $row = one('SELECT * FROM replies WHERE id = ?', [$id]);
                if (!$row) throw new RuntimeException('回复不存在');
                q('INSERT INTO deleted_replies (comment_id, author, content, timestamp, ip) VALUES (?,?,?,?,?)',
                  [$row['comment_id'], $row['author'], $row['content'], $row['timestamp'], $row['ip']]);
                q('DELETE FROM replies WHERE id = ?', [$id]);
                audit("删除回复 id=$id");
                go('content', '回复已删除');

            /* ---- 文章 ---- */
            case 'article_save':
                $id = (int)($_POST['id'] ?? 0);
                $title = trim((string)($_POST['title'] ?? ''));
                $body = (string)($_POST['content'] ?? '');
                if ($title === '' || $body === '') throw new RuntimeException('标题和正文不能为空');
                if ($id > 0) {
                    q('UPDATE articles SET title = ?, content = ? WHERE id = ?', [$title, $body, $id]);
                    audit("编辑文章 id=$id");
                    go('articles', '文章已更新');
                } else {
                    q('INSERT INTO articles (user_id, title, content, created_at) VALUES (?,?,?,NOW())',
                      [$_SESSION['cuid'], $title, $body]);
                    audit('新建文章: ' . $title);
                    go('articles', '文章已发布');
                }
            case 'article_del':
                $id = (int)($_POST['id'] ?? 0);
                if ($confirm !== 'DELETE') throw new RuntimeException('确认词不对（应为 DELETE）');
                q('DELETE FROM articles WHERE id = ?', [$id]);
                audit("删除文章 id=$id");
                go('articles', '文章已删除');

            /* ---- 冰榜 ---- */
            case 'ice_zero':
                $pk = (string)($_POST['pk'] ?? '');
                if ($confirm !== 'ZERO') throw new RuntimeException('确认词不对（应为 ZERO）');
                q('DELETE FROM ice_taps WHERE player_key = ?', [$pk]);
                audit("冰榜清零 player_key=$pk");
                go('ice', '该玩家数据已清零');

            /* ---- 系统 ---- */
            case 'svc':
                $svc = (string)($_POST['svc'] ?? '');
                $ok = in_array($svc, ['nginx', 'php8.2-fpm', 'mariadb'], true);
                if (!$ok) throw new RuntimeException('不允许的服务名');
                if ($confirm !== 'RESTART') throw new RuntimeException('确认词不对（应为 RESTART）');
                $r = sh("systemctl restart " . $svc);
                audit("重启服务 $svc");
                go('system', "已重启 $svc" . ($r ? '：' . mb_substr($r, 0, 120) : ''));
            case 'nginx_test':
                $r = sh('nginx -t');
                audit('nginx -t');
                go('system', mb_substr($r, 0, 200));

            /* ---- 部署 ---- */
            case 'deploy':
                $cmd = (string)($_POST['cmd'] ?? '');
                $map = [
                    'sync-db'  => ['SYNC',  '/usr/local/bin/stg sync-db'],
                    'promote'  => ['GO',    '/usr/local/bin/stg promote'],
                    'rollback' => ['BACK',  '/usr/local/bin/stg rollback'],
                ];
                if (!isset($map[$cmd])) throw new RuntimeException('未知命令');
                [$word, $line] = $map[$cmd];
                if ($confirm !== $word) throw new RuntimeException("确认词不对（应为 $word）");
                $out = sh($line);
                audit("执行部署命令: stg $cmd");
                go('deploy', mb_substr($out, 0, 400));
        }
    } catch (Throwable $e) {
        go((string)($_POST['back'] ?? 'dash'), '操作失败：' . $e->getMessage());
    }
}

/* ================= 数据 ================= */
$p = (string)($_GET['p'] ?? 'dash');
$PAGES = [
    'dash'     => '概览',
    'users'    => '用户',
    'content'  => '评论/回复',
    'articles' => '文章',
    'ice'      => '冰榜',
    'system'   => '系统',
    'deploy'   => '部署',
];
if (!isset($PAGES[$p])) $p = 'dash';

$stats = [
    'users'    => (int)scalar('SELECT COUNT(*) FROM users'),
    'comments' => (int)scalar('SELECT COUNT(*) FROM comments'),
    'replies'  => (int)scalar('SELECT COUNT(*) FROM replies'),
    'articles' => (int)scalar('SELECT COUNT(*) FROM articles'),
    'ice'      => (int)scalar('SELECT COUNT(*) FROM ice_taps'),
    'logs'     => (int)scalar('SELECT COUNT(*) FROM logs'),
    'new24'    => (int)scalar('SELECT COUNT(*) FROM users WHERE created_at > NOW() - INTERVAL 1 DAY'),
    'cm24'     => (int)scalar('SELECT COUNT(*) FROM comments WHERE timestamp > UNIX_TIMESTAMP() - 86400'),
];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title><?= h($PAGES[$p]) ?> · 大宋控制台</title>
<style>
  :root{--bg:#15161a;--card:#1d1f25;--card2:#24272e;--bd:#2f333c;--tx:#e8eaf0;--mut:#a4abb8;--ac:#e8a44e;--ok:#5bd68f;--er:#ff8a8a;--wr:#f5c451}
  *{box-sizing:border-box}
  body{margin:0;background:var(--bg);color:var(--tx);font:14px/1.65 "PingFang SC","Microsoft YaHei",system-ui,sans-serif}
  a{color:var(--ac);text-decoration:none}
  .wrap{display:grid;grid-template-columns:190px 1fr;min-height:100vh}
  nav{background:var(--card);border-right:1px solid var(--bd);padding:18px 12px;position:sticky;top:0;height:100vh}
  nav .brand{font-weight:700;font-size:15px;letter-spacing:.05em;padding:0 8px 16px;border-bottom:1px solid var(--bd);margin-bottom:12px}
  nav a{display:block;padding:8px 10px;border-radius:8px;color:var(--mut);font-size:13.5px;margin-bottom:2px}
  nav a.on{background:rgba(232,164,78,.14);color:var(--ac)}
  nav a:hover{background:rgba(255,255,255,.05)}
  nav .who{position:absolute;bottom:14px;left:12px;right:12px;font-size:12px;color:#6f7684;border-top:1px solid var(--bd);padding-top:10px}
  main{padding:22px 26px 60px;min-width:0}
  h1{margin:0 0 4px;font-size:21px;letter-spacing:.04em}
  .hint{color:var(--mut);font-size:12.5px;margin-bottom:18px}
  .flash{margin-bottom:16px;padding:11px 14px;border-radius:10px;background:rgba(91,214,143,.1);border:1px solid rgba(91,214,143,.25);color:#a7f3c6;font-size:13px;word-break:break-all}
  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px;margin-bottom:20px}
  .kpi{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:14px 16px}
  .kpi b{display:block;font-size:23px;letter-spacing:.02em}
  .kpi span{color:var(--mut);font-size:12px}
  table{width:100%;border-collapse:collapse;background:var(--card);border:1px solid var(--bd);border-radius:12px;overflow:hidden;font-size:13px}
  th,td{padding:9px 11px;text-align:left;border-bottom:1px solid var(--bd);vertical-align:top}
  th{background:var(--card2);color:var(--mut);font-weight:600;font-size:12px;white-space:nowrap}
  tr:last-child td{border-bottom:0}
  tr:hover td{background:rgba(255,255,255,.02)}
  .mono{font-family:Consolas,"SF Mono",monospace;font-size:12px}
  .muted{color:var(--mut)}
  .btn{display:inline-block;padding:4px 9px;border-radius:6px;border:1px solid var(--bd);background:var(--card2);color:var(--tx);font-size:12px;cursor:pointer}
  .btn:hover{border-color:var(--ac);color:var(--ac)}
  .btn.danger:hover{border-color:var(--er);color:var(--er)}
  form.inline{display:inline}
  input,textarea,select{padding:6px 8px;border-radius:6px;border:1px solid var(--bd);background:#15161a;color:var(--tx);font-size:13px;font-family:inherit}
  textarea{width:100%;min-height:220px;line-height:1.6}
  .card{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:16px 18px;margin-bottom:16px}
  .card h3{margin:0 0 10px;font-size:15px}
  pre{margin:0;white-space:pre-wrap;word-break:break-all;background:#111216;border:1px solid var(--bd);border-radius:8px;padding:11px 13px;font-size:12px;color:#c7ccd6;max-height:340px;overflow:auto}
  .tag{display:inline-block;padding:1px 7px;border-radius:99px;font-size:11px;border:1px solid currentColor}
  .ok{color:var(--ok)} .bad{color:var(--er)} .wr{color:var(--wr)}
  .row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
  @media(max-width:760px){.wrap{grid-template-columns:1fr}nav{position:static;height:auto;display:flex;gap:6px;flex-wrap:wrap}nav .brand,nav .who{display:none}main{padding:16px}}
</style>
</head>
<body>
<div class="wrap">
  <nav>
    <div class="brand">🏯 大宋控制台</div>
    <?php foreach ($PAGES as $k => $label): ?>
      <a href="?p=<?= h($k) ?>" class="<?= $k === $p ? 'on' : '' ?>"><?= h($label) ?></a>
    <?php endforeach; ?>
    <div class="who">
      <?= h($me) ?><br>
      <a href="?do=logout">退出登录</a>
    </div>
  </nav>
  <main>
    <h1><?= h($PAGES[$p]) ?></h1>
    <div class="hint">admin.greatsong.cn · <?= h($CONFIG['site']['domain'] ?? '') ?> · <?= date('Y-m-d H:i') ?></div>
    <?php if ($flash): ?><div class="flash"><?= h($flash) ?></div><?php endif; ?>

<?php
/* ================= 各页面 ================= */
switch ($p) {

case 'dash':
    $disk = sh("df -h / | tail -1 | awk '{print $3\"/\"$2\" (\"$5\")\"}'");
    $mem  = sh("free -m | awk 'NR==2{print $3\"MB/\"$2\"MB\"}'");
    $load = sh("cut -d' ' -f1-3 /proc/loadavg");
    $up   = sh("uptime -p | sed 's/^up //'");
    $svcs = [];
    foreach (['nginx', 'php8.2-fpm', 'mariadb'] as $s) $svcs[$s] = sh("systemctl is-active $s");
    $topIce = all('SELECT username, SUM(taps) t FROM ice_taps WHERE is_guest = 0 GROUP BY player_key ORDER BY t DESC LIMIT 8');
    $recent = all('SELECT id, author, content, timestamp FROM comments ORDER BY id DESC LIMIT 6');
    ?>
    <div class="grid">
      <div class="kpi"><b><?= human($stats['users']) ?></b><span>账号（24h 新增 <?= $stats['new24'] ?>）</span></div>
      <div class="kpi"><b><?= human($stats['comments']) ?></b><span>评论（24h <?= $stats['cm24'] ?>）</span></div>
      <div class="kpi"><b><?= human($stats['replies']) ?></b><span>回复</span></div>
      <div class="kpi"><b><?= human($stats['articles']) ?></b><span>文章</span></div>
      <div class="kpi"><b><?= human($stats['ice']) ?></b><span>冰榜记录</span></div>
      <div class="kpi"><b><?= human($stats['logs']) ?></b><span>操作日志</span></div>
    </div>
    <div class="row" style="margin-bottom:16px">
      <?php foreach ($svcs as $s => $st): ?>
        <span class="tag <?= $st === 'active' ? 'ok' : 'bad' ?>"><?= h($s) ?> <?= h($st) ?></span>
      <?php endforeach; ?>
      <span class="muted mono">磁盘 <?= h($disk) ?> · 内存 <?= h($mem) ?> · 负载 <?= h($load) ?> · 已运行 <?= h($up) ?></span>
    </div>
    <div class="card">
      <h3>冰榜前 8</h3>
      <table><tr><th>#</th><th>玩家</th><th>累计</th></tr>
      <?php foreach ($topIce as $i => $r): ?>
        <tr><td><?= $i + 1 ?></td><td><?= h($r['username']) ?></td><td class="mono"><?= number_format((int)$r['t']) ?></td></tr>
      <?php endforeach; ?>
      </table>
    </div>
    <div class="card">
      <h3>最新评论</h3>
      <table><tr><th>ID</th><th>作者</th><th>内容</th><th>时间</th></tr>
      <?php foreach ($recent as $r): ?>
        <tr>
          <td class="mono"><?= (int)$r['id'] ?></td>
          <td><?= h($r['author']) ?></td>
          <td><?= h(mb_substr($r['content'], 0, 60)) ?></td>
          <td class="muted mono"><?= h(date('m-d H:i', (int)$r['timestamp'])) ?></td>
        </tr>
      <?php endforeach; ?>
      </table>
    </div>
    <?php
    break;

case 'users':
    $kw = trim((string)($_GET['kw'] ?? ''));
    $rows = $kw !== ''
        ? all('SELECT * FROM users WHERE username LIKE ? ORDER BY id DESC LIMIT 100', ['%' . $kw . '%'])
        : all('SELECT * FROM users ORDER BY id DESC LIMIT 100');
    ?>
    <form method="get" class="row" style="margin-bottom:14px">
      <input type="hidden" name="p" value="users">
      <input name="kw" placeholder="搜索用户名" value="<?= h($kw) ?>">
      <button class="btn">搜索</button>
      <span class="muted">共 <?= $stats['users'] ?> 个账号，显示最近 100</span>
    </form>
    <table>
      <tr><th>ID</th><th>用户名</th><th>注册</th><th>经验</th><th>最后登录</th><th>IP</th><th>操作</th></tr>
      <?php foreach ($rows as $r): ?>
        <tr>
          <td class="mono"><?= (int)$r['id'] ?></td>
          <td><?= h($r['username']) ?><?= in_array($r['username'], $ADMINS, true) ? ' <span class="tag wr">管理员</span>' : '' ?></td>
          <td class="muted mono"><?= h(substr((string)$r['created_at'], 0, 16)) ?></td>
          <td class="mono"><?= (int)$r['total_experience'] ?></td>
          <td class="muted mono"><?= h(substr((string)($r['last_login_at'] ?? '-'), 0, 16)) ?></td>
          <td class="muted mono"><?= h($r['last_login_ip'] ?? '-') ?></td>
          <td>
            <form class="inline" method="post" onsubmit="return confirm('重置该用户的密码？')">
              <input type="hidden" name="action" value="user_reset_pw">
              <input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
              <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="back" value="users">
              <input name="newpw" placeholder="新密码" size="9">
              <input type="hidden" name="confirm" value="RESET">
              <button class="btn">改密</button>
            </form>
            <form class="inline" method="post" onsubmit="return confirm('删除该用户？（评论保留）')">
              <input type="hidden" name="action" value="user_delete">
              <input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
              <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="back" value="users">
              <input type="hidden" name="confirm" value="DELETE">
              <button class="btn danger">删号</button>
            </form>
          </td>
        </tr>
      <?php endforeach; ?>
    </table>
    <?php
    break;

case 'content':
    $kw = trim((string)($_GET['kw'] ?? ''));
    $cm = $kw !== ''
        ? all('SELECT * FROM comments WHERE content LIKE ? OR author LIKE ? ORDER BY id DESC LIMIT 60', ['%' . $kw . '%', '%' . $kw . '%'])
        : all('SELECT * FROM comments ORDER BY id DESC LIMIT 60');
    $rp = all('SELECT * FROM replies ORDER BY id DESC LIMIT 20');
    ?>
    <form method="get" class="row" style="margin-bottom:14px">
      <input type="hidden" name="p" value="content">
      <input name="kw" placeholder="搜索评论内容/作者" value="<?= h($kw) ?>" size="30">
      <button class="btn">搜索</button>
    </form>
    <div class="card">
      <h3>评论（最近 60 条）</h3>
      <table>
        <tr><th>ID</th><th>作者</th><th>内容</th><th>标记</th><th>时间</th><th>操作</th></tr>
        <?php foreach ($cm as $r): ?>
          <tr>
            <td class="mono"><?= (int)$r['id'] ?></td>
            <td><?= h($r['author']) ?></td>
            <td><?= h(mb_substr($r['content'], 0, 70)) ?></td>
            <td><?= (int)$r['flagged'] ? '<span class="tag wr">已标记</span>' : '<span class="muted">-</span>' ?></td>
            <td class="muted mono"><?= h(date('m-d H:i', (int)$r['timestamp'])) ?></td>
            <td>
              <form class="inline" method="post">
                <input type="hidden" name="action" value="comment_flag">
                <input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
                <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
                <input type="hidden" name="back" value="content">
                <button class="btn"><?= (int)$r['flagged'] ? '取消标记' : '标记' ?></button>
              </form>
              <form class="inline" method="post" onsubmit="return confirm('删除这条评论？（会进回收表）')">
                <input type="hidden" name="action" value="comment_del">
                <input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
                <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
                <input type="hidden" name="back" value="content">
                <input type="hidden" name="confirm" value="DELETE">
                <button class="btn danger">删除</button>
              </form>
            </td>
          </tr>
        <?php endforeach; ?>
      </table>
    </div>
    <div class="card">
      <h3>最新回复（20 条）</h3>
      <table>
        <tr><th>ID</th><th>评论</th><th>作者</th><th>内容</th><th>时间</th><th>操作</th></tr>
        <?php foreach ($rp as $r): ?>
          <tr>
            <td class="mono"><?= (int)$r['id'] ?></td>
            <td class="mono">#<?= (int)$r['comment_id'] ?></td>
            <td><?= h($r['author']) ?></td>
            <td><?= h(mb_substr($r['content'], 0, 60)) ?></td>
            <td class="muted mono"><?= h(date('m-d H:i', (int)$r['timestamp'])) ?></td>
            <td>
              <form class="inline" method="post" onsubmit="return confirm('删除这条回复？')">
                <input type="hidden" name="action" value="reply_del">
                <input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
                <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
                <input type="hidden" name="back" value="content">
                <input type="hidden" name="confirm" value="DELETE">
                <button class="btn danger">删除</button>
              </form>
            </td>
          </tr>
        <?php endforeach; ?>
      </table>
    </div>
    <?php
    break;

case 'articles':
    $editId = (int)($_GET['id'] ?? 0);
    $edit = $editId > 0 ? one('SELECT * FROM articles WHERE id = ?', [$editId]) : null;
    $list = all('SELECT id, user_id, title, created_at, CHAR_LENGTH(content) len FROM articles ORDER BY id DESC');
    ?>
    <div class="card">
      <h3><?= $edit ? '编辑文章 #' . (int)$edit['id'] : '新建文章' ?></h3>
      <form method="post">
        <input type="hidden" name="action" value="article_save">
        <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
        <input type="hidden" name="id" value="<?= $edit ? (int)$edit['id'] : 0 ?>">
        <input name="title" placeholder="标题" style="width:100%;margin-bottom:10px" value="<?= h($edit['title'] ?? '') ?>">
        <textarea name="content" placeholder="正文（支持 Markdown）"><?= h($edit['content'] ?? '') ?></textarea>
        <div style="margin-top:10px">
          <button class="btn"><?= $edit ? '保存修改' : '发布' ?></button>
          <?php if ($edit): ?><a class="btn" href="?p=articles">取消编辑</a><?php endif; ?>
        </div>
      </form>
    </div>
    <table>
      <tr><th>ID</th><th>标题</th><th>字数</th><th>作者 ID</th><th>发布</th><th>操作</th></tr>
      <?php foreach ($list as $r): ?>
        <tr>
          <td class="mono"><?= (int)$r['id'] ?></td>
          <td><a href="?p=articles&id=<?= (int)$r['id'] ?>"><?= h($r['title']) ?></a></td>
          <td class="mono"><?= (int)$r['len'] ?></td>
          <td class="mono"><?= (int)$r['user_id'] ?></td>
          <td class="muted mono"><?= h(substr((string)$r['created_at'], 0, 16)) ?></td>
          <td>
            <form class="inline" method="post" onsubmit="return confirm('删除这篇文章？')">
              <input type="hidden" name="action" value="article_del">
              <input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
              <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="back" value="articles">
              <input type="hidden" name="confirm" value="DELETE">
              <button class="btn danger">删除</button>
            </form>
          </td>
        </tr>
      <?php endforeach; ?>
    </table>
    <?php
    break;

case 'ice':
    $rows = all('SELECT player_key, username, is_guest, SUM(taps) taps, COUNT(*) days, MAX(updated_at) last FROM ice_taps GROUP BY player_key ORDER BY taps DESC LIMIT 80');
    ?>
    <table>
      <tr><th>#</th><th>玩家</th><th>类型</th><th>累计</th><th>天数</th><th>最后敲</th><th>key</th><th>操作</th></tr>
      <?php foreach ($rows as $i => $r): ?>
        <tr>
          <td class="mono"><?= $i + 1 ?></td>
          <td><?= h($r['username']) ?></td>
          <td><?= (int)$r['is_guest'] ? '<span class="tag muted">游客</span>' : '<span class="tag ok">注册</span>' ?></td>
          <td class="mono"><?= number_format((int)$r['taps']) ?></td>
          <td class="mono"><?= (int)$r['days'] ?></td>
          <td class="muted mono"><?= h($r['last']) ?></td>
          <td class="muted mono"><?= h($r['player_key']) ?></td>
          <td>
            <form class="inline" method="post" onsubmit="return confirm('清零该玩家所有记录？不可恢复')">
              <input type="hidden" name="action" value="ice_zero">
              <input type="hidden" name="pk" value="<?= h($r['player_key']) ?>">
              <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="back" value="ice">
              <input type="hidden" name="confirm" value="ZERO">
              <button class="btn danger">清零</button>
            </form>
          </td>
        </tr>
      <?php endforeach; ?>
    </table>
    <?php
    break;

case 'system':
    $info = [
        'nginx 版本'   => sh('nginx -v'),
        'PHP 版本'     => sh('php -r "echo PHP_VERSION;"'),
        'MariaDB'      => sh('mariadb -N -B -e "SELECT VERSION();"'),
        '磁盘'          => sh("df -h / | tail -1 | awk '{print \$3\"/\"\$2\" 已用 \"\$5}'"),
        '内存'          => sh("free -m | awk 'NR==2{print \$3\"MB / \"\$2\"MB\"}'"),
        '负载'          => sh("cut -d' ' -f1-3 /proc/loadavg"),
        '运行时长'      => sh('uptime -p'),
        '监听端口'      => sh("ss -lnt | awk 'NR>1{print \$4}' | sort -u | tr '\\n' ' '"),
    ];
    ?>
    <div class="card">
      <h3>服务状态</h3>
      <table>
        <tr><th>服务</th><th>状态</th><th>开机自启</th><th>操作</th></tr>
        <?php foreach (['nginx', 'php8.2-fpm', 'mariadb'] as $s):
            $st = sh("systemctl is-active $s");
            $en = sh("systemctl is-enabled $s"); ?>
          <tr>
            <td><?= h($s) ?></td>
            <td><span class="tag <?= $st === 'active' ? 'ok' : 'bad' ?>"><?= h($st) ?></span></td>
            <td class="muted mono"><?= h($en) ?></td>
            <td>
              <form class="inline" method="post" onsubmit="return confirm('重启 <?= h($s) ?>？会有短暂中断')">
                <input type="hidden" name="action" value="svc">
                <input type="hidden" name="svc" value="<?= h($s) ?>">
                <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
                <input type="hidden" name="back" value="system">
                <input type="hidden" name="confirm" value="RESTART">
                <button class="btn danger">重启</button>
              </form>
            </td>
          </tr>
        <?php endforeach; ?>
      </table>
    </div>
    <div class="card">
      <h3>环境</h3>
      <table>
        <?php foreach ($info as $k => $v): ?>
          <tr><th style="width:130px"><?= h($k) ?></th><td class="mono"><?= h($v) ?></td></tr>
        <?php endforeach; ?>
      </table>
    </div>
    <div class="card">
      <h3>证书到期</h3>
      <pre><?php
        foreach (['greatsong.cn', 'test.greatsong.cn', 'dasong.online'] as $d) {
            $f = "/etc/nginx/ssl/$d.crt";
            if (!is_file($f)) { echo "$d: （无）\n"; continue; }
            $end = sh("openssl x509 -in $f -noout -enddate | cut -d= -f2");
            $ts = strtotime($end);
            $left = $ts ? (int)round(($ts - time()) / 86400) : -1;
            printf("%-20s %s  （剩 %d 天）\n", $d, $end, $left);
        }
      ?></pre>
    </div>
    <div class="card">
      <h3>最近 nginx 错误日志</h3>
      <form method="post" style="margin-bottom:8px">
        <input type="hidden" name="action" value="nginx_test">
        <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
        <input type="hidden" name="back" value="system">
        <button class="btn">执行 nginx -t</button>
      </form>
      <pre><?= h(sh('tail -n 12 /var/log/nginx/error.log')) ?></pre>
    </div>
    <div class="card">
      <h3>最近控制台操作（审计日志）</h3>
      <pre><?= h(sh('tail -n 15 ' . DATA . '/console_audit.log')) ?></pre>
    </div>
    <?php
    break;

case 'deploy':
    $stgStatus = sh('/usr/local/bin/stg status');
    ?>
    <div class="card">
      <h3>预发布环境状态（stg status）</h3>
      <pre><?= h($stgStatus) ?></pre>
    </div>
    <div class="card">
      <h3>操作</h3>
      <p class="muted">这些都直接影响线上，点击前请确认。每次操作都会写审计日志。</p>
      <table>
        <tr><th>命令</th><th>作用</th><th>确认词</th><th></th></tr>
        <tr>
          <td class="mono">stg sync-db</td>
          <td>生产库 → 测试库（整库刷新，测试站数据清空）</td>
          <td class="mono">SYNC</td>
          <td>
            <form method="post" class="row">
              <input type="hidden" name="action" value="deploy">
              <input type="hidden" name="cmd" value="sync-db">
              <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="back" value="deploy">
              <input name="confirm" placeholder="输入 SYNC" size="8">
              <button class="btn">执行</button>
            </form>
          </td>
        </tr>
        <tr>
          <td class="mono">stg promote</td>
          <td>测试站代码 → 正式站（自动备份，可回滚）</td>
          <td class="mono">GO</td>
          <td>
            <form method="post" class="row">
              <input type="hidden" name="action" value="deploy">
              <input type="hidden" name="cmd" value="promote">
              <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="back" value="deploy">
              <input name="confirm" placeholder="输入 GO" size="8">
              <button class="btn danger">执行</button>
            </form>
          </td>
        </tr>
        <tr>
          <td class="mono">stg rollback</td>
          <td>回滚正式站到上次 promote 之前</td>
          <td class="mono">BACK</td>
          <td>
            <form method="post" class="row">
              <input type="hidden" name="action" value="deploy">
              <input type="hidden" name="cmd" value="rollback">
              <input type="hidden" name="csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="back" value="deploy">
              <input name="confirm" placeholder="输入 BACK" size="8">
              <button class="btn danger">执行</button>
            </form>
          </td>
        </tr>
      </table>
    </div>
    <div class="card">
      <h3>最近部署审计</h3>
      <pre><?= h(sh('grep -E "部署|promote|rollback|sync" ' . DATA . '/console_audit.log | tail -n 10')) ?></pre>
    </div>
    <?php
    break;
}
?>
  </main>
</div>
</body>
</html>
