<?php
/**
 * IP 黑白名单管理（单文件）
 * 依赖 cong.php 中的数据库常量与 LOGIN_PASSWORD
 *
 * INSERT IGNORE 去重依赖唯一约束，若尚未建立可执行：
 * ALTER TABLE ip_list ADD UNIQUE KEY uk_group_ip (group_id, ip_address);
 */
declare(strict_types=1);

require_once __DIR__ . '/cong.php';

session_start();

header('Content-Type: text/html; charset=utf-8');

if (empty($_SESSION['password']) || $_SESSION['password'] !== LOGIN_PASSWORD) {
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['admin_password'])) {
        if (trim((string) $_POST['admin_password']) === LOGIN_PASSWORD) {
            $_SESSION['password'] = LOGIN_PASSWORD;
            header('Location: ' . strtok($_SERVER['REQUEST_URI'] ?? 'admin_ip_manager.php', '?'));
            exit;
        }
        $loginError = '密码错误';
    }
    ?>
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>登录 — IP 名单管理</title>
        <style>
            body { font-family: system-ui, sans-serif; max-width: 360px; margin: 4rem auto; padding: 0 1rem; }
            input[type=password] { width: 100%; padding: 0.5rem; box-sizing: border-box; margin: 0.5rem 0; }
            button { padding: 0.5rem 1rem; cursor: pointer; }
            .err { color: #c00; margin-bottom: 1rem; }
        </style>
    </head>
    <body>
        <h1>IP 名单管理</h1>
        <?php if (!empty($loginError)) { ?><p class="err"><?php echo htmlspecialchars($loginError, ENT_QUOTES, 'UTF-8'); ?></p><?php } ?>
        <form method="post">
            <label>后台密码</label>
            <input type="password" name="admin_password" required autocomplete="current-password">
            <button type="submit">进入</button>
        </form>
        <p><small>与 dashboard 使用相同登录密码；若已在后台登录可刷新本页。</small></p>
    </body>
    </html>
    <?php
    exit;
}

$type = (isset($_GET['type']) && $_GET['type'] === 'white') ? 'white' : 'black';
$self = htmlspecialchars($_SERVER['PHP_SELF'] ?? 'admin_ip_manager.php', ENT_QUOTES, 'UTF-8');

$flash = '';
$flashOk = true;

try {
    $dsn = 'mysql:host=127.0.0.1;dbname=' . DB_NAME . ';charset=utf8mb4';
    $pdo = new PDO($dsn, DB_USERNAME, DB_PASSWORD, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
} catch (PDOException $e) {
    http_response_code(500);
    echo '<!DOCTYPE html><html><head><meta charset="utf-8"><title>错误</title></head><body><p>数据库连接失败。</p></body></html>';
    exit;
}

/** 校验分组属于当前 type，返回 true 或 false */
function groupBelongsToType(PDO $pdo, int $groupId, string $type): bool
{
    $st = $pdo->prepare('SELECT 1 FROM ip_groups WHERE id = ? AND type = ? LIMIT 1');
    $st->execute([$groupId, $type]);
    return (bool) $st->fetchColumn();
}

// —— POST：新建分组 ——
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_group') {
    $name = trim((string) ($_POST['group_name'] ?? ''));
    $postType = ($_POST['list_type'] ?? '') === 'white' ? 'white' : 'black';
    if ($name === '') {
        $flash = '分组名称不能为空';
        $flashOk = false;
    } else {
        $st = $pdo->prepare('INSERT INTO ip_groups (name, type) VALUES (?, ?)');
        $st->execute([$name, $postType]);
        $flash = '分组已创建';
        header('Location: ' . $self . '?type=' . urlencode($postType) . '&msg=' . urlencode($flash) . '&ok=1');
        exit;
    }
}

// —— POST：批量导入 IP ——
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'bulk_import') {
    $groupId = (int) ($_POST['import_group_id'] ?? 0);
    $raw = (string) ($_POST['ip_lines'] ?? '');
    if ($groupId <= 0 || !groupBelongsToType($pdo, $groupId, $type)) {
        $flash = '请选择有效的分组';
        $flashOk = false;
    } else {
        $lines = preg_split('/\R/u', $raw) ?: [];
        $uniqueFromText = [];
        foreach ($lines as $line) {
            $ip = trim($line);
            if ($ip === '') {
                continue;
            }
            $uniqueFromText[$ip] = true;
        }
        $candidateIps = array_keys($uniqueFromText);
        $exSt = $pdo->prepare('SELECT ip_address FROM ip_list WHERE group_id = ?');
        $exSt->execute([$groupId]);
        $already = [];
        while ($row = $exSt->fetch(PDO::FETCH_ASSOC)) {
            $already[$row['ip_address']] = true;
        }
        $toInsert = [];
        foreach ($candidateIps as $ip) {
            if (!isset($already[$ip])) {
                $toInsert[] = $ip;
            }
        }
        $inserted = 0;
        $stmt = $pdo->prepare('INSERT IGNORE INTO ip_list (group_id, ip_address) VALUES (?, ?)');
        $pdo->beginTransaction();
        try {
            foreach ($toInsert as $ip) {
                $stmt->execute([$groupId, $ip]);
                $inserted += $stmt->rowCount();
            }
            $pdo->commit();
        } catch (Throwable $e) {
            $pdo->rollBack();
            $flash = '导入失败：' . $e->getMessage();
            $flashOk = false;
        }
        if ($flashOk) {
            $skippedDupText = count($candidateIps) - count($toInsert);
            $flash = '批量导入完成：新插入 ' . $inserted . ' 条；文本去重后 ' . count($candidateIps) . ' 条；本组已存在跳过 ' . $skippedDupText . ' 条';
            header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1');
            exit;
        }
    }
}

// —— POST：按行批量删除 IP（当前 type 下；可选限定分组）——
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'bulk_delete_ips') {
    $scopeGroup = (int) ($_POST['bulk_delete_group_id'] ?? 0);
    $raw = (string) ($_POST['bulk_delete_lines'] ?? '');
    if ($scopeGroup > 0 && !groupBelongsToType($pdo, $scopeGroup, $type)) {
        $flash = '批量删除：分组无效';
        $flashOk = false;
    } else {
        $lines = preg_split('/\R/u', $raw) ?: [];
        $ips = [];
        foreach ($lines as $line) {
            $ip = trim($line);
            if ($ip !== '') {
                $ips[$ip] = true;
            }
        }
        $ips = array_keys($ips);
        if ($ips === []) {
            $flash = '批量删除：未填写任何 IP';
            $flashOk = false;
        } else {
            $sql = 'DELETE il FROM ip_list il
                    INNER JOIN ip_groups ig ON ig.id = il.group_id
                    WHERE ig.type = ? AND il.ip_address = ?';
            if ($scopeGroup > 0) {
                $sql .= ' AND il.group_id = ?';
            }
            $stmt = $pdo->prepare($sql);
            $deleted = 0;
            $pdo->beginTransaction();
            try {
                foreach ($ips as $ip) {
                    if ($scopeGroup > 0) {
                        $stmt->execute([$type, $ip, $scopeGroup]);
                    } else {
                        $stmt->execute([$type, $ip]);
                    }
                    $deleted += $stmt->rowCount();
                }
                $pdo->commit();
            } catch (Throwable $e) {
                $pdo->rollBack();
                $flash = '批量删除失败：' . $e->getMessage();
                $flashOk = false;
            }
            if ($flashOk) {
                $flash = '批量删除完成，共删除 ' . $deleted . ' 条记录（不存在的 IP 不计入）';
                header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1');
                exit;
            }
        }
    }
}

// —— POST：清空当前类型下所有 IP（保留分组）——
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'clear_ips_only') {
    $phrase = trim((string) ($_POST['clear_confirm_phrase'] ?? ''));
    if ($phrase !== '清空IP') {
        $flash = '清空失败：请在确认框中完整输入「清空IP」';
        $flashOk = false;
    } else {
        $del = $pdo->prepare(
            'DELETE il FROM ip_list il
             INNER JOIN ip_groups ig ON ig.id = il.group_id
             WHERE ig.type = ?'
        );
        $del->execute([$type]);
        $flash = '已清空当前类型下所有 IP（分组已保留），删除 ' . $del->rowCount() . ' 条';
        header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1');
        exit;
    }
}

// —— POST：清空当前类型下全部分组及 IP ——
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'clear_type_all') {
    $phrase = trim((string) ($_POST['clear_confirm_phrase_all'] ?? ''));
    if ($phrase !== '清空全部') {
        $flash = '清空失败：请在确认框中完整输入「清空全部」';
        $flashOk = false;
    } else {
        $pdo->beginTransaction();
        try {
            $pdo->prepare(
                'DELETE il FROM ip_list il
                 INNER JOIN ip_groups ig ON ig.id = il.group_id
                 WHERE ig.type = ?'
            )->execute([$type]);
            $pdo->prepare('DELETE FROM ip_groups WHERE type = ?')->execute([$type]);
            $pdo->commit();
            $flash = '已清空当前类型下的所有分组及 IP';
            header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1');
            exit;
        } catch (Throwable $e) {
            $pdo->rollBack();
            $flash = '清空失败：' . $e->getMessage();
            $flashOk = false;
        }
    }
}

// —— GET：删除分组 ——
if (isset($_GET['delete_group'])) {
    $gid = (int) $_GET['delete_group'];
    if ($gid > 0 && groupBelongsToType($pdo, $gid, $type)) {
        $pdo->beginTransaction();
        try {
            $pdo->prepare('DELETE FROM ip_list WHERE group_id = ?')->execute([$gid]);
            $pdo->prepare('DELETE FROM ip_groups WHERE id = ? AND type = ?')->execute([$gid, $type]);
            $pdo->commit();
            $flash = '分组及其 IP 已删除';
        } catch (Throwable $e) {
            $pdo->rollBack();
            $flash = '删除分组失败';
            $flashOk = false;
        }
    } else {
        $flash = '无效的分组';
        $flashOk = false;
    }
    header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . ($flashOk ? '&ok=1' : ''));
    exit;
}

// —— GET：删除单条 IP ——
if (isset($_GET['delete_ip'])) {
    $ipId = (int) $_GET['delete_ip'];
    if ($ipId > 0) {
        $del = $pdo->prepare(
            'DELETE il FROM ip_list il
             INNER JOIN ip_groups ig ON ig.id = il.group_id
             WHERE il.id = ? AND ig.type = ?'
        );
        $del->execute([$ipId, $type]);
        $flash = $del->rowCount() > 0 ? '已删除该 IP 记录' : '未找到记录或无权删除';
        $flashOk = $del->rowCount() > 0;
    } else {
        $flash = '无效的 IP 记录';
        $flashOk = false;
    }
    header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . ($flashOk ? '&ok=1' : ''));
    exit;
}

if (isset($_GET['msg'])) {
    $flash = (string) $_GET['msg'];
    $flashOk = isset($_GET['ok']) && $_GET['ok'] === '1';
}

// 当前 type 下的分组
$groupsStmt = $pdo->prepare('SELECT id, name FROM ip_groups WHERE type = ? ORDER BY id ASC');
$groupsStmt->execute([$type]);
$groups = $groupsStmt->fetchAll();

$ipSearch = isset($_GET['ip_search']) ? trim((string) $_GET['ip_search']) : '';
$ipSearchSql = '';
$ipListBind = [$type];
if ($ipSearch !== '') {
    $ipSearchSql = ' AND il.ip_address LIKE ?';
    $ipListBind[] = '%' . addcslashes($ipSearch, '\\%_') . '%';
}
$searchQuerySuffix = $ipSearch !== '' ? '&ip_search=' . rawurlencode($ipSearch) : '';

// IP 列表分页
$perPage = 50;
$page = max(1, (int) ($_GET['page'] ?? 1));
$offset = ($page - 1) * $perPage;

$countSql = 'SELECT COUNT(*) FROM ip_list il INNER JOIN ip_groups ig ON ig.id = il.group_id WHERE ig.type = ?' . $ipSearchSql;
$totalStmt = $pdo->prepare($countSql);
$totalStmt->execute($ipListBind);
$totalIps = (int) $totalStmt->fetchColumn();
$totalPages = max(1, (int) ceil($totalIps / $perPage));
if ($page > $totalPages) {
    $page = $totalPages;
    $offset = ($page - 1) * $perPage;
}

$listSql = 'SELECT il.id, il.ip_address, il.group_id, ig.name AS group_name
            FROM ip_list il
            INNER JOIN ip_groups ig ON ig.id = il.group_id
            WHERE ig.type = ?' . $ipSearchSql . '
            ORDER BY il.id DESC
            LIMIT ' . (int) $perPage . ' OFFSET ' . (int) $offset;
$listStmt = $pdo->prepare($listSql);
$listStmt->execute($ipListBind);
$ipRows = $listStmt->fetchAll();

function h(string $s): string
{
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

$typeParam = 'type=' . urlencode($type);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>IP 名单管理 — <?php echo $type === 'white' ? '白名单' : '黑名单'; ?></title>
    <style>
        :root { --bd: #ccc; --bg: #f6f7f9; --ok: #0a0; --err: #c00; }
        body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 1rem 1.25rem 2rem; background: var(--bg); color: #222; }
        h1 { font-size: 1.35rem; margin-top: 0; }
        h2 { font-size: 1.1rem; margin: 1.5rem 0 0.75rem; border-bottom: 1px solid var(--bd); padding-bottom: 0.35rem; }
        .tabs { display: flex; gap: 0.5rem; margin-bottom: 1.25rem; flex-wrap: wrap; }
        .tabs a {
            display: inline-block; padding: 0.45rem 1rem; text-decoration: none; border-radius: 6px;
            border: 1px solid var(--bd); background: #fff; color: #111;
        }
        .tabs a.active { background: #111; color: #fff; border-color: #111; }
        .flash { padding: 0.65rem 1rem; border-radius: 6px; margin-bottom: 1rem; }
        .flash.ok { background: #e8f5e9; color: #1b5e20; }
        .flash.bad { background: #ffebee; color: #b71c1c; }
        section.card { background: #fff; border: 1px solid var(--bd); border-radius: 8px; padding: 1rem 1.1rem; margin-bottom: 1rem; max-width: 960px; }
        label { display: block; font-weight: 600; margin: 0.5rem 0 0.25rem; font-size: 0.9rem; }
        input[type=text], select, textarea { width: 100%; max-width: 100%; box-sizing: border-box; padding: 0.45rem 0.5rem; border: 1px solid var(--bd); border-radius: 4px; font-size: 0.95rem; }
        textarea { min-height: 160px; font-family: ui-monospace, monospace; }
        button, .btn-link {
            display: inline-block; margin-top: 0.5rem; padding: 0.45rem 0.9rem; cursor: pointer; border-radius: 4px;
            border: 1px solid var(--bd); background: #fff; font-size: 0.9rem; text-decoration: none; color: #111;
        }
        button.primary { background: #1976d2; color: #fff; border-color: #1565c0; }
        button.danger, a.danger { color: var(--err); border-color: #e57373; }
        table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
        th, td { border: 1px solid var(--bd); padding: 0.45rem 0.55rem; text-align: left; }
        th { background: #eee; }
        tr:nth-child(even) { background: #fafafa; }
        .pager { margin-top: 1rem; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
        .muted { color: #666; font-size: 0.85rem; }
        .row-inline { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: flex-end; }
        .row-inline > div { flex: 1; min-width: 200px; }
        section.card.danger-zone { border-color: #e57373; background: #fff8f8; }
        section.card.danger-zone h2 { color: #b71c1c; }
    </style>
</head>
<body>
    <h1>IP 黑白名单管理</h1>

    <nav class="tabs">
        <a href="<?php echo $self; ?>?type=black<?php echo $searchQuerySuffix; ?>" class="<?php echo $type === 'black' ? 'active' : ''; ?>">黑名单管理</a>
        <a href="<?php echo $self; ?>?type=white<?php echo $searchQuerySuffix; ?>" class="<?php echo $type === 'white' ? 'active' : ''; ?>">白名单管理</a>
    </nav>

    <?php if ($flash !== '') { ?>
        <div class="flash <?php echo $flashOk ? 'ok' : 'bad'; ?>"><?php echo h($flash); ?></div>
    <?php } ?>

    <section class="card">
        <h2>新建分组</h2>
        <form method="post" action="<?php echo $self; ?>?<?php echo $typeParam; ?>">
            <input type="hidden" name="action" value="create_group">
            <input type="hidden" name="list_type" value="<?php echo h($type); ?>">
            <label for="group_name">组名</label>
            <input type="text" id="group_name" name="group_name" required maxlength="191" placeholder="例如：广告渠道 A">
            <button type="submit" class="primary">创建分组（<?php echo $type === 'white' ? '白名单' : '黑名单'; ?>）</button>
        </form>
    </section>

    <section class="card">
        <h2>当前视图下的分组</h2>
        <?php if ($groups === []) { ?>
            <p class="muted">暂无分组，请先创建。</p>
        <?php } else { ?>
            <table>
                <thead>
                    <tr><th>ID</th><th>组名</th><th>操作</th></tr>
                </thead>
                <tbody>
                    <?php foreach ($groups as $g) { ?>
                        <tr>
                            <td><?php echo (int) $g['id']; ?></td>
                            <td><?php echo h((string) $g['name']); ?></td>
                            <td>
                                <a class="btn-link danger" href="<?php echo $self; ?>?<?php echo $typeParam; ?>&amp;delete_group=<?php echo (int) $g['id']; ?>"
                                   onclick="return confirm(<?php echo json_encode('确定删除分组「' . (string) $g['name'] . '」及其下所有 IP？', JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE); ?>);">删除组</a>
                            </td>
                        </tr>
                    <?php } ?>
                </tbody>
            </table>
        <?php } ?>
    </section>

    <section class="card">
        <h2>批量导入 IP</h2>
        <p class="muted">每行一个 IP；首尾空格与空行忽略；同一文本内重复只计一次；本组数据库中已存在的 IP 不会重复插入。</p>
        <form method="post" action="<?php echo $self; ?>?<?php echo $typeParam; ?>">
            <input type="hidden" name="action" value="bulk_import">
            <label for="import_group_id">目标分组</label>
            <select id="import_group_id" name="import_group_id" required>
                <option value="">— 请选择 —</option>
                <?php foreach ($groups as $g) { ?>
                    <option value="<?php echo (int) $g['id']; ?>"><?php echo h((string) $g['name']); ?> (ID <?php echo (int) $g['id']; ?>)</option>
                <?php } ?>
            </select>
            <label for="ip_lines">IP 列表</label>
            <textarea id="ip_lines" name="ip_lines" placeholder="1.2.3.4&#10;2001:db8::1"></textarea>
            <button type="submit" class="primary">导入到所选分组</button>
        </form>
    </section>

    <section class="card">
        <h2>批量删除 IP</h2>
        <p class="muted">每行一个 IP；同一 IP 在文本中多次出现会去重后删除。选择「不限定分组」时，会删除当前<?php echo $type === 'white' ? '白' : '黑'; ?>名单类型下所有分组中匹配的 IP。</p>
        <form method="post" action="<?php echo $self; ?>?<?php echo $typeParam; ?>" onsubmit="return confirm('确定按列表批量删除？');">
            <input type="hidden" name="action" value="bulk_delete_ips">
            <label for="bulk_delete_group_id">作用范围</label>
            <select id="bulk_delete_group_id" name="bulk_delete_group_id">
                <option value="0">不限定分组（当前类型下全部匹配）</option>
                <?php foreach ($groups as $g) { ?>
                    <option value="<?php echo (int) $g['id']; ?>">仅分组：<?php echo h((string) $g['name']); ?> (ID <?php echo (int) $g['id']; ?>)</option>
                <?php } ?>
            </select>
            <label for="bulk_delete_lines">要删除的 IP 列表</label>
            <textarea id="bulk_delete_lines" name="bulk_delete_lines" placeholder="1.2.3.4&#10;5.6.7.8"></textarea>
            <button type="submit" class="danger">执行批量删除</button>
        </form>
    </section>

    <section class="card danger-zone">
        <h2>清空数据（危险操作）</h2>
        <p class="muted">以下操作仅影响当前视图类型（<?php echo $type === 'white' ? '白名单' : '黑名单'; ?>），不影响另一类型。</p>
        <form method="post" action="<?php echo $self; ?>?<?php echo $typeParam; ?>" style="margin-bottom:1.25rem;" onsubmit="return confirm('确定清空当前类型下所有 IP？分组将保留。');">
            <input type="hidden" name="action" value="clear_ips_only">
            <label for="clear_confirm_phrase">确认清空 IP（在框内输入：<strong>清空IP</strong>）</label>
            <input type="text" id="clear_confirm_phrase" name="clear_confirm_phrase" autocomplete="off" placeholder="清空IP">
            <button type="submit" class="danger">清空当前类型全部 IP（保留分组）</button>
        </form>
        <form method="post" action="<?php echo $self; ?>?<?php echo $typeParam; ?>" onsubmit="return confirm('确定删除当前类型下所有分组及 IP？此操作不可恢复。');">
            <input type="hidden" name="action" value="clear_type_all">
            <label for="clear_confirm_phrase_all">确认清空全部（在框内输入：<strong>清空全部</strong>）</label>
            <input type="text" id="clear_confirm_phrase_all" name="clear_confirm_phrase_all" autocomplete="off" placeholder="清空全部">
            <button type="submit" class="danger">清空当前类型全部分组及 IP</button>
        </form>
    </section>

    <section class="card">
        <h2>IP 列表（<?php echo $type === 'white' ? '白名单' : '黑名单'; ?>）</h2>
        <form method="get" action="<?php echo $self; ?>" class="row-inline" style="margin-bottom:12px;align-items:center;">
            <input type="hidden" name="type" value="<?php echo h($type); ?>">
            <div style="flex:2;min-width:220px;">
                <label for="ip_search">搜索 IP（模糊匹配）</label>
                <input type="text" id="ip_search" name="ip_search" value="<?php echo h($ipSearch); ?>" placeholder="例如 192.168 或完整 IP">
            </div>
            <div style="padding-top:1.5rem;">
                <button type="submit" class="primary">搜索</button>
                <?php if ($ipSearch !== '') { ?>
                <a class="btn-link" href="<?php echo $self; ?>?<?php echo $typeParam; ?>">清除</a>
                <?php } ?>
            </div>
        </form>
        <p class="muted">共 <?php echo $totalIps; ?> 条，每页 <?php echo $perPage; ?> 条<?php echo $ipSearch !== '' ? '（已筛选）' : ''; ?>。</p>
        <?php if ($ipRows === []) { ?>
            <p class="muted">当前类型下暂无 IP。</p>
        <?php } else { ?>
            <table>
                <thead>
                    <tr><th>ID</th><th>IP</th><th>分组</th><th>操作</th></tr>
                </thead>
                <tbody>
                    <?php foreach ($ipRows as $row) { ?>
                        <tr>
                            <td><?php echo (int) $row['id']; ?></td>
                            <td><code><?php echo h((string) $row['ip_address']); ?></code></td>
                            <td><?php echo h((string) $row['group_name']); ?></td>
                            <td>
                                <a class="btn-link danger" href="<?php echo $self; ?>?<?php echo $typeParam; ?>&amp;page=<?php echo $page; echo $searchQuerySuffix; ?>&amp;delete_ip=<?php echo (int) $row['id']; ?>"
                                   onclick="return confirm(<?php echo json_encode('确定删除 IP ' . (string) $row['ip_address'] . '？', JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE); ?>);">删除</a>
                            </td>
                        </tr>
                    <?php } ?>
                </tbody>
            </table>
            <?php if ($totalPages > 1) { ?>
                <div class="pager">
                    <?php if ($page > 1) { ?>
                        <a class="btn-link" href="<?php echo $self; ?>?<?php echo $typeParam; ?>&amp;page=<?php echo $page - 1; echo $searchQuerySuffix; ?>">上一页</a>
                    <?php } ?>
                    <span>第 <?php echo $page; ?> / <?php echo $totalPages; ?> 页</span>
                    <?php if ($page < $totalPages) { ?>
                        <a class="btn-link" href="<?php echo $self; ?>?<?php echo $typeParam; ?>&amp;page=<?php echo $page + 1; echo $searchQuerySuffix; ?>">下一页</a>
                    <?php } ?>
                </div>
            <?php } ?>
        <?php } ?>
    </section>
</body>
</html>
