Вход Регистрация
Файл: new_top/modules/admin/index.php
Строк: 1411
<?php
/**
 * Multifunctional Admin Panel
 */

declare(strict_types=1);

$isAdmin = !empty($_SESSION['user_id']) && !empty($_SESSION['user_level']) && (int)$_SESSION['user_level'] >= 9;
$action $_GET['action'] ?? 'dashboard';

if (!
$isAdmin) {
    if (
$_SERVER['REQUEST_METHOD'] === 'POST') {
        
csrf_verify();
        if (!
login_throttle_check('admin_login')) {
            
$error 'Слишком много попыток. Подождите 15 минут.';
        } else {
            
$login trim($_POST['login'] ?? '');
            
$pass  $_POST['password'] ?? '';
            
$user Database::fetch('SELECT * FROM user WHERE login = ? AND ban = 0', [$login]);
            if (
$user && password_verify($pass$user['password']) && (int)$user['level'] >= 9) {
                
login_throttle_reset('admin_login');
                
session_regenerate_id(true);
                
$_SESSION['user_id'] = (int)$user['id'];
                
$_SESSION['user_level'] = (int)$user['level'];
                
$_SESSION['user_login'] = $user['login'];
                
redirect('/?module=admin');
            }
            
login_throttle_hit('admin_login');
            
$error 'Неверный логин или пароль';
        }
    }
    
?>
    <!DOCTYPE html>
    <html lang="ru"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    <?= pwa_meta() ?>
    <title>Вход — Админ</title>
    <link rel="stylesheet" href="/assets/css/style.css">
    </head><body>
    <div class="auth-wrap">
        <div class="auth-box">
            <div class="logo-lg">A</div>
            <h1>Админ-панель</h1>
            <?php if (!empty($error)): ?><div class="alert alert-error"><?= e($error?></div><?php endif; ?>
            <form method="post">
                <?= csrf_field() ?>
                <label>Логин</label>
                <input type="text" name="login" required autofocus>
                <label>Пароль</label>
                <input type="password" name="password" required>
                <button type="submit" class="btn btn-primary btn-block">Войти</button>
            </form>
            <p class="text-center text-muted" style="margin-top:18px;font-size:.85rem"><a href="/">← На сайт</a></p>
        </div>
    </div>
    <?= pwa_register() ?>
</body></html>
    <?php
    
exit;
}

if (
$action === 'logout') {
    
session_destroy();
    
redirect('/?module=admin');
}

$flash $_SESSION['flash'] ?? '';
unset(
$_SESSION['flash']);
$msg '';
$err '';

// ---- POST handlers ----
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
csrf_verify();
    
$postAction $_POST['do'] ?? '';

    try {
        if (
$postAction === 'ban_pf') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('UPDATE platforms SET ban = 1 WHERE id = ?', [$id]);
            
$_SESSION['flash'] = 'Площадка заблокирована';
            
redirect('/?module=admin&action=platforms');
        }
        if (
$postAction === 'unban_pf') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('UPDATE platforms SET ban = 0 WHERE id = ?', [$id]);
            
$_SESSION['flash'] = 'Площадка разблокирована';
            
redirect('/?module=admin&action=platforms');
        }
        if (
$postAction === 'approve_pf') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('UPDATE platforms SET mode = 0 WHERE id = ?', [$id]);
            
$_SESSION['flash'] = 'Площадка одобрена';
            
redirect('/?module=admin&action=platforms');
        }
        if (
$postAction === 'mod_pf') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('UPDATE platforms SET mode = 1 WHERE id = ?', [$id]);
            
$_SESSION['flash'] = 'Отправлена на модерацию';
            
redirect('/?module=admin&action=platforms');
        }
        if (
$postAction === 'del_pf') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('DELETE FROM platforms WHERE id = ?', [$id]);
            
Database::query('DELETE FROM hs WHERE pf = ?', [$id]);
            
Database::query('DELETE FROM ht WHERE pf = ?', [$id]);
            
Database::query('DELETE FROM on_log WHERE pf = ?', [$id]);
            
Database::query('DELETE FROM in_log WHERE pf = ?', [$id]);
            
$_SESSION['flash'] = 'Площадка удалена';
            
redirect('/?module=admin&action=platforms');
        }
        if (
$postAction === 'gold_pf') {
            
$id = (int)($_POST['id'] ?? 0);
            
$days max(1, (int)($_POST['days'] ?? 7));
            
$until time() + $days 86400;
            
Database::query('UPDATE platforms SET gold = ? WHERE id = ?', [$until$id]);
            
$_SESSION['flash'] = "GOLD на {$days} дн.";
            
redirect('/?module=admin&action=platforms');
        }
        if (
$postAction === 'ban_user') {
            
$id = (int)($_POST['id'] ?? 0);
            if (
$id !== (int)$_SESSION['user_id']) {
                
Database::query('UPDATE user SET ban = 1 WHERE id = ?', [$id]);
            }
            
$_SESSION['flash'] = 'Пользователь заблокирован';
            
redirect('/?module=admin&action=users');
        }
        if (
$postAction === 'unban_user') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('UPDATE user SET ban = 0 WHERE id = ?', [$id]);
            
$_SESSION['flash'] = 'Пользователь разблокирован';
            
redirect('/?module=admin&action=users');
        }
        if (
$postAction === 'set_level') {
            
$id = (int)($_POST['id'] ?? 0);
            
$lvl = (int)($_POST['level'] ?? 1);
            if (
$id !== (int)$_SESSION['user_id']) {
                
Database::query('UPDATE user SET level = ? WHERE id = ?', [$lvl$id]);
            }
            
$_SESSION['flash'] = 'Уровень обновлён';
            
redirect('/?module=admin&action=users');
        }
        if (
$postAction === 'add_cat') {
            
$name trim($_POST['name'] ?? '');
            
$sort = (int)($_POST['sort'] ?? 0);
            if (
$name !== '') {
                
Database::insert('cat', ['name' => mb_substr($name080), 'sort' => $sort]);
                
$_SESSION['flash'] = 'Категория добавлена';
            }
            
redirect('/?module=admin&action=cats');
        }
        if (
$postAction === 'del_cat') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('DELETE FROM cat WHERE id = ?', [$id]);
            
$_SESSION['flash'] = 'Категория удалена';
            
redirect('/?module=admin&action=cats');
        }
        if (
$postAction === 'save_config') {
            
Database::query(
                
'UPDATE config SET title = ?, keywords = ?, description = ?, pf_max = ?, pf_mod = ?, reg = ?, reg_mod = ? WHERE id = 1',
                [
                    
trim($_POST['title'] ?? ''),
                    
trim($_POST['keywords'] ?? ''),
                    
trim($_POST['description'] ?? ''),
                    
max(1, (int)($_POST['pf_max'] ?? 100)),
                    !empty(
$_POST['pf_mod']) ? 0,
                    !empty(
$_POST['reg']) ? 0,
                    !empty(
$_POST['reg_mod']) ? 0,
                ]
            );
            
$_SESSION['flash'] = 'Настройки сохранены';
            
redirect('/?module=admin&action=config');
        }
        if (
$postAction === 'ban_ip') {
            
$ip trim($_POST['ip'] ?? '');
            
$reason trim($_POST['reason'] ?? '');
            
$hours = (int)($_POST['hours'] ?? 0);
            if (
$ip !== '' && filter_var($ipFILTER_VALIDATE_IP)) {
                
AntiCheat::banIp($ip$reason$hours, (int)$_SESSION['user_id']);
                
$_SESSION['flash'] = 'IP заблокирован';
            } else {
                
$_SESSION['flash'] = 'Некорректный IP';
            }
            
redirect('/?module=admin&action=anticheat');
        }
        if (
$postAction === 'unban_ip') {
            
$ip trim($_POST['ip'] ?? '');
            
AntiCheat::unbanIp($ip);
            
$_SESSION['flash'] = 'IP разблокирован';
            
redirect('/?module=admin&action=anticheat');
        }
        if (
$postAction === 'save_points_cfg') {
            
Points::setCfg('enabled', !empty($_POST['enabled']) ? '1' '0');
            
Points::setCfg('in_reward', (string)max(0, (int)($_POST['in_reward'] ?? 1)));
            
Points::setCfg('gold_day_cost', (string)max(1, (int)($_POST['gold_day_cost'] ?? 50)));
            
Points::setCfg('ad_point_per_currency', (string)max(1, (int)($_POST['ad_point_per_currency'] ?? 1)));
            
$_SESSION['flash'] = 'Настройки баллов сохранены';
            
redirect('/?module=admin&action=points');
        }
        if (
$postAction === 'adjust_points') {
            
$uid = (int)($_POST['user_id'] ?? 0);
            
$delta = (int)($_POST['delta'] ?? 0);
            
$note mb_substr(trim($_POST['note'] ?? 'admin'), 040);
            if (
$uid && $delta !== 0) {
                if (
Points::add($uid$delta'admin'0)) {
                    
$_SESSION['flash'] = 'Баллы обновлены';
                } else {
                    
$_SESSION['flash'] = 'Не удалось (недостаточно при списании?)';
                }
            }
            
redirect('/?module=admin&action=points');
        }
        if (
$postAction === 'add_ad') {
            
$pos trim($_POST['position'] ?? 'home_top');
            if (!isset(
Ads::positions()[$pos])) $pos 'home_top';
            
$url sanitize_platform_url((string)($_POST['url'] ?? '')) ?? '';
            
$name mb_substr(trim($_POST['name'] ?? ''), 064);
            
$text mb_substr(trim($_POST['text'] ?? ''), 0200);
            
$days max(1, (int)($_POST['days'] ?? 7));
            
$now time();
            if (
$url === '' || $name === '') {
                
$_SESSION['flash'] = 'URL и название обязательны';
            } else {
                
Database::insert('ads', [
                    
'position' => $pos,
                    
'user_id' => 0,
                    
'status' => 1,
                    
'url' => $url,
                    
'name' => $name,
                    
'text' => $text,
                    
'color' => mb_substr(trim($_POST['color'] ?? ''), 032),
                    
'bold' => !empty($_POST['bold']) ? 0,
                    
'italic' => !empty($_POST['italic']) ? 0,
                    
'days' => $days,
                    
'price' => 0,
                    
'time_pay' => $now,
                    
'time_add' => $now,
                    
'time_end' => $now $days 86400,
                ]);
                
$_SESSION['flash'] = 'Реклама добавлена';
            }
            
redirect('/?module=admin&action=ads');
        }
        if (
$postAction === 'ad_status') {
            
$id = (int)($_POST['id'] ?? 0);
            
$st = (int)($_POST['status'] ?? 0);
            if (
$st === 1) {
                
$ad Database::fetch('SELECT * FROM ads WHERE id = ?', [$id]);
                
$days max(1, (int)($ad['days'] ?? 7));
                
$end time() + $days 86400;
                if (
$ad && (int)$ad['time_end'] > time()) {
                    
$end = (int)$ad['time_end'];
                }
                
Database::query('UPDATE ads SET status = 1, time_pay = ?, time_end = ? WHERE id = ?', [time(), $end$id]);
            } else {
                
Database::query('UPDATE ads SET status = ? WHERE id = ?', [$st$id]);
            }
            
$_SESSION['flash'] = 'Статус обновлён';
            
redirect('/?module=admin&action=ads');
        }
        if (
$postAction === 'del_ad') {
            
Database::query('DELETE FROM ads WHERE id = ?', [(int)($_POST['id'] ?? 0)]);
            
$_SESSION['flash'] = 'Реклама удалена';
            
redirect('/?module=admin&action=ads');
        }
        if (
$postAction === 'save_ad_prices') {
            
$positions Ads::positions();
            foreach (
$positions as $pos => $title) {
                
$price = (float)($_POST['price'][$pos] ?? 0);
                
$en = !empty($_POST['enabled'][$pos]) ? 0;
                
Database::query(
                    
'INSERT INTO ad_prices (position, title, price_day, enabled, sort) VALUES (?, ?, ?, ?, 0)
                     ON DUPLICATE KEY UPDATE title = VALUES(title), price_day = VALUES(price_day), enabled = VALUES(enabled)'
,
                    [
$pos$title$price$en]
                );
            }
            
$_SESSION['flash'] = 'Цены сохранены';
            
redirect('/?module=admin&action=ads');
        }
        if (
$postAction === 'add_news') {
            
$title trim($_POST['title'] ?? '');
            
$text trim($_POST['text'] ?? '');
            if (
$title !== '' && $text !== '') {
                try {
                    
Database::insert('news', [
                        
'title' => mb_substr($title0150),
                        
'text' => $text,
                        
'time_add' => time(),
                        
'active' => 1,
                    ]);
                    
$_SESSION['flash'] = 'Новость добавлена';
                } catch (
Throwable $e) {
                    
$_SESSION['flash'] = 'Ошибка: создайте таблицу news (см. sql/migrate_news.sql)';
                }
            }
            
redirect('/?module=admin&action=news');
        }
        if (
$postAction === 'del_news') {
            
$id = (int)($_POST['id'] ?? 0);
            try { 
Database::query('DELETE FROM news WHERE id = ?', [$id]); } catch (Throwable $e) {}
            
$_SESSION['flash'] = 'Новость удалена';
            
redirect('/?module=admin&action=news');
        }
        if (
$postAction === 'edit_pf') {
            
$id = (int)($_POST['id'] ?? 0);
            
$safeUrl sanitize_platform_url((string)($_POST['url'] ?? ''));
            if (
$safeUrl === null) {
                throw new 
RuntimeException('Некорректный URL');
            }
            
Database::query(
                
'UPDATE platforms SET url = ?, message = ?, cat_id = ? WHERE id = ?',
                [
                    
$safeUrl,
                    
mb_substr(trim($_POST['message'] ?? ''), 0120),
                    (int)(
$_POST['cat_id'] ?? 13),
                    
$id,
                ]
            );
            
$_SESSION['flash'] = 'Площадка обновлена';
            
redirect('/?module=admin&action=platforms');
        }
    } catch (
Throwable $e) {
        
$err $e->getMessage();
    }
}

function 
adminHeader(string $titlestring $active): void
{
    
$tabs = [
        
'dashboard'  => 'Обзор',
        
'stats'      => 'Графики',
        
'platforms'  => 'Площадки',
        
'users'      => 'Юзеры',
        
'cats'       => 'Категории',
        
'config'     => 'Настройки',
        
'news'       => 'Новости',
        
'anticheat'  => 'Антинакрутка',
        
'ads'        => 'Реклама',
        
'points'     => 'Баллы',
    ];
    echo 
'<!DOCTYPE html><html lang="ru"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    <?= pwa_meta() ?>
    <title>' 
e($title) . ' — Админ</title>
    <link rel="stylesheet" href="/assets/css/style.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
    </head><body><div class="app">
    <header class="topbar">
        <div class="topbar-brand"><div class="logo">A</div><span>Админ</span></div>
        <nav class="topbar-nav">
            <a href="/">Топ</a>
            <a href="/?module=admin&action=logout">Выход</a>
        </nav>
    </header>
    <nav class="tabs">'
;
    foreach (
$tabs as $k => $label) {
        
$cls $k === $active 'active' '';
        echo 
'<a class="' $cls '" href="/?module=admin&action=' $k '">' e($label) . '</a>';
    }
    echo 
'</nav>';
}

function 
adminFooter(): void
{
    echo 
pwa_register() . '</div></body></html>';
}

// ================= DASHBOARD =================
if ($action === 'dashboard') {
    
$s Stats::summary();
    
$top Stats::topPlatforms(5);
    
adminHeader('Обзор''dashboard');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (
$err) echo '<div class="alert alert-error">' e($err) . '</div>';
    
?>
    <h1 class="page-title">Панель управления</h1>
    <p class="page-sub">Сводка по системе</p>
    <div class="metric-row metric-row-4">
        <div class="metric"><div class="m-label">Площадки</div><div class="m-val blue"><?= $s['platforms'?></div></div>
        <div class="metric"><div class="m-label">Активные</div><div class="m-val"><?= $s['active'?></div></div>
        <div class="metric"><div class="m-label">На модерации</div><div class="m-val gold"><?= $s['mod'?></div></div>
        <div class="metric"><div class="m-label">Бан</div><div class="m-val danger"><?= $s['banned'?></div></div>
        <div class="metric"><div class="m-label">Пользователи</div><div class="m-val blue"><?= $s['users'?></div></div>
        <div class="metric"><div class="m-label">Хосты</div><div class="m-val"><?= number_format($s['hs'], 0''' '?></div></div>
        <div class="metric"><div class="m-label">Хиты</div><div class="m-val"><?= number_format($s['ht'], 0''' '?></div></div>
        <div class="metric"><div class="m-label">Онлайн</div><div class="m-val gold"><?= $s['online'?></div></div>
    </div>
    <div class="card">
        <h2>Топ-5 площадок</h2>
        <?php if (!$top): ?>
            <p class="text-muted">Нет данных</p>
        <?php else: ?>
            <div class="table-wrap"><table class="data">
                <thead><tr><th>#</th><th>URL</th><th>Хосты</th><th>Хиты</th></tr></thead>
                <tbody>
                <?php foreach ($top as $i => $p): ?>
                    <tr>
                        <td><?= $i 1 ?></td>
                        <td><strong><?= e($p['url']) ?></strong></td>
                        <td><?= (int)$p['hs'?></td>
                        <td><?= (int)$p['ht'?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table></div>
        <?php endif; ?>
    </div>
    <?php
    $catsSum 
Stats::categorySummary();
    
$movers Stats::movers(5);
    
$stale Stats::stalePlatforms(710);
    
?>
    <div class="card">
        <h2>Категории (активные)</h2>
        <div class="table-wrap"><table class="data">
            <thead><tr><th>Категория</th><th>Пл.</th><th>Хосты</th><th>Хиты</th><th>In</th></tr></thead>
            <tbody>
            <?php foreach ($catsSum as $c): if ((int)$c['pf_cnt'] < 1) continue; ?>
                <tr>
                    <td><?= e($c['name']) ?></td>
                    <td><?= (int)$c['pf_cnt'?></td>
                    <td><?= number_format((int)$c['hs'], 0''' '?></td>
                    <td><?= number_format((int)$c['ht'], 0''' '?></td>
                    <td><?= (int)$c['inn'?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table></div>
    </div>
    <div class="chart-grid chart-grid-2">
        <div class="card">
            <h2>Рост в топе</h2>
            <?php if (empty($movers['up'])): ?>
                <p class="text-muted">Нет снимков ранга за вчера (нужен cron)</p>
            <?php else: foreach ($movers['up'] as $m): ?>
                <div style="padding:8px 0;border-bottom:1px solid var(--border);font-size:.88rem">
                    <strong><?= e($m['url']) ?></strong>
                    <span style="color:var(--accent)">↑<?= (int)$m['delta'?></span>
                    <span class="text-muted">#<?= (int)$m['prev'?> → #<?= (int)$m['rank'?></span>
                </div>
            <?php endforeach; endif; ?>
        </div>
        <div class="card">
            <h2>Падение в топе</h2>
            <?php if (empty($movers['down'])): ?>
                <p class="text-muted">Нет данных</p>
            <?php else: foreach ($movers['down'] as $m): ?>
                <div style="padding:8px 0;border-bottom:1px solid var(--border);font-size:.88rem">
                    <strong><?= e($m['url']) ?></strong>
                    <span style="color:var(--danger)">↓<?= abs((int)$m['delta']) ?></span>
                    <span class="text-muted">#<?= (int)$m['prev'?> → #<?= (int)$m['rank'?></span>
                </div>
            <?php endforeach; endif; ?>
        </div>
    </div>
    <div class="card">
        <h2>Без хитов 7+ дней</h2>
        <?php if (!$stale): ?>
            <p class="text-muted">Таких площадок нет</p>
        <?php else: foreach ($stale as $s): ?>
            <div style="padding:8px 0;border-bottom:1px solid var(--border);font-size:.88rem">
                #<?= (int)$s['id'?> · <strong><?= e($s['url']) ?></strong>
                <span class="text-muted">· добавлена <?= $s['time_add'] ? date('d.m.Y', (int)$s['time_add']) : '—' ?></span>
            </div>
        <?php endforeach; endif; ?>
    </div>
    <?php
    adminFooter
();
    exit;
}

// ================= STATS / CHARTS =================

if ($action === 'stats') {
    
$days = (int)($_GET['days'] ?? 14);
    if (!
in_array($days, [71430], true)) {
        
$days 14;
    }
    
$hosts Stats::dailyHosts(0$days);
    
$hits  Stats::dailyHits(0$days);
    
$ins   Stats::dailyIn(0$days);
    
$labels Stats::shortLabels(array_keys($hosts));
    
$dev Stats::deviceSplit(0);
    
$top Stats::topPlatforms(10);
    
$topIn Stats::topByIn(8);
    
$hourly Stats::hourlyHits(0);
    
$hourLabels array_map(static function ($h) { return sprintf('%02d'$h); }, range(023));
    
$weekday Stats::weekdayHits(028);
    
$weekLabels = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
    
$weekData = [];
    for (
$i 1$i <= 7$i++) {
        
$weekData[] = (int)($weekday[$i] ?? 0);
    }
    
$sum Stats::summary();
    
$growth Stats::hostGrowth(0);
    
$peak Stats::peakHour(0);
    
$hits24 Stats::hitsLastHours(240);
    
$hosts24 Stats::hostsLastHours(240);
    
$newPf Stats::newPlatformsCount(7);
    
$cats Stats::categorySummary();
    
$movers Stats::movers(8);
    
$stale Stats::stalePlatforms(715);

    
adminHeader('Статистика''stats');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    
?>
    <h1 class="page-title">Статистика системы</h1>
    <p class="page-sub">Онлайн <?= (int)$sum['online'?> · пик сегодня <?= sprintf('%02d'$peak['hour']) ?>:00 (<?= (int)$peak['hits'?> хитов)</p>

    <div class="tabs" style="margin-bottom:14px">
        <a class="<?= $days===7?'active':'' ?>" href="/?module=admin&action=stats&days=7">7 дней</a>
        <a class="<?= $days===14?'active':'' ?>" href="/?module=admin&action=stats&days=14">14 дней</a>
        <a class="<?= $days===30?'active':'' ?>" href="/?module=admin&action=stats&days=30">30 дней</a>
    </div>

    <div class="metrics">
        <div class="metric"><div class="m-label">Активные</div><div class="m-val"><?= (int)$sum['active'?></div></div>
        <div class="metric"><div class="m-label">Хосты</div><div class="m-val accent"><?= (int)$sum['hs'?></div></div>
        <div class="metric"><div class="m-label">Хиты</div><div class="m-val"><?= (int)$sum['ht'?></div></div>
        <div class="metric"><div class="m-label">In</div><div class="m-val gold"><?= (int)$sum['in'?></div></div>
        <div class="metric"><div class="m-label">24ч хиты</div><div class="m-val"><?= (int)$hits24 ?></div></div>
        <div class="metric"><div class="m-label">24ч хосты</div><div class="m-val"><?= (int)$hosts24 ?></div></div>
        <div class="metric"><div class="m-label">Рост хостов</div><div class="m-val <?= $growth['pct']>=0?'accent':'' ?>"><?= ($growth['pct']>=0?'+':'') . e((string)$growth['pct']) ?>%</div></div>
        <div class="metric"><div class="m-label">Новые 7д</div><div class="m-val"><?= (int)$newPf ?></div></div>
    </div>

    <div class="card">
        <h2>Хосты · хиты · переходы (In)</h2>
        <div class="chart-box" style="height:260px"><canvas id="chartMain"></canvas></div>
    </div>
    <div class="card">
        <h2>Сегодня по часам</h2>
        <div class="chart-box" style="height:200px"><canvas id="chartHour"></canvas></div>
    </div>
    <div class="card">
        <h2>По дням недели (28 дн.)</h2>
        <div class="chart-box" style="height:180px"><canvas id="chartWeek"></canvas></div>
    </div>

    <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
        <div class="card">
            <h2>Устройства</h2>
            <div class="chart-box" style="height:200px"><canvas id="chartDev"></canvas></div>
        </div>
        <div class="card">
            <h2>Топ по хостам</h2>
            <div class="chart-box" style="height:200px"><canvas id="chartTop"></canvas></div>
        </div>
    </div>
    <div class="card">
        <h2>Топ по переходам (In)</h2>
        <div class="chart-box" style="height:220px"><canvas id="chartIn"></canvas></div>
    </div>
    <div class="card">
        <h2>Категории</h2>
        <div class="chart-box" style="height:200px"><canvas id="chartCat"></canvas></div>
    </div>

    <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
        <div class="card">
            <h2>Рост в топе</h2>
            <?php if (empty($movers['up'])): ?>
                <p class="text-muted">Нет снимков (нужен cron)</p>
            <?php else: foreach ($movers['up'] as $m): ?>
                <div style="padding:6px 0;border-bottom:1px solid var(--border);font-size:.85rem">
                    <strong><?= e($m['url']) ?></strong>
                    <span style="color:var(--accent)">↑<?= (int)$m['delta'?></span>
                    <span class="text-muted">#<?= (int)$m['prev'?>→#<?= (int)$m['rank'?></span>
                </div>
            <?php endforeach; endif; ?>
        </div>
        <div class="card">
            <h2>Падение в топе</h2>
            <?php if (empty($movers['down'])): ?>
                <p class="text-muted">Нет данных</p>
            <?php else: foreach ($movers['down'] as $m): ?>
                <div style="padding:6px 0;border-bottom:1px solid var(--border);font-size:.85rem">
                    <strong><?= e($m['url']) ?></strong>
                    <span style="color:var(--danger)">↓<?= abs((int)$m['delta']) ?></span>
                    <span class="text-muted">#<?= (int)$m['prev'?>→#<?= (int)$m['rank'?></span>
                </div>
            <?php endforeach; endif; ?>
        </div>
    </div>
    <div class="card">
        <h2>Без активности 7+ дней</h2>
        <?php if (!$stale): ?>
            <p class="text-muted">Нет</p>
        <?php else: foreach ($stale as $s): ?>
            <div style="padding:6px 0;border-bottom:1px solid var(--border);font-size:.85rem">
                #<?= (int)$s['id'?> · <strong><?= e($s['url']) ?></strong>
            </div>
        <?php endforeach; endif; ?>
    </div>

    <script>
    const grid='rgba(255,255,255,0.06)';
    Chart.defaults.color='#8b9cb8';
    Chart.defaults.borderColor=grid;
    const common={responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'top',labels:{boxWidth:12}}},scales:{x:{grid:{color:grid}},y:{beginAtZero:true,grid:{color:grid}}}};
    new Chart(document.getElementById('chartMain'),{type:'line',data:{labels:<?= json_encode($labelsJSON_UNESCAPED_UNICODE?>,datasets:[
        {label:'Хосты',data:<?= json_encode(array_values($hosts)) ?>,borderColor:'#2ee6b8',backgroundColor:'rgba(46,230,184,0.12)',fill:true,tension:0.35,pointRadius:2},
        {label:'Хиты',data:<?= json_encode(array_values($hits)) ?>,borderColor:'#6b9fff',backgroundColor:'rgba(107,159,255,0.1)',fill:true,tension:0.35,pointRadius:2},
        {label:'In',data:<?= json_encode(array_values($ins)) ?>,borderColor:'#ffc94a',backgroundColor:'rgba(255,201,74,0.1)',fill:true,tension:0.35,pointRadius:2}
    ]},options:common});
    new Chart(document.getElementById('chartHour'),{type:'bar',data:{labels:<?= json_encode($hourLabels?>,datasets:[{label:'Хиты',data:<?= json_encode(array_values($hourly)) ?>,backgroundColor:'rgba(107,159,255,0.7)',borderRadius:4}]},options:{...common,plugins:{legend:{display:false}}}});
    new Chart(document.getElementById('chartWeek'),{type:'bar',data:{labels:<?= json_encode($weekLabelsJSON_UNESCAPED_UNICODE?>,datasets:[{label:'Хиты',data:<?= json_encode($weekData?>,backgroundColor:'rgba(46,230,184,0.65)',borderRadius:6}]},options:{...common,plugins:{legend:{display:false}}}});
    new Chart(document.getElementById('chartDev'),{type:'doughnut',data:{labels:['Mobile','PC'],datasets:[{data:[<?= (int)$dev['mb'?>,<?= (int)$dev['pc'?>],backgroundColor:['#2ee6b8','#6b9fff'],borderWidth:0}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom'}}}});
    new Chart(document.getElementById('chartTop'),{type:'bar',data:{labels:<?= json_encode(array_map(function($p){return mb_substr($p['url'],0,16);},$top), JSON_UNESCAPED_UNICODE?>,datasets:[{label:'Хосты',data:<?= json_encode(array_map(function($p){return (int)$p['hs'];},$top)) ?>,backgroundColor:'#6b9fff',borderRadius:5}]},options:{...common,indexAxis:'y',plugins:{legend:{display:false}}}});
    new Chart(document.getElementById('chartIn'),{type:'bar',data:{labels:<?= json_encode(array_map(function($p){return mb_substr($p['url'],0,16);},$topIn), JSON_UNESCAPED_UNICODE?>,datasets:[{label:'In',data:<?= json_encode(array_map(function($p){return (int)$p['in_count'];},$topIn)) ?>,backgroundColor:'#ffc94a',borderRadius:5}]},options:{...common,indexAxis:'y',plugins:{legend:{display:false}}}});
    new Chart(document.getElementById('chartCat'),{type:'bar',data:{labels:<?= json_encode(array_map(function($c){return mb_substr($c['name']??'',0,14);},$cats), JSON_UNESCAPED_UNICODE?>,datasets:[{label:'Площадки',data:<?= json_encode(array_map(function($c){return (int)($c['cnt']??$c['c']??0);},$cats)) ?>,backgroundColor:'rgba(107,159,255,0.75)',borderRadius:5}]},options:{...common,plugins:{legend:{display:false}}}});
    </script>
    <?php
    adminFooter
();
    exit;
}

if (
$action === 'platforms') {
    
$filter $_GET['f'] ?? 'all';
    
$sql 'SELECT p.*, c.name AS cat_name, u.login AS owner FROM platforms p
            LEFT JOIN cat c ON c.id = p.cat_id LEFT JOIN user u ON u.id = p.user_id WHERE 1=1'
;
    if (
$filter === 'mod'$sql .= ' AND p.mode = 1';
    elseif (
$filter === 'ban'$sql .= ' AND p.ban = 1';
    elseif (
$filter === 'ok'$sql .= ' AND p.mode = 0 AND p.ban = 0';
    
$sql .= ' ORDER BY p.id DESC LIMIT 200';
    
$list Database::fetchAll($sql);
    
$cats Database::fetchAll('SELECT * FROM cat ORDER BY sort, id');
    
$editId = (int)($_GET['edit'] ?? 0);
    
$edit $editId Database::fetch('SELECT * FROM platforms WHERE id = ?', [$editId]) : null;

    
adminHeader('Площадки''platforms');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    
?>
    <h1 class="page-title">Площадки</h1>
    <nav class="tabs">
        <a class="<?= $filter==='all'?'active':'' ?>" href="/?module=admin&action=platforms">Все</a>
        <a class="<?= $filter==='ok'?'active':'' ?>" href="/?module=admin&action=platforms&f=ok">Активные</a>
        <a class="<?= $filter==='mod'?'active':'' ?>" href="/?module=admin&action=platforms&f=mod">Модерация</a>
        <a class="<?= $filter==='ban'?'active':'' ?>" href="/?module=admin&action=platforms&f=ban">Бан</a>
    </nav>

    <?php if ($edit): ?>
    <div class="card">
        <h2>Редактировать #<?= (int)$edit['id'?></h2>
        <form method="post"><?= csrf_field() ?>
            <input type="hidden" name="do" value="edit_pf">
            <input type="hidden" name="id" value="<?= (int)$edit['id'?>">
            <label>URL</label>
            <input type="text" name="url" value="<?= e($edit['url']) ?>" required>
            <label>Описание</label>
            <input type="text" name="message" value="<?= e($edit['message']) ?>">
            <label>Категория</label>
            <select name="cat_id">
                <?php foreach ($cats as $c): ?>
                    <option value="<?= (int)$c['id'?><?= (int)$c['id']===(int)$edit['cat_id']?'selected':'' ?>><?= e($c['name']) ?></option>
                <?php endforeach; ?>
            </select>
            <button class="btn btn-primary" type="submit">Сохранить</button>
            <a class="btn btn-ghost" href="/?module=admin&action=platforms">Отмена</a>
        </form>
    </div>
    <?php endif; ?>

    <?php if (!$list): ?>
        <div class="empty"><div>Список пуст</div></div>
    <?php else: foreach ($list as $p): ?>
        <article class="rank-card" style="grid-template-columns:1fr; gap:8px">
            <div style="display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;align-items:center">
                <div>
                    <strong>#<?= (int)$p['id'?> · <?= e($p['url']) ?></strong>
                    <?php if ((int)$p['ban']): ?><span class="badge badge-ban">BAN</span><?php endif; ?>
                    <?php if ((int)$p['mode']): ?><span class="badge badge-mod">MOD</span><?php endif; ?>
                    <?php if ((int)$p['gold'] > time()): ?><span class="badge-gold">★ Gold</span><?php endif; ?>
                    <div class="text-muted" style="font-size:.8rem;margin-top:4px">
                        <?= e($p['cat_name'] ?? '—'?> · <?= e($p['owner'] ?? '?'?> · hs <?= (int)$p['hs'?> / ht <?= (int)$p['ht'?>
                    </div>
                </div>
            </div>
            <div class="act-btns">
                <a href="/?module=admin&action=platforms&edit=<?= (int)$p['id'?>">Изменить</a>
                <a href="/?module=stats&id=<?= (int)$p['id'?>">Статистика</a>
                <?php if ((int)$p['ban']): ?>
                    <form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="do" value="unban_pf"><input type="hidden" name="id" value="<?= (int)$p['id'?>"><button class="ok" type="submit">Разбан</button></form>
                <?php else: ?>
                    <form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="do" value="ban_pf"><input type="hidden" name="id" value="<?= (int)$p['id'?>"><button class="danger" type="submit">Бан</button></form>
                <?php endif; ?>
                <?php if ((int)$p['mode']): ?>
                    <form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="do" value="approve_pf"><input type="hidden" name="id" value="<?= (int)$p['id'?>"><button class="ok" type="submit">Одобрить</button></form>
                <?php else: ?>
                    <form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="do" value="mod_pf"><input type="hidden" name="id" value="<?= (int)$p['id'?>"><button type="submit">На мод.</button></form>
                <?php endif; ?>
                <form method="post" style="display:inline" onsubmit="return confirm('Удалить?')"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="del_pf"><input type="hidden" name="id" value="<?= (int)$p['id'?>">
                    <button class="danger" type="submit">Удалить</button>
                </form>
                <form method="post" style="display:inline;display:flex;gap:4px;align-items:center"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="gold_pf"><input type="hidden" name="id" value="<?= (int)$p['id'?>">
                    <input type="number" name="days" value="7" min="1" max="365" style="width:56px;margin:0;padding:4px 6px;font-size:.75rem">
                    <button type="submit">GOLD</button>
                </form>
            </div>
        </article>
    <?php endforeach; endif;
    
adminFooter();
    exit;
}

// ================= USERS =================
if ($action === 'users') {
    
$users Database::fetchAll('SELECT * FROM user ORDER BY id DESC LIMIT 200');
    
adminHeader('Пользователи''users');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    
?>
    <h1 class="page-title">Пользователи</h1>
    <p class="page-sub"><?= count($users?> записей</p>
    <?php foreach ($users as $u): ?>
        <article class="rank-card" style="grid-template-columns:1fr;gap:8px">
            <div>
                <strong>#<?= (int)$u['id'?> · <?= e($u['login']) ?></strong>
                <?php if ((int)$u['ban']): ?><span class="badge badge-ban">BAN</span><?php endif; ?>
                <?php if ((int)$u['level'] >= 9): ?><span class="badge badge-ok">ADMIN</span><?php endif; ?>
                <div class="text-muted" style="font-size:.8rem;margin-top:4px">
                    <?= e($u['email'] ?: 'без email'?> · level <?= (int)$u['level'?>
                    · рег. <?= $u['reg_time'] ? date('d.m.Y', (int)$u['reg_time']) : '—' ?>
                </div>
            </div>
            <div class="act-btns">
                <?php if ((int)$u['id'] !== (int)$_SESSION['user_id']): ?>
                    <?php if ((int)$u['ban']): ?>
                        <form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="do" value="unban_user"><input type="hidden" name="id" value="<?= (int)$u['id'?>"><button class="ok" type="submit">Разбан</button></form>
                    <?php else: ?>
                        <form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="do" value="ban_user"><input type="hidden" name="id" value="<?= (int)$u['id'?>"><button class="danger" type="submit">Бан</button></form>
                    <?php endif; ?>
                    <form method="post" style="display:inline;display:flex;gap:4px;align-items:center"><?= csrf_field() ?>
                        <input type="hidden" name="do" value="set_level"><input type="hidden" name="id" value="<?= (int)$u['id'?>">
                        <select name="level" style="width:auto;margin:0;padding:4px 8px;font-size:.75rem">
                            <option value="1" <?= (int)$u['level']===1?'selected':'' ?>>User</option>
                            <option value="9" <?= (int)$u['level']>=9?'selected':'' ?>>Admin</option>
                        </select>
                        <button type="submit">OK</button>
                    </form>
                <?php else: ?>
                    <span class="text-muted" style="font-size:.8rem">это вы</span>
                <?php endif; ?>
            </div>
        </article>
    <?php endforeach;
    
adminFooter();
    exit;
}

// ================= CATEGORIES =================
if ($action === 'cats') {
    
$cats Database::fetchAll('SELECT c.*, (SELECT COUNT(*) FROM platforms p WHERE p.cat_id = c.id) AS cnt FROM cat c ORDER BY sort, id');
    
adminHeader('Категории''cats');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    
?>
    <h1 class="page-title">Категории</h1>
    <div class="card">
        <h2>Добавить</h2>
        <form method="post" class="form-row form-row-2">
            <input type="hidden" name="do" value="add_cat">
            <div>
                <label>Название</label>
                <input type="text" name="name" required maxlength="80">
            </div>
            <div>
                <label>Сортировка</label>
                <input type="number" name="sort" value="0">
            </div>
            <button class="btn btn-primary" type="submit">Добавить</button>
        </form>
    </div>
    <?php foreach ($cats as $c): ?>
        <article class="rank-card" style="grid-template-columns:1fr auto;gap:10px">
            <div>
                <strong><?= e($c['name']) ?></strong>
                <div class="text-muted" style="font-size:.8rem">id <?= (int)$c['id'?> · sort <?= (int)$c['sort'?> · площадок <?= (int)$c['cnt'?></div>
            </div>
            <form method="post" onsubmit="return confirm('Удалить категорию?')"><?= csrf_field() ?>
                <input type="hidden" name="do" value="del_cat">
                <input type="hidden" name="id" value="<?= (int)$c['id'?>">
                <button class="btn btn-sm btn-ghost" type="submit" style="color:var(--danger)">Удалить</button>
            </form>
        </article>
    <?php endforeach;
    
adminFooter();
    exit;
}

// ================= CONFIG =================
if ($action === 'config') {
    
$cfg Database::fetch('SELECT * FROM config WHERE id = 1') ?: [];
    
adminHeader('Настройки''config');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    
?>
    <h1 class="page-title">Настройки сайта</h1>
    <div class="card">
        <form method="post"><?= csrf_field() ?>
            <input type="hidden" name="do" value="save_config">
            <label>Название</label>
            <input type="text" name="title" value="<?= e($cfg['title'] ?? ''?>" required>
            <label>Keywords</label>
            <input type="text" name="keywords" value="<?= e($cfg['keywords'] ?? ''?>">
            <label>Description</label>
            <input type="text" name="description" value="<?= e($cfg['description'] ?? ''?>">
            <label>Макс. площадок в топе</label>
            <input type="number" name="pf_max" value="<?= (int)($cfg['pf_max'] ?? 100?>" min="1">
            <label style="display:flex;align-items:center;gap:8px;color:var(--text)">
                <input type="checkbox" name="pf_mod" value="1" <?= !empty($cfg['pf_mod'])?'checked':'' ?> style="width:auto;margin:0">
                Модерация новых площадок
            </label>
            <label style="display:flex;align-items:center;gap:8px;color:var(--text);margin-top:10px">
                <input type="checkbox" name="reg" value="1" <?= !empty($cfg['reg'])?'checked':'' ?> style="width:auto;margin:0">
                Регистрация открыта
            </label>
            <label style="display:flex;align-items:center;gap:8px;color:var(--text);margin-top:10px;margin-bottom:16px">
                <input type="checkbox" name="reg_mod" value="1" <?= !empty($cfg['reg_mod'])?'checked':'' ?> style="width:auto;margin:0">
                Модерация регистрации
            </label>
            <button class="btn btn-primary btn-block" type="submit">Сохранить</button>
        </form>
    </div>
    <div class="card">
        <h2>Cron (суточный сброс)</h2>
        <p class="text-muted" style="font-size:.88rem;line-height:1.6">
            Вызывайте раз в сутки (00:05):

            <code class="mono">/?module=cron&amp;key=КЛЮЧ</code><br>
            Ключ: <code>cron_key</code> в <code>core/config/database.php</code>
        </p>
    </div>
    <?php
    adminFooter
();
    exit;
}

if (
$action === 'anticheat') {
    
$suspect AntiCheat::suspiciousPlatforms(15);
    
$topIps AntiCheat::topIpsToday(25);
    
$blocks AntiCheat::recentBlocks(40);
    
$bans = [];
    try {
        
$bans Database::fetchAll('SELECT * FROM ip_ban ORDER BY time_add DESC LIMIT 100');
    } catch (
Throwable $e) {
        
$err 'Выполните sql/migrate_anticheat.sql';
    }
    
adminHeader('Антинакрутка''anticheat');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (!empty(
$err)) echo '<div class="alert alert-error">' e($err) . '</div>';
    
?>
    <h1 class="page-title">Антинакрутка</h1>
    <p class="page-sub">Лимиты: <?= (int)AntiCheat::HIT_INTERVAL ?>с между хитами · max <?= (int)AntiCheat::MAX_HITS_IP_PF_DAY ?>/день на IP+площадку · <?= (int)AntiCheat::MAX_HITS_IP_HOUR ?>/час на IP</p>

    <div class="card">
        <h2>Забанить IP</h2>
        <form method="post" class="form-row form-row-2">
            <input type="hidden" name="do" value="ban_ip">
            <div>
                <label>IP</label>
                <input type="text" name="ip" required placeholder="1.2.3.4">
            </div>
            <div>
                <label>Часов (0 = навсегда)</label>
                <input type="number" name="hours" value="24" min="0">
            </div>
            <div style="grid-column:1/-1">
                <label>Причина</label>
                <input type="text" name="reason" maxlength="200">
            </div>
            <button class="btn btn-primary" type="submit">Забанить</button>
        </form>
    </div>

    <div class="card">
        <h2>Подозрительные площадки (ht/hs ≥ <?= (int)AntiCheat::SUSPECT_HT_HS_RATIO ?>)</h2>
        <?php if (!$suspect): ?>
            <p class="text-muted">Нет аномалий</p>
        <?php else: foreach ($suspect as $s): ?>
            <div style="padding:8px 0;border-bottom:1px solid var(--border);font-size:.88rem;display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap">
                <div>
                    #<?= (int)$s['id'?> · <strong><?= e($s['url']) ?></strong>
                    <span class="text-muted">hs <?= (int)$s['hs'?> / ht <?= (int)$s['ht'?></span>
                </div>
                <span class="badge badge-ban">ratio <?= e((string)$s['ratio']) ?></span>
            </div>
        <?php endforeach; endif; ?>
    </div>

    <div class="card">
        <h2>Топ IP сегодня</h2>
        <div class="table-wrap"><table class="data">
            <thead><tr><th>IP</th><th>Хиты</th><th>Площадок</th><th></th></tr></thead>
            <tbody>
            <?php foreach ($topIps as $row): ?>
                <tr>
                    <td class="mono"><?= e($row['ip']) ?></td>
                    <td><?= (int)$row['hits'?></td>
                    <td><?= (int)$row['platforms'?></td>
                    <td>
                        <form method="post" style="display:inline"><?= csrf_field() ?>
                            <input type="hidden" name="do" value="ban_ip">
                            <input type="hidden" name="ip" value="<?= e($row['ip']) ?>">
                            <input type="hidden" name="hours" value="24">
                            <input type="hidden" name="reason" value="auto from top IPs">
                            <button type="submit" class="btn btn-sm btn-ghost" style="color:var(--danger)">Бан 24ч</button>
                        </form>
                    </td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table></div>
    </div>

    <div class="card">
        <h2>Активные баны IP</h2>
        <?php if (!$bans): ?>
            <p class="text-muted">Список пуст</p>
        <?php else: foreach ($bans as $b): ?>
            <div style="padding:8px 0;border-bottom:1px solid var(--border);font-size:.88rem;display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap;align-items:center">
                <div>
                    <strong class="mono"><?= e($b['ip']) ?></strong>
                    · <?= e($b['reason'] ?: '—'?>
                    · до <?= (int)$b['until_time'] === '∞' date('d.m.Y H:i', (int)$b['until_time']) ?>
                </div>
                <form method="post"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="unban_ip">
                    <input type="hidden" name="ip" value="<?= e($b['ip']) ?>">
                    <button type="submit" class="btn btn-sm btn-ghost">Разбан</button>
                </form>
            </div>
        <?php endforeach; endif; ?>
    </div>

    <div class="card">
        <h2>Последние блокировки</h2>
        <?php if (!$blocks): ?>
            <p class="text-muted">Пока нет записей (или нет таблицы cheat_log)</p>
        <?php else: foreach ($blocks as $b): ?>
            <div style="padding:6px 0;border-bottom:1px solid var(--border);font-size:.8rem;color:var(--text-muted)">
                <?= date('d.m H:i:s', (int)$b['time']) ?>
                · <span class="mono"><?= e($b['ip']) ?></span>
                · pf #<?= (int)$b['pf'?>
                · <?= e($b['kind']) ?>
                · <strong style="color:var(--danger)"><?= e(AntiCheat::reasonLabel($b['reason'])) ?></strong>
            </div>
        <?php endforeach; endif; ?>
    </div>
    <?php
    adminFooter
();
    exit;
}

if (
$action === 'points') {
    
$err '';
    try {
        
$cfg = [
            
'enabled' => Points::cfg('enabled''1'),
            
'in_reward' => Points::cfg('in_reward''1'),
            
'gold_day_cost' => Points::cfg('gold_day_cost''50'),
            
'ad_point_per_currency' => Points::cfg('ad_point_per_currency''1'),
        ];
        
$topUsers Database::fetchAll('SELECT id, login, points, level FROM user ORDER BY points DESC LIMIT 30');
    } catch (
Throwable $e) {
        
$err 'Выполните sql/migrate_points.sql';
        
$cfg = ['enabled'=>'1','in_reward'=>'1','gold_day_cost'=>'50','ad_point_per_currency'=>'1'];
        
$topUsers = [];
    }
    
adminHeader('Баллы''points');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (
$err) echo '<div class="alert alert-error">' e($err) . '</div>';
    
?>
    <h1 class="page-title">Баллы</h1>
    <p class="page-sub">Начисление за переходы In, трата на GOLD и рекламу</p>
    <div class="card">
        <h2>Настройки</h2>
        <form method="post">
            <?= csrf_field() ?>
            <input type="hidden" name="do" value="save_points_cfg">
            <label style="display:flex;gap:8px;align-items:center;color:var(--text)">
                <input type="checkbox" name="enabled" value="1" <?= $cfg['enabled']==='1'?'checked':'' ?> style="width:auto;margin:0"> Система баллов включена
            </label>
            <label>Баллов за уникальный In</label>
            <input type="number" name="in_reward" value="<?= e($cfg['in_reward']) ?>" min="0" max="100">
            <label>Стоимость GOLD (баллов / день)</label>
            <input type="number" name="gold_day_cost" value="<?= e($cfg['gold_day_cost']) ?>" min="1">
            <label>Баллов за 1 ₽ цены рекламы</label>
            <input type="number" name="ad_point_per_currency" value="<?= e($cfg['ad_point_per_currency']) ?>" min="1">
            <button class="btn btn-primary" type="submit">Сохранить</button>
        </form>
    </div>
    <div class="card">
        <h2>Начислить / списать</h2>
        <form method="post">
            <?= csrf_field() ?>
            <input type="hidden" name="do" value="adjust_points">
            <label>ID пользователя</label>
            <input type="number" name="user_id" required min="1">
            <label>Изменение (+/−)</label>
            <input type="number" name="delta" required>
            <label>Комментарий</label>
            <input type="text" name="note" maxlength="40" value="admin">
            <button class="btn btn-primary" type="submit">Применить</button>
        </form>
    </div>
    <h2 class="page-title" style="font-size:1.05rem">Топ по баллам</h2>
    <div class="table-wrap"><table class="data">
        <thead><tr><th>ID</th><th>Логин</th><th>Баллы</th><th>Ур.</th></tr></thead>
        <tbody>
        <?php foreach ($topUsers as $u): ?>
            <tr>
                <td><?= (int)$u['id'?></td>
                <td><?= e($u['login']) ?></td>
                <td><strong><?= (int)$u['points'?></strong></td>
                <td><?= (int)$u['level'?></td>
            </tr>
        <?php endforeach; ?>
        </tbody>
    </table></div>
    <?php
    adminFooter
();
    exit;
}

if (
$action === 'ads') {
    
$list = [];
    
$prices = [];
    try {
        
$list Database::fetchAll('SELECT a.*, u.login FROM ads a LEFT JOIN user u ON u.id = a.user_id ORDER BY a.id DESC LIMIT 100');
        
$prices Database::fetchAll('SELECT * FROM ad_prices ORDER BY sort, position');
    } catch (
Throwable $e) {
        
$err 'Выполните sql/migrate_ads.sql';
    }
    
$positions Ads::positions();
    
adminHeader('Реклама''ads');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (!empty(
$err)) echo '<div class="alert alert-error">' e($err) . '</div>';
    
?>
    <h1 class="page-title">Реклама</h1>
    <p class="page-sub">Места размещения и заявки пользователей</p>

    <div class="card">
        <h2>Добавить рекламу (админ)</h2>
        <form method="post">
            <?= csrf_field() ?>
            <input type="hidden" name="do" value="add_ad">
            <label>Место</label>
            <select name="position">
                <?php foreach ($positions as $k => $lab): ?>
                    <option value="<?= e($k?>"><?= e($lab?></option>
                <?php endforeach; ?>
            </select>
            <label>URL (без http://)</label>
            <input type="text" name="url" required maxlength="60" placeholder="example.com">
            <label>Название</label>
            <input type="text" name="name" required maxlength="64">
            <label>Текст</label>
            <input type="text" name="text" maxlength="200">
            <label>Дней показа</label>
            <input type="number" name="days" value="7" min="1" max="365">
            <label>Цвет (CSS, опционально)</label>
            <input type="text" name="color" placeholder="#00d4aa">
            <label style="display:flex;gap:8px;align-items:center;color:var(--text)">
                <input type="checkbox" name="bold" value="1" style="width:auto;margin:0"> Жирный
            </label>
            <button class="btn btn-primary" type="submit" style="margin-top:12px">Опубликовать</button>
        </form>
    </div>

    <div class="card">
        <h2>Цены (₽ / день)</h2>
        <form method="post">
            <?= csrf_field() ?>
            <input type="hidden" name="do" value="save_ad_prices">
            <?php
            $priceMap 
= [];
            foreach (
$prices as $pr) { $priceMap[$pr['position']] = $pr; }
            foreach (
$positions as $k => $lab):
                
$pr $priceMap[$k] ?? ['price_day' => 0'enabled' => 1];
            
?>
            <div style="display:flex;gap:10px;align-items:center;margin-bottom:10px;flex-wrap:wrap">
                <label style="flex:1;min-width:140px;margin:0;color:var(--text)"><?= e($lab?></label>
                <input type="number" step="0.01" name="price[<?= e($k?>]" value="<?= e((string)$pr['price_day']) ?>" style="width:110px;margin:0">
                <label style="display:flex;gap:6px;align-items:center;margin:0;color:var(--text);font-size:.85rem">
                    <input type="checkbox" name="enabled[<?= e($k?>]" value="1" <?= !empty($pr['enabled'])?'checked':'' ?> style="width:auto;margin:0"> вкл.
                </label>
            </div>
            <?php endforeach; ?>
            <button class="btn btn-primary" type="submit">Сохранить цены</button>
        </form>
    </div>

    <h2 class="page-title" style="font-size:1.1rem;margin-top:8px">Объявления</h2>
    <?php if (!$list): ?>
        <div class="empty"><div>Пока нет</div></div>
    <?php else: foreach ($list as $ad): ?>
        <article class="rank-card" style="grid-template-columns:1fr;gap:8px">
            <div>
                <strong><?= e($ad['name']) ?></strong>
                <span class="badge <?= (int)$ad['status']===1?'badge-ok':((int)$ad['status']===0?'badge-mod':'badge-ban'?>">
                    <?= e(Ads::statusLabel((int)$ad['status'])) ?>
                </span>
                <div class="text-muted" style="font-size:.8rem;margin-top:4px">
                    <?= e($positions[$ad['position']] ?? $ad['position']) ?>
                    · <?= e($ad['url']) ?>
                    · <?= e($ad['login'] ?: 'админ'?>
                    · <?= (int)$ad['days'?> дн.
                    · до <?= (int)$ad['time_end'] ? date('d.m.Y', (int)$ad['time_end']) : '—' ?>
                    · <?= number_format((float)$ad['price'], 2'.'' '?> ₽
                </div>
                <?php if ($ad['text']): ?><div style="font-size:.88rem;margin-top:4px"><?= e($ad['text']) ?></div><?php endif; ?>
            </div>
            <div class="act-btns">
                <?php if ((int)$ad['status'] !== 1): ?>
                <form method="post" style="display:inline"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="ad_status"><input type="hidden" name="id" value="<?= (int)$ad['id'?>">
                    <input type="hidden" name="status" value="1"><button class="ok" type="submit">Активировать</button>
                </form>
                <?php endif; ?>
                <?php if ((int)$ad['status'] === 0): ?>
                <form method="post" style="display:inline"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="ad_status"><input type="hidden" name="id" value="<?= (int)$ad['id'?>">
                    <input type="hidden" name="status" value="2"><button class="danger" type="submit">Отклонить</button>
                </form>
                <?php endif; ?>
                <form method="post" style="display:inline" onsubmit="return confirm('Удалить?')"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="del_ad"><input type="hidden" name="id" value="<?= (int)$ad['id'?>">
                    <button class="danger" type="submit">Удалить</button>
                </form>
            </div>
        </article>
    <?php endforeach; endif;
    
adminFooter();
    exit;
}

if (
$action === 'news') {
    
$list = [];
    try {
        
$list Database::fetchAll('SELECT * FROM news ORDER BY time_add DESC LIMIT 50');
    } catch (
Throwable $e) {
        
$err 'Таблица news не найдена. Выполните sql/migrate_news.sql';
    }
    
adminHeader('Новости''news');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (!empty(
$err)) echo '<div class="alert alert-error">' e($err) . '</div>';
    echo 
'<h1 class="page-title">Новости</h1>';
    echo 
'<div class="card"><h2>Добавить</h2><form method="post">' csrf_field();
    echo 
'<input type="hidden" name="do" value="add_news">';
    echo 
'<label>Заголовок</label><input type="text" name="title" required maxlength="150">';
    echo 
'<label>Текст</label><textarea name="text" required rows="4"></textarea>';
    echo 
'<button class="btn btn-primary" type="submit">Опубликовать</button></form></div>';
    foreach (
$list as $n) {
        echo 
'<article class="rank-card" style="grid-template-columns:1fr auto;gap:10px">';
        echo 
'<div><strong>' e($n['title']) . '</strong>';
        echo 
'<div class="text-muted" style="font-size:.8rem">' date('d.m.Y H:i', (int)$n['time_add']) . '</div>';
        echo 
'<div style="font-size:.88rem;margin-top:6px;color:var(--text-muted)">' e(mb_substr($n['text'], 0200)) . '</div></div>';
        echo 
'<form method="post" onsubmit="return confirm('Удалить?')">' csrf_field() . '<input type="hidden" name="do" value="del_news">';
        echo 
'<input type="hidden" name="id" value="' . (int)$n['id'] . '">';
        echo 
'<button class="btn btn-sm btn-ghost" type="submit" style="color:var(--danger)">Удалить</button></form></article>';
    }
    
adminFooter();
    exit;
}

redirect('/?module=admin&action=dashboard');
Онлайн: 2
Реклама