# -*- coding: utf-8 -*- import streamlit as st import mysql.connector import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates import os from dotenv import load_dotenv from datetime import datetime, date import bcrypt import traceback import random # Charger les variables d'environnement load_dotenv() st.set_page_config(page_title="Domo91 - Surveillance", layout="wide") st.title("📊 Domo91 - Surveillance des sondes") st.write("Bienvenue sur l’application de supervision.") # Initialisation session state avec valeurs sûres for key, default in { "authenticated": False, "role": None, "site_autorise": None, "onglet_actif": "Accueil", "selected_date": date.today(), "selected_site": "Saclay", "selected_periode": "Toute la journée", }.items(): st.session_state.setdefault(key, default) # Configuration MySQL db_config = { "host": os.getenv("DB_HOST"), "user": os.getenv("DB_USER"), "password": os.getenv("DB_PASSWORD"), "database": os.getenv("DB_NAME") } def get_connection(): return mysql.connector.connect(**db_config) def hash_password(plain_password): return bcrypt.hashpw(plain_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') def verifier_password(input_password, hash_en_base): return bcrypt.checkpw(input_password.encode('utf-8'), hash_en_base.encode('utf-8')) # --- Connexion utilisateur --- if not st.session_state.get("authenticated", False): login = st.sidebar.text_input("Nom d'utilisateur") password = st.sidebar.text_input("Mot de passe", type="password") if st.sidebar.button("Se connecter"): try: conn = get_connection() cursor = conn.cursor(dictionary=True) # On interroge la bonne table cursor.execute(""" SELECT NomUtilisateur, MotDePasseHash, role, Site, DateExpiration FROM Acces.Utilisateurs WHERE NomUtilisateur = %s LIMIT 1 """, (login,)) result = cursor.fetchone() if not result: st.sidebar.error("Identifiants invalides") elif result["DateExpiration"] and result["DateExpiration"] < date.today(): st.sidebar.error("⛔ Accès expiré.") elif not verifier_password(password, result["MotDePasseHash"]): st.sidebar.error("Identifiants invalides") else: # Authentification réussie st.session_state.update({ "authenticated": True, "role": result["role"], "site_autorise": result["Site"] }) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") cursor.execute(""" INSERT INTO Sondes.Connexion_Log (Utilisateur, Lieu, Date_Connexion) VALUES (%s, %s, %s) """, (result["NomUtilisateur"], result["Site"], now_str)) conn.commit() st.rerun() cursor.close() conn.close() except Exception as e: st.sidebar.error(f"Erreur connexion : {e}") else: st.sidebar.success(f"Connecté ({st.session_state.get('role')})") if st.sidebar.button("🔓 Déconnexion"): for key in ["authenticated", "role", "site_autorise"]: st.session_state[key] = False if key == "authenticated" else None st.rerun() # --- Bandeau Alertes --- try: conn = get_connection() cursor = conn.cursor(dictionary=True) # Récupérer le site autorisé depuis la session site = st.session_state.get("lieu_autorise") if site: # Lecture des alertes non acquittées pour ce site cursor.execute(f""" SELECT Id, Sonde, Debut_defaut, Etat FROM Alertes_{site} WHERE Etat != 'Acquitté' ORDER BY Debut_defaut DESC """) alertes = cursor.fetchall() if alertes: st.markdown( f"
" f"🚨 {len(alertes)} alerte(s) non résolue(s) sur {site}" f"
", unsafe_allow_html=True ) else: st.markdown( f"
" f"✅ Aucune alerte en cours sur {site}" f"
", unsafe_allow_html=True ) cursor.close() conn.close() except Exception as e: st.error(f"Erreur lors de la récupération des alertes : {e}") # --- Navigation --- if st.session_state["authenticated"]: onglets = ["Accueil", "Entretien"] if st.session_state["role"] != "superviseur" else ["Accueil", "Statistiques", "Entretien"] onglet_selectionne = st.sidebar.radio("📁 Navigation", onglets, index=onglets.index(st.session_state["onglet_actif"])) st.session_state["onglet_actif"] = onglet_selectionne site_actuel = st.session_state.get("site_autorise") if st.session_state[ "role"] != "superviseur" else st.session_state.get( "selected_site", "Saclay") date_selectionnee = st.session_state.get("selected_date", date.today()) periode_selectionnee = st.session_state.get("selected_periode", "Toute la journée") # --- Onglet Accueil --- if onglet_selectionne == "Accueil": try: conn = get_connection() cursor = conn.cursor(dictionary=True) if st.session_state["role"] == "superviseur": site_actuel = st.selectbox("📍 Choisissez un site :", ["Saclay", "Meudon"], index=0) st.session_state["selected_site"] = site_actuel else: st.info(f"Site imposé : {site_actuel}") date_selectionnee = st.date_input("📅 Date du relevé", value=date_selectionnee) st.session_state["selected_date"] = date_selectionnee cursor.execute(f"SELECT * FROM `{site_actuel}` WHERE DATE(Date) = %s ORDER BY Sonde, Date DESC", (date_selectionnee.strftime("%Y-%m-%d"),)) rows = cursor.fetchall() if rows: df = pd.DataFrame(rows) df["Date"] = pd.to_datetime(df["Date"]) sondes = sorted(df["Sonde"].unique()) sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes) df_sonde = df[df["Sonde"] == sonde_choisie].copy() df_sonde["Heure"] = df_sonde["Date"].dt.hour tranche = st.radio("🕒 Tranche horaire :", ["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"]) st.session_state["selected_periode"] = tranche if tranche == "Matin (6h-12h)": df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)] elif tranche == "Après-midi (12h-18h)": df_sonde = df_sonde[(df_sonde["Heure"] >= 12) & (df_sonde["Heure"] < 18)] elif tranche == "Nuit (18h-6h)": df_sonde = df_sonde[(df_sonde["Heure"] >= 18) | (df_sonde["Heure"] < 6)] seuil_temp = 10 cursor.execute("SELECT Temp_Max FROM Chambres_froides WHERE Lieu = %s AND Sonde = %s", (site_actuel, sonde_choisie)) seuil = cursor.fetchone() if seuil: seuil_temp = seuil["Temp_Max"] st.subheader("📊 Tableau des relevés") def surlignage_temp(val): try: if float(val) > seuil_temp: return "color: red; font-weight: bold" except: pass return "" styled_df = df_sonde.style.applymap(surlignage_temp, subset=["Temperature"]) st.dataframe(styled_df, use_container_width=True) st.subheader("📈 Évolution de la température") fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(df_sonde["Date"], df_sonde["Temperature"], marker='o') ax.axhline(seuil_temp, color='red', linestyle='--', label=f"Seuil {seuil_temp}°C") ax.set_xlabel("Heure") ax.set_ylabel("Température (°C)") ax.set_title(f"{sonde_choisie} - {date_selectionnee.strftime('%d/%m/%Y')}") ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend() st.pyplot(fig) cursor.close() conn.close() except Exception as e: st.error(f"Erreur : {e}") st.text(traceback.format_exc()) # --- Onglet Statistiques --- elif onglet_selectionne == "Statistiques": st.markdown("## 📈 Statistiques de température") site = ( st.session_state["site_autorise"] if st.session_state["role"] != "superviseur" else st.session_state.get("selected_site", "Saclay") ) date_val = st.session_state.get("selected_date", date.today()) try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) site = ( st.session_state["site_autorise"] if st.session_state["role"] != "superviseur" else st.session_state.get("selected_site", "Saclay") ) date_val = st.session_state.get("selected_date", date.today()) cursor.execute( f"SELECT * FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", (date_val.strftime("%Y-%m-%d"),) ) rows = cursor.fetchall() df = pd.DataFrame(rows) if df.empty: st.info("Aucune donnée pour cette date.") else: df["Date"] = pd.to_datetime(df["Date"]) sondes = sorted(df["Sonde"].unique()) sonde = st.selectbox("Choisir une sonde :", sondes, key="selectbox_stats") df_sonde = df[df["Sonde"] == sonde] st.subheader("Évolution journalière") fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(df_sonde["Date"], df_sonde["Temperature"], marker='o') ax.set_title(f"{sonde} - {date_val.strftime('%d/%m/%Y')}") ax.set_xlabel("Heure") ax.set_ylabel("Température (°C)") ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) st.pyplot(fig) cursor.close() conn.close() except Exception as e: st.error(f"Erreur chargement statistiques : {e}") # Tableau consignes chambres froides if st.session_state["role"] == "superviseur": with st.expander("🛠️ Gestion des chambres froides (administrateur)", expanded=True): if st.button("🔄 Actualiser la liste"): st.session_state["refresh_admin"] = random.randint(0, 9999) try: conn_admin = mysql.connector.connect(**db_config) cursor_admin = conn_admin.cursor(dictionary=True) cursor_admin.execute("SELECT * FROM Chambres_froides WHERE Lieu = %s", (site,)) chambres = cursor_admin.fetchall() if not chambres: st.warning("Aucune chambre froide pour ce site.") else: for chambre in chambres: col1, col2, col3 = st.columns([3, 1, 2]) with col1: st.markdown(f"**{chambre['Sonde']}**") with col2: etat = st.checkbox("ON", value=(chambre["Etat"] == "ON"), key=f"etat_{chambre['Id']}_{st.session_state.get('refresh_admin', 0)}") new_etat = "ON" if etat else "OFF" with col3: temp_max = chambre["Temp_Max"] moins, temp_display, plus = st.columns([1, 2, 1]) with moins: if st.button("▼", key=f"moins_{chambre['Id']}"): temp_max -= 1 with temp_display: st.markdown(f"
{temp_max}°C
", unsafe_allow_html=True) with plus: if st.button("▲", key=f"plus_{chambre['Id']}"): temp_max += 1 if new_etat != chambre["Etat"] or temp_max != chambre["Temp_Max"]: cursor_admin.execute( "UPDATE Chambres_froides SET Etat = %s, Temp_Max = %s WHERE Id = %s", (new_etat, temp_max, chambre["Id"]) ) conn_admin.commit() st.success(f"{chambre['Sonde']} mise à jour") cursor_admin.close() conn_admin.close() except Exception as e: st.error(f"Erreur SQL (admin) : {e}") # --- Onglet Entretien --- elif onglet_selectionne == "Entretien": st.header("🧰 Gestion Entretien") try: conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute("SELECT Id, Sonde, En_entretien FROM Chambres_froides WHERE Lieu = %s", (site_actuel,)) sondes = cursor.fetchall() for sonde in sondes: checked = st.checkbox(f"{sonde['Sonde']}", value=sonde['En_entretien']) if checked != sonde['En_entretien']: cursor.execute("UPDATE Chambres_froides SET En_entretien = %s WHERE Id = %s", (checked, sonde['Id'])) conn.commit() st.success(f"{sonde['Sonde']} {'mise' if checked else 'retirée'} en entretien.") cursor.close() conn.close() except Exception as e: st.error(f"Erreur : {e}") st.text(traceback.format_exc()) # --- Onglet Traffic --- elif onglet_selectionne == "Traffic": st.header("🚦 Connexions récentes") try: conn = get_connection() cursor = conn.cursor(dictionary=True) cursor.execute( "SELECT Utilisateur, Lieu, Date_Connexion FROM Connexion_Log ORDER BY Date_Connexion DESC LIMIT 100") logs = cursor.fetchall() df_logs = pd.DataFrame(logs) st.dataframe(df_logs) cursor.close() conn.close() except Exception as e: st.error(f"Erreur : {e}") st.text(traceback.format_exc())