7 Commits
3.6.3 ... 3.6.4

6 changed files with 845 additions and 718 deletions

Binary file not shown.

View File

@@ -1,187 +1,187 @@
// background.js // background.js
const POLL_INTERVAL_SEC = 2; const POLL_INTERVAL_SEC = 2;
// Alarm erstellen // Alarm erstellen
chrome.runtime.onInstalled.addListener(() => { chrome.runtime.onInstalled.addListener(() => {
chrome.alarms.create('periodicRefresh', { periodInMinutes: POLL_INTERVAL_SEC / 60 }); chrome.alarms.create('periodicRefresh', { periodInMinutes: POLL_INTERVAL_SEC / 60 });
}); });
chrome.alarms.onAlarm.addListener((alarm) => { chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'periodicRefresh') { if (alarm.name === 'periodicRefresh') {
refreshAllServers().catch(console.error); refreshAllServers().catch(console.error);
} }
}); });
// Message Listener // Message Listener
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg && msg.cmd === 'refreshNow') { if (msg && msg.cmd === 'refreshNow') {
refreshAllServers() refreshAllServers()
.then(statuses => sendResponse({ ok: true, statuses })) .then(statuses => sendResponse({ ok: true, statuses }))
.catch(err => sendResponse({ ok: false, error: String(err) })); .catch(err => sendResponse({ ok: false, error: String(err) }));
return true; return true;
} }
}); });
async function refreshAllServers() { async function refreshAllServers() {
const obj = await chrome.storage.local.get('servers'); const obj = await chrome.storage.local.get('servers');
const servers = obj.servers || []; const servers = obj.servers || [];
if (servers.length === 0) { if (servers.length === 0) {
await chrome.storage.local.set({ serverStatuses: {} }); await chrome.storage.local.set({ serverStatuses: {} });
chrome.action.setBadgeText({ text: '' }); chrome.action.setBadgeText({ text: '' });
return {}; return {};
} }
const results = {}; const results = {};
let totalOnlinePlayers = 0; let totalOnlinePlayers = 0;
const tasks = servers.map(async (srv) => { const tasks = servers.map(async (srv) => {
try { try {
const data = await fetchServerStatus(srv, 2000); const data = await fetchServerStatus(srv, 2000);
results[srv.id] = { ok: true, server: srv, fetched: Date.now(), data }; results[srv.id] = { ok: true, server: srv, fetched: Date.now(), data };
if (data) { if (data) {
if (Array.isArray(data.players)) totalOnlinePlayers += data.players.length; if (Array.isArray(data.players)) totalOnlinePlayers += data.players.length;
else if (typeof data.players === 'number') totalOnlinePlayers += data.players; else if (typeof data.players === 'number') totalOnlinePlayers += data.players;
} }
} catch (e) { } catch (e) {
results[srv.id] = { ok: false, server: srv, fetched: Date.now(), error: String(e) }; results[srv.id] = { ok: false, server: srv, fetched: Date.now(), error: String(e) };
} }
}); });
await Promise.all(tasks); await Promise.all(tasks);
await chrome.storage.local.set({ serverStatuses: results }); await chrome.storage.local.set({ serverStatuses: results });
const badgeText = totalOnlinePlayers > 0 ? String(totalOnlinePlayers) : ''; const badgeText = totalOnlinePlayers > 0 ? String(totalOnlinePlayers) : '';
chrome.action.setBadgeText({ text: badgeText }); chrome.action.setBadgeText({ text: badgeText });
chrome.action.setBadgeBackgroundColor({ color: '#3b82f6' }); chrome.action.setBadgeBackgroundColor({ color: '#3b82f6' });
return results; return results;
} }
async function fetchServerStatus(server, timeoutMs = 2000) { async function fetchServerStatus(server, timeoutMs = 2000) {
let serverObj = typeof server === 'string' ? { id: 'legacy', url: server } : server; let serverObj = typeof server === 'string' ? { id: 'legacy', url: server } : server;
let fetchUrl = serverObj.url || ''; let fetchUrl = serverObj.url || '';
// 1. Direkte URL-Abfrage // 1. Direkte URL-Abfrage
if (fetchUrl) { if (fetchUrl) {
if (!/^https?:\/\//i.test(fetchUrl)) fetchUrl = 'http://' + fetchUrl; if (!/^https?:\/\//i.test(fetchUrl)) fetchUrl = 'http://' + fetchUrl;
try { try {
const startTime = performance.now(); const startTime = performance.now();
const controller = new AbortController(); const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeoutMs); const id = setTimeout(() => controller.abort(), timeoutMs);
const resp = await fetch(fetchUrl, { method: 'GET', signal: controller.signal }); const resp = await fetch(fetchUrl, { method: 'GET', signal: controller.signal });
clearTimeout(id); clearTimeout(id);
const endTime = performance.now(); const endTime = performance.now();
const latency = Math.round(endTime - startTime); const latency = Math.round(endTime - startTime);
if (resp.ok) { if (resp.ok) {
let text = await resp.text(); let text = await resp.text();
const lastBrace = text.lastIndexOf('}'); const lastBrace = text.lastIndexOf('}');
if (lastBrace !== -1) text = text.substring(0, lastBrace + 1); if (lastBrace !== -1) text = text.substring(0, lastBrace + 1);
text = text.trim(); text = text.trim();
try { try {
const parsed = JSON.parse(text); const parsed = JSON.parse(text);
if (parsed && (typeof parsed.online !== 'undefined' || Array.isArray(parsed.players) || parsed.version)) { if (parsed && (typeof parsed.online !== 'undefined' || Array.isArray(parsed.players) || parsed.version)) {
parsed.ping = latency; // Ping hinzufügen parsed.ping = latency; // Ping hinzufügen
return parsed; return parsed;
} }
} catch (e) { } catch (e) {
// Parsing fehlgeschlagen → Fallback-Objekt mit Ping // Parsing fehlgeschlagen → Fallback-Objekt mit Ping
return { return {
online: false, online: false,
players: [], players: [],
max_players: 0, max_players: 0,
version: 'unknown', version: 'unknown',
motd: '', motd: '',
ping: latency > timeoutMs ? null : latency // bei Timeout kein sinnvoller Ping ping: latency > timeoutMs ? null : latency // bei Timeout kein sinnvoller Ping
}; };
} }
} }
// HTTP nicht ok → Offline mit gemessener Latenz (falls unter Timeout) // HTTP nicht ok → Offline mit gemessener Latenz (falls unter Timeout)
return { return {
online: false, online: false,
players: [], players: [],
max_players: 0, max_players: 0,
version: 'unknown', version: 'unknown',
motd: '', motd: '',
ping: latency > timeoutMs ? null : latency ping: latency > timeoutMs ? null : latency
}; };
} catch (e) { } catch (e) {
// Timeout oder Netzwerkfehler // Timeout oder Netzwerkfehler
return { return {
online: false, online: false,
players: [], players: [],
max_players: 0, max_players: 0,
version: 'unknown', version: 'unknown',
motd: '', motd: '',
ping: null ping: null
}; };
} }
} }
// 2. WordPress AJAX-Abfrage // 2. WordPress AJAX-Abfrage
const wpSite = serverObj.wpSite ? String(serverObj.wpSite).replace(/\/$/, '') : null; const wpSite = serverObj.wpSite ? String(serverObj.wpSite).replace(/\/$/, '') : null;
const wpServerId = serverObj.wpServerId ? String(serverObj.wpServerId) : null; const wpServerId = serverObj.wpServerId ? String(serverObj.wpServerId) : null;
if (wpSite && wpServerId) { if (wpSite && wpServerId) {
try { try {
const ajaxUrl = wpSite + '/wp-admin/admin-ajax.php'; const ajaxUrl = wpSite + '/wp-admin/admin-ajax.php';
const body = 'action=mcss_fetch&server_id=' + encodeURIComponent(wpServerId); const body = 'action=mcss_fetch&server_id=' + encodeURIComponent(wpServerId);
const startTime = performance.now(); const startTime = performance.now();
const controller2 = new AbortController(); const controller2 = new AbortController();
const id2 = setTimeout(() => controller2.abort(), timeoutMs); const id2 = setTimeout(() => controller2.abort(), timeoutMs);
const resp2 = await fetch(ajaxUrl, { const resp2 = await fetch(ajaxUrl, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body, body,
signal: controller2.signal signal: controller2.signal
}); });
clearTimeout(id2); clearTimeout(id2);
const endTime = performance.now(); const endTime = performance.now();
const latency = Math.round(endTime - startTime); const latency = Math.round(endTime - startTime);
if (!resp2.ok) throw new Error('WP AJAX HTTP ' + resp2.status); if (!resp2.ok) throw new Error('WP AJAX HTTP ' + resp2.status);
const json = await resp2.json(); const json = await resp2.json();
if (json && (typeof json.online !== 'undefined' || Array.isArray(json.players) || json.version)) { if (json && (typeof json.online !== 'undefined' || Array.isArray(json.players) || json.version)) {
json.ping = latency; // Ping hinzufügen json.ping = latency; // Ping hinzufügen
return json; return json;
} }
// JSON nicht im erwarteten Format // JSON nicht im erwarteten Format
return { return {
online: false, online: false,
players: [], players: [],
max_players: 0, max_players: 0,
version: 'unknown', version: 'unknown',
motd: '', motd: '',
ping: latency > timeoutMs ? null : latency ping: latency > timeoutMs ? null : latency
}; };
} catch (e) { } catch (e) {
return { return {
online: false, online: false,
players: [], players: [],
max_players: 0, max_players: 0,
version: 'unknown', version: 'unknown',
motd: '', motd: '',
ping: null ping: null
}; };
} }
} }
// Keine gültige Konfiguration // Keine gültige Konfiguration
return { return {
online: false, online: false,
players: [], players: [],
max_players: 0, max_players: 0,
version: 'unknown', version: 'unknown',
motd: '', motd: '',
ping: null ping: null
}; };
} }

View File

@@ -10,34 +10,63 @@
--offline: #ef4444; --offline: #ef4444;
} }
* { box-sizing:border-box; } * { box-sizing: border-box; }
html,body { margin:0; padding:0; font-family:Inter, Arial, sans-serif; background:var(--bg); color:var(--text);}
.root { width:520px; max-width:520px; padding:12px; }
header { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; } html, body {
header h1 { font-size:16px; margin:0; } margin: 0;
header .actions button { background:transparent; border:1px solid var(--muted); color:var(--text); padding:4px 8px; border-radius:6px; cursor:pointer; } padding: 0;
font-family: Inter, Arial, sans-serif;
background: var(--bg);
color: var(--text);
overflow-x: hidden;
}
.root {
width: 520px;
padding: 12px;
display: flex;
flex-direction: column;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
header h1 { font-size: 16px; margin: 0; }
header .actions button {
background: transparent;
border: 1px solid var(--muted);
color: var(--text);
padding: 4px 8px;
border-radius: 6px;
cursor: pointer;
}
.main { .main {
display:flex; display: flex;
gap:12px; gap: 12px;
position: relative; position: relative;
align-items: flex-start;
} }
.server-list { .server-list {
width:45%; width: 45%;
background:var(--panel); background: var(--panel);
padding:8px; padding: 8px;
border-radius:var(--radius); border-radius: var(--radius);
flex-shrink: 0; flex-shrink: 0;
} }
.detail { .detail {
flex:1; flex: 1;
background:var(--panel); background: var(--panel);
padding:8px; padding: 8px;
border-radius:var(--radius); border-radius: var(--radius);
min-height:220px; min-height: 220px;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@@ -46,175 +75,143 @@ header .actions button { background:transparent; border:1px solid var(--muted);
flex: 1 1 100%; flex: 1 1 100%;
} }
.add-form { display:flex; flex-direction:column; gap:6px; margin-bottom:8px; } .add-form { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.add-form input { padding:6px 8px; border-radius:6px; border:1px solid #21303b; background:#071019; color:var(--text); } .add-form input { padding: 6px 8px; border-radius: 6px; border: 1px solid #21303b; background: #071019; color: var(--text); }
.add-form button { padding:6px 8px; border-radius:6px; border:none; background:var(--accent); color:white; cursor:pointer; } .add-form button { padding: 6px 8px; border-radius: 6px; border: none; background: var(--accent); color: white; cursor: pointer; }
#serversContainer {
list-style: none;
margin: 0;
padding: 0;
}
#serversContainer { list-style:none; margin:0; padding:0; max-height:260px; overflow:auto; }
.server-item { .server-item {
display:flex; display: flex;
align-items:center; align-items: center;
justify-content:space-between; justify-content: space-between;
padding:6px; padding: 8px;
margin-bottom:6px; margin-bottom: 6px;
border-radius:6px; border-radius: 6px;
cursor:pointer; cursor: pointer;
background: rgba(255,255,255,0.02); background: rgba(255,255,255,0.02);
transition: background 0.2s ease; transition: background 0.2s ease;
} }
.server-item:hover { background: rgba(255,255,255,0.05); } .server-item:hover { background: rgba(255,255,255,0.05); }
.server-item .meta { display: flex; flex-direction: column; }
.server-item .meta .name { font-weight: 600; }
.server-item .meta .url { font-size: 11px; color: var(--muted); }
.server-item .meta { display:flex; flex-direction:column; }
.server-item .meta .name { font-weight:600; }
.server-item .meta .url { font-size:12px; color:var(--muted); }
.server-item .status-bubble { .server-item .status-bubble {
font-size:12px; font-size: 11px;
padding:3px 6px; padding: 2px 8px;
border-radius:999px; border-radius: 999px;
color:#fff; color: #fff;
transition: background-color 0.3s ease;
} }
.placeholder { color:var(--muted); padding:12px; } .placeholder { color: var(--muted); padding: 12px; font-size: 13px; }
.hidden { display:none; } .hidden { display: none; }
/* --- Detail Header (Zeile 1) --- */
.detail-header { .detail-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 8px; margin-bottom: 12px;
} }
.server-identity h2 { .server-identity h2 { margin: 0; font-size: 18px; line-height: 1.2; }
margin: 0; .server-url { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
font-size: 18px;
line-height: 1.2;
}
.server-url {
display: block;
font-size: 12px;
color: var(--muted);
margin-top: 2px;
}
.status-wrapper { .status-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
font-weight: 600; font-weight: 600;
font-size: 13px;
} }
/* --- Pulsierender Punkt Animation --- */
.pulsing-dot { .pulsing-dot {
width: 10px; width: 10px; height: 10px; border-radius: 50%;
height: 10px; background-color: var(--offline);
border-radius: 50%; box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
background-color: var(--offline); /* Standard Offline Rot */ animation: pulse-red 2s infinite;
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
animation: pulse-red 2s infinite;
} }
.pulsing-dot.online { .pulsing-dot.online {
background-color: var(--online); /* Online Grün */ background-color: var(--online);
animation: pulse-green 2s infinite; animation: pulse-green 2s infinite;
} }
@keyframes pulse-green { @keyframes pulse-green { 0% {box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);} 70% {box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);} 100% {box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);} }
0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); } @keyframes pulse-red { 0% {box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);} 70% {box-shadow: 0 0 0 8px rgba(239, 68, 68, 0);} 100% {box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);} }
70% { box-shadow: 0 0 0 10px rgba(34, 197, 94, 0); }
100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
}
@keyframes pulse-red {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
/* --- Detail Stats (Zeile 2) --- */
.detail-stats { .detail-stats {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; background: rgba(255,255,255,0.05);
background: rgba(255,255,255,0.05); padding: 10px;
padding: 8px; border-radius: var(--radius);
border-radius: var(--radius); margin-bottom: 12px;
margin-bottom: 16px; font-size: 13px;
font-size: 13px;
} }
/* ---------------------------------- */
.playerList { .playerList {
list-style:none; list-style: none;
padding-left:16px; padding: 10px 0;
max-height:140px; margin: 0;
overflow:auto; display: flex;
margin:6px 0; flex-wrap: wrap;
display:flex; gap: 12px;
flex-wrap:wrap; justify-content: center; /* Das hier setzt die Köpfe wieder in die Mitte */
gap:8px;
} }
.playerList li { .player-item {
display:flex; position: relative;
align-items:center; display: flex;
gap:4px; flex-direction: column;
font-size:13px; align-items: center;
color:var(--text);
} }
.player-avatar { .player-avatar {
width:24px; width: 42px; height: 42px;
height:24px; border-radius: 6px;
border-radius:4px;
transition: transform 0.2s ease; transition: transform 0.2s ease;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
border: 2px solid rgba(255,255,255,0.1);
} }
.player-avatar:hover { transform: scale(1.1); } .player-avatar:hover {
transform: scale(1.1);
.detailButtons { border-color: var(--accent);
display:flex;
gap:8px;
margin-top:8px;
transition: opacity 0.3s ease;
} }
.detailButtons button { .player-hover-info {
padding:6px 8px; position: absolute;
border-radius:6px; bottom: -28px;
border:none; background: rgba(0, 0, 0, 0.95);
cursor:pointer; color: white;
transition: opacity 0.2s ease, transform 0.1s ease; padding: 3px 10px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transform: translateY(5px);
transition: all 0.2s ease;
z-index: 100;
border: 1px solid rgba(255,255,255,0.2);
} }
.detailButtons button:hover { opacity: 0.9; transform: translateY(-1px); } .player-item:hover .player-hover-info {
.detailButtons button:active { transform: translateY(0); } opacity: 1;
transform: translateY(0);
#btnEdit { background: #f59e0b; color:#000; }
#btnDelete { background: var(--danger); color:#fff; }
#serversContainer::-webkit-scrollbar,
.playerList::-webkit-scrollbar {
width: 6px;
} }
#serversContainer::-webkit-scrollbar-track, .detailButtons { display: flex; gap: 8px; margin-top: 15px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 10px; }
.playerList::-webkit-scrollbar-track { .detailButtons button { padding: 6px 12px; border-radius: 6px; border: none; cursor: pointer; font-size: 12px; font-weight: 600; }
background: rgba(255,255,255,0.05); #btnEdit { background: #f59e0b; color: #000; }
border-radius: 3px; #btnDelete { background: var(--danger); color: #fff; }
}
#serversContainer::-webkit-scrollbar-thumb, ::-webkit-scrollbar { width: 6px; }
.playerList::-webkit-scrollbar-thumb { ::-webkit-scrollbar-track { background: transparent; }
background: var(--muted); ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
border-radius: 3px; ::-webkit-scrollbar-thumb:hover { background: var(--accent); }
}
#serversContainer::-webkit-scrollbar-thumb:hover,
.playerList::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}

View File

@@ -33,7 +33,6 @@
<div id="noSelection" class="placeholder">Wähle einen Server aus der linken Liste.</div> <div id="noSelection" class="placeholder">Wähle einen Server aus der linken Liste.</div>
<div id="detailContent" class="detailContent hidden"> <div id="detailContent" class="detailContent hidden">
<!-- NEUES LAYOUT ZEILE 1: Name, URL, Status (mit Pulsierendem Punkt) -->
<div class="detail-header"> <div class="detail-header">
<div class="server-identity"> <div class="server-identity">
<h2 id="detailName"></h2> <h2 id="detailName"></h2>
@@ -45,7 +44,6 @@
</div> </div>
</div> </div>
<!-- NEUES LAYOUT ZEILE 2: Spieler, Version, Ping -->
<div class="detail-stats"> <div class="detail-stats">
<div><strong>Spieler:</strong> <span id="detailPlayers"></span></div> <div><strong>Spieler:</strong> <span id="detailPlayers"></span></div>
<div><strong>Version:</strong> <span id="detailVersion"></span></div> <div><strong>Version:</strong> <span id="detailVersion"></span></div>

View File

@@ -1,384 +1,335 @@
const $ = id => document.getElementById(id); //popup.js
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9); const $ = id => document.getElementById(id);
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
const inputName = $('inputName');
const inputUrl = $('inputUrl'); const inputName = $('inputName');
const inputWpSite = $('inputWpSite'); const inputUrl = $('inputUrl');
const inputWpServerId = $('inputWpServerId'); const inputWpSite = $('inputWpSite');
const btnAdd = $('btnAddServer'); const inputWpServerId = $('inputWpServerId');
const serversContainer = $('serversContainer'); const btnAdd = $('btnAddServer');
const serverListPanel = document.querySelector('.server-list'); const serversContainer = $('serversContainer');
const btnRefresh = $('btnRefresh'); const serverListPanel = document.querySelector('.server-list');
const btnToggleSettings = $('btnToggleSettings'); const btnRefresh = $('btnRefresh');
const settingsForm = $('settingsForm'); const btnToggleSettings = $('btnToggleSettings');
const settingsForm = $('settingsForm');
const noSelection = $('noSelection');
const detailContent = $('detailContent'); const noSelection = $('noSelection');
const detailName = $('detailName'); const detailContent = $('detailContent');
const detailUrlText = $('detailUrlText'); const detailName = $('detailName');
const detailStatus = $('detailStatus'); const detailUrlText = $('detailUrlText');
const detailPulse = $('detailPulse'); const detailStatus = $('detailStatus');
const detailVersion = $('detailVersion'); const detailPulse = $('detailPulse');
const detailPlayers = $('detailPlayers'); const detailVersion = $('detailVersion');
const detailPing = $('detailPing'); const detailPlayers = $('detailPlayers');
const detailPlayerList = $('detailPlayerList'); const detailPing = $('detailPing');
const btnEdit = $('btnEdit'); const detailPlayerList = $('detailPlayerList');
const btnDelete = $('btnDelete'); const btnEdit = $('btnEdit');
const btnDelete = $('btnDelete');
let servers = [];
let selectedId = null; let servers = [];
let statuses = {}; let selectedId = null;
let previousStatuses = {}; let statuses = {};
let settingsVisible = false; let previousStatuses = {};
let settingsVisible = false;
document.addEventListener('DOMContentLoaded', init);
btnAdd.addEventListener('click', handleAdd); document.addEventListener('DOMContentLoaded', init);
btnRefresh.addEventListener('click', manualRefresh); btnAdd.addEventListener('click', handleAdd);
btnToggleSettings.addEventListener('click', toggleSettings); btnRefresh.addEventListener('click', manualRefresh);
btnEdit.addEventListener('click', handleEdit); btnToggleSettings.addEventListener('click', toggleSettings);
btnDelete.addEventListener('click', handleDelete); btnEdit.addEventListener('click', handleEdit);
btnDelete.addEventListener('click', handleDelete);
async function init() {
await loadServersFromStorage(); async function init() {
await loadStatusesFromStorage(); await loadServersFromStorage();
await loadSettingsVisibility(); await loadStatusesFromStorage();
renderServerList(); await loadSettingsVisibility();
renderServerList();
if (servers.length === 1) selectedId = servers[0].id;
renderDetail(selectedId); if (servers.length === 1) selectedId = servers[0].id;
renderDetail(selectedId);
adjustDetailLayout();
} adjustDetailLayout();
}
// --- Settings Visibility ---
async function loadSettingsVisibility() { // --- NEU: Minecraft Color Parser (Eingebaut ohne Löschung) ---
const obj = await chrome.storage.local.get(['settingsVisible']); function parseMinecraftColors(text) {
settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false; if (!text) return '';
if (!settingsVisible) settingsForm.classList.add('hidden'); const map = {
else settingsForm.classList.remove('hidden'); '&0': '#000000', '&1': '#0000AA', '&2': '#00AA00', '&3': '#00AAAA',
} '&4': '#AA0000', '&5': '#AA00AA', '&6': '#FFAA00', '&7': '#AAAAAA',
'&8': '#555555', '&9': '#5555FF', '&a': '#55FF55', '&b': '#55FFFF',
async function saveSettingsVisibility() { '&c': '#FF5555', '&d': '#FF55FF', '&e': '#FFFF55', '&f': '#FFFFFF'
await chrome.storage.local.set({ settingsVisible }); };
} let html = text.replace(/[<>]/g, '');
Object.keys(map).forEach(code => {
async function toggleSettings() { const color = map[code];
settingsVisible = !settingsVisible; const regex = new RegExp(code, 'g');
settingsForm.classList.toggle('hidden'); html = html.replace(regex, `</span><span style="color: ${color}">`);
await saveSettingsVisibility(); });
adjustDetailLayout(); return `<span>${html}</span>`.replace(/&l/g, '<b>').replace(/&r/g, '</b>');
} }
// --- Storage --- // --- 3D Avatar URL Generator ---
async function loadServersFromStorage() { function get3DAvatarUrl(playerName, uuid = null) {
const obj = await chrome.storage.local.get(['servers']); const isBedrock = playerName.includes('.') || (uuid && uuid.startsWith('xuid'));
servers = obj.servers || []; if (isBedrock) {
for (const s of servers) if (!s.id) s.id = uid(); if (uuid && uuid.length > 0) {
await chrome.storage.local.set({ servers }); return `https://mc-heads.net/head/${encodeURIComponent(uuid)}/64`;
} }
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
async function saveServersToStorage() { } else {
await chrome.storage.local.set({ servers }); return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
} }
}
async function loadStatusesFromStorage() {
const obj = await chrome.storage.local.get(['serverStatuses']); async function loadSettingsVisibility() {
statuses = obj.serverStatuses || {}; const obj = await chrome.storage.local.get(['settingsVisible']);
} settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false;
if (!settingsVisible) settingsForm.classList.add('hidden');
// --- Layout --- else settingsForm.classList.remove('hidden');
function adjustDetailLayout() { }
const settingsHidden = settingsForm.classList.contains('hidden');
detailContent.classList.toggle('full-width', settingsHidden); async function saveSettingsVisibility() {
serverListPanel.classList.toggle('hidden', settingsHidden); await chrome.storage.local.set({ settingsVisible });
btnEdit.style.display = settingsHidden ? 'none' : 'inline-block'; }
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
} async function toggleSettings() {
settingsVisible = !settingsVisible;
// --- Render Server List --- settingsForm.classList.toggle('hidden');
function renderServerList() { await saveSettingsVisibility();
serversContainer.innerHTML = ''; adjustDetailLayout();
if (servers.length === 0) { }
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
return; async function loadServersFromStorage() {
} const obj = await chrome.storage.local.get(['servers']);
servers = obj.servers || [];
for (const s of servers) { for (const s of servers) if (!s.id) s.id = uid();
const item = document.createElement('li'); await chrome.storage.local.set({ servers });
item.className = 'server-item'; }
item.dataset.id = s.id;
async function saveServersToStorage() {
const meta = document.createElement('div'); await chrome.storage.local.set({ servers });
meta.className = 'meta'; }
const name = document.createElement('div');
name.className = 'name'; async function loadStatusesFromStorage() {
name.textContent = s.name || '(Kein Name)'; const obj = await chrome.storage.local.get(['serverStatuses']);
statuses = obj.serverStatuses || {};
let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : ''); }
listUrl = listUrl.replace(':9191', '');
function adjustDetailLayout() {
const url = document.createElement('div'); const settingsHidden = settingsForm.classList.contains('hidden');
url.className = 'url'; detailContent.classList.toggle('full-width', settingsHidden);
url.textContent = listUrl; serverListPanel.classList.toggle('hidden', settingsHidden);
meta.append(name, url); btnEdit.style.display = settingsHidden ? 'none' : 'inline-block';
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
const statusBubble = document.createElement('div'); }
statusBubble.className = 'status-bubble';
statusBubble.textContent = '—'; function renderServerList() {
statusBubble.style.backgroundColor = 'transparent'; serversContainer.innerHTML = '';
item.statusBubble = statusBubble; if (servers.length === 0) {
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
item.append(meta, statusBubble); return;
item.addEventListener('click', () => { }
selectedId = s.id; for (const s of servers) {
renderDetail(selectedId); const item = document.createElement('li');
}); item.className = 'server-item';
item.dataset.id = s.id;
serversContainer.appendChild(item); const meta = document.createElement('div');
} meta.className = 'meta';
updateServerListStatuses(false); const name = document.createElement('div');
} name.className = 'name';
name.textContent = s.name || '(Kein Name)';
// --- Render Detail --- let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : '');
function renderDetail(id) { listUrl = listUrl.replace(':9191', '');
if (!id) { const url = document.createElement('div');
noSelection.classList.remove('hidden'); url.className = 'url';
detailContent.classList.add('hidden'); url.textContent = listUrl;
return; meta.append(name, url);
} const statusBubble = document.createElement('div');
const srv = servers.find(x => x.id === id); statusBubble.className = 'status-bubble';
if (!srv) { statusBubble.textContent = '—';
noSelection.classList.remove('hidden'); statusBubble.style.backgroundColor = 'transparent';
detailContent.classList.add('hidden'); item.statusBubble = statusBubble;
return; item.append(meta, statusBubble);
} item.addEventListener('click', () => {
noSelection.classList.add('hidden'); selectedId = s.id;
detailContent.classList.remove('hidden'); renderDetail(selectedId);
});
detailName.textContent = srv.name || 'Unbenannter Server'; serversContainer.appendChild(item);
}
let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal'); updateServerListStatuses();
urlToShow = urlToShow.replace(':9191', ''); }
detailUrlText.textContent = urlToShow; // --- Fortsetzung popup.js ---
updateDetailForServer(srv, true); function renderDetail(id) {
} if (!id) {
noSelection.classList.remove('hidden');
// --- Update Detail --- detailContent.classList.add('hidden');
function updateDetailForServer(srv, force = false) { return;
const st = statuses[srv.id]; }
const prevSt = previousStatuses[srv.id]; const srv = servers.find(x => x.id === id);
if (!srv) {
const statusChanged = force || noSelection.classList.remove('hidden');
!prevSt || detailContent.classList.add('hidden');
(prevSt.ok !== st?.ok) || return;
(prevSt.data?.online !== st?.data?.online); }
noSelection.classList.add('hidden');
if (!st || !st.ok || !st.data) { detailContent.classList.remove('hidden');
if (statusChanged) { detailName.textContent = srv.name || 'Unbenannter Server';
detailStatus.textContent = 'Offline'; let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal');
detailPulse.classList.remove('online'); urlToShow = urlToShow.replace(':9191', '');
detailVersion.textContent = '-'; detailUrlText.textContent = urlToShow;
detailPlayers.textContent = '-'; updateDetailForServer(srv, true);
detailPing.textContent = '-'; }
updatePlayerList([]);
} function updateDetailForServer(srv, force = false) {
} else { const st = statuses[srv.id];
const d = st.data; const prevSt = previousStatuses[srv.id];
const statusChanged = force || !prevSt || (prevSt.ok !== st?.ok) || (prevSt.data?.online !== st?.data?.online);
if (statusChanged) {
detailStatus.textContent = d.online ? 'Online' : 'Offline'; if (!st || !st.ok || !st.data) {
if (d.online) { if (statusChanged) {
detailPulse.classList.add('online'); detailStatus.textContent = 'Offline';
} else { detailPulse.classList.remove('online');
detailPulse.classList.remove('online'); detailVersion.textContent = '-';
} detailPlayers.textContent = '-';
} detailPing.textContent = '-';
updatePlayerList([]);
const newVersion = d.version || 'unknown'; }
if (force || detailVersion.textContent !== newVersion) { } else {
detailVersion.textContent = newVersion; const d = st.data;
} if (statusChanged) {
detailStatus.textContent = d.online ? 'Online' : 'Offline';
const playersCount = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0); if (d.online) detailPulse.classList.add('online');
const maxPlayers = d.max_players; else detailPulse.classList.remove('online');
let newPlayersText = String(playersCount); }
if (maxPlayers && maxPlayers !== '-1') { const newVersion = d.version || 'unknown';
newPlayersText += ` / ${maxPlayers}`; if (force || detailVersion.textContent !== newVersion) detailVersion.textContent = newVersion;
} const playersCount = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0);
const maxPlayers = d.max_players;
if (force || detailPlayers.textContent !== newPlayersText) { let newPlayersText = String(playersCount);
detailPlayers.textContent = newPlayersText; if (maxPlayers && maxPlayers !== '-1') newPlayersText += ` / ${maxPlayers}`;
} if (force || detailPlayers.textContent !== newPlayersText) detailPlayers.textContent = newPlayersText;
let pingVal = d.ping || d.latency || '-';
let pingVal = d.ping || d.latency || '-'; if (pingVal !== '-' && typeof pingVal === 'number') pingVal = pingVal + ' ms';
if (pingVal !== '-' && typeof pingVal === 'number') { if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) detailPing.textContent = pingVal;
pingVal = pingVal + ' ms';
} const currentPlayers = Array.isArray(d.players) ? d.players : [];
const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : [];
if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) { let playersChanged = force || JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers);
detailPing.textContent = pingVal; if (playersChanged) updatePlayerList(currentPlayers);
} }
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
const currentPlayers = Array.isArray(d.players) ? d.players : []; }
const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : [];
// --- Spielerliste mit Prefix-Hover und Farbsupport ---
let playersChanged = false; function updatePlayerList(players) {
if (force) { detailPlayerList.innerHTML = '';
playersChanged = true; if (!players || players.length === 0) {
} else { detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>';
playersChanged = JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers); return;
} }
if (playersChanged) { for (const p of players) {
updatePlayerList(currentPlayers); const li = document.createElement('li');
} li.className = 'player-item'; // CSS Klasse für den Hover-Container
}
let name = '';
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null; let uuid = null;
} let prefix = '';
// --- Spielerliste --- if (typeof p === 'object') {
function updatePlayerList(players) { name = p.name || p.username || p.player || '';
detailPlayerList.innerHTML = ''; uuid = p.uuid || null;
if (!players || players.length === 0) { prefix = p.prefix || '';
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>'; } else {
return; name = String(p);
} }
for (const p of players) {
const li = document.createElement('li'); const img = document.createElement('img');
const name = typeof p === 'object' ? p.name || p.username || p.player || '' : String(p); img.src = (typeof p === 'object' && p.avatar) ? p.avatar : get3DAvatarUrl(name, uuid);
img.className = 'player-avatar';
if (typeof p === 'object' && p.avatar) { img.loading = 'lazy';
const img = document.createElement('img'); img.onerror = function() {
img.src = p.avatar; this.onerror = null;
img.className = 'player-avatar'; this.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/64`;
img.title = name; };
li.appendChild(img);
} else { // Das Hover-Element mit farbigem Prefix + Name
// Falls kein Avatar vorhanden, generiere einen von mc-heads.net const info = document.createElement('div');
const img = document.createElement('img'); info.className = 'player-hover-info';
img.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/32`; info.innerHTML = `${parseMinecraftColors(prefix)} ${name}`.trim();
img.className = 'player-avatar';
img.title = name; li.appendChild(img);
li.appendChild(img); li.appendChild(info);
} detailPlayerList.appendChild(li);
}
detailPlayerList.appendChild(li); }
}
} function updateServerListStatuses() {
const items = serversContainer.querySelectorAll('.server-item');
// --- Update Server List Statuses --- items.forEach(item => {
function updateServerListStatuses() { const s = servers.find(x => x.id === item.dataset.id);
const items = serversContainer.querySelectorAll('.server-item'); if (!s) return;
items.forEach(item => { const st = statuses[s.id];
const s = servers.find(x => x.id === item.dataset.id); if (!st || !st.ok || !st.data) {
if (!s) return; item.statusBubble.textContent = 'Offline';
item.statusBubble.style.backgroundColor = 'var(--offline)';
const st = statuses[s.id]; } else if (st.data.online) {
const prevSt = previousStatuses[s.id]; item.statusBubble.textContent = 'Online';
item.statusBubble.style.backgroundColor = 'var(--online)';
const statusChanged = !prevSt || } else {
(prevSt.ok !== st?.ok) || item.statusBubble.textContent = 'Offline';
(prevSt.data?.online !== st?.data?.online); item.statusBubble.style.backgroundColor = 'var(--offline)';
}
if (!statusChanged) return; });
}
if (!st || !st.ok || !st.data) {
item.statusBubble.textContent = 'Offline'; async function handleAdd() {
item.statusBubble.style.backgroundColor = 'var(--offline)'; const name = inputName.value.trim();
} else if (st.data.online) { const url = inputUrl.value.trim();
item.statusBubble.textContent = 'Online'; const wpSite = inputWpSite.value.trim();
item.statusBubble.style.backgroundColor = 'var(--online)'; const wpServerId = inputWpServerId.value.trim();
} else { if (!url && !wpSite) return;
item.statusBubble.textContent = 'Offline'; const s = { id: uid(), name: name || url || wpSite, url: url || null, wpSite: wpSite || null, wpServerId: wpServerId || null };
item.statusBubble.style.backgroundColor = 'var(--offline)'; servers.push(s);
} await saveServersToStorage();
inputName.value = inputUrl.value = inputWpSite.value = inputWpServerId.value = '';
previousStatuses[s.id] = st ? JSON.parse(JSON.stringify(st)) : null; renderServerList();
}); }
}
async function handleEdit() {
// --- Add / Edit / Delete --- if (!selectedId) return;
async function handleAdd() { const srv = servers.find(s => s.id === selectedId);
const name = inputName.value.trim(); if (!srv) return;
const url = inputUrl.value.trim(); const newName = prompt('Name:', srv.name) || srv.name;
const wpSite = inputWpSite.value.trim(); srv.name = newName.trim();
const wpServerId = inputWpServerId.value.trim(); await saveServersToStorage();
renderServerList();
if (!url && !wpSite) { renderDetail(selectedId);
alert('Bitte URL oder WP Site angeben'); }
return;
} async function handleDelete() {
if (!selectedId || !confirm('Server wirklich löschen?')) return;
const s = { servers = servers.filter(s => s.id !== selectedId);
id: uid(), selectedId = null;
name: name || url || wpSite, await saveServersToStorage();
url: url || null, renderServerList();
wpSite: wpSite || null, renderDetail(null);
wpServerId: wpServerId || null }
};
async function manualRefresh() {
servers.push(s); try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {}
await saveServersToStorage(); }
inputName.value = ''; chrome.storage.onChanged.addListener((changes, area) => {
inputUrl.value = ''; if (area === 'local' && changes.serverStatuses) {
inputWpSite.value = ''; statuses = changes.serverStatuses.newValue || {};
inputWpServerId.value = ''; updateServerListStatuses();
if (selectedId) {
renderServerList(); const srv = servers.find(s => s.id === selectedId);
} if (srv) updateDetailForServer(srv);
}
async function handleEdit() { }
if (!selectedId) return;
const srv = servers.find(s => s.id === selectedId);
if (!srv) return;
const newName = prompt('Name:', srv.name) || srv.name;
const newUrl = prompt('URL:', srv.url || '') || srv.url;
const newWpSite = prompt('WP Site:', srv.wpSite || '') || srv.wpSite;
const newWpServerId = prompt('WP Server ID:', srv.wpServerId || '') || srv.wpServerId;
srv.name = newName.trim();
srv.url = newUrl.trim();
srv.wpSite = newWpSite.trim();
srv.wpServerId = newWpServerId.trim();
await saveServersToStorage();
renderServerList();
renderDetail(selectedId);
}
async function handleDelete() {
if (!selectedId) return;
if (!confirm('Server wirklich löschen?')) return;
servers = servers.filter(s => s.id !== selectedId);
selectedId = null;
await saveServersToStorage();
renderServerList();
renderDetail(null);
}
async function manualRefresh() {
try {
chrome.runtime.sendMessage({ cmd: 'refreshNow' });
} catch(e) {
console.error('Refresh fehlgeschlagen:', e);
}
}
// --- Storage Listener ---
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.serverStatuses) {
statuses = changes.serverStatuses.newValue || {};
updateServerListStatuses();
if (selectedId) {
const srv = servers.find(s => s.id === selectedId);
if (srv) updateDetailForServer(srv);
}
}
}); });

View File

@@ -3,7 +3,7 @@
* Plugin Name: Minecraft BungeeCord Status Network Edition * Plugin Name: Minecraft BungeeCord Status Network Edition
* Description: Der ultimative Live-Status für dein BungeeCord Netzwerk (Border None Fix). * Description: Der ultimative Live-Status für dein BungeeCord Netzwerk (Border None Fix).
* Tags: minecraft, bungeecord, server status, player list * Tags: minecraft, bungeecord, server status, player list
* Version: 3.6.3 * Version: 3.6.4
* Author: M_Viper * Author: M_Viper
* Requires at least: 6.0 * Requires at least: 6.0
* Requires PHP: 7.4 * Requires PHP: 7.4
@@ -897,4 +897,185 @@ function mcss_shortcode($atts) {
<?php return ob_get_clean(); <?php return ob_get_clean();
} }
/* ---------------- SIDEBAR WIDGET: MODERN PILL BADGE (ROBUST INLINE STYLES) ---------------- */
class MCSS_Sidebar_Status_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'mcss_sidebar_status', // Basis ID
'MC Server Status (Simple)', // Name
array( 'description' => 'Zeigt einen modernen Online/Offline Status in der Sidebar an.' ) // Args
);
}
// Backend: Formular im Widget-Bereich
public function form( $instance ) {
$title = ! empty( $instance['title'] ) ? $instance['title'] : 'Server Status';
$server_id = ! empty( $instance['server_id'] ) ? $instance['server_id'] : '';
// Server-Liste holen
$servers = get_option('mcss_servers', []);
?>
<p>
<label for="<?php echo $this->get_field_id( 'title' ); ?>">Titel:</label>
<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id( 'server_id' ); ?>">Server wählen:</label>
<select class="widefat" id="<?php echo $this->get_field_id( 'server_id' ); ?>" name="<?php echo $this->get_field_name( 'server_id' ); ?>">
<option value="">-- Bitte wählen --</option>
<?php foreach ($servers as $srv): ?>
<option value="<?php echo esc_attr($srv['id'] ?? ''); ?>" <?php selected($server_id, $srv['id'] ?? ''); ?>>
<?php echo esc_html($srv['name'] ?? 'Unbenannt'); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<?php
}
// Backend: Speichern
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = ( ! empty( $new_instance['title'] ) ) ? sanitize_text_field( $new_instance['title'] ) : '';
$instance['server_id'] = ( ! empty( $new_instance['server_id'] ) ) ? sanitize_text_field( $new_instance['server_id'] ) : '';
return $instance;
}
// Frontend: Ausgabe
public function widget( $args, $instance ) {
echo $args['before_widget'];
if ( ! empty( $instance['title'] ) ) {
echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
}
$server_id = $instance['server_id'];
$servers = get_option('mcss_servers', []);
$target_srv = null;
// Server suchen
foreach ($servers as $srv) {
if (($srv['id'] ?? '') === $server_id) { $target_srv = $srv; break; }
}
if ($target_srv) {
// Initiale Daten holen
$data = mcss_fetch_server_with_ranks($target_srv);
$is_online = $data['online'] ?? false;
$uid = md5($target_srv['host'] . 'widget'); // Einzigartige ID für JS
// Farben definieren
$bg_online = '#ecfdf5';
$bg_offline = '#fef2f2';
$text_online = '#059669';
$text_offline = '#dc2626';
$dot_online = '#10b981';
$dot_offline = '#ef4444';
$border_online = 'rgba(16, 185, 129, 0.2)';
$border_offline = 'rgba(239, 68, 68, 0.2)';
$anim_name_online = 'mcss-pulse-modern';
$anim_name_offline = 'mcss-pulse-modern-red';
// Werte für den Initialzustand setzen
$current_bg = $is_online ? $bg_online : $bg_offline;
$current_text = $is_online ? $text_online : $text_offline;
$current_dot = $is_online ? $dot_online : $dot_offline;
$current_border = $is_online ? $border_online : $border_offline;
$current_anim = $is_online ? $anim_name_online : $anim_name_offline;
$status_label = $is_online ? 'Online' : 'Offline';
// Inline Animation Definitionen für Robustheit
?>
<style>
@keyframes mcss-pulse-modern {
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}
@keyframes mcss-pulse-modern-red {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
</style>
<!-- Wrapper mit PUREN Inline Styles -->
<div id="mcss-sw-wrapper-<?php echo esc_attr($uid); ?>"
style="display: flex; align-items: center; justify-content: center; width: 93%; padding: 8px; border-radius: 50px; background-color: <?php echo $current_bg; ?>; border: 1px solid <?php echo $current_border; ?>; transition: all 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; cursor: default; margin: 10px 0;">
<span id="mcss-sw-dot-<?php echo esc_attr($uid); ?>"
style="width: 8px; height: 8px; border-radius: 50%; margin-right: 10px; background-color: <?php echo $current_dot; ?>; animation: <?php echo $current_anim; ?> 2s infinite;"></span>
<span id="mcss-sw-text-<?php echo esc_attr($uid); ?>"
style="font-size: 12px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: <?php echo $current_text; ?>;">
<?php echo $status_label; ?>
</span>
</div>
<!-- Kleines Skript für Live-Update (alle 3 Sekunden) -->
<script>
(function(){
var uid = "<?php echo esc_js($uid); ?>";
var serverId = "<?php echo esc_js($target_srv['id']); ?>";
// Konstanten Farben
var cOnlineBg = '<?php echo esc_js($bg_online); ?>';
var cOfflineBg = '<?php echo esc_js($bg_offline); ?>';
var cOnlineText = '<?php echo esc_js($text_online); ?>';
var cOfflineText = '<?php echo esc_js($text_offline); ?>';
var cOnlineDot = '<?php echo esc_js($dot_online); ?>';
var cOfflineDot = '<?php echo esc_js($dot_offline); ?>';
var bOnline = '<?php echo esc_js($border_online); ?>';
var bOffline = '<?php echo esc_js($border_offline); ?>';
var aOnline = '<?php echo esc_js($anim_name_online); ?>';
var aOffline = '<?php echo esc_js($anim_name_offline); ?>';
setInterval(function(){
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: 'action=mcss_fetch&server_id=' + serverId
})
.then(r => r.json())
.then(d => {
var wrapper = document.getElementById('mcss-sw-wrapper-' + uid);
var dot = document.getElementById('mcss-sw-dot-' + uid);
var txt = document.getElementById('mcss-sw-text-' + uid);
if(wrapper && dot && txt && d) {
if(d.online) {
wrapper.style.backgroundColor = cOnlineBg;
wrapper.style.borderColor = bOnline;
dot.style.backgroundColor = cOnlineDot;
dot.style.animationName = aOnline;
txt.style.color = cOnlineText;
txt.textContent = 'Online';
} else {
wrapper.style.backgroundColor = cOfflineBg;
wrapper.style.borderColor = bOffline;
dot.style.backgroundColor = cOfflineDot;
dot.style.animationName = aOffline;
txt.style.color = cOfflineText;
txt.textContent = 'Offline';
}
}
});
}, 3000);
})();
</script>
<?php
} else {
echo '<p>Bitte Server in den Einstellungen wählen.</p>';
}
echo $args['after_widget'];
}
}
// Widget registrieren
add_action( 'widgets_init', function(){
register_widget( 'MCSS_Sidebar_Status_Widget' );
});