Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eb462e5a7 | ||
|
|
e5dda9531f | ||
|
|
b851cbc20b | ||
|
|
de04e58242 | ||
|
|
166bfad1d4 | ||
|
|
1de9e4b155 | ||
|
|
501be8649a | ||
|
|
3c80758b12 | ||
|
|
f7e0dcb23d | ||
|
|
65ea93e22d | ||
|
|
fde1947ae7 | ||
|
|
7556521a0e |
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Minecraft Bungee Status",
|
"name": "Minecraft Bungee Status",
|
||||||
"description": "Zeigt den Live-Status mehrerer Bungee/Proxy-Server an. Fallback auf WordPress AJAX möglich.",
|
"description": "Zeigt den Live-Status mehrerer Bungee/Proxy-Server an. Fallback auf WordPress AJAX möglich.",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"action": {
|
"action": {
|
||||||
"default_popup": "popup.html",
|
"default_popup": "popup.html",
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
|
|||||||
+435
-217
@@ -1,217 +1,435 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #111827;
|
--bg: #0d1117;
|
||||||
--panel: #0b1220;
|
--panel: #161b27;
|
||||||
--text: #e6eef8;
|
--panel2: #111620;
|
||||||
--muted: #9aa7b3;
|
--text: #e6eef8;
|
||||||
--accent: #3b82f6;
|
--muted: #5a6a7a;
|
||||||
--danger: #ef4444;
|
--muted2: #8899aa;
|
||||||
--radius: 8px;
|
--accent: #3b82f6;
|
||||||
--online: #22c55e;
|
--radius: 9px;
|
||||||
--offline: #ef4444;
|
--online: #22c55e;
|
||||||
}
|
--offline: #ef4444;
|
||||||
|
--border: rgba(255,255,255,0.07);
|
||||||
* { box-sizing: border-box; }
|
}
|
||||||
|
|
||||||
html, body {
|
* { box-sizing: border-box; }
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
html, body {
|
||||||
font-family: Inter, Arial, sans-serif;
|
margin: 0; padding: 0;
|
||||||
background: var(--bg);
|
font-family: Inter, Arial, sans-serif;
|
||||||
color: var(--text);
|
background: var(--bg);
|
||||||
overflow-x: hidden;
|
color: var(--text);
|
||||||
}
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
.root {
|
|
||||||
width: 520px;
|
.root {
|
||||||
padding: 12px;
|
width: 560px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
/* ── Header ── */
|
||||||
display: flex;
|
header {
|
||||||
align-items: center;
|
background: var(--panel);
|
||||||
justify-content: space-between;
|
display: flex;
|
||||||
margin-bottom: 8px;
|
align-items: center;
|
||||||
}
|
justify-content: space-between;
|
||||||
|
padding: 13px 20px;
|
||||||
header h1 { font-size: 16px; margin: 0; }
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
header .actions button {
|
|
||||||
background: transparent;
|
header h1 {
|
||||||
border: 1px solid var(--muted);
|
font-size: 14px;
|
||||||
color: var(--text);
|
font-weight: 700;
|
||||||
padding: 4px 8px;
|
margin: 0;
|
||||||
border-radius: 6px;
|
letter-spacing: 0.03em;
|
||||||
cursor: pointer;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
header .actions { display: flex; gap: 7px; }
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
header .actions button {
|
||||||
position: relative;
|
background: rgba(255,255,255,0.07);
|
||||||
align-items: flex-start;
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
}
|
color: var(--muted2);
|
||||||
|
border-radius: 7px;
|
||||||
.server-list {
|
width: 32px; height: 32px;
|
||||||
width: 45%;
|
cursor: pointer;
|
||||||
background: var(--panel);
|
font-size: 16px;
|
||||||
padding: 8px;
|
display: flex; align-items: center; justify-content: center;
|
||||||
border-radius: var(--radius);
|
transition: background 0.15s, color 0.15s;
|
||||||
flex-shrink: 0;
|
}
|
||||||
}
|
|
||||||
|
header .actions button:hover {
|
||||||
.detail {
|
background: rgba(255,255,255,0.12);
|
||||||
flex: 1;
|
color: #fff;
|
||||||
background: var(--panel);
|
}
|
||||||
padding: 8px;
|
|
||||||
border-radius: var(--radius);
|
/* ── Main layout ── */
|
||||||
min-height: 220px;
|
.main {
|
||||||
transition: all 0.3s ease;
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
|
}
|
||||||
.detail.full-width {
|
|
||||||
width: 100%;
|
/* ── Settings / add-form panel ── */
|
||||||
flex: 1 1 100%;
|
.settings-panel {
|
||||||
}
|
background: var(--panel2);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
.add-form { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
|
padding: 14px 20px;
|
||||||
.add-form input { padding: 6px 8px; border-radius: 6px; border: 1px solid #21303b; background: #071019; color: var(--text); }
|
display: flex;
|
||||||
.add-form button { padding: 6px 8px; border-radius: 6px; border: none; background: var(--accent); color: white; cursor: pointer; }
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
#serversContainer {
|
}
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
.settings-panel input {
|
||||||
padding: 0;
|
padding: 8px 12px;
|
||||||
}
|
border-radius: 7px;
|
||||||
|
border: 1px solid rgba(255,255,255,0.09);
|
||||||
.server-item {
|
background: rgba(0,0,0,0.3);
|
||||||
display: flex;
|
color: var(--text);
|
||||||
align-items: center;
|
font-size: 13px;
|
||||||
justify-content: space-between;
|
outline: none;
|
||||||
padding: 8px;
|
}
|
||||||
margin-bottom: 6px;
|
|
||||||
border-radius: 6px;
|
.settings-panel input:focus {
|
||||||
cursor: pointer;
|
border-color: var(--accent);
|
||||||
background: rgba(255,255,255,0.02);
|
}
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
.settings-panel button {
|
||||||
|
padding: 8px 12px;
|
||||||
.server-item:hover { background: rgba(255,255,255,0.05); }
|
border-radius: 7px;
|
||||||
.server-item .meta { display: flex; flex-direction: column; }
|
border: none;
|
||||||
.server-item .meta .name { font-weight: 600; }
|
background: var(--accent);
|
||||||
.server-item .meta .url { font-size: 11px; color: var(--muted); }
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
.server-item .status-bubble {
|
font-weight: 600;
|
||||||
font-size: 11px;
|
cursor: pointer;
|
||||||
padding: 2px 8px;
|
}
|
||||||
border-radius: 999px;
|
|
||||||
color: #fff;
|
/* ── Server list ── */
|
||||||
}
|
.server-list-panel {
|
||||||
|
background: var(--panel2);
|
||||||
.placeholder { color: var(--muted); padding: 12px; font-size: 13px; }
|
border-bottom: 1px solid var(--border);
|
||||||
.hidden { display: none; }
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
.detail-header {
|
|
||||||
display: flex;
|
#serversContainer {
|
||||||
justify-content: space-between;
|
list-style: none;
|
||||||
align-items: center;
|
margin: 0; padding: 0;
|
||||||
margin-bottom: 12px;
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
.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; }
|
|
||||||
|
.server-item {
|
||||||
.status-wrapper {
|
display: flex;
|
||||||
display: flex;
|
align-items: center;
|
||||||
align-items: center;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
padding: 9px 12px;
|
||||||
font-weight: 600;
|
border-radius: 8px;
|
||||||
font-size: 13px;
|
cursor: pointer;
|
||||||
}
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
.pulsing-dot {
|
transition: background 0.15s, border-color 0.15s;
|
||||||
width: 10px; height: 10px; border-radius: 50%;
|
}
|
||||||
background-color: var(--offline);
|
|
||||||
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
|
.server-item:hover { background: rgba(255,255,255,0.04); }
|
||||||
animation: pulse-red 2s infinite;
|
.server-item.selected { background: rgba(59,130,246,0.08); border-color: rgba(59,130,246,0.25); }
|
||||||
}
|
|
||||||
.pulsing-dot.online {
|
.server-item .meta { display: flex; flex-direction: column; gap: 2px; }
|
||||||
background-color: var(--online);
|
.server-item .meta .name { font-size: 13px; font-weight: 600; color: #fff; }
|
||||||
animation: pulse-green 2s infinite;
|
.server-item .meta .url { font-size: 11px; color: var(--muted); }
|
||||||
}
|
|
||||||
|
.status-bubble {
|
||||||
@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);} }
|
font-size: 11px;
|
||||||
@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);} }
|
font-weight: 600;
|
||||||
|
padding: 3px 10px;
|
||||||
.detail-stats {
|
border-radius: 999px;
|
||||||
display: flex;
|
color: #fff;
|
||||||
justify-content: space-between;
|
background: transparent;
|
||||||
background: rgba(255,255,255,0.05);
|
}
|
||||||
padding: 10px;
|
|
||||||
border-radius: var(--radius);
|
/* ── Detail panel ── */
|
||||||
margin-bottom: 12px;
|
.detail-panel {
|
||||||
font-size: 13px;
|
padding: 18px 20px;
|
||||||
}
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
.playerList {
|
gap: 16px;
|
||||||
list-style: none;
|
min-height: 200px;
|
||||||
padding: 10px 0;
|
}
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
.placeholder {
|
||||||
flex-wrap: wrap;
|
color: var(--muted);
|
||||||
gap: 12px;
|
font-size: 13px;
|
||||||
justify-content: center; /* Das hier setzt die Köpfe wieder in die Mitte */
|
padding: 24px 0;
|
||||||
}
|
text-align: center;
|
||||||
|
}
|
||||||
.player-item {
|
|
||||||
position: relative;
|
/* Server identity + online badge */
|
||||||
display: flex;
|
.srv-identity {
|
||||||
flex-direction: column;
|
display: flex;
|
||||||
align-items: center;
|
justify-content: space-between;
|
||||||
}
|
align-items: flex-start;
|
||||||
|
}
|
||||||
.player-avatar {
|
|
||||||
width: 42px; height: 42px;
|
.srv-name { font-size: 20px; font-weight: 700; color: #fff; margin: 0 0 4px; }
|
||||||
border-radius: 6px;
|
.srv-url { font-size: 12px; color: var(--muted); }
|
||||||
transition: transform 0.2s ease;
|
|
||||||
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
.online-badge {
|
||||||
border: 2px solid rgba(255,255,255,0.1);
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
.player-avatar:hover {
|
border-radius: 999px;
|
||||||
transform: scale(1.1);
|
padding: 5px 14px;
|
||||||
border-color: var(--accent);
|
font-size: 13px;
|
||||||
}
|
font-weight: 600;
|
||||||
|
}
|
||||||
.player-hover-info {
|
|
||||||
position: absolute;
|
.online-badge.online {
|
||||||
bottom: -28px;
|
background: rgba(34,197,94,0.12);
|
||||||
background: rgba(0, 0, 0, 0.95);
|
border: 1px solid rgba(34,197,94,0.25);
|
||||||
color: white;
|
color: #4ade80;
|
||||||
padding: 3px 10px;
|
}
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 11px;
|
.online-badge.offline {
|
||||||
white-space: nowrap;
|
background: rgba(239,68,68,0.1);
|
||||||
pointer-events: none;
|
border: 1px solid rgba(239,68,68,0.2);
|
||||||
opacity: 0;
|
color: #f87171;
|
||||||
transform: translateY(5px);
|
}
|
||||||
transition: all 0.2s ease;
|
|
||||||
z-index: 100;
|
.pulsing-dot {
|
||||||
border: 1px solid rgba(255,255,255,0.2);
|
width: 7px; height: 7px;
|
||||||
}
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
.player-item:hover .player-hover-info {
|
}
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
.pulsing-dot.online {
|
||||||
}
|
background: var(--online);
|
||||||
|
animation: pulse-green 2s infinite;
|
||||||
.detailButtons { display: flex; gap: 8px; margin-top: 15px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 10px; }
|
}
|
||||||
.detailButtons button { padding: 6px 12px; border-radius: 6px; border: none; cursor: pointer; font-size: 12px; font-weight: 600; }
|
|
||||||
#btnEdit { background: #f59e0b; color: #000; }
|
.pulsing-dot.offline {
|
||||||
#btnDelete { background: var(--danger); color: #fff; }
|
background: var(--offline);
|
||||||
|
animation: pulse-red 2s infinite;
|
||||||
::-webkit-scrollbar { width: 6px; }
|
}
|
||||||
::-webkit-scrollbar-track { background: transparent; }
|
|
||||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
|
@keyframes pulse-green {
|
||||||
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
|
0%,100% { box-shadow: 0 0 0 0 rgba(34,197,94,0.5); }
|
||||||
|
50% { box-shadow: 0 0 0 5px rgba(34,197,94,0); }
|
||||||
|
}
|
||||||
|
@keyframes pulse-red {
|
||||||
|
0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.5); }
|
||||||
|
50% { box-shadow: 0 0 0 5px rgba(239,68,68,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MOTD */
|
||||||
|
.motd-bar {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted2);
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-left: 2px solid rgba(59,130,246,0.35);
|
||||||
|
font-style: italic;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats row – 3 gleich breite Karten */
|
||||||
|
.stats-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-val {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-val.small {
|
||||||
|
font-size: 12px;
|
||||||
|
padding-top: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--muted2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section label */
|
||||||
|
.section-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
margin-bottom: 9px;
|
||||||
|
padding-top: 6px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sub-server chips – immer 3 pro Zeile */
|
||||||
|
.sub-servers {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-chip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--panel);
|
||||||
|
color: var(--muted2);
|
||||||
|
cursor: default;
|
||||||
|
transition: background 0.15s;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-chip.active {
|
||||||
|
background: rgba(59,130,246,0.1);
|
||||||
|
border-color: rgba(59,130,246,0.3);
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-chip-dot {
|
||||||
|
width: 7px; height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #2a3040;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-chip.active .sub-chip-dot {
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-chip-name {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-chip-count {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0.65;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Players grid – 2 Spalten */
|
||||||
|
.players-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 10px 14px;
|
||||||
|
transition: background 0.15s;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-card:hover {
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-avatar {
|
||||||
|
width: 40px; height: 40px;
|
||||||
|
border-radius: 7px;
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-info { flex: 1; min-width: 0; }
|
||||||
|
|
||||||
|
.player-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-prefix {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted2);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-server-tag {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 3px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: rgba(59,130,246,0.18);
|
||||||
|
color: #93c5fd;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 2px 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit/Delete buttons */
|
||||||
|
.detail-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-buttons button {
|
||||||
|
padding: 7px 16px;
|
||||||
|
border-radius: 7px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnEdit { background: rgba(245,158,11,0.15); color: #fbbf24; border: 1px solid rgba(245,158,11,0.25); }
|
||||||
|
#btnDelete { background: rgba(239,68,68,0.12); color: #f87171; border: 1px solid rgba(239,68,68,0.2); }
|
||||||
|
|
||||||
|
.hidden { display: none !important; }
|
||||||
|
|
||||||
|
::-webkit-scrollbar { width: 5px; }
|
||||||
|
::-webkit-scrollbar-track { background: transparent; }
|
||||||
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 10px; }
|
||||||
|
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
|
||||||
|
|||||||
@@ -1,66 +1,97 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Bungee Status</title>
|
<title>Bungee Status</title>
|
||||||
<link rel="stylesheet" href="popup.css">
|
<link rel="stylesheet" href="popup.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<header>
|
|
||||||
<h1>Bungee Status</h1>
|
<header>
|
||||||
<div class="actions">
|
<h1>Bungee Status</h1>
|
||||||
<button id="btnRefresh" title="Jetzt aktualisieren">↻</button>
|
<div class="actions">
|
||||||
<button id="btnToggleSettings">⚙</button>
|
<button id="btnRefresh" title="Jetzt aktualisieren">↻</button>
|
||||||
</div>
|
<button id="btnToggleSettings" title="Einstellungen">⚙</button>
|
||||||
</header>
|
</div>
|
||||||
|
</header>
|
||||||
<section class="main">
|
|
||||||
<aside class="server-list">
|
<div class="main">
|
||||||
<div class="add-form" id="settingsForm">
|
|
||||||
<input id="inputName" placeholder="Name (z. B. Lobby)" />
|
<div id="settingsForm" class="settings-panel hidden">
|
||||||
<input id="inputUrl" placeholder="URL (127.0.0.1:9191)" />
|
<h3 id="settingsTitle">Neuen Server hinzufügen</h3>
|
||||||
<input id="inputWpSite" placeholder="WP Site" />
|
<input id="inputName" placeholder="Name (z. B. Lobby)" />
|
||||||
<input id="inputWpServerId" placeholder="WP Server ID" />
|
<input id="inputUrl" placeholder="URL (z. B. 192.168.1.1:9191)" />
|
||||||
<button id="btnAddServer">Hinzufügen</button>
|
<input id="inputWpSite" placeholder="WP Site (z. B. https://meine-seite.de)" />
|
||||||
</div>
|
<input id="inputWpServerId" placeholder="WP Server ID" />
|
||||||
|
<button id="btnAddServer">Hinzufügen</button>
|
||||||
<ul id="serversContainer"></ul>
|
</div>
|
||||||
</aside>
|
|
||||||
|
<div id="serverListPanel" class="server-list-panel hidden">
|
||||||
<div class="detail">
|
<ul id="serversContainer"></ul>
|
||||||
<div id="noSelection" class="placeholder">Wähle einen Server aus der linken Liste.</div>
|
</div>
|
||||||
<div id="detailContent" class="detailContent hidden">
|
|
||||||
|
<div class="detail-panel">
|
||||||
<div class="detail-header">
|
|
||||||
<div class="server-identity">
|
<div id="noSelection" class="placeholder">Noch kein Server ausgewählt.</div>
|
||||||
<h2 id="detailName"></h2>
|
|
||||||
<span id="detailUrlText" class="server-url"></span>
|
<div id="detailContent" class="hidden">
|
||||||
</div>
|
|
||||||
<div class="status-wrapper">
|
<!-- Server Name + Status Badge -->
|
||||||
<div id="detailPulse" class="pulsing-dot"></div>
|
<div class="srv-identity">
|
||||||
<span id="detailStatus"></span>
|
<div>
|
||||||
</div>
|
<div class="srv-name" id="detailName"></div>
|
||||||
</div>
|
<div class="srv-url" id="detailUrlText"></div>
|
||||||
|
</div>
|
||||||
<div class="detail-stats">
|
<div class="online-badge offline" id="statusBadge">
|
||||||
<div><strong>Spieler:</strong> <span id="detailPlayers"></span></div>
|
<div class="pulsing-dot offline" id="detailPulse"></div>
|
||||||
<div><strong>Version:</strong> <span id="detailVersion"></span></div>
|
<span id="detailStatus">Offline</span>
|
||||||
<div><strong>Ping:</strong> <span id="detailPing">-</span></div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Online Spieler</h3>
|
<!-- MOTD -->
|
||||||
<ul id="detailPlayerList" class="playerList"></ul>
|
<div id="motdBar" class="motd-bar hidden"></div>
|
||||||
|
|
||||||
<div class="detailButtons">
|
<!-- Stats: Spieler / Version / Ping -->
|
||||||
<button id="btnEdit">Bearbeiten</button>
|
<div class="stats-row">
|
||||||
<button id="btnDelete">Löschen</button>
|
<div class="stat-card">
|
||||||
</div>
|
<div class="stat-label">Spieler</div>
|
||||||
</div>
|
<div class="stat-val" id="detailPlayers">-</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="stat-card">
|
||||||
</div>
|
<div class="stat-label">Version</div>
|
||||||
<script src="popup.js"></script>
|
<div class="stat-val small" id="detailVersion">-</div>
|
||||||
</body>
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-label">Ping</div>
|
||||||
|
<div class="stat-val" id="detailPing">-</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sub-Server -->
|
||||||
|
<div id="backendSection" class="hidden">
|
||||||
|
<div class="section-label">Sub-Server</div>
|
||||||
|
<div class="sub-servers" id="backendServersList"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Online Spieler -->
|
||||||
|
<div>
|
||||||
|
<div class="section-label">Online Spieler</div>
|
||||||
|
<div class="players-grid" id="detailPlayerList"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit / Delete -->
|
||||||
|
<div class="detail-buttons hidden" id="detailButtons">
|
||||||
|
<button id="btnEdit">Bearbeiten</button>
|
||||||
|
<button id="btnDelete">Löschen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+429
-334
@@ -1,335 +1,430 @@
|
|||||||
//popup.js
|
// popup.js
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
|
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
|
||||||
|
|
||||||
const inputName = $('inputName');
|
let servers = [];
|
||||||
const inputUrl = $('inputUrl');
|
let selectedId = null;
|
||||||
const inputWpSite = $('inputWpSite');
|
let statuses = {};
|
||||||
const inputWpServerId = $('inputWpServerId');
|
let previousStatuses = {};
|
||||||
const btnAdd = $('btnAddServer');
|
let settingsVisible = false;
|
||||||
const serversContainer = $('serversContainer');
|
let editingId = null; // Welcher Server gerade bearbeitet wird
|
||||||
const serverListPanel = document.querySelector('.server-list');
|
|
||||||
const btnRefresh = $('btnRefresh');
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
const btnToggleSettings = $('btnToggleSettings');
|
$('btnAdd')?.addEventListener('click', handleAdd);
|
||||||
const settingsForm = $('settingsForm');
|
$('btnAddServer')?.addEventListener('click', handleAdd);
|
||||||
|
$('btnRefresh').addEventListener('click', manualRefresh);
|
||||||
const noSelection = $('noSelection');
|
$('btnToggleSettings').addEventListener('click', toggleSettings);
|
||||||
const detailContent = $('detailContent');
|
$('btnEdit').addEventListener('click', handleEdit);
|
||||||
const detailName = $('detailName');
|
$('btnDelete').addEventListener('click', handleDelete);
|
||||||
const detailUrlText = $('detailUrlText');
|
|
||||||
const detailStatus = $('detailStatus');
|
async function init() {
|
||||||
const detailPulse = $('detailPulse');
|
await loadServersFromStorage();
|
||||||
const detailVersion = $('detailVersion');
|
await loadStatusesFromStorage();
|
||||||
const detailPlayers = $('detailPlayers');
|
await loadSettingsVisibility();
|
||||||
const detailPing = $('detailPing');
|
renderServerList();
|
||||||
const detailPlayerList = $('detailPlayerList');
|
if (servers.length === 1) selectedId = servers[0].id;
|
||||||
const btnEdit = $('btnEdit');
|
renderDetail(selectedId);
|
||||||
const btnDelete = $('btnDelete');
|
}
|
||||||
|
|
||||||
let servers = [];
|
// ── Minecraft color code parser ──
|
||||||
let selectedId = null;
|
function parseMinecraftColors(text) {
|
||||||
let statuses = {};
|
if (!text) return '';
|
||||||
let previousStatuses = {};
|
const map = {
|
||||||
let settingsVisible = false;
|
'§0':'#000000','§1':'#0000AA','§2':'#00AA00','§3':'#00AAAA',
|
||||||
|
'§4':'#AA0000','§5':'#AA00AA','§6':'#FFAA00','§7':'#AAAAAA',
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
'§8':'#555555','§9':'#5555FF','§a':'#55FF55','§b':'#55FFFF',
|
||||||
btnAdd.addEventListener('click', handleAdd);
|
'§c':'#FF5555','§d':'#FF55FF','§e':'#FFFF55','§f':'#FFFFFF',
|
||||||
btnRefresh.addEventListener('click', manualRefresh);
|
'&0':'#000000','&1':'#0000AA','&2':'#00AA00','&3':'#00AAAA',
|
||||||
btnToggleSettings.addEventListener('click', toggleSettings);
|
'&4':'#AA0000','&5':'#AA00AA','&6':'#FFAA00','&7':'#AAAAAA',
|
||||||
btnEdit.addEventListener('click', handleEdit);
|
'&8':'#555555','&9':'#5555FF','&a':'#55FF55','&b':'#55FFFF',
|
||||||
btnDelete.addEventListener('click', handleDelete);
|
'&c':'#FF5555','&d':'#FF55FF','&e':'#FFFF55','&f':'#FFFFFF'
|
||||||
|
};
|
||||||
async function init() {
|
let html = text.replace(/[<>]/g, '');
|
||||||
await loadServersFromStorage();
|
let result = '';
|
||||||
await loadStatusesFromStorage();
|
let i = 0;
|
||||||
await loadSettingsVisibility();
|
while (i < html.length) {
|
||||||
renderServerList();
|
const ch = html[i];
|
||||||
|
if ((ch === '§' || ch === '&') && i + 1 < html.length) {
|
||||||
if (servers.length === 1) selectedId = servers[0].id;
|
const code = ch + html[i+1].toLowerCase();
|
||||||
renderDetail(selectedId);
|
if (map[code]) {
|
||||||
|
result += `<span style="color:${map[code]}">`;
|
||||||
adjustDetailLayout();
|
i += 2;
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
// --- NEU: Minecraft Color Parser (Eingebaut ohne Löschung) ---
|
}
|
||||||
function parseMinecraftColors(text) {
|
result += html[i];
|
||||||
if (!text) return '';
|
i++;
|
||||||
const map = {
|
}
|
||||||
'&0': '#000000', '&1': '#0000AA', '&2': '#00AA00', '&3': '#00AAAA',
|
return result;
|
||||||
'&4': '#AA0000', '&5': '#AA00AA', '&6': '#FFAA00', '&7': '#AAAAAA',
|
}
|
||||||
'&8': '#555555', '&9': '#5555FF', '&a': '#55FF55', '&b': '#55FFFF',
|
|
||||||
'&c': '#FF5555', '&d': '#FF55FF', '&e': '#FFFF55', '&f': '#FFFFFF'
|
function get3DAvatarUrl(name, uuid = null) {
|
||||||
};
|
return `https://mc-heads.net/head/${encodeURIComponent(name)}/64`;
|
||||||
let html = text.replace(/[<>]/g, '');
|
}
|
||||||
Object.keys(map).forEach(code => {
|
|
||||||
const color = map[code];
|
// ── Storage ──
|
||||||
const regex = new RegExp(code, 'g');
|
async function loadSettingsVisibility() {
|
||||||
html = html.replace(regex, `</span><span style="color: ${color}">`);
|
const obj = await chrome.storage.local.get(['settingsVisible']);
|
||||||
});
|
settingsVisible = !!obj.settingsVisible;
|
||||||
return `<span>${html}</span>`.replace(/&l/g, '<b>').replace(/&r/g, '</b>');
|
applySettingsVisibility();
|
||||||
}
|
}
|
||||||
|
async function saveSettingsVisibility() {
|
||||||
// --- 3D Avatar URL Generator ---
|
await chrome.storage.local.set({ settingsVisible });
|
||||||
function get3DAvatarUrl(playerName, uuid = null) {
|
}
|
||||||
const isBedrock = playerName.includes('.') || (uuid && uuid.startsWith('xuid'));
|
function applySettingsVisibility() {
|
||||||
if (isBedrock) {
|
$('settingsForm').classList.toggle('hidden', !settingsVisible);
|
||||||
if (uuid && uuid.length > 0) {
|
$('serverListPanel').classList.toggle('hidden', !settingsVisible);
|
||||||
return `https://mc-heads.net/head/${encodeURIComponent(uuid)}/64`;
|
$('detailButtons').classList.toggle('hidden', !settingsVisible);
|
||||||
}
|
}
|
||||||
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
|
async function toggleSettings() {
|
||||||
} else {
|
settingsVisible = !settingsVisible;
|
||||||
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
|
applySettingsVisibility();
|
||||||
}
|
await saveSettingsVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSettingsVisibility() {
|
async function loadServersFromStorage() {
|
||||||
const obj = await chrome.storage.local.get(['settingsVisible']);
|
const obj = await chrome.storage.local.get(['servers']);
|
||||||
settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false;
|
servers = obj.servers || [];
|
||||||
if (!settingsVisible) settingsForm.classList.add('hidden');
|
for (const s of servers) if (!s.id) s.id = uid();
|
||||||
else settingsForm.classList.remove('hidden');
|
await chrome.storage.local.set({ servers });
|
||||||
}
|
}
|
||||||
|
async function saveServersToStorage() {
|
||||||
async function saveSettingsVisibility() {
|
await chrome.storage.local.set({ servers });
|
||||||
await chrome.storage.local.set({ settingsVisible });
|
}
|
||||||
}
|
async function loadStatusesFromStorage() {
|
||||||
|
const obj = await chrome.storage.local.get(['serverStatuses']);
|
||||||
async function toggleSettings() {
|
statuses = obj.serverStatuses || {};
|
||||||
settingsVisible = !settingsVisible;
|
}
|
||||||
settingsForm.classList.toggle('hidden');
|
|
||||||
await saveSettingsVisibility();
|
// ── Server list ──
|
||||||
adjustDetailLayout();
|
function renderServerList() {
|
||||||
}
|
const container = $('serversContainer');
|
||||||
|
container.innerHTML = '';
|
||||||
async function loadServersFromStorage() {
|
if (servers.length === 0) {
|
||||||
const obj = await chrome.storage.local.get(['servers']);
|
container.innerHTML = '<div class="placeholder" style="padding:8px 0;">Noch kein Server hinzugefügt.</div>';
|
||||||
servers = obj.servers || [];
|
return;
|
||||||
for (const s of servers) if (!s.id) s.id = uid();
|
}
|
||||||
await chrome.storage.local.set({ servers });
|
for (const s of servers) {
|
||||||
}
|
const li = document.createElement('li');
|
||||||
|
li.className = 'server-item' + (s.id === selectedId ? ' selected' : '');
|
||||||
async function saveServersToStorage() {
|
li.dataset.id = s.id;
|
||||||
await chrome.storage.local.set({ servers });
|
|
||||||
}
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'meta';
|
||||||
async function loadStatusesFromStorage() {
|
|
||||||
const obj = await chrome.storage.local.get(['serverStatuses']);
|
const nameEl = document.createElement('div');
|
||||||
statuses = obj.serverStatuses || {};
|
nameEl.className = 'name';
|
||||||
}
|
nameEl.textContent = s.name || '(Kein Name)';
|
||||||
|
|
||||||
function adjustDetailLayout() {
|
let urlText = s.url || (s.wpSite || '');
|
||||||
const settingsHidden = settingsForm.classList.contains('hidden');
|
urlText = urlText.replace(':9191', '');
|
||||||
detailContent.classList.toggle('full-width', settingsHidden);
|
const urlEl = document.createElement('div');
|
||||||
serverListPanel.classList.toggle('hidden', settingsHidden);
|
urlEl.className = 'url';
|
||||||
btnEdit.style.display = settingsHidden ? 'none' : 'inline-block';
|
urlEl.textContent = urlText;
|
||||||
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
|
|
||||||
}
|
meta.append(nameEl, urlEl);
|
||||||
|
|
||||||
function renderServerList() {
|
const bubble = document.createElement('div');
|
||||||
serversContainer.innerHTML = '';
|
bubble.className = 'status-bubble';
|
||||||
if (servers.length === 0) {
|
bubble.textContent = '—';
|
||||||
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
|
li._bubble = bubble;
|
||||||
return;
|
|
||||||
}
|
li.append(meta, bubble);
|
||||||
for (const s of servers) {
|
li.addEventListener('click', () => {
|
||||||
const item = document.createElement('li');
|
selectedId = s.id;
|
||||||
item.className = 'server-item';
|
document.querySelectorAll('.server-item').forEach(el => el.classList.remove('selected'));
|
||||||
item.dataset.id = s.id;
|
li.classList.add('selected');
|
||||||
const meta = document.createElement('div');
|
renderDetail(selectedId);
|
||||||
meta.className = 'meta';
|
});
|
||||||
const name = document.createElement('div');
|
container.appendChild(li);
|
||||||
name.className = 'name';
|
}
|
||||||
name.textContent = s.name || '(Kein Name)';
|
updateServerListStatuses();
|
||||||
let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : '');
|
}
|
||||||
listUrl = listUrl.replace(':9191', '');
|
|
||||||
const url = document.createElement('div');
|
// ── Detail ──
|
||||||
url.className = 'url';
|
function renderDetail(id) {
|
||||||
url.textContent = listUrl;
|
if (!id) {
|
||||||
meta.append(name, url);
|
$('noSelection').classList.remove('hidden');
|
||||||
const statusBubble = document.createElement('div');
|
$('detailContent').classList.add('hidden');
|
||||||
statusBubble.className = 'status-bubble';
|
return;
|
||||||
statusBubble.textContent = '—';
|
}
|
||||||
statusBubble.style.backgroundColor = 'transparent';
|
const srv = servers.find(x => x.id === id);
|
||||||
item.statusBubble = statusBubble;
|
if (!srv) {
|
||||||
item.append(meta, statusBubble);
|
$('noSelection').classList.remove('hidden');
|
||||||
item.addEventListener('click', () => {
|
$('detailContent').classList.add('hidden');
|
||||||
selectedId = s.id;
|
return;
|
||||||
renderDetail(selectedId);
|
}
|
||||||
});
|
$('noSelection').classList.add('hidden');
|
||||||
serversContainer.appendChild(item);
|
$('detailContent').classList.remove('hidden');
|
||||||
}
|
|
||||||
updateServerListStatuses();
|
$('detailName').textContent = srv.name || 'Unbenannter Server';
|
||||||
}
|
let urlToShow = srv.url || (srv.wpSite || 'Lokal');
|
||||||
|
urlToShow = urlToShow.replace(':9191', '');
|
||||||
// --- Fortsetzung popup.js ---
|
$('detailUrlText').textContent = urlToShow;
|
||||||
|
|
||||||
function renderDetail(id) {
|
updateDetailForServer(srv, true);
|
||||||
if (!id) {
|
}
|
||||||
noSelection.classList.remove('hidden');
|
|
||||||
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);
|
const changed = force || JSON.stringify(st) !== JSON.stringify(prevSt);
|
||||||
if (!srv) {
|
if (!changed) return;
|
||||||
noSelection.classList.remove('hidden');
|
|
||||||
detailContent.classList.add('hidden');
|
const badge = $('statusBadge');
|
||||||
return;
|
const pulse = $('detailPulse');
|
||||||
}
|
|
||||||
noSelection.classList.add('hidden');
|
if (!st || !st.ok || !st.data) {
|
||||||
detailContent.classList.remove('hidden');
|
badge.className = 'online-badge offline';
|
||||||
detailName.textContent = srv.name || 'Unbenannter Server';
|
pulse.className = 'pulsing-dot offline';
|
||||||
let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal');
|
$('detailStatus').textContent = 'Offline';
|
||||||
urlToShow = urlToShow.replace(':9191', '');
|
$('detailVersion').textContent = '-';
|
||||||
detailUrlText.textContent = urlToShow;
|
$('detailPlayers').textContent = '-';
|
||||||
updateDetailForServer(srv, true);
|
$('detailPing').textContent = '-';
|
||||||
}
|
$('motdBar').classList.add('hidden');
|
||||||
|
updatePlayerList([]);
|
||||||
function updateDetailForServer(srv, force = false) {
|
updateBackendServers(null);
|
||||||
const st = statuses[srv.id];
|
} else {
|
||||||
const prevSt = previousStatuses[srv.id];
|
const d = st.data;
|
||||||
const statusChanged = force || !prevSt || (prevSt.ok !== st?.ok) || (prevSt.data?.online !== st?.data?.online);
|
const isOnline = !!d.online;
|
||||||
|
|
||||||
if (!st || !st.ok || !st.data) {
|
badge.className = 'online-badge ' + (isOnline ? 'online' : 'offline');
|
||||||
if (statusChanged) {
|
pulse.className = 'pulsing-dot ' + (isOnline ? 'online' : 'offline');
|
||||||
detailStatus.textContent = 'Offline';
|
$('detailStatus').textContent = isOnline ? 'Online' : 'Offline';
|
||||||
detailPulse.classList.remove('online');
|
$('detailVersion').textContent = d.version || 'unknown';
|
||||||
detailVersion.textContent = '-';
|
|
||||||
detailPlayers.textContent = '-';
|
const count = Array.isArray(d.players) ? d.players.length
|
||||||
detailPing.textContent = '-';
|
: (typeof d.players === 'number' ? d.players : 0);
|
||||||
updatePlayerList([]);
|
const max = d.max_players;
|
||||||
}
|
$('detailPlayers').textContent = max && max !== '-1' ? `${count} / ${max}` : String(count);
|
||||||
} else {
|
|
||||||
const d = st.data;
|
const ping = d.ping || d.latency;
|
||||||
if (statusChanged) {
|
$('detailPing').textContent = typeof ping === 'number' ? ping + ' ms' : '-';
|
||||||
detailStatus.textContent = d.online ? 'Online' : 'Offline';
|
|
||||||
if (d.online) detailPulse.classList.add('online');
|
const motdRaw = d.motd || (d.network && d.network.motd) || '';
|
||||||
else detailPulse.classList.remove('online');
|
if (motdRaw) {
|
||||||
}
|
const motdEl = $('motdBar');
|
||||||
const newVersion = d.version || 'unknown';
|
motdEl.innerHTML = parseMinecraftColors(motdRaw);
|
||||||
if (force || detailVersion.textContent !== newVersion) detailVersion.textContent = newVersion;
|
motdEl.classList.remove('hidden');
|
||||||
const playersCount = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0);
|
} else {
|
||||||
const maxPlayers = d.max_players;
|
$('motdBar').classList.add('hidden');
|
||||||
let newPlayersText = String(playersCount);
|
}
|
||||||
if (maxPlayers && maxPlayers !== '-1') newPlayersText += ` / ${maxPlayers}`;
|
|
||||||
if (force || detailPlayers.textContent !== newPlayersText) detailPlayers.textContent = newPlayersText;
|
const currentPlayers = Array.isArray(d.players) ? d.players : [];
|
||||||
let pingVal = d.ping || d.latency || '-';
|
updatePlayerList(currentPlayers);
|
||||||
if (pingVal !== '-' && typeof pingVal === 'number') pingVal = pingVal + ' ms';
|
updateBackendServers(d.network?.backend_servers || null);
|
||||||
if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) detailPing.textContent = pingVal;
|
}
|
||||||
|
|
||||||
const currentPlayers = Array.isArray(d.players) ? d.players : [];
|
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
|
||||||
const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : [];
|
}
|
||||||
let playersChanged = force || JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers);
|
|
||||||
if (playersChanged) updatePlayerList(currentPlayers);
|
// ── Sub-Server chips ──
|
||||||
}
|
function updateBackendServers(list) {
|
||||||
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
|
const section = $('backendSection');
|
||||||
}
|
const container = $('backendServersList');
|
||||||
|
if (!list || list.length === 0) {
|
||||||
// --- Spielerliste mit Prefix-Hover und Farbsupport ---
|
section.classList.add('hidden');
|
||||||
function updatePlayerList(players) {
|
container.innerHTML = '';
|
||||||
detailPlayerList.innerHTML = '';
|
return;
|
||||||
if (!players || players.length === 0) {
|
}
|
||||||
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>';
|
section.classList.remove('hidden');
|
||||||
return;
|
container.innerHTML = '';
|
||||||
}
|
for (const bs of list) {
|
||||||
|
const count = bs.online_players || 0;
|
||||||
for (const p of players) {
|
const chip = document.createElement('div');
|
||||||
const li = document.createElement('li');
|
chip.className = 'sub-chip' + (count > 0 ? ' active' : '');
|
||||||
li.className = 'player-item'; // CSS Klasse für den Hover-Container
|
|
||||||
|
const dot = document.createElement('span');
|
||||||
let name = '';
|
dot.className = 'sub-chip-dot';
|
||||||
let uuid = null;
|
|
||||||
let prefix = '';
|
const nameEl = document.createElement('span');
|
||||||
|
nameEl.className = 'sub-chip-name';
|
||||||
if (typeof p === 'object') {
|
nameEl.textContent = bs.name;
|
||||||
name = p.name || p.username || p.player || '';
|
|
||||||
uuid = p.uuid || null;
|
const countEl = document.createElement('span');
|
||||||
prefix = p.prefix || '';
|
countEl.className = 'sub-chip-count';
|
||||||
} else {
|
countEl.textContent = count > 0 ? count : 'leer';
|
||||||
name = String(p);
|
|
||||||
}
|
chip.append(dot, nameEl, countEl);
|
||||||
|
container.appendChild(chip);
|
||||||
const img = document.createElement('img');
|
}
|
||||||
img.src = (typeof p === 'object' && p.avatar) ? p.avatar : get3DAvatarUrl(name, uuid);
|
}
|
||||||
img.className = 'player-avatar';
|
|
||||||
img.loading = 'lazy';
|
// ── Player list ──
|
||||||
img.onerror = function() {
|
function updatePlayerList(players) {
|
||||||
this.onerror = null;
|
const grid = $('detailPlayerList');
|
||||||
this.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/64`;
|
grid.innerHTML = '';
|
||||||
};
|
|
||||||
|
if (!players || players.length === 0) {
|
||||||
// Das Hover-Element mit farbigem Prefix + Name
|
grid.innerHTML = '<div class="placeholder">Keine Spieler online.</div>';
|
||||||
const info = document.createElement('div');
|
return;
|
||||||
info.className = 'player-hover-info';
|
}
|
||||||
info.innerHTML = `${parseMinecraftColors(prefix)} ${name}`.trim();
|
|
||||||
|
for (const p of players) {
|
||||||
li.appendChild(img);
|
const card = document.createElement('div');
|
||||||
li.appendChild(info);
|
card.className = 'player-card';
|
||||||
detailPlayerList.appendChild(li);
|
|
||||||
}
|
let name = '', uuid = null, prefix = '', serverName = null;
|
||||||
}
|
|
||||||
|
if (typeof p === 'object') {
|
||||||
function updateServerListStatuses() {
|
name = p.name || p.username || p.player || '';
|
||||||
const items = serversContainer.querySelectorAll('.server-item');
|
uuid = p.uuid || null;
|
||||||
items.forEach(item => {
|
prefix = p.prefix || p.group || '';
|
||||||
const s = servers.find(x => x.id === item.dataset.id);
|
serverName = p.server || p.current_server || p.connected_server || null;
|
||||||
if (!s) return;
|
} else {
|
||||||
const st = statuses[s.id];
|
name = String(p);
|
||||||
if (!st || !st.ok || !st.data) {
|
}
|
||||||
item.statusBubble.textContent = 'Offline';
|
|
||||||
item.statusBubble.style.backgroundColor = 'var(--offline)';
|
const img = document.createElement('img');
|
||||||
} else if (st.data.online) {
|
img.src = (typeof p === 'object' && p.avatar) ? p.avatar : get3DAvatarUrl(name, uuid);
|
||||||
item.statusBubble.textContent = 'Online';
|
img.className = 'player-avatar';
|
||||||
item.statusBubble.style.backgroundColor = 'var(--online)';
|
img.loading = 'lazy';
|
||||||
} else {
|
img.onerror = function() {
|
||||||
item.statusBubble.textContent = 'Offline';
|
this.onerror = null;
|
||||||
item.statusBubble.style.backgroundColor = 'var(--offline)';
|
this.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/64`;
|
||||||
}
|
};
|
||||||
});
|
|
||||||
}
|
const info = document.createElement('div');
|
||||||
|
info.className = 'player-info';
|
||||||
async function handleAdd() {
|
|
||||||
const name = inputName.value.trim();
|
if (prefix) {
|
||||||
const url = inputUrl.value.trim();
|
const prefixEl = document.createElement('div');
|
||||||
const wpSite = inputWpSite.value.trim();
|
prefixEl.className = 'player-prefix';
|
||||||
const wpServerId = inputWpServerId.value.trim();
|
prefixEl.innerHTML = parseMinecraftColors(prefix);
|
||||||
if (!url && !wpSite) return;
|
info.appendChild(prefixEl);
|
||||||
const s = { id: uid(), name: name || url || wpSite, url: url || null, wpSite: wpSite || null, wpServerId: wpServerId || null };
|
}
|
||||||
servers.push(s);
|
|
||||||
await saveServersToStorage();
|
const nameEl = document.createElement('div');
|
||||||
inputName.value = inputUrl.value = inputWpSite.value = inputWpServerId.value = '';
|
nameEl.className = 'player-name';
|
||||||
renderServerList();
|
nameEl.textContent = name;
|
||||||
}
|
info.appendChild(nameEl);
|
||||||
|
|
||||||
async function handleEdit() {
|
if (serverName) {
|
||||||
if (!selectedId) return;
|
const tag = document.createElement('span');
|
||||||
const srv = servers.find(s => s.id === selectedId);
|
tag.className = 'player-server-tag';
|
||||||
if (!srv) return;
|
tag.textContent = serverName;
|
||||||
const newName = prompt('Name:', srv.name) || srv.name;
|
info.appendChild(tag);
|
||||||
srv.name = newName.trim();
|
}
|
||||||
await saveServersToStorage();
|
|
||||||
renderServerList();
|
card.append(img, info);
|
||||||
renderDetail(selectedId);
|
grid.appendChild(card);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async function handleDelete() {
|
|
||||||
if (!selectedId || !confirm('Server wirklich löschen?')) return;
|
// ── Server list status bubbles ──
|
||||||
servers = servers.filter(s => s.id !== selectedId);
|
function updateServerListStatuses() {
|
||||||
selectedId = null;
|
const items = $('serversContainer').querySelectorAll('.server-item');
|
||||||
await saveServersToStorage();
|
items.forEach(item => {
|
||||||
renderServerList();
|
const s = servers.find(x => x.id === item.dataset.id);
|
||||||
renderDetail(null);
|
if (!s || !item._bubble) return;
|
||||||
}
|
const st = statuses[s.id];
|
||||||
|
if (!st || !st.ok || !st.data || !st.data.online) {
|
||||||
async function manualRefresh() {
|
item._bubble.textContent = 'Offline';
|
||||||
try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {}
|
item._bubble.style.background = 'rgba(239,68,68,0.15)';
|
||||||
}
|
item._bubble.style.color = '#f87171';
|
||||||
|
} else {
|
||||||
chrome.storage.onChanged.addListener((changes, area) => {
|
item._bubble.textContent = 'Online';
|
||||||
if (area === 'local' && changes.serverStatuses) {
|
item._bubble.style.background = 'rgba(34,197,94,0.15)';
|
||||||
statuses = changes.serverStatuses.newValue || {};
|
item._bubble.style.color = '#4ade80';
|
||||||
updateServerListStatuses();
|
}
|
||||||
if (selectedId) {
|
});
|
||||||
const srv = servers.find(s => s.id === selectedId);
|
}
|
||||||
if (srv) updateDetailForServer(srv);
|
|
||||||
}
|
// ── Edit / Add Mode ──
|
||||||
}
|
function enterEditMode(server) {
|
||||||
|
editingId = server.id;
|
||||||
|
$('settingsTitle').textContent = 'Server bearbeiten';
|
||||||
|
$('btnAddServer').textContent = 'Änderungen speichern';
|
||||||
|
|
||||||
|
$('inputName').value = server.name || '';
|
||||||
|
$('inputUrl').value = server.url || '';
|
||||||
|
$('inputWpSite').value = server.wpSite || '';
|
||||||
|
$('inputWpServerId').value = server.wpServerId || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitEditMode() {
|
||||||
|
editingId = null;
|
||||||
|
$('settingsTitle').textContent = 'Neuen Server hinzufügen';
|
||||||
|
$('btnAddServer').textContent = 'Hinzufügen';
|
||||||
|
$('inputName').value = '';
|
||||||
|
$('inputUrl').value = '';
|
||||||
|
$('inputWpSite').value = '';
|
||||||
|
$('inputWpServerId').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ──
|
||||||
|
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 eine URL oder WordPress-Einstellungen angeben.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editingId) {
|
||||||
|
// === BEARBEITEN ===
|
||||||
|
const srv = servers.find(s => s.id === editingId);
|
||||||
|
if (srv) {
|
||||||
|
srv.name = name || url || wpSite;
|
||||||
|
if (url) srv.url = url;
|
||||||
|
if (wpSite) srv.wpSite = wpSite;
|
||||||
|
if (wpServerId) srv.wpServerId = wpServerId;
|
||||||
|
}
|
||||||
|
exitEditMode();
|
||||||
|
} else {
|
||||||
|
// === NEU HINZUFÜGEN ===
|
||||||
|
const s = {
|
||||||
|
id: uid(),
|
||||||
|
name: name || url || wpSite,
|
||||||
|
url: url || null,
|
||||||
|
wpSite: wpSite || null,
|
||||||
|
wpServerId: wpServerId || null
|
||||||
|
};
|
||||||
|
servers.push(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveServersToStorage();
|
||||||
|
renderServerList();
|
||||||
|
if (selectedId) renderDetail(selectedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEdit() {
|
||||||
|
if (!selectedId) return;
|
||||||
|
const srv = servers.find(s => s.id === selectedId);
|
||||||
|
if (!srv) return;
|
||||||
|
|
||||||
|
settingsVisible = true;
|
||||||
|
applySettingsVisibility();
|
||||||
|
await saveSettingsVisibility();
|
||||||
|
|
||||||
|
enterEditMode(srv);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete() {
|
||||||
|
if (!selectedId || !confirm('Server wirklich löschen?')) return;
|
||||||
|
servers = servers.filter(s => s.id !== selectedId);
|
||||||
|
selectedId = null;
|
||||||
|
await saveServersToStorage();
|
||||||
|
renderServerList();
|
||||||
|
renderDetail(null);
|
||||||
|
exitEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function manualRefresh() {
|
||||||
|
try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Live updates from background ──
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
@@ -1,12 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* 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).
|
Plugin URI:https://git.viper.ipv64.net/M_Viper/Minecraft-BungeeCord-Status
|
||||||
* Tags: minecraft, bungeecord, server status, player list
|
Description: Der ultimative Live-Status für dein BungeeCord Netzwerk.
|
||||||
* Version: 3.6.4
|
Version: 3.6.6
|
||||||
* Author: M_Viper
|
Author: M_Viper
|
||||||
* Requires at least: 6.0
|
Author URI: https://m-viper.de
|
||||||
* Requires PHP: 7.4
|
Requires at least: 6.0
|
||||||
|
Requires PHP: 7.4
|
||||||
|
Tested up to: 6.8
|
||||||
|
PHP Version: 7.4
|
||||||
|
License: GPL2
|
||||||
|
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
Text Domain: minecraft-bungeecord-status
|
||||||
|
Tags: minecraft, bungeecord, server status, player list
|
||||||
|
Support: [Discord Support](https://discord.com/invite/FdRs4BRd8D)
|
||||||
|
Support: [Telegram Support](https://t.me/M_Viper04)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!defined('ABSPATH')) exit;
|
if (!defined('ABSPATH')) exit;
|
||||||
@@ -76,144 +85,116 @@ function mcss_get_3d_avatar($name, $uuid = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- AUTO UPDATE (MCSS) ---------------- */
|
|
||||||
if ( ! class_exists( 'MCSS_Auto_Update' ) ) {
|
|
||||||
class MCSS_Auto_Update {
|
|
||||||
|
|
||||||
private $plugin_file;
|
// ===============================
|
||||||
private $repo_owner = 'M_Viper';
|
// MCSS - UPDATE NOTICE SYSTEM
|
||||||
private $repo_name = 'Minecraft-BungeeCord-Status';
|
// ===============================
|
||||||
private $api_url;
|
|
||||||
private $transient_key;
|
|
||||||
|
|
||||||
public function __construct( $plugin_file ) {
|
// Plugin-Version aus Header lesen
|
||||||
$this->plugin_file = $plugin_file;
|
function mcss_get_plugin_version() {
|
||||||
$this->api_url = 'https://git.viper.ipv64.net/api/v1/repos/' . rawurlencode( $this->repo_owner ) . '/' . rawurlencode( $this->repo_name ) . '/releases';
|
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||||
$this->transient_key = 'mcss_update_check_' . md5( $this->repo_owner . '/' . $this->repo_name );
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||||
|
}
|
||||||
|
|
||||||
if ( is_admin() ) {
|
$plugin_data = get_plugin_data( __FILE__ );
|
||||||
add_action( 'admin_init', array( $this, 'check_for_update' ) );
|
return $plugin_data['Version'] ?? '0.0.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache manuell leeren
|
||||||
|
function mcss_clear_update_cache() {
|
||||||
|
if ( isset($_GET['mcss_clear_cache']) && current_user_can('manage_options') ) {
|
||||||
|
check_admin_referer('mcss_clear_cache_action');
|
||||||
|
delete_transient('mcss_latest_release');
|
||||||
|
wp_redirect( admin_url('plugins.php') );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('admin_init', 'mcss_clear_update_cache');
|
||||||
|
|
||||||
|
// Neueste Release-Infos von Gitea holen
|
||||||
|
function mcss_get_latest_release_info( $force_refresh = false ) {
|
||||||
|
$transient_key = 'mcss_latest_release';
|
||||||
|
|
||||||
|
if ( $force_refresh ) {
|
||||||
|
delete_transient( $transient_key );
|
||||||
|
}
|
||||||
|
|
||||||
|
$release_info = get_transient( $transient_key );
|
||||||
|
|
||||||
|
if ( false === $release_info ) {
|
||||||
|
$response = wp_remote_get(
|
||||||
|
'https://git.viper.ipv64.net/api/v1/repos/M_Viper/Minecraft-BungeeCord-Status/releases/latest',
|
||||||
|
['timeout' => 10]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( ! is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response) ) {
|
||||||
|
$body = wp_remote_retrieve_body($response);
|
||||||
|
$data = json_decode($body, true);
|
||||||
|
|
||||||
|
if ( $data && isset($data['tag_name']) ) {
|
||||||
|
$tag = ltrim( $data['tag_name'], 'vV' );
|
||||||
|
|
||||||
|
$release_info = [
|
||||||
|
'version' => $tag,
|
||||||
|
'download_url' => $data['zipball_url'] ?? '',
|
||||||
|
'notes' => $data['body'] ?? '',
|
||||||
|
'published_at' => $data['published_at'] ?? '',
|
||||||
|
];
|
||||||
|
|
||||||
|
set_transient( $transient_key, $release_info, 6 * HOUR_IN_SECONDS );
|
||||||
|
} else {
|
||||||
|
set_transient( $transient_key, [], HOUR_IN_SECONDS );
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
set_transient( $transient_key, [], HOUR_IN_SECONDS );
|
||||||
public function check_for_update() {
|
|
||||||
if ( ! function_exists( 'get_file_data' ) ) {
|
|
||||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
$current = get_file_data( $this->plugin_file, array( 'Version' => 'Version' ), 'plugin' );
|
|
||||||
$current_version = isset( $current['Version'] ) ? $current['Version'] : '0.0.0';
|
|
||||||
|
|
||||||
$latest = $this->get_latest_release_info();
|
|
||||||
|
|
||||||
if ( $latest && ! empty( $latest['version'] ) && ! empty( $latest['url'] ) ) {
|
|
||||||
if ( version_compare( $latest['version'], $current_version, '>' ) ) {
|
|
||||||
add_action( 'admin_notices', function() use ( $latest, $current_version ) {
|
|
||||||
?>
|
|
||||||
<div class="notice notice-warning is-dismissible">
|
|
||||||
<p>
|
|
||||||
<strong>Minecraft BungeeCord Status – Update verfügbar</strong><br>
|
|
||||||
Neue Version: <strong><?php echo esc_html( $latest['version'] ); ?></strong><br>
|
|
||||||
Installiert: <strong><?php echo esc_html( $current_version ); ?></strong><br>
|
|
||||||
<a href="<?php echo esc_url( $latest['url'] ); ?>" class="button button-primary" target="_blank" rel="noreferrer noopener">Direkter Download (ZIP)</a>
|
|
||||||
<a href="<?php echo esc_url( 'https://git.viper.ipv64.net/' . rawurlencode( $this->repo_owner ) . '/' . rawurlencode( $this->repo_name ) . '/releases' ); ?>" class="button" target="_blank" style="margin-left:8px;" rel="noreferrer noopener">Releases ansehen</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<?php
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function get_latest_release_info() {
|
|
||||||
$cached = get_transient( $this->transient_key );
|
|
||||||
if ( false !== $cached && is_array( $cached ) && ! empty( $cached['version'] ) ) {
|
|
||||||
return $cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = false;
|
|
||||||
|
|
||||||
$response = wp_remote_get( $this->api_url, array(
|
|
||||||
'timeout' => 8,
|
|
||||||
'headers' => array(
|
|
||||||
'Accept' => 'application/json',
|
|
||||||
'User-Agent' => 'MCSS-Update-Checker/1.0',
|
|
||||||
),
|
|
||||||
) );
|
|
||||||
|
|
||||||
if ( is_wp_error( $response ) ) {
|
|
||||||
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$code = wp_remote_retrieve_response_code( $response );
|
|
||||||
if ( 200 !== (int) $code ) {
|
|
||||||
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = wp_remote_retrieve_body( $response );
|
|
||||||
$json = json_decode( $body, true );
|
|
||||||
|
|
||||||
if ( ! is_array( $json ) || empty( $json ) ) {
|
|
||||||
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ( $json as $release ) {
|
|
||||||
$tag = '';
|
|
||||||
if ( ! empty( $release['tag_name'] ) ) {
|
|
||||||
$tag = ltrim( (string) $release['tag_name'], 'vV' );
|
|
||||||
} elseif ( ! empty( $release['name'] ) ) {
|
|
||||||
$tag = ltrim( (string) $release['name'], 'vV' );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! empty( $release['assets'] ) && is_array( $release['assets'] ) ) {
|
|
||||||
foreach ( $release['assets'] as $asset ) {
|
|
||||||
if ( empty( $asset['name'] ) || empty( $asset['browser_download_url'] ) ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( strtolower( $asset['name'] ) === 'minecraft-bungeecord-status.zip' ) {
|
|
||||||
$version = $tag ?: $this->extract_version_from_string( $asset['name'] . ' ' . ( $release['name'] ?? '' ) );
|
|
||||||
if ( $version ) {
|
|
||||||
$result = array(
|
|
||||||
'version' => $version,
|
|
||||||
'url' => $asset['browser_download_url'],
|
|
||||||
);
|
|
||||||
break 2;
|
|
||||||
} else {
|
|
||||||
$result = array(
|
|
||||||
'version' => ( $tag ?: (string) ( $release['name'] ?? '' ) ),
|
|
||||||
'url' => $asset['browser_download_url'],
|
|
||||||
);
|
|
||||||
break 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $result ) {
|
|
||||||
set_transient( $this->transient_key, $result, 12 * HOUR_IN_SECONDS );
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), 6 * HOUR_IN_SECONDS );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function extract_version_from_string( $str ) {
|
|
||||||
if ( preg_match( '/([0-9]+\.[0-9]+(?:\.[0-9]+)?)/', $str, $m ) ) {
|
|
||||||
return $m[1];
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new MCSS_Auto_Update( __FILE__ );
|
return $release_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Admin-Update-Hinweis anzeigen
|
||||||
|
function mcss_show_update_notice() {
|
||||||
|
if ( ! current_user_can('manage_options') ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_version = mcss_get_plugin_version();
|
||||||
|
$latest_release = mcss_get_latest_release_info();
|
||||||
|
|
||||||
|
if ( ! empty($latest_release['version']) && version_compare($current_version, $latest_release['version'], '<') ) {
|
||||||
|
|
||||||
|
$refresh_url = wp_nonce_url(
|
||||||
|
admin_url('plugins.php?mcss_clear_cache=1'),
|
||||||
|
'mcss_clear_cache_action'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<div class="notice notice-warning is-dismissible">
|
||||||
|
<h3>Minecraft BungeeCord Status – Update verfügbar</h3>
|
||||||
|
<p>
|
||||||
|
Installiert: <strong><?php echo esc_html($current_version); ?></strong><br>
|
||||||
|
Neueste Version: <strong><?php echo esc_html($latest_release['version']); ?></strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="<?php echo esc_url($latest_release['download_url']); ?>" class="button button-primary" target="_blank">
|
||||||
|
Update herunterladen
|
||||||
|
</a>
|
||||||
|
<a href="https://git.viper.ipv64.net/M_Viper/Minecraft-BungeeCord-Status/releases" class="button" target="_blank">
|
||||||
|
Release Notes
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo esc_url($refresh_url); ?>" class="button">
|
||||||
|
Jetzt neu prüfen
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('admin_notices', 'mcss_show_update_notice');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ---------------- Assets ---------------- */
|
/* ---------------- Assets ---------------- */
|
||||||
add_action('admin_enqueue_scripts', function($hook){
|
add_action('admin_enqueue_scripts', function($hook){
|
||||||
global $pagenow;
|
global $pagenow;
|
||||||
@@ -516,25 +497,89 @@ function mcss_render_row($srv, $i, $font_sizes, $announcement_types) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- LOGIC: STATUS API FETCHER ---------------- */
|
/* ---------------- LOGIC: STATUS API FETCHER ---------------- */
|
||||||
function mcss_fetch_data_via_api($host, $port, $timeout = 2) {
|
function mcss_socket_http_json($host, $port, $path = '/', $timeout = 2.0) {
|
||||||
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
|
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
|
||||||
if (!$socket) return false;
|
if (!$socket) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$out = "GET / HTTP/1.1\r\n";
|
stream_set_timeout($socket, (int)ceil($timeout));
|
||||||
|
|
||||||
|
$out = "GET " . $path . " HTTP/1.1\r\n";
|
||||||
$out .= "Host: " . $host . "\r\n";
|
$out .= "Host: " . $host . "\r\n";
|
||||||
$out .= "Connection: Close\r\n\r\n";
|
$out .= "User-Agent: MCSS-WordPress/3.6.6\r\n";
|
||||||
fwrite($socket, $out);
|
$out .= "Accept: application/json\r\n";
|
||||||
|
$out .= "Connection: close\r\n\r\n";
|
||||||
|
|
||||||
|
if (@fwrite($socket, $out) === false) {
|
||||||
|
@fclose($socket);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$response = '';
|
$response = '';
|
||||||
while (!feof($socket)) {
|
while (!feof($socket)) {
|
||||||
$response .= fgets($socket, 128);
|
$chunk = @fread($socket, 2048);
|
||||||
|
if ($chunk === false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($chunk === '') {
|
||||||
|
$meta = stream_get_meta_data($socket);
|
||||||
|
if (!empty($meta['timed_out'])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$response .= $chunk;
|
||||||
}
|
}
|
||||||
fclose($socket);
|
|
||||||
|
|
||||||
$parts = explode("\r\n\r\n", $response);
|
$meta = stream_get_meta_data($socket);
|
||||||
$body = end($parts);
|
@fclose($socket);
|
||||||
$data = json_decode($body, true);
|
|
||||||
return $data;
|
if (!empty($meta['timed_out'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode("\r\n\r\n", $response, 2);
|
||||||
|
if (count($parts) !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$statusLine = strtok($parts[0], "\r\n");
|
||||||
|
if (!is_string($statusLine) || stripos($statusLine, '200') === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($parts[1], true);
|
||||||
|
return is_array($data) ? $data : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mcss_fetch_data_via_api($host, $port, $timeout = 2) {
|
||||||
|
// Primärer Endpunkt mit Detaildaten
|
||||||
|
$full = mcss_socket_http_json($host, $port, '/', $timeout);
|
||||||
|
if (is_array($full) && isset($full['online'])) {
|
||||||
|
return $full;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback auf Health-Endpunkt (StatusAPI >= hardened build)
|
||||||
|
$health = mcss_socket_http_json($host, $port, '/health', $timeout);
|
||||||
|
if (is_array($health) && !empty($health['online'])) {
|
||||||
|
return [
|
||||||
|
'online' => true,
|
||||||
|
'players' => [],
|
||||||
|
'version' => 'BungeeCord',
|
||||||
|
'ping' => 1,
|
||||||
|
'motd' => 'StatusAPI erreichbar (Health)',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ein zweiter kurzer Versuch reduziert False Negatives bei Paketverlust.
|
||||||
|
$retry = mcss_socket_http_json($host, $port, '/', max(1.0, $timeout));
|
||||||
|
if (is_array($retry) && isset($retry['online'])) {
|
||||||
|
return $retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mcss_parse_bungeecord_version($raw_version) {
|
function mcss_parse_bungeecord_version($raw_version) {
|
||||||
@@ -545,16 +590,26 @@ function mcss_parse_bungeecord_version($raw_version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mcss_fetch_server_with_ranks($srv) {
|
function mcss_fetch_server_with_ranks($srv) {
|
||||||
$cache_key = 'mcss_data_' . md5($srv['host']);
|
$host = $srv['host'];
|
||||||
|
$port = $srv['player_port'] ?? 9191;
|
||||||
|
|
||||||
|
$cache_key = 'mcss_data_' . md5($host . ':' . $port);
|
||||||
|
$last_good_key = 'mcss_last_good_' . md5($host . ':' . $port);
|
||||||
|
|
||||||
$cached = get_transient($cache_key);
|
$cached = get_transient($cache_key);
|
||||||
if ($cached !== false) return $cached;
|
if ($cached !== false) return $cached;
|
||||||
|
|
||||||
$host = $srv['host'];
|
$api_data = mcss_fetch_data_via_api($host, $port, 2);
|
||||||
$port = $srv['player_port'] ?? 9191;
|
|
||||||
|
|
||||||
$api_data = mcss_fetch_data_via_api($host, $port, 1);
|
|
||||||
|
|
||||||
if (!$api_data || !isset($api_data['online'])) {
|
if (!$api_data || !isset($api_data['online'])) {
|
||||||
|
$last_good = get_transient($last_good_key);
|
||||||
|
if (is_array($last_good) && !empty($last_good['online'])) {
|
||||||
|
$last_good['stale'] = true;
|
||||||
|
$last_good['stale_reason'] = 'temporary_connection_loss';
|
||||||
|
set_transient($cache_key, $last_good, 5);
|
||||||
|
return $last_good;
|
||||||
|
}
|
||||||
|
|
||||||
return ['online'=>false,'players'=>[],'version'=>'Offline','ping'=>9999,'motd'=>'Verbindung fehlgeschlagen'];
|
return ['online'=>false,'players'=>[],'version'=>'Offline','ping'=>9999,'motd'=>'Verbindung fehlgeschlagen'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,7 +657,7 @@ function mcss_fetch_server_with_ranks($srv) {
|
|||||||
$clean_version = mcss_parse_bungeecord_version($api_data['version'] ?? 'BungeeCord');
|
$clean_version = mcss_parse_bungeecord_version($api_data['version'] ?? 'BungeeCord');
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'online' => true,
|
'online' => !empty($api_data['online']),
|
||||||
'players' => $players_info,
|
'players' => $players_info,
|
||||||
'version' => $clean_version,
|
'version' => $clean_version,
|
||||||
'ping' => 1,
|
'ping' => 1,
|
||||||
@@ -611,6 +666,9 @@ function mcss_fetch_server_with_ranks($srv) {
|
|||||||
|
|
||||||
$fast_cache_ttl = max(2, absint($srv['cache_ttl'] ?? 10));
|
$fast_cache_ttl = max(2, absint($srv['cache_ttl'] ?? 10));
|
||||||
set_transient($cache_key, $result, $fast_cache_ttl);
|
set_transient($cache_key, $result, $fast_cache_ttl);
|
||||||
|
if (!empty($result['online'])) {
|
||||||
|
set_transient($last_good_key, $result, 180);
|
||||||
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,4 +135,10 @@ Bei Problemen überprüfe bitte:
|
|||||||
## Weitere Informationen
|
## Weitere Informationen
|
||||||
|
|
||||||
Für detaillierte Informationen zur Installation, Konfiguration und Fehlerbehebung der StatusAPI:
|
Für detaillierte Informationen zur Installation, Konfiguration und Fehlerbehebung der StatusAPI:
|
||||||
**[https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)**
|
**[https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Copyright © 2026 - M_Viper - Alle Rechte vorbehalten**
|
||||||
|
|
||||||
|
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.
|
||||||
Reference in New Issue
Block a user