Compare commits

...
2 Commits
Author SHA1 Message Date
xavier 43be6b5be6 feat: intégration de tous les fournisseurs d'IA (OpenRouter, Gemini, Groq, DeepSeek, Mistral, Cloudflare, HF) dans la base de données et l'interface d'installation
Build and Push Docker Image / build-and-push (push) Successful in 42s
2026-10-07 10:26:04 +02:00
xavier 22c640837c feat: intégration du paramétrage obligatoire SMTP et optionnel des clés API lors du setup initial
Build and Push Docker Image / build-and-push (push) Successful in 42s
2026-10-07 10:17:23 +02:00
4 changed files with 203 additions and 46 deletions

No files matched your search

+23 -7
View File
@@ -10,6 +10,7 @@ from .database import engine, Base, get_db
from .models import User, Project, Message from .models import User, Project, Message
from .auth import get_password_hash, generate_totp_secret, get_totp_uri, verify_password, verify_totp, create_access_token, verify_token from .auth import get_password_hash, generate_totp_secret, get_totp_uri, verify_password, verify_totp, create_access_token, verify_token
from .schemas import AdminCreate, LoginRequest, ProjectCreate, ProjectResponse, ProjectRename, MessageCreate, MessageResponse from .schemas import AdminCreate, LoginRequest, ProjectCreate, ProjectResponse, ProjectRename, MessageCreate, MessageResponse
from .models import User, Project, Message, SystemSettings
# Création des tables dans la base de données # Création des tables dans la base de données
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
@@ -58,17 +59,16 @@ def login_page(db: Session = Depends(get_db)):
@app.post("/api/setup") @app.post("/api/setup")
def create_admin(admin_data: AdminCreate, db: Session = Depends(get_db)): def create_admin(admin_data: AdminCreate, db: Session = Depends(get_db)):
"""Reçoit les données du frontend, crée l'admin et retourne le QR Code 2FA.""" """Reçoit les données du frontend, crée l'admin, sauvegarde la configuration et retourne le QR Code 2FA."""
if not is_setup_required(db): if not is_setup_required(db):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="L'installation a déjà été effectuée." detail="L'installation a déjà été effectuée."
) )
# Sécurisation des accès # Création du Super Admin
hashed_pw = get_password_hash(admin_data.password) hashed_pw = get_password_hash(admin_data.password)
totp_secret = generate_totp_secret() totp_secret = generate_totp_secret()
new_admin = User( new_admin = User(
email=admin_data.email, email=admin_data.email,
username=admin_data.username, username=admin_data.username,
@@ -76,16 +76,32 @@ def create_admin(admin_data: AdminCreate, db: Session = Depends(get_db)):
totp_secret=totp_secret, totp_secret=totp_secret,
is_admin=True is_admin=True
) )
db.add(new_admin) db.add(new_admin)
# Sauvegarde des paramètres système
new_settings = SystemSettings(
smtp_host=admin_data.smtp_host,
smtp_port=admin_data.smtp_port,
smtp_user=admin_data.smtp_user,
smtp_password=admin_data.smtp_password,
openrouter_api_key=admin_data.openrouter_api_key,
openrouter_management_key=admin_data.openrouter_management_key,
groq_api_key=admin_data.groq_api_key,
gemini_api_key=admin_data.gemini_api_key,
deepseek_api_key=admin_data.deepseek_api_key,
mistral_api_key=admin_data.mistral_api_key,
cloudflare_account_id=admin_data.cloudflare_account_id,
cloudflare_api_token=admin_data.cloudflare_api_token,
huggingface_api_key=admin_data.huggingface_api_key
)
db.add(new_settings)
db.commit() db.commit()
db.refresh(new_admin) db.refresh(new_admin)
# Génération de l'URI pour l'affichage du QR Code côté frontend
totp_uri = get_totp_uri(totp_secret, new_admin.username) totp_uri = get_totp_uri(totp_secret, new_admin.username)
return { return {
"message": "Administrateur créé avec succès.", "message": "Configuration terminée avec succès.",
"totp_secret": totp_secret, "totp_secret": totp_secret,
"totp_uri": totp_uri "totp_uri": totp_uri
} }
+20
View File
@@ -42,3 +42,23 @@ class Message(Base):
# Relation # Relation
project = relationship("Project", back_populates="messages") project = relationship("Project", back_populates="messages")
class SystemSettings(Base):
__tablename__ = "system_settings"
id = Column(Integer, primary_key=True, index=True)
# Serveur Mail (Obligatoire)
smtp_host = Column(String, nullable=False)
smtp_port = Column(Integer, nullable=False)
smtp_user = Column(String, nullable=False)
smtp_password = Column(String, nullable=False)
# Clés API (Optionnelles)
openrouter_api_key = Column(String, nullable=True)
openrouter_management_key = Column(String, nullable=True)
groq_api_key = Column(String, nullable=True)
gemini_api_key = Column(String, nullable=True)
deepseek_api_key = Column(String, nullable=True)
mistral_api_key = Column(String, nullable=True)
cloudflare_account_id = Column(String, nullable=True)
cloudflare_api_token = Column(String, nullable=True)
huggingface_api_key = Column(String, nullable=True)
+15 -3
View File
@@ -6,6 +6,21 @@ class AdminCreate(BaseModel):
email: EmailStr email: EmailStr
username: str username: str
password: str password: str
# Serveur Mail (Obligatoire)
smtp_host: str
smtp_port: int
smtp_user: str
smtp_password: str
# Clés API (Optionnelles)
openrouter_api_key: Optional[str] = None
openrouter_management_key: Optional[str] = None
groq_api_key: Optional[str] = None
gemini_api_key: Optional[str] = None
deepseek_api_key: Optional[str] = None
mistral_api_key: Optional[str] = None
cloudflare_account_id: Optional[str] = None
cloudflare_api_token: Optional[str] = None
huggingface_api_key: Optional[str] = None
class LoginRequest(BaseModel): class LoginRequest(BaseModel):
username: str username: str
@@ -40,8 +55,5 @@ class MessageResponse(MessageBase):
created_at: datetime created_at: datetime
project_id: int project_id: int
class Config:
from_attributes = True
class ProjectRename(BaseModel): class ProjectRename(BaseModel):
title: str title: str
+145 -36
View File
@@ -3,60 +3,166 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETHAS38 - Installation</title> <title>AETHAS38 - Installation Complète</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script> <script>
tailwind.config = { tailwind.config = { darkMode: 'class', theme: { extend: { colors: { aethas: { dark: '#0f172a', panel: '#1e293b', cyan: '#06b6d4', purple: '#a855f7' } } } } }
darkMode: 'class',
theme: {
extend: { colors: { aethas: { dark: '#0f172a', panel: '#1e293b', cyan: '#06b6d4', purple: '#a855f7' } } }
}
}
</script> </script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style> <style>
body { background-color: #0f172a; color: #f8fafc; font-family: 'Inter', sans-serif; } body { background-color: #0f172a; color: #f8fafc; font-family: 'Inter', sans-serif; }
.glass-panel { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(10px); border: 1px solid rgba(6, 182, 212, 0.2); box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5); } .glass-panel { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(10px); border: 1px solid rgba(6, 182, 212, 0.2); box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5); }
.neon-text { background: linear-gradient(to right, #06b6d4, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .neon-text { background: linear-gradient(to right, #06b6d4, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-thumb { background: #334155; border-radius: 10px; }
</style> </style>
</head> </head>
<body class="flex items-center justify-center min-h-screen p-4 sm:p-8"> <body class="flex items-center justify-center min-h-screen p-4 sm:p-8 overflow-y-auto">
<div id="app" class="w-full max-w-md transition-all duration-500"> <div id="app" class="w-full max-w-3xl transition-all duration-500 my-8">
<div class="text-center mb-8"> <div class="text-center mb-8">
<!-- Chemin du logo corrigé -->
<img src="/static/logo.png" alt="AETHAS38 Logo" class="w-24 h-24 mx-auto mb-4 rounded-full border-2 border-aethas-cyan shadow-[0_0_15px_rgba(6,182,212,0.5)] object-cover bg-gray-800"> <img src="/static/logo.png" alt="AETHAS38 Logo" class="w-24 h-24 mx-auto mb-4 rounded-full border-2 border-aethas-cyan shadow-[0_0_15px_rgba(6,182,212,0.5)] object-cover bg-gray-800">
<h1 class="text-3xl font-bold tracking-wider neon-text">AETHAS38</h1> <h1 class="text-3xl font-bold tracking-wider neon-text">AETHAS38</h1>
<p class="text-gray-400 text-sm mt-2">Configuration de l'Orchestrateur Multi-IA</p> <p class="text-gray-400 text-sm mt-2">Configuration Initiale (Super Admin)</p>
</div> </div>
<div class="glass-panel rounded-2xl p-6 sm:p-8"> <div class="glass-panel rounded-2xl p-6 sm:p-8">
<form v-if="!setupComplete" @submit.prevent="submitSetup" class="space-y-8">
<form v-if="!setupComplete" @submit.prevent="submitSetup" class="space-y-5">
<div v-if="errorMessage" class="p-3 bg-red-900/50 border border-red-500 rounded text-red-200 text-sm text-center">{{ errorMessage }}</div> <div v-if="errorMessage" class="p-3 bg-red-900/50 border border-red-500 rounded text-red-200 text-sm text-center">{{ errorMessage }}</div>
<!-- Section 1 : Compte Admin -->
<div> <div>
<label class="block text-sm font-medium text-gray-300 mb-1">Email Administrateur</label> <h2 class="text-lg font-semibold text-white border-b border-gray-700 pb-2 mb-4 flex items-center gap-2">
<input v-model="form.email" type="email" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2.5 focus:outline-none focus:border-aethas-cyan focus:ring-1 focus:ring-aethas-cyan transition-colors" placeholder="admin@domaine.com"> <i data-lucide="user-plus" class="w-5 h-5 text-aethas-cyan"></i> Compte Administrateur
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm text-gray-300 mb-1">Nom d'utilisateur</label>
<input v-model="form.username" type="text" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2 focus:border-aethas-cyan focus:ring-1 focus:ring-aethas-cyan outline-none" placeholder="Admin">
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-300 mb-1">Nom d'utilisateur</label> <label class="block text-sm text-gray-300 mb-1">Mot de passe</label>
<input v-model="form.username" type="text" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2.5 focus:outline-none focus:border-aethas-cyan focus:ring-1 focus:ring-aethas-cyan transition-colors" placeholder="Admin"> <input v-model="form.password" type="password" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2 focus:border-aethas-cyan focus:ring-1 focus:ring-aethas-cyan outline-none" placeholder="••••••••">
</div>
<div class="md:col-span-2">
<label class="block text-sm text-gray-300 mb-1">Email de récupération</label>
<input v-model="form.email" type="email" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2 focus:border-aethas-cyan focus:ring-1 focus:ring-aethas-cyan outline-none" placeholder="admin@domaine.com">
</div>
</div>
</div>
<!-- Section 2 : Serveur Mail -->
<div>
<div class="flex items-center justify-between border-b border-gray-700 pb-2 mb-4">
<h2 class="text-lg font-semibold text-white flex items-center gap-2">
<i data-lucide="mail" class="w-5 h-5 text-aethas-cyan"></i> Serveur Mail (Obligatoire)
</h2>
<button type="button" @click="showWiki = !showWiki" class="text-xs text-aethas-cyan hover:text-white underline">Aide FAI Français</button>
</div>
<div v-if="showWiki" class="mb-4 p-4 bg-gray-800 rounded-lg border border-gray-600 text-sm text-gray-300 space-y-2">
<p><strong>Freebox :</strong> Hôte: smtp.free.fr | Port: 465 (SSL) ou 587 (TLS)</p>
<p><strong>Orange :</strong> Hôte: smtp.orange.fr | Port: 465 (SSL)</p>
<p><strong>SFR :</strong> Hôte: smtp.sfr.fr | Port: 465 (SSL)</p>
<p><strong>Bouygues :</strong> Hôte: smtp.bbox.fr | Port: 587 (TLS)</p>
<p><strong>Gmail :</strong> Hôte: smtp.gmail.com | Port: 465 (Nécessite un "Mot de passe d'application" Google)</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="md:col-span-3">
<label class="block text-sm text-gray-300 mb-1">Hôte SMTP</label>
<input v-model="form.smtp_host" type="text" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2 focus:border-aethas-cyan outline-none" placeholder="ex: smtp.free.fr">
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-300 mb-1">Mot de passe</label> <label class="block text-sm text-gray-300 mb-1">Port</label>
<input v-model="form.password" type="password" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2.5 focus:outline-none focus:border-aethas-cyan focus:ring-1 focus:ring-aethas-cyan transition-colors" placeholder="••••••••"> <input v-model="form.smtp_port" type="number" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2 focus:border-aethas-cyan outline-none" placeholder="465">
</div> </div>
<button type="submit" :disabled="isSubmitting" class="w-full bg-gradient-to-r from-aethas-cyan to-aethas-purple hover:opacity-90 text-white font-semibold py-3 px-4 rounded-lg transition-all shadow-[0_0_10px_rgba(168,85,247,0.4)] flex justify-center items-center mt-6"> <div class="md:col-span-2">
<span v-if="isSubmitting">Création en cours...</span> <label class="block text-sm text-gray-300 mb-1">Utilisateur SMTP</label>
<span v-else>Initialiser le système</span> <input v-model="form.smtp_user" type="text" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2 focus:border-aethas-cyan outline-none">
</div>
<div class="md:col-span-2">
<label class="block text-sm text-gray-300 mb-1">Mot de passe SMTP</label>
<input v-model="form.smtp_password" type="password" required class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-4 py-2 focus:border-aethas-cyan outline-none">
</div>
</div>
</div>
<!-- Section 3 : Clés API IA -->
<div>
<h2 class="text-lg font-semibold text-white border-b border-gray-700 pb-2 mb-4 flex items-center gap-2">
<i data-lucide="cpu" class="w-5 h-5 text-aethas-cyan"></i> Moteurs IA (Optionnel - modifiable plus tard)
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- OpenRouter -->
<div class="space-y-2 bg-gray-800/50 p-3 rounded-lg border border-gray-700">
<h3 class="text-sm font-bold text-white flex items-center justify-between">OpenRouter <a href="https://openrouter.ai/keys" target="_blank" class="text-aethas-cyan hover:underline text-xs"><i data-lucide="external-link" class="w-3 h-3 inline"></i> Récupérer</a></h3>
<p class="text-xs text-gray-400">Générez une clé standard pour les requêtes, et une "Management Key" pour la récupération des crédits.</p>
<input v-model="form.openrouter_api_key" type="password" class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="API Key (sk-or-v1-...)">
<input v-model="form.openrouter_management_key" type="password" class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="Management Key">
</div>
<!-- Gemini -->
<div class="space-y-2 bg-gray-800/50 p-3 rounded-lg border border-gray-700">
<h3 class="text-sm font-bold text-white flex items-center justify-between">Google Gemini <a href="https://aistudio.google.com/app/apikey" target="_blank" class="text-aethas-cyan hover:underline text-xs"><i data-lucide="external-link" class="w-3 h-3 inline"></i> Récupérer</a></h3>
<p class="text-xs text-gray-400">Créez une API Key depuis Google AI Studio (Associez un projet Google Cloud pour lever les quotas gratuits limités).</p>
<input v-model="form.gemini_api_key" type="password" class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="AIzaSy...">
</div>
<!-- Groq -->
<div class="space-y-2 bg-gray-800/50 p-3 rounded-lg border border-gray-700">
<h3 class="text-sm font-bold text-white flex items-center justify-between">Groq <a href="https://console.groq.com/keys" target="_blank" class="text-aethas-cyan hover:underline text-xs"><i data-lucide="external-link" class="w-3 h-3 inline"></i> Récupérer</a></h3>
<p class="text-xs text-gray-400">Créez une clé d'API. L'accès aux modèles Llama est gratuit avec quotas.</p>
<input v-model="form.groq_api_key" type="password" class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="gsk_...">
</div>
<!-- DeepSeek -->
<div class="space-y-2 bg-gray-800/50 p-3 rounded-lg border border-gray-700">
<h3 class="text-sm font-bold text-white flex items-center justify-between">DeepSeek <a href="https://platform.deepseek.com/api_keys" target="_blank" class="text-aethas-cyan hover:underline text-xs"><i data-lucide="external-link" class="w-3 h-3 inline"></i> Récupérer</a></h3>
<p class="text-xs text-gray-400">Créez une clé d'API. Parfait pour le traitement du code de AETHAS38.</p>
<input v-model="form.deepseek_api_key" type="password" class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="sk-...">
</div>
<!-- Mistral -->
<div class="space-y-2 bg-gray-800/50 p-3 rounded-lg border border-gray-700">
<h3 class="text-sm font-bold text-white flex items-center justify-between">Mistral AI <a href="https://console.mistral.ai/api-keys/" target="_blank" class="text-aethas-cyan hover:underline text-xs"><i data-lucide="external-link" class="w-3 h-3 inline"></i> Récupérer</a></h3>
<p class="text-xs text-gray-400">Assurez-vous d'avoir enregistré une méthode de facturation pour lever la période d'essai.</p>
<input v-model="form.mistral_api_key" type="password" class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="Clé API Mistral">
</div>
<!-- HuggingFace -->
<div class="space-y-2 bg-gray-800/50 p-3 rounded-lg border border-gray-700">
<h3 class="text-sm font-bold text-white flex items-center justify-between">HuggingFace <a href="https://huggingface.co/settings/tokens" target="_blank" class="text-aethas-cyan hover:underline text-xs"><i data-lucide="external-link" class="w-3 h-3 inline"></i> Récupérer</a></h3>
<p class="text-xs text-gray-400">Générez un Access Token (Fine-grained) avec les droits de lecture et génération (Inference).</p>
<input v-model="form.huggingface_api_key" type="password" class="w-full bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="hf_...">
</div>
<!-- Cloudflare -->
<div class="space-y-2 bg-gray-800/50 p-3 rounded-lg border border-gray-700 md:col-span-2">
<h3 class="text-sm font-bold text-white flex items-center justify-between">Cloudflare Workers AI <a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank" class="text-aethas-cyan hover:underline text-xs"><i data-lucide="external-link" class="w-3 h-3 inline"></i> Récupérer</a></h3>
<p class="text-xs text-gray-400">Créez un "Custom Token" avec l'autorisation "Workers AI : Read/Write". Votre ID de compte se trouve sur la page d'accueil Workers.</p>
<div class="flex gap-2">
<input v-model="form.cloudflare_account_id" type="text" class="w-1/3 bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="Account ID">
<input v-model="form.cloudflare_api_token" type="password" class="w-2/3 bg-aethas-dark border border-gray-600 rounded-lg px-3 py-1.5 text-sm focus:border-aethas-cyan outline-none" placeholder="API Token">
</div>
</div>
</div>
</div>
<button type="submit" :disabled="isSubmitting" class="w-full bg-gradient-to-r from-aethas-cyan to-aethas-purple hover:opacity-90 text-white font-semibold py-3 px-4 rounded-lg transition-all shadow-[0_0_10px_rgba(168,85,247,0.4)] flex justify-center items-center mt-8">
<span v-if="isSubmitting">Initialisation et Chiffrement...</span>
<span v-else>Terminer l'installation</span>
</button> </button>
</form> </form>
<div v-else class="text-center space-y-6 animate-fade-in"> <div v-else class="text-center space-y-6 animate-fade-in">
<div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-900/50 text-green-400 mb-2"> <div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-green-900/50 text-green-400 mb-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> <i data-lucide="check" class="w-6 h-6"></i>
</div> </div>
<h2 class="text-xl font-bold text-white">Compte créé avec succès</h2> <h2 class="text-xl font-bold text-white">Plateforme Opérationnelle</h2>
<div class="bg-white p-4 rounded-xl inline-block mx-auto min-w-[200px] min-h-[200px] flex items-center justify-center"> <div class="bg-white p-4 rounded-xl inline-block mx-auto min-w-[200px] min-h-[200px] flex items-center justify-center">
<img v-if="qrCodeUrl" :src="qrCodeUrl" alt="QR Code 2FA" class="w-full h-full object-contain"> <img v-if="qrCodeUrl" :src="qrCodeUrl" alt="QR Code 2FA" class="w-full h-full object-contain">
@@ -65,7 +171,7 @@
<div class="text-sm text-gray-300 space-y-2"> <div class="text-sm text-gray-300 space-y-2">
<p>Scannez ce QR Code avec votre application 2FA (Keepassium, Google Authenticator...).</p> <p>Scannez ce QR Code avec votre application 2FA (Keepassium, Google Authenticator...).</p>
<p class="text-xs text-gray-500 break-all bg-aethas-dark p-2 rounded">Secret manuel : {{ totpSecret }}</p> <p class="text-xs text-gray-500 break-all bg-aethas-dark p-2 rounded border border-gray-600">Secret manuel : {{ totpSecret }}</p>
</div> </div>
<a href="/login" class="block w-full border border-aethas-cyan text-aethas-cyan hover:bg-aethas-cyan hover:text-aethas-dark font-semibold py-3 px-4 rounded-lg transition-colors mt-6"> <a href="/login" class="block w-full border border-aethas-cyan text-aethas-cyan hover:bg-aethas-cyan hover:text-aethas-dark font-semibold py-3 px-4 rounded-lg transition-colors mt-6">
@@ -76,16 +182,25 @@
</div> </div>
<script> <script>
const { createApp, ref, reactive } = Vue; const { createApp, ref, reactive, onMounted } = Vue;
createApp({ createApp({
setup() { setup() {
const form = reactive({ email: '', username: '', password: '' }); const form = reactive({
email: '', username: '', password: '',
smtp_host: '', smtp_port: 465, smtp_user: '', smtp_password: '',
openrouter_api_key: '', openrouter_management_key: '',
groq_api_key: '', gemini_api_key: '', deepseek_api_key: '', mistral_api_key: '',
cloudflare_account_id: '', cloudflare_api_token: '', huggingface_api_key: ''
});
const isSubmitting = ref(false); const isSubmitting = ref(false);
const setupComplete = ref(false); const setupComplete = ref(false);
const errorMessage = ref(''); const errorMessage = ref('');
const totpSecret = ref(''); const totpSecret = ref('');
const qrCodeUrl = ref(''); const qrCodeUrl = ref('');
const showWiki = ref(false);
onMounted(() => { lucide.createIcons(); });
const submitSetup = async () => { const submitSetup = async () => {
isSubmitting.value = true; isSubmitting.value = true;
@@ -97,27 +212,21 @@
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form) body: JSON.stringify(form)
}); });
const data = await response.json(); const data = await response.json();
if (!response.ok) throw new Error(data.detail || "Erreur lors de l'installation");
if (!response.ok) {
throw new Error(data.detail || "Erreur lors de l'installation");
}
totpSecret.value = data.totp_secret; totpSecret.value = data.totp_secret;
setupComplete.value = true; setupComplete.value = true;
// CORRECTION ICI : Génération par API externe ultra fiable (plus besoin de librairie)
qrCodeUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(data.totp_uri)}&color=0f172a&bgcolor=ffffff`; qrCodeUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(data.totp_uri)}&color=0f172a&bgcolor=ffffff`;
setTimeout(() => lucide.createIcons(), 100);
} catch (error) { } catch (error) {
errorMessage.value = error.message; errorMessage.value = error.message;
} finally { } finally {
isSubmitting.value = false; isSubmitting.value = false;
} }
}; };
return { form, isSubmitting, setupComplete, errorMessage, totpSecret, qrCodeUrl, showWiki, submitSetup };
return { form, isSubmitting, setupComplete, errorMessage, totpSecret, qrCodeUrl, submitSetup };
} }
}).mount('#app'); }).mount('#app');
</script> </script>