| Current Path : /home/seto/888mirror.pm/admin/ |
| Current File : /home/seto/888mirror.pm/admin/redirects.php |
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/helpers.php';
auth_check();
$db = db();
$msg = '';
// Сохранение глобального режима
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['global_mode'])) {
$gm = in_array($_POST['global_mode'], ['random','top']) ? $_POST['global_mode'] : 'random';
setting_set('redirect_mode_default', $gm);
$msg = 'Настройки сохранены.';
}
// Сохранение режима для конкретной страны
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['country_code'], $_POST['mode'])) {
$cc = preg_replace('/[^a-z]/', '', strtolower($_POST['country_code']));
$m = in_array($_POST['mode'], ['random','top','default']) ? $_POST['mode'] : 'default';
if ($cc) {
if ($m === 'default') {
$db->prepare("DELETE FROM redirect_settings WHERE country_code = ?")->execute([$cc]);
} else {
$db->prepare("INSERT OR REPLACE INTO redirect_settings (country_code, mode) VALUES (?,?)")->execute([$cc, $m]);
}
}
$msg = 'Настройки сохранены.';
}
$global_mode = setting('redirect_mode_default') ?: 'random';
// Сортировка
$sort_col = in_array($_GET['sort'] ?? '', ['country','brands','clicks']) ? $_GET['sort'] : 'clicks';
$sort_dir = ($_GET['dir'] ?? 'desc') === 'asc' ? 'asc' : 'desc';
$order = match($sort_col) {
'country' => 'bc.country_code ' . $sort_dir,
'brands' => 'brand_count ' . $sort_dir . ', bc.country_code ASC',
default => 'clicks_30d ' . $sort_dir . ', bc.country_code ASC',
};
// Страны с брендами + статистика за 30 дней
$since = date('Y-m-d H:i:s', strtotime('-30 days'));
$countries = $db->query("
SELECT
bc.country_code,
COUNT(DISTINCT bc.brand_id) as brand_count,
COALESCE(rs.mode, 'default') as mode,
COALESCE(cs.clicks_30d, 0) as clicks_30d
FROM brand_countries bc
LEFT JOIN redirect_settings rs ON rs.country_code = bc.country_code
LEFT JOIN (
SELECT country, COUNT(*) as clicks_30d
FROM redirect_clicks
WHERE created_at >= '$since'
GROUP BY country
) cs ON cs.country = bc.country_code
WHERE bc.country_code NOT IN ('en','crypto')
GROUP BY bc.country_code
ORDER BY $order
")->fetchAll();
// Global и Crypto отдельно
$global_brands = (int)$db->query("SELECT COUNT(DISTINCT brand_id) FROM brand_countries WHERE country_code='en'")->fetchColumn();
$crypto_brands = (int)$db->query("SELECT COUNT(DISTINCT brand_id) FROM brand_countries WHERE country_code='crypto'")->fetchColumn();
// Топ источники за 30 дней
$top_sources = $db->prepare("
SELECT source, COUNT(*) as cnt
FROM redirect_clicks
WHERE created_at >= ? AND source != ''
GROUP BY source ORDER BY cnt DESC LIMIT 10
");
$top_sources->execute([$since]);
$top_sources = $top_sources->fetchAll();
// Топ бренды за 30 дней
$top_brands = $db->prepare("
SELECT b.name, COUNT(*) as cnt
FROM redirect_clicks rc JOIN brands b ON b.id = rc.brand_id
WHERE rc.created_at >= ?
GROUP BY rc.brand_id ORDER BY cnt DESC LIMIT 10
");
$top_brands->execute([$since]);
$top_brands = $top_brands->fetchAll();
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Redirects — Admin</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<?= admin_nav('redirects') ?>
<div class="container-fluid" style="max-width:1200px">
<?php if ($msg): ?>
<div class="alert alert-success alert-dismissible fade show"><i class="bi bi-check-circle"></i> <?= h($msg) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>
<?php endif; ?>
<div class="d-flex align-items-center justify-content-between mb-3">
<h4 class="mb-0"><i class="bi bi-signpost-split"></i> Redirects</h4>
<div class="text-muted small">
Endpoint: <code><?= h((isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? '888mirror.pm') . '/redirect.php') ?></code>
</div>
</div>
<div class="row g-3 mb-4">
<!-- Глобальный режим -->
<div class="col-md-4">
<div class="card h-100">
<div class="card-header fw-semibold"><i class="bi bi-globe"></i> Глобальный режим по умолчанию</div>
<div class="card-body">
<form method="post" class="d-flex align-items-center gap-2">
<select name="global_mode" class="form-select form-select-sm">
<option value="random" <?= $global_mode === 'random' ? 'selected' : '' ?>>🎲 Random (случайный с учётом веса)</option>
<option value="top" <?= $global_mode === 'top' ? 'selected' : '' ?>>🏆 Top (наивысший вес)</option>
</select>
<button class="btn btn-sm btn-primary">Сохранить</button>
</form>
<div class="text-muted small mt-2">Применяется если для страны не задан свой режим.</div>
</div>
</div>
</div>
<!-- Статистика -->
<div class="col-md-4">
<div class="card h-100">
<div class="card-header fw-semibold"><i class="bi bi-bar-chart"></i> Топ брендов (30 дней)</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<?php foreach ($top_brands as $tb): ?>
<tr><td><?= h($tb['name']) ?></td><td class="text-end fw-semibold"><?= $tb['cnt'] ?></td></tr>
<?php endforeach; ?>
<?php if (empty($top_brands)): ?><tr><td class="text-muted">Нет данных</td></tr><?php endif; ?>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-header fw-semibold"><i class="bi bi-hdd-network"></i> Топ источников (30 дней)</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<?php foreach ($top_sources as $ts): ?>
<tr><td class="font-monospace small"><?= h($ts['source']) ?></td><td class="text-end fw-semibold"><?= $ts['cnt'] ?></td></tr>
<?php endforeach; ?>
<?php if (empty($top_sources)): ?><tr><td class="text-muted">Нет данных</td></tr><?php endif; ?>
</table>
</div>
</div>
</div>
</div>
<!-- Таблица стран -->
<div class="card">
<div class="card-header fw-semibold d-flex justify-content-between align-items-center">
<span><i class="bi bi-flag"></i> Настройки по GEO</span>
<span class="text-muted small">Глобальный фолбэк: <?= $global_brands ?> брендов (en) · <?= $crypto_brands ?> crypto</span>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<?php
function sort_link(string $label, string $col, string $cur_col, string $cur_dir): string {
$next = ($cur_col === $col && $cur_dir === 'desc') ? 'asc' : 'desc';
$icon = $cur_col === $col ? ($cur_dir === 'asc' ? ' ↑' : ' ↓') : ' ↕';
return '<a href="?sort=' . $col . '&dir=' . $next . '" class="text-decoration-none text-body">' . $label . '<span class="text-muted">' . $icon . '</span></a>';
}
?>
<thead class="table-light">
<tr>
<th><?= sort_link('Страна', 'country', $sort_col, $sort_dir) ?></th>
<th class="text-center"><?= sort_link('Брендов', 'brands', $sort_col, $sort_dir) ?></th>
<th class="text-center"><?= sort_link('Кликов (30д)', 'clicks', $sort_col, $sort_dir) ?></th>
<th>Режим</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($countries as $row): ?>
<?php
$cc = $row['country_code'];
$cname = country_name($cc);
$mode_val = $row['mode'];
?>
<tr>
<td>
<img src="https://flagicons.lipis.dev/flags/4x3/<?= h($cc) ?>.svg"
width="20" class="me-1 rounded-1">
<?= h($cname) ?>
<span class="text-muted small">(<?= h(strtoupper($cc)) ?>)</span>
</td>
<td class="text-center"><?= $row['brand_count'] ?></td>
<td class="text-center"><?= $row['clicks_30d'] ?></td>
<td>
<form method="post" class="d-flex align-items-center gap-1">
<input type="hidden" name="country_code" value="<?= h($cc) ?>">
<select name="mode" class="form-select form-select-sm" style="width:auto">
<option value="default" <?= $mode_val === 'default' ? 'selected' : '' ?>>↩ Глобальный (<?= $global_mode ?>)</option>
<option value="random" <?= $mode_val === 'random' ? 'selected' : '' ?>>🎲 Random</option>
<option value="top" <?= $mode_val === 'top' ? 'selected' : '' ?>>🏆 Top</option>
</select>
<button class="btn btn-sm btn-outline-primary">Сохранить</button>
</form>
</td>
<td>
<?php if ($row['clicks_30d'] > 0): ?>
<a href="/admin/stats.php?tab=redirects&country=<?= h($cc) ?>" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-graph-up"></i>
</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>