From deca890b342ca7a71cedd976fd4f75ee9d3cd656 Mon Sep 17 00:00:00 2001 From: Jules Date: Wed, 8 Apr 2026 20:55:34 +0200 Subject: [PATCH] Admin: onglet Infos systeme (reseau, RAM, disque, temp, camera) - Endpoint /api/systeme/info : IPs par interface, hostname, uptime, disque, RAM, temperature CPU, version Python, statut camera - Onglet Infos dans le backoffice avec affichage visuel (barres, couleurs) - Bouton Actualiser pour rafraichir les donnees en temps reel Co-Authored-By: Claude Sonnet 4.6 --- backend/main.py | 103 +++++++++++++++++++++++++++++++++++++++++ frontend/css/style.css | 76 ++++++++++++++++++++++++++++++ frontend/index.html | 12 +++++ frontend/js/admin.js | 75 ++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+) diff --git a/backend/main.py b/backend/main.py index d86eb78..adb63ec 100644 --- a/backend/main.py +++ b/backend/main.py @@ -152,6 +152,109 @@ async def api_preview(): return {"image": f"data:image/jpeg;base64,{b64}"} +@app.get("/api/systeme/info") +async def api_systeme_info(): + import socket + import subprocess + import shutil + + # Interfaces reseau avec leurs IPs + interfaces = [] + try: + import netifaces + for iface in netifaces.interfaces(): + addrs = netifaces.ifaddresses(iface) + ipv4 = addrs.get(netifaces.AF_INET, []) + ipv6 = addrs.get(netifaces.AF_INET6, []) + ips = [a['addr'] for a in ipv4 if a.get('addr') != '127.0.0.1'] + ips += [a['addr'].split('%')[0] for a in ipv6 if not a.get('addr', '').startswith('::1')] + if ips: + interfaces.append({"nom": iface, "ips": ips}) + except ImportError: + # Fallback sans netifaces + try: + out = subprocess.check_output(["ip", "-o", "addr", "show"], text=True) + for ligne in out.splitlines(): + parts = ligne.split() + if len(parts) >= 4: + iface = parts[1] + ip = parts[3].split('/')[0] + if ip not in ('127.0.0.1', '::1') and not ip.startswith('fe80'): + existing = next((x for x in interfaces if x['nom'] == iface), None) + if existing: + existing['ips'].append(ip) + else: + interfaces.append({"nom": iface, "ips": [ip]}) + except Exception: + pass + + # Hostname + hostname = socket.gethostname() + + # Uptime + uptime_str = "" + try: + with open('/proc/uptime') as f: + secs = float(f.read().split()[0]) + h, rem = divmod(int(secs), 3600) + m = rem // 60 + uptime_str = f"{h}h {m}min" + except Exception: + pass + + # Espace disque (partition racine) + disque = {} + try: + total, used, free = shutil.disk_usage("/") + disque = { + "total": round(total / 1e9, 1), + "utilise": round(used / 1e9, 1), + "libre": round(free / 1e9, 1), + } + except Exception: + pass + + # Memoire RAM + ram = {} + try: + with open('/proc/meminfo') as f: + lignes = {l.split(':')[0]: l.split(':')[1].strip() for l in f.readlines()} + total_kb = int(lignes.get('MemTotal', '0 kB').split()[0]) + libre_kb = int(lignes.get('MemAvailable', '0 kB').split()[0]) + utilise_kb = total_kb - libre_kb + ram = { + "total": round(total_kb / 1e6, 1), + "utilise": round(utilise_kb / 1e6, 1), + "libre": round(libre_kb / 1e6, 1), + } + except Exception: + pass + + # Temperature CPU (RPi / Linux) + temp = None + try: + with open('/sys/class/thermal/thermal_zone0/temp') as f: + temp = round(int(f.read().strip()) / 1000, 1) + except Exception: + pass + + # Version Python et app + import sys + python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" + + return { + "hostname": hostname, + "interfaces": interfaces, + "uptime": uptime_str, + "disque": disque, + "ram": ram, + "temperature_cpu": temp, + "python": python_version, + "camera_mode": camera.mode, + "camera_connectee": camera.connectee, + } + + @app.get("/api/camera/statut") async def api_camera_statut(): return { diff --git a/frontend/css/style.css b/frontend/css/style.css index 1149010..e844bda 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -780,6 +780,82 @@ html, body { display: flex; } +/* Infos systeme */ +.infos-systeme-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.5rem; +} + +.infos-systeme-contenu { + display: flex; + flex-direction: column; + gap: 1.2rem; +} + +.infos-section { + background: rgba(255,255,255,0.05); + border-radius: 0.8rem; + padding: 1rem 1.2rem; +} + +.infos-section h4 { + margin: 0 0 0.8rem 0; + font-size: 1rem; + opacity: 0.7; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.infos-ligne { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.35rem 0; + border-bottom: 1px solid rgba(255,255,255,0.07); + font-size: 1.05rem; +} + +.infos-ligne:last-child { + border-bottom: none; +} + +.infos-ligne span { + opacity: 0.65; +} + +.infos-ip { + font-family: monospace; + font-size: 1.1rem; + letter-spacing: 0.03em; +} + +.infos-barre-fond { + margin-top: 0.6rem; + background: rgba(255,255,255,0.1); + border-radius: 4px; + height: 6px; + overflow: hidden; +} + +.infos-barre { + height: 100%; + border-radius: 4px; + transition: width 0.4s ease; +} + +.infos-chargement, .infos-erreur { + opacity: 0.5; + text-align: center; + padding: 2rem; +} + +.btn-petit { + font-size: 0.9rem; + padding: 0.4rem 1rem; +} + /* Toggles */ .toggle-groupe { display: flex; diff --git a/frontend/index.html b/frontend/index.html index 6f92536..88f8d62 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -331,6 +331,7 @@ + @@ -567,6 +568,17 @@ + + +
+
+

Informations systeme

+ +
+
+

Chargement...

+
+
diff --git a/frontend/js/admin.js b/frontend/js/admin.js index bbff9a3..6f1efc0 100644 --- a/frontend/js/admin.js +++ b/frontend/js/admin.js @@ -685,6 +685,81 @@ async function sauvegarderBooth() { chargerBoothInfo(); } +// === INFOS SYSTEME === + +async function chargerInfosSysteme() { + const contenu = document.getElementById('infos-systeme-contenu'); + contenu.innerHTML = '

Chargement...

'; + try { + const info = await apiGet('/api/systeme/info'); + let html = ''; + + // Reseau + html += '
'; + html += '

🌐 Reseau

'; + html += `
Hostname${info.hostname}
`; + if (info.interfaces && info.interfaces.length > 0) { + info.interfaces.forEach(iface => { + iface.ips.forEach(ip => { + html += `
${iface.nom}${ip}
`; + }); + }); + } else { + html += '
Aucune interface—
'; + } + html += '
'; + + // Systeme + html += '
'; + html += '

💻 Systeme

'; + if (info.uptime) html += `
Uptime${info.uptime}
`; + if (info.temperature_cpu !== null && info.temperature_cpu !== undefined) { + const couleur = info.temperature_cpu > 70 ? '#e53935' : info.temperature_cpu > 55 ? '#fb8c00' : '#43a047'; + html += `
Temperature CPU${info.temperature_cpu} °C
`; + } + if (info.python) html += `
Python${info.python}
`; + html += '
'; + + // Stockage + if (info.disque && info.disque.total) { + const pct = Math.round((info.disque.utilise / info.disque.total) * 100); + const couleurDisk = pct > 85 ? '#e53935' : pct > 65 ? '#fb8c00' : '#43a047'; + html += '
'; + html += '

💾 Stockage

'; + html += `
Total${info.disque.total} Go
`; + html += `
Utilise${info.disque.utilise} Go (${pct}%)
`; + html += `
Libre${info.disque.libre} Go
`; + html += '
'; + html += '
'; + } + + // RAM + if (info.ram && info.ram.total) { + const pctRam = Math.round((info.ram.utilise / info.ram.total) * 100); + const couleurRam = pctRam > 85 ? '#e53935' : pctRam > 65 ? '#fb8c00' : '#43a047'; + html += '
'; + html += '

🫀 Memoire RAM

'; + html += `
Total${info.ram.total} Go
`; + html += `
Utilise${info.ram.utilise} Go (${pctRam}%)
`; + html += `
Libre${info.ram.libre} Go
`; + html += '
'; + html += '
'; + } + + // Camera + html += '
'; + html += '

📷 Camera

'; + const camOk = info.camera_connectee; + html += `
Mode${info.camera_mode}
`; + html += `
Statut${camOk ? 'Connectee' : 'Deconnectee'}
`; + html += '
'; + + contenu.innerHTML = html; + } catch (e) { + contenu.innerHTML = '

Impossible de charger les informations systeme.

'; + } +} + async function regenererBoothPassword() { const booth = config.booth || {}; const url = getValue('admin-booth-url').replace(/\/$/, '');