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 @@ + + +
Chargement...
+Chargement...
'; + try { + const info = await apiGet('/api/systeme/info'); + let html = ''; + + // Reseau + html += 'Impossible de charger les informations systeme.
'; + } +} + async function regenererBoothPassword() { const booth = config.booth || {}; const url = getValue('admin-booth-url').replace(/\/$/, '');