diff --git a/BungeeCord-Chrome/background.js b/BungeeCord-Chrome/background.js
new file mode 100644
index 0000000..f4dde14
--- /dev/null
+++ b/BungeeCord-Chrome/background.js
@@ -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: '' };
+}
\ No newline at end of file
diff --git a/BungeeCord-Chrome/manifest.json b/BungeeCord-Chrome/manifest.json
new file mode 100644
index 0000000..1e2972d
--- /dev/null
+++ b/BungeeCord-Chrome/manifest.json
@@ -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://*/*"
+ ]
+}
diff --git a/BungeeCord-Chrome/popup.css b/BungeeCord-Chrome/popup.css
new file mode 100644
index 0000000..b2dffac
--- /dev/null
+++ b/BungeeCord-Chrome/popup.css
@@ -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);
+}
\ No newline at end of file
diff --git a/BungeeCord-Chrome/popup.html b/BungeeCord-Chrome/popup.html
new file mode 100644
index 0000000..4cb393a
--- /dev/null
+++ b/BungeeCord-Chrome/popup.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+Bungee Status
+
+
+
+
+
+ Bungee Status
+
+
+
+
+
+
+
+
+
+
+
Wähle einen Server aus der linken Liste.
+
+
+
+
+
+
+
+
Spieler:
+
Version:
+
Ping: -
+
+
+
Online Spieler
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BungeeCord-Chrome/popup.js b/BungeeCord-Chrome/popup.js
new file mode 100644
index 0000000..f423981
--- /dev/null
+++ b/BungeeCord-Chrome/popup.js
@@ -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 = 'Noch keine Server hinzugefügt.
';
+ 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 = 'Keine Spieler online.';
+ 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);
+ }
+ }
+});
\ No newline at end of file