Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

194 wiersze
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)