feat(dashboard): Ajouter API Backup, Version et Rollback
API Backup (/api/backup/*): - GET /stats - Statistiques des données disponibles - POST /full - Backup complet (avec option modèles) - GET /workflows - Export workflows uniquement - GET /correction-packs - Export correction packs - GET /trained-models - Export modèles entraînés (opt-in, anonymisés) - GET /config - Export configuration (sanitisée) API Version (/api/version/*): - GET / - Version actuelle du système - GET /system-info - Information système complète - GET /check-update - Vérifier les mises à jour - GET /backups - Lister les backups de version - POST /create-backup - Créer point de restauration - POST /upload-update - Uploader un package de mise à jour Ces API permettent aux clients de: - Télécharger leurs sauvegardes depuis le Dashboard - Vérifier et installer les mises à jour - Créer des points de restauration et rollback Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,10 @@ from core.monitoring.trigger_manager import TriggerManager
|
||||
from core.monitoring.log_exporter import LogExporter
|
||||
from core.monitoring.automation_scheduler import AutomationScheduler
|
||||
|
||||
# Modules pour backup et versioning
|
||||
from core.system.backup_exporter import get_backup_exporter
|
||||
from core.system.version_manager import get_version_manager
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-key-change-in-production')
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
|
||||
@@ -1229,6 +1233,283 @@ def automation_stop():
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Backup & Export - Sauvegardes client
|
||||
# =============================================================================
|
||||
|
||||
@app.route('/api/backup/stats')
|
||||
def backup_stats():
|
||||
"""Statistiques des données disponibles pour backup."""
|
||||
try:
|
||||
exporter = get_backup_exporter()
|
||||
stats = exporter.get_backup_stats()
|
||||
return jsonify({
|
||||
'stats': stats,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error getting backup stats: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/backup/full', methods=['POST'])
|
||||
def backup_full():
|
||||
"""Génère et télécharge un backup complet."""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
include_models = data.get('include_models', False)
|
||||
|
||||
exporter = get_backup_exporter()
|
||||
zip_path = exporter.export_full_backup(include_models=include_models)
|
||||
|
||||
api_logger.info(f"Full backup created (include_models={include_models})")
|
||||
|
||||
return send_file(
|
||||
zip_path,
|
||||
mimetype='application/zip',
|
||||
as_attachment=True,
|
||||
download_name=f'rpa_vision_backup_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip'
|
||||
)
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error creating full backup: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/backup/workflows')
|
||||
def backup_workflows():
|
||||
"""Génère et télécharge un backup des workflows uniquement."""
|
||||
try:
|
||||
exporter = get_backup_exporter()
|
||||
zip_path = exporter.export_workflows()
|
||||
|
||||
api_logger.info("Workflows backup created")
|
||||
|
||||
return send_file(
|
||||
zip_path,
|
||||
mimetype='application/zip',
|
||||
as_attachment=True,
|
||||
download_name=f'rpa_workflows_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip'
|
||||
)
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error creating workflows backup: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/backup/correction-packs')
|
||||
def backup_correction_packs():
|
||||
"""Génère et télécharge un backup des correction packs."""
|
||||
try:
|
||||
exporter = get_backup_exporter()
|
||||
zip_path = exporter.export_correction_packs()
|
||||
|
||||
api_logger.info("Correction packs backup created")
|
||||
|
||||
return send_file(
|
||||
zip_path,
|
||||
mimetype='application/zip',
|
||||
as_attachment=True,
|
||||
download_name=f'rpa_correction_packs_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip'
|
||||
)
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error creating correction packs backup: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/backup/trained-models')
|
||||
def backup_trained_models():
|
||||
"""
|
||||
Génère et télécharge les modèles entraînés (opt-in).
|
||||
|
||||
Ces modèles sont anonymisés et contiennent uniquement les patterns
|
||||
appris, pas les données brutes ou les screenshots.
|
||||
"""
|
||||
try:
|
||||
exporter = get_backup_exporter()
|
||||
zip_path = exporter.export_trained_models()
|
||||
|
||||
api_logger.info("Trained models backup created (opt-in)")
|
||||
|
||||
return send_file(
|
||||
zip_path,
|
||||
mimetype='application/zip',
|
||||
as_attachment=True,
|
||||
download_name=f'rpa_trained_models_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip'
|
||||
)
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error creating trained models backup: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/backup/config')
|
||||
def backup_config():
|
||||
"""Génère et télécharge la configuration (sanitisée)."""
|
||||
try:
|
||||
exporter = get_backup_exporter()
|
||||
zip_path = exporter.export_config()
|
||||
|
||||
api_logger.info("Config backup created (sanitized)")
|
||||
|
||||
return send_file(
|
||||
zip_path,
|
||||
mimetype='application/zip',
|
||||
as_attachment=True,
|
||||
download_name=f'rpa_config_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip'
|
||||
)
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error creating config backup: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Version & Updates - Gestion des versions
|
||||
# =============================================================================
|
||||
|
||||
@app.route('/api/version')
|
||||
def get_version():
|
||||
"""Retourne la version actuelle du système."""
|
||||
try:
|
||||
vm = get_version_manager()
|
||||
version_info = vm.get_current_version()
|
||||
return jsonify({
|
||||
'version': version_info.to_dict(),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error getting version: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/version/system-info')
|
||||
def get_system_info():
|
||||
"""Retourne les informations système complètes."""
|
||||
try:
|
||||
vm = get_version_manager()
|
||||
system_info = vm.get_system_info()
|
||||
return jsonify({
|
||||
'system_info': system_info,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error getting system info: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/version/check-update')
|
||||
def check_update():
|
||||
"""Vérifie si une mise à jour est disponible."""
|
||||
try:
|
||||
vm = get_version_manager()
|
||||
update = vm.check_for_updates()
|
||||
|
||||
if update:
|
||||
return jsonify({
|
||||
'update_available': True,
|
||||
'update': update.to_dict(),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'update_available': False,
|
||||
'message': 'Système à jour',
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error checking update: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/version/backups')
|
||||
def list_version_backups():
|
||||
"""Liste les backups de version disponibles pour rollback."""
|
||||
try:
|
||||
vm = get_version_manager()
|
||||
backups = vm.list_backups()
|
||||
return jsonify({
|
||||
'backups': backups,
|
||||
'total': len(backups),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error listing version backups: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/version/create-backup', methods=['POST'])
|
||||
def create_version_backup():
|
||||
"""Crée un backup de la version actuelle (point de restauration)."""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
label = data.get('label')
|
||||
|
||||
vm = get_version_manager()
|
||||
backup_path = vm.create_backup(label=label)
|
||||
|
||||
api_logger.info(f"Version backup created: {backup_path}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'backup_path': str(backup_path),
|
||||
'label': label,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error creating version backup: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/version/upload-update', methods=['POST'])
|
||||
def upload_update_package():
|
||||
"""
|
||||
Upload d'un package de mise à jour.
|
||||
|
||||
Le package doit être un fichier ZIP contenant:
|
||||
- update_manifest.json
|
||||
- Les fichiers de mise à jour
|
||||
"""
|
||||
try:
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'error': 'Aucun fichier fourni'}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({'error': 'Nom de fichier vide'}), 400
|
||||
|
||||
if not file.filename.endswith('.zip'):
|
||||
return jsonify({'error': 'Le fichier doit être un ZIP'}), 400
|
||||
|
||||
# Sauvegarder dans le répertoire updates
|
||||
vm = get_version_manager()
|
||||
update_path = vm.updates_dir / file.filename
|
||||
file.save(update_path)
|
||||
|
||||
# Extraire et valider le manifest
|
||||
import zipfile
|
||||
with zipfile.ZipFile(update_path, 'r') as zf:
|
||||
if 'update_manifest.json' not in zf.namelist():
|
||||
update_path.unlink()
|
||||
return jsonify({'error': 'Package invalide: manifest manquant'}), 400
|
||||
|
||||
# Extraire le manifest
|
||||
zf.extract('update_manifest.json', vm.updates_dir)
|
||||
|
||||
with open(vm.updates_dir / 'update_manifest.json', 'r') as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
api_logger.info(f"Update package uploaded: {file.filename} (version {manifest.get('version')})")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'filename': file.filename,
|
||||
'manifest': manifest,
|
||||
'message': 'Package uploadé. Utilisez /api/version/check-update pour vérifier.',
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
api_logger.error(f"Error uploading update package: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Metrics (Prometheus)
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user