Dateien nach "BungeeCord-Chrome" hochladen
This commit is contained in:
111
BungeeCord-Chrome/background.js
Normal file
111
BungeeCord-Chrome/background.js
Normal file
@@ -0,0 +1,111 @@
|
||||
// background.js
|
||||
const POLL_INTERVAL_SEC = 2;
|
||||
|
||||
// Alarm erstellen
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
chrome.alarms.create('periodicRefresh', { periodInMinutes: POLL_INTERVAL_SEC / 60 });
|
||||
});
|
||||
|
||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === 'periodicRefresh') {
|
||||
refreshAllServers().catch(console.error);
|
||||
}
|
||||
});
|
||||
|
||||
// Message Listener
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (msg && msg.cmd === 'refreshNow') {
|
||||
refreshAllServers()
|
||||
.then(statuses => sendResponse({ ok: true, statuses }))
|
||||
.catch(err => sendResponse({ ok: false, error: String(err) }));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
async function refreshAllServers() {
|
||||
const obj = await chrome.storage.local.get('servers');
|
||||
const servers = obj.servers || [];
|
||||
if (servers.length === 0) {
|
||||
await chrome.storage.local.set({ serverStatuses: {} });
|
||||
chrome.action.setBadgeText({ text: '' });
|
||||
return {};
|
||||
}
|
||||
|
||||
const results = {};
|
||||
let totalOnlinePlayers = 0;
|
||||
|
||||
const tasks = servers.map(async (srv) => {
|
||||
try {
|
||||
const data = await fetchServerStatus(srv, 2000);
|
||||
results[srv.id] = { ok: true, server: srv, fetched: Date.now(), data };
|
||||
|
||||
if (data) {
|
||||
if (Array.isArray(data.players)) totalOnlinePlayers += data.players.length;
|
||||
else if (typeof data.players === 'number') totalOnlinePlayers += data.players;
|
||||
}
|
||||
} catch (e) {
|
||||
results[srv.id] = { ok: false, server: srv, fetched: Date.now(), error: String(e) };
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(tasks);
|
||||
await chrome.storage.local.set({ serverStatuses: results });
|
||||
|
||||
const badgeText = totalOnlinePlayers > 0 ? String(totalOnlinePlayers) : '';
|
||||
chrome.action.setBadgeText({ text: badgeText });
|
||||
chrome.action.setBadgeBackgroundColor({ color: '#3b82f6' });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function fetchServerStatus(server, timeoutMs = 2000) {
|
||||
let serverObj = typeof server === 'string' ? { id: 'legacy', url: server } : server;
|
||||
let fetchUrl = serverObj.url || '';
|
||||
|
||||
if (fetchUrl) {
|
||||
if (!/^https?:\/\//i.test(fetchUrl)) fetchUrl = 'http://' + fetchUrl;
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeoutMs);
|
||||
const resp = await fetch(fetchUrl, { method: 'GET', signal: controller.signal });
|
||||
clearTimeout(id);
|
||||
|
||||
if (resp.ok) {
|
||||
let text = await resp.text();
|
||||
const lastBrace = text.lastIndexOf('}');
|
||||
if (lastBrace !== -1) text = text.substring(0, lastBrace + 1);
|
||||
text = text.trim();
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
if (parsed && (typeof parsed.online !== 'undefined' || Array.isArray(parsed.players) || parsed.version)) return parsed;
|
||||
} catch {}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const wpSite = serverObj.wpSite ? String(serverObj.wpSite).replace(/\/$/, '') : null;
|
||||
const wpServerId = serverObj.wpServerId ? String(serverObj.wpServerId) : null;
|
||||
if (wpSite && wpServerId) {
|
||||
try {
|
||||
const ajaxUrl = wpSite + '/wp-admin/admin-ajax.php';
|
||||
const body = 'action=mcss_fetch&server_id=' + encodeURIComponent(wpServerId);
|
||||
|
||||
const controller2 = new AbortController();
|
||||
const id2 = setTimeout(() => controller2.abort(), timeoutMs);
|
||||
|
||||
const resp2 = await fetch(ajaxUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body,
|
||||
signal: controller2.signal
|
||||
});
|
||||
clearTimeout(id2);
|
||||
|
||||
if (!resp2.ok) throw new Error('WP AJAX HTTP ' + resp2.status);
|
||||
const json = await resp2.json();
|
||||
if (json && (typeof json.online !== 'undefined' || Array.isArray(json.players) || json.version)) return json;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return { online: false, players: [], max_players: 0, version: 'unknown', motd: '' };
|
||||
}
|
||||
25
BungeeCord-Chrome/manifest.json
Normal file
25
BungeeCord-Chrome/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Minecraft Bungee Status",
|
||||
"description": "Zeigt den Live-Status mehrerer Bungee/Proxy-Server an. Fallback auf WordPress AJAX möglich.",
|
||||
"version": "1.1.0",
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"permissions": [
|
||||
"storage",
|
||||
"alarms"
|
||||
],
|
||||
"host_permissions": [
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
]
|
||||
}
|
||||
220
BungeeCord-Chrome/popup.css
Normal file
220
BungeeCord-Chrome/popup.css
Normal file
@@ -0,0 +1,220 @@
|
||||
:root {
|
||||
--bg: #111827;
|
||||
--panel: #0b1220;
|
||||
--text: #e6eef8;
|
||||
--muted: #9aa7b3;
|
||||
--accent: #3b82f6;
|
||||
--danger: #ef4444;
|
||||
--radius: 8px;
|
||||
--online: #22c55e;
|
||||
--offline: #ef4444;
|
||||
}
|
||||
|
||||
* { 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; }
|
||||
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 {
|
||||
display:flex;
|
||||
gap:12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.server-list {
|
||||
width:45%;
|
||||
background:var(--panel);
|
||||
padding:8px;
|
||||
border-radius:var(--radius);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail {
|
||||
flex:1;
|
||||
background:var(--panel);
|
||||
padding:8px;
|
||||
border-radius:var(--radius);
|
||||
min-height:220px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.detail.full-width {
|
||||
width: 100%;
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.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 button { padding:6px 8px; border-radius:6px; border:none; background:var(--accent); color:white; cursor:pointer; }
|
||||
|
||||
#serversContainer { list-style:none; margin:0; padding:0; max-height:260px; overflow:auto; }
|
||||
.server-item {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
padding:6px;
|
||||
margin-bottom:6px;
|
||||
border-radius:6px;
|
||||
cursor:pointer;
|
||||
background: rgba(255,255,255,0.02);
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.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:12px; color:var(--muted); }
|
||||
.server-item .status-bubble {
|
||||
font-size:12px;
|
||||
padding:3px 6px;
|
||||
border-radius:999px;
|
||||
color:#fff;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.placeholder { color:var(--muted); padding:12px; }
|
||||
.hidden { display:none; }
|
||||
|
||||
/* --- Detail Header (Zeile 1) --- */
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.server-identity h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.server-url {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.status-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* --- Pulsierender Punkt Animation --- */
|
||||
.pulsing-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--offline); /* Standard Offline Rot */
|
||||
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
|
||||
animation: pulse-red 2s infinite;
|
||||
}
|
||||
|
||||
.pulsing-dot.online {
|
||||
background-color: var(--online); /* Online Grün */
|
||||
animation: pulse-green 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-green {
|
||||
0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); }
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: rgba(255,255,255,0.05);
|
||||
padding: 8px;
|
||||
border-radius: var(--radius);
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ---------------------------------- */
|
||||
|
||||
.playerList {
|
||||
list-style:none;
|
||||
padding-left:16px;
|
||||
max-height:140px;
|
||||
overflow:auto;
|
||||
margin:6px 0;
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
gap:8px;
|
||||
}
|
||||
|
||||
.playerList li {
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:4px;
|
||||
font-size:13px;
|
||||
color:var(--text);
|
||||
}
|
||||
|
||||
.player-avatar {
|
||||
width:24px;
|
||||
height:24px;
|
||||
border-radius:4px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.player-avatar:hover { transform: scale(1.1); }
|
||||
|
||||
.detailButtons {
|
||||
display:flex;
|
||||
gap:8px;
|
||||
margin-top:8px;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.detailButtons button {
|
||||
padding:6px 8px;
|
||||
border-radius:6px;
|
||||
border:none;
|
||||
cursor:pointer;
|
||||
transition: opacity 0.2s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.detailButtons button:hover { opacity: 0.9; transform: translateY(-1px); }
|
||||
.detailButtons button:active { 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,
|
||||
.playerList::-webkit-scrollbar-track {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#serversContainer::-webkit-scrollbar-thumb,
|
||||
.playerList::-webkit-scrollbar-thumb {
|
||||
background: var(--muted);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#serversContainer::-webkit-scrollbar-thumb:hover,
|
||||
.playerList::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent);
|
||||
}
|
||||
68
BungeeCord-Chrome/popup.html
Normal file
68
BungeeCord-Chrome/popup.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Bungee Status</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="root">
|
||||
<header>
|
||||
<h1>Bungee Status</h1>
|
||||
<div class="actions">
|
||||
<button id="btnRefresh" title="Jetzt aktualisieren">↻</button>
|
||||
<button id="btnToggleSettings">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="main">
|
||||
<aside class="server-list">
|
||||
<div class="add-form" id="settingsForm">
|
||||
<input id="inputName" placeholder="Name (z. B. Lobby)" />
|
||||
<input id="inputUrl" placeholder="URL (127.0.0.1:9191)" />
|
||||
<input id="inputWpSite" placeholder="WP Site" />
|
||||
<input id="inputWpServerId" placeholder="WP Server ID" />
|
||||
<button id="btnAddServer">Hinzufügen</button>
|
||||
</div>
|
||||
|
||||
<ul id="serversContainer"></ul>
|
||||
</aside>
|
||||
|
||||
<div class="detail">
|
||||
<div id="noSelection" class="placeholder">Wähle einen Server aus der linken Liste.</div>
|
||||
<div id="detailContent" class="detailContent hidden">
|
||||
|
||||
<!-- NEUES LAYOUT ZEILE 1: Name, URL, Status (mit Pulsierendem Punkt) -->
|
||||
<div class="detail-header">
|
||||
<div class="server-identity">
|
||||
<h2 id="detailName"></h2>
|
||||
<span id="detailUrlText" class="server-url"></span>
|
||||
</div>
|
||||
<div class="status-wrapper">
|
||||
<div id="detailPulse" class="pulsing-dot"></div>
|
||||
<span id="detailStatus"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NEUES LAYOUT ZEILE 2: Spieler, Version, Ping -->
|
||||
<div class="detail-stats">
|
||||
<div><strong>Spieler:</strong> <span id="detailPlayers"></span></div>
|
||||
<div><strong>Version:</strong> <span id="detailVersion"></span></div>
|
||||
<div><strong>Ping:</strong> <span id="detailPing">-</span></div>
|
||||
</div>
|
||||
|
||||
<h3>Online Spieler</h3>
|
||||
<ul id="detailPlayerList" class="playerList"></ul>
|
||||
|
||||
<div class="detailButtons">
|
||||
<button id="btnEdit">Bearbeiten</button>
|
||||
<button id="btnDelete">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
353
BungeeCord-Chrome/popup.js
Normal file
353
BungeeCord-Chrome/popup.js
Normal file
@@ -0,0 +1,353 @@
|
||||
const $ = id => document.getElementById(id);
|
||||
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
|
||||
|
||||
const inputName = $('inputName');
|
||||
const inputUrl = $('inputUrl');
|
||||
const inputWpSite = $('inputWpSite');
|
||||
const inputWpServerId = $('inputWpServerId');
|
||||
const btnAdd = $('btnAddServer');
|
||||
const serversContainer = $('serversContainer');
|
||||
const serverListPanel = document.querySelector('.server-list');
|
||||
const btnRefresh = $('btnRefresh');
|
||||
const btnToggleSettings = $('btnToggleSettings');
|
||||
const settingsForm = $('settingsForm');
|
||||
|
||||
const noSelection = $('noSelection');
|
||||
const detailContent = $('detailContent');
|
||||
const detailName = $('detailName');
|
||||
const detailUrlText = $('detailUrlText');
|
||||
const detailStatus = $('detailStatus');
|
||||
const detailPulse = $('detailPulse');
|
||||
const detailVersion = $('detailVersion');
|
||||
const detailPlayers = $('detailPlayers');
|
||||
const detailPing = $('detailPing');
|
||||
const detailPlayerList = $('detailPlayerList');
|
||||
const btnEdit = $('btnEdit');
|
||||
const btnDelete = $('btnDelete');
|
||||
|
||||
let servers = [];
|
||||
let selectedId = null;
|
||||
let statuses = {};
|
||||
let previousStatuses = {};
|
||||
let settingsVisible = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
btnAdd.addEventListener('click', handleAdd);
|
||||
btnRefresh.addEventListener('click', manualRefresh);
|
||||
btnToggleSettings.addEventListener('click', toggleSettings);
|
||||
btnEdit.addEventListener('click', handleEdit);
|
||||
btnDelete.addEventListener('click', handleDelete);
|
||||
|
||||
async function init() {
|
||||
await loadServersFromStorage();
|
||||
await loadStatusesFromStorage();
|
||||
await loadSettingsVisibility();
|
||||
renderServerList();
|
||||
|
||||
if (servers.length === 1) selectedId = servers[0].id;
|
||||
renderDetail(selectedId);
|
||||
|
||||
adjustDetailLayout();
|
||||
}
|
||||
|
||||
// --- Settings Visibility ---
|
||||
async function loadSettingsVisibility() {
|
||||
const obj = await chrome.storage.local.get(['settingsVisible']);
|
||||
settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false;
|
||||
if (!settingsVisible) settingsForm.classList.add('hidden');
|
||||
else settingsForm.classList.remove('hidden');
|
||||
}
|
||||
|
||||
async function saveSettingsVisibility() {
|
||||
await chrome.storage.local.set({ settingsVisible });
|
||||
}
|
||||
|
||||
async function toggleSettings() {
|
||||
settingsVisible = !settingsVisible;
|
||||
settingsForm.classList.toggle('hidden');
|
||||
await saveSettingsVisibility();
|
||||
adjustDetailLayout();
|
||||
}
|
||||
|
||||
// --- Storage ---
|
||||
async function loadServersFromStorage() {
|
||||
const obj = await chrome.storage.local.get(['servers']);
|
||||
servers = obj.servers || [];
|
||||
for (const s of servers) if (!s.id) s.id = uid();
|
||||
await chrome.storage.local.set({ servers });
|
||||
}
|
||||
|
||||
async function saveServersToStorage() {
|
||||
await chrome.storage.local.set({ servers });
|
||||
}
|
||||
|
||||
async function loadStatusesFromStorage() {
|
||||
const obj = await chrome.storage.local.get(['serverStatuses']);
|
||||
statuses = obj.serverStatuses || {};
|
||||
}
|
||||
|
||||
// --- Layout ---
|
||||
function adjustDetailLayout() {
|
||||
const settingsHidden = settingsForm.classList.contains('hidden');
|
||||
detailContent.classList.toggle('full-width', settingsHidden);
|
||||
serverListPanel.classList.toggle('hidden', settingsHidden);
|
||||
btnEdit.style.display = settingsHidden ? 'none' : 'inline-block';
|
||||
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
|
||||
}
|
||||
|
||||
// --- Render Server List ---
|
||||
function renderServerList() {
|
||||
serversContainer.innerHTML = '';
|
||||
if (servers.length === 0) {
|
||||
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const s of servers) {
|
||||
const item = document.createElement('li');
|
||||
item.className = 'server-item';
|
||||
item.dataset.id = s.id;
|
||||
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'meta';
|
||||
const name = document.createElement('div'); name.className = 'name';
|
||||
name.textContent = s.name || '(Kein Name)';
|
||||
|
||||
let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : '');
|
||||
listUrl = listUrl.replace(':9191', '');
|
||||
|
||||
const url = document.createElement('div'); url.className = 'url';
|
||||
url.textContent = listUrl;
|
||||
meta.append(name, url);
|
||||
|
||||
const statusBubble = document.createElement('div');
|
||||
statusBubble.className = 'status-bubble';
|
||||
statusBubble.textContent = '—';
|
||||
statusBubble.style.backgroundColor = 'transparent';
|
||||
item.statusBubble = statusBubble;
|
||||
|
||||
item.append(meta, statusBubble);
|
||||
item.addEventListener('click', () => {
|
||||
selectedId = s.id;
|
||||
renderDetail(selectedId);
|
||||
});
|
||||
|
||||
serversContainer.appendChild(item);
|
||||
}
|
||||
updateServerListStatuses(false);
|
||||
}
|
||||
|
||||
// --- Render Detail ---
|
||||
function renderDetail(id) {
|
||||
if (!id) {
|
||||
noSelection.classList.remove('hidden');
|
||||
detailContent.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
const srv = servers.find(x => x.id === id);
|
||||
if (!srv) {
|
||||
noSelection.classList.remove('hidden');
|
||||
detailContent.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
noSelection.classList.add('hidden');
|
||||
detailContent.classList.remove('hidden');
|
||||
|
||||
detailName.textContent = srv.name || 'Unbenannter Server';
|
||||
|
||||
let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal');
|
||||
urlToShow = urlToShow.replace(':9191', '');
|
||||
|
||||
detailUrlText.textContent = urlToShow;
|
||||
|
||||
updateDetailForServer(srv, true);
|
||||
}
|
||||
|
||||
// --- Update Detail ---
|
||||
function updateDetailForServer(srv, force = false) {
|
||||
const st = statuses[srv.id];
|
||||
const prevSt = previousStatuses[srv.id];
|
||||
|
||||
const statusChanged = force ||
|
||||
!prevSt ||
|
||||
(prevSt.ok !== st?.ok) ||
|
||||
(prevSt.data?.online !== st?.data?.online);
|
||||
|
||||
if (!st || !st.ok || !st.data) {
|
||||
if (statusChanged) {
|
||||
detailStatus.textContent = 'Offline';
|
||||
detailPulse.classList.remove('online');
|
||||
detailVersion.textContent = '-';
|
||||
detailPlayers.textContent = '-';
|
||||
detailPing.textContent = '-';
|
||||
updatePlayerList([]);
|
||||
}
|
||||
} else {
|
||||
const d = st.data;
|
||||
|
||||
if (statusChanged) {
|
||||
detailStatus.textContent = d.online ? 'Online' : 'Offline';
|
||||
if (d.online) {
|
||||
detailPulse.classList.add('online');
|
||||
} else {
|
||||
detailPulse.classList.remove('online');
|
||||
}
|
||||
}
|
||||
|
||||
const newVersion = d.version || 'unknown';
|
||||
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;
|
||||
let newPlayersText = String(playersCount);
|
||||
if (maxPlayers) newPlayersText += ` / ${maxPlayers}`;
|
||||
|
||||
if (force || detailPlayers.textContent !== newPlayersText) {
|
||||
detailPlayers.textContent = newPlayersText;
|
||||
}
|
||||
|
||||
let pingVal = d.ping || d.latency || '-';
|
||||
if (pingVal !== '-' && typeof pingVal === 'number') {
|
||||
pingVal = pingVal + ' ms';
|
||||
}
|
||||
|
||||
if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) {
|
||||
detailPing.textContent = pingVal;
|
||||
}
|
||||
|
||||
const currentPlayers = Array.isArray(d.players) ? d.players : [];
|
||||
const prevPlayers = (prevSt && Array.isArray(prevSt.data.players)) ? prevSt.data.players : [];
|
||||
|
||||
let playersChanged = false;
|
||||
if (force) {
|
||||
playersChanged = true;
|
||||
} else {
|
||||
playersChanged = JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers);
|
||||
}
|
||||
|
||||
if (playersChanged) {
|
||||
updatePlayerList(currentPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
|
||||
}
|
||||
|
||||
// --- Spielerliste ---
|
||||
function updatePlayerList(players) {
|
||||
detailPlayerList.innerHTML = '';
|
||||
if (!players || players.length === 0) {
|
||||
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>';
|
||||
return;
|
||||
}
|
||||
for (const p of players) {
|
||||
const li = document.createElement('li');
|
||||
const name = typeof p === 'object' ? p.name || p.username || p.player || '' : String(p);
|
||||
if (p.avatar) {
|
||||
const img = document.createElement('img');
|
||||
img.src = p.avatar;
|
||||
img.className = 'player-avatar';
|
||||
img.title = name;
|
||||
li.appendChild(img);
|
||||
}
|
||||
detailPlayerList.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Update List (Fix: Syntaxfehler behoben) ---
|
||||
function updateServerListStatuses() {
|
||||
const items = serversContainer.querySelectorAll('.server-item');
|
||||
items.forEach(item => {
|
||||
const s = servers.find(x => x.id === item.dataset.id);
|
||||
if (!s) return;
|
||||
|
||||
const st = statuses[s.id];
|
||||
const prevSt = previousStatuses[s.id];
|
||||
|
||||
const statusChanged = !prevSt ||
|
||||
(prevSt.ok !== st?.ok) ||
|
||||
(prevSt.data?.online !== st?.data?.online);
|
||||
|
||||
if (!statusChanged) return;
|
||||
|
||||
if (!st || !st.ok || !st.data) {
|
||||
item.statusBubble.textContent = 'Offline';
|
||||
item.statusBubble.style.backgroundColor = 'var(--offline)';
|
||||
} else if (st.data.online) {
|
||||
item.statusBubble.textContent = 'Online';
|
||||
item.statusBubble.style.backgroundColor = 'var(--online)';
|
||||
} else {
|
||||
item.statusBubble.textContent = 'Offline';
|
||||
// Klammerfehler korrigiert: ')' zu '}'
|
||||
item.statusBubble.style.backgroundColor = 'var(--offline)';
|
||||
}
|
||||
|
||||
previousStatuses[s.id] = st ? JSON.parse(JSON.stringify(st)) : null;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Add / Edit / Delete ---
|
||||
async function handleAdd() {
|
||||
const name = inputName.value.trim();
|
||||
const url = inputUrl.value.trim();
|
||||
const wpSite = inputWpSite.value.trim();
|
||||
const wpServerId = inputWpServerId.value.trim();
|
||||
if (!url && !wpSite) {
|
||||
alert('Bitte URL oder WP Site angeben');
|
||||
return;
|
||||
}
|
||||
const s = {
|
||||
id: uid(),
|
||||
name: name || url || wpSite,
|
||||
url: url || null,
|
||||
wpSite: wpSite || null,
|
||||
wpServerId: wpServerId || null
|
||||
};
|
||||
servers.push(s);
|
||||
await saveServersToStorage();
|
||||
inputName.value=''; inputUrl.value=''; inputWpSite.value=''; inputWpServerId.value='';
|
||||
renderServerList();
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
|
||||
// --- 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user