initial commit

This commit is contained in:
2026-03-24 07:17:48 +01:00
commit 371cbce202
147 changed files with 18244 additions and 0 deletions

687
app.py Normal file
View File

@@ -0,0 +1,687 @@
"""
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/<path:sensor_id>')
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/<int:app_id>', 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)