8 Commits
Author SHA1 Message Date
iv3jdv caf77819a2 Merge pull request 'update index.html' (#4) from develop into main
Reviewed-on: #4
2026-10-16 21:01:30 +02:00
iv3jdv 47e51e10f0 update index.html 2026-10-16 21:00:21 +02:00
iv3jdv d56d3d6b5b Merge pull request 'update index.html' (#3) from develop into main
Reviewed-on: #3
2026-10-16 20:48:43 +02:00
iv3jdv cd2960d00b update index.html 2026-10-16 20:45:13 +02:00
iv3jdv 95b5dd4cbc update index.html 2026-10-16 20:41:36 +02:00
iv3jdv 74d7c41b93 enhanceded RSSI 2026-10-16 20:31:23 +02:00
iv3jdv cf4dd93c06 Merge pull request 'add RSSI data' (#2) from develop into main
Reviewed-on: #2
2026-10-16 19:51:12 +02:00
iv3jdv 9fb0b01f30 add RSSI data 2026-10-16 19:39:58 +02:00
2 changed files with 68 additions and 6 deletions
+14 -3
View File
@@ -96,6 +96,10 @@ def init_db():
c.execute("ALTER TABLE radio_logs ADD COLUMN source_ext TEXT DEFAULT ''") c.execute("ALTER TABLE radio_logs ADD COLUMN source_ext TEXT DEFAULT ''")
except sqlite3.OperationalError: pass except sqlite3.OperationalError: pass
try:
c.execute("ALTER TABLE radio_logs ADD COLUMN rssi REAL DEFAULT 0")
except sqlite3.OperationalError: pass
c.execute('''CREATE TABLE IF NOT EXISTS users c.execute('''CREATE TABLE IF NOT EXISTS users
(id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT, (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT,
role TEXT, allowed_nodes TEXT)''') role TEXT, allowed_nodes TEXT)''')
@@ -185,8 +189,15 @@ def save_cache(data):
def save_to_sqlite(client_id, data, protocol="DMR"): def save_to_sqlite(client_id, data, protocol="DMR"):
conn = sqlite3.connect(DB_PATH, timeout=10) conn = sqlite3.connect(DB_PATH, timeout=10)
c = conn.cursor() c = conn.cursor()
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('src_id', '---')), str(data.get('dst_id', '---')), data.get('slot', 0), round(data.get('duration', 0), 1), round(data.get('ber', 0), 2), str(data.get('src_ext', '')))) rssi_val = 0
if 'rssi' in data and isinstance(data['rssi'], dict):
rssi_val = data['rssi'].get('ave', 0)
elif 'rssi' in data:
rssi_val = data['rssi']
c.execute("INSERT INTO radio_logs (timestamp, client_id, protocol, source_id, target, slot, duration, ber, source_ext, rssi) VALUES (datetime('now', 'localtime'), ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(client_id, protocol, str(data.get('src_id', '---')), str(data.get('dst_id', '---')), data.get('slot', 0), round(data.get('duration', 0), 1), round(data.get('ber', 0), 2), str(data.get('src_ext', '')), rssi_val))
conn.commit() conn.commit()
conn.close() conn.close()
notify_update('log_aggiornato') notify_update('log_aggiornato')
@@ -631,7 +642,7 @@ def get_clients():
def get_logs(): def get_logs():
conn = sqlite3.connect(DB_PATH, timeout=10) conn = sqlite3.connect(DB_PATH, timeout=10)
c = conn.cursor() c = conn.cursor()
c.execute("SELECT timestamp, client_id, protocol, source_id, target, slot, duration, ber, source_ext FROM radio_logs ORDER BY id DESC LIMIT 60") c.execute("SELECT timestamp, client_id, protocol, source_id, target, slot, duration, ber, source_ext, rssi FROM radio_logs ORDER BY id DESC LIMIT 60")
logs = c.fetchall() logs = c.fetchall()
conn.close() conn.close()
return jsonify(logs) return jsonify(logs)
+54 -3
View File
@@ -213,6 +213,7 @@
<th>Target / TG</th> <th>Target / TG</th>
<th data-i18n="thDur">Duration</th> <th data-i18n="thDur">Duration</th>
<th>BER</th> <th>BER</th>
<th>RSSI</th> <!-- NUOVA COLONNA -->
</tr> </tr>
</thead> </thead>
<tbody id="log-body"></tbody> <tbody id="log-body"></tbody>
@@ -970,17 +971,67 @@
const time = row[0] ? row[0].split(' ')[1] : "--:--"; 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 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]; const rawSlot = row[5]; const source_ext = row[8];
const rawRssi = row[9]; // Estrai il nuovo valore
let sourceHtml = esc(source); let sourceHtml = esc(source);
if (source_ext && source_ext.trim() !== "") { sourceHtml += ` <span style="font-size:0.8em; color:#94a3b8; font-family:'JetBrains Mono', monospace;">/${esc(source_ext)}</span>`; } if (source_ext && source_ext.trim() !== "") { sourceHtml += ` <span style="font-size:0.8em; color:#94a3b8; font-family:'JetBrains Mono', monospace;">/${esc(source_ext)}</span>`; }
const slotDisplay = protocol === "DMR" ? `TS${rawSlot}` : "--"; 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"; 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";
// Formatta l'RSSI con S-Meter grafico a segmenti
let rssiDisplay = '---';
if (rawRssi && rawRssi !== 0) {
// Calcolo livello S-Meter (Standard VHF/UHF: S9 = -93 dBm, 6dB per punto S)
let sLabel = "S0";
if (rawRssi >= -93) {
let over = Math.round((rawRssi + 93) / 10) * 10;
sLabel = over > 0 ? `S9<sup style="color: var(--danger); font-weight: 800;">+${over}</sup>` : "S9";
} else {
let sUnits = Math.floor((rawRssi + 141) / 6) + 1;
sLabel = `S${Math.max(1, Math.min(9, sUnits))}`;
}
// Calcolo segmenti accesi (9 segmenti totali)
let activeSegs = 1;
if (rawRssi >= -53) activeSegs = 9;
else if (rawRssi >= -63) activeSegs = 8;
else if (rawRssi >= -73) activeSegs = 7;
else if (rawRssi >= -83) activeSegs = 6;
else if (rawRssi >= -93) activeSegs = 5; // S9
else if (rawRssi >= -105) activeSegs = 4;
else if (rawRssi >= -117) activeSegs = 3;
else if (rawRssi >= -129) activeSegs = 2;
// Generazione barre HTML
let barsHtml = '';
const heights = [4, 5, 6, 7, 8, 10, 12, 14, 14];
for (let i = 0; i < 9; i++) {
let color = "rgba(255,255,255,0.1)"; // Spento
if (i < activeSegs) {
if (i < 5) color = "var(--success)"; // Verde
else if (i < 7) color = "#f59e0b"; // Giallo
else color = "var(--danger)"; // Rosso
}
barsHtml += `<div style="width: 4px; height: ${heights[i]}px; background: ${color}; border-radius: 1px;"></div>`;
}
rssiDisplay = `
<div style="display: flex; align-items: center; gap: 10px;" title="${esc(rawRssi)} dBm">
<div style="min-width: 40px; text-align: right; font-weight: bold; font-family: 'Inter', sans-serif;">${sLabel}</div>
<div style="display: flex; align-items: flex-end; gap: 2px; height: 14px;">
${barsHtml}
</div>
<span style="font-size: 0.85em; color: var(--text-muted); min-width: 7ch; text-align: right;">${esc(rawRssi)} dBm</span>
</div>`;
}
if (source.includes("🌐")) { if (source.includes("🌐")) {
if (!networkLogs[clientId]) networkLogs[clientId] = ""; if (!networkLogs[clientId]) networkLogs[clientId] = "";
networkLogs[clientId] += `<div style="border-bottom:1px solid rgba(255,255,255,0.1); padding:4px 0;"><span style="color:#64748b;">[${esc(time)}]</span> <span style="color:${protoColor}; font-weight:bold;">[${esc(protocol)}]</span> <span style="color:#cbd5e1;">${esc(source.replace("🌐", "").trim())}</span></div>`; networkLogs[clientId] += `<div style="border-bottom:1px solid rgba(255,255,255,0.1); padding:4px 0;"><span style="color:#64748b;">[${esc(time)}]</span> <span style="color:${protoColor}; font-weight:bold;">[${esc(protocol)}]</span> <span style="color:#cbd5e1;">${esc(source.replace("🌐", "").trim())}</span></div>`;
} else { } else {
tableHTML += `<tr><td>${esc(time)}</td><td><b style="color:var(--text-main);">${esc(String(clientId).toUpperCase())}</b></td><td><span style="background:${protoColor}; color:white; padding:3px 8px; border-radius:8px; font-size:0.7rem; font-weight:800;">${esc(protocol)}</span></td><td style="font-family:'JetBrains Mono', monospace; font-weight:bold; text-align:center;">${esc(slotDisplay)}</td><td style="color:var(--accent); font-weight:800;">${sourceHtml}</td><td style="font-family:'JetBrains Mono', monospace;">${esc(target.trim())}</td><td>${esc(row[6])}s</td><td>${esc(row[7])}%</td></tr>`; // Aggiunto il TD per l'RSSI alla fine
tableHTML += `<tr><td>${esc(time)}</td><td><b style="color:var(--text-main);">${esc(String(clientId).toUpperCase())}</b></td><td><span style="background:${protoColor}; color:white; padding:3px 8px; border-radius:8px; font-size:0.7rem; font-weight:800;">${esc(protocol)}</span></td><td style="font-family:'JetBrains Mono', monospace; font-weight:bold; text-align:center;">${esc(slotDisplay)}</td><td style="color:var(--accent); font-weight:800;">${sourceHtml}</td><td style="font-family:'JetBrains Mono', monospace;">${esc(target.trim())}</td><td>${esc(row[6])}s</td><td>${esc(row[7])}%</td><td style="font-family:'JetBrains Mono', monospace;">${rssiDisplay}</td></tr>`;
} }
}); });
tbody.innerHTML = tableHTML; tbody.innerHTML = tableHTML;
@@ -1121,10 +1172,10 @@
html += `<div style="display:flex; justify-content:space-between; align-items:center; padding:15px; border-bottom:1px solid var(--border-color); background:rgba(128,128,128,0.05); border-radius:12px; margin-bottom:8px; border-left: 5px solid ${statusColor};"> html += `<div style="display:flex; justify-content:space-between; align-items:center; padding:15px; border-bottom:1px solid var(--border-color); background:rgba(128,128,128,0.05); border-radius:12px; margin-bottom:8px; border-left: 5px solid ${statusColor};">
<div><strong style="font-size:1.1rem; display:block; font-family:'JetBrains Mono',monospace;">${esc(name)}</strong><span class="${isOnline?'':'blink'}" style="color:${statusColor}; font-size:0.8rem; font-weight:800;">${esc(String(status).toUpperCase())}</span></div> <div><strong style="font-size:1.1rem; display:block; font-family:'JetBrains Mono',monospace;">${esc(name)}</strong><span class="${isOnline?'':'blink'}" style="color:${statusColor}; font-size:0.8rem; font-weight:800;">${esc(String(status).toUpperCase())}</span></div>
<div style="display:flex; gap:8px;"> <div style="display:flex; gap:8px;">
${!isOnline ? `<button onclick="controlService(${q(clientId)}, ${q(name)}, 'start')" class="btn-cmd" style="background:var(--success);">▶</button>` : ''} ${!isOnline ? `<button onclick="controlService(${q(clientId)},${q(name)}, 'start')" class="btn-cmd" style="background:var(--success);">▶</button>` : ''}
<button onclick="controlService(${q(clientId)}, ${q(name)}, 'restart')" class="btn-cmd" style="background:#f59e0b;">🔄</button> <button onclick="controlService(${q(clientId)}, ${q(name)}, 'restart')" class="btn-cmd" style="background:#f59e0b;">🔄</button>
<button onclick="openEditorModal(${q(clientId)}, ${q(name)})" class="btn-cmd" style="background:#8e44ad;">📝</button> <button onclick="openEditorModal(${q(clientId)}, ${q(name)})" class="btn-cmd" style="background:#8e44ad;">📝</button>
${isOnline ? `<button onclick="controlService(${q(clientId)}, ${q(name)}, 'stop')" class="btn-cmd" style="background:var(--danger);">🛑</button>` : ''} ${isOnline ? `<button onclick="controlService(${q(clientId)},${q(name)}, 'stop')" class="btn-cmd" style="background:var(--danger);">🛑</button>` : ''}
</div> </div>
</div>`; </div>`;
} }