Publish Void Bastion website
This commit is contained in:
commit
8474894756
15 files changed
+570
No files matched your search
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
header('X-Robots-Tag: noindex, nofollow, noarchive', true);
|
||||
header('Cache-Control: no-store, private', true);
|
||||
header('Referrer-Policy: no-referrer', true);
|
||||
header('X-Content-Type-Options: nosniff', true);
|
||||
|
||||
session_name('void_bastion_internal_stats');
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/intern/statistik/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
]);
|
||||
session_start();
|
||||
|
||||
$configPath = __DIR__ . '/stats-config.local.php';
|
||||
if (!is_file($configPath)) {
|
||||
http_response_code(503);
|
||||
exit('Die interne Statistik ist noch nicht konfiguriert.');
|
||||
}
|
||||
$config = require $configPath;
|
||||
|
||||
if (isset($_GET['logout'])) {
|
||||
$_SESSION = [];
|
||||
session_destroy();
|
||||
header('Location: ./');
|
||||
exit;
|
||||
}
|
||||
|
||||
$loginError = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username'], $_POST['password'])) {
|
||||
$userOk = hash_equals((string) $config['username'], (string) $_POST['username']);
|
||||
$passwordOk = hash_equals((string) $config['password_sha256'], hash('sha256', (string) $_POST['password']));
|
||||
if ($userOk && $passwordOk) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['authenticated'] = true;
|
||||
header('Location: ./');
|
||||
exit;
|
||||
}
|
||||
usleep(350000);
|
||||
$loginError = 'Anmeldung fehlgeschlagen.';
|
||||
}
|
||||
|
||||
function fetchJson(string $url, string $method = 'GET', ?string $body = null): ?array
|
||||
{
|
||||
if (function_exists('curl_init')) {
|
||||
$handle = curl_init($url);
|
||||
$options = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 4,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_USERAGENT => 'GerGames-Internal-Stats/1.0',
|
||||
CURLOPT_HTTPHEADER => ['Accept: application/json', 'Content-Type: application/json'],
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
];
|
||||
if ($body !== null) {
|
||||
$options[CURLOPT_POSTFIELDS] = $body;
|
||||
}
|
||||
curl_setopt_array($handle, $options);
|
||||
$response = curl_exec($handle);
|
||||
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
|
||||
curl_close($handle);
|
||||
if (!is_string($response) || $status < 200 || $status >= 300) return null;
|
||||
} else {
|
||||
$context = stream_context_create(['http' => [
|
||||
'method' => $method,
|
||||
'timeout' => 8,
|
||||
'ignore_errors' => true,
|
||||
'header' => "Accept: application/json\r\nContent-Type: application/json\r\nUser-Agent: GerGames-Internal-Stats/1.0\r\n",
|
||||
'content' => $body ?? '',
|
||||
]]);
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
if (!is_string($response)) return null;
|
||||
}
|
||||
$decoded = json_decode($response, true);
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
|
||||
function e(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
if (empty($_SESSION['authenticated'])):
|
||||
?>
|
||||
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow,noarchive"><title>GerGames – Interne Statistik</title><style>
|
||||
:root{color-scheme:dark;font-family:Inter,system-ui,sans-serif;background:#06121e;color:#eaf8fc}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;background:radial-gradient(circle at 50% 0,#12394d 0,transparent 48%),#06121e}.box{width:min(100%,420px);padding:32px;border:1px solid #2d7182;background:#091d2a;box-shadow:0 24px 80px #0008}h1{margin:0 0 8px;font-size:1.7rem}p{color:#9db6c1}label{display:block;margin:18px 0 6px;color:#bcd5df}input,button{width:100%;min-height:48px;border:1px solid #316f80;background:#071722;color:#fff;padding:10px 12px;font:inherit}button{margin-top:22px;background:#5cecff;color:#031119;font-weight:800;cursor:pointer}.error{color:#ffac99}
|
||||
</style></head><body><main class="box"><h1>Interne Statistik</h1><p>Geschützter GerGames-Bereich</p><?php if ($loginError): ?><p class="error"><?= e($loginError) ?></p><?php endif; ?><form method="post" autocomplete="off"><label for="username">Benutzername</label><input id="username" name="username" required autocomplete="username"><label for="password">Passwort</label><input id="password" name="password" type="password" required autocomplete="current-password"><button type="submit">Anmelden</button></form></main></body></html>
|
||||
<?php
|
||||
exit;
|
||||
endif;
|
||||
|
||||
$releases = fetchJson((string) $config['gitea_releases_api']) ?? [];
|
||||
$downloadTotal = 0;
|
||||
$latestDownloads = 0;
|
||||
$latestRelease = 'Noch kein Release';
|
||||
$platformDownloads = ['windows' => 0, 'android' => 0];
|
||||
foreach ($releases as $releaseIndex => $release) {
|
||||
if (!is_array($release)) continue;
|
||||
if ($releaseIndex === 0) $latestRelease = (string) ($release['name'] ?? $release['tag_name'] ?? 'Aktuelles Release');
|
||||
foreach (($release['assets'] ?? []) as $asset) {
|
||||
if (!is_array($asset)) continue;
|
||||
$count = max(0, (int) ($asset['download_count'] ?? 0));
|
||||
$downloadTotal += $count;
|
||||
if ($releaseIndex === 0) $latestDownloads += $count;
|
||||
$name = strtolower((string) ($asset['name'] ?? ''));
|
||||
if (substr($name, -4) === '.exe') $platformDownloads['windows'] += $count;
|
||||
if (substr($name, -4) === '.apk') $platformDownloads['android'] += $count;
|
||||
}
|
||||
}
|
||||
|
||||
$playerStats = null;
|
||||
$httpKey = trim((string) ($config['nakama_http_key'] ?? ''));
|
||||
if ($httpKey !== '') {
|
||||
$statsUrl = (string) $config['nakama_rpc_url'] . '?http_key=' . rawurlencode($httpKey) . '&unwrap';
|
||||
$rpcResponse = fetchJson($statsUrl, 'POST', '{}');
|
||||
if (is_array($rpcResponse)) {
|
||||
$playerStats = $rpcResponse;
|
||||
if (isset($rpcResponse['payload']) && is_string($rpcResponse['payload'])) {
|
||||
$payload = json_decode($rpcResponse['payload'], true);
|
||||
if (is_array($payload)) $playerStats = $payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$players = (int) ($playerStats['total_players'] ?? 0);
|
||||
$today = gmdate('Y-m-d');
|
||||
$activeToday = (int) (($playerStats['daily_active'] ?? [])[$today] ?? 0);
|
||||
$updatedAt = isset($playerStats['updated_at']) ? date('d.m.Y H:i', (int) $playerStats['updated_at'] / 1000) : 'Noch keine Daten';
|
||||
?>
|
||||
<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow,noarchive"><meta http-equiv="refresh" content="300"><title>Void Bastion – Interne Statistik</title><style>
|
||||
:root{color-scheme:dark;--bg:#06121e;--panel:#0a2030;--line:#26596a;--cyan:#5cecff;--muted:#92aebb;font-family:Inter,system-ui,sans-serif}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 85% 0,#12384b 0,transparent 35%),var(--bg);color:#edfaff;min-height:100vh}.shell{width:min(1120px,calc(100% - 32px));margin:auto;padding:48px 0}header{display:flex;justify-content:space-between;align-items:start;gap:24px;margin-bottom:34px}h1{margin:0;font-size:clamp(2rem,5vw,4rem);letter-spacing:-.04em}p{color:var(--muted)}a{color:var(--cyan)}.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.card{padding:24px;border:1px solid var(--line);background:linear-gradient(145deg,#0d2c3d,#071722)}.card strong{display:block;color:var(--cyan);font-size:clamp(2rem,4vw,3.5rem);line-height:1}.card span{display:block;margin-top:10px;color:#b9ced7}.panel{margin-top:18px;padding:24px;border:1px solid var(--line);background:#081b28}.warning{color:#ffbd7b}.meta{font-size:.9rem}@media(max-width:800px){.grid{grid-template-columns:1fr 1fr}header{display:block}}@media(max-width:480px){.grid{grid-template-columns:1fr}}
|
||||
</style></head><body><main class="shell"><header><div><p>GERGAMES · NICHT ÖFFENTLICH</p><h1>Void Bastion Statistik</h1></div><a href="?logout=1">Abmelden</a></header><section class="grid"><article class="card"><strong><?= $downloadTotal ?></strong><span>Downloads insgesamt</span></article><article class="card"><strong><?= $latestDownloads ?></strong><span>Downloads aktuelles Release</span></article><article class="card"><strong><?= $players ?></strong><span>Registrierte Piloten</span></article><article class="card"><strong><?= $activeToday ?></strong><span>Heute aktive Piloten</span></article><article class="card"><strong><?= $platformDownloads['windows'] ?></strong><span>Windows-Downloads</span></article><article class="card"><strong><?= $platformDownloads['android'] ?></strong><span>Android-Downloads</span></article></section><section class="panel"><h2><?= e($latestRelease) ?></h2><p>Die Downloadwerte stammen direkt aus den Gitea-Release-Anhängen und werden alle fünf Minuten neu geladen.</p><?php if ($playerStats === null): ?><p class="warning">Die private Nakama-Statistik ist noch nicht mit dem HTTP-Schlüssel verbunden.</p><?php else: ?><p>Letzte Spieleraktualisierung: <?= e($updatedAt) ?></p><?php endif; ?><p class="meta">Diese Seite ist nicht verlinkt, von Suchmaschinen ausgeschlossen und verlangt eine Anmeldung. Es werden ausschließlich zusammengefasste Werte dargestellt.</p></section></main></body></html>
|
||||
Reference in new issue
Block a user