feat: badges SMART pills, versionning serveur, fix copier HTTP
- Dashboard: icônes SMART → pills OK/USAGÉ/PREFAIL/HS cliquables (tuile + popup détail + popup SMART redessiné pour novices) - Serveur: constante version 0.1.0 exposée via WS server_stats → footer - Fix copier script install en HTTP (isSecureContext avant clipboard API) - install.sh: ajout ethtool, suppression logique OVERWRITE_CONFIG Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -199,12 +199,15 @@ body{background:var(--bg-1);color:var(--ink-1);font-family:var(--font-ui);font-s
|
||||
.chart-svg{width:100%;height:52px;display:block}
|
||||
.chart-axis{display:flex;justify-content:space-between;margin-top:2px;font-family:var(--font-terminal);font-size:9px;color:var(--ink-4)}
|
||||
.chart-minmax{display:flex;justify-content:space-between;margin-top:3px;font-family:var(--font-mono);font-size:9px;color:var(--ink-4)}
|
||||
.smart-btn{display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border-radius:8px;
|
||||
border:1px solid var(--border-2);background:var(--bg-3);cursor:pointer;
|
||||
transition:background .12s,border-color .12s,transform .08s;font-family:var(--font-terminal);font-size:11px}
|
||||
.smart-btn:hover{background:var(--bg-4)}.smart-btn:active{transform:translateY(1px)}
|
||||
.smart-btn.ok{border-color:rgba(77,187,38,.3);color:var(--ok)}
|
||||
.smart-dot{width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 5px var(--ok)}
|
||||
.smart-pill{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;
|
||||
font-size:9px;font-family:var(--font-terminal);font-weight:700;border:1px solid;
|
||||
cursor:pointer;user-select:none;flex-shrink:0;
|
||||
transition:opacity .12s,transform .08s,box-shadow .12s}
|
||||
.smart-pill:hover{opacity:.82;transform:scale(1.06)}
|
||||
.smart-pill.ok{color:var(--ok);background:rgba(77,187,38,.12);border-color:rgba(77,187,38,.32)}
|
||||
.smart-pill.old{color:var(--warn);background:rgba(250,189,47,.12);border-color:rgba(250,189,47,.32)}
|
||||
.smart-pill.prefail{color:var(--accent);background:var(--accent-tint);border-color:rgba(254,128,25,.32)}
|
||||
.smart-pill.hs{color:var(--err);background:rgba(251,73,52,.12);border-color:rgba(251,73,52,.32)}
|
||||
.meta-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
|
||||
.meta{background:var(--bg-3);border-radius:6px;padding:8px 10px;border:1px solid var(--border-1)}
|
||||
.meta-lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
|
||||
@@ -57,6 +57,10 @@
|
||||
<span class="f-val" id="srv-mem">—</span>
|
||||
<div class="f-minibar"><div class="f-minifill" id="srv-mem-bar"></div></div>
|
||||
</div>
|
||||
<div class="f-cell" style="gap:4px">
|
||||
<i class="fa-solid fa-code-branch" style="font-size:9px;color:var(--ink-4)"></i>
|
||||
<span id="srv-ver" style="font-family:var(--font-mono);font-size:9px;color:var(--ink-4)">—</span>
|
||||
</div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right">
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
|
||||
@@ -50,6 +50,8 @@ const App = (() => {
|
||||
const memEl = document.getElementById('srv-mem');
|
||||
const cpuBar = document.getElementById('srv-cpu-bar');
|
||||
const memBar = document.getElementById('srv-mem-bar');
|
||||
const verEl = document.getElementById('srv-ver');
|
||||
if (verEl && stats.version) verEl.textContent = 'v' + stats.version;
|
||||
if (cpuEl) {
|
||||
cpuEl.textContent = cpu.toFixed(0) + '%';
|
||||
cpuEl.className = 'f-val' + (cpu >= 70 ? ' w' : '');
|
||||
|
||||
+21
-5
@@ -58,6 +58,18 @@ const Grid = (() => {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const _stateLabel = { ok: 'OK', old: 'USAGÉ', prefail: 'PREFAIL', hs: 'HS' };
|
||||
|
||||
function smartState(s) {
|
||||
if (!s.passed) return 'hs';
|
||||
if (s.reallocated_sectors > 0 ||
|
||||
(s.wear_level != null && s.wear_level < 20) ||
|
||||
(s.power_on_hours != null && s.power_on_hours > 40000)) return 'prefail';
|
||||
if ((s.wear_level != null && s.wear_level < 50) ||
|
||||
(s.power_on_hours != null && s.power_on_hours > 25000)) return 'old';
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function renderTile(agent, metrics) {
|
||||
const id = agent.id;
|
||||
const sc = statusClass(agent);
|
||||
@@ -78,10 +90,14 @@ const Grid = (() => {
|
||||
}
|
||||
|
||||
const smartIco = !offline && metrics?.smart?.length > 0
|
||||
? metrics.smart.map(s => s.passed
|
||||
? `<i class="fa-solid fa-shield-check" style="color:var(--ok);font-size:10px;flex-shrink:0" data-tip="SMART OK — ${s.device}"></i>`
|
||||
: `<i class="fa-solid fa-triangle-exclamation" style="color:var(--err);font-size:10px;flex-shrink:0" data-tip="SMART FAILED — ${s.device}"></i>`
|
||||
).join('')
|
||||
? '<div style="display:flex;gap:3px;flex-shrink:0">' +
|
||||
metrics.smart.map((s, i) => {
|
||||
const st = smartState(s);
|
||||
const lbl = _stateLabel[st];
|
||||
return `<span class="smart-pill ${st}"
|
||||
onclick="event.stopPropagation();Popups.showSmart('${esc(id)}',${i})"
|
||||
data-tip="SMART ${esc(s.device)} — ${lbl}">${lbl}</span>`;
|
||||
}).join('') + '</div>'
|
||||
: '';
|
||||
|
||||
const iconContent = `<img src="${API.iconUrl(id)}" alt=""
|
||||
@@ -215,5 +231,5 @@ const Grid = (() => {
|
||||
updateStats();
|
||||
}
|
||||
|
||||
return { refresh, update, updateStatus, removeAgent, rerenderAll, getAgent, fmt, fmtPct };
|
||||
return { refresh, update, updateStatus, removeAgent, rerenderAll, getAgent, fmt, fmtPct, smartState };
|
||||
})();
|
||||
|
||||
+75
-33
@@ -80,16 +80,18 @@ const Popups = (() => {
|
||||
? `<div class="chart-minmax"><span>min ${Grid.fmt(ramMin)}</span><span>max ${Grid.fmt(ramMax)}</span></div>`
|
||||
: '';
|
||||
|
||||
const smartBtn = metrics?.smart?.length > 0
|
||||
? metrics.smart.map((s, i) => `
|
||||
<div class="smart-btn ${s.passed ? 'ok' : 'err'}" onclick="Popups.showSmart('${esc(agentId)}',${i})" data-tip="Voir la santé du disque ${esc(s.device)}">
|
||||
<div class="smart-dot" style="${s.passed ? '' : 'background:var(--err);box-shadow:0 0 5px var(--err)'}"></div>
|
||||
<span style="font-weight:600">${esc(s.device) || 'disque'}</span>
|
||||
<span>·</span>
|
||||
<span>${s.passed ? 'PASSED' : 'FAILED'}</span>
|
||||
${s.temperature != null ? `<span style="font-family:var(--font-mono);font-size:10px;color:var(--ink-3)"><i class="fa-solid fa-temperature-half"></i> ${s.temperature}°C</span>` : ''}
|
||||
<i class="fa-solid fa-chevron-right" style="font-size:10px;color:var(--ink-4);margin-left:auto"></i>
|
||||
</div>`).join('')
|
||||
const smartBadges = metrics?.smart?.length > 0
|
||||
? '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px">' +
|
||||
metrics.smart.map((s, i) => {
|
||||
const st = Grid.smartState(s);
|
||||
const lbl = { ok: 'OK', old: 'USAGÉ', prefail: 'PREFAIL', hs: 'HS' }[st];
|
||||
return `<span class="smart-pill ${st}"
|
||||
onclick="Popups.showSmart('${esc(agentId)}',${i})"
|
||||
data-tip="Santé SMART de ${esc(s.device)}">
|
||||
<i class="fa-solid fa-hard-drive" style="font-size:8px"></i>
|
||||
${esc(s.device)} · ${lbl}
|
||||
</span>`;
|
||||
}).join('') + '</div>'
|
||||
: '';
|
||||
|
||||
const protos = [
|
||||
@@ -143,7 +145,7 @@ const Popups = (() => {
|
||||
<div style="height:100%;border-radius:4px;background:var(--ok);width:${metrics?.hdd_total ? (metrics.hdd_used/metrics.hdd_total*100).toFixed(0) : 0}%"></div></div>
|
||||
<span style="font-family:var(--font-mono);font-size:12px;color:var(--ink-2);width:90px;text-align:right">${Grid.fmt(metrics?.hdd_used)} / ${Grid.fmt(metrics?.hdd_total)}</span>
|
||||
</div>
|
||||
${smartBtn}
|
||||
${smartBadges}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -394,54 +396,94 @@ const Popups = (() => {
|
||||
const smartList = Grid.getAgent(agentId)?.metrics?.smart;
|
||||
if (!smartList?.length) return;
|
||||
const m = smartList[diskIdx] ?? smartList[0];
|
||||
const state = Grid.smartState(m);
|
||||
|
||||
document.getElementById('smart-sub').textContent = m.device ? `${agentId} — ${m.device}` : agentId;
|
||||
const passColor = m.passed ? 'var(--ok)' : 'var(--err)';
|
||||
const passText = m.passed ? 'Disque en bonne santé' : 'Disque en mauvais état';
|
||||
const passSub = m.passed
|
||||
? 'Aucun problème détecté. Le disque fonctionne normalement.'
|
||||
: 'Des problèmes ont été détectés. Envisagez un remplacement.';
|
||||
|
||||
const stateInfo = {
|
||||
ok: { color:'var(--ok)', bg:'rgba(77,187,38,.1)', border:'rgba(77,187,38,.3)', icon:'fa-circle-check',
|
||||
title:'Disque en bonne santé',
|
||||
desc:'Aucun problème détecté. Votre disque fonctionne normalement.' },
|
||||
old: { color:'var(--warn)', bg:'rgba(250,189,47,.1)', border:'rgba(250,189,47,.3)', icon:'fa-clock-rotate-left',
|
||||
title:'Disque ancien ou très utilisé',
|
||||
desc:'Votre disque fonctionne encore, mais il a accumulé beaucoup d\'heures. Pensez à prévoir un remplacement.' },
|
||||
prefail: { color:'var(--accent)', bg:'var(--accent-tint)', border:'rgba(254,128,25,.3)', icon:'fa-triangle-exclamation',
|
||||
title:'Signes de défaillance imminente',
|
||||
desc:'Ce disque présente des indicateurs préoccupants. Sauvegardez vos données dès maintenant et envisagez un remplacement rapide.' },
|
||||
hs: { color:'var(--err)', bg:'rgba(251,73,52,.1)', border:'rgba(251,73,52,.3)', icon:'fa-circle-xmark',
|
||||
title:'Disque défaillant',
|
||||
desc:'Ce disque a échoué au test SMART. Il peut tomber en panne à tout moment. Sauvegardez immédiatement et remplacez-le.' },
|
||||
};
|
||||
const si = stateInfo[state];
|
||||
|
||||
const tempColor = m.temperature == null ? null
|
||||
: m.temperature > 60 ? 'var(--err)' : m.temperature > 50 ? 'var(--warn)' : 'var(--ok)';
|
||||
const tempLabel = m.temperature == null ? null
|
||||
: m.temperature > 60 ? 'Critique' : m.temperature > 50 ? 'Élevée' : 'Normale';
|
||||
const tempBg = tempColor === 'var(--ok)' ? 'rgba(77,187,38,.15)'
|
||||
: tempColor === 'var(--warn)' ? 'rgba(250,189,47,.15)' : 'rgba(251,73,52,.15)';
|
||||
|
||||
const secColor = m.reallocated_sectors == null ? null
|
||||
: m.reallocated_sectors === 0 ? 'var(--ok)' : m.reallocated_sectors < 10 ? 'var(--warn)' : 'var(--err)';
|
||||
const secDesc = m.reallocated_sectors === 0
|
||||
? 'Aucun secteur défectueux — parfait.'
|
||||
: m.reallocated_sectors < 10 ? 'Quelques secteurs remplacés. Surveillez l\'évolution.'
|
||||
: 'Nombreux secteurs défectueux — risque de panne élevé.';
|
||||
|
||||
const hoursColor = m.power_on_hours == null ? null
|
||||
: m.power_on_hours > 40000 ? 'var(--err)' : m.power_on_hours > 25000 ? 'var(--warn)' : 'var(--ok)';
|
||||
|
||||
const wearColor = m.wear_level == null ? null
|
||||
: m.wear_level < 20 ? 'var(--err)' : m.wear_level < 50 ? 'var(--warn)' : 'var(--ok)';
|
||||
const wearDesc = m.wear_level == null ? ''
|
||||
: m.wear_level >= 80 ? 'Très bonne durée de vie restante.'
|
||||
: m.wear_level >= 50 ? 'Durée de vie acceptable, à surveiller.'
|
||||
: m.wear_level >= 20 ? 'Durée de vie réduite — pensez au remplacement.'
|
||||
: 'Durée de vie critique — remplacez ce SSD rapidement.';
|
||||
|
||||
document.getElementById('smart-body').innerHTML = `
|
||||
<div class="smart-verdict" style="${m.passed ? '' : 'background:rgba(251,73,52,.1);border-color:rgba(251,73,52,.3)'}">
|
||||
<div style="font-size:28px;color:${passColor}"><i class="fa-solid ${m.passed ? 'fa-circle-check' : 'fa-circle-xmark'}"></i></div>
|
||||
<div><div style="font-size:16px;font-weight:700;color:${passColor}">${passText}</div>
|
||||
<div style="font-size:12px;color:var(--ink-3);margin-top:3px">${passSub}</div></div>
|
||||
<div class="smart-verdict" style="background:${si.bg};border-color:${si.border}">
|
||||
<div style="font-size:28px;color:${si.color}"><i class="fa-solid ${si.icon}"></i></div>
|
||||
<div>
|
||||
<div style="font-size:16px;font-weight:700;color:${si.color}">${si.title}</div>
|
||||
<div style="font-size:12px;color:var(--ink-3);margin-top:3px">${si.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">POINTS DE CONTRÔLE</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
|
||||
${m.temperature != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:var(--warn);font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-temperature-half"></i></span>
|
||||
<span style="color:${tempColor};font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-temperature-half"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Température</span>
|
||||
<span style="font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px;background:rgba(77,187,38,.15);color:var(--ok)">Normale</span>
|
||||
<span style="font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px;background:${tempBg};color:${tempColor}">${tempLabel}</span>
|
||||
</div>
|
||||
<div class="si-val">${m.temperature}<span class="u">°C</span></div>
|
||||
<div class="si-desc">Idéal : 20–50°C. Au-delà de 60°C le disque risque de s'abîmer.</div>
|
||||
<div class="si-desc">Normale entre 20–50°C. Au-delà de 60°C le disque risque de s'abîmer.</div>
|
||||
</div>` : ''}
|
||||
${m.reallocated_sectors != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:${m.reallocated_sectors > 0 ? 'var(--err)' : 'var(--ok)'};font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-circle-check"></i></span>
|
||||
<span style="color:${secColor};font-size:14px;width:22px;text-align:center"><i class="fa-solid ${m.reallocated_sectors === 0 ? 'fa-circle-check' : 'fa-circle-exclamation'}"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Secteurs défectueux</span>
|
||||
</div>
|
||||
<div class="si-val">${m.reallocated_sectors}<span class="u"> sect.</span></div>
|
||||
<div class="si-desc">S'ils apparaissent en grand nombre, une panne est imminente.</div>
|
||||
<div class="si-desc">${secDesc}</div>
|
||||
</div>` : ''}
|
||||
${m.power_on_hours != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:var(--blue);font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-clock-rotate-left"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Heures de fonctionnement</span>
|
||||
<span style="color:${hoursColor};font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-clock-rotate-left"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Durée de fonctionnement</span>
|
||||
</div>
|
||||
<div class="si-val">${m.power_on_hours.toLocaleString('fr-FR')}<span class="u">h</span></div>
|
||||
<div class="si-desc">≈${Math.floor(m.power_on_hours/24)} jours. Un disque dure en moyenne 3 à 5 ans.</div>
|
||||
<div class="si-val">${m.power_on_hours.toLocaleString('fr-FR')}<span class="u"> h</span></div>
|
||||
<div class="si-desc">≈${Math.floor(m.power_on_hours / 24)} jours d'utilisation. Un disque dur dure en moyenne 3 à 5 ans (25 000–40 000 h).</div>
|
||||
</div>` : ''}
|
||||
${m.wear_level != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:var(--ok);font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-battery-full"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Durée de vie SSD</span>
|
||||
<span style="color:${wearColor};font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-battery-full"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Durée de vie SSD restante</span>
|
||||
</div>
|
||||
<div class="si-val">${m.wear_level}<span class="u">%</span></div>
|
||||
<div class="si-desc">100% = neuf · 0% = fin de vie recommandée.</div>
|
||||
<div class="si-desc">${wearDesc}</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -474,7 +516,7 @@ const Popups = (() => {
|
||||
function _copyInstallCmd(btn) {
|
||||
const text = document.getElementById('s-install-cmd').value;
|
||||
const done = () => { btn.textContent = '✓ Copié'; setTimeout(() => btn.textContent = 'Copier', 1500); };
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
if (window.isSecureContext && navigator.clipboard?.writeText) {
|
||||
navigator.clipboard.writeText(text).then(done).catch(() => _copyFallback(text, done));
|
||||
} else {
|
||||
_copyFallback(text, done);
|
||||
|
||||
+29
-41
@@ -29,7 +29,23 @@ echo " Nanometrics Agent — Installation"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# ── 1. Détection de l'architecture ────────────────────────────────────────────
|
||||
# ── 1. Dépendances système ─────────────────────────────────────────────────────
|
||||
PKGS_NEEDED=()
|
||||
for pkg in curl python3 smartmontools ethtool; do
|
||||
dpkg -l "$pkg" 2>/dev/null | grep -q '^ii' || PKGS_NEEDED+=("$pkg")
|
||||
done
|
||||
|
||||
if [ ${#PKGS_NEEDED[@]} -gt 0 ]; then
|
||||
echo "→ Installation des paquets manquants : ${PKGS_NEEDED[*]}"
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq "${PKGS_NEEDED[@]}"
|
||||
ok "Paquets installés : ${PKGS_NEEDED[*]}"
|
||||
else
|
||||
ok "Dépendances système déjà présentes"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── 3. Détection de l'architecture ────────────────────────────────────────────
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) LABEL="linux-amd64" ;;
|
||||
@@ -42,7 +58,7 @@ case "$ARCH" in
|
||||
esac
|
||||
ok "Architecture détectée : $ARCH → $LABEL"
|
||||
|
||||
# ── 2. Récupérer l'URL du binaire depuis la dernière release ──────────────────
|
||||
# ── 4. Récupérer l'URL du binaire depuis la dernière release ──────────────────
|
||||
echo "→ Récupération de la dernière release..."
|
||||
|
||||
ASSETS_JSON=$(curl -sf "$REPO_API/releases?limit=1&page=1")
|
||||
@@ -69,7 +85,7 @@ print(releases[0]['tag_name'])
|
||||
|
||||
ok "Release : $TAG — URL : $ASSET_URL"
|
||||
|
||||
# ── 3. Télécharger le binaire ─────────────────────────────────────────────────
|
||||
# ── 5. Télécharger le binaire ─────────────────────────────────────────────────
|
||||
TMP_BIN="$(mktemp)"
|
||||
trap 'rm -f "$TMP_BIN"' EXIT
|
||||
|
||||
@@ -78,7 +94,7 @@ curl -fsSL -o "$TMP_BIN" "$ASSET_URL"
|
||||
chmod 755 "$TMP_BIN"
|
||||
ok "Binaire téléchargé ($(du -sh "$TMP_BIN" | cut -f1))"
|
||||
|
||||
# ── 4. Paramètres de configuration ────────────────────────────────────────────
|
||||
# ── 6. Paramètres de configuration ────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "--- Configuration du serveur ---"
|
||||
|
||||
@@ -89,9 +105,9 @@ MQTT_ENABLED="${MQTT_ENABLED:-false}"
|
||||
|
||||
ok "Serveur : $SERVER_IP:$SERVER_PORT | MQTT broker : $MQTT_HOST"
|
||||
|
||||
# ── 5. Installer le binaire ────────────────────────────────────────────────────
|
||||
# ── 7. Installer le binaire ────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "[1/5] Installation du binaire dans /usr/local/bin/"
|
||||
echo "[1/5] Installation du binaire..."
|
||||
|
||||
# Arrêter le service si en cours (le binaire ne peut pas être écrasé à chaud)
|
||||
if systemctl is-active --quiet nanometrics-agent 2>/dev/null; then
|
||||
@@ -103,42 +119,15 @@ cp "$TMP_BIN" "$INSTALL_BIN"
|
||||
chmod 755 "$INSTALL_BIN"
|
||||
ok "Binaire installé"
|
||||
|
||||
# ── 6. Créer le répertoire de configuration ───────────────────────────────────
|
||||
# ── 8. Créer le répertoire de configuration ───────────────────────────────────
|
||||
echo "[2/5] Création de $CONFIG_DIR"
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
chmod 755 "$CONFIG_DIR"
|
||||
ok "Répertoire créé"
|
||||
|
||||
# ── 7. Écrire config.toml ─────────────────────────────────────────────────────
|
||||
# ── 9. Écrire config.toml ─────────────────────────────────────────────────────
|
||||
echo "[3/5] Écriture de $CONFIG_FILE"
|
||||
|
||||
OVERWRITE_CONFIG="${OVERWRITE_CONFIG:-}"
|
||||
WRITE_CONFIG=true
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
if [ "${OVERWRITE_CONFIG}" = "true" ]; then
|
||||
warn "OVERWRITE_CONFIG=true — config.toml sera écrasé"
|
||||
WRITE_CONFIG=true
|
||||
elif [ -t 0 ]; then
|
||||
# Mode interactif (bash local, pas curl | bash)
|
||||
echo ""
|
||||
warn "Un config.toml existe déjà :"
|
||||
echo " $CONFIG_FILE"
|
||||
printf " Écraser la configuration existante ? [o/N] : "
|
||||
read -r _ANS
|
||||
if [[ "$_ANS" =~ ^[Oo]$ ]]; then
|
||||
WRITE_CONFIG=true
|
||||
else
|
||||
ok "config.toml conservé"
|
||||
WRITE_CONFIG=false
|
||||
fi
|
||||
else
|
||||
warn "config.toml déjà présent — conservé (relancez avec OVERWRITE_CONFIG=true pour écraser)"
|
||||
WRITE_CONFIG=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$WRITE_CONFIG" = "true" ]; then
|
||||
cat > "$CONFIG_FILE" << TOML
|
||||
[server]
|
||||
ip = "$SERVER_IP"
|
||||
@@ -184,15 +173,14 @@ mqtt = false
|
||||
udp = true
|
||||
mqtt = false
|
||||
TOML
|
||||
chmod 644 "$CONFIG_FILE"
|
||||
ok "config.toml créé"
|
||||
fi
|
||||
chmod 644 "$CONFIG_FILE"
|
||||
ok "config.toml écrit"
|
||||
|
||||
# S'assurer que le fichier est toujours lisible (cas d'un config existant en 640)
|
||||
# S'assurer que le répertoire est accessible
|
||||
chmod 644 "$CONFIG_FILE" 2>/dev/null || true
|
||||
chmod 755 "$CONFIG_DIR"
|
||||
|
||||
# ── 8. Installer le fichier service ──────────────────────────────────────────
|
||||
# ── 10. Installer le fichier service ─────────────────────────────────────────
|
||||
echo "[4/5] Installation du service systemd"
|
||||
curl -fsSL -o "$SERVICE_FILE" "$SERVICE_URL"
|
||||
chmod 644 "$SERVICE_FILE"
|
||||
@@ -200,7 +188,7 @@ systemctl daemon-reload
|
||||
systemctl enable nanometrics-agent
|
||||
ok "Service installé et activé"
|
||||
|
||||
# ── 9. Démarrer le service ────────────────────────────────────────────────────
|
||||
# ── 11. Démarrer le service ───────────────────────────────────────────────────
|
||||
echo "[5/5] Démarrage du service"
|
||||
systemctl restart nanometrics-agent
|
||||
sleep 2
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
ws "github.com/user/nanometrics/server/websocket"
|
||||
)
|
||||
|
||||
const serverVersion = "0.1.0"
|
||||
|
||||
func main() {
|
||||
cfg := config.Load()
|
||||
|
||||
@@ -92,6 +94,7 @@ func main() {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
stats.Version = serverVersion
|
||||
hub.Broadcast(models.WSMessage{Type: "server_stats", Data: stats})
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -127,4 +127,5 @@ type ServerStats struct {
|
||||
CPUPercent float64 `json:"cpu_percent"`
|
||||
MemUsed int64 `json:"mem_used"`
|
||||
MemTotal int64 `json:"mem_total"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user