makeup
This commit is contained in:
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user