# -*- coding: utf-8 -*- import streamlit as st import mysql.connector import pandas as pd from datetime import date import matplotlib.pyplot as plt import matplotlib.dates as mdates from fpdf import FPDF import os import random import datetime from dotenv import load_dotenv # Charger les variables d'environnement load_dotenv() st.set_page_config(page_title="Domo91 - Surveillance", layout="wide") if "authenticated" not in st.session_state: st.session_state["authenticated"] = False st.session_state["role"] = None st.session_state["lieu_autorise"] = None st.title("📡 Supervision Températures") db_config = { "host": os.getenv("DB_HOST"), "user": os.getenv("DB_USER"), "password": os.getenv("DB_PASSWORD"), "database": os.getenv("DB_NAME") } # --- Fonction de génération PDF --- def generer_pdf(site, date_str): st.info(f"Génération du rapport PDF pour {site} à la date {date_str}") try: conn = mysql.connector.connect(**db_config) pdf_cursor = conn.cursor(dictionary=True) cursor.execute(f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", (date_str,)) rows = cursor.fetchall() df = pd.DataFrame(rows) df["Heure"] = pd.to_datetime(df["Date"]).dt.strftime("%H:%M") releves = {} for sonde in df["Sonde"].unique(): df_sonde = df[df["Sonde"] == sonde] releves[sonde] = list(zip(df_sonde["Heure"], df_sonde["Temperature"])) table_alertes = f"Alertes_{site}" cursor.execute(f"SELECT Sonde, Debut_defaut, Status FROM {table_alertes} WHERE DATE(Debut_defaut) = %s", (date_str,)) alertes = cursor.fetchall() cursor.close() conn.close() class RapportPDF(FPDF): def header(self): self.set_font("Arial", "B", 14) self.cell(0, 10, "Rapport de surveillance des sondes", ln=1, align="C") self.set_font("Arial", "", 12) self.cell(0, 10, f"Date : {date_str}", ln=1, align="C") self.ln(5) def site_info(self, site_name): self.set_font("Arial", "B", 12) self.cell(0, 10, f"Site : {site_name}", ln=1) self.ln(2) def releves_section(self, data): self.set_font("Arial", "B", 12) self.cell(0, 10, "Relevés de température", ln=1) for sonde, mesures in data.items(): self.set_font("Arial", "B", 11) self.cell(0, 8, f"Sonde : {sonde}", ln=1) self.set_font("Arial", "", 10) for heure, temp in mesures: self.cell(0, 6, f"{heure} - {temp} °C", ln=1) self.ln(2) def alertes_section(self, data): self.set_font("Arial", "B", 12) self.cell(0, 10, "Alertes enregistrées", ln=1) self.set_font("Arial", "", 10) for a in data: self.cell(0, 6, f"{a['Sonde']} - {a['Debut_defaut']} - {a['Status']}", ln=1) pdf = RapportPDF() pdf.add_page() pdf.site_info(site) pdf.releves_section(releves) pdf.alertes_section(alertes) file_name = f"rapport_{site}_{date_str}.pdf" output_dir = "PDF" os.makedirs(output_dir, exist_ok=True) # 🔧 Crée le dossier si absent output_path = os.path.join(output_dir, file_name) pdf.output(output_path) with open(output_path, "rb") as f: st.download_button( label="📥 Télécharger le rapport PDF", data=f, file_name=file_name, mime="application/pdf" ) except Exception as err1: st.error(f"Erreur lors de la génération du PDF : {err1}") # --- Initialisation des variables de session --- if "authenticated" not in st.session_state: st.session_state["authenticated"] = False if "role" not in st.session_state: st.session_state["role"] = None if "lieu_autorise" not in st.session_state: st.session_state["lieu_autorise"] = None # --- Connexion utilisateur dans la sidebar --- st.sidebar.header("🔐 Connexion") if not st.session_state.get("authenticated"): 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 = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) cursor.execute("SELECT * FROM MotsDePasse WHERE utilisateur = %s", (login,)) result = cursor.fetchone() if result and result["mot_de_passe"] == password: st.session_state["authenticated"] = True st.session_state["role"] = result["role"] st.session_state["lieu_autorise"] = result["Lieu"] st.success(f"Connecté comme {result['role']} ({result['Lieu']})") # Enregistrement de la connexion dans Connexion_Log try: now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") cursor.execute(""" INSERT INTO Connexion_Log (Utilisateur, Lieu, Date_Connexion) VALUES (%s, %s, %s) """, (login, result["Lieu"], now)) conn.commit() except Exception as e: st.warning(f"⚠️ Connexion enregistrée échouée : {e}") st.rerun() else: st.sidebar.error("Identifiants invalides") cursor.close() conn.close() except Exception as e: st.sidebar.error(f"Erreur lors de la connexion à la base : {e}") else: st.sidebar.success(f"Connecté ({st.session_state['role']})") if st.sidebar.button("🔓 Déconnexion", key="logout_sidebar"): st.session_state["authenticated"] = False st.session_state["role"] = None st.session_state["lieu_autorise"] = None st.rerun() # 📄 Affichage bouton PDF si une date est choisie site_pdf = ( st.session_state.get("lieu_autorise") if st.session_state.get("role") != "superviseur" else st.session_state.get("selected_site") ) date_pdf = st.session_state.get("selected_date") if site_pdf and date_pdf: st.sidebar.markdown("---") st.sidebar.subheader("📄 Rapport PDF") if st.sidebar.button("📥 Télécharger l’état du jour (PDF)", key="pdf_btn"): generer_pdf(site_pdf, date_pdf.strftime("%Y-%m-%d")) # --- Forcer une alerte de test dynamique (réservé aux superviseurs) if st.session_state.get("authenticated") and st.session_state.get("role") == "superviseur": site_actuel = ( st.session_state.get("lieu_autorise") if st.session_state.get("role") != "superviseur" else st.session_state.get("selected_site") ) if site_actuel: # Initialiser la détection de changement de site if "last_site" not in st.session_state: st.session_state["last_site"] = None # Si le site a changé, recharger les sondes disponibles if site_actuel != st.session_state["last_site"]: try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor() cursor.execute(f"SELECT DISTINCT Sonde FROM `{site_actuel}` ORDER BY Sonde ASC") st.session_state["sondes_dispo"] = [row[0] for row in cursor.fetchall()] cursor.close() conn.close() st.session_state["last_site"] = site_actuel except Exception as e: st.sidebar.warning(f"Erreur chargement des sondes : {e}") # Affichage de la liste des sondes disponibles sondes_dispo = st.session_state.get("sondes_dispo", []) if sondes_dispo: st.sidebar.markdown("---") st.sidebar.subheader("🧪 Test alerte manuelle") sonde_test = st.sidebar.selectbox("Choisir une sonde :", sondes_dispo) if st.sidebar.button("🔔 Forcer une alerte de test"): now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor() table_alertes = f"Alertes_{site_actuel}" cursor.execute(f""" INSERT INTO {table_alertes} (Sonde, Debut_defaut, Status) VALUES (%s, %s, %s) """, (sonde_test, now, "Test")) conn.commit() cursor.close() conn.close() st.success(f"Alerte de test créée pour {sonde_test} à {now}") except Exception as e: st.error(f"Erreur lors de la création de l'alerte : {e}") else: st.success(f"Connecté ({st.session_state['role']})") if st.button("🔓 Déconnexion", key="logout_main"): st.session_state["authenticated"] = False st.session_state["role"] = None st.session_state["lieu_autorise"] = None st.rerun() st.markdown("---") st.subheader("📄 Rapport PDF") if "selected_date" in st.session_state: if st.button("📅 Télécharger l'état du jour (PDF)"): site = st.session_state["lieu_autorise"] date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") generer_pdf(site, date_val) else: st.info("Sélectionnez une date pour activer la génération PDF.") # --- CONTENU PRINCIPAL SI AUTHENTIFIÉ --- if st.session_state["authenticated"]: # --- AFFICHAGE GLOBAL DES ALERTES NON ACQUITTÉES --- try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) if "role" not in st.session_state: st.session_state["role"] = None if "lieu_autorise" not in st.session_state: st.session_state["lieu_autorise"] = None site_selectionne = ( st.session_state["lieu_autorise"] if st.session_state["role"] != "superviseur" else st.session_state.get("selected_site", "Saclay") ) table_alertes = f"Alertes_{site_selectionne}" cursor.execute( f"SELECT Sonde, Debut_defaut, Status FROM `{table_alertes}` WHERE Status != 'Acquitté' ORDER BY Debut_defaut DESC" ) alertes = cursor.fetchall() if alertes: df_alertes = pd.DataFrame(alertes) st.subheader("🚨 Alertes non acquittées") st.dataframe(df_alertes, use_container_width=True) else: st.success("✅ Aucune alerte en cours.") 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["role"] == "superviseur": onglet = st.sidebar.radio("📁 Navigation", ["Accueil", "Statistiques", "Traffic"]) # --- ONGLET ACCUEIL --- else: onglet = "Accueil" if onglet == "Accueil": st.markdown("## Sélection du site et de la date") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) sites_possibles = ["Saclay", "Meudon"] if st.session_state["role"] == "superviseur": site_selectionne = st.selectbox("📍 Choisissez un site :", sites_possibles) st.session_state["selected_site"] = site_selectionne else: site_selectionne = st.session_state["lieu_autorise"] st.info(f"Site imposé : {site_selectionne}") selected_date = st.date_input("📅 Date du relevé", value=date.today()) st.session_state["selected_date"] = selected_date cursor.execute( f"SELECT * FROM `{site_selectionne}` WHERE DATE(Date) = %s ORDER BY Sonde, Date DESC", (selected_date.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, key="selectbox_accueil") df_sonde = df[df["Sonde"] == sonde_choisie] df_sonde.loc[:, "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)"]) 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)] df_sonde = df_sonde.copy() cursor.execute("SELECT Temp_Max FROM Chambres_froides WHERE Lieu = %s AND Sonde = %s", (site_selectionne, sonde_choisie)) seuil = cursor.fetchone() seuil_temp = seuil["Temp_Max"] if seuil else 10 st.subheader("📊 Tableau des relevés") df_filtre = df_sonde.copy() df_filtre = df_filtre.drop(columns="Id", errors="ignore") def surlignage_temp(val): try: if float(val) > seuil_temp: return "color: red; font-weight: bold" except: pass return "" styled_df = df_filtre.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_filtre["Date"], df_filtre["Temperature"], marker='o', label="Température") 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} - {selected_date.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 MySQL : {e}") # ---- ONGLET STATISTIQUES --- elif onglet == "Statistiques": st.markdown("## 📈 Statistiques de température") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) site = ( st.session_state["lieu_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}") # --- Affichage automatique des alertes non acquittées --- site_selectionne = ( st.session_state.get("lieu_autorise") if st.session_state.get("role") != "superviseur" else st.session_state.get("selected_site") ) try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) table_alertes = f"Alertes_{site_selectionne}" cursor.close() conn.close() except Exception as e: st.error(f"Erreur lors de la récupération des alertes : {e}") # --- Fonctionnalités administrateur : ajout et gestion des chambres froides --- if st.session_state["role"] == "superviseur": with st.expander("+ Ajouter une nouvelle chambre froide", expanded=False): with st.form("ajout_sonde"): nouvelle_sonde = st.sidebar.text_input("Nom de la nouvelle sonde :") seuil_max = st.sidebar.number_input("Température maximale autorisée :", min_value=-50.0, max_value=50.0, value=6.0, step=0.1) if st.sidebar.button("➕ Ajouter la nouvelle sonde"): try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor() # Insérer dans Chambres_froides cursor.execute(""" INSERT INTO Chambres_froides (Lieu, Sonde, Temp_Max, Etat) VALUES (%s, %s, %s, %s) """, (site_actuel, nouvelle_sonde, seuil_max, "ON")) conn.commit() # Insérer un premier relevé dans la table de relevés table_releves = site_actuel cursor.execute(f""" INSERT INTO `{table_releves}` (Date, Sonde, Temperature) VALUES (NOW(), %s, %s) """, (nouvelle_sonde, 0.0)) conn.commit() cursor.close() conn.close() st.success( f"Sonde {nouvelle_sonde} ajoutée avec Temp_Max {seuil_max}°C et premier relevé enregistré.") except Exception as e: st.error(f"Erreur lors de l'ajout de la sonde : {e}") 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, val, plus = st.columns([1, 2, 1]) with moins: if st.button("▼", key=f"moins_{chambre['Id']}"): temp_max -= 1 with val: 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 TRAFFIC ------ elif onglet == "Traffic": st.markdown("## 🚦 Connexions récentes") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) cursor.execute( "SELECT Utilisateur, Lieu, Date_Connexion FROM Connexion_Log WHERE Lieu IS NOT NULL ORDER BY Date_Connexion DESC LIMIT 200" ) connexions = cursor.fetchall() cursor.close() conn.close() if connexions: df_connexions = pd.DataFrame(connexions) st.success(f"{len(df_connexions)} connexions trouvées.") # Compteur affiché proprement st.dataframe(df_connexions, use_container_width=True) else: st.info("Aucune connexion enregistrée.") except Exception as e: st.error(f"Erreur chargement des connexions : {e}") except Exception as err: st.error(f"Erreur MySQL : {err}")