""" PC Monitor - Serveur Flask pour monitoring hardware Supporte plusieurs plugins: LibreHardwareMonitor, HWiNFO, Plexamp, etc. """ from flask import Flask, render_template, jsonify, request import json import os import subprocess from threading import Thread import time # Import du système de plugins from plugins import get_plugin, get_available_plugins, PLUGINS from plugins import get_media_plugin, get_available_media_plugins, MEDIA_PLUGINS app = Flask(__name__) app.config['SECRET_KEY'] = 'votre-cle-secrete-ici' # Fichiers de configuration CONFIG_FILE = 'config.json' # Plugins actifs (initialisés au démarrage) active_plugin = None plexamp_plugin = None # Configuration par défaut DEFAULT_CONFIG = { "active_plugin": "librehardwaremonitor", "theme": "dark", "apps": [], "apps_tab": "", "plugins": { "librehardwaremonitor": { "host": "127.0.0.1", "port": 8085 }, "hwinfo": { "host": "127.0.0.1", "port": 60000 } }, "media": { "plexamp": { "enabled": False, "host": "127.0.0.1", "port": 32400, "token": "" } } } DEFAULT_PLUGIN_CONFIG = { "tabs": [ { "id": "tab1", "name": "Général", "sensors": [] } ] } # ==================== Gestion de la configuration ==================== def load_config(): """Charge la configuration globale""" if os.path.exists(CONFIG_FILE): try: with open(CONFIG_FILE, 'r', encoding='utf-8') as f: config = json.load(f) # Fusionner avec les valeurs par défaut for key, value in DEFAULT_CONFIG.items(): if key not in config: config[key] = value return config except Exception as e: print(f"Erreur chargement config: {e}") return DEFAULT_CONFIG.copy() return DEFAULT_CONFIG.copy() def save_config(config): """Sauvegarde la configuration globale""" with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) def get_plugin_config_file(plugin_id): """Retourne le nom du fichier de config pour un plugin""" return f'config_{plugin_id}.json' def load_plugin_config(plugin_id): """Charge la configuration spécifique à un plugin""" config_file = get_plugin_config_file(plugin_id) if os.path.exists(config_file): try: with open(config_file, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: print(f"Erreur chargement config plugin {plugin_id}: {e}") return DEFAULT_PLUGIN_CONFIG.copy() return DEFAULT_PLUGIN_CONFIG.copy() def save_plugin_config(plugin_id, config): """Sauvegarde la configuration spécifique à un plugin""" config_file = get_plugin_config_file(plugin_id) with open(config_file, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) def get_merged_config(): """ Retourne une config fusionnée (global + plugin actif) Pour compatibilité avec le code existant """ global_config = load_config() plugin_id = global_config.get('active_plugin', 'librehardwaremonitor') plugin_config = load_plugin_config(plugin_id) # Fusionner merged = global_config.copy() merged['tabs'] = plugin_config.get('tabs', []) return merged def save_merged_config(config): """ Sauvegarde une config fusionnée Sépare les données globales et plugin """ global_config = load_config() plugin_id = global_config.get('active_plugin', 'librehardwaremonitor') # Extraire les données plugin plugin_config = { 'tabs': config.get('tabs', []) } # Mettre à jour la config globale global_config['theme'] = config.get('theme', 'dark') global_config['apps'] = config.get('apps', []) global_config['apps_tab'] = config.get('apps_tab', '') # Sauvegarder les deux save_config(global_config) save_plugin_config(plugin_id, plugin_config) # ==================== Gestion du plugin actif ==================== def init_plugin(): """Initialise le plugin actif""" global active_plugin config = load_config() plugin_id = config.get('active_plugin', 'librehardwaremonitor') plugin_config = config.get('plugins', {}).get(plugin_id, {}) try: active_plugin = get_plugin(plugin_id, plugin_config) print(f"Plugin initialisé: {active_plugin.get_name()}") except Exception as e: print(f"Erreur initialisation plugin {plugin_id}: {e}") # Fallback sur LHM active_plugin = get_plugin('librehardwaremonitor', {'host': '127.0.0.1', 'port': 8085}) def switch_plugin(plugin_id): """Change le plugin actif""" global active_plugin config = load_config() plugin_config = config.get('plugins', {}).get(plugin_id, {}) try: active_plugin = get_plugin(plugin_id, plugin_config) config['active_plugin'] = plugin_id save_config(config) print(f"Plugin changé: {active_plugin.get_name()}") return True except Exception as e: print(f"Erreur changement plugin: {e}") return False # ==================== Thread d'historique ==================== def update_history_loop(): """Boucle pour mettre à jour l'historique des capteurs""" global active_plugin while True: if active_plugin: try: data = active_plugin.get_data() if data: sensors = active_plugin.parse_sensors(data) active_plugin.update_history(sensors) except Exception as e: print(f"Erreur update history: {e}") time.sleep(1) # ==================== Routes Pages ==================== @app.route('/') def index(): """Page d'accueil - redirige vers dashboard""" return render_template('dashboard.html') @app.route('/admin') def admin(): """Page d'administration""" return render_template('admin.html') @app.route('/dashboard') def dashboard(): """Page de dashboard""" return render_template('dashboard.html') # ==================== API Plugins ==================== @app.route('/api/plugins') def api_plugins_list(): """Liste des plugins disponibles""" plugins = get_available_plugins() config = load_config() active_id = config.get('active_plugin', 'librehardwaremonitor') # Ajouter l'état actif for plugin in plugins: plugin['active'] = plugin['id'] == active_id plugin['config'] = config.get('plugins', {}).get(plugin['id'], {}) return jsonify(plugins) @app.route('/api/plugins/active') def api_plugins_active(): """Retourne le plugin actif""" global active_plugin if not active_plugin: return jsonify({'error': 'Aucun plugin actif'}), 500 config = load_config() plugin_id = active_plugin.get_id() return jsonify({ 'id': plugin_id, 'name': active_plugin.get_name(), 'config': config.get('plugins', {}).get(plugin_id, {}) }) @app.route('/api/plugins/switch', methods=['POST']) def api_plugins_switch(): """Change le plugin actif""" data = request.get_json() plugin_id = data.get('plugin_id') if not plugin_id or plugin_id not in PLUGINS: return jsonify({'success': False, 'error': 'Plugin invalide'}), 400 if switch_plugin(plugin_id): return jsonify({'success': True, 'message': f'Plugin changé: {plugin_id}'}) else: return jsonify({'success': False, 'error': 'Erreur changement plugin'}), 500 @app.route('/api/plugins/config', methods=['POST']) def api_plugins_config(): """Met à jour la configuration d'un plugin""" data = request.get_json() plugin_id = data.get('plugin_id') plugin_config = data.get('config', {}) if not plugin_id: return jsonify({'success': False, 'error': 'Plugin ID requis'}), 400 config = load_config() if 'plugins' not in config: config['plugins'] = {} config['plugins'][plugin_id] = plugin_config save_config(config) # Si c'est le plugin actif, le réinitialiser if active_plugin and active_plugin.get_id() == plugin_id: init_plugin() return jsonify({'success': True, 'message': 'Configuration mise à jour'}) @app.route('/api/plugins/test', methods=['POST']) def api_plugins_test(): """Teste la connexion d'un plugin""" data = request.get_json() plugin_id = data.get('plugin_id') plugin_config = data.get('config', {}) if not plugin_id or plugin_id not in PLUGINS: return jsonify({'success': False, 'error': 'Plugin invalide'}), 400 try: # Créer une instance temporaire pour tester test_plugin = get_plugin(plugin_id, plugin_config) result = test_plugin.test_connection() return jsonify(result) except Exception as e: return jsonify({'success': False, 'error': str(e)}), 500 # ==================== API Capteurs (compatible) ==================== @app.route('/api/lhm/sensors') def api_lhm_sensors(): """Retourne la liste complète de tous les capteurs""" global active_plugin if not active_plugin: return jsonify({"error": "Aucun plugin actif"}), 500 data = active_plugin.get_data() if not data: return jsonify({"error": "Impossible de récupérer les données"}), 500 sensors = active_plugin.parse_sensors(data) # Grouper par catégorie grouped = {} for sensor in sensors: category = sensor.get('category', 'Autre') if category not in grouped: grouped[category] = [] grouped[category].append(sensor) return jsonify(grouped) @app.route('/api/lhm/hierarchy') def api_lhm_hierarchy(): """Retourne la structure hiérarchique complète des capteurs""" global active_plugin if not active_plugin: return jsonify({"error": "Aucun plugin actif"}), 500 hierarchy = active_plugin.get_hierarchy() if not hierarchy: return jsonify({"error": "Impossible de récupérer les données"}), 500 return jsonify(hierarchy) @app.route('/api/lhm/data') def api_lhm_data(): """Retourne les données actuelles des capteurs sélectionnés""" global active_plugin if not active_plugin: return jsonify({"error": "Aucun plugin actif"}), 500 config = get_merged_config() data = active_plugin.get_data() if not data: return jsonify({"error": "Impossible de récupérer les données"}), 500 all_sensors = active_plugin.parse_sensors(data) # Créer un dictionnaire pour lookup rapide sensor_dict = {s['id']: s for s in all_sensors} # Organiser par onglets result = { "tabs": [], "timestamp": time.time() } for tab in config.get('tabs', []): tab_data = { "id": tab['id'], "name": tab['name'], "sensors": [] } for sensor_config in tab.get('sensors', []): sensor_id = sensor_config['id'] if sensor_id in sensor_dict: sensor = sensor_dict[sensor_id].copy() sensor['show_graph'] = sensor_config.get('show_graph', False) sensor['viz_type'] = sensor_config.get('viz_type', 'line') sensor['card_size'] = sensor_config.get('card_size', 'medium') sensor['font_family'] = sensor_config.get('font_family', 'system') sensor['font_bold'] = sensor_config.get('font_bold', True) sensor['font_size'] = sensor_config.get('font_size', 'small') sensor['hide_value_mobile'] = sensor_config.get('hide_value_mobile', False) sensor['show_type_badge'] = sensor_config.get('show_type_badge', True) sensor['gauge_options'] = sensor_config.get('gauge_options', {}) sensor['chart_options'] = sensor_config.get('chart_options', {}) tab_data['sensors'].append(sensor) result['tabs'].append(tab_data) return jsonify(result) @app.route('/api/lhm/history/') def api_lhm_history(sensor_id): """Retourne l'historique d'un capteur pour le graphique""" global active_plugin if not active_plugin: return jsonify({"error": "Aucun plugin actif"}), 500 # Reconstruire l'ID complet if not sensor_id.startswith('/'): sensor_id = '/' + sensor_id count = request.args.get('count', 60, type=int) history = active_plugin.get_history(sensor_id, count) return jsonify({ "sensor_id": sensor_id, "values": history, "count": len(history) }) # ==================== API Configuration ==================== @app.route('/api/config') def api_config_get(): """Récupère la configuration actuelle (fusionnée)""" config = get_merged_config() return jsonify(config) @app.route('/api/config', methods=['POST']) def api_config_save(): """Sauvegarde la configuration""" try: config = request.get_json() save_merged_config(config) return jsonify({"success": True, "message": "Configuration sauvegardée"}) except Exception as e: return jsonify({"success": False, "error": str(e)}), 500 # ==================== API Applications ==================== @app.route('/api/apps') def api_apps_list(): """Liste des applications configurées""" config = load_config() return jsonify(config.get('apps', [])) @app.route('/api/apps/launch/', methods=['POST']) def api_apps_launch(app_id): """Lance une application""" config = load_config() apps = config.get('apps', []) if app_id < 0 or app_id >= len(apps): return jsonify({"success": False, "error": "Application non trouvée"}), 404 app_config = apps[app_id] try: subprocess.Popen(app_config['path'], shell=True) return jsonify({"success": True, "message": f"{app_config['name']} lancée"}) except Exception as e: return jsonify({"success": False, "error": str(e)}), 500 # ==================== API Plexamp ==================== def init_plexamp(): """Initialise le plugin Plexamp si configuré""" global plexamp_plugin config = load_config() plexamp_config = config.get('media', {}).get('plexamp', {}) if plexamp_config.get('enabled') and plexamp_config.get('token'): try: plexamp_plugin = get_media_plugin('plexamp', plexamp_config) print(f"Plexamp initialisé sur {plexamp_config.get('host')}:{plexamp_config.get('port')}") except Exception as e: print(f"Erreur initialisation Plexamp: {e}") plexamp_plugin = None else: plexamp_plugin = None @app.route('/api/plexamp/status') def api_plexamp_status(): """Retourne l'état actuel de lecture Plexamp""" global plexamp_plugin if not plexamp_plugin: return jsonify({"enabled": False, "error": "Plexamp non configuré"}) now_playing = plexamp_plugin.get_now_playing() if not now_playing: return jsonify({"enabled": True, "playing": False, "state": "unknown"}) # Ajouter l'URL complète de l'artwork if now_playing.get('thumb'): now_playing['artwork_url'] = plexamp_plugin.get_artwork_url(now_playing['thumb']) now_playing['enabled'] = True return jsonify(now_playing) @app.route('/api/plexamp/play', methods=['POST']) def api_plexamp_play(): """Reprend la lecture""" global plexamp_plugin if not plexamp_plugin: return jsonify({"success": False, "error": "Plexamp non configuré"}), 400 success = plexamp_plugin.play() return jsonify({"success": success}) @app.route('/api/plexamp/pause', methods=['POST']) def api_plexamp_pause(): """Met en pause""" global plexamp_plugin if not plexamp_plugin: return jsonify({"success": False, "error": "Plexamp non configuré"}), 400 success = plexamp_plugin.pause() return jsonify({"success": success}) @app.route('/api/plexamp/playpause', methods=['POST']) def api_plexamp_playpause(): """Toggle play/pause""" global plexamp_plugin if not plexamp_plugin: return jsonify({"success": False, "error": "Plexamp non configuré"}), 400 success = plexamp_plugin.play_pause() return jsonify({"success": success}) @app.route('/api/plexamp/next', methods=['POST']) def api_plexamp_next(): """Piste suivante""" global plexamp_plugin if not plexamp_plugin: return jsonify({"success": False, "error": "Plexamp non configuré"}), 400 success = plexamp_plugin.next_track() return jsonify({"success": success}) @app.route('/api/plexamp/prev', methods=['POST']) def api_plexamp_prev(): """Piste précédente""" global plexamp_plugin if not plexamp_plugin: return jsonify({"success": False, "error": "Plexamp non configuré"}), 400 success = plexamp_plugin.prev_track() return jsonify({"success": success}) @app.route('/api/plexamp/seek', methods=['POST']) def api_plexamp_seek(): """Aller à une position""" global plexamp_plugin if not plexamp_plugin: return jsonify({"success": False, "error": "Plexamp non configuré"}), 400 data = request.get_json() position = data.get('position', 0) success = plexamp_plugin.seek(int(position)) return jsonify({"success": success}) @app.route('/api/plexamp/config', methods=['GET']) def api_plexamp_config_get(): """Récupère la config Plexamp""" config = load_config() plexamp_config = config.get('media', {}).get('plexamp', {}) # Ne pas renvoyer le token complet pour la sécurité safe_config = { 'enabled': plexamp_config.get('enabled', False), 'host': plexamp_config.get('host', '127.0.0.1'), 'port': plexamp_config.get('port', 32400), 'has_token': bool(plexamp_config.get('token')) } return jsonify(safe_config) @app.route('/api/plexamp/config', methods=['POST']) def api_plexamp_config_save(): """Sauvegarde la config Plexamp""" global plexamp_plugin data = request.get_json() config = load_config() if 'media' not in config: config['media'] = {} config['media']['plexamp'] = { 'enabled': data.get('enabled', False), 'host': data.get('host', '127.0.0.1'), 'port': data.get('port', 32400), 'token': data.get('token', '') } save_config(config) # Réinitialiser le plugin init_plexamp() return jsonify({"success": True, "message": "Configuration Plexamp sauvegardée"}) @app.route('/api/plexamp/test', methods=['POST']) def api_plexamp_test(): """Teste la connexion Plexamp""" data = request.get_json() try: test_plugin = get_media_plugin('plexamp', data) result = test_plugin.test_connection() return jsonify(result) except Exception as e: return jsonify({"success": False, "error": str(e)}) # ==================== Démarrage ==================== if __name__ == '__main__': # Initialiser les plugins init_plugin() init_plexamp() # Démarrer le thread d'historique history_thread = Thread(target=update_history_loop, daemon=True) history_thread.start() config = load_config() plugin_id = config.get('active_plugin', 'librehardwaremonitor') plugin_config = config.get('plugins', {}).get(plugin_id, {}) plexamp_config = config.get('media', {}).get('plexamp', {}) print("=" * 60) print("PC Monitor - Serveur de monitoring v1.2.2") print("=" * 60) print(f"Plugin actif: {plugin_id}") print(f"Hôte: {plugin_config.get('host', '127.0.0.1')}") print(f"Port: {plugin_config.get('port', 'N/A')}") print("-" * 60) if plexamp_config.get('enabled'): print(f"Plexamp: activé ({plexamp_config.get('host')}:{plexamp_config.get('port')})") else: print("Plexamp: désactivé") print("-" * 60) print(f"Admin: http://localhost:5000/admin") print(f"Dashboard: http://localhost:5000/dashboard") print("=" * 60) app.run(host='0.0.0.0', port=5000, debug=True)