Aggiunti WebSockets al frontend
This commit is contained in:
@@ -13,24 +13,25 @@
|
||||
|
||||
### 🏗️ System Architecture
|
||||
The ecosystem consists of three main parts:
|
||||
1. **The Central Dashboard (`app.py`):** A Flask web server that handles the UI (with modern Glassmorphism design), user permissions (SQLite), and logic.
|
||||
1. **The Central Dashboard (`app.py`):** A Flask web server that handles the UI (with modern Glassmorphism design), user permissions (SQLite), and **WebSocket connections for zero-latency updates**.
|
||||
2. **The Remote Agent (`system_monitor.py`):** A lightweight script running on each repeater (Raspberry Pi/Linux) that monitors hardware and executes commands.
|
||||
3. **The MQTT Broker:** The communication backbone. All data and commands flow through MQTT for near-instant responsiveness.
|
||||
|
||||
### ✨ Features
|
||||
* **Zero-Latency Real-Time UI:** Powered by WebSockets (Socket.IO), the dashboard updates instantly upon radio traffic or telemetry changes, completely eliminating heavy HTTP polling overhead.
|
||||
* **Centralized Telemetry:** Real-time CPU, RAM, Temperature, and Disk usage for all nodes.
|
||||
* **Service Management:** Start, Stop, or Restart system daemons (MMDVMHost, DMRGateway, etc.) remotely.
|
||||
* **Smart Auto-Healing:** The agent automatically detects crashed services and attempts to revive them before raising critical alerts (includes Telegram notifications).
|
||||
* **Hardware Reset (GPIO):** Physically reboot the MMDVM radio HAT via GPIO pins directly from the dashboard, automatically restarting the host daemon for a fail-proof serial connection recovery.
|
||||
* **Remote Configuration:** Built-in web editor for `.ini` files—no SSH required.
|
||||
* **Live Heard Log:** Unified view of radio traffic across the entire network.
|
||||
* **Live Heard Log:** Unified, real-time view of radio traffic across the entire network.
|
||||
* **Global Operations:** Switch profiles (e.g., Profile A/B) or force network-wide configuration updates instantly on all repeaters with one click.
|
||||
* **PWA Ready (Mobile App):** Fully responsive design that can be installed directly on Android/iOS as a Progressive Web App for a native, full-screen mobile experience.
|
||||
|
||||
### 🚀 Installation & Setup
|
||||
|
||||
#### 1. Server Setup (Central Hub)
|
||||
* Install dependencies: `pip install flask paho-mqtt psutil werkzeug`
|
||||
* Install dependencies: `pip install flask flask-socketio paho-mqtt psutil werkzeug`
|
||||
* Configure `config.json` (use `config.example.json` as template) with your MQTT credentials.
|
||||
* Define your repeaters in `clients.json`.
|
||||
* Run: `python3 app.py`
|
||||
@@ -53,24 +54,25 @@ The `system_monitor.py` must be installed on every node you wish to monitor.
|
||||
|
||||
### 🏗️ Architettura del Sistema
|
||||
L'ecosistema si compone di tre parti principali:
|
||||
1. **Dashboard Centrale (`app.py`):** Un server web Flask che gestisce l'interfaccia (con design moderno in stile Glassmorphism), i permessi utenti (SQLite) e la logica di controllo.
|
||||
1. **Dashboard Centrale (`app.py`):** Un server web Flask che gestisce l'interfaccia (con design moderno in stile Glassmorphism), i permessi utenti (SQLite) e **connessioni WebSocket per aggiornamenti a latenza zero**.
|
||||
2. **Agente Remoto (`system_monitor.py`):** Uno script leggero in esecuzione su ogni ripetitore (Raspberry Pi/Linux) che raccoglie i dati hardware ed esegue i comandi.
|
||||
3. **Broker MQTT:** Il centro nevralgico della comunicazione. Tutti i dati e i comandi viaggiano su MQTT per una reattività istantanea.
|
||||
|
||||
### ✨ Funzionalità
|
||||
* **Interfaccia Real-Time a Latenza Zero:** Grazie all'integrazione di WebSockets (Socket.IO), la dashboard scatta all'istante al passaggio di traffico radio o ai cambi di telemetria, eliminando totalmente il carico del polling HTTP continuo.
|
||||
* **Telemetria Centralizzata:** Stato in tempo reale di CPU, RAM, Temperatura e Disco di tutti i nodi.
|
||||
* **Gestione Servizi:** Avvio, arresto o riavvio dei demoni di sistema (MMDVMHost, DMRGateway, ecc.) da remoto.
|
||||
* **Auto-Healing Intelligente:** L'agente rileva automaticamente i servizi andati in blocco e tenta di rianimarli prima di inviare allarmi critici (include notifiche Telegram).
|
||||
* **Reset Hardware (GPIO):** Invia un impulso di reset fisico alla scheda radio MMDVM tramite i pin GPIO direttamente dalla dashboard, riavviando automaticamente il demone host per ripristinare la comunicazione seriale.
|
||||
* **Configurazione Remota:** Editor web integrato per i file `.ini`: modifica i parametri senza accedere in SSH.
|
||||
* **Log Ascolti Live:** Vista unificata del traffico radio di tutta la rete.
|
||||
* **Log Ascolti Live:** Vista unificata e in tempo reale del traffico radio di tutta la rete.
|
||||
* **Operazioni Globali:** Commuta tra diversi assetti (es. Profilo A/B) o forza l'aggiornamento dei dati contemporaneamente su tutta la rete con un solo clic.
|
||||
* **PWA Ready (App Mobile):** Design completamente responsivo, installabile su smartphone Android e iOS come Progressive Web App per un'esperienza fluida e nativa a schermo intero.
|
||||
|
||||
### 🚀 Installazione e Configurazione
|
||||
|
||||
#### 1. Setup del Server (Hub Centrale)
|
||||
* Installa le dipendenze: `pip install flask paho-mqtt psutil werkzeug`
|
||||
* Installa le dipendenze: `pip install flask flask-socketio paho-mqtt psutil werkzeug`
|
||||
* Configura `config.json` (usa `config.example.json` come base) con le credenziali MQTT.
|
||||
* Definisci i tuoi ripetitori nel file `clients.json`.
|
||||
* Avvia: `python3 app.py`
|
||||
|
||||
@@ -9,6 +9,7 @@ import threading
|
||||
import time
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from flask_socketio import SocketIO, emit
|
||||
|
||||
# --- CONFIGURAZIONE LOGGING ---
|
||||
logging.basicConfig(
|
||||
@@ -99,6 +100,7 @@ def get_call(id, proto="DMR"):
|
||||
|
||||
def save_cache(data):
|
||||
with open(CACHE_FILE, 'w') as f: json.dump(data, f)
|
||||
socketio.emit('dati_aggiornati')
|
||||
|
||||
def save_to_sqlite(client_id, data, protocol="DMR"):
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
@@ -107,8 +109,10 @@ def save_to_sqlite(client_id, data, protocol="DMR"):
|
||||
(client_id, protocol, str(data.get('source_id', '---')), str(data.get('destination_id', '---')), data.get('slot', 0), round(data.get('duration', 0), 1), round(data.get('ber', 0), 2)))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
socketio.emit('dati_aggiornati')
|
||||
|
||||
app = Flask(__name__)
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
|
||||
app.secret_key = 'ari_fvg_secret_ultra_secure'
|
||||
client_states = {}
|
||||
device_configs = {}
|
||||
@@ -169,6 +173,7 @@ def on_message(client, userdata, msg):
|
||||
|
||||
elif parts[0] == 'servizi':
|
||||
client_states[cid] = payload
|
||||
socketio.emit('dati_aggiornati') # <--- WEBSOCKET
|
||||
if payload.upper() not in ['OFF', 'OFFLINE', '']:
|
||||
tel = client_telemetry.get(cid, {})
|
||||
if isinstance(tel, dict) and '🔄' in str(tel.get('ts1', '')):
|
||||
@@ -187,9 +192,9 @@ def on_message(client, userdata, msg):
|
||||
"files": data.get("files", data.get("config_files", [])),
|
||||
"profiles": data.get("profiles", {"A": "PROFILO A", "B": "PROFILO B"})
|
||||
}
|
||||
socketio.emit('dati_aggiornati') # <--- WEBSOCKET
|
||||
except Exception as e: logger.error(f"Errore parsing health: {e}")
|
||||
|
||||
# NUOVO BLOCCO: Intercettazione configurazione DMRGateway
|
||||
elif len(parts) >= 4 and parts[0] == 'data' and parts[2].lower() == 'dmrgateway' and (parts[3].upper().startswith('NETWORK') or parts[3].upper().startswith('DMR NETWORK')):
|
||||
try:
|
||||
cid = parts[1].lower()
|
||||
@@ -211,6 +216,7 @@ def on_message(client, userdata, msg):
|
||||
|
||||
if is_ts1: network_mapping[cid]["ts1"] = net_name
|
||||
if is_ts2: network_mapping[cid]["ts2"] = net_name
|
||||
socketio.emit('dati_aggiornati') # <--- WEBSOCKET
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Errore parsing DMRGateway per {cid}: {e}")
|
||||
@@ -259,6 +265,7 @@ def on_message(client, userdata, msg):
|
||||
active_calls[cid][sk] = {'src': src, 'dst': dst}
|
||||
client_telemetry[cid]["alt"] = ""
|
||||
client_telemetry[cid][sk] = f"🎙️ {src} ➔ TG {dst}"
|
||||
socketio.emit('dati_aggiornati') # <--- WEBSOCKET
|
||||
elif act in ['end', 'lost']:
|
||||
info = active_calls[cid].get(sk, {'src': '---', 'dst': '---'})
|
||||
d['source_id'], d['destination_id'] = info['src'], info['dst']
|
||||
@@ -288,6 +295,7 @@ def on_message(client, userdata, msg):
|
||||
|
||||
active_calls[cid][k] = {'src': src, 'dst': target}
|
||||
client_telemetry[cid].update({"ts1":"","ts2":"","alt": f"{ico} {name}: {src} ➔ {target}"})
|
||||
socketio.emit('dati_aggiornati') # <--- WEBSOCKET
|
||||
|
||||
elif act in ['end', 'lost']:
|
||||
info = active_calls[cid].get(k, {'src': '---', 'dst': '---'})
|
||||
@@ -619,4 +627,4 @@ def serve_icon():
|
||||
|
||||
if __name__ == '__main__':
|
||||
threading.Thread(target=auto_update_ids, daemon=True).start()
|
||||
app.run(host='0.0.0.0', port=5000)
|
||||
socketio.run(app, host='0.0.0.0', port=9000)
|
||||
|
||||
+20
-1
@@ -363,6 +363,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
|
||||
|
||||
<script>
|
||||
// --- 1. TRANSLATION SYSTEM (i18n) ---
|
||||
const i18n = {
|
||||
@@ -655,7 +657,7 @@
|
||||
}).join('');
|
||||
|
||||
refreshStates(); refreshLogs();
|
||||
setInterval(() => { refreshStates(); refreshLogs(); }, 3000);
|
||||
// setInterval rimosso! Da oggi si va in tempo reale!
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
@@ -1075,6 +1077,23 @@
|
||||
}
|
||||
|
||||
initUI();
|
||||
|
||||
// --- MOTORE WEBSOCKET REAL-TIME ---
|
||||
const socket = io();
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log("🟢 Connesso al server via WebSocket in tempo reale!");
|
||||
// Facciamo un aggiornamento di sicurezza appena il tunnel si apre
|
||||
refreshStates();
|
||||
refreshLogs();
|
||||
});
|
||||
|
||||
socket.on('dati_aggiornati', function() {
|
||||
console.log("⚡ Rilevato nuovo traffico! Scatto istantaneo dell'interfaccia...");
|
||||
// Il server ha appena urlato che ci sono novità: ricarichiamo le card e i log!
|
||||
refreshStates();
|
||||
refreshLogs();
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
|
||||
Reference in New Issue
Block a user