3 Commits

Author SHA1 Message Date
iv3jdv 5fe69b5846 update images 2026-04-23 18:02:46 +02:00
iv3jdv d4b901410e feat: add D-Star source_ext support in dashboard and database 2026-04-23 17:43:34 +02:00
iv3jdv 5f41744d93 Add HTTPS warnings for Push notifications 2026-04-23 14:46:58 +02:00
5 changed files with 35 additions and 18 deletions
+4
View File
@@ -38,6 +38,8 @@ The ecosystem consists of three main parts:
* Run: `python3 app.py`
#### 🔑 Generating VAPID Keys (Push Notifications)
> ⚠️ **IMPORTANT:** Web Push Notifications strictly require the dashboard to be accessed via a secure **HTTPS** connection (or localhost). Modern browsers will block push features over standard HTTP.
To enable Web Push Notifications, you must generate a unique VAPID key pair for your server.
1. Go to a free online generator like [vapidkeys.com](https://vapidkeys.com/).
2. Generate the keys.
@@ -100,6 +102,8 @@ L'ecosistema si compone di tre parti principali:
* Avvia: `python3 app.py`
#### 🔑 Generare le chiavi VAPID (Notifiche Push)
> ⚠️ **IMPORTANTE:** Le Notifiche Push Web richiedono tassativamente che la dashboard sia accessibile tramite una connessione sicura **HTTPS** (o localhost). I browser mobili e desktop bloccano le notifiche su connessioni HTTP non protette.
Per abilitare le Notifiche Push, devi generare una coppia di chiavi VAPID univoca per il tuo server.
1. Vai su un generatore online gratuito come [vapidkeys.com](https://vapidkeys.com/).
2. Genera la coppia di chiavi.
+10 -6
View File
@@ -47,6 +47,10 @@ def init_db():
c.execute("ALTER TABLE radio_logs ADD COLUMN protocol TEXT DEFAULT 'DMR'")
except: pass
try:
c.execute("ALTER TABLE radio_logs ADD COLUMN source_ext TEXT DEFAULT ''")
except: pass
c.execute('''CREATE TABLE IF NOT EXISTS users
(id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT,
role TEXT, allowed_nodes TEXT)''')
@@ -122,8 +126,8 @@ def save_cache(data):
def save_to_sqlite(client_id, data, protocol="DMR"):
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("INSERT INTO radio_logs (timestamp, client_id, protocol, source_id, target, slot, duration, ber) VALUES (datetime('now', 'localtime'), ?, ?, ?, ?, ?, ?, ?)",
(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)))
c.execute("INSERT INTO radio_logs (timestamp, client_id, protocol, source_id, target, slot, duration, ber, source_ext) VALUES (datetime('now', 'localtime'), ?, ?, ?, ?, ?, ?, ?, ?)",
(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), str(data.get('source_ext', ''))))
conn.commit()
conn.close()
socketio.emit('dati_aggiornati')
@@ -345,13 +349,13 @@ def on_message(client, userdata, msg):
else:
target = current_target
active_calls[cid][k] = {'src': src, 'dst': target}
active_calls[cid][k] = {'src': src, 'dst': target, 'ext': str(p.get('source_ext', ''))}
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': '---'})
p.update({'source_id': info['src'], 'destination_id': info['dst']})
info = active_calls[cid].get(k, {'src': '---', 'dst': '---', 'ext': ''})
p.update({'source_id': info['src'], 'destination_id': info['dst'], 'source_ext': info['ext']})
save_to_sqlite(cid, p, protocol=name)
client_telemetry[cid]["alt"] = f"{'' if act=='end' else '⚠️'} {name}: {info['src']}"
save_cache(client_telemetry)
@@ -381,7 +385,7 @@ def get_clients():
def get_logs():
conn = sqlite3.connect(DB_PATH, timeout=10)
c = conn.cursor()
c.execute("SELECT timestamp, client_id, protocol, source_id, target, slot, duration, ber FROM radio_logs ORDER BY id DESC LIMIT 60")
c.execute("SELECT timestamp, client_id, protocol, source_id, target, slot, duration, ber, source_ext FROM radio_logs ORDER BY id DESC LIMIT 60")
logs = c.fetchall()
conn.close()
return jsonify(logs)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 88 KiB

+8 -10
View File
@@ -4,14 +4,12 @@
This guide describes the steps to install the Central
Dashboard and the Remote Agents on MMDVM nodes.
------------------------------------------------------------
1. PRE-REQUISITES
------------------------------------------------------------
Ensure Python 3 is installed on all systems.
The necessary dependencies are listed in the
'requirements.txt' file.
Install dependencies:
pip install -r requirements.txt
@@ -19,7 +17,6 @@ Install dependencies:
2. SERVER SETUP (CENTRAL HUB)
------------------------------------------------------------
The server handles the web interface and user permissions.
Steps:
1. Configure 'config.json' using 'config.example.json' as a template.
2. Enter MQTT credentials and VAPID keys.
@@ -32,6 +29,10 @@ Steps:
------------------------------------------------------------
Required to enable browser and mobile notifications.
⚠️ WARNING: Web Push Notifications strictly require the
dashboard to be accessed via a secure HTTPS connection
(or localhost). They will NOT work over standard HTTP.
1. Go to https://vapidkeys.com/ and generate the keys.
2. Copy 'Public Key' and 'Private Key' into 'config.json'.
3. Set 'vapid_claim_email' (e.g., "mailto:your@email.com").
@@ -40,7 +41,6 @@ Required to enable browser and mobile notifications.
4. AGENT SETUP (REMOTE NODES)
------------------------------------------------------------
To be installed on each Raspberry Pi / MMDVM Node.
1. Copy 'system_monitor.py' and 'node_config.json' to
'/opt/node_agent/'.
2. Edit 'node_config.json' with a unique 'client_id'.
@@ -50,7 +50,6 @@ To be installed on each Raspberry Pi / MMDVM Node.
5. RUNNING AS A SERVICE (SYSTEMD)
------------------------------------------------------------
For auto-start and process monitoring.
Configuration:
1. Copy .service files to '/etc/systemd/system/':
- Server: sudo cp fleet-console.service /etc/systemd/system/
@@ -69,14 +68,12 @@ Configuration:
Questa guida descrive i passaggi per installare la Dashboard
Centrale e gli Agenti Remoti sui nodi MMDVM.
------------------------------------------------------------
1. REQUISITI PRELIMINARI
------------------------------------------------------------
Assicurarsi di avere Python 3 installato su tutti i sistemi.
Le dipendenze necessarie sono elencate nel file
'requirements.txt'.
Installazione dipendenze:
pip install -r requirements.txt
@@ -84,7 +81,6 @@ Installazione dipendenze:
2. SETUP DEL SERVER (HUB CENTRALE)
------------------------------------------------------------
Il server gestisce l'interfaccia web e i permessi.
Passaggi:
1. Configura 'config.json' partendo da 'config.example.json'.
2. Inserisci le credenziali MQTT e le chiavi VAPID.
@@ -97,6 +93,10 @@ Passaggi:
------------------------------------------------------------
Necessarie per abilitare le notifiche su browser e mobile.
⚠️ ATTENZIONE: Le notifiche push richiedono tassativamente
che la dashboard sia accessibile tramite una connessione
sicura HTTPS. I browser bloccano la funzione su HTTP normale.
1. Vai su https://vapidkeys.com/ e genera le chiavi.
2. Copia 'Public Key' e 'Private Key' nel 'config.json'.
3. Imposta 'vapid_claim_email' (es. "mailto:tua@email.com").
@@ -105,7 +105,6 @@ Necessarie per abilitare le notifiche su browser e mobile.
4. SETUP DELL'AGENTE (NODI REMOTI)
------------------------------------------------------------
Da installare su ogni Raspberry Pi / Nodo MMDVM.
1. Copia 'system_monitor.py' e 'node_config.json' in
'/opt/node_agent/'.
2. Modifica 'node_config.json' con il 'client_id' univoco.
@@ -115,7 +114,6 @@ Da installare su ogni Raspberry Pi / Nodo MMDVM.
5. ESECUZIONE COME SERVIZIO (SYSTEMD)
------------------------------------------------------------
Per l'avvio automatico e il monitoraggio del processo.
Configurazione:
1. Copia i file .service in '/etc/systemd/system/':
- Server: sudo cp fleet-console.service /etc/systemd/system/
+13 -2
View File
@@ -795,7 +795,19 @@
const res = await fetch('/api/logs'); const logs = await res.json();
const tbody = document.getElementById('log-body'); let tableHTML = ""; let networkLogs = {};
logs.forEach(row => {
const time = row[0] ? row[0].split(' ')[1] : "--:--"; const clientId = row[1]; const protocol = row[2] || "DMR"; const source = row[3] || "---"; const target = row[4] || "---"; const rawSlot = row[5];
const time = row[0] ? row[0].split(' ')[1] : "--:--";
const clientId = row[1];
const protocol = row[2] || "DMR";
let source = row[3] || "---";
const target = row[4] || "---";
const rawSlot = row[5];
const source_ext = row[8]; // <--- NUOVO CAMPO DAL BACKEND
// Formattiamo il source_ext se presente e non vuoto
if (source_ext && source_ext.trim() !== "") {
source = `${source} <span style="font-size:0.8em; color:#94a3b8; font-family:'JetBrains Mono', monospace;">/${source_ext}</span>`;
}
const slotDisplay = protocol === "DMR" ? `TS${rawSlot}` : "--";
let protoColor = "#3b82f6"; if (protocol === "NXDN") protoColor = "#10b981"; else if (protocol === "YSF") protoColor = "#8b5cf6"; else if (protocol === "D-STAR") protoColor = "#06b6d4"; else if (protocol === "P25") protoColor = "#f59e0b";
@@ -810,7 +822,6 @@
clients.forEach(c => { const localDiv = document.getElementById(`sys-log-${c.id}`); if (localDiv && networkLogs[c.id]) { localDiv.innerHTML = networkLogs[c.id]; } });
} catch (e) { console.error(e); }
}
// --- USER MANAGEMENT (ADD & EDIT) ---
async function openAdmin() { document.getElementById('admin-modal').style.display = 'flex'; loadUsers(); loadSettings(); cancelEdit(); }
function closeAdmin() { document.getElementById('admin-modal').style.display = 'none'; cancelEdit(); }