This commit is contained in:
2026-04-22 02:58:07 +02:00
parent 66d22411a4
commit 69d7e885bd
+58 -59
View File
@@ -12,7 +12,7 @@ from pywebpush import webpush, WebPushException
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from flask_socketio import SocketIO, emit from flask_socketio import SocketIO, emit
# --- CONFIGURAZIONE LOGGING --- # --- LOGGING CONFIGURATION ---
logging.basicConfig( logging.basicConfig(
handlers=[ handlers=[
RotatingFileHandler('/opt/web-control/fleet_console.log', maxBytes=10000000, backupCount=3), RotatingFileHandler('/opt/web-control/fleet_console.log', maxBytes=10000000, backupCount=3),
@@ -23,10 +23,10 @@ logging.basicConfig(
datefmt='%Y-%m-%d %H:%M:%S' datefmt='%Y-%m-%d %H:%M:%S'
) )
logger = logging.getLogger("FleetHub") logger = logging.getLogger("FleetHub")
# Silenzia lo spam delle richieste HTTP (GET /api/states 200 OK) # Silence HTTP request spam (GET /api/states 200 OK)
logging.getLogger('werkzeug').setLevel(logging.ERROR) logging.getLogger('werkzeug').setLevel(logging.ERROR)
# --- PERCORSI --- # --- PATHS ---
DB_PATH = '/opt/web-control/monitor.db' DB_PATH = '/opt/web-control/monitor.db'
CACHE_FILE = '/opt/web-control/telemetry_cache.json' CACHE_FILE = '/opt/web-control/telemetry_cache.json'
CONFIG_PATH = '/opt/web-control/config.json' CONFIG_PATH = '/opt/web-control/config.json'
@@ -38,7 +38,7 @@ def init_db():
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
c = conn.cursor() c = conn.cursor()
c.execute('PRAGMA journal_mode=WAL;') # <-- MAGIA: Abilita letture/scritture simultanee! c.execute('PRAGMA journal_mode=WAL;') # <-- MAGIC: Enable simultaneous read/write!
c.execute('''CREATE TABLE IF NOT EXISTS radio_logs c.execute('''CREATE TABLE IF NOT EXISTS radio_logs
(id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME, client_id TEXT, (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME, client_id TEXT,
@@ -63,14 +63,14 @@ def init_db():
h = generate_password_hash('admin123') h = generate_password_hash('admin123')
c.execute("INSERT INTO users (username, password_hash, role, allowed_nodes) VALUES (?,?,?,?)", c.execute("INSERT INTO users (username, password_hash, role, allowed_nodes) VALUES (?,?,?,?)",
('admin', h, 'admin', 'all')) ('admin', h, 'admin', 'all'))
logger.info(">>> UTENTE DI DEFAULT CREATO - User: admin | Pass: admin123 <<<") logger.info(">>> DEFAULT USER CREATED - User: admin | Pass: admin123 <<<")
conn.commit() conn.commit()
conn.close() conn.close()
init_db() init_db()
# --- CARICAMENTO DATABASE ID --- # --- ID DATABASE LOADING ---
user_db = {} user_db = {}
nxdn_db = {} nxdn_db = {}
@@ -134,10 +134,10 @@ if os.path.exists(CACHE_FILE):
active_calls = {} active_calls = {}
with open(CONFIG_PATH) as f: config = json.load(f) with open(CONFIG_PATH) as f: config = json.load(f)
# --- CALLBACKS MQTT --- # --- MQTT CALLBACKS ---
def on_connect(client, userdata, flags, reason_code, properties=None): def on_connect(client, userdata, flags, reason_code, properties=None):
if reason_code == 0: if reason_code == 0:
logger.info("Connesso al Broker MQTT con successo! Sottoscrizione ai topic in corso...") logger.info("Successfully connected to MQTT Broker! Subscribing to topics...")
client.subscribe([ client.subscribe([
("servizi/+/stat", 0), ("servizi/+/stat", 0),
("dmr-gateway/+/json", 0), ("dmr-gateway/+/json", 0),
@@ -151,10 +151,10 @@ def on_connect(client, userdata, flags, reason_code, properties=None):
("data/#", 0) ("data/#", 0)
]) ])
else: else:
logger.error(f"Errore di connessione MQTT. Codice motivo: {reason_code}") logger.error(f"MQTT Connection Error. Reason code: {reason_code}")
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties=None): def on_disconnect(client, userdata, disconnect_flags, reason_code, properties=None):
logger.warning(f"⚠️ Disconnessione MQTT rilevata! Codice motivo: {reason_code}. Tentativo di riconnessione automatico in corso...") logger.warning(f"⚠️ MQTT Disconnection detected! Reason code: {reason_code}. Attempting automatic reconnection...")
def on_message(client, userdata, msg): def on_message(client, userdata, msg):
try: try:
@@ -164,7 +164,7 @@ def on_message(client, userdata, msg):
if len(parts) < 2: return if len(parts) < 2: return
cid = parts[1].lower() cid = parts[1].lower()
# --- CATTURA CONFIGURAZIONI COMPLETE --- # --- CAPTURE FULL CONFIGURATIONS ---
if parts[0] == 'data' and len(parts) >= 4 and parts[3] == 'full_config': if parts[0] == 'data' and len(parts) >= 4 and parts[3] == 'full_config':
cid_conf = parts[1].lower() cid_conf = parts[1].lower()
svc_name = parts[2].lower() svc_name = parts[2].lower()
@@ -172,16 +172,16 @@ def on_message(client, userdata, msg):
device_configs[cid_conf] = {} device_configs[cid_conf] = {}
try: try:
device_configs[cid_conf][svc_name] = json.loads(payload) device_configs[cid_conf][svc_name] = json.loads(payload)
logger.debug(f"Configurazione salvata per {cid_conf} -> {svc_name}") logger.debug(f"Configuration saved for {cid_conf} -> {svc_name}")
except Exception as e: except Exception as e:
logger.error(f"Errore parsing config JSON: {e}") logger.error(f"Error parsing config JSON: {e}")
# --- GESTIONE STATI SERVIZIO E NODO --- # --- NODE AND SERVICE STATE MANAGEMENT ---
elif parts[0] == 'servizi': elif parts[0] == 'servizi':
client_states[cid] = payload client_states[cid] = payload
socketio.emit('dati_aggiornati') # <--- WEBSOCKET socketio.emit('dati_aggiornati') # <--- WEBSOCKET
# --- GRILLETTO PUSH: STATO NODO --- # --- PUSH TRIGGER: NODE STATE ---
if payload.upper() == 'OFFLINE': if payload.upper() == 'OFFLINE':
if last_notified_errors.get(f"{cid}_NODE") != 'OFFLINE': if last_notified_errors.get(f"{cid}_NODE") != 'OFFLINE':
broadcast_push_notification(f"💀 NODE OFFLINE: {cid.upper()}", "Connection lost with broker.") broadcast_push_notification(f"💀 NODE OFFLINE: {cid.upper()}", "Connection lost with broker.")
@@ -194,10 +194,10 @@ def on_message(client, userdata, msg):
if payload.upper() not in ['OFF', 'OFFLINE', '']: if payload.upper() not in ['OFF', 'OFFLINE', '']:
tel = client_telemetry.get(cid, {}) tel = client_telemetry.get(cid, {})
if isinstance(tel, dict) and '🔄' in str(tel.get('ts1', '')): if isinstance(tel, dict) and '🔄' in str(tel.get('ts1', '')):
client_telemetry[cid] = {"ts1": "In attesa...", "ts2": "In attesa...", "alt": ""} client_telemetry[cid] = {"ts1": "Waiting...", "ts2": "Waiting...", "alt": ""}
save_cache(client_telemetry) save_cache(client_telemetry)
# --- GESTIONE SALUTE DISPOSITIVI --- # --- DEVICE HEALTH MANAGEMENT ---
elif parts[0] == 'devices' and len(parts) >= 3 and parts[2] == 'services': elif parts[0] == 'devices' and len(parts) >= 3 and parts[2] == 'services':
try: try:
data = json.loads(payload) data = json.loads(payload)
@@ -208,30 +208,30 @@ def on_message(client, userdata, msg):
"disk": round(data.get("disk_usage_percent", 0), 1), "disk": round(data.get("disk_usage_percent", 0), 1),
"processes": data.get("processes", {}), "processes": data.get("processes", {}),
"files": data.get("files", data.get("config_files", [])), "files": data.get("files", data.get("config_files", [])),
"profiles": data.get("profiles", {"A": "PROFILO A", "B": "PROFILO B"}) "profiles": data.get("profiles", {"A": "PROFILE A", "B": "PROFILE B"})
} }
socketio.emit('dati_aggiornati') # <--- WEBSOCKET socketio.emit('dati_aggiornati') # <--- WEBSOCKET
# --- GRILLETTO PUSH: SERVIZI IN ERRORE --- # --- PUSH TRIGGER: SERVICE ERRORS ---
processes = data.get("processes", {}) processes = data.get("processes", {})
for svc_name, svc_status in processes.items(): for svc_name, svc_status in processes.items():
status_key = f"{cid}_{svc_name}" status_key = f"{cid}_{svc_name}"
s_lower = svc_status.lower() s_lower = svc_status.lower()
if s_lower in ["error", "stopped", "failed"]: if s_lower in ["error", "stopped", "failed"]:
if last_notified_errors.get(status_key) != s_lower: if last_notified_errors.get(status_key) != s_lower:
msg_err = f"Servizio {svc_name} KO ({svc_status})" msg_err = f"Service {svc_name} KO ({svc_status})"
if s_lower == "error": msg_err += " - Auto-healing fallito! ⚠️" if s_lower == "error": msg_err += " - Auto-healing failed! ⚠️"
broadcast_push_notification(f"🚨 ALARM: {cid.upper()}", msg_err) broadcast_push_notification(f"🚨 ALARM: {cid.upper()}", msg_err)
last_notified_errors[status_key] = s_lower last_notified_errors[status_key] = s_lower
elif s_lower == "online" and status_key in last_notified_errors: elif s_lower == "online" and status_key in last_notified_errors:
broadcast_push_notification(f"✅ RESTORED: {cid.upper()}", f"Service{svc_name} back ONLINE.") broadcast_push_notification(f"✅ RESTORED: {cid.upper()}", f"Service {svc_name} back ONLINE.")
del last_notified_errors[status_key] del last_notified_errors[status_key]
# ----------------------------------------- # -----------------------------------------
except Exception as e: except Exception as e:
logger.error(f"Errore parsing health: {e}") logger.error(f"Error parsing health data: {e}")
# --- GESTIONE DMR GATEWAY --- # --- DMR GATEWAY MANAGEMENT ---
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')): 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: try:
cid = parts[1].lower() cid = parts[1].lower()
@@ -256,9 +256,9 @@ def on_message(client, userdata, msg):
socketio.emit('dati_aggiornati') # <--- WEBSOCKET socketio.emit('dati_aggiornati') # <--- WEBSOCKET
except Exception as e: except Exception as e:
logger.error(f"Errore parsing DMRGateway per {cid}: {e}") logger.error(f"Error parsing DMRGateway for {cid}: {e}")
# --- GESTIONE ALTRI GATEWAY --- # --- OTHER GATEWAYS MANAGEMENT ---
elif parts[0] in ['dmr-gateway', 'nxdn-gateway', 'ysf-gateway', 'p25-gateway', 'dstar-gateway']: elif parts[0] in ['dmr-gateway', 'nxdn-gateway', 'ysf-gateway', 'p25-gateway', 'dstar-gateway']:
data = json.loads(payload) data = json.loads(payload)
proto = "DMR" proto = "DMR"
@@ -280,12 +280,12 @@ def on_message(client, userdata, msg):
if m: save_to_sqlite(cid, {'source_id': "🌐 " + m, 'destination_id': 'NET'}, protocol=proto) if m: save_to_sqlite(cid, {'source_id': "🌐 " + m, 'destination_id': 'NET'}, protocol=proto)
# --- GESTIONE MMDVM E TRAFFICO --- # --- MMDVM AND TRAFFIC MANAGEMENT ---
elif parts[0] == 'mmdvm': elif parts[0] == 'mmdvm':
data = json.loads(payload) data = json.loads(payload)
if cid not in active_calls: active_calls[cid] = {} if cid not in active_calls: active_calls[cid] = {}
if cid not in client_telemetry or not isinstance(client_telemetry.get(cid), dict): if cid not in client_telemetry or not isinstance(client_telemetry.get(cid), dict):
client_telemetry[cid] = {"ts1": "In attesa...", "ts2": "In attesa...", "alt": "", "idle": True} client_telemetry[cid] = {"ts1": "Waiting...", "ts2": "Waiting...", "alt": "", "idle": True}
if 'MMDVM' in data and data['MMDVM'].get('mode') == 'idle': if 'MMDVM' in data and data['MMDVM'].get('mode') == 'idle':
client_telemetry[cid]["idle"] = True client_telemetry[cid]["idle"] = True
@@ -344,9 +344,9 @@ def on_message(client, userdata, msg):
save_cache(client_telemetry) save_cache(client_telemetry)
if k in active_calls[cid]: del active_calls[cid][k] if k in active_calls[cid]: del active_calls[cid][k]
except Exception as e: except Exception as e:
logger.error(f"ERRORE MQTT MSG: {e}") logger.error(f"MQTT MSG ERROR: {e}")
# --- INIZIALIZZAZIONE CLIENT MQTT --- # --- MQTT CLIENT INITIALIZATION ---
mqtt_backend = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2, "flask_backend") mqtt_backend = mqtt_client.Client(mqtt_client.CallbackAPIVersion.VERSION2, "flask_backend")
mqtt_backend.username_pw_set(config['mqtt']['user'], config['mqtt']['password']) mqtt_backend.username_pw_set(config['mqtt']['user'], config['mqtt']['password'])
mqtt_backend.on_connect = on_connect mqtt_backend.on_connect = on_connect
@@ -384,7 +384,7 @@ def get_states():
@app.route('/api/service_control', methods=['POST']) @app.route('/api/service_control', methods=['POST'])
def service_control(): def service_control():
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
d = request.json d = request.json
cid = d.get('clientId').lower() cid = d.get('clientId').lower()
action = d.get('action') action = d.get('action')
@@ -418,7 +418,7 @@ def login():
@app.route('/api/command', methods=['POST']) @app.route('/api/command', methods=['POST'])
def cmd(): def cmd():
if not session.get('logged_in'): return jsonify({"success": False, "error": "Non autenticato"}), 403 if not session.get('logged_in'): return jsonify({"success": False, "error": "Not authenticated"}), 403
d = request.json d = request.json
cid = d['clientId'].lower() cid = d['clientId'].lower()
cmd_type = d['type'] cmd_type = d['type']
@@ -427,7 +427,7 @@ def cmd():
allowed = session.get('allowed_nodes', '') allowed = session.get('allowed_nodes', '')
is_allowed = (role == 'admin' or allowed == 'all' or cid in [x.strip() for x in allowed.split(',')]) is_allowed = (role == 'admin' or allowed == 'all' or cid in [x.strip() for x in allowed.split(',')])
if cmd_type == 'REBOOT' and role != 'admin': if cmd_type == 'REBOOT' and role != 'admin':
return jsonify({"success": False, "error": "Solo gli Admin possono riavviare."}), 403 return jsonify({"success": False, "error": "Only Admins can reboot."}), 403
if is_allowed: if is_allowed:
mqtt_backend.publish(f"servizi/{cid}/cmnd", cmd_type) mqtt_backend.publish(f"servizi/{cid}/cmnd", cmd_type)
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
@@ -436,20 +436,19 @@ def cmd():
(username, cid, cmd_type)) (username, cid, cmd_type))
conn.commit() conn.commit()
conn.close() conn.close()
client_telemetry[cid] = {"ts1": "🔄 Inviato...", "ts2": "🔄 Inviato...", "alt": ""} client_telemetry[cid] = {"ts1": "🔄 Sent...", "ts2": "🔄 Sent...", "alt": ""}
return jsonify({"success": True}) return jsonify({"success": True})
return jsonify({"success": False, "error": "Non hai i permessi per questo nodo."}), 403 return jsonify({"success": False, "error": "You do not have permission for this node."}), 403
# --- API PER IL PULSANTE DI AGGIORNAMENTO ---
@app.route('/api/update_nodes', methods=['POST']) @app.route('/api/update_nodes', methods=['POST'])
def update_nodes(): def update_nodes():
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
mqtt_backend.publish("devices/control/request", "update") mqtt_backend.publish("devices/control/request", "update")
return jsonify({"success": True}) return jsonify({"success": True})
@app.route('/api/users', methods=['GET']) @app.route('/api/users', methods=['GET'])
def get_users(): def get_users():
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
c = conn.cursor() c = conn.cursor()
@@ -460,14 +459,14 @@ def get_users():
@app.route('/api/users', methods=['POST']) @app.route('/api/users', methods=['POST'])
def add_user(): def add_user():
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
d = request.json d = request.json
username = d.get('username') username = d.get('username')
password = d.get('password') password = d.get('password')
role = d.get('role', 'operator') role = d.get('role', 'operator')
allowed = d.get('allowed_nodes', '') allowed = d.get('allowed_nodes', '')
if not username or not password: if not username or not password:
return jsonify({"success": False, "error": "Dati mancanti"}) return jsonify({"success": False, "error": "Missing data"})
h = generate_password_hash(password) h = generate_password_hash(password)
try: try:
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
@@ -478,18 +477,18 @@ def add_user():
conn.close() conn.close()
return jsonify({"success": True}) return jsonify({"success": True})
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
return jsonify({"success": False, "error": "Username già esistente"}) return jsonify({"success": False, "error": "Username already exists"})
@app.route('/api/users/<int:user_id>', methods=['DELETE']) @app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id): def delete_user(user_id):
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
c = conn.cursor() c = conn.cursor()
c.execute("SELECT username FROM users WHERE id = ?", (user_id,)) c.execute("SELECT username FROM users WHERE id = ?", (user_id,))
u = c.fetchone() u = c.fetchone()
if u and u[0] == session.get('user'): if u and u[0] == session.get('user'):
conn.close() conn.close()
return jsonify({"success": False, "error": "Non puoi cancellare te stesso!"}) return jsonify({"success": False, "error": "You cannot delete yourself!"})
c.execute("DELETE FROM users WHERE id = ?", (user_id,)) c.execute("DELETE FROM users WHERE id = ?", (user_id,))
conn.commit() conn.commit()
conn.close() conn.close()
@@ -498,7 +497,7 @@ def delete_user(user_id):
@app.route('/api/users/<int:user_id>', methods=['PUT']) @app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id): def update_user(user_id):
if session.get('role') != 'admin': if session.get('role') != 'admin':
return jsonify({"error": "Non autorizzato"}), 403 return jsonify({"error": "Unauthorized"}), 403
data = request.json data = request.json
role = data.get('role', 'operator') role = data.get('role', 'operator')
@@ -526,14 +525,14 @@ def update_user(user_id):
@app.route('/api/change_password', methods=['POST']) @app.route('/api/change_password', methods=['POST'])
def change_password(): def change_password():
if not session.get('logged_in'): if not session.get('logged_in'):
return jsonify({"success": False, "error": "Non autenticato"}), 403 return jsonify({"success": False, "error": "Not authenticated"}), 403
d = request.json d = request.json
new_pass = d.get('new_password') new_pass = d.get('new_password')
user_to_change = d.get('username') user_to_change = d.get('username')
if session.get('role') != 'admin' and session.get('user') != user_to_change: if session.get('role') != 'admin' and session.get('user') != user_to_change:
return jsonify({"success": False, "error": "Non autorizzato"}), 403 return jsonify({"success": False, "error": "Unauthorized"}), 403
if not new_pass: if not new_pass:
return jsonify({"success": False, "error": "La password non può essere vuota"}), 400 return jsonify({"success": False, "error": "Password cannot be empty"}), 400
h = generate_password_hash(new_pass) h = generate_password_hash(new_pass)
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
c = conn.cursor() c = conn.cursor()
@@ -545,7 +544,7 @@ def change_password():
@app.route('/api/global_command', methods=['POST']) @app.route('/api/global_command', methods=['POST'])
def global_cmd(): def global_cmd():
if session.get('role') != 'admin': if session.get('role') != 'admin':
return jsonify({"success": False, "error": "Azione riservata all'Admin!"}), 403 return jsonify({"success": False, "error": "Admin action only!"}), 403
d = request.json d = request.json
cmd_type = d.get('type') cmd_type = d.get('type')
clients_list = [] clients_list = []
@@ -575,14 +574,14 @@ def auto_update_ids():
}) })
now = time.strftime("%H:%M") now = time.strftime("%H:%M")
if now == target_time: if now == target_time:
logger.info(f">>> [AUTO-UPDATE] Orario raggiunto ({now}). Download in corso...") logger.info(f">>> [AUTO-UPDATE] Scheduled time reached ({now}). Downloading...")
urllib.request.urlretrieve(urls["dmr"], DMR_IDS_PATH) urllib.request.urlretrieve(urls["dmr"], DMR_IDS_PATH)
urllib.request.urlretrieve(urls["nxdn"], NXDN_IDS_PATH) urllib.request.urlretrieve(urls["nxdn"], NXDN_IDS_PATH)
load_ids() load_ids()
logger.info(f">>> [AUTO-UPDATE] Completato con successo.") logger.info(f">>> [AUTO-UPDATE] Completed successfully.")
time.sleep(65) time.sleep(65)
except Exception as e: except Exception as e:
logger.error(f">>> [AUTO-UPDATE] Errore: {e}") logger.error(f">>> [AUTO-UPDATE] Error: {e}")
time.sleep(30) time.sleep(30)
@app.route('/api/ui_config', methods=['GET']) @app.route('/api/ui_config', methods=['GET'])
@@ -591,9 +590,9 @@ def get_ui_config():
with open(CONFIG_PATH, 'r') as f: with open(CONFIG_PATH, 'r') as f:
cfg = json.load(f) cfg = json.load(f)
ui_cfg = cfg.get("ui", { ui_cfg = cfg.get("ui", {
"profileA_Name": "PROFILO A", "profileA_Name": "PROFILE A",
"profileA_Color": "#3498db", "profileA_Color": "#3498db",
"profileB_Name": "PROFILO B", "profileB_Name": "PROFILE B",
"profileB_Color": "#9b59b6" "profileB_Color": "#9b59b6"
}) })
return jsonify(ui_cfg) return jsonify(ui_cfg)
@@ -602,7 +601,7 @@ def get_ui_config():
@app.route('/api/config', methods=['GET']) @app.route('/api/config', methods=['GET'])
def get_config_api(): def get_config_api():
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
with open(CONFIG_PATH, 'r') as f: with open(CONFIG_PATH, 'r') as f:
cfg = json.load(f) cfg = json.load(f)
return jsonify({ return jsonify({
@@ -613,7 +612,7 @@ def get_config_api():
@app.route('/api/config', methods=['POST']) @app.route('/api/config', methods=['POST'])
def save_config_api(): def save_config_api():
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
new_data = request.json new_data = request.json
with open(CONFIG_PATH, 'r') as f: with open(CONFIG_PATH, 'r') as f:
cfg = json.load(f) cfg = json.load(f)
@@ -627,18 +626,18 @@ def save_config_api():
@app.route('/api/config_file/<cid>/<service>', methods=['GET']) @app.route('/api/config_file/<cid>/<service>', methods=['GET'])
def get_config_file(cid, service): def get_config_file(cid, service):
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
cid = cid.lower() cid = cid.lower()
service = service.lower() service = service.lower()
config_data = device_configs.get(cid, {}).get(service) config_data = device_configs.get(cid, {}).get(service)
if not config_data: if not config_data:
return jsonify({"error": "Configurazione non ancora ricevuta. Attendi o invia un comando UPDATE."}), 404 return jsonify({"error": "Configuration not received yet. Wait or send an UPDATE command."}), 404
return jsonify({"success": True, "data": config_data}) return jsonify({"success": True, "data": config_data})
@app.route('/api/config_file', methods=['POST']) @app.route('/api/config_file', methods=['POST'])
def save_config_file(): def save_config_file():
if session.get('role') != 'admin': return jsonify({"error": "Non autorizzato"}), 403 if session.get('role') != 'admin': return jsonify({"error": "Unauthorized"}), 403
d = request.json d = request.json
cid = d.get('clientId').lower() cid = d.get('clientId').lower()
service = d.get('service').lower() service = d.get('service').lower()
@@ -687,7 +686,7 @@ def broadcast_push_notification(title, body):
c.execute("DELETE FROM push_subscriptions WHERE id = ?", (sub_id,)) c.execute("DELETE FROM push_subscriptions WHERE id = ?", (sub_id,))
conn.commit() conn.commit()
except Exception as e: except Exception as e:
logger.error(f"Errore generico Push: {e}") logger.error(f"Generic Push Error: {e}")
conn.close() conn.close()
@app.route('/api/vapid_public_key') @app.route('/api/vapid_public_key')