Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

194 righe
7.1 KiB

  1. import streamlit as st
  2. import yaml
  3. import subprocess
  4. import os
  5. import time
  6. import signal
  7. import web_status
  8. import psutil
  9. from pathlib import Path
  10. # --- 1. CONFIGURAZIONE PAGINA ---
  11. st.set_page_config(
  12. page_title="BLE Localizer Manager",
  13. layout="wide",
  14. initial_sidebar_state="auto"
  15. )
  16. # --- 2. CONFIGURAZIONE PERCORSI E STATI PERSISTENTI ---
  17. REAL_CONFIG_PATH = "/config/config.yaml" if os.path.exists("/config/config.yaml") else "/app/config/config.yaml"
  18. LOG_FILE = "/tmp/main_process.log"
  19. # File di stato su disco per la persistenza
  20. CORE_STATE_FILE = "/data/config/core.enabled"
  21. LOG_PID_FILE = "/tmp/mqtt_logging.pid"
  22. def load_yaml(path):
  23. if not os.path.exists(path): return {}
  24. with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) or {}
  25. def save_yaml(path, data):
  26. with open(path, 'w', encoding='utf-8') as f: yaml.dump(data, f, default_flow_style=False)
  27. cfg = load_yaml(REAL_CONFIG_PATH)
  28. # --- 3. LOGICA PERSISTENZA CORE ---
  29. def stop_core_engine():
  30. """Arresta il core e rimuove il flag di persistenza."""
  31. if os.path.exists(CORE_STATE_FILE):
  32. os.remove(CORE_STATE_FILE)
  33. for proc in psutil.process_iter(['cmdline']):
  34. try:
  35. cmd = proc.info['cmdline']
  36. if cmd and ('app.main' in ' '.join(cmd) or 'main.py' in ' '.join(cmd)):
  37. proc.terminate()
  38. proc.wait(timeout=2)
  39. except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.TimeoutExpired):
  40. try: proc.kill()
  41. except: pass
  42. def start_core_engine():
  43. """Avvia il core e crea il flag di persistenza su disco."""
  44. Path(CORE_STATE_FILE).touch()
  45. env = os.environ.copy()
  46. env["CONFIG"] = REAL_CONFIG_PATH
  47. env["PYTHONPATH"] = "/app"
  48. # Reindirizzamento append log per non perdere lo storico dell'entrypoint
  49. with open(LOG_FILE, "a") as log_f:
  50. subprocess.Popen(
  51. ["python3", "-m", "app.main"],
  52. env=env,
  53. cwd="/app",
  54. stdout=log_f,
  55. stderr=log_f,
  56. start_new_session=True
  57. )
  58. # --- 4. MONITORAGGIO ATTIVO (SENZA AUTO-START BLOCCANTE) ---
  59. # La logica di avvio al boot è ora gestita da entrypoint.sh.
  60. # Qui segnaliamo solo se lo stato desiderato (file esistente) non coincide con quello reale.
  61. if os.path.exists(CORE_STATE_FILE) and not web_status.is_main_running():
  62. st.sidebar.warning("⚠️ Core abilitato ma non in esecuzione.")
  63. if st.sidebar.button("RIPRISTINA ORA"):
  64. start_core_engine()
  65. st.rerun()
  66. # --- 5. TITOLO E LOGIN ---
  67. st.title("🛰️ BLE AI Localizer - Suite")
  68. if "password_correct" not in st.session_state:
  69. st.session_state["password_correct"] = False
  70. if not st.session_state["password_correct"]:
  71. user = st.text_input("Username")
  72. pw = st.text_input("Password", type="password")
  73. if st.button("ACCEDI"):
  74. if user == os.environ.get("UI_USER", "admin") and pw == os.environ.get("UI_PASSWORD", "password"):
  75. st.session_state["password_correct"] = True
  76. st.rerun()
  77. else:
  78. st.error("Credenziali errate")
  79. st.stop()
  80. # --- 6. SIDEBAR: CONTROLLI SISTEMA ---
  81. with st.sidebar:
  82. st.header("🛠️ Core Control")
  83. is_running = web_status.is_main_running()
  84. st.markdown(f"STATO: {':green[**CORE ATTIVO**]' if is_running else ':red[**CORE FERMO**]'}")
  85. if st.button("🚀 RIAVVIA CORE" if is_running else "▶️ AVVIA CORE", use_container_width=True):
  86. stop_core_engine()
  87. time.sleep(1)
  88. start_core_engine()
  89. st.rerun()
  90. st.divider()
  91. # --- GESTIONE MQTT RAW PERSISTENTE ---
  92. is_logging_active = False
  93. if os.path.exists(LOG_PID_FILE):
  94. try:
  95. with open(LOG_PID_FILE, "r") as f:
  96. saved_pid = int(f.read().strip())
  97. if psutil.pid_exists(saved_pid):
  98. is_logging_active = True
  99. else:
  100. os.remove(LOG_PID_FILE)
  101. except:
  102. pass
  103. raw_active = st.toggle("🔴 MQTT RAW RECORDING", value=is_logging_active)
  104. if raw_active and not is_logging_active:
  105. RAW_LOG_DIR = cfg.get('paths', {}).get('mqtt_raw_dir', '/data/mqtt_raw/')
  106. os.makedirs(RAW_LOG_DIR, exist_ok=True)
  107. dt = time.strftime("%Y%m%d_%H%M%S")
  108. filepath = os.path.join(RAW_LOG_DIR, f"mqtt_raw_{dt}.log")
  109. m_cfg = cfg.get('mqtt', {})
  110. m_filter = m_cfg.get('topic', 'publish_out').split('/')[0]
  111. cmd = f"stdbuf -oL mosquitto_sub -v -h {m_cfg.get('host')} -p {m_cfg.get('port')} -t '#' | stdbuf -oL grep '{m_filter}' > {filepath}"
  112. proc = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid)
  113. with open(LOG_PID_FILE, "w") as f:
  114. f.write(str(proc.pid))
  115. st.rerun()
  116. elif not raw_active and is_logging_active:
  117. try:
  118. with open(LOG_PID_FILE, "r") as f:
  119. pid_to_kill = int(f.read().strip())
  120. os.killpg(os.getpgid(pid_to_kill), signal.SIGTERM)
  121. if os.path.exists(LOG_PID_FILE): os.remove(LOG_PID_FILE)
  122. st.rerun()
  123. except:
  124. pass
  125. # --- 7. IMPORT MODULI E TABS ---
  126. from map_manager import show_mapper
  127. from web_gateway import show_gateway_manager
  128. from web_beacon import show_beacon_manager
  129. from web_test_inference import show_test_inference
  130. import web_training_data
  131. import web_inference
  132. tab_log, tab_cfg, tab_sec, tab_gw, tab_beac, tab_map, tab_model, tab_infer_test, tab_infer, tab_status = st.tabs([
  133. "📜 Log", "⚙️ Config", "🔑 Secrets", "🌐 Gateway", "🏷️ Beacon", "📍 Rilevamento", "🧠 Modello", "🧪 InferTest", "🤖 Inferenza", "🖥️ Stato"
  134. ])
  135. with tab_log:
  136. col_l1, col_l2 = st.columns(2)
  137. if col_l1.button("🔄 AGGIORNA LOG", use_container_width=True): st.rerun()
  138. if col_l2.button("🗑️ AZZERA LOG", use_container_width=True):
  139. try:
  140. with open(LOG_FILE, "w") as f: f.write(f"--- Log resettato il {time.strftime('%Y-%m-%d %H:%M:%S')} ---\n")
  141. st.success("Log azzerato!"); time.sleep(1); st.rerun()
  142. except: st.error("Errore pulizia log")
  143. if os.path.exists(LOG_FILE):
  144. with open(LOG_FILE, "r") as f: st.code("".join(f.readlines()[-100:]))
  145. with tab_cfg:
  146. st.subheader("🚀 Configurazione Operativa")
  147. cfg_text = st.text_area("Expert Mode (YAML)", yaml.dump(cfg), height=400)
  148. if st.button("💾 SALVA CONFIG"):
  149. save_yaml(REAL_CONFIG_PATH, yaml.safe_load(cfg_text)); st.success("Salvato!")
  150. with tab_sec:
  151. CURR_SECRETS = os.environ.get("SECRETS_FILE") or "/config/secrets.yaml"
  152. sec = load_yaml(CURR_SECRETS)
  153. sec_edit = st.text_area("Secrets YAML", yaml.dump(sec), height=200)
  154. if st.button("SALVA SECRETS"):
  155. save_yaml(CURR_SECRETS, yaml.safe_load(sec_edit)); st.success("OK!")
  156. with tab_gw: show_gateway_manager(cfg)
  157. with tab_beac: show_beacon_manager(cfg)
  158. with tab_map: show_mapper(cfg)
  159. with tab_model: web_training_data.show_training_data_manager(cfg)
  160. with tab_infer_test: show_test_inference(cfg)
  161. with tab_infer: web_inference.show_inference_page(cfg)
  162. with tab_status:
  163. sec_data = load_yaml(os.environ.get("SECRETS_FILE") or "/config/secrets.yaml")
  164. web_status.show_system_status(cfg, sec_data)