- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
200 lines
7.0 KiB
Python
200 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Script de gestion des migrations de base de données VWB
|
|
|
|
Usage:
|
|
python manage.py init # Initialiser les migrations (une seule fois)
|
|
python manage.py migrate # Créer une nouvelle migration
|
|
python manage.py upgrade # Appliquer les migrations pendantes
|
|
python manage.py downgrade # Annuler la dernière migration
|
|
python manage.py stamp # Marquer une révision comme appliquée
|
|
python manage.py current # Afficher la révision actuelle
|
|
python manage.py history # Afficher l'historique des migrations
|
|
python manage.py backup # Sauvegarder la base de données
|
|
python manage.py status # Afficher le status complet
|
|
|
|
Exemple de workflow de migration:
|
|
1. Modifier les modèles dans db/models.py
|
|
2. python manage.py migrate "Description du changement"
|
|
3. Vérifier le fichier de migration généré dans migrations/versions/
|
|
4. python manage.py upgrade
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from datetime import datetime
|
|
|
|
# Chemin vers le backend
|
|
BACKEND_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
VENV_PYTHON = '/home/dom/ai/rpa_vision_v3/venv_v3/bin/python'
|
|
VENV_FLASK = '/home/dom/ai/rpa_vision_v3/venv_v3/bin/flask'
|
|
|
|
# Variables d'environnement pour Flask
|
|
ENV = os.environ.copy()
|
|
ENV['FLASK_APP'] = 'app.py'
|
|
|
|
|
|
def run_flask_db(args):
|
|
"""Exécute une commande flask db"""
|
|
cmd = [VENV_FLASK, 'db'] + args
|
|
result = subprocess.run(cmd, cwd=BACKEND_DIR, env=ENV, capture_output=True, text=True)
|
|
|
|
# Filtrer les warnings connus
|
|
output = result.stdout + result.stderr
|
|
for line in output.split('\n'):
|
|
if line.strip() and not any(skip in line for skip in [
|
|
'FutureWarning', 'pynvml', 'use_fast', 'Workflow ignoré',
|
|
'ScreenState non disponible', 'Server initialized'
|
|
]):
|
|
print(line)
|
|
|
|
return result.returncode == 0
|
|
|
|
|
|
def find_database():
|
|
"""Trouve le fichier de base de données SQLite"""
|
|
instance_dir = os.path.join(BACKEND_DIR, 'instance')
|
|
if os.path.exists(instance_dir):
|
|
for f in os.listdir(instance_dir):
|
|
if f.endswith('.db') and not f.startswith('.'):
|
|
return os.path.join(instance_dir, f)
|
|
return None
|
|
|
|
|
|
def backup_database():
|
|
"""Sauvegarde la base de données avant une migration"""
|
|
instance_dir = os.path.join(BACKEND_DIR, 'instance')
|
|
db_path = find_database()
|
|
|
|
if db_path and os.path.exists(db_path):
|
|
db_name = os.path.basename(db_path).replace('.db', '')
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
backup_dir = os.path.join(instance_dir, 'backups')
|
|
os.makedirs(backup_dir, exist_ok=True)
|
|
backup_path = os.path.join(backup_dir, f'{db_name}_{timestamp}.db')
|
|
shutil.copy2(db_path, backup_path)
|
|
print(f"✅ Base de données sauvegardée: {backup_path}")
|
|
|
|
# Garder seulement les 10 dernières sauvegardes
|
|
backups = sorted([f for f in os.listdir(backup_dir) if f.endswith('.db')])
|
|
if len(backups) > 10:
|
|
for old_backup in backups[:-10]:
|
|
os.remove(os.path.join(backup_dir, old_backup))
|
|
print(f" 🗑️ Ancienne sauvegarde supprimée: {old_backup}")
|
|
|
|
return backup_path
|
|
else:
|
|
print("⚠️ Pas de base de données existante à sauvegarder")
|
|
return None
|
|
|
|
|
|
def show_status():
|
|
"""Affiche le status complet des migrations"""
|
|
migrations_dir = os.path.join(BACKEND_DIR, 'migrations')
|
|
instance_dir = os.path.join(BACKEND_DIR, 'instance')
|
|
db_path = find_database()
|
|
|
|
print("=" * 50)
|
|
print("Status des Migrations VWB")
|
|
print("=" * 50)
|
|
print(f"Dossier backend: {BACKEND_DIR}")
|
|
print(f"Dossier migrations: {'✅ Existe' if os.path.exists(migrations_dir) else '❌ Non initialisé'}")
|
|
print(f"Base de données: {'✅ ' + db_path if db_path else '❌ Non créée'}")
|
|
|
|
if os.path.exists(migrations_dir):
|
|
versions_dir = os.path.join(migrations_dir, 'versions')
|
|
if os.path.exists(versions_dir):
|
|
migrations = sorted([f for f in os.listdir(versions_dir) if f.endswith('.py') and not f.startswith('__')])
|
|
print(f"\nMigrations disponibles ({len(migrations)}):")
|
|
for m in migrations:
|
|
print(f" 📄 {m}")
|
|
|
|
# Backups
|
|
backup_dir = os.path.join(instance_dir, 'backups')
|
|
if os.path.exists(backup_dir):
|
|
backups = sorted([f for f in os.listdir(backup_dir) if f.endswith('.db')])
|
|
if backups:
|
|
print(f"\nSauvegardes ({len(backups)}):")
|
|
for b in backups[-5:]: # Afficher les 5 dernières
|
|
print(f" 💾 {b}")
|
|
|
|
print("\nRévision actuelle de la base:")
|
|
run_flask_db(['current'])
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
|
|
command = sys.argv[1].lower()
|
|
|
|
if command == 'init':
|
|
print("🔧 Initialisation des migrations...")
|
|
if run_flask_db(['init']):
|
|
print("✅ Migrations initialisées")
|
|
print(" Prochaine étape: python manage.py migrate 'Initial migration'")
|
|
else:
|
|
print("❌ Échec de l'initialisation")
|
|
|
|
elif command == 'migrate':
|
|
message = ' '.join(sys.argv[2:]) if len(sys.argv) > 2 else f"Migration {datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
print(f"📝 Création de la migration: {message}")
|
|
backup_database()
|
|
if run_flask_db(['migrate', '-m', message]):
|
|
print("✅ Migration créée")
|
|
print(" Prochaine étape: python manage.py upgrade")
|
|
else:
|
|
print("❌ Échec de la création de migration")
|
|
|
|
elif command == 'upgrade':
|
|
revision = sys.argv[2] if len(sys.argv) > 2 else 'head'
|
|
print(f"⬆️ Application des migrations jusqu'à: {revision}")
|
|
backup_database()
|
|
if run_flask_db(['upgrade', revision]):
|
|
print("✅ Migrations appliquées")
|
|
else:
|
|
print("❌ Échec de l'upgrade")
|
|
|
|
elif command == 'downgrade':
|
|
revision = sys.argv[2] if len(sys.argv) > 2 else '-1'
|
|
print(f"⬇️ Annulation des migrations: {revision}")
|
|
backup_database()
|
|
if run_flask_db(['downgrade', revision]):
|
|
print("✅ Downgrade effectué")
|
|
else:
|
|
print("❌ Échec du downgrade")
|
|
|
|
elif command == 'stamp':
|
|
revision = sys.argv[2] if len(sys.argv) > 2 else 'head'
|
|
print(f"🏷️ Marquage de la révision: {revision}")
|
|
if run_flask_db(['stamp', revision]):
|
|
print("✅ Révision marquée")
|
|
else:
|
|
print("❌ Échec du stamp")
|
|
|
|
elif command == 'current':
|
|
print("📍 Révision actuelle:")
|
|
run_flask_db(['current'])
|
|
|
|
elif command == 'history':
|
|
print("📜 Historique des migrations:")
|
|
run_flask_db(['history'])
|
|
|
|
elif command == 'backup':
|
|
backup_database()
|
|
|
|
elif command == 'status':
|
|
show_status()
|
|
|
|
else:
|
|
print(f"❌ Commande inconnue: {command}")
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|