diff --git a/Telegram_sondes.py b/Telegram_sondes.py deleted file mode 100644 index 3077d92..0000000 --- a/Telegram_sondes.py +++ /dev/null @@ -1,324 +0,0 @@ -# Gestion des chambres froides & alertes Telegram -import requests -import mysql.connector -from datetime import datetime, timedelta -import time -import sys -import os -import schedule - -def connect_db(): - return mysql.connector.connect( - host="54.36.188.119", - user="michel", - password="#SO2&1nf%mZ@jfh", - database="Sondes" - ) - -def envoi_etat_quotidien(cursor, site): - token = "8128378340:AAF2sO3gaH1XpMNya_pEslzerqokoCiFRGs" - chat_id = get_chat_id(cursor, site) - etat_sondes(cursor, site, chat_id, token) - print(f"[INFO] État des sondes envoyé pour le site {site}.") - -def get_active_sondes(cursor, site): - query = "SELECT Sonde, Temp_Max FROM Chambres_froides WHERE Lieu = %s AND Etat = 'On';" - cursor.execute(query, (site,)) - result = cursor.fetchall() - return {row[0]: float(row[1]) for row in result} - - -def check_temperature_limits(cursor, table_historique, sonde, limite, duree=30): - temps_limite = datetime.now() - timedelta(minutes=duree) - query = f""" - SELECT Temperature - FROM {table_historique} - WHERE Sonde = %s AND Date >= %s - ORDER BY Date DESC LIMIT 6; - """ - cursor.execute(query, (sonde, temps_limite)) - result = cursor.fetchall() - if len(result) == 6 and all(float(temp[0]) > limite for temp in result): - return True, float(result[0][0]) - return False, None - -def simulate_alert(db, cursor, site, sonde, chat_id, token): - table_alertes = f"Alertes_{site}" - print(f"[TEST] tentative d'insertion de sonde : {sonde} dans table {table_alertes}") - - try: - query = f""" - INSERT INTO {table_alertes} (Sonde, Debut_defaut, Status) - VALUES (%s, NOW(), 'test') - ON DUPLICATE KEY UPDATE Debut_defaut = NOW(), Status = 'test'; - """ - cursor.execute(query, (sonde,)) - db.commit() - print(f"[TEST] insertion ou mise à jour réussie pour {sonde}.") - except Exception as e: - print(f"[ERREUR] lors de l'insertion test : {e}") - - message = f"⚠️ TEST ALERTE : Simulation d'une alerte pour la sonde {sonde}." - url = f"https://api.telegram.org/bot{token}/sendMessage" - try: - requests.get(url, params={'chat_id': chat_id, 'text': message}) - except Exception as e: - print(f"[ERREUR] lors de l'envoi Telegram : {e}") - -def create_alert(db, cursor, table_alertes, sonde, temperature): - query = f""" - INSERT INTO {table_alertes} (Sonde, Debut_defaut, Status) - VALUES (%s, NOW(), 'en cours'); - """ - cursor.execute(query, (sonde,)) - send_telegram_message(sonde, temperature) - print(f"Alerte créée pour la sonde {sonde}") - db.commit() - -def resolve_alert(cursor, table_alertes, sonde): - query = f""" - UPDATE {table_alertes} - SET Status = 'résolu' - WHERE Sonde = %s AND Status = 'en cours'; - """ - cursor.execute(query, (sonde,)) - print(f"Alerte résolue pour la sonde {sonde}") - -def acquitter_alerte(cursor, site, sonde, chat_id, token): - table_alertes = f"Alertes_{site}" - query = f""" - UPDATE {table_alertes} - SET Status = 'acquittée' - WHERE Sonde = %s AND Status IN ('en cours', 'test'); - """ - cursor.execute(query, (sonde,)) - lignes_modifiees = cursor.rowcount - - if lignes_modifiees > 0: - message = f"✅ Alerte acquittée pour la sonde {sonde} par commande Telegram." - else: - message = f"ℹ️ Aucune alerte active à acquitter pour la sonde {sonde}." - - url = f"https://api.telegram.org/bot{token}/sendMessage" - try: - requests.get(url, params={'chat_id': chat_id, 'text': message}) - except Exception as e: - print(f"[ERREUR] envoi message Telegram : {e}") - -def send_telegram_message(sonde, temperature): - token = "5714323406:AAGSj9jrfBHbfxubz3ooabPEizI8aBOLnvE" - chat_id = "-1002442631825" - message = f"⚠️ Alerte température : La sonde {sonde} dépasse la limite avec une température de {temperature}°C." - url = f"https://api.telegram.org/bot{token}/sendMessage" - params = {'chat_id': chat_id, 'text': message} - try: - response = requests.get(url, params=params) - if response.status_code == 200: - print(f"Message envoyé via Telegram pour la sonde {sonde}.") - else: - print(f"Erreur lors de l'envoi du message Telegram : {response.status_code}") - except Exception as e: - print(f"Erreur lors de l'envoi du message Telegram : {e}") - - -def get_chat_id(cursor, site): - cursor.execute("SELECT Chat_ID FROM Sites WHERE Nom = %s;", (site,)) - result = cursor.fetchone() - if result: - return result[0] - else: - print(f"Aucun Chat_ID trouvé pour le site {site}") - return None - -def passer_en_maintenance(cursor, site, sonde, chat_id, token): - try: - query = """ - UPDATE Chambres_froides - SET Etat = 'Off' - WHERE Lieu = %s AND Sonde = %s; - """ - cursor.execute(query, (site, sonde)) - lignes_modifiees = cursor.rowcount - - if lignes_modifiees > 0: - message = f"🛠️ La sonde {sonde} a été passée en mode maintenance (OFF)." - else: - message = f"⚠️ Sonde {sonde} introuvable pour le site {site}." - - url = f"https://api.telegram.org/bot{token}/sendMessage" - requests.get(url, params={'chat_id': chat_id, 'text': message}) - - except Exception as e: - print(f"[ERREUR] lors du passage en maintenance : {e}") - -def reactiver_sonde(cursor, site, sonde, chat_id, token): - try: - query = """ - UPDATE Chambres_froides - SET Etat = 'On' - WHERE Lieu = %s AND Sonde = %s; - """ - cursor.execute(query, (site, sonde)) - lignes_modifiees = cursor.rowcount - - if lignes_modifiees > 0: - message = f"✅ La sonde {sonde} a été réactivée (ON)." - else: - message = f"⚠️ Sonde {sonde} introuvable pour le site {site}." - - url = f"https://api.telegram.org/bot{token}/sendMessage" - requests.get(url, params={'chat_id': chat_id, 'text': message}) - - except Exception as e: - print(f"[ERREUR] lors de la réactivation : {e}") - -def etat_sondes(cursor, site, chat_id, token): - query = "SELECT Sonde, Etat FROM Chambres_froides WHERE Lieu = %s ORDER BY Sonde;" - cursor.execute(query, (site,)) - result = cursor.fetchall() - - message = f"📊 État des sondes - {site} :\n" - for sonde, etat in result: - symbole = "🟢" if etat.upper() == "ON" else "🔴" - message += f"{symbole} {sonde} ({etat.upper()})\n" - - url = f"https://api.telegram.org/bot{token}/sendMessage" - requests.get(url, params={'chat_id': chat_id, 'text': message}) - -def monitor_temperatures_simple(site, db, cursor): - table_historique = site - table_alertes = f"Alertes_{site}" - sondes = get_active_sondes(cursor, site) - print(f"[MONITORING] Sondes actives pour {site} : {sondes}") - - for sonde, limite in sondes.items(): - alert_needed, temperature = check_temperature_limits(cursor, table_historique, sonde, limite) - if alert_needed: - create_alert(db, cursor, table_alertes, sonde, temperature) - else: - resolve_alert(cursor, table_alertes, sonde) - - db.commit() - print("[MONITORING] Vérification des sondes terminée.") - -def monitor_temperatures(site): - db = connect_db() - cursor = db.cursor() - - table_historique = site # Ex: Saclay - table_alertes = f"Alertes_{site}" # Ex: Alertes_Saclay - - sondes = get_active_sondes(cursor, site) - print(f"Sondes actives pour le site {site} :", sondes) - - for sonde, limite in sondes.items(): - alert_needed, temperature = check_temperature_limits(cursor, table_historique, sonde, limite) - if alert_needed: - create_alert(db, cursor, table_alertes, sonde, temperature) - else: - resolve_alert(cursor, table_alertes, sonde) - - db.commit() - listen_for_commands(db, cursor, site) - cursor.close() - db.close() - -def listen_for_commands(db, cursor, site): - token = "5714323406:AAGSj9jrfBHbfxubz3ooabPEizI8aBOLnvE" - chat_id = get_chat_id(cursor, site) - offset_file = os.path.join(os.path.dirname(__file__), f"last_update_id_{site}.txt") - - try: - with open(offset_file, 'r') as f: - last_update_id = int(f.read().strip()) - except FileNotFoundError: - last_update_id = None - - url = f"https://api.telegram.org/bot{token}/getUpdates" - if last_update_id is not None: - url += f"?offset={last_update_id + 1}" - - try: - response = requests.get(url) - data = response.json() - - if "result" in data: - for update in data["result"]: - update_id = update["update_id"] - - if "message" in update: - message = update["message"]["text"] - print(f"[CMD] Message reçu : {message}") - - if message.lower().startswith("/etat"): - etat_sondes(cursor, site, chat_id, token) - - elif message.lower().startswith("/acquitter"): - parts = message.strip().split(" ", 1) - if len(parts) == 2 and parts[1].strip(): - sonde = parts[1].strip() - acquitter_alerte(cursor, site, sonde, chat_id, token) - db.commit() - else: - erreur_msg = "❌ Utilisation incorrecte. Format attendu : /acquitter " - requests.get(f"https://api.telegram.org/bot{token}/sendMessage", - params={'chat_id': chat_id, 'text': erreur_msg}) - elif message.lower().startswith("/maintenance"): - parts = message.strip().split(" ", 1) - if len(parts) == 2 and parts[1].strip(): - sonde = parts[1].strip() - passer_en_maintenance(cursor, site, sonde, chat_id, token) - db.commit() - else: - erreur_msg = "❌ Utilisation incorrecte. Format attendu : /maintenance " - requests.get(f"https://api.telegram.org/bot{token}/sendMessage", - params={'chat_id': chat_id, 'text': erreur_msg}) - elif message.lower().startswith("/reactiver"): - parts = message.strip().split(" ", 1) - if len(parts) == 2 and parts[1].strip(): - sonde = parts[1].strip() - reactiver_sonde(cursor, site, sonde, chat_id, token) - db.commit() - else: - erreur_msg = "❌ Utilisation incorrecte. Format attendu : /reactiver " - requests.get(f"https://api.telegram.org/bot{token}/sendMessage", - params={'chat_id': chat_id, 'text': erreur_msg}) - elif message.lower().startswith("/test"): - parts = message.strip().split(" ", 1) - if len(parts) == 2 and parts[1].strip(): - sonde = parts[1].strip() - simulate_alert(db, cursor, site, sonde, chat_id, token) - else: - erreur_msg = "❌ Utilisation incorrecte. Format attendu : /test " - requests.get(f"https://api.telegram.org/bot{token}/sendMessage", - params={'chat_id': chat_id, 'text': erreur_msg}) - - with open(offset_file, 'w') as f: - f.write(str(update_id)) - - except Exception as e: - print(f"Erreur lors de la lecture des commandes Telegram : {e}") - - -def main(): - site = sys.argv[1] if len(sys.argv) > 1 else "Saclay" - db = connect_db() - cursor = db.cursor() - - # ✅ Planification : tous les jours à 07:00 → état des sondes - schedule.every().day.at("07:00").do(envoi_etat_quotidien, cursor=cursor, site=site) - - # ✅ Planification : toutes les 2m30 → surveillance - schedule.every(2).minutes.do(monitor_temperatures_simple, site=site, db=db, cursor=cursor) - - try: - while True: - schedule.run_pending() - listen_for_commands(db, cursor, site) - time.sleep(2) # petite pause pour ne pas surcharger Telegram - finally: - cursor.close() - db.close() - -if __name__ == "__main__": - main() diff --git a/domo91.py b/domo91.py index 4473fe9..0f7b645 100644 --- a/domo91.py +++ b/domo91.py @@ -5,8 +5,6 @@ import pandas as pd from datetime import date import matplotlib.pyplot as plt import matplotlib.dates as mdates -from fpdf import FPDF -import os st.set_page_config(page_title="Domo91 - Surveillance", layout="wide") st.title("📡 Supervision Températures") @@ -18,94 +16,102 @@ db_config = { "password": "#SO2&1nf%mZ@jfh", "database": "Sondes" } -# --- 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) - 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") +def afficher_tableau_filtré(df): + st.markdown("### 🔧 Filtrage horaire") - releves = {} - for sonde in df["Sonde"].unique(): - df_sonde = df[df["Sonde"] == sonde] - releves[sonde] = list(zip(df_sonde["Heure"], df_sonde["Temperature"])) + df = df.copy() + df["Date"] = pd.to_datetime(df["Date"]) - 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() + # Supprimer la colonne Id si elle existe + if "Id" in df.columns: + df.drop(columns="Id", inplace=True) - cursor.close() - conn.close() + # Sélection de la tranche horaire + plage = st.selectbox( + "Sélectionnez une plage horaire :", + ["Toutes", "Matin (06h-12h)", "Après-midi (12h-18h)", "Soir (18h-00h)"] + ) - 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) + if plage == "Matin (06h-12h)": + df = df[(df["Date"].dt.hour >= 6) & (df["Date"].dt.hour < 12)] + elif plage == "Après-midi (12h-18h)": + df = df[(df["Date"].dt.hour >= 12) & (df["Date"].dt.hour < 18)] + elif plage == "Soir (18h-00h)": + df = df[(df["Date"].dt.hour >= 18)] - 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) + # Centrage de la colonne Température (et autres si souhaité) + def style_center(s): + return ['text-align: center'] * len(s) - 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) + st.dataframe( + df.style.apply(style_center, subset=["Temperature"]), + use_container_width=True + ) - 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) + return df - 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 e: - st.error(f"Erreur lors de la génération du PDF : {e}") -# --- Initialisation session --- if "authenticated" not in st.session_state: st.session_state["authenticated"] = False st.session_state["role"] = None st.session_state["lieu_autorise"] = None -# --- Sidebar (connexion + bouton PDF) --- +# --- Accès aux logs dès connexion superviseur --- +if st.session_state.get("authenticated") and st.session_state["role"] == "superviseur": + st.markdown("### 🔍 Accès à l’analyse des relevés") + if st.button("🧾 Analyse des logs Monitor.py"): + st.session_state["page"] = "analyse_logs" + st.rerun() + +# --- Analyse logs si page active --- +if st.session_state.get("page") == "analyse_logs": + st.title("🧾 Analyse des notifications de relevés") + try: + df_logs = pd.read_csv("/home/debian/travail/logs_monitor.csv", sep=";", parse_dates=["Date"]) + df_logs["Date_str"] = df_logs["Date"].dt.date + dates_dispo = sorted(df_logs["Date_str"].unique(), reverse=True) + date_selection = st.selectbox("📅 Sélectionnez une date :", dates_dispo) + lieux = sorted(df_logs["Lieu"].unique()) + lieu_selection = st.selectbox("📍 Site :", lieux) + df_filtré = df_logs[(df_logs["Date_str"] == date_selection) & (df_logs["Lieu"] == lieu_selection)] + sondes = sorted(df_filtré["Sonde"].unique()) + sonde_selection = st.selectbox("🧪 Sonde :", sondes) + df_sonde = df_filtré[df_filtré["Sonde"] == sonde_selection].copy() + + st.subheader(f"📈 Évolution de la température - {sonde_selection}") + fig, ax = plt.subplots(figsize=(10, 4)) + ax.plot(df_sonde["Date"], df_sonde["Température"], marker='o') + ax.set_title(f"{sonde_selection} - {date_selection}") + ax.set_xlabel("Heure") + ax.set_ylabel("Température (°C)") + ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) + st.pyplot(fig) + + st.subheader("📋 Détail des relevés") + + # Coloration conditionnelle des températures > seuil + df_sonde["Depassement"] = df_sonde["Température"] > df_sonde["Seuil"] + st.dataframe(df_sonde.style.apply( + lambda row: [ + 'background-color: red; color: white' if row["Depassement"] and col == "Température" else '' + for col in df_sonde.columns + ], axis=1 + ), use_container_width=True) + + st.markdown("---") + if st.button("⬅️ Retour à l'accueil"): + st.session_state["page"] = None + st.rerun() + + except Exception as e: + st.error(f"Erreur lecture logs : {e}") + st.stop() + + with st.sidebar: st.header("🔐 Connexion") - if not st.session_state.get("authenticated"): + if not st.session_state["authenticated"]: login = st.text_input("Nom d'utilisateur") password = st.text_input("Mot de passe", type="password") if st.button("Se connecter"): @@ -133,19 +139,8 @@ with st.sidebar: 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É --- +# --- Interface principale if st.session_state["authenticated"]: - st.markdown("## Sélection du site et de la date") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) @@ -155,10 +150,7 @@ if st.session_state["authenticated"]: 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", (selected_date.strftime("%Y-%m-%d"),) @@ -170,15 +162,12 @@ if st.session_state["authenticated"]: sondes = sorted(df["Sonde"].unique()) sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes) df_sonde = df[df["Sonde"] == sonde_choisie] - 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_filtré = df_sonde.copy() - df_filtré = df_filtré.drop(columns="Id", errors="ignore") - st.dataframe(df_filtré, use_container_width=True) + df_filtré = afficher_tableau_filtré(df_sonde) + st.subheader("📈 Évolution de la température") fig, ax = plt.subplots(figsize=(10, 4)) @@ -190,14 +179,71 @@ if st.session_state["authenticated"]: ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend() st.pyplot(fig) - else: st.warning("Aucune donnée trouvée pour cette date.") + # --- Alertes et bouton vers analyse logs --- + st.markdown("---") + st.subheader("🚨 Alertes de température") + if st.session_state["role"] == "superviseur": + if st.button("🧾 Voir les notifications relevées dans Monitor"): + st.session_state["page"] = "analyse_logs" + st.rerun() + + table_alertes = f"Alertes_{site_selectionne}" + voir_toutes = False + if st.session_state["role"] == "superviseur": + voir_toutes = st.toggle("Afficher toutes les alertes (y compris acquittées)", value=False) + if voir_toutes: + cursor.execute(f"SELECT * FROM {table_alertes} ORDER BY Debut_defaut DESC") + else: + cursor.execute(f"SELECT * FROM {table_alertes} WHERE Status = 'En cours' ORDER BY Debut_defaut DESC") + alertes = cursor.fetchall() + if alertes: + for alerte in alertes: + cols = st.columns([3, 3, 2, 2, 2]) + cols[0].markdown(f"**Sonde :** {alerte['Sonde']}") + cols[1].markdown(f"**Début :** {alerte['Debut_defaut'].strftime('%Y-%m-%d %H:%M')}") + cols[2].markdown(f"**Statut :** {alerte['Status']}") + if st.session_state["role"] == "superviseur" and alerte['Status'] == 'En cours': + key_btn = f"acq_{alerte['Id']}" + if cols[4].button("✅ Acquitter", key=key_btn): + cursor.execute(f"UPDATE {table_alertes} SET Status = 'Acquitté' WHERE Id = %s", (alerte['Id'],)) + conn.commit() + st.success(f"Alerte acquittée pour {alerte['Sonde']}") + st.rerun() + else: + st.info("✅ Aucune alerte en cours.") + + # --- Interface admin seuils / ON-OFF --- + if st.session_state["role"] == "superviseur": + st.markdown("---") + st.subheader("🛠️ Paramètres des sondes") + cursor.execute("SELECT * FROM Chambres_froides WHERE Lieu = %s", (site_selectionne,)) + sondes_info = cursor.fetchall() + if sondes_info: + for sonde in sondes_info: + cols = st.columns([3, 2, 3]) + cols[0].markdown(f"**{sonde['Sonde']}**") + etat_key = f"etat_{sonde['Id']}" + seuil_key = f"seuil_{sonde['Id']}" + etat_actuel = sonde["Etat"] == "ON" + new_etat = cols[1].checkbox("Actif", value=etat_actuel, key=etat_key) + new_seuil = cols[2].number_input("Seuil Max", min_value=-30.0, max_value=30.0, value=float(sonde["Temp_Max"]), step=0.5, key=seuil_key) + sonde["_new_etat"] = "ON" if new_etat else "OFF" + sonde["_new_seuil"] = new_seuil + if st.button("💾 Enregistrer les modifications"): + try: + for sonde in sondes_info: + cursor.execute("UPDATE Chambres_froides SET Etat = %s, Temp_Max = %s WHERE Id = %s", (sonde["_new_etat"], sonde["_new_seuil"], sonde["Id"])) + conn.commit() + st.success("✅ Modifications enregistrées.") + st.rerun() + except Exception as e: + st.error(f"Erreur lors de la mise à jour : {e}") + cursor.close() conn.close() except Exception as e: - st.error(f"Erreur MySQL : {e}") - - + st.error(f"Erreur MySQL : {e}") \ No newline at end of file