v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution
- 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>
This commit is contained in:
271
web_dashboard/README.md
Normal file
271
web_dashboard/README.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Dashboard Web RPA Vision V3
|
||||
|
||||
Interface web pour visualiser les logs et lancer les tests unitaires de RPA Vision V3.
|
||||
|
||||
## 🚀 Lancement Rapide
|
||||
|
||||
```bash
|
||||
cd rpa_vision_v3
|
||||
./web_dashboard/start_dashboard.sh
|
||||
```
|
||||
|
||||
Puis ouvrir dans le navigateur : **http://localhost:5001**
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
|
||||
### 📊 Statut du Système
|
||||
- Nombre total de tests (unitaires, intégration, performance)
|
||||
- État des dépendances (PyTorch, OpenCLIP, FAISS)
|
||||
- Chemins des répertoires
|
||||
|
||||
### 🧪 Tests Unitaires
|
||||
- Liste de tous les tests disponibles
|
||||
- Lancement individuel de chaque test
|
||||
- Lancement groupé par type (unit, integration, performance)
|
||||
- Affichage en temps réel de la sortie
|
||||
|
||||
### 📝 Logs en Temps Réel
|
||||
- Affichage des 50 derniers logs
|
||||
- Rafraîchissement automatique toutes les 5 secondes
|
||||
- Scroll automatique vers les nouveaux logs
|
||||
- Filtrage par fichier de log
|
||||
|
||||
### 📋 Sortie des Tests
|
||||
- Affichage détaillé de la sortie des tests
|
||||
- Code retour et statut (✅ RÉUSSI / ❌ ÉCHOUÉ)
|
||||
- STDOUT et STDERR séparés
|
||||
- Format console avec coloration
|
||||
|
||||
## 🛠️ Installation Manuelle
|
||||
|
||||
Si le script automatique ne fonctionne pas :
|
||||
|
||||
```bash
|
||||
# 1. Activer l'environnement virtuel
|
||||
source venv_v3/bin/activate
|
||||
|
||||
# 2. Installer Flask
|
||||
pip install Flask==3.0.0
|
||||
|
||||
# 3. Lancer le dashboard
|
||||
cd web_dashboard
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
## 📁 Structure
|
||||
|
||||
```
|
||||
web_dashboard/
|
||||
├── app.py # Application Flask
|
||||
├── templates/
|
||||
│ └── index.html # Interface web
|
||||
├── requirements.txt # Dépendances
|
||||
├── start_dashboard.sh # Script de lancement
|
||||
└── README.md # Ce fichier
|
||||
```
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Port
|
||||
|
||||
Par défaut, le dashboard écoute sur le port **5001**. Pour changer :
|
||||
|
||||
```python
|
||||
# Dans app.py, ligne finale
|
||||
app.run(debug=True, host='0.0.0.0', port=5001) # Changer 5001
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
Les logs sont lus depuis `rpa_vision_v3/logs/*.log`. Pour changer :
|
||||
|
||||
```python
|
||||
# Dans app.py
|
||||
LOGS_DIR = BASE_DIR / "logs" # Changer le chemin
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
Les tests sont lus depuis `rpa_vision_v3/tests/`. Structure attendue :
|
||||
|
||||
```
|
||||
tests/
|
||||
├── unit/ # Tests unitaires
|
||||
├── integration/ # Tests d'intégration
|
||||
└── performance/ # Tests de performance
|
||||
```
|
||||
|
||||
## 🎨 Interface
|
||||
|
||||
### Thème
|
||||
|
||||
L'interface utilise un thème moderne avec :
|
||||
- Gradient violet/bleu pour l'en-tête
|
||||
- Cartes blanches avec ombres
|
||||
- Console sombre pour les logs et sorties
|
||||
- Boutons colorés par type de test
|
||||
|
||||
### Responsive
|
||||
|
||||
L'interface s'adapte automatiquement :
|
||||
- Desktop : 2 colonnes
|
||||
- Mobile : 1 colonne
|
||||
|
||||
## 🔄 Auto-Refresh
|
||||
|
||||
- **Logs** : Rafraîchissement automatique toutes les 5 secondes
|
||||
- **Statut système** : Rafraîchissement toutes les 10 secondes
|
||||
- **Tests** : Rafraîchissement manuel uniquement
|
||||
|
||||
## 🐛 Dépannage
|
||||
|
||||
### Erreur "Flask not found"
|
||||
|
||||
```bash
|
||||
pip install Flask==3.0.0
|
||||
```
|
||||
|
||||
### Erreur "Port 5001 already in use"
|
||||
|
||||
Tuer le processus existant :
|
||||
|
||||
```bash
|
||||
lsof -ti:5001 | xargs kill -9
|
||||
```
|
||||
|
||||
Ou changer le port dans `app.py`.
|
||||
|
||||
### Logs vides
|
||||
|
||||
Vérifier que le dossier `logs/` existe :
|
||||
|
||||
```bash
|
||||
mkdir -p rpa_vision_v3/logs
|
||||
```
|
||||
|
||||
### Tests non trouvés
|
||||
|
||||
Vérifier la structure des dossiers :
|
||||
|
||||
```bash
|
||||
ls -la rpa_vision_v3/tests/unit/
|
||||
ls -la rpa_vision_v3/tests/integration/
|
||||
```
|
||||
|
||||
## 📝 API Endpoints
|
||||
|
||||
### GET /api/system/status
|
||||
Retourne le statut du système.
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"dependencies_ok": true,
|
||||
"tests": {
|
||||
"unit": 10,
|
||||
"integration": 5,
|
||||
"performance": 3,
|
||||
"total": 18
|
||||
},
|
||||
"logs_dir": "/path/to/logs",
|
||||
"base_dir": "/path/to/rpa_vision_v3"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/tests
|
||||
Liste tous les tests disponibles.
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"tests": [
|
||||
{
|
||||
"name": "test_fusion_engine",
|
||||
"path": "tests/unit/test_fusion_engine.py",
|
||||
"type": "unit",
|
||||
"status": "unknown"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/tests/run
|
||||
Lance un test spécifique.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"test_path": "tests/unit/test_fusion_engine.py"
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"returncode": 0,
|
||||
"stdout": "...",
|
||||
"stderr": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/tests/run-all
|
||||
Lance tous les tests d'un type.
|
||||
|
||||
**Requête :**
|
||||
```json
|
||||
{
|
||||
"type": "unit" // ou "integration", "performance", "all"
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"returncode": 0,
|
||||
"stdout": "...",
|
||||
"stderr": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/logs
|
||||
Récupère les logs récents.
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"logs": [
|
||||
{
|
||||
"timestamp": "2024-11-24T10:30:00",
|
||||
"file": "app.log",
|
||||
"message": "Test completed successfully"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Utilisation en Production
|
||||
|
||||
Pour un déploiement en production, utiliser **gunicorn** :
|
||||
|
||||
```bash
|
||||
pip install gunicorn
|
||||
gunicorn -w 4 -b 0.0.0.0:5001 app:app
|
||||
```
|
||||
|
||||
## 📄 Licence
|
||||
|
||||
Même licence que RPA Vision V3.
|
||||
|
||||
## 🤝 Contribution
|
||||
|
||||
Pour contribuer :
|
||||
1. Améliorer l'interface (CSS, JavaScript)
|
||||
2. Ajouter des graphiques de métriques
|
||||
3. Ajouter des filtres de logs
|
||||
4. Améliorer la gestion des erreurs
|
||||
|
||||
---
|
||||
|
||||
**Créé pour RPA Vision V3** - Dashboard de monitoring et de tests
|
||||
@@ -1833,6 +1833,234 @@ def api_services_stop_all():
|
||||
return jsonify({"results": results, "timestamp": datetime.now().isoformat()})
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Configuration - Interface de configuration centralisee
|
||||
# =============================================================================
|
||||
|
||||
CONFIG_FILE_PATH = BASE_PATH / "data" / "config" / "system_config.json"
|
||||
|
||||
def load_system_config():
|
||||
"""Charge la configuration systeme depuis le fichier JSON."""
|
||||
try:
|
||||
if CONFIG_FILE_PATH.exists():
|
||||
with open(CONFIG_FILE_PATH, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
api_logger.error(f"Erreur chargement config: {e}")
|
||||
# Config par defaut
|
||||
return {
|
||||
"version": "1.0.0",
|
||||
"services": {},
|
||||
"llm": {"provider": "ollama", "base_url": "http://localhost:11434", "model": "qwen2.5:7b"},
|
||||
"vlm": {"provider": "ollama", "base_url": "http://localhost:11434", "model": "qwen2.5vl:7b"},
|
||||
"detection": {"owl_model": "google/owlv2-base-patch16-ensemble", "confidence_threshold": 0.3},
|
||||
"database": {"type": "sqlite", "path": "data/training/workflows.db"},
|
||||
"security": {"enable_encryption": True, "require_authentication": False}
|
||||
}
|
||||
|
||||
def save_system_config(config):
|
||||
"""Sauvegarde la configuration systeme dans le fichier JSON."""
|
||||
try:
|
||||
CONFIG_FILE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(CONFIG_FILE_PATH, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
return True
|
||||
except Exception as e:
|
||||
api_logger.error(f"Erreur sauvegarde config: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@app.route('/api/config')
|
||||
def get_config():
|
||||
"""Retourne la configuration complete du systeme."""
|
||||
try:
|
||||
config = load_system_config()
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"config": config,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/config', methods=['POST'])
|
||||
def update_config():
|
||||
"""Met a jour la configuration complete du systeme."""
|
||||
try:
|
||||
new_config = request.get_json()
|
||||
if not new_config:
|
||||
return jsonify({"success": False, "error": "Configuration vide"}), 400
|
||||
|
||||
# Garder la version
|
||||
current = load_system_config()
|
||||
new_config['version'] = current.get('version', '1.0.0')
|
||||
|
||||
if save_system_config(new_config):
|
||||
api_logger.info("Configuration systeme mise a jour")
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "Configuration sauvegardee",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
return jsonify({"success": False, "error": "Erreur lors de la sauvegarde"}), 500
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/config/<section>')
|
||||
def get_config_section(section):
|
||||
"""Retourne une section specifique de la configuration."""
|
||||
try:
|
||||
config = load_system_config()
|
||||
if section not in config:
|
||||
return jsonify({"success": False, "error": f"Section '{section}' introuvable"}), 404
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"section": section,
|
||||
"data": config[section],
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/config/<section>', methods=['PUT'])
|
||||
def update_config_section(section):
|
||||
"""Met a jour une section specifique de la configuration."""
|
||||
try:
|
||||
section_data = request.get_json()
|
||||
if not section_data:
|
||||
return jsonify({"success": False, "error": "Donnees vides"}), 400
|
||||
|
||||
config = load_system_config()
|
||||
config[section] = section_data
|
||||
|
||||
if save_system_config(config):
|
||||
api_logger.info(f"Section config '{section}' mise a jour")
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"Section '{section}' sauvegardee",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
return jsonify({"success": False, "error": "Erreur lors de la sauvegarde"}), 500
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/config/test-connection', methods=['POST'])
|
||||
def test_connection():
|
||||
"""Teste la connexion a un service (LLM, base de donnees, etc.)."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
service_type = data.get('type')
|
||||
|
||||
if service_type == 'ollama':
|
||||
# Test connexion Ollama
|
||||
import urllib.request
|
||||
url = data.get('base_url', 'http://localhost:11434') + '/api/tags'
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=5) as response:
|
||||
models = json.loads(response.read().decode())
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "Connexion Ollama OK",
|
||||
"models": [m.get('name') for m in models.get('models', [])]
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": f"Connexion Ollama echouee: {e}"})
|
||||
|
||||
elif service_type == 'database':
|
||||
# Test connexion base de donnees
|
||||
db_path = data.get('path') or 'data/training/workflows.db'
|
||||
full_path = BASE_PATH / db_path
|
||||
if full_path.exists():
|
||||
return jsonify({"success": True, "message": f"Base de donnees accessible: {db_path}"})
|
||||
else:
|
||||
return jsonify({"success": False, "error": f"Fichier introuvable: {full_path}"})
|
||||
|
||||
elif service_type == 'service':
|
||||
# Test connexion a un service HTTP
|
||||
port = data.get('port')
|
||||
host = data.get('host', 'localhost')
|
||||
import socket
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(2)
|
||||
try:
|
||||
s.connect((host, int(port)))
|
||||
return jsonify({"success": True, "message": f"Service {host}:{port} accessible"})
|
||||
except:
|
||||
return jsonify({"success": False, "error": f"Service {host}:{port} inaccessible"})
|
||||
|
||||
return jsonify({"success": False, "error": "Type de test inconnu"})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/config/ollama-models')
|
||||
def get_ollama_models():
|
||||
"""Recupere la liste des modeles Ollama disponibles."""
|
||||
try:
|
||||
config = load_system_config()
|
||||
base_url = config.get('llm', {}).get('base_url', 'http://localhost:11434')
|
||||
|
||||
import urllib.request
|
||||
url = base_url + '/api/tags'
|
||||
with urllib.request.urlopen(url, timeout=5) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
models = [m.get('name') for m in data.get('models', [])]
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"models": models,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e), "models": []}), 500
|
||||
|
||||
|
||||
@app.route('/api/config/export')
|
||||
def export_config():
|
||||
"""Exporte la configuration en JSON."""
|
||||
try:
|
||||
config = load_system_config()
|
||||
return Response(
|
||||
json.dumps(config, indent=2, ensure_ascii=False),
|
||||
mimetype='application/json',
|
||||
headers={'Content-Disposition': 'attachment;filename=rpa_config.json'}
|
||||
)
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/config/import', methods=['POST'])
|
||||
def import_config():
|
||||
"""Importe une configuration depuis un fichier JSON."""
|
||||
try:
|
||||
if 'file' in request.files:
|
||||
file = request.files['file']
|
||||
config = json.load(file)
|
||||
else:
|
||||
config = request.get_json()
|
||||
|
||||
if not config:
|
||||
return jsonify({"success": False, "error": "Configuration vide"}), 400
|
||||
|
||||
if save_system_config(config):
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "Configuration importee avec succes",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
return jsonify({"success": False, "error": "Erreur lors de l'import"}), 500
|
||||
except json.JSONDecodeError:
|
||||
return jsonify({"success": False, "error": "Format JSON invalide"}), 400
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Main
|
||||
# =============================================================================
|
||||
|
||||
3
web_dashboard/requirements.txt
Normal file
3
web_dashboard/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# Requirements pour le Dashboard Web
|
||||
|
||||
Flask>=3.0.0
|
||||
6
web_dashboard/start.sh
Executable file
6
web_dashboard/start.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
# Démarrage du dashboard depuis le bon répertoire
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
source ../venv_v3/bin/activate
|
||||
python app.py
|
||||
61
web_dashboard/start_dashboard.sh
Executable file
61
web_dashboard/start_dashboard.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Script de lancement du dashboard web RPA Vision V3
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ RPA VISION V3 - DASHBOARD WEB ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Couleurs
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Vérifier qu'on est dans le bon répertoire
|
||||
if [ ! -f "web_dashboard/app.py" ]; then
|
||||
echo "❌ Erreur: Exécutez ce script depuis rpa_vision_v3/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Activer l'environnement virtuel
|
||||
if [ -d "../geniusia2/venv" ]; then
|
||||
echo -e "${YELLOW}[1/3]${NC} Activation de l'environnement virtuel..."
|
||||
source ../geniusia2/venv/bin/activate
|
||||
echo -e "${GREEN}✓${NC} Environnement activé"
|
||||
elif [ -d "venv_v3" ]; then
|
||||
echo -e "${YELLOW}[1/3]${NC} Activation de l'environnement virtuel..."
|
||||
source venv_v3/bin/activate
|
||||
echo -e "${GREEN}✓${NC} Environnement activé"
|
||||
else
|
||||
echo "❌ Aucun environnement virtuel trouvé"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Installer Flask si nécessaire
|
||||
echo -e "${YELLOW}[2/3]${NC} Vérification de Flask..."
|
||||
if ! python3 -c "import flask" 2>/dev/null; then
|
||||
echo "Installation de Flask..."
|
||||
pip install -q Flask==3.0.0
|
||||
echo -e "${GREEN}✓${NC} Flask installé"
|
||||
else
|
||||
echo -e "${GREEN}✓${NC} Flask déjà installé"
|
||||
fi
|
||||
|
||||
# Créer le dossier logs s'il n'existe pas
|
||||
mkdir -p logs
|
||||
|
||||
# Lancer le dashboard
|
||||
echo -e "${YELLOW}[3/3]${NC} Lancement du dashboard..."
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Dashboard disponible sur: http://localhost:5000 ║"
|
||||
echo "║ Appuyez sur Ctrl+C pour arrêter ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
cd web_dashboard
|
||||
python3 app.py
|
||||
@@ -64,6 +64,7 @@
|
||||
<div class="tab" onclick="switchTab('system')">⚙️ Système</div>
|
||||
<div class="tab" onclick="switchTab('corrections')">🔧 Corrections</div>
|
||||
<div class="tab" onclick="switchTab('learning')">🧠 Apprentissage</div>
|
||||
<div class="tab" onclick="switchTab('config')">🔧 Configuration</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
@@ -662,10 +663,259 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Configuration -->
|
||||
<div id="tab-config" class="tab-content">
|
||||
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
|
||||
<div>
|
||||
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">🔧</span> Configuration Systeme</h2>
|
||||
<p style="color:#64748b;font-size:13px;">Configurez les services, modeles LLM/VLM, base de donnees et parametres de securite</p>
|
||||
</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<button class="btn btn-success" onclick="saveConfig()">💾 Sauvegarder</button>
|
||||
<button class="btn btn-primary" onclick="refreshConfig()">🔄 Actualiser</button>
|
||||
<button class="btn btn-secondary" onclick="exportConfig()">📥 Exporter</button>
|
||||
<label class="btn btn-secondary" style="cursor:pointer;">
|
||||
📤 Importer
|
||||
<input type="file" id="importConfigFile" accept=".json" style="display:none;" onchange="importConfig(this)">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-2">
|
||||
<!-- Section Services -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">🌐</span> Services & Ports</h2>
|
||||
<div id="configServices" class="config-section">
|
||||
<div class="config-item">
|
||||
<label>VWB Backend</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="cfg_vwb_backend_host" placeholder="localhost" class="config-input" style="flex:2;">
|
||||
<input type="number" id="cfg_vwb_backend_port" placeholder="5000" class="config-input" style="flex:1;">
|
||||
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'vwb_backend')">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>VWB Frontend</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="cfg_vwb_frontend_host" placeholder="localhost" class="config-input" style="flex:2;">
|
||||
<input type="number" id="cfg_vwb_frontend_port" placeholder="3000" class="config-input" style="flex:1;">
|
||||
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'vwb_frontend')">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Agent Chat</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="cfg_agent_chat_host" placeholder="localhost" class="config-input" style="flex:2;">
|
||||
<input type="number" id="cfg_agent_chat_port" placeholder="5002" class="config-input" style="flex:1;">
|
||||
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'agent_chat')">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>API Upload</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="cfg_upload_api_host" placeholder="localhost" class="config-input" style="flex:2;">
|
||||
<input type="number" id="cfg_upload_api_port" placeholder="8000" class="config-input" style="flex:1;">
|
||||
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'upload_api')">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section LLM/VLM -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">🤖</span> Modeles LLM & VLM</h2>
|
||||
<div id="configLLM" class="config-section">
|
||||
<div class="config-item">
|
||||
<label>Provider</label>
|
||||
<select id="cfg_llm_provider" class="config-input">
|
||||
<option value="ollama">Ollama (local)</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="anthropic">Anthropic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>URL Ollama</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="cfg_llm_base_url" placeholder="http://localhost:11434" class="config-input" style="flex:1;">
|
||||
<button class="btn btn-small btn-primary" onclick="testOllamaConnection()">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Modele LLM</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<select id="cfg_llm_model" class="config-input" style="flex:1;">
|
||||
<option value="">Chargement...</option>
|
||||
</select>
|
||||
<button class="btn btn-small btn-secondary" onclick="refreshOllamaModels()">🔄</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Modele VLM</label>
|
||||
<select id="cfg_vlm_model" class="config-input">
|
||||
<option value="">Chargement...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Temperature</label>
|
||||
<input type="range" id="cfg_llm_temperature" min="0" max="2" step="0.1" value="0.7" class="config-input" oninput="document.getElementById('tempValue').textContent = this.value">
|
||||
<span id="tempValue" style="color:#3b82f6;margin-left:10px;">0.7</span>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Max Tokens</label>
|
||||
<input type="number" id="cfg_llm_max_tokens" placeholder="2048" class="config-input" value="2048">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-2">
|
||||
<!-- Section Detection -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">👁️</span> Detection Visuelle</h2>
|
||||
<div id="configDetection" class="config-section">
|
||||
<div class="config-item">
|
||||
<label>Modele OWL</label>
|
||||
<select id="cfg_detection_owl_model" class="config-input">
|
||||
<option value="google/owlv2-base-patch16-ensemble">OWLv2 Base Ensemble</option>
|
||||
<option value="google/owlv2-large-patch14-ensemble">OWLv2 Large Ensemble</option>
|
||||
<option value="google/owlvit-base-patch32">OWLViT Base</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Seuil de confiance</label>
|
||||
<input type="range" id="cfg_detection_confidence" min="0.1" max="0.9" step="0.05" value="0.3" class="config-input" oninput="document.getElementById('confValue').textContent = this.value">
|
||||
<span id="confValue" style="color:#3b82f6;margin-left:10px;">0.3</span>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Seuil NMS</label>
|
||||
<input type="range" id="cfg_detection_nms" min="0.1" max="0.9" step="0.05" value="0.3" class="config-input" oninput="document.getElementById('nmsValue').textContent = this.value">
|
||||
<span id="nmsValue" style="color:#3b82f6;margin-left:10px;">0.3</span>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>
|
||||
<input type="checkbox" id="cfg_detection_use_gpu" checked>
|
||||
Utiliser GPU (CUDA)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Base de donnees -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">💾</span> Base de Donnees</h2>
|
||||
<div id="configDatabase" class="config-section">
|
||||
<div class="config-item">
|
||||
<label>Type</label>
|
||||
<select id="cfg_database_type" class="config-input">
|
||||
<option value="sqlite">SQLite (local)</option>
|
||||
<option value="postgresql">PostgreSQL</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Chemin / URL</label>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="cfg_database_path" placeholder="data/training/workflows.db" class="config-input" style="flex:1;">
|
||||
<button class="btn btn-small btn-primary" onclick="testDatabaseConnection()">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>
|
||||
<input type="checkbox" id="cfg_database_backup_enabled" checked>
|
||||
Backup automatique
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Intervalle backup (heures)</label>
|
||||
<input type="number" id="cfg_database_backup_interval" value="24" class="config-input">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-2">
|
||||
<!-- Section Securite -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">🔒</span> Securite</h2>
|
||||
<div id="configSecurity" class="config-section">
|
||||
<div class="config-item">
|
||||
<label>
|
||||
<input type="checkbox" id="cfg_security_encryption" checked>
|
||||
Chiffrement des donnees
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>
|
||||
<input type="checkbox" id="cfg_security_auth">
|
||||
Authentification requise
|
||||
</label>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Timeout session (minutes)</label>
|
||||
<input type="number" id="cfg_security_timeout" value="60" class="config-input">
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Origines autorisees (CORS)</label>
|
||||
<input type="text" id="cfg_security_origins" placeholder="http://localhost:3000,http://localhost:5001" class="config-input">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Logs -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">📝</span> Logs</h2>
|
||||
<div id="configLogging" class="config-section">
|
||||
<div class="config-item">
|
||||
<label>Niveau de log</label>
|
||||
<select id="cfg_logging_level" class="config-input">
|
||||
<option value="DEBUG">DEBUG</option>
|
||||
<option value="INFO" selected>INFO</option>
|
||||
<option value="WARNING">WARNING</option>
|
||||
<option value="ERROR">ERROR</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Fichier de log</label>
|
||||
<input type="text" id="cfg_logging_file" placeholder="logs/rpa_vision.log" class="config-input">
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Taille max (MB)</label>
|
||||
<input type="number" id="cfg_logging_max_size" value="50" class="config-input">
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<label>Nombre de backups</label>
|
||||
<input type="number" id="cfg_logging_backup_count" value="5" class="config-input">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resultat des tests -->
|
||||
<div class="card" style="margin-top:20px;" id="configTestResults" style="display:none;">
|
||||
<h2><span class="icon">✅</span> Resultats des tests</h2>
|
||||
<div id="testResultsContent" style="padding:15px;background:#0f172a;border-radius:8px;">
|
||||
<p style="color:#64748b;text-align:center;">Cliquez sur "Test" pour verifier les connexions</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.execution-panel { padding: 15px; background: #0f172a; border-radius: 8px; }
|
||||
|
||||
/* Configuration styles */
|
||||
.config-section { display: flex; flex-direction: column; gap: 15px; padding: 10px 0; }
|
||||
.config-item { display: flex; flex-direction: column; gap: 5px; }
|
||||
.config-item label { color: #94a3b8; font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 8px; }
|
||||
.config-item label input[type="checkbox"] { width: 18px; height: 18px; accent-color: #3b82f6; }
|
||||
.config-input { width: 100%; padding: 10px 12px; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #e2e8f0; font-size: 14px; transition: border-color 0.2s; }
|
||||
.config-input:focus { outline: none; border-color: #3b82f6; }
|
||||
.config-input:hover { border-color: #475569; }
|
||||
select.config-input { cursor: pointer; }
|
||||
input[type="range"].config-input { padding: 0; height: 6px; border: none; cursor: pointer; }
|
||||
.exec-idle { color: #64748b; text-align: center; padding: 30px; }
|
||||
.exec-running { color: #22c55e; }
|
||||
.exec-controls { display: flex; flex-direction: column; gap: 15px; }
|
||||
@@ -887,6 +1137,7 @@
|
||||
if (tabName === 'system') refreshSystemInfo();
|
||||
if (tabName === 'corrections') refreshCorrectionPacks();
|
||||
if (tabName === 'learning') refreshLearningStats();
|
||||
if (tabName === 'config') refreshConfig();
|
||||
}
|
||||
|
||||
// Update execution UI
|
||||
@@ -2516,6 +2767,367 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SECTION: Configuration
|
||||
// ============================================================
|
||||
|
||||
let currentConfig = {};
|
||||
|
||||
async function refreshConfig() {
|
||||
try {
|
||||
const data = await fetchJSON('/api/config');
|
||||
if (data.success) {
|
||||
currentConfig = data.config;
|
||||
populateConfigForm(data.config);
|
||||
showNotification('Configuration chargee', 'success');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading config:', e);
|
||||
showNotification('Erreur chargement config: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function populateConfigForm(config) {
|
||||
// Services
|
||||
if (config.services) {
|
||||
for (const [key, svc] of Object.entries(config.services)) {
|
||||
const hostEl = document.getElementById(`cfg_${key}_host`);
|
||||
const portEl = document.getElementById(`cfg_${key}_port`);
|
||||
if (hostEl) hostEl.value = svc.host || 'localhost';
|
||||
if (portEl) portEl.value = svc.port || '';
|
||||
}
|
||||
}
|
||||
|
||||
// LLM
|
||||
if (config.llm) {
|
||||
document.getElementById('cfg_llm_provider').value = config.llm.provider || 'ollama';
|
||||
document.getElementById('cfg_llm_base_url').value = config.llm.base_url || 'http://localhost:11434';
|
||||
document.getElementById('cfg_llm_temperature').value = config.llm.temperature || 0.7;
|
||||
document.getElementById('tempValue').textContent = config.llm.temperature || 0.7;
|
||||
document.getElementById('cfg_llm_max_tokens').value = config.llm.max_tokens || 2048;
|
||||
// Charger les modeles Ollama
|
||||
refreshOllamaModels();
|
||||
}
|
||||
|
||||
// Detection
|
||||
if (config.detection) {
|
||||
document.getElementById('cfg_detection_owl_model').value = config.detection.owl_model || 'google/owlv2-base-patch16-ensemble';
|
||||
document.getElementById('cfg_detection_confidence').value = config.detection.confidence_threshold || 0.3;
|
||||
document.getElementById('confValue').textContent = config.detection.confidence_threshold || 0.3;
|
||||
document.getElementById('cfg_detection_nms').value = config.detection.nms_threshold || 0.3;
|
||||
document.getElementById('nmsValue').textContent = config.detection.nms_threshold || 0.3;
|
||||
document.getElementById('cfg_detection_use_gpu').checked = config.detection.use_gpu !== false;
|
||||
}
|
||||
|
||||
// Database
|
||||
if (config.database) {
|
||||
document.getElementById('cfg_database_type').value = config.database.type || 'sqlite';
|
||||
document.getElementById('cfg_database_path').value = config.database.path || 'data/training/workflows.db';
|
||||
document.getElementById('cfg_database_backup_enabled').checked = config.database.backup_enabled !== false;
|
||||
document.getElementById('cfg_database_backup_interval').value = config.database.backup_interval_hours || 24;
|
||||
}
|
||||
|
||||
// Security
|
||||
if (config.security) {
|
||||
document.getElementById('cfg_security_encryption').checked = config.security.enable_encryption !== false;
|
||||
document.getElementById('cfg_security_auth').checked = config.security.require_authentication === true;
|
||||
document.getElementById('cfg_security_timeout').value = config.security.session_timeout_minutes || 60;
|
||||
document.getElementById('cfg_security_origins').value = (config.security.allowed_origins || []).join(',');
|
||||
}
|
||||
|
||||
// Logging
|
||||
if (config.logging) {
|
||||
document.getElementById('cfg_logging_level').value = config.logging.level || 'INFO';
|
||||
document.getElementById('cfg_logging_file').value = config.logging.file_path || 'logs/rpa_vision.log';
|
||||
document.getElementById('cfg_logging_max_size').value = config.logging.max_size_mb || 50;
|
||||
document.getElementById('cfg_logging_backup_count').value = config.logging.backup_count || 5;
|
||||
}
|
||||
}
|
||||
|
||||
function collectConfigFromForm() {
|
||||
const config = {
|
||||
version: currentConfig.version || '1.0.0',
|
||||
services: {
|
||||
vwb_backend: {
|
||||
host: document.getElementById('cfg_vwb_backend_host').value || 'localhost',
|
||||
port: parseInt(document.getElementById('cfg_vwb_backend_port').value) || 5000,
|
||||
description: 'Visual Workflow Builder - Backend API'
|
||||
},
|
||||
vwb_frontend: {
|
||||
host: document.getElementById('cfg_vwb_frontend_host').value || 'localhost',
|
||||
port: parseInt(document.getElementById('cfg_vwb_frontend_port').value) || 3000,
|
||||
description: 'Visual Workflow Builder - Interface React'
|
||||
},
|
||||
web_dashboard: {
|
||||
host: 'localhost',
|
||||
port: 5001,
|
||||
description: 'Dashboard de monitoring RPA'
|
||||
},
|
||||
agent_chat: {
|
||||
host: document.getElementById('cfg_agent_chat_host').value || 'localhost',
|
||||
port: parseInt(document.getElementById('cfg_agent_chat_port').value) || 5002,
|
||||
description: 'Agent conversationnel RPA'
|
||||
},
|
||||
upload_api: {
|
||||
host: document.getElementById('cfg_upload_api_host').value || 'localhost',
|
||||
port: parseInt(document.getElementById('cfg_upload_api_port').value) || 8000,
|
||||
description: 'API d\'upload de sessions'
|
||||
}
|
||||
},
|
||||
llm: {
|
||||
provider: document.getElementById('cfg_llm_provider').value,
|
||||
base_url: document.getElementById('cfg_llm_base_url').value,
|
||||
model: document.getElementById('cfg_llm_model').value,
|
||||
temperature: parseFloat(document.getElementById('cfg_llm_temperature').value),
|
||||
max_tokens: parseInt(document.getElementById('cfg_llm_max_tokens').value),
|
||||
description: 'Modele LLM pour le parsing de commandes'
|
||||
},
|
||||
vlm: {
|
||||
provider: document.getElementById('cfg_llm_provider').value,
|
||||
base_url: document.getElementById('cfg_llm_base_url').value,
|
||||
model: document.getElementById('cfg_vlm_model').value,
|
||||
description: 'Modele VLM pour l\'analyse visuelle'
|
||||
},
|
||||
detection: {
|
||||
owl_model: document.getElementById('cfg_detection_owl_model').value,
|
||||
confidence_threshold: parseFloat(document.getElementById('cfg_detection_confidence').value),
|
||||
nms_threshold: parseFloat(document.getElementById('cfg_detection_nms').value),
|
||||
use_gpu: document.getElementById('cfg_detection_use_gpu').checked,
|
||||
description: 'Configuration du detecteur visuel OWL-v2'
|
||||
},
|
||||
embedding: currentConfig.embedding || {
|
||||
model: 'clip',
|
||||
dimension: 512,
|
||||
use_gpu: true,
|
||||
cache_size: 10000,
|
||||
description: 'Configuration des embeddings visuels'
|
||||
},
|
||||
database: {
|
||||
type: document.getElementById('cfg_database_type').value,
|
||||
path: document.getElementById('cfg_database_path').value,
|
||||
backup_enabled: document.getElementById('cfg_database_backup_enabled').checked,
|
||||
backup_interval_hours: parseInt(document.getElementById('cfg_database_backup_interval').value),
|
||||
description: 'Base de donnees des workflows'
|
||||
},
|
||||
faiss: currentConfig.faiss || {
|
||||
index_type: 'Flat',
|
||||
use_gpu: true,
|
||||
nprobe: 10,
|
||||
description: 'Configuration de l\'index FAISS'
|
||||
},
|
||||
security: {
|
||||
enable_encryption: document.getElementById('cfg_security_encryption').checked,
|
||||
session_timeout_minutes: parseInt(document.getElementById('cfg_security_timeout').value),
|
||||
require_authentication: document.getElementById('cfg_security_auth').checked,
|
||||
allowed_origins: document.getElementById('cfg_security_origins').value.split(',').map(s => s.trim()).filter(s => s),
|
||||
description: 'Parametres de securite'
|
||||
},
|
||||
logging: {
|
||||
level: document.getElementById('cfg_logging_level').value,
|
||||
file_path: document.getElementById('cfg_logging_file').value,
|
||||
max_size_mb: parseInt(document.getElementById('cfg_logging_max_size').value),
|
||||
backup_count: parseInt(document.getElementById('cfg_logging_backup_count').value),
|
||||
description: 'Configuration des logs'
|
||||
}
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
try {
|
||||
const config = collectConfigFromForm();
|
||||
showNotification('Sauvegarde en cours...', 'info');
|
||||
|
||||
const response = await fetchJSON('/api/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
currentConfig = config;
|
||||
showNotification('Configuration sauvegardee avec succes', 'success');
|
||||
} else {
|
||||
showNotification('Erreur: ' + (response.error || 'Inconnu'), 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Erreur: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshOllamaModels() {
|
||||
try {
|
||||
const data = await fetchJSON('/api/config/ollama-models');
|
||||
const llmSelect = document.getElementById('cfg_llm_model');
|
||||
const vlmSelect = document.getElementById('cfg_vlm_model');
|
||||
|
||||
if (data.success && data.models) {
|
||||
const llmOptions = data.models.map(m => `<option value="${m}" ${m === currentConfig.llm?.model ? 'selected' : ''}>${m}</option>`).join('');
|
||||
const vlmOptions = data.models.map(m => `<option value="${m}" ${m === currentConfig.vlm?.model ? 'selected' : ''}>${m}</option>`).join('');
|
||||
|
||||
llmSelect.innerHTML = llmOptions || '<option value="">Aucun modele</option>';
|
||||
vlmSelect.innerHTML = vlmOptions || '<option value="">Aucun modele</option>';
|
||||
} else {
|
||||
llmSelect.innerHTML = '<option value="">Ollama inaccessible</option>';
|
||||
vlmSelect.innerHTML = '<option value="">Ollama inaccessible</option>';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading Ollama models:', e);
|
||||
document.getElementById('cfg_llm_model').innerHTML = '<option value="">Erreur chargement</option>';
|
||||
document.getElementById('cfg_vlm_model').innerHTML = '<option value="">Erreur chargement</option>';
|
||||
}
|
||||
}
|
||||
|
||||
async function testOllamaConnection() {
|
||||
const baseUrl = document.getElementById('cfg_llm_base_url').value;
|
||||
showNotification('Test connexion Ollama...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetchJSON('/api/config/test-connection', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'ollama', base_url: baseUrl })
|
||||
});
|
||||
|
||||
updateTestResults('Ollama', response.success, response.message || response.error);
|
||||
|
||||
if (response.success) {
|
||||
showNotification('Connexion Ollama OK - ' + (response.models?.length || 0) + ' modeles', 'success');
|
||||
await refreshOllamaModels();
|
||||
} else {
|
||||
showNotification('Erreur Ollama: ' + response.error, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
updateTestResults('Ollama', false, e.message);
|
||||
showNotification('Erreur: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function testDatabaseConnection() {
|
||||
const path = document.getElementById('cfg_database_path').value;
|
||||
showNotification('Test connexion base de donnees...', 'info');
|
||||
|
||||
try {
|
||||
const response = await fetchJSON('/api/config/test-connection', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'database', path: path })
|
||||
});
|
||||
|
||||
updateTestResults('Database', response.success, response.message || response.error);
|
||||
|
||||
if (response.success) {
|
||||
showNotification('Base de donnees accessible', 'success');
|
||||
} else {
|
||||
showNotification('Erreur DB: ' + response.error, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
updateTestResults('Database', false, e.message);
|
||||
showNotification('Erreur: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection(type, serviceKey) {
|
||||
const hostEl = document.getElementById(`cfg_${serviceKey}_host`);
|
||||
const portEl = document.getElementById(`cfg_${serviceKey}_port`);
|
||||
|
||||
if (!hostEl || !portEl) return;
|
||||
|
||||
showNotification(`Test connexion ${serviceKey}...`, 'info');
|
||||
|
||||
try {
|
||||
const response = await fetchJSON('/api/config/test-connection', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
type: 'service',
|
||||
host: hostEl.value,
|
||||
port: portEl.value
|
||||
})
|
||||
});
|
||||
|
||||
updateTestResults(serviceKey, response.success, response.message || response.error);
|
||||
|
||||
if (response.success) {
|
||||
showNotification(`${serviceKey} accessible`, 'success');
|
||||
} else {
|
||||
showNotification(`${serviceKey} inaccessible: ` + response.error, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
updateTestResults(serviceKey, false, e.message);
|
||||
showNotification('Erreur: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function updateTestResults(name, success, message) {
|
||||
const container = document.getElementById('testResultsContent');
|
||||
const existing = container.querySelector(`[data-test="${name}"]`);
|
||||
const html = `
|
||||
<div data-test="${name}" style="display:flex;align-items:center;gap:10px;padding:8px;border-radius:6px;margin-bottom:5px;background:${success ? 'rgba(34,197,94,0.1)' : 'rgba(239,68,68,0.1)'};">
|
||||
<span style="color:${success ? '#22c55e' : '#ef4444'};">${success ? '✅' : '❌'}</span>
|
||||
<span style="color:#e2e8f0;font-weight:500;">${name}</span>
|
||||
<span style="color:#94a3b8;font-size:12px;">${message}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (existing) {
|
||||
existing.outerHTML = html;
|
||||
} else {
|
||||
if (container.querySelector('.loading') || container.textContent.includes('Cliquez')) {
|
||||
container.innerHTML = html;
|
||||
} else {
|
||||
container.innerHTML += html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function exportConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/config/export');
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `rpa_config_${new Date().toISOString().split('T')[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
showNotification('Configuration exportee', 'success');
|
||||
} catch (e) {
|
||||
showNotification('Erreur export: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function importConfig(input) {
|
||||
if (!input.files.length) return;
|
||||
|
||||
const file = input.files[0];
|
||||
showNotification('Import en cours...', 'info');
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await fetch('/api/config/import', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showNotification('Configuration importee avec succes', 'success');
|
||||
await refreshConfig();
|
||||
} else {
|
||||
showNotification('Erreur import: ' + data.error, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Erreur import: ' + e.message, 'error');
|
||||
}
|
||||
|
||||
input.value = ''; // Reset file input
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initOverviewChart();
|
||||
|
||||
Reference in New Issue
Block a user