283 lines
9.9 KiB
JavaScript
Executable File
283 lines
9.9 KiB
JavaScript
Executable File
/**
|
|
* Minecraft Server Status - Sidebar Widget
|
|
* Source : API Crafty Controller (proxy PHP crafty-stats.php)
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
const API = '/data/api/crafty-stats.php';
|
|
const MODS_API = '/data/api/mods-mc.php';
|
|
const INTERVAL = 30000; // 30s
|
|
|
|
const SERVERS = [
|
|
{ key: 'mc1', cardId: 'mc-card-0' },
|
|
{ key: 'mc2', cardId: 'mc-card-1' },
|
|
];
|
|
|
|
let timer = null;
|
|
let modsData = null; // cache des mods, chargé une seule fois
|
|
|
|
// ── Mods ────────────────────────────────────────────────────────────────
|
|
|
|
const modsStyle = document.createElement('style');
|
|
modsStyle.textContent = `
|
|
.mc-mods-section {
|
|
margin-top: 0.5rem;
|
|
border-top: 1px solid rgba(98, 114, 164, 0.3);
|
|
padding-top: 0.5rem;
|
|
}
|
|
.mc-mods-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
cursor: pointer;
|
|
padding: 0.25rem 0;
|
|
user-select: none;
|
|
}
|
|
.mc-mods-toggle-label {
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
color: #6272a4;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.07em;
|
|
}
|
|
.mc-mods-toggle-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
font-size: 0.68rem;
|
|
color: #6272a4;
|
|
}
|
|
.mc-mods-arrow {
|
|
font-size: 0.6rem;
|
|
color: #6272a4;
|
|
transition: transform 0.2s;
|
|
display: inline-block;
|
|
}
|
|
.mc-mods-arrow.open { transform: rotate(90deg); }
|
|
.mc-mods-pills {
|
|
display: none;
|
|
flex-wrap: wrap;
|
|
gap: 0.3rem;
|
|
padding-top: 0.45rem;
|
|
}
|
|
.mc-mods-pills.open { display: flex; }
|
|
.mc-mod-pill {
|
|
background: rgba(98, 114, 164, 0.25);
|
|
color: #f8f8f2;
|
|
font-size: 0.65rem;
|
|
padding: 2px 7px;
|
|
border-radius: 20px;
|
|
border: 1px solid rgba(98, 114, 164, 0.35);
|
|
white-space: nowrap;
|
|
cursor: default;
|
|
}
|
|
.mc-mod-pill:hover {
|
|
background: rgba(189, 147, 249, 0.2);
|
|
border-color: rgba(189, 147, 249, 0.5);
|
|
color: #bd93f9;
|
|
}
|
|
.mc-mods-error {
|
|
font-size: 0.7rem;
|
|
color: #ff5555;
|
|
padding-top: 0.3rem;
|
|
font-style: italic;
|
|
}
|
|
`;
|
|
document.head.appendChild(modsStyle);
|
|
|
|
async function fetchMods() {
|
|
try {
|
|
const res = await fetch(MODS_API, { cache: 'no-cache' });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
modsData = await res.json();
|
|
} catch (e) {
|
|
console.warn('[mods-mc] Erreur fetch :', e);
|
|
}
|
|
}
|
|
|
|
function appendMods(card, srv) {
|
|
if (!modsData) return;
|
|
|
|
// trouve l'entrée correspondant au nom du serveur Crafty
|
|
const entry = modsData.find(m => m.label === srv?.name) ?? modsData[SERVERS.findIndex(s => s.cardId === card.id)];
|
|
if (!entry) return;
|
|
|
|
const section = document.createElement('div');
|
|
section.className = 'mc-mods-section';
|
|
|
|
if (entry.error) {
|
|
const err = document.createElement('div');
|
|
err.className = 'mc-mods-error';
|
|
err.textContent = entry.error;
|
|
section.appendChild(err);
|
|
card.appendChild(section);
|
|
return;
|
|
}
|
|
|
|
const count = entry.mods.length;
|
|
const toggle = document.createElement('div');
|
|
toggle.className = 'mc-mods-toggle';
|
|
toggle.innerHTML = `
|
|
<span class="mc-mods-toggle-label">Mods</span>
|
|
<span class="mc-mods-toggle-meta">
|
|
<span>${count} mod${count !== 1 ? 's' : ''}</span>
|
|
<span class="mc-mods-arrow">▸</span>
|
|
</span>
|
|
`;
|
|
|
|
const pills = document.createElement('div');
|
|
pills.className = 'mc-mods-pills';
|
|
entry.mods.forEach(mod => {
|
|
const pill = document.createElement('span');
|
|
pill.className = 'mc-mod-pill';
|
|
pill.title = mod.file;
|
|
pill.textContent = mod.name;
|
|
pills.appendChild(pill);
|
|
});
|
|
|
|
toggle.addEventListener('click', () => {
|
|
pills.classList.toggle('open');
|
|
toggle.querySelector('.mc-mods-arrow').classList.toggle('open');
|
|
});
|
|
|
|
section.appendChild(toggle);
|
|
section.appendChild(pills);
|
|
card.appendChild(section);
|
|
}
|
|
|
|
// ── Status ───────────────────────────────────────────────────────────────
|
|
|
|
async function fetchAll() {
|
|
let data;
|
|
try {
|
|
const res = await fetch(API, { cache: 'no-cache' });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
data = await res.json();
|
|
} catch (e) {
|
|
console.error('crafty-stats error:', e);
|
|
return;
|
|
}
|
|
if (!data.success) return;
|
|
|
|
SERVERS.forEach(({ key, cardId }) => {
|
|
const card = document.getElementById(cardId);
|
|
if (!card) return;
|
|
renderCard(card, data.servers?.[key]);
|
|
});
|
|
|
|
updateStatCards(data.servers);
|
|
}
|
|
|
|
function renderCard(card, srv) {
|
|
if (!srv) {
|
|
card.innerHTML = `
|
|
<div class="mc-header">
|
|
<span class="mc-name">Serveur inconnu</span>
|
|
<span class="mc-badge mc-offline">● Hors ligne</span>
|
|
</div>`;
|
|
appendMods(card, srv);
|
|
return;
|
|
}
|
|
|
|
const online = srv.running;
|
|
const players = online ? `${srv.players} / ${srv.max_players}` : '— / —';
|
|
|
|
let ramHtml = '';
|
|
if (online && srv.ram_used !== null) {
|
|
const usedStr = ramFmt(srv.ram_used);
|
|
const allocStr = srv.ram_alloc ? ramFmt(srv.ram_alloc) : '?';
|
|
const pct = srv.ram_alloc > 0 ? Math.round((srv.ram_used / srv.ram_alloc) * 100) : 0;
|
|
const color = pct > 90 ? '#ff5555' : pct > 70 ? '#ffb86c' : '#50fa7b';
|
|
ramHtml = `
|
|
<span class="mc-ram" style="color:${color}">
|
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" style="color:#bd93f9;flex-shrink:0">
|
|
<path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/>
|
|
</svg>
|
|
${usedStr}<span style="color:#6272a4;font-weight:400"> / ${allocStr}</span>
|
|
</span>`;
|
|
}
|
|
|
|
const cpuHtml = (online && srv.cpu !== null)
|
|
? `<span class="mc-version">${srv.cpu}% CPU</span>`
|
|
: '';
|
|
|
|
card.innerHTML = `
|
|
<div class="mc-header">
|
|
<span class="mc-name">${esc(srv.name)}</span>
|
|
<span class="mc-badge ${online ? 'mc-online' : 'mc-offline'}">${online ? '● En ligne' : '● Hors ligne'}</span>
|
|
</div>
|
|
<div class="mc-address">${esc('nix.roulaise.net')}:${srv.port}</div>
|
|
<div class="mc-info">
|
|
<span class="mc-players">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
|
|
</svg>
|
|
${players} joueurs
|
|
</span>
|
|
${ramHtml}
|
|
${cpuHtml}
|
|
</div>`;
|
|
|
|
appendMods(card, srv);
|
|
}
|
|
|
|
function updateStatCards(servers) {
|
|
['mc1', 'mc2'].forEach((key, i) => {
|
|
const n = i + 1;
|
|
const el = document.getElementById(`mc${n}-ram`);
|
|
const sub = document.getElementById(`mc${n}-ram-subtitle`);
|
|
if (!el) return;
|
|
|
|
const srv = servers?.[key];
|
|
if (!srv || !srv.running) {
|
|
el.textContent = 'Éteint';
|
|
if (sub) sub.textContent = '—';
|
|
el.style.color = '#6272a4';
|
|
return;
|
|
}
|
|
if (srv.ram_used !== null) {
|
|
const pct = srv.ram_alloc > 0 ? Math.round((srv.ram_used / srv.ram_alloc) * 100) : 0;
|
|
el.textContent = ramFmt(srv.ram_used);
|
|
if (sub) sub.textContent = `alloué: ${srv.ram_alloc ? ramFmt(srv.ram_alloc) : '?'} (${pct}%)`;
|
|
el.style.color = pct > 90 ? '#ff5555' : pct > 70 ? '#ffb86c' : '#50fa7b';
|
|
} else {
|
|
el.textContent = '...';
|
|
if (sub) sub.textContent = '—';
|
|
}
|
|
});
|
|
}
|
|
|
|
function ramFmt(mio) {
|
|
return mio >= 1024 ? (mio / 1024).toFixed(1) + ' Gio' : mio + ' Mio';
|
|
}
|
|
|
|
function esc(s) {
|
|
return String(s)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
}
|
|
|
|
function start() { timer = setInterval(fetchAll, INTERVAL); }
|
|
function stop() { clearInterval(timer); timer = null; }
|
|
|
|
async function init() {
|
|
if (!document.getElementById('mc-card-0')) return;
|
|
await fetchMods(); // charge les mods une seule fois au démarrage
|
|
fetchAll();
|
|
start();
|
|
window.addEventListener('beforeunload', stop);
|
|
document.addEventListener('visibilitychange', () =>
|
|
document.hidden ? stop() : (fetchAll(), start())
|
|
);
|
|
}
|
|
|
|
document.readyState === 'loading'
|
|
? document.addEventListener('DOMContentLoaded', init)
|
|
: init();
|
|
|
|
})();
|