first commit
This commit is contained in:
Executable
+282
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* 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();
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user