Files
Gestion_sondes/app/domo91.py

385 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 lapplication 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"<div style='background-color:#ffcccc;padding:10px;border-radius:8px;'>"
f"🚨 <b>{len(alertes)} alerte(s) non résolue(s)</b> sur <b>{site}</b>"
f"</div>",
unsafe_allow_html=True
)
else:
st.markdown(
f"<div style='background-color:#ccffcc;padding:10px;border-radius:8px;'>"
f"✅ Aucune alerte en cours sur <b>{site}</b>"
f"</div>",
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"<div style='text-align:center;font-size:20px'>{temp_max}°C</div>",
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())