diff options
Diffstat (limited to 'index.php')
| -rw-r--r-- | index.php | 363 |
1 files changed, 0 insertions, 363 deletions
diff --git a/index.php b/index.php deleted file mode 100644 index 60d3239..0000000 --- a/index.php +++ /dev/null @@ -1,363 +0,0 @@ -<?php
-/**
- * Explorateur Web - TeamSpeak 6
- */
-
-$config_file = __DIR__ . '/config.php';
-if (!file_exists($config_file)) {
- die("Erreur : Le fichier 'config.php' est introuvable. Veuillez le créer à partir de 'config.example.php'.");
-}
-$config = require $config_file;
-
-// Utilitaires
-function e($string) { return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8'); }
-function safeUrl($filename) { return str_replace('%2F', '/', rawurlencode($filename)); }
-
-function genererEtoiles($nombre, $opacite) {
- $ombres = [];
- for ($i = 0; $i < $nombre; $i++) {
- $x = rand(0, 2000); $y = rand(0, 2000);
- $ombres[] = "{$x}px {$y}px rgba(255, 255, 255, {$opacite})";
- }
- return implode(", ", $ombres);
-}
-
-// Récupération API (Strictement en LECTURE SEULE - scope=read)
-function fetch_teamspeak_data($cfg) {
- $cache_file = $cfg['cache_file'];
- if (file_exists($cache_file) && (time() - filemtime($cache_file)) < $cfg['cache_time']) {
- $cached_data = file_get_contents($cache_file);
- if ($cached_data) return json_decode($cached_data, true);
- }
-
- $options = ['http' => ['header' => "x-api-key: {$cfg['api_key']}\r\n", 'method' => 'GET', 'timeout' => 2, 'ignore_errors' => true]];
- $context = stream_context_create($options);
-
- $base_url = "http://{$cfg['host']}:{$cfg['port']}/{$cfg['server_id']}";
- $channels_res = @file_get_contents("$base_url/channellist", false, $context);
- $clients_res = @file_get_contents("$base_url/clientlist?-groups", false, $context);
-
- if ($channels_res !== false && $clients_res !== false) {
- $data = [
- 'channels' => json_decode($channels_res, true)['body'] ?? [],
- 'clients' => json_decode($clients_res, true)['body'] ?? []
- ];
- file_put_contents($cache_file, json_encode($data));
- return $data;
- }
- return ['channels' => [], 'clients' => []];
-}
-
-// Tri hiérarchique
-function sort_ts_channels(array $channels, $pid = 0) {
- $ordered = []; $by_order = [];
- foreach ($channels as $c) { if (($c['pid'] ?? 0) == $pid) $by_order[$c['channel_order'] ?? 0] = $c; }
- $current_order = 0; $max_iterations = count($channels) + 1; $i = 0;
- while (isset($by_order[$current_order]) && $i < $max_iterations) {
- $channel = $by_order[$current_order]; $ordered[] = $channel;
- $sub_channels = sort_ts_channels($channels, $channel['cid']);
- foreach ($sub_channels as $sc) $ordered[] = $sc;
- $current_order = $channel['cid']; $i++;
- }
- return $ordered;
-}
-
-// Traitement des données
-$start_time = microtime(true);
-$ts_data = fetch_teamspeak_data($config['server']);
-$api_ping = round((microtime(true) - $start_time) * 1000); // Calcule le temps de réponse de l'API locale
-
-$raw_channels = $ts_data['channels'];
-$raw_clients = $ts_data['clients'];
-$is_online = !empty($raw_channels);
-
-$channels = sort_ts_channels($raw_channels, 0);
-
-$clients_by_channel = [];
-$total_real_clients = 0;
-
-if ($is_online) {
- foreach ($raw_clients as $client) {
- if (($client['client_type'] ?? 0) == 1) continue; // On ignore les bots/ServerQuery
- $clients_by_channel[$client['cid']][] = $client;
- $total_real_clients++;
- }
-}
-
-// Rendu du fragment AJAX
-ob_start();
-if ($is_online): ?>
-
- <div class="server-stats">
- <div class="stat-item">
- <span class="stat-label">En Ligne</span>
- <span class="stat-value"><?php echo $total_real_clients; ?></span>
- </div>
- <div class="stat-item">
- <span class="stat-label">Salons</span>
- <span class="stat-value"><?php echo count($channels); ?></span>
- </div>
- <div class="stat-item">
- <span class="stat-label">Latence API</span>
- <span class="stat-value"><?php echo $api_ping; ?> ms</span>
- </div>
- </div>
-
- <div class="channel-list">
- <?php foreach ($channels as $channel):
- $c_name = $channel['channel_name'];
- if (strpos($c_name, '[spacer') !== false) continue;
-
- $cid = $channel['cid'];
- $channel_clients = $clients_by_channel[$cid] ?? [];
-
- $bg_img = $config['bannieres_canaux'][$c_name] ?? ($config['images_generiques']['fond'] ?? '');
- $icon_img = $config['icones_canaux'][$c_name] ?? ($config['images_generiques']['icone'] ?? '');
-
- $bg_style = "";
- if (!empty($bg_img)) {
- $bg_style = "background-image: linear-gradient(90deg, rgba(19, 24, 35, 0.95) 0%, rgba(19, 24, 35, 0.7) 40%, rgba(19, 24, 35, 0.9) 100%), url('" . safeUrl($bg_img) . "');";
- }
- ?>
- <article class="channel">
- <header class="channel-header" style="<?php echo $bg_style; ?>">
- <div class="channel-title-group">
- <?php if (!empty($icon_img)): ?>
- <img src="<?php echo safeUrl($icon_img); ?>" class="channel-icon" alt="" aria-hidden="true">
- <?php endif; ?>
- <h2><?php echo e($c_name); ?></h2>
- </div>
- <span class="channel-meta">
- <?php echo count($channel_clients); ?> / <?php echo ($channel['channel_maxclients'] == -1) ? '∞' : e($channel['channel_maxclients']); ?>
- </span>
- </header>
-
- <?php if (!empty($channel_clients)): ?>
- <ul class="client-list">
- <?php foreach ($channel_clients as $client): ?>
- <li class="client">
- <?php
- $role_icon = '<span class="client-no-role-icon"></span>';
- if (isset($client['client_servergroups'])) {
- $groups = explode(',', $client['client_servergroups']);
- foreach ($groups as $g_id) {
- if (isset($config['icones_roles'][$g_id])) {
- $role_icon = '<img src="' . safeUrl($config['icones_roles'][$g_id]) . '" class="role-icon" alt="Rôle">';
- break;
- }
- }
- }
- echo $role_icon;
- ?>
- <span><?php echo e($client['client_nickname']); ?></span>
- </li>
- <?php endforeach; ?>
- </ul>
- <?php endif; ?>
- </article>
- <?php endforeach; ?>
- </div>
-<?php else: ?>
- <div class="empty-server">Impossible d'établir le contact avec le serveur central.</div>
-<?php endif;
-$html_fragment = ob_get_clean();
-
-// Gestion de la réponse AJAX
-if (isset($_GET['ajax']) && $_GET['ajax'] === '1') {
- header('Content-Type: text/html; charset=utf-8');
- echo $html_fragment;
- exit;
-}
-?>
-<!DOCTYPE html>
-<html lang="fr">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="<?php echo e($config['ui']['sous_titre']); ?>">
- <title><?php echo e($config['ui']['titre']); ?></title>
- <link rel="icon" type="image/png" href="logo.png">
- <style>
- :root {
- --bg-color: <?php echo e($config['ui']['couleur_fond']); ?>;
- --panel-bg: <?php echo e($config['ui']['couleur_panneau']); ?>;
- --accent: <?php echo e($config['ui']['couleur_accent']); ?>;
- --text-main: <?php echo e($config['ui']['couleur_texte']); ?>;
- --text-muted: #64748b;
- --channel-bg: #1e2433;
- }
-
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: var(--bg-color); color: var(--text-main); margin: 0; padding: 2rem; display: flex; justify-content: center; min-height: 100vh; box-sizing: border-box; }
-
- <?php if ($config['ui']['activer_etoiles'] ?? false): ?>
- #space-bg { position: fixed; inset: 0; z-index: -1; background-color: var(--bg-color); overflow: hidden;}
- .star-layer { position: absolute; background: transparent; }
- .star-layer::after { content: ""; position: absolute; top: 2000px; background: inherit; box-shadow: inherit; }
- .stars-s { width: 1px; height: 1px; box-shadow: <?php echo genererEtoiles(150, 0.4); ?>; animation: animStar 250s linear infinite; }
- .stars-m { width: 2px; height: 2px; box-shadow: <?php echo genererEtoiles(50, 0.6); ?>; animation: animStar 180s linear infinite; }
- .stars-l { width: 2px; height: 2px; box-shadow: <?php echo genererEtoiles(15, 0.8); ?>; animation: animStar 100s linear infinite; }
- @keyframes animStar { from { transform: translateY(0); } to { transform: translateY(-2000px); } }
- <?php endif; ?>
-
- .app-container { background: var(--panel-bg); border-radius: 12px; width: 100%; max-width: 650px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7); border: 1px solid rgba(255, 255, 255, 0.04); position: relative; overflow: hidden; height: fit-content; }
-
- .banner { width: 100%; height: auto; display: block; border-bottom: 2px solid var(--accent); opacity: 0.95; }
- .content-wrapper { padding: 2rem; }
-
- /* Barre de progression fluide */
- .progress-bar { position: absolute; top: 0; left: 0; height: 2px; background-color: var(--accent); width: 100%; z-index: 10; }
-
- .header { text-align: center; margin-bottom: 2rem; }
- .header h1 { margin: 0 0 0.25rem 0; font-size: 1.5rem; color: #f8fafc; }
- .header p { color: var(--text-muted); margin: 0; font-size: 0.9rem; }
-
- .status-badge { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 4px; font-size: 0.7rem; font-weight: 700; margin-top: 15px; letter-spacing: 1px; }
- .status-online { color: #a7f3d0; background-color: rgba(6, 78, 59, 0.4); border: 1px solid rgba(16, 185, 129, 0.2); }
- .status-offline { color: #fca5a5; background-color: rgba(127, 29, 29, 0.4); border: 1px solid rgba(239, 68, 68, 0.2); }
- .pulse-dot { width: 6px; height: 6px; background-color: #10b981; border-radius: 50%; margin-right: 8px; animation: pulse 2s infinite; }
- .status-offline .pulse-dot { background-color: #ef4444; animation: none; }
- @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); } 70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); } 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } }
-
- /* Stats du serveur adaptées à la lecture seule */
- .server-stats { display: flex; justify-content: space-around; background: rgba(0, 0, 0, 0.2); padding: 12px; border-radius: 8px; margin-bottom: 1.5rem; border: 1px solid rgba(255, 255, 255, 0.03); }
- .stat-item { display: flex; flex-direction: column; align-items: center; }
- .stat-label { font-size: 0.65rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; }
- .stat-value { font-size: 1rem; font-weight: 700; color: var(--text-main); font-variant-numeric: tabular-nums; }
-
- .btn-primary { display: block; padding: 12px; background-color: var(--accent); color: #000; text-align: center; text-decoration: none; border-radius: 6px; font-weight: 800; text-transform: uppercase; font-size: 0.85rem; transition: filter 0.2s; margin-bottom: 0.5rem; }
- .btn-primary:hover { filter: brightness(1.1); }
-
- .help-text { text-align: center; font-size: 0.8rem; color: var(--text-muted); margin: 0.5rem 0 2rem 0; }
- .help-text a { color: var(--text-main); text-decoration: none; border-bottom: 1px solid rgba(255,255,255,0.2); transition: border-color 0.2s; }
- .help-text a:hover { border-color: var(--accent); color: var(--accent); }
-
- .channel-list { display: flex; flex-direction: column; gap: 6px; }
- .channel { border-radius: 12px; overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.03); background-color: var(--channel-bg); }
- .channel-header { padding: 8px 14px; display: flex; justify-content: space-between; align-items: center; color: #f1f5f9; background-size: cover; background-position: center; background-repeat: no-repeat; }
-
- .channel-title-group { display: flex; align-items: center; margin: 0; font-size: 0.9rem; font-weight: 600; }
- .channel-title-group h2 { font-size: 1rem; margin: 0; font-weight: 600; }
- .channel-icon { width: 20px; height: 20px; margin-right: 10px; border-radius: 4px; object-fit: contain; }
- .channel-meta { font-size: 0.8rem; color: rgba(255,255,255,0.7); }
-
- .client-list { list-style: none; padding: 4px 0 8px 0; margin: 0; }
- .client { padding: 4px 15px 4px 38px; font-size: 0.85rem; color: #cbd5e1; display: flex; align-items: center; font-weight: 500; }
- .role-icon { width: 16px; height: 16px; object-fit: contain; margin-right: 10px; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5)); }
- .client-no-role-icon { display: inline-block; width: 8px; height: 8px; border: 2px solid #475569; border-radius: 50%; margin-right: 10px; }
-
- .empty-server { text-align: center; color: var(--text-muted); padding: 3rem 0; font-size: 0.9rem; }
-
- .footer-links { text-align: center; padding-top: 1.5rem; margin-top: 2rem; border-top: 1px solid rgba(255, 255, 255, 0.05); }
- .footer-links a { color: var(--text-muted); text-decoration: none; font-size: 0.85rem; margin: 0 10px; transition: color 0.2s; }
- .footer-links a:hover { color: var(--text-main); }
-
- .credits { text-align: center; margin-top: 1rem; font-size: 0.75rem; color: #475569; }
- .credits a { color: var(--text-muted); font-weight: 600; text-decoration: none; transition: color 0.2s; }
- .credits a:hover { color: var(--accent); }
-
- #ajax-container { transition: opacity 0.3s ease-in-out; }
- </style>
-</head>
-<body>
-
-<?php if ($config['ui']['activer_etoiles'] ?? false): ?>
-<div id="space-bg" aria-hidden="true">
- <div class="star-layer stars-s"></div>
- <div class="star-layer stars-m"></div>
- <div class="star-layer stars-l"></div>
-</div>
-<?php endif; ?>
-
-<main class="app-container">
- <?php if ($is_online): ?><div class="progress-bar" id="refresh-bar"></div><?php endif; ?>
-
- <?php if (!empty($config['ui']['banniere_url'])): ?>
- <img src="<?php echo safeUrl($config['ui']['banniere_url']); ?>" alt="Bannière d'en-tête" class="banner">
- <?php endif; ?>
-
- <div class="content-wrapper">
- <header class="header">
- <h1><?php echo e($config['ui']['titre']); ?></h1>
- <p><?php echo e($config['ui']['sous_titre']); ?></p>
- <?php if ($is_online): ?>
- <div class="status-badge status-online"><span class="pulse-dot"></span> TRANSMISSION ACTIVE</div>
- <?php else: ?>
- <div class="status-badge status-offline"><span class="pulse-dot"></span> SIGNAL PERDU</div>
- <?php endif; ?>
- </header>
-
- <a href="ts3server://<?php echo e($config['server']['ts_domain']); ?>" class="btn-primary"><?php echo e($config['boutons']['rejoindre']); ?></a>
- <p class="help-text"><?php echo e($config['boutons']['telecharger_texte']); ?> <a href="<?php echo e($config['boutons']['telecharger_url']); ?>" target="_blank" rel="noopener"><?php echo e($config['boutons']['telecharger_lien']); ?></a></p>
-
- <section id="ajax-container" aria-live="polite">
- <?php echo $html_fragment; ?>
- </section>
-
- <?php if (!empty($config['liens_externes'])): ?>
- <nav class="footer-links">
- <?php foreach ($config['liens_externes'] as $nom => $url): ?>
- <a href="<?php echo e($url); ?>" target="_blank" rel="noopener"><?php echo e($nom); ?></a>
- <?php endforeach; ?>
- </nav>
- <?php endif; ?>
-
- <footer class="credits">
- <?php echo e($config['footer']['texte']); ?> <a href="<?php echo e($config['footer']['lien_auteur']); ?>" target="_blank" rel="noopener"><?php echo e($config['footer']['auteur']); ?></a> — Scan radar : <span id="timer-text"><?php echo $config['server']['cache_time']; ?></span>s
- </footer>
- </div>
-</main>
-
-<script>
- <?php if ($is_online): ?>
- document.addEventListener("DOMContentLoaded", () => {
- let timeLeft = <?php echo (int)$config['server']['cache_time']; ?>;
- const totalTime = <?php echo (int)$config['server']['cache_time']; ?>;
-
- const progressBar = document.getElementById('refresh-bar');
- const timerText = document.getElementById('timer-text');
- const container = document.getElementById('ajax-container');
-
- // Animation fluide de la barre de temps
- function resetProgressBar() {
- progressBar.style.transition = 'none';
- progressBar.style.width = '100%';
- void progressBar.offsetWidth;
- progressBar.style.transition = `width ${totalTime}s linear`;
- progressBar.style.width = '0%';
- }
-
- resetProgressBar();
-
- setInterval(() => {
- timeLeft--;
- timerText.innerText = timeLeft;
-
- if (timeLeft <= 0) {
- container.style.opacity = '0.5';
- fetch(window.location.href.split('?')[0] + '?ajax=1', { cache: 'no-store', headers: {'X-Requested-With': 'XMLHttpRequest'} })
- .then(r => {
- if (!r.ok) throw new Error("HTTP error " + r.status);
- return r.text();
- })
- .then(html => {
- container.innerHTML = html;
- container.style.opacity = '1';
-
- timeLeft = totalTime;
- timerText.innerText = timeLeft;
-
- resetProgressBar();
- })
- .catch(err => {
- console.error("Erreur de rafraîchissement :", err);
- container.style.opacity = '1';
- timeLeft = totalTime;
- resetProgressBar();
- });
- }
- }, 1000);
- });
- <?php endif; ?>
-</script>
-</body>
-</html>
\ No newline at end of file |
