diff --git a/main.py b/main.py index 6115ef5..30b8c0c 100644 --- a/main.py +++ b/main.py @@ -8,11 +8,10 @@ from fasthtml.common import * from starlette.staticfiles import StaticFiles from starlette.responses import FileResponse, Response, HTMLResponse, RedirectResponse from starlette.middleware.base import BaseHTTPMiddleware -from modules.user_db import load_users, save_users, changer_nom_utilisateur, mettre_a_jour_mot_de_passe -from modules.system_utils import obtenir_infos_serveur + from modules.theme import get_theme_hdrs from modules.database import nettoyer_vieilles_discussions, HISTORY_FILE, load_history, save_history -from modules.ui_components import render_main_page, render_sidebar, render_login_page, render_rag_upload_modal +from modules.ui_components import render_main_page, render_sidebar, render_login_page, render_rag_upload_modal, render_backoffice_page from modules.chat_handler import handle_chat_request from modules.security import verifier_et_bloquer_vpn, journaliser_ip from modules.auth_manager import IDENTIFIANTS_ADMIN, verifier_mot_de_passe @@ -20,6 +19,8 @@ from modules.logger import log_event from modules.billing_manager import charger_soldes, sauvegarder_soldes from modules.rag_manager import auto_apprendre_rag from modules.vector_rag import indexer_dossier_thematique +from modules.user_db import load_users, save_users, changer_nom_utilisateur, mettre_a_jour_mot_de_passe +from modules.system_utils import obtenir_infos_serveur  # Lancement du module d'auto-mise à jour en arrière-plan import modules.model_updater @@ -27,108 +28,13 @@ import modules.model_updater dossier_media = "/DATA/AppData/MULTI-IA-AETHAS38/RAG/Media" os.makedirs(dossier_media, exist_ok=True)  -@rt('/backoffice') -def get(session, request): - utilisateur = session.get('user') or request.cookies.get('aethas_user') - if not utilisateur: - return RedirectResponse('/login', status_code=303) -  - users = load_users() - infos_user = users.get(utilisateur, {"role": "Membre"}) - role = infos_user.get("role", "Membre") -  - # Données spécifiques Super Admin - stats_serveur = obtenir_infos_serveur() if role == "Super Admin" else None -  - return render_backoffice_page(utilisateur, role, users, stats_serveur) - -@rt('/backoffice/update_profile') -async def post(request, session): - utilisateur = session.get('user') or request.cookies.get('aethas_user') - if not utilisateur: - return RedirectResponse('/login', status_code=303) -  - form = await request.form() - nouveau_pseudo = form.get("nouveau_pseudo") - nouveau_mdp = form.get("nouveau_mdp") -  - if nouveau_pseudo and nouveau_pseudo != utilisateur: - succes = changer_nom_utilisateur(utilisateur, nouveau_pseudo) - if succes: - session['user'] = nouveau_pseudo - resp = RedirectResponse('/backoffice', status_code=303) - resp.set_cookie("aethas_user", nouveau_pseudo, max_age=86400) - return resp -  - if nouveau_mdp: - mettre_a_jour_mot_de_passe(utilisateur, nouveau_mdp) -  - return RedirectResponse('/backoffice', status_code=303) - -# --- WIDGET PAYPAL (RECHARGE DE CRÉDITS) --- -def render_paypal_recharge_widget(montant_usd: float = 10.00): - client_id = os.getenv("PAYPAL_CLIENT_ID", "test") -  - script_paypal = Script(src=f"https://www.paypal.com/sdk/js?client-id={client_id}¤cy=USD") - container_id = f"paypal-button-container-{str(montant_usd).replace('.', '')}" -  - script_logique = Script(f""" - function chargerBoutonPayPal() {{ - if (typeof paypal !== 'undefined') {{ - paypal.Buttons({{ - style: {{ layout: 'vertical', height: 35, shape: 'rect', label: 'paypal' }}, - createOrder: function(data, actions) {{ - return actions.order.create({{ - purchase_units: [{{ amount: {{ value: '{montant_usd}' }} }}] - }}); - }}, - onApprove: function(data, actions) {{ - return actions.order.capture().then(function(details) {{ - alert('Paiement validé par ' + details.payer.name.given_name + ' ! Vos crédits vont être ajoutés.'); -  - fetch('/api/paypal/success', {{ - method: 'POST', - headers: {{ 'Content-Type': 'application/json' }}, - body: JSON.stringify({{ orderID: data.orderID }}) - }}).then(res => {{ - if(res.ok) {{ - document.getElementById('modal-recharge-paypal').style.display = 'none'; - window.location.reload(); - }} else {{ - alert("Erreur lors de la validation serveur."); - }} - }}); - }}); - }} - }}).render('#{container_id}'); - }} else {{ - setTimeout(chargerBoutonPayPal, 100); - }} - }} - chargerBoutonPayPal(); - """) - - paypal_box = Div( - H4(f"Recharge : {montant_usd} $", style="color: #fff; margin: 0 0 10px 0; font-size: 1.1em; text-align: center;"), - Div(id=container_id, style="min-height: 100px; width: 100%;"), - Button("Annuler", type="button", onclick="document.getElementById('modal-recharge-paypal').style.display='none'",  - style="background: transparent; border: 1px solid #555; color: #aaa; width: 100%; margin-top: 5px; padding: 6px; border-radius: 6px; font-size: 0.85em; cursor: pointer;"), - style="background: #1a1a1a; padding: 20px; border-radius: 10px; border: 1px solid #333; width: 320px; max-width: 90vw; box-shadow: 0 8px 24px rgba(0,0,0,0.9);" - ) - - modal_overlay = Div( - script_paypal, paypal_box, script_logique, - id="modal-recharge-paypal", - style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.6); backdrop-filter: blur(3px); align-items: center; justify-content: center; z-index: 10000;" - ) - return modal_overlay - # --- MIDDLEWARE DE SÉCURITÉ & SERVEUR MÉDIA UNIFIÉ --- class SecurityMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): ip = request.headers.get("X-Forwarded-For", request.client.host).split(",")[0].strip() utilisateur = request.cookies.get("aethas_user", "visiteur")  + # --- LECTURE EN RAM ULTRA-DIRECTE PAR LE MIDDLEWARE --- if request.url.path.startswith("/Media/"): fname = request.url.path.replace("/Media/", "", 1) chemin = os.path.join(dossier_media, fname) @@ -140,10 +46,12 @@ class SecurityMiddleware(BaseHTTPMiddleware): return HTMLResponse(f"Erreur : {e}", status_code=500) return HTMLResponse("Fichier introuvable", status_code=404)  + # EXCLUSION DE /Media POUR NE PAS BLOQUER LE RESTE if not request.url.path.startswith(("/Logo", "/login", "/Media")): if verifier_et_bloquer_vpn(ip, utilisateur): return HTMLResponse("

Accès refusé

VPN ou Proxy strictement interdit.

", status_code=403)  + # Exclusion stricte du spam de logs (Silence pour /ping et /favicon) if request.url.path not in ["/ping", "/favicon.ico"]: journaliser_ip(ip, f"Accès à {request.url.path}")  @@ -182,6 +90,44 @@ def get(session): resp.delete_cookie("aethas_user") return resp  +# --- ROUTES DU BACK OFFICE & PROFIL --- +@rt('/backoffice') +def get(session, request): + utilisateur = session.get('user') or request.cookies.get('aethas_user') + if not utilisateur: + return RedirectResponse('/login', status_code=303) +  + users = load_users() + infos_user = users.get(utilisateur, {"role": "Membre"}) + role = infos_user.get("role", "Membre") +  + stats_serveur = obtenir_infos_serveur() if role == "Super Admin" else None +  + return render_backoffice_page(utilisateur, role, users, stats_serveur) + +@rt('/backoffice/update_profile') +async def post(request, session): + utilisateur = session.get('user') or request.cookies.get('aethas_user') + if not utilisateur: + return RedirectResponse('/login', status_code=303) +  + form = await request.form() + nouveau_pseudo = form.get("nouveau_pseudo") + nouveau_mdp = form.get("nouveau_mdp") +  + if nouveau_pseudo and nouveau_pseudo != utilisateur: + succes = changer_nom_utilisateur(utilisateur, nouveau_pseudo) + if succes: + session['user'] = nouveau_pseudo + resp = RedirectResponse('/backoffice', status_code=303) + resp.set_cookie("aethas_user", nouveau_pseudo, max_age=86400) + return resp +  + if nouveau_mdp: + mettre_a_jour_mot_de_passe(utilisateur, nouveau_mdp) +  + return RedirectResponse('/backoffice', status_code=303) + # --- ROUTES PROTÉGÉES --- @rt('/') def get(session, request): @@ -192,11 +138,7 @@ def get(session, request): session['user'] = utilisateur if 'current_chat_id' not in session or not session['current_chat_id']: session['current_chat_id'] = str(uuid.uuid4()) -  - main_page = render_main_page(session['current_chat_id'], utilisateur_nom=session['user']) - paypal_modal = render_paypal_recharge_widget(10.00) -  - return paypal_modal, main_page + return render_main_page(session['current_chat_id'], utilisateur_nom=session['user'])  @rt('/chat') async def post(request, session): @@ -245,18 +187,14 @@ def post(cid: str): if cid_str in h: del h[cid_str]; save_history(h) return render_sidebar()  -@rt('/api/paypal/success') -async def post(request): - data = await request.json() - order_id = data.get("orderID") - log_event("INFO", "FINANCE", f"Recharge PayPal validée - OrderID: {order_id}") - return {"status": "success"} - +# --- NOUVELLE ROUTE POUR LE RAG (BOUTON MANUEL) --- @rt('/api/forcer_rag') def post(prompt: str, reponse: str, theme: str): + """Intercepte la demande manuelle, lance l'indexation et renvoie un badge vert.""" auto_apprendre_rag(prompt, reponse, theme) return Span("✅ Indexé", cls="msg-action-btn", style="background-color: #4caf50; color: white; border: none; cursor: default;")  +# --- NOUVELLES ROUTES POUR L'UPLOAD MODAL DU RAG --- @rt('/admin/rag_modal') def get(session): if session.get('user', '') != 'xavier':  @@ -295,6 +233,7 @@ async def post(request, session):  from modules.export_utils import generer_export_comptable  +# --- NOUVELLE ROUTE POUR L'EXPORT COMPTABLE EXCEL --- @rt('/admin/export_comptable') def get(session): if session.get('user', '') != 'xavier':