25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

185 satır
7.3 KiB

  1. import streamlit as st
  2. import yaml
  3. import subprocess
  4. import os
  5. import web_status # Nuovo modulo
  6. # --- COSTANTI E UTILS (Invariati) ---
  7. CONFIG_PATH = os.environ.get("CONFIG") or os.environ.get("CONFIG_FILE") or "/config/config.yaml"
  8. SECRETS_PATH = os.environ.get("SECRETS_FILE") or "/config/secrets.yaml"
  9. LOG_FILE = "/tmp/main_process.log"
  10. STATE_FILE = "/data/.web_state"
  11. def load_yaml(path):
  12. if not os.path.exists(path): return {}
  13. with open(path, 'r') as f: return yaml.safe_load(f) or {}
  14. def save_yaml(path, data):
  15. with open(path, 'w') as f: yaml.dump(data, f, default_flow_style=False)
  16. cfg = load_yaml(CONFIG_PATH)
  17. # --- CONFIGURAZIONE PAGINA E MENU ABOUT ---
  18. st.set_page_config(
  19. page_title="BLE Localizer Manager",
  20. layout="wide",
  21. initial_sidebar_state="auto",
  22. menu_items={
  23. 'Get Help': None,
  24. 'Report a bug': None,
  25. 'About': "🛰️BLE AI Localizer - Suite\nSistema professionale di posizionamento BLE."
  26. }
  27. )
  28. st.title("🛰️ BLE AI Localizer - Suite") # Forza il titolo qui
  29. # --- CSS OTTIMIZZATO (Sidebar Fina + Mobile) ---
  30. st.markdown("""
  31. <style>
  32. [data-testid="stSidebar"] { min-width: 200px !important; max-width: 230px !important; }
  33. [data-testid="stSidebar"] .stButton > button { width: 100%; height: 2.5rem; border-radius: 8px; font-size: 0.9rem !important; }
  34. .stTextInput input, .stSelectbox div, .stNumberInput input { height: 3rem !important; }
  35. button[data-baseweb="tab"] { height: 3.5rem !important; }
  36. .stButton > button { border-radius: 10px; font-weight: bold; }
  37. </style>
  38. """, unsafe_allow_html=True)
  39. if "main_login_user" not in st.session_state:
  40. st.session_state["main_login_user"] = ""
  41. # --- LOGIN (Tua versione originale) ---
  42. if "password_correct" not in st.session_state:
  43. st.session_state["password_correct"] = False
  44. if not st.session_state["password_correct"]:
  45. user = st.text_input("Username", key="main_login_user")
  46. pw = st.text_input("Password", type="password", key="main_login_pw")
  47. if st.button("ACCEDI"):
  48. if user == os.environ.get("UI_USER", "admin") and pw == os.environ.get("UI_PASSWORD", "password"):
  49. st.session_state["password_correct"] = True
  50. st.rerun()
  51. else: st.error("Credenziali errate")
  52. st.stop()
  53. # Import moduli dopo login
  54. from map_manager import show_mapper
  55. from web_gateway import show_gateway_manager
  56. from web_beacon import show_beacon_manager
  57. import web_training_data
  58. import web_inference
  59. def is_proc_alive():
  60. return st.session_state.get('proc') is not None and st.session_state.proc.poll() is None
  61. # Auto-riavvio (Tua logica)
  62. if os.path.exists(STATE_FILE):
  63. with open(STATE_FILE, "r") as f:
  64. if f.read().strip() == "running" and not is_proc_alive():
  65. log_out = open(LOG_FILE, "a")
  66. st.session_state.proc = subprocess.Popen(
  67. ["python", "-u", "-m", "app.main"],
  68. env={**os.environ, "PYTHONUNBUFFERED": "1", "CONFIG": CONFIG_PATH},
  69. stdout=log_out, stderr=subprocess.STDOUT, text=True
  70. )
  71. # --- SIDEBAR ---
  72. with st.sidebar:
  73. st.header("Stato Sistema")
  74. alive = is_proc_alive()
  75. if not alive:
  76. if st.button("▶️ AVVIA MAIN"):
  77. if os.path.exists(LOG_FILE): os.remove(LOG_FILE)
  78. st.session_state.proc = subprocess.Popen(
  79. ["python", "-u", "-m", "app.main"],
  80. env={**os.environ, "PYTHONUNBUFFERED": "1", "CONFIG": CONFIG_PATH},
  81. stdout=open(LOG_FILE, "w"), stderr=subprocess.STDOUT, text=True
  82. )
  83. with open(STATE_FILE, "w") as f: f.write("running")
  84. st.rerun()
  85. else:
  86. if st.button("⏹️ FERMA MAIN"):
  87. st.session_state.proc.terminate()
  88. st.session_state.proc = None
  89. with open(STATE_FILE, "w") as f: f.write("stopped")
  90. st.rerun()
  91. st.write(f"Stato: {':green[Running]' if alive else ':red[Stopped]'}")
  92. st.write(f"Modo: **{cfg.get('mode', 'N/D').upper()}**")
  93. if st.button("LOGOUT"):
  94. st.session_state["password_correct"] = False
  95. st.rerun()
  96. st.divider()
  97. st.caption("🚦 LIVE STATUS")
  98. c1, c2, c3 = st.columns(3)
  99. c1.markdown("`MQTT` 🟢")
  100. c2.markdown("`GWs` 🟢")
  101. c3.markdown("`API` 🟢")
  102. # --- TABS (Incluso il nuovo tab Stato) ---
  103. tab_log, tab_cfg, tab_sec, tab_gw, tab_beac, tab_map, tab_data, tab_infer, tab_status = st.tabs([
  104. "📜 Log", "⚙️ Config", "🔑 Secrets", "🌐 Gateway", "🏷️ Beacon", "🗺️ Mappa", "📂 Dati", "🤖 Inferenza", "🖥️ Stato"
  105. ])
  106. with tab_status:
  107. import web_status
  108. # Carica i segreti e passali alla funzione
  109. sec_data = load_yaml(SECRETS_PATH)
  110. web_status.show_system_status(cfg, sec_data)
  111. with tab_cfg:
  112. # RIPRISTINATA TUTTA LA TUA LOGICA ORIGINALE
  113. c_cfg = load_yaml(CONFIG_PATH)
  114. st.subheader("🚀 Stato Operativo")
  115. mode_options = ["infer", "train", "collect_train"]
  116. new_mode = st.selectbox("Modalità:", mode_options, index=mode_options.index(c_cfg.get('mode', 'infer')))
  117. with st.expander("📊 Parametri Raccolta Dati", expanded=True):
  118. p_cfg = c_cfg.get('collect_train', {})
  119. col_a, col_b = st.columns(2)
  120. win_sec = col_a.number_input("Finestra (sec)", value=int(p_cfg.get('window_seconds', 30)))
  121. min_gw = col_a.number_input("Min. Gateway", value=int(p_cfg.get('min_non_nan', 3)))
  122. rssi_min = col_b.slider("RSSI Min", -120, -50, int(p_cfg.get('rssi_min', -110)))
  123. rssi_max = col_b.slider("RSSI Max", -40, 0, int(p_cfg.get('rssi_max', -25)))
  124. outlier = col_b.selectbox("Outlier", ["mad", "iqr", "none"], index=0)
  125. with st.expander("📡 MQTT & 🌐 API", expanded=False):
  126. m_cfg = c_cfg.get('mqtt', {})
  127. mq_host = st.text_input("Broker", value=m_cfg.get('host', '127.0.0.1'))
  128. mq_port = st.number_input("Porta", value=int(m_cfg.get('port', 1883)))
  129. a_cfg = c_cfg.get('api', {})
  130. t_url = st.text_input("Token URL", value=a_cfg.get('token_url', ''))
  131. v_tls = st.checkbox("Verify TLS", value=a_cfg.get('verify_tls', False))
  132. with st.expander("🛠️ Expert Mode (YAML)", expanded=False):
  133. cfg_text = st.text_area("Edit manuale", yaml.dump(c_cfg), height=300)
  134. if st.button("💾 SALVA TUTTO"):
  135. try:
  136. final_cfg = yaml.safe_load(cfg_text)
  137. final_cfg['mode'] = new_mode
  138. # Aggiorna con i valori dei widget per sicurezza
  139. if 'collect_train' not in final_cfg: final_cfg['collect_train'] = {}
  140. final_cfg['collect_train'].update({'window_seconds': win_sec, 'rssi_min': rssi_min})
  141. save_yaml(CONFIG_PATH, final_cfg)
  142. st.success("Salvato!")
  143. except Exception as e: st.error(e)
  144. with tab_sec:
  145. sec = load_yaml(SECRETS_PATH)
  146. sec_edit = st.text_area("Secrets YAML", yaml.dump(sec), height=200)
  147. if st.button("SALVA SECRETS"):
  148. save_yaml(SECRETS_PATH, yaml.safe_load(sec_edit))
  149. st.success("ApiToken salvati!")
  150. with tab_log:
  151. if os.path.exists(LOG_FILE):
  152. with open(LOG_FILE, "r") as f: st.code("".join(f.readlines()[-50:]))
  153. if st.button("🔄 AGGIORNA LOG"): st.rerun()
  154. with tab_map: show_mapper(cfg)
  155. with tab_gw: show_gateway_manager(load_yaml(CONFIG_PATH))
  156. with tab_beac: show_beacon_manager(load_yaml(CONFIG_PATH))
  157. with tab_data: web_training_data.show_training_data_manager(cfg)
  158. with tab_infer: web_inference.show_inference_page(cfg)