first commit
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Expires: 0');
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(0);
|
||||
|
||||
define('CRAFTY_URL', 'https://craft.ipv6.nix.roulaise.net');
|
||||
define('CRAFTY_TOKEN', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJpYXQiOjE3NzczMDIwNjIsInRva2VuX2lkIjoxfQ.DXFKO4aim-nJ_Wyn-o3qMaVyv6AP3aCoAYukR-uQels');
|
||||
|
||||
// Ports connus → clé logique
|
||||
const PORT_MAP = [9191 => 'mc1', 9292 => 'mc2'];
|
||||
|
||||
function craftyGet(string $path): ?array {
|
||||
$ch = curl_init(CRAFTY_URL . $path);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . CRAFTY_TOKEN],
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $body ? json_decode($body, true) : null;
|
||||
}
|
||||
|
||||
// Extraire -Xmx depuis la commande d'exécution → retourne en Mio
|
||||
function extractXmx(string $cmd): ?int {
|
||||
if (preg_match('/-Xmx(\d+)([GgMm])/i', $cmd, $m)) {
|
||||
return strtolower($m[2]) === 'g'
|
||||
? (int)$m[1] * 1024
|
||||
: (int)$m[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. Lister les serveurs
|
||||
$list = craftyGet('/api/v2/servers');
|
||||
if (!$list || ($list['status'] ?? '') !== 'ok') {
|
||||
echo json_encode(['success' => false, 'error' => 'Crafty unreachable']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($list['data'] as $srv) {
|
||||
$uuid = $srv['server_id'];
|
||||
|
||||
// Stats de ce serveur
|
||||
$stats = craftyGet("/api/v2/servers/{$uuid}/stats");
|
||||
if (!$stats || ($stats['status'] ?? '') !== 'ok') continue;
|
||||
$d = $stats['data'];
|
||||
|
||||
// Identifier la clé logique par port
|
||||
$port = (int)($d['server_port'] ?? $srv['server_port'] ?? 0);
|
||||
$key = PORT_MAP[$port] ?? null;
|
||||
|
||||
// Fallback par nom si port inconnu
|
||||
if (!$key) {
|
||||
$name = strtolower($d['server_name'] ?? $srv['server_name'] ?? '');
|
||||
if (str_contains($name, '#1') || str_contains($name, 'mc1') || str_contains($name, 'forge')) $key = 'mc1';
|
||||
elseif (str_contains($name, '#2') || str_contains($name, 'mc2') || str_contains($name, 'fabric')) $key = 'mc2';
|
||||
else $key = $uuid;
|
||||
}
|
||||
|
||||
// RAM utilisée : Crafty retourne en octets
|
||||
$ramUsed = isset($d['mem']) ? (int)round($d['mem'] / 1024 / 1024) : null;
|
||||
|
||||
// RAM allouée : extraire -Xmx depuis execution_command
|
||||
$cmd = $d['execution_command'] ?? $srv['execution_command'] ?? '';
|
||||
$ramAlloc = extractXmx($cmd);
|
||||
|
||||
$result[$key] = [
|
||||
'uuid' => $uuid,
|
||||
'name' => $d['server_name'] ?? $srv['server_name'] ?? $uuid,
|
||||
'port' => $port,
|
||||
'running' => (bool)($d['running'] ?? false),
|
||||
'players' => (int)($d['online'] ?? 0),
|
||||
'max_players' => (int)($d['max'] ?? 0),
|
||||
'ram_used' => $ramUsed,
|
||||
'ram_alloc' => $ramAlloc,
|
||||
'cpu' => isset($d['cpu']) ? round((float)$d['cpu'], 1) : null,
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'servers' => $result], JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(0);
|
||||
|
||||
$result = [];
|
||||
|
||||
// ===== EVENTS (logs parsés par mc-log-watcher) =====
|
||||
$logsFile = '/var/cache/mc-logs.json';
|
||||
if (file_exists($logsFile)) {
|
||||
$raw = @file_get_contents($logsFile);
|
||||
$decoded = $raw ? @json_decode($raw, true) : null;
|
||||
$result['events'] = $decoded['events'] ?? [];
|
||||
// Renvoyer les N derniers triés du plus récent au plus ancien
|
||||
$result['events'] = array_reverse(array_slice($result['events'], -100));
|
||||
} else {
|
||||
$result['events'] = [];
|
||||
}
|
||||
|
||||
// ===== WHITELIST =====
|
||||
$whitelists = [
|
||||
'mc1' => '/srv/Minecraft/whitelist.json',
|
||||
'mc2' => '/srv/Fabric-1.21.8/whitelist.json',
|
||||
];
|
||||
$result['whitelist'] = [];
|
||||
foreach ($whitelists as $key => $path) {
|
||||
if (file_exists($path)) {
|
||||
$raw = @file_get_contents($path);
|
||||
$players = $raw ? @json_decode($raw, true) : [];
|
||||
$result['whitelist'][$key] = is_array($players) ? $players : [];
|
||||
} else {
|
||||
$result['whitelist'][$key] = null; // null = whitelist désactivée / fichier absent
|
||||
}
|
||||
}
|
||||
|
||||
// ===== BANNISSEMENTS =====
|
||||
$banlists = [
|
||||
'mc1' => '/srv/Minecraft/banned-players.json',
|
||||
'mc2' => '/srv/Fabric-1.21.8/banned-players.json',
|
||||
];
|
||||
$result['banned'] = [];
|
||||
foreach ($banlists as $key => $path) {
|
||||
if (file_exists($path)) {
|
||||
$raw = @file_get_contents($path);
|
||||
$players = $raw ? @json_decode($raw, true) : [];
|
||||
$result['banned'][$key] = is_array($players) ? $players : [];
|
||||
} else {
|
||||
$result['banned'][$key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// ===== MODS (Fabric uniquement - dossier mods/) =====
|
||||
$modsDir = '/srv/Fabric-1.21.8/mods';
|
||||
$result['mods'] = [];
|
||||
if (is_dir($modsDir)) {
|
||||
$files = @scandir($modsDir);
|
||||
if ($files) {
|
||||
foreach ($files as $file) {
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) === 'jar') {
|
||||
// Tenter de lire fabric.mod.json à l'intérieur du jar (zip)
|
||||
$jarPath = $modsDir . '/' . $file;
|
||||
$modInfo = ['filename' => $file, 'name' => null, 'version' => null, 'description' => null];
|
||||
|
||||
// Lire le fabric.mod.json depuis le jar (zip)
|
||||
if (class_exists('ZipArchive')) {
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($jarPath) === true) {
|
||||
$idx = $zip->locateName('fabric.mod.json');
|
||||
if ($idx !== false) {
|
||||
$json = @json_decode($zip->getFromIndex($idx), true);
|
||||
if ($json) {
|
||||
$modInfo['name'] = $json['name'] ?? $json['id'] ?? null;
|
||||
$modInfo['version'] = $json['version'] ?? null;
|
||||
$modInfo['description'] = $json['description'] ?? null;
|
||||
$modInfo['id'] = $json['id'] ?? null;
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: nom depuis le fichier
|
||||
if (!$modInfo['name']) {
|
||||
$modInfo['name'] = preg_replace('/[-_][\d.]+.*\.jar$/', '', $file);
|
||||
}
|
||||
|
||||
$result['mods'][] = $modInfo;
|
||||
}
|
||||
}
|
||||
// Trier par nom
|
||||
usort($result['mods'], fn($a, $b) => strcasecmp($a['name'] ?? '', $b['name'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
// ===== SERVER PROPERTIES (whitelist activée ?) =====
|
||||
$propsFiles = [
|
||||
'mc1' => '/srv/Minecraft/server.properties',
|
||||
'mc2' => '/srv/Fabric-1.21.8/server.properties',
|
||||
];
|
||||
$result['whitelist_enabled'] = [];
|
||||
foreach ($propsFiles as $key => $path) {
|
||||
$result['whitelist_enabled'][$key] = false;
|
||||
if (file_exists($path)) {
|
||||
$content = @file_get_contents($path);
|
||||
if ($content && preg_match('/^white-list\s*=\s*true/m', $content)) {
|
||||
$result['whitelist_enabled'][$key] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $result], JSON_UNESCAPED_UNICODE);
|
||||
?>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-store');
|
||||
|
||||
$content = @file_get_contents('/proc/mdstat');
|
||||
if (!$content) { echo json_encode(['error' => 'unreadable']); exit; }
|
||||
|
||||
$arrays = [];
|
||||
$current = null;
|
||||
|
||||
foreach (explode("\n", $content) as $line) {
|
||||
if (preg_match('/^(md\d+)\s*:\s*(\w+)\s+(\w+)\s+(.+)$/', $line, $m)) {
|
||||
$current = $m[1];
|
||||
$arrays[$current] = ['name' => $m[1], 'level' => $m[3], 'health' => 'ok', 'bitmap' => '', 'detail' => ''];
|
||||
}
|
||||
if ($current && preg_match('/\[(\d+)\/(\d+)\]\s*\[([U_]+)\]/', $line, $m)) {
|
||||
$arrays[$current]['bitmap'] = $m[3];
|
||||
$arrays[$current]['active'] = (int)$m[2];
|
||||
$arrays[$current]['total'] = (int)$m[1];
|
||||
if ((int)$m[2] < (int)$m[1] || str_contains($m[3], '_'))
|
||||
$arrays[$current]['health'] = 'degraded';
|
||||
}
|
||||
if ($current && preg_match('/(resync|recovery)\s*=\s*([\d.]+%)/i', $line, $m)) {
|
||||
$arrays[$current]['health'] = 'recovering';
|
||||
$arrays[$current]['detail'] = "{$m[1]} {$m[2]}";
|
||||
}
|
||||
}
|
||||
|
||||
$list = array_values($arrays);
|
||||
$healthy = !array_filter($list, fn($a) => $a['health'] !== 'ok');
|
||||
|
||||
echo json_encode(['healthy' => $healthy, 'arrays' => $list]);
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Expires: 0');
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(0);
|
||||
|
||||
/**
|
||||
* Minecraft Server List Ping (protocole 1.7+)
|
||||
* Ref: https://wiki.vg/Server_List_Ping
|
||||
*/
|
||||
function pingMinecraftServer(string $host, int $port = 25565, int $timeout = 4): array {
|
||||
$result = [
|
||||
'online' => false,
|
||||
'players' => 0,
|
||||
'max_players' => 0,
|
||||
'version' => '',
|
||||
'motd' => '',
|
||||
'latency' => null,
|
||||
];
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
|
||||
if (!$socket) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
stream_set_timeout($socket, $timeout);
|
||||
|
||||
// ---- Handshake packet (0x00) ----
|
||||
$hostBytes = $host;
|
||||
$hostLen = strlen($hostBytes);
|
||||
|
||||
$handshakeData = "\x00"; // Packet ID 0x00
|
||||
$handshakeData .= varInt(47); // Protocol version (47 = 1.8, -1 non-spécifique)
|
||||
$handshakeData .= varInt($hostLen) . $hostBytes; // Server address
|
||||
$handshakeData .= pack('n', $port); // Port (unsigned short big-endian)
|
||||
$handshakeData .= varInt(1); // Next state: 1 = status
|
||||
|
||||
fwrite($socket, varInt(strlen($handshakeData)) . $handshakeData);
|
||||
|
||||
// ---- Status Request (0x00, longueur 1) ----
|
||||
fwrite($socket, "\x01\x00");
|
||||
|
||||
// ---- Lire la réponse ----
|
||||
$packetLen = readVarInt($socket);
|
||||
if ($packetLen <= 0) {
|
||||
fclose($socket);
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Lire l'ID du paquet (doit être 0x00)
|
||||
readVarInt($socket);
|
||||
|
||||
// Lire la longueur de la chaîne JSON
|
||||
$strLen = readVarInt($socket);
|
||||
if ($strLen <= 0) {
|
||||
fclose($socket);
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Lire le JSON
|
||||
$json = '';
|
||||
$remaining = $strLen;
|
||||
while ($remaining > 0 && !feof($socket)) {
|
||||
$chunk = fread($socket, min($remaining, 4096));
|
||||
if ($chunk === false || $chunk === '') break;
|
||||
$json .= $chunk;
|
||||
$remaining -= strlen($chunk);
|
||||
}
|
||||
|
||||
fclose($socket);
|
||||
|
||||
$latency = (int) round((microtime(true) - $start) * 1000);
|
||||
$data = @json_decode($json, true);
|
||||
|
||||
if (!$data) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ---- Extraire le MOTD ----
|
||||
$motd = '';
|
||||
if (isset($data['description'])) {
|
||||
if (is_string($data['description'])) {
|
||||
$motd = $data['description'];
|
||||
} elseif (is_array($data['description'])) {
|
||||
$motd = $data['description']['text'] ?? '';
|
||||
// Concatener les extras
|
||||
if (!empty($data['description']['extra']) && is_array($data['description']['extra'])) {
|
||||
foreach ($data['description']['extra'] as $extra) {
|
||||
$motd .= $extra['text'] ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Supprimer les codes couleur §X
|
||||
$motd = preg_replace('/§[0-9a-fk-orA-FK-OR]/u', '', $motd);
|
||||
$motd = trim(preg_replace('/\s+/', ' ', $motd));
|
||||
|
||||
$result['online'] = true;
|
||||
$result['players'] = (int) ($data['players']['online'] ?? 0);
|
||||
$result['max_players'] = (int) ($data['players']['max'] ?? 0);
|
||||
$result['version'] = $data['version']['name'] ?? '';
|
||||
$result['motd'] = $motd;
|
||||
$result['latency'] = $latency;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode un entier en VarInt Minecraft
|
||||
*/
|
||||
function varInt(int $value): string {
|
||||
$out = '';
|
||||
do {
|
||||
$byte = $value & 0x7F;
|
||||
$value = ($value >> 7) & PHP_INT_MAX; // shift sans signe
|
||||
if ($value !== 0) $byte |= 0x80;
|
||||
$out .= chr($byte);
|
||||
} while ($value !== 0);
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit un VarInt depuis un socket
|
||||
*/
|
||||
function readVarInt($socket): int {
|
||||
$value = 0;
|
||||
$shift = 0;
|
||||
do {
|
||||
$raw = fgetc($socket);
|
||||
if ($raw === false) return 0;
|
||||
$byte = ord($raw);
|
||||
$value |= ($byte & 0x7F) << $shift;
|
||||
$shift += 7;
|
||||
if ($shift > 35) break; // sécurité anti-boucle infinie
|
||||
} while ($byte & 0x80);
|
||||
return $value;
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// Configuration des serveurs à pinger
|
||||
// ======================================================
|
||||
$servers = [
|
||||
[
|
||||
'name' => 'Serveur MC #1',
|
||||
'host' => 'nix.roulaise.net',
|
||||
'port' => 9191,
|
||||
],
|
||||
[
|
||||
'name' => 'Serveur MC #2',
|
||||
'host' => 'nix.roulaise.net',
|
||||
'port' => 9292,
|
||||
],
|
||||
];
|
||||
|
||||
// ======================================================
|
||||
// Ping tous les serveurs et retourner le résultat JSON
|
||||
// ======================================================
|
||||
$results = [];
|
||||
foreach ($servers as $srv) {
|
||||
$ping = pingMinecraftServer($srv['host'], $srv['port']);
|
||||
$results[] = array_merge([
|
||||
'name' => $srv['name'],
|
||||
'host' => $srv['host'],
|
||||
'port' => $srv['port'],
|
||||
], $ping);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'servers' => $results,
|
||||
'timestamp' => time(),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK);
|
||||
?>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// --- Configuration des serveurs ---
|
||||
$servers = [
|
||||
'Fabric 1.19.2' => '/srv/fabric-1.19.2',
|
||||
'Forge 1.19.2' => '/srv/forge-1.19.2',
|
||||
];
|
||||
// ----------------------------------
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
function mod_name(string $filename): string {
|
||||
$name = preg_replace('/\.jar$/i', '', $filename);
|
||||
$name = preg_replace('/[-_](?:v?\d+[\.\d]*[\+\-\w]*).*$/i', '', $name);
|
||||
$name = str_replace(['-', '_'], ' ', $name);
|
||||
return ucwords(strtolower(trim($name)));
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($servers as $label => $root) {
|
||||
$mods_dir = $root . '/mods';
|
||||
$entry = ['label' => $label, 'mods' => [], 'error' => null];
|
||||
|
||||
if (!is_dir($mods_dir)) {
|
||||
$entry['error'] = 'Dossier introuvable : ' . $mods_dir;
|
||||
} else {
|
||||
$files = glob($mods_dir . '/*.jar');
|
||||
sort($files);
|
||||
foreach ($files as $f) {
|
||||
$filename = basename($f);
|
||||
$entry['mods'][] = [
|
||||
'file' => $filename,
|
||||
'name' => mod_name($filename),
|
||||
];
|
||||
}
|
||||
}
|
||||
$result[] = $entry;
|
||||
}
|
||||
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Expires: 0');
|
||||
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(0);
|
||||
|
||||
function getSystemStats() {
|
||||
$stats = [];
|
||||
|
||||
// ========== UPTIME ==========
|
||||
if (file_exists('/proc/uptime')) {
|
||||
$uptime = @file_get_contents('/proc/uptime');
|
||||
if ($uptime !== false) {
|
||||
$uptimeSeconds = floatval(explode(' ', $uptime)[0]);
|
||||
$days = floor($uptimeSeconds / 86400);
|
||||
$hours = floor(($uptimeSeconds % 86400) / 3600);
|
||||
$minutes = floor(($uptimeSeconds % 3600) / 60);
|
||||
$stats['uptime'] = sprintf('%dd %dh %dm', $days, $hours, $minutes);
|
||||
} else {
|
||||
$stats['uptime'] = 'N/A';
|
||||
}
|
||||
} else {
|
||||
$stats['uptime'] = 'N/A';
|
||||
}
|
||||
|
||||
// ========== CPU USAGE ==========
|
||||
if (file_exists('/proc/stat')) {
|
||||
$stat1 = @file('/proc/stat');
|
||||
if ($stat1 !== false && count($stat1) > 0) {
|
||||
usleep(100000);
|
||||
$stat2 = @file('/proc/stat');
|
||||
if ($stat2 !== false && count($stat2) > 0) {
|
||||
$info1 = explode(' ', preg_replace('!cpu +!', '', $stat1[0]));
|
||||
$info2 = explode(' ', preg_replace('!cpu +!', '', $stat2[0]));
|
||||
$dif = [
|
||||
'user' => floatval($info2[0]) - floatval($info1[0]),
|
||||
'nice' => floatval($info2[1]) - floatval($info1[1]),
|
||||
'sys' => floatval($info2[2]) - floatval($info1[2]),
|
||||
'idle' => floatval($info2[3]) - floatval($info1[3])
|
||||
];
|
||||
$total = array_sum($dif);
|
||||
$stats['cpu_usage'] = $total > 0 ? round(100 - ($dif['idle'] / $total * 100), 1) : 0;
|
||||
} else {
|
||||
$load = sys_getloadavg();
|
||||
$stats['cpu_usage'] = round(min($load[0] * 25, 100), 1);
|
||||
}
|
||||
} else {
|
||||
$load = sys_getloadavg();
|
||||
$stats['cpu_usage'] = round(min($load[0] * 25, 100), 1);
|
||||
}
|
||||
} else {
|
||||
$stats['cpu_usage'] = 0;
|
||||
}
|
||||
|
||||
// ========== MEMORY ==========
|
||||
if (file_exists('/proc/meminfo')) {
|
||||
$meminfo = @file_get_contents('/proc/meminfo');
|
||||
if ($meminfo !== false) {
|
||||
preg_match_all('/^(\w+):\s+(\d+)/m', $meminfo, $matches);
|
||||
$mem = array_combine($matches[1], $matches[2]);
|
||||
$memTotal = floatval($mem['MemTotal']) / 1024 / 1024;
|
||||
$memAvailable = isset($mem['MemAvailable'])
|
||||
? floatval($mem['MemAvailable'])
|
||||
: (floatval($mem['MemFree']) + floatval($mem['Buffers']) + floatval($mem['Cached']));
|
||||
$memFree = $memAvailable / 1024 / 1024;
|
||||
$memUsed = $memTotal - $memFree;
|
||||
$stats['ram_total'] = round($memTotal, 1);
|
||||
$stats['ram_used'] = round($memUsed, 1);
|
||||
$stats['ram_free'] = round($memFree, 1);
|
||||
$stats['ram_percent'] = round(($memUsed / $memTotal) * 100, 1);
|
||||
} else {
|
||||
$stats['ram_total'] = $stats['ram_used'] = $stats['ram_percent'] = 0;
|
||||
}
|
||||
} else {
|
||||
$stats['ram_total'] = $stats['ram_used'] = $stats['ram_percent'] = 0;
|
||||
}
|
||||
|
||||
// ========== DISK ==========
|
||||
$partitions = ['/', '/var', '/srv'];
|
||||
foreach ($partitions as $partition) {
|
||||
$key = str_replace('/', '', $partition);
|
||||
if ($key === '') $key = 'root';
|
||||
$diskTotal = @disk_total_space($partition);
|
||||
$diskFree = @disk_free_space($partition);
|
||||
if ($diskTotal !== false && $diskFree !== false) {
|
||||
$diskUsed = $diskTotal - $diskFree;
|
||||
$stats['disk_' . $key . '_total'] = round($diskTotal / 1024 / 1024 / 1024, 1);
|
||||
$stats['disk_' . $key . '_used'] = round($diskUsed / 1024 / 1024 / 1024, 1);
|
||||
$stats['disk_' . $key . '_free'] = round($diskFree / 1024 / 1024 / 1024, 1);
|
||||
$stats['disk_' . $key . '_percent'] = round(($diskUsed / $diskTotal) * 100, 1);
|
||||
} else {
|
||||
$stats['disk_' . $key . '_total'] = 0;
|
||||
$stats['disk_' . $key . '_free'] = 0;
|
||||
$stats['disk_' . $key . '_percent'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== LOAD AVERAGE ==========
|
||||
$loadavg = sys_getloadavg();
|
||||
$stats['load_1'] = round($loadavg[0], 2);
|
||||
$stats['load_5'] = round($loadavg[1], 2);
|
||||
$stats['load_15'] = round($loadavg[2], 2);
|
||||
|
||||
// ========== PROCESSES ==========
|
||||
if (function_exists('shell_exec')) {
|
||||
$processes = @shell_exec('ps aux 2>/dev/null | wc -l');
|
||||
$stats['processes'] = $processes !== null ? max(intval(trim($processes)) - 1, 0) : 0;
|
||||
} else {
|
||||
$stats['processes'] = 0;
|
||||
}
|
||||
|
||||
$stats['hostname'] = @gethostname() ?: 'Unknown';
|
||||
$stats['timestamp'] = time();
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
try {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => getSystemStats()
|
||||
], JSON_NUMERIC_CHECK);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,94 @@
|
||||
body {
|
||||
font-family: 'Comfortaa', cursive;
|
||||
background: #282a36;
|
||||
color: #f8f8f2;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(68, 71, 90, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
box-shadow: 0 12px 30px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #bd93f9;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.3rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.7rem;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
background: #6272a4;
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
background: #50fa7b;
|
||||
color: #282a36;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 0.6rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row > div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 1.5rem;
|
||||
width: 100%;
|
||||
background: #bd93f9;
|
||||
color: #282a36;
|
||||
border: none;
|
||||
padding: 0.8rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #ff79c6;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 1.5rem;
|
||||
text-align: center;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
color: #50fa7b;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.container {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,756 @@
|
||||
/* =============================== Font ================================ */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@400;700&display=swap');
|
||||
|
||||
/* =============================== Reset ================================ */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* =============================== Body ================================ */
|
||||
body {
|
||||
font-family: 'Comfortaa', cursive;
|
||||
color: #f8f8f2;
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 2rem;
|
||||
background: transparent;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* =============================== Background Media ================================ */
|
||||
#bg-video,
|
||||
#bg-image {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
object-fit: cover;
|
||||
z-index: -2;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Dark overlay for readability */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(40, 42, 54, 0.55);
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* =============================== Main Container ================================ */
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ============================== Descriptions ============================== */
|
||||
.description {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
color: #6272a4;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* =============================== Header ================================ */
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #bd93f9;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
color: #6272a4;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* =============================== Cards ================================ */
|
||||
.card {
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
color: #f8f8f2;
|
||||
padding: 1.5rem 2rem;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin: 0.7rem 0;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
background-color: #6272a4;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.card a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* =============================== Images ================================ */
|
||||
.img {
|
||||
max-height: 200px;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* =============================== Forms ================================ */
|
||||
form {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.6rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
background-color: #6272a4;
|
||||
color: #f8f8f2;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
select:focus {
|
||||
background-color: #50fa7b;
|
||||
color: #282a36;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* =============================== Buttons ================================ */
|
||||
button {
|
||||
background-color: #bd93f9;
|
||||
color: #282a36;
|
||||
font-family: inherit;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
padding: 0.7rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #ff79c6;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* =============================== Footer ================================ */
|
||||
footer {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
color: #6272a4;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* =============================== PHP Directory Index ================================ */
|
||||
.card .file-type {
|
||||
display: block;
|
||||
margin-top: 0.3rem;
|
||||
font-size: 0.9rem;
|
||||
color: #bd93f9;
|
||||
}
|
||||
|
||||
/* =============================== Cookies ================================ */
|
||||
.cookie-btn {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
button.cookie-btn {
|
||||
background-color: #bd93f9 !important;
|
||||
color: #282a36 !important;
|
||||
}
|
||||
|
||||
button.cookie-btn:hover {
|
||||
background-color: #ff79c6 !important;
|
||||
}
|
||||
|
||||
/* ============================= Barre de chargement ============================= */
|
||||
#loading-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #ff69b4, #ff1493);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 0 10px rgba(255, 105, 180, 0.6);
|
||||
}
|
||||
|
||||
/* =============================== Dashboard Layout ================================ */
|
||||
.dashboard-wrapper {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 450px 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Card avec image */
|
||||
.card-with-image {
|
||||
padding: 1rem;
|
||||
min-height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-with-image .img {
|
||||
max-height: 200px;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* =============================== Stats Section ================================ */
|
||||
.stats-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-section h2 {
|
||||
color: #bd93f9;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
background-color: #6272a4;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.95rem;
|
||||
color: #6272a4;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
color: #50fa7b;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: #6272a4;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
/* =============================== Other Links Section ================================ */
|
||||
.other-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.other-section h2 {
|
||||
color: #bd93f9;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.other-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.small-card {
|
||||
padding: 1rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* =============================== Credits Section ================================ */
|
||||
.credits-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.credits-section h2 {
|
||||
color: #bd93f9;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.credits-category {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.credits-category:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.credits-category h3 {
|
||||
color: #ff79c6;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.credits-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.credit-card {
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
padding: 1.2rem;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
text-decoration: none;
|
||||
color: #f8f8f2;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.credit-card:hover {
|
||||
background-color: #6272a4;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
border-color: #bd93f9;
|
||||
}
|
||||
|
||||
.credit-icon {
|
||||
flex-shrink: 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: rgba(189, 147, 249, 0.2);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #bd93f9;
|
||||
}
|
||||
|
||||
.artist-card .credit-icon {
|
||||
background-color: rgba(255, 121, 198, 0.2);
|
||||
color: #ff79c6;
|
||||
}
|
||||
|
||||
.credit-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.credit-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: #f8f8f2;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.credit-desc {
|
||||
font-size: 0.9rem;
|
||||
color: #6272a4;
|
||||
}
|
||||
|
||||
.small-credits {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
}
|
||||
|
||||
.credit-tag {
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
padding: 0.8rem 1.2rem;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.credit-tag:hover {
|
||||
background-color: #6272a4;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.credit-tag a {
|
||||
color: #f8f8f2;
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.credits-thanks {
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.credits-thanks p {
|
||||
margin: 0.5rem 0;
|
||||
color: #f8f8f2;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.credits-thanks strong {
|
||||
color: #50fa7b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* =============================== Minecraft Server Widget ================================ */
|
||||
|
||||
/* ===== Titre de section MC ===== */
|
||||
.mc-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: #bd93f9;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
padding: 0.5rem 0.2rem 0;
|
||||
}
|
||||
|
||||
/* ===== Carte serveur ===== */
|
||||
.card.mc-server-card {
|
||||
padding: 0.9rem 1.1rem;
|
||||
cursor: default;
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
border-left: 3px solid #bd93f9;
|
||||
}
|
||||
|
||||
.card.mc-server-card:hover {
|
||||
background-color: rgba(80, 85, 110, 0.95);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
/* En-tête: nom violet + badge */
|
||||
.mc-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.mc-name {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #bd93f9; /* violet dracula */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Badge en ligne / hors ligne */
|
||||
.mc-badge {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
padding: 0.15rem 0.55rem;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.mc-online {
|
||||
background: rgba(80, 250, 123, 0.15);
|
||||
color: #50fa7b;
|
||||
border: 1px solid rgba(80, 250, 123, 0.4);
|
||||
animation: mc-pulse 2.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.mc-offline {
|
||||
background: rgba(255, 85, 85, 0.13);
|
||||
color: #ff5555;
|
||||
border: 1px solid rgba(255, 85, 85, 0.35);
|
||||
}
|
||||
|
||||
@keyframes mc-pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(80, 250, 123, 0); }
|
||||
50% { box-shadow: 0 0 0 4px rgba(80, 250, 123, 0.1); }
|
||||
}
|
||||
|
||||
/* Adresse IP:port en cyan monospace */
|
||||
.mc-address {
|
||||
font-size: 0.7rem;
|
||||
color: #8be9fd; /* cyan dracula */
|
||||
font-family: 'Courier New', monospace;
|
||||
margin-bottom: 0.3rem;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* MOTD en jaune italique */
|
||||
.mc-motd {
|
||||
font-size: 0.72rem;
|
||||
color: #f1fa8c; /* jaune dracula */
|
||||
font-style: italic;
|
||||
margin-bottom: 0.3rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Séparateur fin avant les infos */
|
||||
.mc-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
margin-top: 0.35rem;
|
||||
padding-top: 0.35rem;
|
||||
border-top: 1px solid rgba(98, 114, 164, 0.3);
|
||||
}
|
||||
|
||||
/* Joueurs : icône rose + texte blanc */
|
||||
.mc-players {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.82rem;
|
||||
color: #f8f8f2;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mc-players svg {
|
||||
color: #ff79c6; /* rose dracula */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Latence en orange */
|
||||
.mc-latency {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: #ffb86c; /* orange dracula */
|
||||
background: rgba(255, 184, 108, 0.12);
|
||||
border: 1px solid rgba(255, 184, 108, 0.25);
|
||||
border-radius: 5px;
|
||||
padding: 0.05rem 0.4rem;
|
||||
}
|
||||
|
||||
/* Version en gris/bleu à droite */
|
||||
.mc-version {
|
||||
font-size: 0.66rem;
|
||||
color: #6272a4;
|
||||
margin-left: auto;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Skeleton loader */
|
||||
.mc-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.4rem 0;
|
||||
}
|
||||
|
||||
.mc-loading-dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: #6272a4;
|
||||
animation: mc-bounce 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.mc-loading-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.mc-loading-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes mc-bounce {
|
||||
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
|
||||
40% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
/* =============================== Responsive ================================ */
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-wrapper {
|
||||
grid-template-columns: 380px 1fr;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.dashboard-wrapper {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.card,
|
||||
form {
|
||||
font-size: 1.1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.other-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.dashboard-wrapper {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.credits-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.small-credits {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== Reduced Motion ================================ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#bg-video {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#bg-image {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mc-online {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.mc-loading-dot {
|
||||
animation: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== Stat Cards Minecraft ================================ */
|
||||
.stat-card-mc {
|
||||
border-top: 2px solid rgba(189, 147, 249, 0.4);
|
||||
}
|
||||
|
||||
.stat-label-port {
|
||||
font-size: 0.75rem;
|
||||
color: #8be9fd;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: 400;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
|
||||
.mc-ram {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/* Dracula Log Viewer Theme */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@400;700&display=swap');
|
||||
|
||||
body {
|
||||
background-color: #282a36;
|
||||
color: #f8f8f2;
|
||||
font-family: 'Comfortaa', cursive;
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #bd93f9;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 2.3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Output box */
|
||||
.log-box {
|
||||
background: #44475a;
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem 2rem;
|
||||
font-size: 1.2rem;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
box-shadow: 0 6px 25px rgba(0,0,0,0.4);
|
||||
line-height: 1.6;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Highlight line on hover */
|
||||
.log-box:hover {
|
||||
background-color: #6272a4;
|
||||
transform: translateY(-3px);
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
margin-top: 3rem;
|
||||
font-size: 0.9rem;
|
||||
color: #6272a4;
|
||||
}
|
||||
|
After Width: | Height: | Size: 612 KiB |
|
After Width: | Height: | Size: 218 KiB |
@@ -0,0 +1,112 @@
|
||||
/* Comfortaa */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300;400;700&display=swap');
|
||||
|
||||
/* Global */
|
||||
body {
|
||||
background: #1c1e26;
|
||||
color: #ededed;
|
||||
font-family: "Comfortaa", sans-serif;
|
||||
padding: 2.5rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Title */
|
||||
h1 {
|
||||
color: #bd93f9;
|
||||
text-align: center;
|
||||
font-size: 2.4rem;
|
||||
margin-bottom: 2.3rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Table container */
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
background: #262933;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 14px 32px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* Header row */
|
||||
th {
|
||||
background: #2f3340;
|
||||
padding: 1rem 1.2rem;
|
||||
font-size: 0.95rem;
|
||||
color: #bbb;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* File rows */
|
||||
td {
|
||||
padding: 0.9rem 1.2rem;
|
||||
border-bottom: 1px solid #343847;
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Hover effect */
|
||||
tr:hover td {
|
||||
background: #3a3f51;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: #bd93f9;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #ffb2ff;
|
||||
text-shadow: 0 0 6px rgba(255, 150, 255, 0.6);
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Parent directory row */
|
||||
tr:first-of-type td {
|
||||
background: #232630;
|
||||
}
|
||||
|
||||
/* File size/date columns */
|
||||
td:nth-child(3),
|
||||
td:nth-child(4) {
|
||||
color: #a1a8c6;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Apache signature footer */
|
||||
address {
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
color: #737a95;
|
||||
font-size: 0.85rem;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
padding: 1.3rem;
|
||||
}
|
||||
table {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
td, th {
|
||||
padding: 0.7rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
const audios = [
|
||||
"/data/media/mp3/win7.start.mp3",
|
||||
"/data/media/mp3/win10.usb.mp3",
|
||||
"/data/media/mp3/winxp.shut.mp3",
|
||||
"/data/media/mp3/win10.error.mp3"
|
||||
];
|
||||
|
||||
// Calculer l'index basé sur le jour actuel
|
||||
const audioIndex = Math.floor(Date.now() / (1000 * 60 * 60 * 24)) % audios.length;
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const audio = document.getElementById("monSon");
|
||||
|
||||
if (!audio) {
|
||||
console.error("Element audio non trouvé");
|
||||
return;
|
||||
}
|
||||
|
||||
audio.src = audios[audioIndex];
|
||||
audio.volume = 1.0;
|
||||
|
||||
console.log("En attente d'un clic pour jouer:", audios[audioIndex]);
|
||||
|
||||
// Jouer le son au premier clic
|
||||
const playAudio = () => {
|
||||
console.log("Clic détecté - lecture du son");
|
||||
audio.play().catch((error) => {
|
||||
console.log("Erreur de lecture:", error);
|
||||
});
|
||||
document.removeEventListener("click", playAudio);
|
||||
document.removeEventListener("keydown", playAudio);
|
||||
document.removeEventListener("touchstart", playAudio);
|
||||
};
|
||||
|
||||
document.addEventListener("click", playAudio);
|
||||
document.addEventListener("keydown", playAudio);
|
||||
document.addEventListener("touchstart", playAudio);
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
// ============================================
|
||||
// bg.js - Optimized with WebM support
|
||||
// ============================================
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const bgs = [
|
||||
"/data/fond/moon_1080p.webm", // Changed to WebM
|
||||
"/data/fond/astro.webp", // Changed to WebP
|
||||
"/data/fond/Pino.png",
|
||||
];
|
||||
|
||||
const bgIndex = Math.floor(Date.now() / (1000 * 60 * 60 * 24)) % bgs.length;
|
||||
|
||||
let visibilityHandler = null;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const src = bgs[bgIndex];
|
||||
const video = document.getElementById("bg-video");
|
||||
const img = document.getElementById("bg-image");
|
||||
|
||||
if (!video || !img) return;
|
||||
|
||||
const isVideo = /\.(mp4|webm|ogg)$/i.test(src);
|
||||
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
// Reduced motion → force image with WebP fallback
|
||||
if (prefersReducedMotion && isVideo) {
|
||||
const fallback = src.replace(/\.(mp4|webm|ogg)$/i, ".webp");
|
||||
img.src = fallback;
|
||||
img.style.display = "block";
|
||||
video.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVideo) {
|
||||
video.src = src;
|
||||
video.style.display = "block";
|
||||
img.style.display = "none";
|
||||
|
||||
// Visibility handler for pause/play
|
||||
visibilityHandler = function() {
|
||||
if (document.hidden) {
|
||||
video.pause();
|
||||
} else {
|
||||
video.play().catch(e => console.log('Video play prevented:', e));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', visibilityHandler);
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (visibilityHandler) {
|
||||
document.removeEventListener('visibilitychange', visibilityHandler);
|
||||
}
|
||||
video.pause();
|
||||
video.src = '';
|
||||
});
|
||||
|
||||
video.load();
|
||||
} else {
|
||||
img.src = src;
|
||||
img.style.display = "block";
|
||||
video.style.display = "none";
|
||||
video.pause();
|
||||
video.src = '';
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,18 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const motdMessage = window.motds[window.motdIndex];
|
||||
const targetMessage = "Linux c'est mieux que tout, si quelqu'un pointe les problèmes de compatibilité, c'est la faute des devs des logiciels.";
|
||||
|
||||
if (motdMessage === targetMessage) {
|
||||
// Create a div with an ID
|
||||
const linkTarget = document.createElement("div");
|
||||
linkTarget.id = "linux-motd";
|
||||
linkTarget.textContent = "Special MOTD section!";
|
||||
document.getElementById("links-container").appendChild(linkTarget);
|
||||
|
||||
// Create an anchor linking to it
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = "#linux-motd";
|
||||
anchor.textContent = "Go to special MOTD";
|
||||
document.getElementById("links-container").appendChild(anchor);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// ============================================
|
||||
// captcha.js - Optimized
|
||||
// ============================================
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const captchas = [
|
||||
"secret/captcha2.html",
|
||||
"secret/captcha.html",
|
||||
];
|
||||
|
||||
const captchaIndex = Math.floor(Date.now() / (1000 * 60 * 60 * 24)) % captchas.length;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const captchaLink = document.getElementById("captchas");
|
||||
if (captchaLink) {
|
||||
captchaLink.href = captchas[captchaIndex];
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,41 @@
|
||||
window.addEventListener('load', () => {
|
||||
const cookieBtn = document.createElement('button');
|
||||
cookieBtn.textContent = 'Accept Cookies 🍪';
|
||||
cookieBtn.className = 'cookie-btn';
|
||||
|
||||
cookieBtn.style.position = 'fixed';
|
||||
cookieBtn.style.bottom = '20px';
|
||||
cookieBtn.style.right = '20px';
|
||||
cookieBtn.style.zIndex = '9999';
|
||||
|
||||
document.body.appendChild(cookieBtn);
|
||||
|
||||
cookieBtn.addEventListener('click', () => {
|
||||
for (let i = 0; i < 100; i++) createCookie();
|
||||
});
|
||||
|
||||
function createCookie() {
|
||||
const cookie = document.createElement('div');
|
||||
cookie.textContent = '🍪';
|
||||
|
||||
Object.assign(cookie.style, {
|
||||
position: 'fixed',
|
||||
fontSize: `${Math.random() * 30 + 20}px`,
|
||||
left: `${Math.random() * window.innerWidth}px`,
|
||||
top: `-${Math.random() * 50}px`,
|
||||
zIndex: '9999',
|
||||
pointerEvents: 'none',
|
||||
transition: 'transform 5s linear, opacity 5s linear'
|
||||
});
|
||||
|
||||
document.body.appendChild(cookie);
|
||||
|
||||
setTimeout(() => {
|
||||
cookie.style.transform =
|
||||
`translateY(${window.innerHeight + 50}px) rotate(${Math.random() * 360}deg)`;
|
||||
cookie.style.opacity = '0';
|
||||
}, 50);
|
||||
|
||||
setTimeout(() => cookie.remove(), 5500);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,388 @@
|
||||
/**
|
||||
* Sparklines & Easter Egg - Nixiews Dashboard
|
||||
* Ajoute des mini-graphiques dans les stat-cards existantes
|
||||
* + easter egg clavier dans le loader
|
||||
* + lecture du fichier server.status (remplace news.json)
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// =========================================================
|
||||
// CONFIG
|
||||
// =========================================================
|
||||
const MAX_POINTS = 20;
|
||||
const CANVAS_H = 36;
|
||||
|
||||
const COLOR_OK = '#50fa7b';
|
||||
const COLOR_WARN = '#ffb86c';
|
||||
const COLOR_DANGER = '#ff5555';
|
||||
const COLOR_LINE = 'rgba(98,114,164,0.35)';
|
||||
const COLOR_FILL_OK = 'rgba(80,250,123,0.12)';
|
||||
|
||||
const TRACKED = [
|
||||
{
|
||||
id: 'cpu',
|
||||
max: 100,
|
||||
warn: 50,
|
||||
danger: 80,
|
||||
parse: v => parseFloat(v),
|
||||
},
|
||||
{
|
||||
id: 'ram',
|
||||
max: null,
|
||||
maxId: 'ram-subtitle',
|
||||
warn: 60,
|
||||
danger: 80,
|
||||
parse: (v, sub) => {
|
||||
const m = sub && sub.match(/\((\d+\.?\d*)%\)/);
|
||||
return m ? parseFloat(m[1]) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'load',
|
||||
max: 8,
|
||||
warn: 50,
|
||||
danger: 80,
|
||||
parse: v => Math.min((parseFloat(v) / 8) * 100, 100),
|
||||
},
|
||||
];
|
||||
|
||||
const history = {};
|
||||
TRACKED.forEach(t => { history[t.id] = []; });
|
||||
|
||||
// =========================================================
|
||||
// CANVAS / SPARKLINES
|
||||
// =========================================================
|
||||
function injectCanvas(statCard) {
|
||||
if (statCard.querySelector('.spark-canvas')) return;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.className = 'spark-canvas';
|
||||
canvas.width = statCard.offsetWidth || 150;
|
||||
canvas.height = CANVAS_H;
|
||||
canvas.style.cssText = `
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: ${CANVAS_H}px;
|
||||
margin-top: 0.5rem;
|
||||
border-radius: 6px;
|
||||
opacity: 0.85;
|
||||
`;
|
||||
statCard.appendChild(canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function drawSparkline(canvas, data, warn, danger) {
|
||||
if (!canvas || data.length < 2) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
const W = canvas.offsetWidth || canvas.width;
|
||||
const H = canvas.height;
|
||||
canvas.width = W;
|
||||
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
const range = 100;
|
||||
const stepX = W / (MAX_POINTS - 1);
|
||||
const xOf = i => i * stepX;
|
||||
const yOf = v => H - ((v / range) * (H - 4)) - 2;
|
||||
|
||||
const last = data[data.length - 1];
|
||||
const color = last > danger ? COLOR_DANGER : last > warn ? COLOR_WARN : COLOR_OK;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xOf(0), H);
|
||||
data.forEach((v, i) => ctx.lineTo(xOf(i), yOf(v)));
|
||||
ctx.lineTo(xOf(data.length - 1), H);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = last > danger
|
||||
? 'rgba(255,85,85,0.10)'
|
||||
: last > warn
|
||||
? 'rgba(255,184,108,0.10)'
|
||||
: COLOR_FILL_OK;
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
data.forEach((v, i) => {
|
||||
i === 0 ? ctx.moveTo(xOf(i), yOf(v)) : ctx.lineTo(xOf(i), yOf(v));
|
||||
});
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.stroke();
|
||||
|
||||
const lx = xOf(data.length - 1);
|
||||
const ly = yOf(last);
|
||||
ctx.beginPath();
|
||||
ctx.arc(lx, ly, 3, 0, Math.PI * 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
|
||||
[50, 80].forEach(pct => {
|
||||
const gy = yOf(pct);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, gy);
|
||||
ctx.lineTo(W, gy);
|
||||
ctx.strokeStyle = COLOR_LINE;
|
||||
ctx.lineWidth = 0.8;
|
||||
ctx.setLineDash([3, 4]);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
});
|
||||
}
|
||||
|
||||
function sample() {
|
||||
TRACKED.forEach(t => {
|
||||
const el = document.getElementById(t.id);
|
||||
const subEl = document.getElementById(t.id + '-subtitle');
|
||||
if (!el) return;
|
||||
|
||||
const raw = el.textContent.trim();
|
||||
const sub = subEl ? subEl.textContent.trim() : '';
|
||||
const val = t.parse(raw, sub);
|
||||
|
||||
if (val === null || isNaN(val)) return;
|
||||
|
||||
history[t.id].push(val);
|
||||
if (history[t.id].length > MAX_POINTS) history[t.id].shift();
|
||||
|
||||
const card = el.closest('.stat-card');
|
||||
if (!card) return;
|
||||
|
||||
let canvas = card.querySelector('.spark-canvas');
|
||||
if (!canvas) canvas = injectCanvas(card);
|
||||
if (!canvas) return;
|
||||
|
||||
drawSparkline(canvas, history[t.id], t.warn, t.danger);
|
||||
});
|
||||
}
|
||||
|
||||
function watchStats() {
|
||||
const section = document.querySelector('.stats-section');
|
||||
if (!section) return;
|
||||
|
||||
const obs = new MutationObserver(() => sample());
|
||||
obs.observe(section, { subtree: true, characterData: true, childList: true });
|
||||
setTimeout(sample, 6000);
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// EASTER EGG
|
||||
// =========================================================
|
||||
const EASTER_COMMANDS = {
|
||||
'sudo': '⚠ [sudo] password for nixiews: \n💀 sudo: permission refusée. T\'es pas root ici.',
|
||||
'rm -rf /': '💀 SIGTERM — Au revoir cruel monde... \n ... nan je déconne, j\'ai pas les droits.',
|
||||
'rm -rf': '💀 Haha non. Pas sur mon serveur.',
|
||||
'emerge': '🟢 emerge: Calcul du dépôt world...\n ETA: 3 jours, 14 heures, 7 minutes.\n (C\'est Gentoo, t\'avais qu\'à pas.)',
|
||||
'nixos-rebuild': '❓ nixos-rebuild: command not found\n Ici c\'est Gentoo/Debian. Le pseudo c\'est juste un pseudo.',
|
||||
'pacman': '🔴 erreur: pacman not found. Ici c\'est Gentoo/Debian.',
|
||||
'apt': '🟡 apt: command not found (on Gentoo side)\n Essaie emerge plutôt.',
|
||||
'reboot': '♻ Reboot programmé dans... nan, j\'ai changé d\'avis.',
|
||||
'uname': '🐧 Linux nicoleta 6.x.x-gentoo #1 SMP PREEMPT_DYNAMIC\n x86_64 GNU/Linux',
|
||||
'htop': '📊 htop: trop stylé pour être lancé dans un easter egg.',
|
||||
'ls': '📁 . .. index.html data/ secret/ binpkg/ fun/ minecraft/',
|
||||
'cat /etc/passwd': '😏 root:x:0:0::/root:/bin/bash\n nixiews:x:1000:1000::/home/nixiews:/bin/zsh\n claude:x:9999:9999:meilleur ami:/dev/null:/bin/sh',
|
||||
'help': '📖 Commandes disponibles:\n sudo, rm -rf /, emerge, nixos-rebuild, pacman, apt,\n reboot, uname, htop, ls, cat /etc/passwd, help\n (Nixiews = pseudo, pas une distro 🙃)',
|
||||
};
|
||||
|
||||
let typedBuffer = '';
|
||||
let eggTimeout = null;
|
||||
|
||||
function resetBuffer() { typedBuffer = ''; }
|
||||
|
||||
function checkEasterEgg(key) {
|
||||
const loader = document.getElementById('loader');
|
||||
if (!loader || loader.style.display === 'none' || loader.style.opacity === '0') return;
|
||||
|
||||
typedBuffer += key.toLowerCase();
|
||||
if (typedBuffer.length > 30) typedBuffer = typedBuffer.slice(-30);
|
||||
|
||||
clearTimeout(eggTimeout);
|
||||
eggTimeout = setTimeout(resetBuffer, 2500);
|
||||
|
||||
for (const [cmd, response] of Object.entries(EASTER_COMMANDS)) {
|
||||
if (typedBuffer.endsWith(cmd)) {
|
||||
triggerEgg(cmd, response);
|
||||
typedBuffer = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function triggerEgg(cmd, response) {
|
||||
const logs = document.querySelector('#loader .logs');
|
||||
if (!logs) return;
|
||||
|
||||
const cmdLine = document.createElement('div');
|
||||
cmdLine.style.cssText = `
|
||||
color: #8be9fd;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.3rem;
|
||||
animation: fadeIn 0.2s ease;
|
||||
`;
|
||||
cmdLine.innerHTML = `<span style="color:#50fa7b">nixiews@nicoleta</span><span style="color:#f8f8f2">:</span><span style="color:#bd93f9">~</span><span style="color:#f8f8f2">$</span> ${cmd}`;
|
||||
logs.appendChild(cmdLine);
|
||||
|
||||
setTimeout(() => {
|
||||
const responseLine = document.createElement('div');
|
||||
responseLine.style.cssText = `
|
||||
color: #f1fa8c;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.82rem;
|
||||
white-space: pre-wrap;
|
||||
margin-bottom: 0.3rem;
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
responseLine.textContent = response;
|
||||
logs.appendChild(responseLine);
|
||||
logs.scrollTop = logs.scrollHeight;
|
||||
}, 300);
|
||||
|
||||
logs.scrollTop = logs.scrollHeight;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// SERVER STATUS — lecture directe du .status
|
||||
// =========================================================
|
||||
const STATUS_ENDPOINT = '/data/server.status';
|
||||
|
||||
function parseStatusFile(text) {
|
||||
const result = { status: '', couleur: '#bd93f9', date: '', message: '', motds: [] };
|
||||
const lines = text.split('\n');
|
||||
for (const line of lines) {
|
||||
const m = line.match(/^\[(\w+)\]\s*(.*)$/);
|
||||
if (!m) continue;
|
||||
const [, key, val] = m;
|
||||
if (key === 'motd') {
|
||||
result.motds.push(val.trim());
|
||||
} else {
|
||||
result[key] = val.trim();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildStatusCard(data) {
|
||||
const motdsHtml = data.motds.map(m =>
|
||||
`<div class="status-motd">💬 ${m}</div>`
|
||||
).join('');
|
||||
|
||||
return `
|
||||
<div class="status-header">
|
||||
<span class="status-badge" style="color:${data.couleur};border-color:${data.couleur}20;background:${data.couleur}15">
|
||||
● ${data.status}
|
||||
</span>
|
||||
${data.date ? `<span class="status-date">${data.date}</span>` : ''}
|
||||
</div>
|
||||
${data.message ? `<p class="status-message">${data.message}</p>` : ''}
|
||||
${motdsHtml ? `<div class="status-motds">${motdsHtml}</div>` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
function injectStatusSection() {
|
||||
const mainContent = document.querySelector('.main-content');
|
||||
const otherSection = document.querySelector('.other-section');
|
||||
if (!mainContent) return null;
|
||||
|
||||
const section = document.createElement('div');
|
||||
section.className = 'news-section';
|
||||
section.innerHTML = `
|
||||
<h2>Infos du jour</h2>
|
||||
<div class="news-card">
|
||||
<div class="news-loading">
|
||||
<div class="mc-loading-dot"></div>
|
||||
<div class="mc-loading-dot"></div>
|
||||
<div class="mc-loading-dot"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (otherSection) {
|
||||
mainContent.insertBefore(section, otherSection);
|
||||
} else {
|
||||
mainContent.appendChild(section);
|
||||
}
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.news-section h2 { color: #bd93f9; font-size: 1.5rem; margin-bottom: 1rem; }
|
||||
.news-card {
|
||||
background-color: rgba(68,71,90,0.95);
|
||||
border-radius: 12px;
|
||||
padding: 1.4rem 1.6rem;
|
||||
border-left: 3px solid #bd93f9;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
.news-card:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.4); }
|
||||
.status-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.8rem; }
|
||||
.status-badge {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
padding: 0.2rem 0.7rem;
|
||||
border-radius: 20px;
|
||||
border: 1px solid;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.status-date { font-size: 0.78rem; color: #6272a4; font-family: 'Courier New', monospace; margin-left: auto; }
|
||||
.status-message { color: #f8f8f2; font-size: 0.95rem; margin-bottom: 0.9rem; line-height: 1.5; }
|
||||
.status-motds { display: flex; flex-direction: column; gap: 0.4rem; }
|
||||
.status-motd {
|
||||
font-size: 0.85rem;
|
||||
color: #6272a4;
|
||||
padding: 0.3rem 0.6rem;
|
||||
background: rgba(40,42,54,0.6);
|
||||
border-radius: 6px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
.news-loading { display: flex; align-items: center; justify-content: center; gap: 0.4rem; padding: 0.6rem 0; }
|
||||
.status-error { color: #ff5555; font-size: 0.85rem; font-family: 'Courier New', monospace; }
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
return section.querySelector('.news-card');
|
||||
}
|
||||
|
||||
async function fetchStatus(card) {
|
||||
try {
|
||||
const res = await fetch(STATUS_ENDPOINT, { cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const text = await res.text();
|
||||
card.innerHTML = buildStatusCard(parseStatusFile(text));
|
||||
} catch (e) {
|
||||
card.innerHTML = `<span class="status-error">⚠ Impossible de charger server.status (${e.message})</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function initStatus() {
|
||||
const card = injectStatusSection();
|
||||
if (card) fetchStatus(card);
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// INIT
|
||||
// =========================================================
|
||||
function init() {
|
||||
document.addEventListener('keypress', e => {
|
||||
if (e.key && e.key.length === 1) checkEasterEgg(e.key);
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') checkEasterEgg(' ');
|
||||
});
|
||||
|
||||
if (document.querySelector('.stats-section')) {
|
||||
watchStats();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', watchStats);
|
||||
}
|
||||
|
||||
initStatus();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,19 @@
|
||||
// script normal
|
||||
// change d'image tout les jours
|
||||
const images = [
|
||||
"autre/Image1.html",
|
||||
"autre/Image2.html",
|
||||
"autre/Image3.html",
|
||||
"autre/Image4.html",
|
||||
"autre/Image5.html",
|
||||
"autre/Image6.html",
|
||||
"autre/Image7.html",
|
||||
"autre/Image8.html",
|
||||
"autre/Image9.html",
|
||||
]
|
||||
|
||||
const imageIndex = Math.floor(Date.now() / (1000 * 60 * 60 * 24)) % images.length
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.getElementById("image").href = images[imageIndex]
|
||||
})
|
||||
@@ -0,0 +1,315 @@
|
||||
/* =========================
|
||||
CONFIG
|
||||
========================= */
|
||||
const MIN_TIME = 10000;
|
||||
const startTime = performance.now();
|
||||
|
||||
/* =========================
|
||||
HELPERS
|
||||
========================= */
|
||||
function getTimeStamp() {
|
||||
return new Date().toLocaleTimeString("fr-FR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit"
|
||||
});
|
||||
}
|
||||
|
||||
/* =========================
|
||||
SEQUENTIAL TEXT + FROZEN TIME
|
||||
========================= */
|
||||
const steps = document.querySelectorAll(".step");
|
||||
|
||||
steps.forEach((el, i) => {
|
||||
setTimeout(() => {
|
||||
const timeEl = el.querySelector(".time");
|
||||
if (timeEl) timeEl.textContent = getTimeStamp();
|
||||
el.classList.add("visible");
|
||||
}, i * 600);
|
||||
});
|
||||
|
||||
/* =========================
|
||||
RANDOM GRID / PIXEL ART
|
||||
========================= */
|
||||
const grid = document.getElementById("grid");
|
||||
let gridSquares = []; // Track for cleanup
|
||||
let pixelIntervals = []; // Track pixel intervals for cleanup
|
||||
|
||||
if (grid) {
|
||||
const squareCount = Math.floor(Math.random() * 6) + 8;
|
||||
const occupiedCells = new Set(); // Track occupied grid positions
|
||||
|
||||
for (let i = 0; i < squareCount; i++) {
|
||||
const square = document.createElement("div");
|
||||
square.className = "square";
|
||||
|
||||
// Find an unoccupied position
|
||||
let col, row, posKey;
|
||||
let attempts = 0;
|
||||
do {
|
||||
col = Math.floor(Math.random() * 5) + 1;
|
||||
row = Math.floor(Math.random() * 4) + 1;
|
||||
posKey = `${col}-${row}`;
|
||||
attempts++;
|
||||
} while (occupiedCells.has(posKey) && attempts < 50);
|
||||
|
||||
// Mark position as occupied
|
||||
occupiedCells.add(posKey);
|
||||
|
||||
square.style.gridColumn = col;
|
||||
square.style.gridRow = row;
|
||||
// Make squares visible immediately without animation
|
||||
square.style.opacity = "1";
|
||||
square.style.transform = "none";
|
||||
|
||||
// Draw pixels on hover (can be triggered multiple times)
|
||||
const drawHandler = () => {
|
||||
const canvas = square.querySelector("canvas");
|
||||
if (!canvas || !square.hasPixels) {
|
||||
drawPixels(square);
|
||||
if (!square.pixelInterval) {
|
||||
startPixelCycle(square); // Start the 5-second cycle for this square
|
||||
}
|
||||
}
|
||||
};
|
||||
square.addEventListener("pointerenter", drawHandler);
|
||||
|
||||
gridSquares.push(square);
|
||||
grid.appendChild(square);
|
||||
}
|
||||
}
|
||||
|
||||
function drawPixels(el) {
|
||||
const existingCanvas = el.querySelector("canvas");
|
||||
if (existingCanvas) {
|
||||
existingCanvas.remove();
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = el.clientWidth;
|
||||
canvas.height = el.clientHeight;
|
||||
canvas.style.position = "absolute";
|
||||
canvas.style.top = "0";
|
||||
canvas.style.left = "0";
|
||||
|
||||
el.appendChild(canvas);
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
const colors = ["#ff4fd8", "#9333ea", "#ef4444"];
|
||||
const px = 6;
|
||||
|
||||
// Fill background
|
||||
ctx.fillStyle = "#0a0a0c";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Collect all pixel positions that will be drawn
|
||||
const pixels = [];
|
||||
for (let x = 0; x < canvas.width; x += px) {
|
||||
for (let y = 0; y < canvas.height; y += px) {
|
||||
if (Math.random() > 0.6) {
|
||||
pixels.push({
|
||||
x: x,
|
||||
y: y,
|
||||
color: colors[Math.floor(Math.random() * colors.length)]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark that this square has pixels
|
||||
el.hasPixels = true;
|
||||
|
||||
// Create an offscreen canvas to draw all pixels at once
|
||||
const offscreenCanvas = document.createElement("canvas");
|
||||
offscreenCanvas.width = canvas.width;
|
||||
offscreenCanvas.height = canvas.height;
|
||||
const offscreenCtx = offscreenCanvas.getContext("2d");
|
||||
|
||||
// Draw all pixels on offscreen canvas
|
||||
offscreenCtx.fillStyle = "#0a0a0c";
|
||||
offscreenCtx.fillRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
|
||||
pixels.forEach(pixel => {
|
||||
offscreenCtx.fillStyle = pixel.color;
|
||||
offscreenCtx.fillRect(pixel.x, pixel.y, px, px);
|
||||
});
|
||||
|
||||
// Animate the entire pixel pattern sliding in from left
|
||||
const duration = 400; // ms for animation
|
||||
const startTime = performance.now();
|
||||
const startX = -canvas.width; // Start completely off-screen to the left
|
||||
const endX = 0;
|
||||
|
||||
function animate(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// Clear canvas
|
||||
ctx.fillStyle = "#0a0a0c";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Calculate current X position
|
||||
const currentX = startX + (endX - startX) * progress;
|
||||
|
||||
// Draw the entire offscreen canvas at the current position
|
||||
ctx.drawImage(offscreenCanvas, currentX, 0);
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function clearPixels(el) {
|
||||
const canvas = el.querySelector("canvas");
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Create an offscreen canvas with current pixel state
|
||||
const offscreenCanvas = document.createElement("canvas");
|
||||
offscreenCanvas.width = canvas.width;
|
||||
offscreenCanvas.height = canvas.height;
|
||||
const offscreenCtx = offscreenCanvas.getContext("2d");
|
||||
|
||||
// Copy current canvas to offscreen
|
||||
offscreenCtx.drawImage(canvas, 0, 0);
|
||||
|
||||
// Animate the entire pixel pattern sliding out to the right
|
||||
const duration = 400; // ms for animation
|
||||
const startTime = performance.now();
|
||||
const startX = 0;
|
||||
const endX = canvas.width; // Slide completely off-screen to the right
|
||||
|
||||
function animate(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// Clear canvas
|
||||
ctx.fillStyle = "#0a0a0c";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Calculate current X position
|
||||
const currentX = startX + (endX - startX) * progress;
|
||||
|
||||
// Draw the entire offscreen canvas at the current position
|
||||
ctx.drawImage(offscreenCanvas, currentX, 0);
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
} else {
|
||||
// Final clear
|
||||
ctx.fillStyle = "#0a0a0c";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function startPixelCycle(square) {
|
||||
const interval = setInterval(() => {
|
||||
clearPixels(square);
|
||||
square.hasPixels = false; // Mark that pixels are cleared
|
||||
}, 5000);
|
||||
|
||||
pixelIntervals.push(interval);
|
||||
|
||||
// Store interval on the element for later cleanup
|
||||
square.pixelInterval = interval;
|
||||
}
|
||||
|
||||
|
||||
/* =========================
|
||||
LOADER CLEANUP
|
||||
========================= */
|
||||
function cleanupLoader() {
|
||||
// Clear all intervals
|
||||
pixelIntervals.forEach(interval => clearInterval(interval));
|
||||
pixelIntervals = [];
|
||||
|
||||
// Remove grid squares
|
||||
gridSquares.forEach(sq => sq.remove());
|
||||
gridSquares = [];
|
||||
|
||||
// Remove loader
|
||||
const loader = document.getElementById("loader");
|
||||
if (loader) {
|
||||
loader.style.opacity = "0";
|
||||
loader.style.pointerEvents = "none";
|
||||
|
||||
setTimeout(() => {
|
||||
loader.remove();
|
||||
}, 800);
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================
|
||||
LOADER HANDOFF
|
||||
========================= */
|
||||
function hideLoader() {
|
||||
const elapsed = performance.now() - startTime;
|
||||
const remaining = Math.max(0, MIN_TIME - elapsed);
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove("loading");
|
||||
cleanupLoader(); // Clean up properly
|
||||
}, remaining);
|
||||
}
|
||||
|
||||
window.addEventListener("load", hideLoader);
|
||||
|
||||
/* =========================
|
||||
LOADING BAR - OPTIMIZED
|
||||
========================= */
|
||||
const loadingBar = document.getElementById('loading-bar');
|
||||
let currentLoadingInterval = null;
|
||||
|
||||
function showLoadingBar() {
|
||||
// Clear any existing interval first
|
||||
if (currentLoadingInterval) {
|
||||
clearInterval(currentLoadingInterval);
|
||||
}
|
||||
|
||||
loadingBar.style.width = '0%';
|
||||
loadingBar.style.display = 'block';
|
||||
|
||||
setTimeout(() => loadingBar.style.width = '30%', 50);
|
||||
|
||||
let progress = 30;
|
||||
currentLoadingInterval = setInterval(() => {
|
||||
progress += Math.random() * 20;
|
||||
if (progress < 90) {
|
||||
loadingBar.style.width = progress + '%';
|
||||
} else {
|
||||
clearInterval(currentLoadingInterval);
|
||||
currentLoadingInterval = null;
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return currentLoadingInterval;
|
||||
}
|
||||
|
||||
function completeLoadingBar(interval) {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
if (interval === currentLoadingInterval) {
|
||||
currentLoadingInterval = null;
|
||||
}
|
||||
}
|
||||
loadingBar.style.width = '100%';
|
||||
setTimeout(() => {
|
||||
loadingBar.style.width = '0%';
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// Initial page load
|
||||
let pageLoadInterval;
|
||||
if (document.readyState === 'loading') {
|
||||
pageLoadInterval = showLoadingBar();
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
completeLoadingBar(pageLoadInterval);
|
||||
});
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Minecraft Server Status - Sidebar Widget
|
||||
* Source : API Crafty Controller (proxy PHP crafty-stats.php)
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const API = '/data/api/crafty-stats.php';
|
||||
const MODS_API = '/data/api/mods-mc.php';
|
||||
const INTERVAL = 30000; // 30s
|
||||
|
||||
const SERVERS = [
|
||||
{ key: 'mc1', cardId: 'mc-card-0' },
|
||||
{ key: 'mc2', cardId: 'mc-card-1' },
|
||||
];
|
||||
|
||||
let timer = null;
|
||||
let modsData = null; // cache des mods, chargé une seule fois
|
||||
|
||||
// ── Mods ────────────────────────────────────────────────────────────────
|
||||
|
||||
const modsStyle = document.createElement('style');
|
||||
modsStyle.textContent = `
|
||||
.mc-mods-section {
|
||||
margin-top: 0.5rem;
|
||||
border-top: 1px solid rgba(98, 114, 164, 0.3);
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
.mc-mods-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0;
|
||||
user-select: none;
|
||||
}
|
||||
.mc-mods-toggle-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
color: #6272a4;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
}
|
||||
.mc-mods-toggle-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.68rem;
|
||||
color: #6272a4;
|
||||
}
|
||||
.mc-mods-arrow {
|
||||
font-size: 0.6rem;
|
||||
color: #6272a4;
|
||||
transition: transform 0.2s;
|
||||
display: inline-block;
|
||||
}
|
||||
.mc-mods-arrow.open { transform: rotate(90deg); }
|
||||
.mc-mods-pills {
|
||||
display: none;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
padding-top: 0.45rem;
|
||||
}
|
||||
.mc-mods-pills.open { display: flex; }
|
||||
.mc-mod-pill {
|
||||
background: rgba(98, 114, 164, 0.25);
|
||||
color: #f8f8f2;
|
||||
font-size: 0.65rem;
|
||||
padding: 2px 7px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(98, 114, 164, 0.35);
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.mc-mod-pill:hover {
|
||||
background: rgba(189, 147, 249, 0.2);
|
||||
border-color: rgba(189, 147, 249, 0.5);
|
||||
color: #bd93f9;
|
||||
}
|
||||
.mc-mods-error {
|
||||
font-size: 0.7rem;
|
||||
color: #ff5555;
|
||||
padding-top: 0.3rem;
|
||||
font-style: italic;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(modsStyle);
|
||||
|
||||
async function fetchMods() {
|
||||
try {
|
||||
const res = await fetch(MODS_API, { cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
modsData = await res.json();
|
||||
} catch (e) {
|
||||
console.warn('[mods-mc] Erreur fetch :', e);
|
||||
}
|
||||
}
|
||||
|
||||
function appendMods(card, srv) {
|
||||
if (!modsData) return;
|
||||
|
||||
// trouve l'entrée correspondant au nom du serveur Crafty
|
||||
const entry = modsData.find(m => m.label === srv?.name) ?? modsData[SERVERS.findIndex(s => s.cardId === card.id)];
|
||||
if (!entry) return;
|
||||
|
||||
const section = document.createElement('div');
|
||||
section.className = 'mc-mods-section';
|
||||
|
||||
if (entry.error) {
|
||||
const err = document.createElement('div');
|
||||
err.className = 'mc-mods-error';
|
||||
err.textContent = entry.error;
|
||||
section.appendChild(err);
|
||||
card.appendChild(section);
|
||||
return;
|
||||
}
|
||||
|
||||
const count = entry.mods.length;
|
||||
const toggle = document.createElement('div');
|
||||
toggle.className = 'mc-mods-toggle';
|
||||
toggle.innerHTML = `
|
||||
<span class="mc-mods-toggle-label">Mods</span>
|
||||
<span class="mc-mods-toggle-meta">
|
||||
<span>${count} mod${count !== 1 ? 's' : ''}</span>
|
||||
<span class="mc-mods-arrow">▸</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
const pills = document.createElement('div');
|
||||
pills.className = 'mc-mods-pills';
|
||||
entry.mods.forEach(mod => {
|
||||
const pill = document.createElement('span');
|
||||
pill.className = 'mc-mod-pill';
|
||||
pill.title = mod.file;
|
||||
pill.textContent = mod.name;
|
||||
pills.appendChild(pill);
|
||||
});
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
pills.classList.toggle('open');
|
||||
toggle.querySelector('.mc-mods-arrow').classList.toggle('open');
|
||||
});
|
||||
|
||||
section.appendChild(toggle);
|
||||
section.appendChild(pills);
|
||||
card.appendChild(section);
|
||||
}
|
||||
|
||||
// ── Status ───────────────────────────────────────────────────────────────
|
||||
|
||||
async function fetchAll() {
|
||||
let data;
|
||||
try {
|
||||
const res = await fetch(API, { cache: 'no-cache' });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
data = await res.json();
|
||||
} catch (e) {
|
||||
console.error('crafty-stats error:', e);
|
||||
return;
|
||||
}
|
||||
if (!data.success) return;
|
||||
|
||||
SERVERS.forEach(({ key, cardId }) => {
|
||||
const card = document.getElementById(cardId);
|
||||
if (!card) return;
|
||||
renderCard(card, data.servers?.[key]);
|
||||
});
|
||||
|
||||
updateStatCards(data.servers);
|
||||
}
|
||||
|
||||
function renderCard(card, srv) {
|
||||
if (!srv) {
|
||||
card.innerHTML = `
|
||||
<div class="mc-header">
|
||||
<span class="mc-name">Serveur inconnu</span>
|
||||
<span class="mc-badge mc-offline">● Hors ligne</span>
|
||||
</div>`;
|
||||
appendMods(card, srv);
|
||||
return;
|
||||
}
|
||||
|
||||
const online = srv.running;
|
||||
const players = online ? `${srv.players} / ${srv.max_players}` : '— / —';
|
||||
|
||||
let ramHtml = '';
|
||||
if (online && srv.ram_used !== null) {
|
||||
const usedStr = ramFmt(srv.ram_used);
|
||||
const allocStr = srv.ram_alloc ? ramFmt(srv.ram_alloc) : '?';
|
||||
const pct = srv.ram_alloc > 0 ? Math.round((srv.ram_used / srv.ram_alloc) * 100) : 0;
|
||||
const color = pct > 90 ? '#ff5555' : pct > 70 ? '#ffb86c' : '#50fa7b';
|
||||
ramHtml = `
|
||||
<span class="mc-ram" style="color:${color}">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" style="color:#bd93f9;flex-shrink:0">
|
||||
<path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/>
|
||||
</svg>
|
||||
${usedStr}<span style="color:#6272a4;font-weight:400"> / ${allocStr}</span>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
const cpuHtml = (online && srv.cpu !== null)
|
||||
? `<span class="mc-version">${srv.cpu}% CPU</span>`
|
||||
: '';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="mc-header">
|
||||
<span class="mc-name">${esc(srv.name)}</span>
|
||||
<span class="mc-badge ${online ? 'mc-online' : 'mc-offline'}">${online ? '● En ligne' : '● Hors ligne'}</span>
|
||||
</div>
|
||||
<div class="mc-address">${esc('nix.roulaise.net')}:${srv.port}</div>
|
||||
<div class="mc-info">
|
||||
<span class="mc-players">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
|
||||
</svg>
|
||||
${players} joueurs
|
||||
</span>
|
||||
${ramHtml}
|
||||
${cpuHtml}
|
||||
</div>`;
|
||||
|
||||
appendMods(card, srv);
|
||||
}
|
||||
|
||||
function updateStatCards(servers) {
|
||||
['mc1', 'mc2'].forEach((key, i) => {
|
||||
const n = i + 1;
|
||||
const el = document.getElementById(`mc${n}-ram`);
|
||||
const sub = document.getElementById(`mc${n}-ram-subtitle`);
|
||||
if (!el) return;
|
||||
|
||||
const srv = servers?.[key];
|
||||
if (!srv || !srv.running) {
|
||||
el.textContent = 'Éteint';
|
||||
if (sub) sub.textContent = '—';
|
||||
el.style.color = '#6272a4';
|
||||
return;
|
||||
}
|
||||
if (srv.ram_used !== null) {
|
||||
const pct = srv.ram_alloc > 0 ? Math.round((srv.ram_used / srv.ram_alloc) * 100) : 0;
|
||||
el.textContent = ramFmt(srv.ram_used);
|
||||
if (sub) sub.textContent = `alloué: ${srv.ram_alloc ? ramFmt(srv.ram_alloc) : '?'} (${pct}%)`;
|
||||
el.style.color = pct > 90 ? '#ff5555' : pct > 70 ? '#ffb86c' : '#50fa7b';
|
||||
} else {
|
||||
el.textContent = '...';
|
||||
if (sub) sub.textContent = '—';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ramFmt(mio) {
|
||||
return mio >= 1024 ? (mio / 1024).toFixed(1) + ' Gio' : mio + ' Mio';
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function start() { timer = setInterval(fetchAll, INTERVAL); }
|
||||
function stop() { clearInterval(timer); timer = null; }
|
||||
|
||||
async function init() {
|
||||
if (!document.getElementById('mc-card-0')) return;
|
||||
await fetchMods(); // charge les mods une seule fois au démarrage
|
||||
fetchAll();
|
||||
start();
|
||||
window.addEventListener('beforeunload', stop);
|
||||
document.addEventListener('visibilitychange', () =>
|
||||
document.hidden ? stop() : (fetchAll(), start())
|
||||
);
|
||||
}
|
||||
|
||||
document.readyState === 'loading'
|
||||
? document.addEventListener('DOMContentLoaded', init)
|
||||
: init();
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,128 @@
|
||||
(function () {
|
||||
const API_URL = '/data/api/mods-mc.php';
|
||||
|
||||
// Injecte le CSS dans <head>
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.mc-mods-section {
|
||||
margin-top: 0.5rem;
|
||||
border-top: 1px solid rgba(98, 114, 164, 0.3);
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
.mc-mods-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0;
|
||||
user-select: none;
|
||||
}
|
||||
.mc-mods-toggle-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
color: #6272a4;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
}
|
||||
.mc-mods-toggle-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.68rem;
|
||||
color: #6272a4;
|
||||
}
|
||||
.mc-mods-arrow {
|
||||
font-size: 0.6rem;
|
||||
color: #6272a4;
|
||||
transition: transform 0.2s;
|
||||
display: inline-block;
|
||||
}
|
||||
.mc-mods-arrow.open { transform: rotate(90deg); }
|
||||
.mc-mods-pills {
|
||||
display: none;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
padding-top: 0.45rem;
|
||||
}
|
||||
.mc-mods-pills.open { display: flex; }
|
||||
.mc-mod-pill {
|
||||
background: rgba(98, 114, 164, 0.25);
|
||||
color: #f8f8f2;
|
||||
font-size: 0.65rem;
|
||||
padding: 2px 7px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(98, 114, 164, 0.35);
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.mc-mod-pill:hover {
|
||||
background: rgba(189, 147, 249, 0.2);
|
||||
border-color: rgba(189, 147, 249, 0.5);
|
||||
color: #bd93f9;
|
||||
}
|
||||
.mc-mods-error {
|
||||
font-size: 0.7rem;
|
||||
color: #ff5555;
|
||||
padding-top: 0.3rem;
|
||||
font-style: italic;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
function buildModsBlock(srv, cardEl) {
|
||||
const section = document.createElement('div');
|
||||
section.className = 'mc-mods-section';
|
||||
|
||||
if (srv.error) {
|
||||
const err = document.createElement('div');
|
||||
err.className = 'mc-mods-error';
|
||||
err.textContent = srv.error;
|
||||
section.appendChild(err);
|
||||
cardEl.appendChild(section);
|
||||
return;
|
||||
}
|
||||
|
||||
const count = srv.mods.length;
|
||||
const toggle = document.createElement('div');
|
||||
toggle.className = 'mc-mods-toggle';
|
||||
toggle.innerHTML = `
|
||||
<span class="mc-mods-toggle-label">Mods</span>
|
||||
<span class="mc-mods-toggle-meta">
|
||||
<span>${count} mod${count !== 1 ? 's' : ''}</span>
|
||||
<span class="mc-mods-arrow">▸</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
const pills = document.createElement('div');
|
||||
pills.className = 'mc-mods-pills';
|
||||
srv.mods.forEach(mod => {
|
||||
const pill = document.createElement('span');
|
||||
pill.className = 'mc-mod-pill';
|
||||
pill.title = mod.file;
|
||||
pill.textContent = mod.name;
|
||||
pills.appendChild(pill);
|
||||
});
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
const arrow = toggle.querySelector('.mc-mods-arrow');
|
||||
pills.classList.toggle('open');
|
||||
arrow.classList.toggle('open');
|
||||
});
|
||||
|
||||
section.appendChild(toggle);
|
||||
section.appendChild(pills);
|
||||
cardEl.appendChild(section);
|
||||
}
|
||||
|
||||
fetch(API_URL)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
data.forEach((srv, i) => {
|
||||
const card = document.getElementById('mc-card-' + i);
|
||||
if (card) buildModsBlock(srv, card);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn('[mods-mc] Erreur fetch :', err);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,41 @@
|
||||
// ============================================
|
||||
// motd.js - Optimized
|
||||
// ============================================
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const motds = [
|
||||
"Gloire à Titebouille!",
|
||||
"On dit chocolatine ici!",
|
||||
"C'est un MOTD ça?",
|
||||
"Je vais vous ciscoter!",
|
||||
"Belgian gooner femboy ahhh",
|
||||
"Miku cosplay UwU",
|
||||
"Nothing to see",
|
||||
"If it's stupid but works, then it's not stupid",
|
||||
"Wait, you're playing Unturned?!",
|
||||
"Revert to Aquaba!",
|
||||
"Cherche GOTHIQUES svp :(",
|
||||
"Vous m'ouvrez Packet Tracer.",
|
||||
"Gooner Land",
|
||||
"QT va te faire foutre",
|
||||
"J'aime l'informatique vu que je suis obligé",
|
||||
"Allo à l'huile",
|
||||
"On à cours dans le local technique",
|
||||
"Je suis pas con, je suis autiste",
|
||||
"Toni aime bien penser que son café est italien.",
|
||||
"C'est une violation de la loi Sherman",
|
||||
"Le kernel cisco packet ryzen dragon s24+ c'est pour quand ?",
|
||||
"Hacheur - HkmGZ",
|
||||
"Met un PEKKA! Nan, CT3000+L!",
|
||||
];
|
||||
|
||||
const motdIndex = Math.floor(Date.now() / (1000 * 60 * 60 * 24)) % motds.length;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const motdElement = document.getElementById("motd");
|
||||
if (motdElement) {
|
||||
motdElement.textContent = motds[motdIndex];
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1 @@
|
||||
console.log('Bienvenue sur le site le plus inutile du monde!');
|
||||
@@ -0,0 +1,30 @@
|
||||
// ============================================
|
||||
// ri.js - Optimized (keeping GIFs as GIF)
|
||||
// ============================================
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const ris = [
|
||||
"data/media/linux-tux.gif", // Optimized GIF
|
||||
"data/media/Titebouille.gif", // Optimized GIF
|
||||
"data/media/gurl1.gif", // Optimized GIF
|
||||
"data/media/gurl2.gif", // Optimized GIF
|
||||
"data/media/gurl3.gif", // Optimized GIF
|
||||
"data/media/Dance1.gif", // Optimized GIF
|
||||
"data/media/Duck2.webp", // WebP (was PNG)
|
||||
"data/media/Honk.gif", // Optimized GIF
|
||||
"data/media/petpet.gif", // Optimized GIF
|
||||
"data/media/Ereaser.jpg",
|
||||
];
|
||||
|
||||
const riIndex = Math.floor(Date.now() / (1000 * 60 * 60 * 24)) % ris.length;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const riElement = document.getElementById("ris");
|
||||
if (riElement) {
|
||||
// Set loading attribute for better performance
|
||||
riElement.loading = "lazy";
|
||||
riElement.src = ris[riIndex];
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* System Stats Loader — inclut MDADM
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const API_STATS = '/data/api/system-stats.php';
|
||||
const API_MDADM = '/data/api/mdadm-status.php';
|
||||
const INTERVAL = 5000;
|
||||
|
||||
let timer = null;
|
||||
let updating = false;
|
||||
let loadedOnce = false;
|
||||
|
||||
const el = {
|
||||
uptime: document.getElementById('uptime'),
|
||||
cpu: document.getElementById('cpu'),
|
||||
ram: document.getElementById('ram'),
|
||||
ramSub: document.getElementById('ram-subtitle'),
|
||||
diskRoot: document.getElementById('disk-root'),
|
||||
diskRootSub: document.getElementById('disk-root-subtitle'),
|
||||
diskVar: document.getElementById('disk-var'),
|
||||
diskVarSub: document.getElementById('disk-var-subtitle'),
|
||||
diskSrv: document.getElementById('disk-srv'),
|
||||
diskSrvSub: document.getElementById('disk-srv-subtitle'),
|
||||
load: document.getElementById('load'),
|
||||
processes: document.getElementById('processes'),
|
||||
mdadmCard: document.getElementById('mdadm-card'),
|
||||
mdadmStatus: document.getElementById('mdadm-status'),
|
||||
mdadmSub: document.getElementById('mdadm-subtitle'),
|
||||
};
|
||||
|
||||
function set(element, value) {
|
||||
if (!element || value === undefined) return;
|
||||
const v = String(value);
|
||||
if (element.textContent !== v) element.textContent = v;
|
||||
}
|
||||
|
||||
function color(element, percent, warn, danger) {
|
||||
if (!element) return;
|
||||
const c = percent > danger ? '#ff5555' : percent > warn ? '#ffb86c' : '#50fa7b';
|
||||
if (element.style.color !== c) element.style.color = c;
|
||||
}
|
||||
|
||||
async function fetchStats() {
|
||||
if (updating) return;
|
||||
updating = true;
|
||||
try {
|
||||
const r = await fetch(API_STATS, { cache: 'no-cache' });
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const { success, data } = await r.json();
|
||||
if (success && data) renderStats(data);
|
||||
else if (loadedOnce) showError();
|
||||
} catch(e) {
|
||||
console.error('system-stats:', e);
|
||||
if (loadedOnce) showError();
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
function renderStats(s) {
|
||||
loadedOnce = true;
|
||||
|
||||
set(el.uptime, s.uptime);
|
||||
|
||||
if (s.cpu_usage !== undefined) {
|
||||
set(el.cpu, s.cpu_usage + '%');
|
||||
color(el.cpu, s.cpu_usage, 50, 80);
|
||||
}
|
||||
|
||||
if (s.ram_used !== undefined) {
|
||||
set(el.ram, s.ram_used + ' Gio');
|
||||
set(el.ramSub, `sur ${s.ram_total} Gio (${s.ram_percent}%)`);
|
||||
color(el.ram, s.ram_percent, 60, 80);
|
||||
}
|
||||
|
||||
if (s.disk_root_free !== undefined) {
|
||||
set(el.diskRoot, s.disk_root_free + ' Gio');
|
||||
set(el.diskRootSub, `sur ${s.disk_root_total} Gio (${s.disk_root_percent}% utilisé)`);
|
||||
color(el.diskRoot, s.disk_root_percent, 70, 90);
|
||||
}
|
||||
|
||||
if (s.disk_var_free !== undefined) {
|
||||
set(el.diskVar, s.disk_var_free + ' Gio');
|
||||
set(el.diskVarSub, `sur ${s.disk_var_total} Gio (${s.disk_var_percent}% utilisé)`);
|
||||
color(el.diskVar, s.disk_var_percent, 70, 90);
|
||||
}
|
||||
|
||||
if (s.disk_srv_free !== undefined) {
|
||||
set(el.diskSrv, s.disk_srv_free + ' Gio');
|
||||
set(el.diskSrvSub, `sur ${s.disk_srv_total} Gio (${s.disk_srv_percent}% utilisé)`);
|
||||
color(el.diskSrv, s.disk_srv_percent, 70, 90);
|
||||
}
|
||||
|
||||
if (s.load_1 !== undefined) {
|
||||
set(el.load, s.load_1);
|
||||
color(el.load, (s.load_1 / 4) * 100, 50, 80);
|
||||
}
|
||||
|
||||
set(el.processes, s.processes);
|
||||
}
|
||||
|
||||
async function fetchMdadm() {
|
||||
try {
|
||||
const r = await fetch(API_MDADM, { cache: 'no-cache' });
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const d = await r.json();
|
||||
|
||||
if (d.healthy) {
|
||||
set(el.mdadmStatus, '✓ Sain');
|
||||
if (el.mdadmStatus) el.mdadmStatus.style.color = '#50fa7b';
|
||||
set(el.mdadmSub, d.arrays.map(a => `${a.name} [${a.bitmap}]`).join(' · '));
|
||||
if (el.mdadmCard) el.mdadmCard.style.borderTop = '';
|
||||
} else {
|
||||
set(el.mdadmStatus, '⚠ Dégradé');
|
||||
if (el.mdadmStatus) el.mdadmStatus.style.color = '#ff5555';
|
||||
set(el.mdadmSub, d.arrays
|
||||
.filter(a => a.health !== 'ok')
|
||||
.map(a => `${a.name}: ${a.detail || a.health}`)
|
||||
.join(' · '));
|
||||
if (el.mdadmCard) el.mdadmCard.style.borderTop = '2px solid #ff5555';
|
||||
}
|
||||
} catch(e) {
|
||||
set(el.mdadmStatus, '?');
|
||||
if (el.mdadmStatus) el.mdadmStatus.style.color = '#ffb86c';
|
||||
set(el.mdadmSub, 'erreur fetch');
|
||||
}
|
||||
}
|
||||
|
||||
function showError() {
|
||||
['uptime','cpu','ram','diskRoot','diskVar','diskSrv','load','processes'].forEach(k => {
|
||||
if (el[k]) { el[k].textContent = 'Erreur'; el[k].style.color = '#ff5555'; }
|
||||
});
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
await fetchStats();
|
||||
await fetchMdadm();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (timer) { clearInterval(timer); timer = null; }
|
||||
}
|
||||
|
||||
function start() {
|
||||
stop();
|
||||
timer = setInterval(refresh, INTERVAL);
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (!document.querySelector('.stats-section')) return;
|
||||
refresh();
|
||||
start();
|
||||
window.addEventListener('beforeunload', stop);
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) stop();
|
||||
else { refresh(); start(); }
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
||||
else init();
|
||||
|
||||
})();
|
||||
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,224 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- (used for SSR) -->
|
||||
|
||||
<meta name="description" content="1 shared photos & videos" />
|
||||
|
||||
<!-- Facebook Meta Tags -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Public Share" />
|
||||
<meta property="og:description" content="1 shared photos & videos" />
|
||||
<meta property="og:image" content="https://immich.nix.roulaise.net/api/assets/c9f559b2-e6e7-4c25-b82c-2311833755c9/thumbnail?key=e6xKBo19ZjbUGxU_h_vv4nBEwS_XWdW9UN5OTHQ5C4Q_rTqltVgwLXUiwd98tY3Iofs" />
|
||||
|
||||
<!-- Twitter Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Public Share" />
|
||||
<meta name="twitter:description" content="1 shared photos & videos" />
|
||||
|
||||
<meta name="twitter:image" content="https://immich.nix.roulaise.net/api/assets/c9f559b2-e6e7-4c25-b82c-2311833755c9/thumbnail?key=e6xKBo19ZjbUGxU_h_vv4nBEwS_XWdW9UN5OTHQ5C4Q_rTqltVgwLXUiwd98tY3Iofs" />
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48.png" />
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96.png" />
|
||||
<link rel="icon" type="image/png" sizes="144x144" href="/favicon-144.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180.png" />
|
||||
<link rel="preload" as="font" type="font/ttf" href="/_app/immutable/assets/GoogleSans.DGRbB7N7.ttf" crossorigin="anonymous" />
|
||||
<link rel="preload" as="font" type="font/ttf" href="/_app/immutable/assets/GoogleSansCode.a41q3NA0.ttf" crossorigin="anonymous" />
|
||||
|
||||
<link rel="modulepreload" href="/_app/immutable/entry/start.RpA_pNyV.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/DlDBckNA.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/CSizKli0.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/yCQ_fqx4.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/DIeogL5L.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/94Xz9ZHe.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/BUApaBEI.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/entry/app.CepPnjFj.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/BOCziLuX.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/BP6NvnZ7.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/7tRQeUtl.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/CSfvng8n.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/DsnmJJEf.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/BzhrqIL4.js">
|
||||
<link rel="modulepreload" href="/_app/immutable/chunks/DpNthyPY.js">
|
||||
<link rel="modulepreload" href="/_app/env.js">
|
||||
<style>
|
||||
/* prevent FOUC */
|
||||
html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@keyframes delayedVisibility {
|
||||
to {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loadspin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#stencil {
|
||||
--stencil-width: 150px;
|
||||
display: flex;
|
||||
width: var(--stencil-width);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: calc(50vh - var(--stencil-width) / 2);
|
||||
margin-bottom: 100vh;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
animation:
|
||||
0s linear 0.3s forwards delayedVisibility,
|
||||
loadspin 8s linear infinite;
|
||||
}
|
||||
|
||||
.bg-immich-bg {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.dark .dark\:bg-immich-dark-bg {
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
/**
|
||||
* Prevent FOUC on page load.
|
||||
*/
|
||||
const colorThemeKeyName = 'color-theme';
|
||||
|
||||
let theme = localStorage.getItem(colorThemeKeyName);
|
||||
if (!theme) {
|
||||
theme = { value: 'light', system: true };
|
||||
} else if (theme === 'dark' || theme === 'light') {
|
||||
theme = { value: theme, system: false };
|
||||
localStorage.setItem(colorThemeKeyName, JSON.stringify(theme));
|
||||
} else {
|
||||
theme = JSON.parse(theme);
|
||||
}
|
||||
|
||||
let themeValue = theme.value;
|
||||
if (theme.system) {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
themeValue = 'dark';
|
||||
} else {
|
||||
themeValue = 'light';
|
||||
}
|
||||
}
|
||||
|
||||
if (themeValue === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="/custom.css" />
|
||||
</head>
|
||||
|
||||
<noscript
|
||||
class="absolute z-1000 flex h-screen w-screen place-content-center place-items-center bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg"
|
||||
>
|
||||
To use Immich, you must enable JavaScript or use a JavaScript compatible browser.
|
||||
</noscript>
|
||||
|
||||
<body class="bg-light text-dark">
|
||||
<div id="stencil">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 792 792">
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #fa2921;
|
||||
}
|
||||
.st1 {
|
||||
fill: #ed79b5;
|
||||
}
|
||||
.st2 {
|
||||
fill: #ffb400;
|
||||
}
|
||||
.st3 {
|
||||
fill: #1e83f7;
|
||||
}
|
||||
.st4 {
|
||||
fill: #18c249;
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<path
|
||||
class="st0"
|
||||
d="M375.48,267.63c38.64,34.21,69.78,70.87,89.82,105.42c34.42-61.56,57.42-134.71,57.71-181.3
|
||||
c0-0.33,0-0.63,0-0.91c0-68.94-68.77-95.77-128.01-95.77s-128.01,26.83-128.01,95.77c0,0.94,0,2.2,0,3.72
|
||||
C300.01,209.24,339.15,235.47,375.48,267.63z"
|
||||
/>
|
||||
<path
|
||||
class="st1"
|
||||
d="M164.7,455.63c24.15-26.87,61.2-55.99,103.01-80.61c44.48-26.18,88.97-44.47,128.02-52.84
|
||||
c-47.91-51.76-110.37-96.24-154.6-110.91c-0.31-0.1-0.6-0.19-0.86-0.28c-65.57-21.3-112.34,35.81-130.64,92.15
|
||||
c-18.3,56.34-14.04,130.04,51.53,151.34C162.05,454.77,163.25,455.16,164.7,455.63z"
|
||||
/>
|
||||
<path
|
||||
class="st2"
|
||||
d="M681.07,302.19c-18.3-56.34-65.07-113.45-130.64-92.15c-0.9,0.29-2.1,0.68-3.54,1.15
|
||||
c-3.75,35.93-16.6,81.27-35.96,125.76c-20.59,47.32-45.84,88.27-72.51,118c69.18,13.72,145.86,12.98,190.26-1.14
|
||||
c0.31-0.1,0.6-0.2,0.86-0.28C695.11,432.22,699.37,358.52,681.07,302.19z"
|
||||
/>
|
||||
<path
|
||||
class="st3"
|
||||
d="M336.54,510.71c-11.15-50.39-14.8-98.36-10.7-138.08c-64.03,29.57-125.63,75.23-153.26,112.76
|
||||
c-0.19,0.26-0.37,0.51-0.53,0.73c-40.52,55.78-0.66,117.91,47.27,152.72c47.92,34.82,119.33,53.54,159.86-2.24
|
||||
c0.56-0.76,1.3-1.78,2.19-3.01C363.28,602.32,347.02,558.08,336.54,510.71z"
|
||||
/>
|
||||
<path
|
||||
class="st4"
|
||||
d="M617.57,482.52c-35.33,7.54-82.42,9.33-130.72,4.66c-51.37-4.96-98.11-16.32-134.63-32.5
|
||||
c8.33,70.03,32.73,142.73,59.88,180.6c0.19,0.26,0.37,0.51,0.53,0.73c40.52,55.78,111.93,37.06,159.86,2.24
|
||||
c47.92-34.82,87.79-96.95,47.27-152.72C619.2,484.77,618.46,483.75,617.57,482.52z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<script>
|
||||
{
|
||||
__sveltekit_1gheimp = {
|
||||
base: "",
|
||||
env: null
|
||||
};
|
||||
|
||||
const element = document.currentScript.parentElement;
|
||||
|
||||
import("/_app/env.js").then(({ env }) => {
|
||||
__sveltekit_1gheimp.env = env;
|
||||
|
||||
Promise.all([
|
||||
import("/_app/immutable/entry/start.RpA_pNyV.js"),
|
||||
import("/_app/immutable/entry/app.CepPnjFj.js")
|
||||
]).then(([kit, app]) => {
|
||||
kit.start(app, element);
|
||||
});
|
||||
});
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('/service-worker.js');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 25 MiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 7.7 MiB |
|
After Width: | Height: | Size: 6.9 MiB |
|
After Width: | Height: | Size: 5.4 MiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 55 KiB |
@@ -0,0 +1,7 @@
|
||||
[status] Tout va bien
|
||||
[couleur] #50fa7b
|
||||
[date] 04/04/2026
|
||||
[message] Status du serveur actuel :
|
||||
[motd] HDD de 4To en place, partitions finies (je crois)
|
||||
[motd] J'ai le RAID 10 le plus bordélique possible au monde!
|
||||
[motd] J'adore le bordel que je fout et corige pas :O
|
||||
@@ -0,0 +1,267 @@
|
||||
/* =============================== Font ================================ */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@400;700&display=swap');
|
||||
|
||||
/* =============================== Reset ================================ */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* =============================== Body ================================ */
|
||||
body {
|
||||
font-family: 'Comfortaa', cursive;
|
||||
color: #f8f8f2;
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 2rem;
|
||||
background: transparent;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* =============================== Background Media ================================ */
|
||||
#bg-video,
|
||||
#bg-image {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
object-fit: cover;
|
||||
z-index: -2;
|
||||
pointer-events: none;
|
||||
display: none; /* JS decides which one is visible */
|
||||
}
|
||||
|
||||
/* Dark overlay for readability */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(40, 42, 54, 0.55);
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* =============================== Main Container ================================ */
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ============================== Descriptions de cases ============================== */
|
||||
.description {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
color: #6272a4;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* =============================== Header ================================ */
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #bd93f9;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* =============================== Cards ================================ */
|
||||
.card {
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
color: #f8f8f2;
|
||||
padding: 1.5rem 2rem;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0.7rem 0;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
background-color: #6272a4;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Make links fill card */
|
||||
.card a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* =============================== Images ================================ */
|
||||
.img {
|
||||
max-height: 200px;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* =============================== Forms ================================ */
|
||||
form {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.6rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
background-color: #6272a4;
|
||||
color: #f8f8f2;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
select:focus {
|
||||
background-color: #50fa7b;
|
||||
color: #282a36;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* =============================== Buttons ================================ */
|
||||
button {
|
||||
background-color: #bd93f9;
|
||||
color: #282a36;
|
||||
font-family: inherit;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
padding: 0.7rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #ff79c6;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* =============================== Footer ================================ */
|
||||
footer {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
color: #6272a4;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* =============================== PHP Directory Index Helpers ================================ */
|
||||
.card .file-type {
|
||||
display: block;
|
||||
margin-top: 0.3rem;
|
||||
font-size: 0.9rem;
|
||||
color: #bd93f9;
|
||||
}
|
||||
|
||||
/* =============================== Responsive ================================ */
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.card,
|
||||
form {
|
||||
font-size: 1.1rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================== Cookies ================================ */
|
||||
.cookie-btn {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
button.cookie-btn {
|
||||
background-color: #bd93f9 !important;
|
||||
color: #282a36 !important;
|
||||
}
|
||||
|
||||
button.cookie-btn:hover {
|
||||
background-color: #ff79c6 !important;
|
||||
}
|
||||
|
||||
/* =============================== Reduced Motion ================================ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#bg-video {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#bg-image {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================= Barre de chargement ============================= */
|
||||
#loading-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #ff69b4, #ff1493);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 0 10px rgba(255, 105, 180, 0.6);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/* =========================
|
||||
LOADER STYLES
|
||||
========================= */
|
||||
#loader {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: #0a0a0c;
|
||||
color: #c7c7d1;
|
||||
display: flex;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
z-index: 9999;
|
||||
opacity: 1;
|
||||
transition: opacity 0.8s ease;
|
||||
}
|
||||
|
||||
.loader-left {
|
||||
width: 48%;
|
||||
padding: 80px 60px;
|
||||
}
|
||||
|
||||
.ascii.big {
|
||||
font-size: 18px;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 2px;
|
||||
color: #e2e2ff;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 30px;
|
||||
font-size: 13px;
|
||||
opacity: 0.6;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.logs {
|
||||
margin-top: 40px;
|
||||
font-size: 12px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.logs div {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.logs .ok {
|
||||
opacity: 1;
|
||||
color: #b6b6ff;
|
||||
}
|
||||
|
||||
.loader-right {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-auto-rows: 140px;
|
||||
gap: 30px;
|
||||
padding: 80px;
|
||||
}
|
||||
|
||||
.square {
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
position: relative;
|
||||
overflow: hidden; /* Ensures pixels stay within bounds */
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.square canvas {
|
||||
display: block; /* Removes any spacing issues */
|
||||
}
|
||||
|
||||
/* Remove slide animations - squares stay static */
|
||||
.square.slide-in,
|
||||
.square.slide-out {
|
||||
/* No animation */
|
||||
}
|
||||
|
||||
.time {
|
||||
opacity: 0.5;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.step {
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
transition: opacity 0.4s ease, transform 0.4s ease;
|
||||
}
|
||||
|
||||
.step.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* =========================
|
||||
SMOOTH CROSSFADE
|
||||
========================= */
|
||||
/* Main content starts invisible */
|
||||
.container,
|
||||
footer,
|
||||
#bg-video,
|
||||
#bg-image {
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease;
|
||||
}
|
||||
|
||||
/* When loading class is removed, content fades in */
|
||||
body:not(.loading) .container,
|
||||
body:not(.loading) footer,
|
||||
body:not(.loading) #bg-video,
|
||||
body:not(.loading) #bg-image {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Hide main site until loader starts fading */
|
||||
body.loading .container,
|
||||
body.loading footer,
|
||||
body.loading #bg-video,
|
||||
body.loading #bg-image {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Block scroll bar while loading */
|
||||
body.loading,
|
||||
html:has(body.loading) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.loading {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
MOBILE / RESPONSIVE
|
||||
========================= */
|
||||
@media (max-width: 600px) {
|
||||
.loader-left {
|
||||
padding: 10px 0 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.loader-left .ascii.big {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
font-size: 10px;
|
||||
letter-spacing: 1px;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loader-left .subtitle {
|
||||
margin: 5px 0 0 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.logs {
|
||||
font-size: 10px;
|
||||
margin: 5px 0 0 0;
|
||||
}
|
||||
|
||||
.logs div {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.loader-right {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-auto-rows: 100px;
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.square {
|
||||
border-width: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very small screens: hide pixel grid */
|
||||
@media (max-width: 400px) {
|
||||
.loader-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/* =============================== Dashboard Specific Styles ================================ */
|
||||
|
||||
/* Dashboard Layout */
|
||||
.dashboard-wrapper {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 350px 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
/* Fix for image card */
|
||||
.card-with-image {
|
||||
padding: 1rem;
|
||||
min-height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-with-image .img {
|
||||
max-height: 200px;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Stats Section */
|
||||
.stats-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-section h2 {
|
||||
color: #bd93f9;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: rgba(68, 71, 90, 0.95);
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
background-color: #6272a4;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.95rem;
|
||||
color: #6272a4;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
color: #50fa7b;
|
||||
}
|
||||
|
||||
.stat-subtitle {
|
||||
font-size: 0.85rem;
|
||||
color: #6272a4;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
/* Other Links Section */
|
||||
.other-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.other-section h2 {
|
||||
color: #bd93f9;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.other-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.small-card {
|
||||
padding: 1rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-wrapper {
|
||||
grid-template-columns: 300px 1fr;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.dashboard-wrapper {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.other-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Nix Storage Portal</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: "Fira Code", monospace;
|
||||
background: #282a36;
|
||||
color: #f8f8f2;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
header {
|
||||
background: #44475a;
|
||||
padding: 40px 20px;
|
||||
font-size: 2em;
|
||||
color: #ff79c6;
|
||||
}
|
||||
.container {
|
||||
padding: 50px;
|
||||
}
|
||||
a.button {
|
||||
display: inline-block;
|
||||
padding: 15px 30px;
|
||||
margin: 15px;
|
||||
background: #6272a4;
|
||||
color: #f8f8f2;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-size: 1.2em;
|
||||
transition: 0.2s;
|
||||
}
|
||||
a.button:hover {
|
||||
background: #bd93f9;
|
||||
color: #fff;
|
||||
}
|
||||
footer {
|
||||
margin-top: 50px;
|
||||
font-size: 0.9em;
|
||||
color: #6272a4;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>🍪 Nix Storage Portal</header>
|
||||
<div class="container">
|
||||
<p>Welcome to your secure storage interface.</p>
|
||||
<a href="filemanager/index.php" class="button">📂 File Manager</a>
|
||||
<a href="about.html" class="button">ℹ️ About</a>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Powered by PHP · Dracula Theme · Nix Cookie</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||