377 lines
12 KiB
HTML
377 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>SCP:SL Server Status</title>
|
|
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
|
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
:root {
|
|
--primary-bg-color: rgba(0, 0, 0, 0.3);
|
|
--text-color: #e0e0e0;
|
|
--highlight-color: #00ff00;
|
|
--error-color: #ff4444;
|
|
--border-color: rgba(255, 255, 255, 0.08);
|
|
--shadow-color: rgba(0, 0, 0, 0.6);
|
|
--angle: 90deg;
|
|
--warning-color: #ffcc00;
|
|
}
|
|
|
|
body {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
background: linear-gradient(180deg, #07131c, #305472);
|
|
font-family: 'Roboto', sans-serif;
|
|
color: var(--text-color);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
position: relative;
|
|
}
|
|
|
|
.server-info-card {
|
|
position: relative;
|
|
z-index: 10;
|
|
text-align: center;
|
|
background-color: var(--primary-bg-color);
|
|
padding: 40px 70px;
|
|
border-radius: 16px;
|
|
box-shadow: 0 10px 32px var(--shadow-color);
|
|
max-width: 98%;
|
|
width: 1000px;
|
|
border: 1px solid var(--border-color);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
transition: transform 0.5s ease-out, opacity 0.5s ease-out;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.server-info-card.hidden {
|
|
transform: translateY(20px);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
#server-name {
|
|
font-size: 3.125em;
|
|
margin-bottom: 18px;
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
letter-spacing: 0.5px;
|
|
text-transform: none;
|
|
animation: fadeIn 1.5s ease-in-out forwards;
|
|
text-shadow: 1px 1px 3px var(--shadow-color);
|
|
word-break: break-word;
|
|
}
|
|
|
|
#server-name span {
|
|
display: inline-block;
|
|
}
|
|
|
|
#player-count {
|
|
font-size: 2.75em;
|
|
margin-top: 18px;
|
|
font-weight: 700;
|
|
color: var(--highlight-color);
|
|
transition: color 0.3s ease-in-out;
|
|
text-shadow: 1px 1px 3px var(--shadow-color);
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
.loading-message {
|
|
font-size: 1.75em;
|
|
color: var(--text-color);
|
|
animation: pulse 1.5s infinite alternate;
|
|
}
|
|
|
|
.error-message {
|
|
font-size: 1.75em;
|
|
color: var(--error-color);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.warning-message {
|
|
font-size: 1.75em;
|
|
color: var(--warning-color);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.rain {
|
|
position: absolute;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
z-index: 0;
|
|
top: 0;
|
|
left: 0;
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.drop {
|
|
border: 0.25vmin solid transparent;
|
|
border-bottom-color: #abc2e9;
|
|
position: absolute;
|
|
top: -5vmin;
|
|
width: 0;
|
|
height: 0;
|
|
animation: fall var(--drop-duration) var(--drop-delay) linear infinite;
|
|
left: var(--drop-left);
|
|
border-left-width: var(--drop-width);
|
|
opacity: var(--drop-opacity);
|
|
transform: rotate(var(--angle));
|
|
transform-origin: 50% 100%;
|
|
}
|
|
|
|
@keyframes fall {
|
|
from {
|
|
transform: translateY(0) rotate(var(--angle));
|
|
}
|
|
to {
|
|
transform: translateY(calc(100vh + 10vmin)) rotate(var(--angle));
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(-10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes pulse {
|
|
from { opacity: 0.7; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
.graph-image-container {
|
|
width: 100%;
|
|
max-width: 900px;
|
|
height: auto;
|
|
background-color: rgba(0, 0, 0, 0.2);
|
|
border-radius: 22px;
|
|
padding: 32px;
|
|
box-sizing: border-box;
|
|
margin-top: 36px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.graph-image-container img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
display: block;
|
|
border-radius: 14px;
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
.server-info-card {
|
|
width: 98%;
|
|
padding: 25px 10px;
|
|
}
|
|
.graph-image-container {
|
|
max-width: 98vw;
|
|
padding: 10px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.server-info-card {
|
|
padding: 18px 2vw;
|
|
width: 99vw;
|
|
}
|
|
#server-name {
|
|
font-size: 2.25em;
|
|
}
|
|
#player-count {
|
|
font-size: 2.0em;
|
|
}
|
|
.loading-message, .error-message, .warning-message {
|
|
font-size: 1.25em;
|
|
}
|
|
.graph-image-container {
|
|
max-width: 100vw;
|
|
height: auto;
|
|
padding: 4vw;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="rain"></div>
|
|
|
|
<div class="server-info-card">
|
|
<div id="server-name" class="loading-message">Lade Servernamen...</div>
|
|
<div id="player-count" class="loading-message">Lade Spieleranzahl...</div>
|
|
<div id="global-error-message" class="error-message" style="display:none; margin-top: 10px;"></div>
|
|
|
|
<div class="graph-image-container">
|
|
<img id="playerHistoryGraph" src="/player_history_graph_img" alt="Spielerstatistik der letzten 24h">
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const numDrops = 500;
|
|
const rainContainer = document.querySelector('.rain');
|
|
const defaultAngle = 90;
|
|
let initialLoadDone = false;
|
|
|
|
for (let i = 0; i < numDrops; i++) {
|
|
const drop = document.createElement('div');
|
|
drop.classList.add('drop');
|
|
drop.style.setProperty('--drop-opacity', (Math.random() * 0.7) + 0.1);
|
|
drop.style.setProperty('--drop-left', (Math.random() * 100) + 'vw');
|
|
drop.style.setProperty('--drop-width', (Math.random() * 0.4) + 0.05 + 'vmin');
|
|
drop.style.setProperty('--drop-duration', (Math.random() * 1.5) + 1.5 + 's');
|
|
drop.style.setProperty('--drop-delay', (Math.random() * -3) + 's');
|
|
drop.style.setProperty('--angle', `${defaultAngle}deg`);
|
|
rainContainer.appendChild(drop);
|
|
}
|
|
|
|
function extractShortServerName(rawInfo) {
|
|
const nameMatch = rawInfo.match(/(<span style="color:#[a-fA-F0-9]{6}">\[G<\/span><span style="color:#[a-fA-F0-9]{6}">E<\/span><span style="color:#[a-fA-F0-9]{6}">R\]<\/span>)?\s*<b>(.*?)<\/b>(\s*<span style="color:#[a-fA-F0-9]{6}">\[#\d+\]<\/span>)?/);
|
|
|
|
if (nameMatch) {
|
|
let finalName = '';
|
|
if (nameMatch[1]) finalName += nameMatch[1] + ' ';
|
|
if (nameMatch[2]) finalName += nameMatch[2];
|
|
if (nameMatch[3]) finalName += nameMatch[3];
|
|
return finalName.split('<br')[0].trim();
|
|
}
|
|
return rawInfo.split('<br')[0].replace(/<[^>]*>/g, '').trim();
|
|
}
|
|
|
|
function updatePlayerHistoryGraph() {
|
|
const graphImage = document.getElementById('playerHistoryGraph');
|
|
graphImage.src = `/player_history_graph_img?t=${new Date().getTime()}`;
|
|
}
|
|
|
|
async function fetchServerData(isInitialCall = false) {
|
|
const apiUrl = '/api/scpsl_proxy';
|
|
const serverNameElement = document.getElementById('server-name');
|
|
const playerCountElement = document.getElementById('player-count');
|
|
const cardElement = document.querySelector('.server-info-card');
|
|
const globalErrorMessageElement = document.getElementById('global-error-message');
|
|
|
|
if (isInitialCall || !initialLoadDone || serverNameElement.classList.contains('error-message') || globalErrorMessageElement.style.display === 'block') {
|
|
serverNameElement.className = 'loading-message';
|
|
serverNameElement.innerHTML = 'Lade Servernamen...';
|
|
playerCountElement.className = 'loading-message';
|
|
playerCountElement.textContent = 'Lade Spieleranzahl...';
|
|
globalErrorMessageElement.style.display = 'none';
|
|
cardElement.classList.remove('hidden');
|
|
} else {
|
|
if (!playerCountElement.classList.contains('error-message')) {
|
|
playerCountElement.classList.add('loading-message');
|
|
}
|
|
}
|
|
|
|
const timeoutPromise = new Promise((resolve, reject) => {
|
|
const id = setTimeout(() => {
|
|
clearTimeout(id);
|
|
reject(new Error('Server response timed out (mehr als 5 Sekunden).'));
|
|
}, 5000);
|
|
});
|
|
|
|
try {
|
|
const response = await Promise.race([
|
|
fetch(apiUrl),
|
|
timeoutPromise
|
|
]);
|
|
|
|
if (!response.ok) {
|
|
let errorDetails = 'Unbekannter Fehler';
|
|
try {
|
|
const errorData = await response.json();
|
|
errorDetails = errorData.error || errorDetails;
|
|
} catch (e) {
|
|
errorDetails = response.statusText || errorDetails;
|
|
}
|
|
throw new Error(`Serverfehler: ${response.status} - ${errorDetails}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
let rawServerInfo = data.info || "Unbekannter Server";
|
|
|
|
const currentServerNameHTML = serverNameElement.innerHTML;
|
|
const newServerNameHTML = extractShortServerName(rawServerInfo);
|
|
if (isInitialCall || !initialLoadDone || serverNameElement.classList.contains('error-message') || currentServerNameHTML !== newServerNameHTML) {
|
|
serverNameElement.innerHTML = newServerNameHTML;
|
|
}
|
|
|
|
if (data.online) {
|
|
playerCountElement.textContent = `Spieler: ${data.players}`;
|
|
playerCountElement.style.color = 'var(--highlight-color)';
|
|
serverNameElement.classList.remove('loading-message', 'error-message', 'warning-message');
|
|
playerCountElement.classList.remove('loading-message', 'error-message', 'warning-message');
|
|
globalErrorMessageElement.style.display = 'none';
|
|
cardElement.classList.remove('hidden');
|
|
} else {
|
|
if (!serverNameElement.innerHTML.includes('[OFFLINE]')) {
|
|
serverNameElement.innerHTML = newServerNameHTML + '<br><span style="font-size:0.6em; color:var(--error-color);">[OFFLINE]</span>';
|
|
}
|
|
playerCountElement.textContent = 'Server ist offline.';
|
|
playerCountElement.style.color = 'var(--error-color)';
|
|
serverNameElement.classList.remove('loading-message', 'warning-message');
|
|
serverNameElement.classList.add('error-message');
|
|
playerCountElement.classList.remove('loading-message', 'warning-message');
|
|
playerCountElement.classList.add('error-message');
|
|
globalErrorMessageElement.style.display = 'none';
|
|
cardElement.classList.remove('hidden');
|
|
}
|
|
playerCountElement.classList.remove('loading-message');
|
|
initialLoadDone = true;
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Abrufen der Serverdaten:', error);
|
|
|
|
let errorMessage = 'Server konnte nicht erreicht werden.';
|
|
let errorSubtext = '<i>(Allgemeiner Fehler)</i>';
|
|
let errorClass = 'error-message';
|
|
|
|
if (error.message.includes('timed out')) {
|
|
errorMessage = 'Server-Antwort Timeout.';
|
|
errorSubtext = '<i>(Ladeversuch läuft im Hintergrund)</i>';
|
|
errorClass = 'warning-message';
|
|
} else if (error.message.includes('Serverfehler:')) {
|
|
errorMessage = 'Serverfehler.';
|
|
errorSubtext = `<i>(${error.message.split('Serverfehler: ')[1]})</i>`;
|
|
errorClass = 'error-message';
|
|
}
|
|
|
|
serverNameElement.className = errorClass;
|
|
serverNameElement.innerHTML = errorMessage + '<br><small>' + errorSubtext + '</small>';
|
|
|
|
playerCountElement.className = errorClass;
|
|
playerCountElement.textContent = 'Daten nicht verfügbar.';
|
|
|
|
globalErrorMessageElement.textContent = 'Aktualisierung fehlgeschlagen. Bitte versuche es später erneut.';
|
|
globalErrorMessageElement.style.display = 'block';
|
|
|
|
if (errorClass === 'error-message') {
|
|
cardElement.classList.add('hidden');
|
|
} else {
|
|
cardElement.classList.remove('hidden');
|
|
}
|
|
|
|
initialLoadDone = false;
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
fetchServerData(true);
|
|
updatePlayerHistoryGraph();
|
|
setInterval(fetchServerData, 5000);
|
|
setInterval(updatePlayerHistoryGraph, 60000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |