initial commit
This commit is contained in:
687
app.py
Normal file
687
app.py
Normal 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)
|
||||||
30
config.json
Normal file
30
config.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"active_plugin": "hwinfo",
|
||||||
|
"theme": "dark",
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"icon": "🚀",
|
||||||
|
"name": "plex",
|
||||||
|
"path": "C:\\Users\\tech\\AppData\\Local\\Programs\\Plexamp\\Plexamp.exe"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"apps_tab": "tab1770970837781",
|
||||||
|
"plugins": {
|
||||||
|
"librehardwaremonitor": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 8085
|
||||||
|
},
|
||||||
|
"hwinfo": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 60000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"plexamp": {
|
||||||
|
"enabled": true,
|
||||||
|
"host": "192.168.1.235",
|
||||||
|
"port": 32400,
|
||||||
|
"token": "ihycwfjqNtqndjWbziko"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
135
config_hwinfo.json
Normal file
135
config_hwinfo.json
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
{
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"id": "tab1",
|
||||||
|
"name": "Général",
|
||||||
|
"sensors": [
|
||||||
|
{
|
||||||
|
"id": "/hwinfo/GPU_0__NVIDIA_GeForce_RTX_3080_Ti__MSI_RTX_3080_Ti_Ventus_3x_OC_Limite_de_performance_-_Thermique",
|
||||||
|
"name": "Limite de performance - Thermique",
|
||||||
|
"show_graph": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "/hwinfo/Windows_Hardware_Errors_(WHEA)_Total_des_erreurs",
|
||||||
|
"name": "Total des erreurs",
|
||||||
|
"show_graph": false,
|
||||||
|
"type": "generic",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "/hwinfo/CPU_0__Intel_Core_i5-14600KF__DTS_CPU_Entier",
|
||||||
|
"name": "CPU Entier",
|
||||||
|
"show_graph": false,
|
||||||
|
"type": "temperature",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "/hwinfo/CPU_0__Intel_Core_i5-14600KF__DTS_Étranglement_thermique_de_paquet_anneau",
|
||||||
|
"name": "Étranglement thermique de paquet/anneau",
|
||||||
|
"show_graph": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "xs",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/hwinfo/GPU_0__NVIDIA_GeForce_RTX_3080_Ti__MSI_RTX_3080_Ti_Ventus_3x_OC_Température_GPU",
|
||||||
|
"name": "Température GPU",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "temperature",
|
||||||
|
"viz_type": "line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "small",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/hwinfo/0_134217729_Mémoire_virtuelle_disponible",
|
||||||
|
"name": "Mémoire virtuelle disponible",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "generic",
|
||||||
|
"viz_type": "line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "tiny",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "xs",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/hwinfo/0_134217730_Charge_de_mémoire_virtuelle",
|
||||||
|
"name": "Charge de mémoire virtuelle",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "xs",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/hwinfo/2_134217740_Étranglement_thermique_de_paquet_anneau",
|
||||||
|
"name": "Étranglement thermique de paquet/anneau",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "generic",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "xs",
|
||||||
|
"gauge_options": {
|
||||||
|
"show_value": true,
|
||||||
|
"size": "xs",
|
||||||
|
"style": "semicircle"
|
||||||
|
},
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/hwinfo/22_16777216_Diode_thermique_GPU",
|
||||||
|
"name": "Diode thermique GPU",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "temperature",
|
||||||
|
"viz_type": "gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "xs",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/hwinfo/2_16777470_CPU_Entier",
|
||||||
|
"name": "CPU Entier",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "temperature",
|
||||||
|
"viz_type": "line"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tab1770970837781",
|
||||||
|
"name": "apps",
|
||||||
|
"sensors": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
240
config_librehardwaremonitor.json
Normal file
240
config_librehardwaremonitor.json
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
{
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"id": "tab1",
|
||||||
|
"name": "Général",
|
||||||
|
"sensors": [
|
||||||
|
{
|
||||||
|
"card_size": "tiny",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "tiny",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/lpc/nct6687d/0/fan/0",
|
||||||
|
"name": "CPU Fan",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "fan",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "tiny",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "tiny",
|
||||||
|
"gauge_options": {
|
||||||
|
"show_value": false,
|
||||||
|
"size": "xs",
|
||||||
|
"style": "semicircle"
|
||||||
|
},
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/intelcpu/0/power/0",
|
||||||
|
"name": "CPU Package",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "power",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "xs",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/intelcpu/0/load/0",
|
||||||
|
"name": "CPU Total",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "system",
|
||||||
|
"font_size": "xs",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/ram/load/0",
|
||||||
|
"name": "Memory",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "system",
|
||||||
|
"font_size": "small",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/vram/data/2",
|
||||||
|
"name": "Memory Used",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "memory",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": true,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "large",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/gpu-nvidia/0/power/0",
|
||||||
|
"name": "GPU Package",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "power",
|
||||||
|
"viz_type": "line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "small",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "xs",
|
||||||
|
"gauge_options": {
|
||||||
|
"show_value": false,
|
||||||
|
"size": "xs",
|
||||||
|
"style": "arc"
|
||||||
|
},
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/gpu-nvidia/0/clock/4",
|
||||||
|
"name": "GPU Memory",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "frequency",
|
||||||
|
"viz_type": "line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "system",
|
||||||
|
"font_size": "xs",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/lpc/nct6687d/0/temperature/0",
|
||||||
|
"name": "CPU",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "temperature",
|
||||||
|
"viz_type": "line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "small",
|
||||||
|
"gauge_options": {
|
||||||
|
"show_value": false,
|
||||||
|
"size": "tiny",
|
||||||
|
"style": "arc"
|
||||||
|
},
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/gpu-nvidia/0/clock/0",
|
||||||
|
"name": "GPU Core",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "frequency",
|
||||||
|
"viz_type": "gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "roboto",
|
||||||
|
"font_size": "xs",
|
||||||
|
"gauge_options": {
|
||||||
|
"show_value": false,
|
||||||
|
"size": "tiny",
|
||||||
|
"style": "arc"
|
||||||
|
},
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/gpu-nvidia/0/temperature/0",
|
||||||
|
"name": "GPU Core",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "temperature",
|
||||||
|
"viz_type": "gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "tiny",
|
||||||
|
"font_bold": true,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "tiny",
|
||||||
|
"gauge_options": {
|
||||||
|
"show_value": false,
|
||||||
|
"size": "xs",
|
||||||
|
"style": "semicircle"
|
||||||
|
},
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/gpu-nvidia/0/smalldata/1",
|
||||||
|
"name": "GPU Memory Used",
|
||||||
|
"show_graph": false,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "memory",
|
||||||
|
"viz_type": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "tiny",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/nic/%7BD712B4D3-745F-4969-BC51-728433D0605D%7D/load/1",
|
||||||
|
"name": "Network Utilization",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "small",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/nic/%7BD712B4D3-745F-4969-BC51-728433D0605D%7D/throughput/7",
|
||||||
|
"name": "Upload Speed",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card_size": "xs",
|
||||||
|
"chart_options": {
|
||||||
|
"height": "xs"
|
||||||
|
},
|
||||||
|
"font_bold": false,
|
||||||
|
"font_family": "opensans",
|
||||||
|
"font_size": "small",
|
||||||
|
"hide_value_mobile": false,
|
||||||
|
"id": "/nic/%7BD712B4D3-745F-4969-BC51-728433D0605D%7D/throughput/8",
|
||||||
|
"name": "Download Speed",
|
||||||
|
"show_graph": true,
|
||||||
|
"show_type_badge": false,
|
||||||
|
"type": "percentage",
|
||||||
|
"viz_type": "line"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tab2",
|
||||||
|
"name": "Application",
|
||||||
|
"sensors": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
113
diagnostic.py
Normal file
113
diagnostic.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
"""
|
||||||
|
Script de diagnostic pour tester la connexion à LibreHardwareMonitor
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
import urllib.request
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("DIAGNOSTIC DE CONNEXION À LIBREHARDWAREMONITOR")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Test 1: Résolution DNS de localhost
|
||||||
|
print("\n[1] Résolution DNS de 'localhost'...")
|
||||||
|
try:
|
||||||
|
ip = socket.gethostbyname('localhost')
|
||||||
|
print(f" ✅ localhost résolu en: {ip}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
# Test 2: Vérifier toutes les adresses pour localhost
|
||||||
|
print("\n[2] Toutes les adresses pour 'localhost'...")
|
||||||
|
try:
|
||||||
|
infos = socket.getaddrinfo('localhost', 8085)
|
||||||
|
for info in infos:
|
||||||
|
print(f" → {info[0].name}: {info[4]}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
# Test 3: Connexion socket directe sur 127.0.0.1
|
||||||
|
print("\n[3] Test connexion socket sur 127.0.0.1:8085...")
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(5)
|
||||||
|
result = sock.connect_ex(('127.0.0.1', 8085))
|
||||||
|
sock.close()
|
||||||
|
if result == 0:
|
||||||
|
print(" ✅ Port 8085 ouvert sur 127.0.0.1")
|
||||||
|
else:
|
||||||
|
print(f" ❌ Port fermé (code: {result})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
# Test 4: Connexion socket directe sur localhost
|
||||||
|
print("\n[4] Test connexion socket sur localhost:8085...")
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(5)
|
||||||
|
result = sock.connect_ex(('localhost', 8085))
|
||||||
|
sock.close()
|
||||||
|
if result == 0:
|
||||||
|
print(" ✅ Port 8085 ouvert sur localhost")
|
||||||
|
else:
|
||||||
|
print(f" ❌ Port fermé (code: {result})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
# Test 5: Requête HTTP avec urllib (sans requests)
|
||||||
|
print("\n[5] Test HTTP avec urllib sur 127.0.0.1...")
|
||||||
|
try:
|
||||||
|
req = urllib.request.urlopen('http://127.0.0.1:8085/data.json', timeout=5)
|
||||||
|
data = req.read()
|
||||||
|
print(f" ✅ Réponse reçue! ({len(data)} bytes)")
|
||||||
|
print(f" Début: {data[:100]}...")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
# Test 6: Requête HTTP avec urllib sur localhost
|
||||||
|
print("\n[6] Test HTTP avec urllib sur localhost...")
|
||||||
|
try:
|
||||||
|
req = urllib.request.urlopen('http://localhost:8085/data.json', timeout=5)
|
||||||
|
data = req.read()
|
||||||
|
print(f" ✅ Réponse reçue! ({len(data)} bytes)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
# Test 7: Requête avec requests (si disponible)
|
||||||
|
print("\n[7] Test HTTP avec requests sur 127.0.0.1...")
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
resp = requests.get('http://127.0.0.1:8085/data.json', timeout=5)
|
||||||
|
print(f" ✅ Réponse reçue! Status: {resp.status_code}, {len(resp.content)} bytes")
|
||||||
|
except ImportError:
|
||||||
|
print(" ⚠️ Module requests non installé")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
# Test 8: Requête avec requests sur localhost
|
||||||
|
print("\n[8] Test HTTP avec requests sur localhost...")
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
resp = requests.get('http://localhost:8085/data.json', timeout=5)
|
||||||
|
print(f" ✅ Réponse reçue! Status: {resp.status_code}")
|
||||||
|
except ImportError:
|
||||||
|
print(" ⚠️ Module requests non installé")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Erreur: {e}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("RECOMMANDATION:")
|
||||||
|
print("=" * 60)
|
||||||
|
print("""
|
||||||
|
Si les tests 3-4 échouent mais que le navigateur fonctionne:
|
||||||
|
→ Vérifiez le pare-feu Windows (autorisez Python)
|
||||||
|
|
||||||
|
Si les tests avec 127.0.0.1 réussissent mais localhost échoue:
|
||||||
|
→ Problème IPv6, utilisez 127.0.0.1 dans la config
|
||||||
|
|
||||||
|
Si tout échoue sauf le navigateur:
|
||||||
|
→ Un proxy ou antivirus bloque peut-être Python
|
||||||
|
→ Essayez de désactiver temporairement l'antivirus
|
||||||
|
""")
|
||||||
|
|
||||||
|
input("\nAppuyez sur Entrée pour fermer...")
|
||||||
BIN
output/Pc-monitor/Pc-monitor.exe
Normal file
BIN
output/Pc-monitor/Pc-monitor.exe
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2010 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: MarkupSafe
|
||||||
|
Version: 3.0.2
|
||||||
|
Summary: Safely add untrusted strings to HTML/XML markup.
|
||||||
|
Maintainer-email: Pallets <contact@palletsprojects.com>
|
||||||
|
License: Copyright 2010 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Project-URL: Donate, https://palletsprojects.com/donate
|
||||||
|
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
|
||||||
|
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
|
||||||
|
Project-URL: Source, https://github.com/pallets/markupsafe/
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Environment :: Web Environment
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||||
|
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
Requires-Python: >=3.9
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
License-File: LICENSE.txt
|
||||||
|
|
||||||
|
# MarkupSafe
|
||||||
|
|
||||||
|
MarkupSafe implements a text object that escapes characters so it is
|
||||||
|
safe to use in HTML and XML. Characters that have special meanings are
|
||||||
|
replaced so that they display as the actual characters. This mitigates
|
||||||
|
injection attacks, meaning untrusted user input can safely be displayed
|
||||||
|
on a page.
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```pycon
|
||||||
|
>>> from markupsafe import Markup, escape
|
||||||
|
|
||||||
|
>>> # escape replaces special characters and wraps in Markup
|
||||||
|
>>> escape("<script>alert(document.cookie);</script>")
|
||||||
|
Markup('<script>alert(document.cookie);</script>')
|
||||||
|
|
||||||
|
>>> # wrap in Markup to mark text "safe" and prevent escaping
|
||||||
|
>>> Markup("<strong>Hello</strong>")
|
||||||
|
Markup('<strong>hello</strong>')
|
||||||
|
|
||||||
|
>>> escape(Markup("<strong>Hello</strong>"))
|
||||||
|
Markup('<strong>hello</strong>')
|
||||||
|
|
||||||
|
>>> # Markup is a str subclass
|
||||||
|
>>> # methods and operators escape their arguments
|
||||||
|
>>> template = Markup("Hello <em>{name}</em>")
|
||||||
|
>>> template.format(name='"World"')
|
||||||
|
Markup('Hello <em>"World"</em>')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
The Pallets organization develops and supports MarkupSafe and other
|
||||||
|
popular packages. In order to grow the community of contributors and
|
||||||
|
users, and allow the maintainers to devote more time to the projects,
|
||||||
|
[please donate today][].
|
||||||
|
|
||||||
|
[please donate today]: https://palletsprojects.com/donate
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
MarkupSafe-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
MarkupSafe-3.0.2.dist-info/LICENSE.txt,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
|
||||||
|
MarkupSafe-3.0.2.dist-info/METADATA,sha256=nhoabjupBG41j_JxPCJ3ylgrZ6Fx8oMCFbiLF9Kafqc,4067
|
||||||
|
MarkupSafe-3.0.2.dist-info/RECORD,,
|
||||||
|
MarkupSafe-3.0.2.dist-info/WHEEL,sha256=-v_yZ08fSknsoT62oIKG9wp1eCBV9_ao2rO4BeIReTY,101
|
||||||
|
MarkupSafe-3.0.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
|
||||||
|
markupsafe/__init__.py,sha256=pREerPwvinB62tNCMOwqxBS2YHV6R52Wcq1d-rB4Z5o,13609
|
||||||
|
markupsafe/__pycache__/__init__.cpython-313.pyc,,
|
||||||
|
markupsafe/__pycache__/_native.cpython-313.pyc,,
|
||||||
|
markupsafe/_native.py,sha256=2ptkJ40yCcp9kq3L1NqpgjfpZB-obniYKFFKUOkHh4Q,218
|
||||||
|
markupsafe/_speedups.c,sha256=SglUjn40ti9YgQAO--OgkSyv9tXq9vvaHyVhQows4Ok,4353
|
||||||
|
markupsafe/_speedups.cp313-win_amd64.pyd,sha256=7MA12j0aUiSeNpFy-98h_pPSqgCpLeRacgp3I-j00Yo,13312
|
||||||
|
markupsafe/_speedups.pyi,sha256=LSDmXYOefH4HVpAXuL8sl7AttLw0oXh1njVoVZp2wqQ,42
|
||||||
|
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: setuptools (75.2.0)
|
||||||
|
Root-Is-Purelib: false
|
||||||
|
Tag: cp313-cp313-win_amd64
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
markupsafe
|
||||||
BIN
output/Pc-monitor/_internal/VCRUNTIME140.dll
Normal file
BIN
output/Pc-monitor/_internal/VCRUNTIME140.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/VCRUNTIME140_1.dll
Normal file
BIN
output/Pc-monitor/_internal/VCRUNTIME140_1.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_asyncio.pyd
Normal file
BIN
output/Pc-monitor/_internal/_asyncio.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_bz2.pyd
Normal file
BIN
output/Pc-monitor/_internal/_bz2.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_ctypes.pyd
Normal file
BIN
output/Pc-monitor/_internal/_ctypes.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_decimal.pyd
Normal file
BIN
output/Pc-monitor/_internal/_decimal.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_hashlib.pyd
Normal file
BIN
output/Pc-monitor/_internal/_hashlib.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_lzma.pyd
Normal file
BIN
output/Pc-monitor/_internal/_lzma.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_multiprocessing.pyd
Normal file
BIN
output/Pc-monitor/_internal/_multiprocessing.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_overlapped.pyd
Normal file
BIN
output/Pc-monitor/_internal/_overlapped.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_queue.pyd
Normal file
BIN
output/Pc-monitor/_internal/_queue.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_socket.pyd
Normal file
BIN
output/Pc-monitor/_internal/_socket.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_ssl.pyd
Normal file
BIN
output/Pc-monitor/_internal/_ssl.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_uuid.pyd
Normal file
BIN
output/Pc-monitor/_internal/_uuid.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/_wmi.pyd
Normal file
BIN
output/Pc-monitor/_internal/_wmi.pyd
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-console-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-console-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-datetime-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-datetime-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-debug-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-debug-l1-1-0.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-fibers-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-fibers-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-file-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-file-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-file-l1-2-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-file-l1-2-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-file-l2-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-file-l2-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-handle-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-handle-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-heap-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-heap-l1-1-0.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-memory-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-memory-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-namedpipe-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-namedpipe-l1-1-0.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-profile-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-profile-l1-1-0.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-string-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-string-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-synch-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-synch-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-synch-l1-2-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-synch-l1-2-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-sysinfo-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-sysinfo-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-timezone-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-timezone-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-core-util-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-core-util-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-conio-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-conio-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-convert-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-convert-l1-1-0.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-filesystem-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-filesystem-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-heap-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-heap-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-locale-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-locale-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-math-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-math-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-process-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-process-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-runtime-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-runtime-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-stdio-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-stdio-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-string-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-string-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-time-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-time-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/api-ms-win-crt-utility-l1-1-0.dll
Normal file
BIN
output/Pc-monitor/_internal/api-ms-win-crt-utility-l1-1-0.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/base_library.zip
Normal file
BIN
output/Pc-monitor/_internal/base_library.zip
Normal file
Binary file not shown.
4635
output/Pc-monitor/_internal/certifi/cacert.pem
Normal file
4635
output/Pc-monitor/_internal/certifi/cacert.pem
Normal file
File diff suppressed because it is too large
Load Diff
0
output/Pc-monitor/_internal/certifi/py.typed
Normal file
0
output/Pc-monitor/_internal/certifi/py.typed
Normal file
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
82
output/Pc-monitor/_internal/click-8.2.1.dist-info/METADATA
Normal file
82
output/Pc-monitor/_internal/click-8.2.1.dist-info/METADATA
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: click
|
||||||
|
Version: 8.2.1
|
||||||
|
Summary: Composable command line interface toolkit
|
||||||
|
Maintainer-email: Pallets <contact@palletsprojects.com>
|
||||||
|
Requires-Python: >=3.10
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
License-Expression: BSD-3-Clause
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
License-File: LICENSE.txt
|
||||||
|
Requires-Dist: colorama; platform_system == 'Windows'
|
||||||
|
Project-URL: Changes, https://click.palletsprojects.com/page/changes/
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Project-URL: Documentation, https://click.palletsprojects.com/
|
||||||
|
Project-URL: Donate, https://palletsprojects.com/donate
|
||||||
|
Project-URL: Source, https://github.com/pallets/click/
|
||||||
|
|
||||||
|
# $ click_
|
||||||
|
|
||||||
|
Click is a Python package for creating beautiful command line interfaces
|
||||||
|
in a composable way with as little code as necessary. It's the "Command
|
||||||
|
Line Interface Creation Kit". It's highly configurable but comes with
|
||||||
|
sensible defaults out of the box.
|
||||||
|
|
||||||
|
It aims to make the process of writing command line tools quick and fun
|
||||||
|
while also preventing any frustration caused by the inability to
|
||||||
|
implement an intended CLI API.
|
||||||
|
|
||||||
|
Click in three points:
|
||||||
|
|
||||||
|
- Arbitrary nesting of commands
|
||||||
|
- Automatic help page generation
|
||||||
|
- Supports lazy loading of subcommands at runtime
|
||||||
|
|
||||||
|
|
||||||
|
## A Simple Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import click
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("--count", default=1, help="Number of greetings.")
|
||||||
|
@click.option("--name", prompt="Your name", help="The person to greet.")
|
||||||
|
def hello(count, name):
|
||||||
|
"""Simple program that greets NAME for a total of COUNT times."""
|
||||||
|
for _ in range(count):
|
||||||
|
click.echo(f"Hello, {name}!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
hello()
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python hello.py --count=3
|
||||||
|
Your name: Click
|
||||||
|
Hello, Click!
|
||||||
|
Hello, Click!
|
||||||
|
Hello, Click!
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
The Pallets organization develops and supports Click and other popular
|
||||||
|
packages. In order to grow the community of contributors and users, and
|
||||||
|
allow the maintainers to devote more time to the projects, [please
|
||||||
|
donate today][].
|
||||||
|
|
||||||
|
[please donate today]: https://palletsprojects.com/donate
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See our [detailed contributing documentation][contrib] for many ways to
|
||||||
|
contribute, including reporting issues, requesting features, asking or answering
|
||||||
|
questions, and making PRs.
|
||||||
|
|
||||||
|
[contrib]: https://palletsprojects.com/contributing/
|
||||||
|
|
||||||
38
output/Pc-monitor/_internal/click-8.2.1.dist-info/RECORD
Normal file
38
output/Pc-monitor/_internal/click-8.2.1.dist-info/RECORD
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
click-8.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
click-8.2.1.dist-info/METADATA,sha256=dI1MbhHTLoKD2tNCCGnx9rK2gok23HDNylFeLKdLSik,2471
|
||||||
|
click-8.2.1.dist-info/RECORD,,
|
||||||
|
click-8.2.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
||||||
|
click-8.2.1.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
||||||
|
click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473
|
||||||
|
click/__pycache__/__init__.cpython-313.pyc,,
|
||||||
|
click/__pycache__/_compat.cpython-313.pyc,,
|
||||||
|
click/__pycache__/_termui_impl.cpython-313.pyc,,
|
||||||
|
click/__pycache__/_textwrap.cpython-313.pyc,,
|
||||||
|
click/__pycache__/_winconsole.cpython-313.pyc,,
|
||||||
|
click/__pycache__/core.cpython-313.pyc,,
|
||||||
|
click/__pycache__/decorators.cpython-313.pyc,,
|
||||||
|
click/__pycache__/exceptions.cpython-313.pyc,,
|
||||||
|
click/__pycache__/formatting.cpython-313.pyc,,
|
||||||
|
click/__pycache__/globals.cpython-313.pyc,,
|
||||||
|
click/__pycache__/parser.cpython-313.pyc,,
|
||||||
|
click/__pycache__/shell_completion.cpython-313.pyc,,
|
||||||
|
click/__pycache__/termui.cpython-313.pyc,,
|
||||||
|
click/__pycache__/testing.cpython-313.pyc,,
|
||||||
|
click/__pycache__/types.cpython-313.pyc,,
|
||||||
|
click/__pycache__/utils.cpython-313.pyc,,
|
||||||
|
click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693
|
||||||
|
click/_termui_impl.py,sha256=ASXhLi9IQIc0Js9KQSS-3-SLZcPet3VqysBf9WgbbpI,26712
|
||||||
|
click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400
|
||||||
|
click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465
|
||||||
|
click/core.py,sha256=gUhpNS9cFBGdEXXdisGVG-eRvGf49RTyFagxulqwdFw,117343
|
||||||
|
click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461
|
||||||
|
click/exceptions.py,sha256=1rdtXgHJ1b3OjGkN-UpXB9t_HCBihJvh_DtpmLmwn9s,9891
|
||||||
|
click/formatting.py,sha256=Bhqx4QXdKQ9W4WKknIwj5KPKFmtduGOuGq1yw_THLZ8,9726
|
||||||
|
click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923
|
||||||
|
click/parser.py,sha256=nU1Ah2p11q29ul1vNdU9swPo_PUuKrxU6YXToi71q1c,18979
|
||||||
|
click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
click/shell_completion.py,sha256=CQSGdjgun4ORbOZrXP0CVhEtPx4knsufOkRsDiK64cM,19857
|
||||||
|
click/termui.py,sha256=vAYrKC2a7f_NfEIhAThEVYfa__ib5XQbTSCGtJlABRA,30847
|
||||||
|
click/testing.py,sha256=2eLdAaCJCGToP5Tw-XN8JjrDb3wbJIfARxg3d0crW5M,18702
|
||||||
|
click/types.py,sha256=KBTRxN28cR1VZ5mb9iJX98MQSw_p9SGzljqfEI8z5Tw,38389
|
||||||
|
click/utils.py,sha256=b1Mm-usEDBHtEwcPltPIn3zSK4nw2KTp5GC7_oSTlLo,20245
|
||||||
4
output/Pc-monitor/_internal/click-8.2.1.dist-info/WHEEL
Normal file
4
output/Pc-monitor/_internal/click-8.2.1.dist-info/WHEEL
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: flit 3.12.0
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2014 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2010 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
116
output/Pc-monitor/_internal/flask-3.0.0.dist-info/METADATA
Normal file
116
output/Pc-monitor/_internal/flask-3.0.0.dist-info/METADATA
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: Flask
|
||||||
|
Version: 3.0.0
|
||||||
|
Summary: A simple framework for building complex web applications.
|
||||||
|
Maintainer-email: Pallets <contact@palletsprojects.com>
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Description-Content-Type: text/x-rst
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Environment :: Web Environment
|
||||||
|
Classifier: Framework :: Flask
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
||||||
|
Requires-Dist: Werkzeug>=3.0.0
|
||||||
|
Requires-Dist: Jinja2>=3.1.2
|
||||||
|
Requires-Dist: itsdangerous>=2.1.2
|
||||||
|
Requires-Dist: click>=8.1.3
|
||||||
|
Requires-Dist: blinker>=1.6.2
|
||||||
|
Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10'
|
||||||
|
Requires-Dist: asgiref>=3.2 ; extra == "async"
|
||||||
|
Requires-Dist: python-dotenv ; extra == "dotenv"
|
||||||
|
Project-URL: Changes, https://flask.palletsprojects.com/changes/
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Project-URL: Documentation, https://flask.palletsprojects.com/
|
||||||
|
Project-URL: Donate, https://palletsprojects.com/donate
|
||||||
|
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
|
||||||
|
Project-URL: Source Code, https://github.com/pallets/flask/
|
||||||
|
Provides-Extra: async
|
||||||
|
Provides-Extra: dotenv
|
||||||
|
|
||||||
|
Flask
|
||||||
|
=====
|
||||||
|
|
||||||
|
Flask is a lightweight `WSGI`_ web application framework. It is designed
|
||||||
|
to make getting started quick and easy, with the ability to scale up to
|
||||||
|
complex applications. It began as a simple wrapper around `Werkzeug`_
|
||||||
|
and `Jinja`_ and has become one of the most popular Python web
|
||||||
|
application frameworks.
|
||||||
|
|
||||||
|
Flask offers suggestions, but doesn't enforce any dependencies or
|
||||||
|
project layout. It is up to the developer to choose the tools and
|
||||||
|
libraries they want to use. There are many extensions provided by the
|
||||||
|
community that make adding new functionality easy.
|
||||||
|
|
||||||
|
.. _WSGI: https://wsgi.readthedocs.io/
|
||||||
|
.. _Werkzeug: https://werkzeug.palletsprojects.com/
|
||||||
|
.. _Jinja: https://jinja.palletsprojects.com/
|
||||||
|
|
||||||
|
|
||||||
|
Installing
|
||||||
|
----------
|
||||||
|
|
||||||
|
Install and update using `pip`_:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ pip install -U Flask
|
||||||
|
|
||||||
|
.. _pip: https://pip.pypa.io/en/stable/getting-started/
|
||||||
|
|
||||||
|
|
||||||
|
A Simple Example
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# save this as app.py
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def hello():
|
||||||
|
return "Hello, World!"
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ flask run
|
||||||
|
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||||
|
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
For guidance on setting up a development environment and how to make a
|
||||||
|
contribution to Flask, see the `contributing guidelines`_.
|
||||||
|
|
||||||
|
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
|
||||||
|
|
||||||
|
|
||||||
|
Donate
|
||||||
|
------
|
||||||
|
|
||||||
|
The Pallets organization develops and supports Flask and the libraries
|
||||||
|
it uses. In order to grow the community of contributors and users, and
|
||||||
|
allow the maintainers to devote more time to the projects, `please
|
||||||
|
donate today`_.
|
||||||
|
|
||||||
|
.. _please donate today: https://palletsprojects.com/donate
|
||||||
|
|
||||||
|
|
||||||
|
Links
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Documentation: https://flask.palletsprojects.com/
|
||||||
|
- Changes: https://flask.palletsprojects.com/changes/
|
||||||
|
- PyPI Releases: https://pypi.org/project/Flask/
|
||||||
|
- Source Code: https://github.com/pallets/flask/
|
||||||
|
- Issue Tracker: https://github.com/pallets/flask/issues/
|
||||||
|
- Chat: https://discord.gg/pallets
|
||||||
|
|
||||||
58
output/Pc-monitor/_internal/flask-3.0.0.dist-info/RECORD
Normal file
58
output/Pc-monitor/_internal/flask-3.0.0.dist-info/RECORD
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
../../Scripts/flask.exe,sha256=ND9f0z8XN9fqR_S6jQEm-9esibL5LMMT-NWKLivt0pI,108381
|
||||||
|
flask-3.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
flask-3.0.0.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
|
||||||
|
flask-3.0.0.dist-info/METADATA,sha256=02XP69VTiwn5blcRgHcyuSQ2cLTuJFV8FXw2x4QnxKo,3588
|
||||||
|
flask-3.0.0.dist-info/RECORD,,
|
||||||
|
flask-3.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
flask-3.0.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
||||||
|
flask-3.0.0.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40
|
||||||
|
flask/__init__.py,sha256=6xMqdVA0FIQ2U1KVaGX3lzNCdXPzoHPaa0hvQCNcfSk,2625
|
||||||
|
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
|
||||||
|
flask/__pycache__/__init__.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/__main__.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/app.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/blueprints.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/cli.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/config.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/ctx.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/debughelpers.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/globals.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/helpers.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/logging.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/sessions.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/signals.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/templating.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/testing.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/typing.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/views.cpython-313.pyc,,
|
||||||
|
flask/__pycache__/wrappers.cpython-313.pyc,,
|
||||||
|
flask/app.py,sha256=voUkc9xk9B039AhVrU21GDpsQ6wqrr-NobqLx8fURfQ,59201
|
||||||
|
flask/blueprints.py,sha256=zO8bLO9Xy1aVD92bDmzihutjVEXf8xdDaVfiy7c--Ck,3129
|
||||||
|
flask/cli.py,sha256=PDwZCfPagi5GUzb-D6dEN7y20gWiVAg3ejRnxBKNHPA,33821
|
||||||
|
flask/config.py,sha256=YZSZ-xpFj1iW1B1Kj1iDhpc5s7pHncloiRLqXhsU7Hs,12856
|
||||||
|
flask/ctx.py,sha256=x2kGzUXtPzVyi2YSKrU_PV1AvtxTmh2iRdriJRTSPGM,14841
|
||||||
|
flask/debughelpers.py,sha256=WKzD2FNTSimNSwCJVLr9_fFo1f2VlTWB5EZ6lmR5bwE,5548
|
||||||
|
flask/globals.py,sha256=XdQZmStBmPIs8t93tjx6pO7Bm3gobAaONWkFcUHaGas,1713
|
||||||
|
flask/helpers.py,sha256=ynEoMB7fdF5Y1P-ngxMjZDZWfrJ4St-9OGZZsTcUwx8,22992
|
||||||
|
flask/json/__init__.py,sha256=pdtpoK2b0b1u7Sxbx3feM7VWhsI20l1yGAvbYWxaxvc,5572
|
||||||
|
flask/json/__pycache__/__init__.cpython-313.pyc,,
|
||||||
|
flask/json/__pycache__/provider.cpython-313.pyc,,
|
||||||
|
flask/json/__pycache__/tag.cpython-313.pyc,,
|
||||||
|
flask/json/provider.py,sha256=VBKSK75t3OsTvZ3N10B3Fsu7-NdpfrGYcl41goQJ3q8,7640
|
||||||
|
flask/json/tag.py,sha256=ihb7QWrNEr0YC3KD4TolZbftgSPCuLk7FAvK49huYC0,8871
|
||||||
|
flask/logging.py,sha256=VcdJgW4Axm5l_-7vXLQjRTL0eckaMks7Ya_HaoDm0wg,2330
|
||||||
|
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
flask/sansio/README.md,sha256=-0X1tECnilmz1cogx-YhNw5d7guK7GKrq_DEV2OzlU0,228
|
||||||
|
flask/sansio/__pycache__/app.cpython-313.pyc,,
|
||||||
|
flask/sansio/__pycache__/blueprints.cpython-313.pyc,,
|
||||||
|
flask/sansio/__pycache__/scaffold.cpython-313.pyc,,
|
||||||
|
flask/sansio/app.py,sha256=nZWCFMOW8qK95Ck9UvDzxvswQr-coLJhIFaa_OVobCc,37977
|
||||||
|
flask/sansio/blueprints.py,sha256=caskVI1Zf3mM5msevK5-tWy3VqX_A8mlB0KGNyRx5_0,24319
|
||||||
|
flask/sansio/scaffold.py,sha256=-Cus0cVS4PmLof4qLvfjSQzk4AKsLqPR6LBpv6ALw3Y,30580
|
||||||
|
flask/sessions.py,sha256=rFH2QKXG24dEazkKGxAHqUpAUh_30hDHrddhVYgAcY0,14169
|
||||||
|
flask/signals.py,sha256=V7lMUww7CqgJ2ThUBn1PiatZtQanOyt7OZpu2GZI-34,750
|
||||||
|
flask/templating.py,sha256=EtL8CE5z2aefdR1I-TWYVNg0cSuXBqz_lvOGKeggktk,7538
|
||||||
|
flask/testing.py,sha256=h7AinggrMgGzKlDN66VfB0JjWW4Z1U_OD6FyjqBNiYM,10017
|
||||||
|
flask/typing.py,sha256=2pGlhSaZqJVJOoh-QdH-20QVzl2r-zLXyP8otXfCCs4,3156
|
||||||
|
flask/views.py,sha256=V5hOGZLx0Bn99QGcM6mh5x_uM-MypVT0-RysEFU84jc,6789
|
||||||
|
flask/wrappers.py,sha256=PhMp3teK3SnEmIdog59cO_DHiZ9Btn0qI1EifrTdwP8,5709
|
||||||
4
output/Pc-monitor/_internal/flask-3.0.0.dist-info/WHEEL
Normal file
4
output/Pc-monitor/_internal/flask-3.0.0.dist-info/WHEEL
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: flit 3.9.0
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[console_scripts]
|
||||||
|
flask=flask.cli:main
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2011 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: itsdangerous
|
||||||
|
Version: 2.2.0
|
||||||
|
Summary: Safely pass data to untrusted environments and back.
|
||||||
|
Maintainer-email: Pallets <contact@palletsprojects.com>
|
||||||
|
Requires-Python: >=3.8
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Typing :: Typed
|
||||||
|
Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/
|
||||||
|
Project-URL: Chat, https://discord.gg/pallets
|
||||||
|
Project-URL: Documentation, https://itsdangerous.palletsprojects.com/
|
||||||
|
Project-URL: Donate, https://palletsprojects.com/donate
|
||||||
|
Project-URL: Source, https://github.com/pallets/itsdangerous/
|
||||||
|
|
||||||
|
# ItsDangerous
|
||||||
|
|
||||||
|
... so better sign this
|
||||||
|
|
||||||
|
Various helpers to pass data to untrusted environments and to get it
|
||||||
|
back safe and sound. Data is cryptographically signed to ensure that a
|
||||||
|
token has not been tampered with.
|
||||||
|
|
||||||
|
It's possible to customize how data is serialized. Data is compressed as
|
||||||
|
needed. A timestamp can be added and verified automatically while
|
||||||
|
loading a token.
|
||||||
|
|
||||||
|
|
||||||
|
## A Simple Example
|
||||||
|
|
||||||
|
Here's how you could generate a token for transmitting a user's id and
|
||||||
|
name between web requests.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from itsdangerous import URLSafeSerializer
|
||||||
|
auth_s = URLSafeSerializer("secret key", "auth")
|
||||||
|
token = auth_s.dumps({"id": 5, "name": "itsdangerous"})
|
||||||
|
|
||||||
|
print(token)
|
||||||
|
# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg
|
||||||
|
|
||||||
|
data = auth_s.loads(token)
|
||||||
|
print(data["name"])
|
||||||
|
# itsdangerous
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
The Pallets organization develops and supports ItsDangerous and other
|
||||||
|
popular packages. In order to grow the community of contributors and
|
||||||
|
users, and allow the maintainers to devote more time to the projects,
|
||||||
|
[please donate today][].
|
||||||
|
|
||||||
|
[please donate today]: https://palletsprojects.com/donate
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
itsdangerous-2.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
itsdangerous-2.2.0.dist-info/LICENSE.txt,sha256=Y68JiRtr6K0aQlLtQ68PTvun_JSOIoNnvtfzxa4LCdc,1475
|
||||||
|
itsdangerous-2.2.0.dist-info/METADATA,sha256=0rk0-1ZwihuU5DnwJVwPWoEI4yWOyCexih3JyZHblhE,1924
|
||||||
|
itsdangerous-2.2.0.dist-info/RECORD,,
|
||||||
|
itsdangerous-2.2.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
||||||
|
itsdangerous/__init__.py,sha256=4SK75sCe29xbRgQE1ZQtMHnKUuZYAf3bSpZOrff1IAY,1427
|
||||||
|
itsdangerous/__pycache__/__init__.cpython-313.pyc,,
|
||||||
|
itsdangerous/__pycache__/_json.cpython-313.pyc,,
|
||||||
|
itsdangerous/__pycache__/encoding.cpython-313.pyc,,
|
||||||
|
itsdangerous/__pycache__/exc.cpython-313.pyc,,
|
||||||
|
itsdangerous/__pycache__/serializer.cpython-313.pyc,,
|
||||||
|
itsdangerous/__pycache__/signer.cpython-313.pyc,,
|
||||||
|
itsdangerous/__pycache__/timed.cpython-313.pyc,,
|
||||||
|
itsdangerous/__pycache__/url_safe.cpython-313.pyc,,
|
||||||
|
itsdangerous/_json.py,sha256=wPQGmge2yZ9328EHKF6gadGeyGYCJQKxtU-iLKE6UnA,473
|
||||||
|
itsdangerous/encoding.py,sha256=wwTz5q_3zLcaAdunk6_vSoStwGqYWe307Zl_U87aRFM,1409
|
||||||
|
itsdangerous/exc.py,sha256=Rr3exo0MRFEcPZltwecyK16VV1bE2K9_F1-d-ljcUn4,3201
|
||||||
|
itsdangerous/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
itsdangerous/serializer.py,sha256=PmdwADLqkSyQLZ0jOKAgDsAW4k_H0TlA71Ei3z0C5aI,15601
|
||||||
|
itsdangerous/signer.py,sha256=YO0CV7NBvHA6j549REHJFUjUojw2pHqwcUpQnU7yNYQ,9647
|
||||||
|
itsdangerous/timed.py,sha256=6RvDMqNumGMxf0-HlpaZdN9PUQQmRvrQGplKhxuivUs,8083
|
||||||
|
itsdangerous/url_safe.py,sha256=az4e5fXi_vs-YbWj8YZwn4wiVKfeD--GEKRT5Ueu4P4,2505
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: flit 3.9.0
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
BIN
output/Pc-monitor/_internal/libcrypto-3.dll
Normal file
BIN
output/Pc-monitor/_internal/libcrypto-3.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/libffi-8.dll
Normal file
BIN
output/Pc-monitor/_internal/libffi-8.dll
Normal file
Binary file not shown.
BIN
output/Pc-monitor/_internal/libssl-3.dll
Normal file
BIN
output/Pc-monitor/_internal/libssl-3.dll
Normal file
Binary file not shown.
Binary file not shown.
35
output/Pc-monitor/_internal/plugins/__init__.py
Normal file
35
output/Pc-monitor/_internal/plugins/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Plugins pour PC Monitor
|
||||||
|
from .base import BasePlugin
|
||||||
|
from .librehardwaremonitor import LibreHardwareMonitorPlugin
|
||||||
|
from .hwinfo import HWiNFOPlugin
|
||||||
|
|
||||||
|
# Registry des plugins disponibles
|
||||||
|
PLUGINS = {
|
||||||
|
'librehardwaremonitor': LibreHardwareMonitorPlugin,
|
||||||
|
'hwinfo': HWiNFOPlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_plugin(name: str, config: dict) -> BasePlugin:
|
||||||
|
"""Retourne une instance du plugin demandé"""
|
||||||
|
if name not in PLUGINS:
|
||||||
|
raise ValueError(f"Plugin inconnu: {name}")
|
||||||
|
return PLUGINS[name](config)
|
||||||
|
|
||||||
|
def get_available_plugins() -> list:
|
||||||
|
"""Retourne la liste des plugins disponibles"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': 'librehardwaremonitor',
|
||||||
|
'name': 'LibreHardwareMonitor',
|
||||||
|
'description': 'Open source, API REST intégrée',
|
||||||
|
'default_port': 8085,
|
||||||
|
'website': 'https://github.com/LibreHardwareMonitor/LibreHardwareMonitor'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'hwinfo',
|
||||||
|
'name': 'HWiNFO + RemoteHWInfo',
|
||||||
|
'description': 'HWiNFO avec RemoteHWInfo (github.com/Demion/remotehwinfo)',
|
||||||
|
'default_port': 60000,
|
||||||
|
'website': 'https://github.com/Demion/remotehwinfo'
|
||||||
|
}
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
151
output/Pc-monitor/_internal/plugins/base.py
Normal file
151
output/Pc-monitor/_internal/plugins/base.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
"""
|
||||||
|
Classe de base pour tous les plugins de monitoring
|
||||||
|
"""
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
class BasePlugin(ABC):
|
||||||
|
"""Classe abstraite définissant l'interface des plugins"""
|
||||||
|
|
||||||
|
def __init__(self, config: dict):
|
||||||
|
"""
|
||||||
|
Initialise le plugin avec sa configuration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Configuration du plugin (host, port, etc.)
|
||||||
|
"""
|
||||||
|
self.host = config.get('host', '127.0.0.1')
|
||||||
|
self.port = config.get('port', self.get_default_port())
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_id(self) -> str:
|
||||||
|
"""Retourne l'identifiant unique du plugin"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_name(self) -> str:
|
||||||
|
"""Retourne le nom affichable du plugin"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_default_port(self) -> int:
|
||||||
|
"""Retourne le port par défaut"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def test_connection(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Teste la connexion au logiciel de monitoring
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict avec 'success' (bool), 'message' (str), et optionnellement 'version'
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_data(self) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
Récupère les données brutes du logiciel de monitoring
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Données brutes ou None si erreur
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_hierarchy(self) -> List[dict]:
|
||||||
|
"""
|
||||||
|
Récupère la hiérarchie des capteurs (pour l'admin)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste de hardware avec leurs capteurs
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def parse_sensors(self, data: dict) -> List[dict]:
|
||||||
|
"""
|
||||||
|
Parse les données brutes en liste standardisée de capteurs
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Données brutes du logiciel
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste de capteurs au format standardisé:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'id': str, # Identifiant unique
|
||||||
|
'name': str, # Nom du capteur
|
||||||
|
'value': str, # Valeur formatée (ex: "45 °C")
|
||||||
|
'raw_value': float, # Valeur numérique
|
||||||
|
'type': str, # Type (temperature, load, voltage, etc.)
|
||||||
|
'unit': str, # Unité (°C, %, MHz, etc.)
|
||||||
|
'hardware': str, # Nom du hardware parent
|
||||||
|
'hardware_type': str # Type de hardware (CPU, GPU, etc.)
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_base_url(self) -> str:
|
||||||
|
"""Retourne l'URL de base pour les requêtes"""
|
||||||
|
return f"http://{self.host}:{self.port}"
|
||||||
|
|
||||||
|
def get_sensor_type(self, unit: str, name: str = '') -> str:
|
||||||
|
"""
|
||||||
|
Détermine le type de capteur à partir de l'unité et du nom
|
||||||
|
|
||||||
|
Args:
|
||||||
|
unit: Unité du capteur
|
||||||
|
name: Nom du capteur (pour affiner la détection)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type standardisé du capteur
|
||||||
|
"""
|
||||||
|
unit_lower = unit.lower() if unit else ''
|
||||||
|
name_lower = name.lower() if name else ''
|
||||||
|
|
||||||
|
# Détection par unité
|
||||||
|
if '°c' in unit_lower or '°f' in unit_lower:
|
||||||
|
return 'temperature'
|
||||||
|
elif '%' in unit_lower:
|
||||||
|
if 'load' in name_lower or 'usage' in name_lower:
|
||||||
|
return 'load'
|
||||||
|
return 'percentage'
|
||||||
|
elif 'mhz' in unit_lower or 'ghz' in unit_lower:
|
||||||
|
return 'frequency'
|
||||||
|
elif 'rpm' in unit_lower:
|
||||||
|
return 'fan'
|
||||||
|
elif 'w' in unit_lower and 'wh' not in unit_lower:
|
||||||
|
return 'power'
|
||||||
|
elif 'v' in unit_lower and 'mv' not in unit_lower:
|
||||||
|
return 'voltage'
|
||||||
|
elif 'mv' in unit_lower:
|
||||||
|
return 'voltage'
|
||||||
|
elif 'mb' in unit_lower or 'gb' in unit_lower or 'kb' in unit_lower:
|
||||||
|
return 'data'
|
||||||
|
elif 'mb/s' in unit_lower or 'kb/s' in unit_lower or 'gb/s' in unit_lower:
|
||||||
|
return 'throughput'
|
||||||
|
elif 'a' in unit_lower:
|
||||||
|
return 'current'
|
||||||
|
elif 'wh' in unit_lower:
|
||||||
|
return 'energy'
|
||||||
|
|
||||||
|
# Détection par nom si unité non reconnue
|
||||||
|
if 'temp' in name_lower or 'temperature' in name_lower:
|
||||||
|
return 'temperature'
|
||||||
|
elif 'fan' in name_lower:
|
||||||
|
return 'fan'
|
||||||
|
elif 'clock' in name_lower or 'freq' in name_lower:
|
||||||
|
return 'frequency'
|
||||||
|
elif 'load' in name_lower or 'usage' in name_lower:
|
||||||
|
return 'load'
|
||||||
|
elif 'power' in name_lower:
|
||||||
|
return 'power'
|
||||||
|
elif 'voltage' in name_lower or 'vcore' in name_lower:
|
||||||
|
return 'voltage'
|
||||||
|
|
||||||
|
return 'generic'
|
||||||
274
output/Pc-monitor/_internal/plugins/hwinfo.py
Normal file
274
output/Pc-monitor/_internal/plugins/hwinfo.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
"""
|
||||||
|
Plugin pour HWiNFO via RemoteHWInfo (Demion)
|
||||||
|
https://github.com/Demion/remotehwinfo
|
||||||
|
|
||||||
|
Format JSON RemoteHWInfo:
|
||||||
|
{
|
||||||
|
"hwinfo": {
|
||||||
|
"sensors": [
|
||||||
|
{"entryIndex": 0, "sensorNameUser": "System", ...},
|
||||||
|
{"entryIndex": 1, "sensorNameUser": "CPU [#0]: Intel Core i7", ...}
|
||||||
|
],
|
||||||
|
"readings": [
|
||||||
|
{"sensorIndex": 0, "labelUser": "Memory Load", "value": 45.5, "unit": "%", ...},
|
||||||
|
{"sensorIndex": 1, "labelUser": "CPU Temp", "value": 65.0, "unit": "°C", ...}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
from .base import BasePlugin
|
||||||
|
|
||||||
|
|
||||||
|
class HWiNFOPlugin(BasePlugin):
|
||||||
|
"""Plugin pour HWiNFO via RemoteHWInfo"""
|
||||||
|
|
||||||
|
def __init__(self, config: dict):
|
||||||
|
super().__init__(config)
|
||||||
|
self.history = {}
|
||||||
|
self.max_history = 120
|
||||||
|
|
||||||
|
def get_id(self) -> str:
|
||||||
|
return 'hwinfo'
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return 'HWiNFO'
|
||||||
|
|
||||||
|
def get_default_port(self) -> int:
|
||||||
|
return 60000
|
||||||
|
|
||||||
|
def get_base_url(self) -> str:
|
||||||
|
"""Retourne l'URL de l'API RemoteHWInfo"""
|
||||||
|
return f"http://{self.host}:{self.port}/json.json"
|
||||||
|
|
||||||
|
def test_connection(self) -> Dict[str, Any]:
|
||||||
|
"""Teste la connexion à RemoteHWInfo"""
|
||||||
|
try:
|
||||||
|
url = self.get_base_url()
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Vérifier le format RemoteHWInfo
|
||||||
|
if 'hwinfo' not in data:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': 'Format invalide. Vérifiez que RemoteHWInfo est lancé.'
|
||||||
|
}
|
||||||
|
|
||||||
|
hwinfo = data['hwinfo']
|
||||||
|
sensor_count = len(hwinfo.get('sensors', []))
|
||||||
|
reading_count = len(hwinfo.get('readings', []))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': f'Connecté - {sensor_count} capteurs, {reading_count} lectures',
|
||||||
|
'version': 'RemoteHWInfo',
|
||||||
|
'sensor_count': reading_count
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f'Impossible de se connecter à {self.get_base_url()}. Vérifiez que RemoteHWInfo est lancé.'
|
||||||
|
}
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': 'Timeout - Le serveur ne répond pas'
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f'Erreur: {str(e)}'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_data(self) -> Optional[Dict]:
|
||||||
|
"""Récupère les données JSON depuis RemoteHWInfo"""
|
||||||
|
try:
|
||||||
|
url = self.get_base_url()
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if 'hwinfo' in data:
|
||||||
|
return data['hwinfo']
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur HWiNFO get_data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_hierarchy(self) -> List[dict]:
|
||||||
|
"""Récupère la hiérarchie des capteurs pour l'admin"""
|
||||||
|
data = self.get_data()
|
||||||
|
if not data:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return self._build_hierarchy(data)
|
||||||
|
|
||||||
|
def _build_hierarchy(self, data: Dict) -> List[dict]:
|
||||||
|
"""Construit la hiérarchie des capteurs"""
|
||||||
|
sensors = data.get('sensors', [])
|
||||||
|
readings = data.get('readings', [])
|
||||||
|
|
||||||
|
# Créer un dictionnaire des sensors par index
|
||||||
|
sensor_map = {}
|
||||||
|
for sensor in sensors:
|
||||||
|
idx = sensor.get('entryIndex', 0)
|
||||||
|
sensor_map[idx] = {
|
||||||
|
'name': sensor.get('sensorNameUser', sensor.get('sensorNameOriginal', 'Unknown')),
|
||||||
|
'value': '',
|
||||||
|
'id': '',
|
||||||
|
'level': 0,
|
||||||
|
'type': 'group',
|
||||||
|
'children': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ajouter les readings aux sensors correspondants
|
||||||
|
for reading in readings:
|
||||||
|
sensor_idx = reading.get('sensorIndex', 0)
|
||||||
|
|
||||||
|
if sensor_idx not in sensor_map:
|
||||||
|
continue
|
||||||
|
|
||||||
|
label = reading.get('labelUser', reading.get('labelOriginal', 'Unknown'))
|
||||||
|
value = reading.get('value', 0)
|
||||||
|
unit = reading.get('unit', '')
|
||||||
|
|
||||||
|
# Générer un ID unique
|
||||||
|
sensor_id = self._generate_sensor_id(sensor_idx, reading.get('readingId', 0), label)
|
||||||
|
|
||||||
|
# Formater la valeur
|
||||||
|
if isinstance(value, float):
|
||||||
|
if value == int(value):
|
||||||
|
formatted_value = f"{int(value)} {unit}".strip()
|
||||||
|
else:
|
||||||
|
formatted_value = f"{value:.2f} {unit}".strip()
|
||||||
|
else:
|
||||||
|
formatted_value = f"{value} {unit}".strip()
|
||||||
|
|
||||||
|
reading_entry = {
|
||||||
|
'name': label,
|
||||||
|
'value': formatted_value,
|
||||||
|
'id': sensor_id,
|
||||||
|
'level': 1,
|
||||||
|
'type': self.get_sensor_type(unit, label),
|
||||||
|
'children': []
|
||||||
|
}
|
||||||
|
|
||||||
|
sensor_map[sensor_idx]['children'].append(reading_entry)
|
||||||
|
|
||||||
|
# Retourner seulement les sensors qui ont des readings
|
||||||
|
return [s for s in sensor_map.values() if s['children']]
|
||||||
|
|
||||||
|
def parse_sensors(self, data: Dict) -> List[Dict]:
|
||||||
|
"""Parse les données et retourne une liste plate de capteurs"""
|
||||||
|
sensors_list = []
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return sensors_list
|
||||||
|
|
||||||
|
sensors = data.get('sensors', [])
|
||||||
|
readings = data.get('readings', [])
|
||||||
|
|
||||||
|
# Créer un dictionnaire des sensors par index
|
||||||
|
sensor_names = {}
|
||||||
|
for sensor in sensors:
|
||||||
|
idx = sensor.get('entryIndex', 0)
|
||||||
|
sensor_names[idx] = sensor.get('sensorNameUser', sensor.get('sensorNameOriginal', 'Unknown'))
|
||||||
|
|
||||||
|
# Parser chaque reading
|
||||||
|
for reading in readings:
|
||||||
|
sensor_idx = reading.get('sensorIndex', 0)
|
||||||
|
hardware_name = sensor_names.get(sensor_idx, 'Unknown')
|
||||||
|
|
||||||
|
label = reading.get('labelUser', reading.get('labelOriginal', 'Unknown'))
|
||||||
|
value = reading.get('value', 0)
|
||||||
|
unit = reading.get('unit', '')
|
||||||
|
|
||||||
|
# Générer un ID unique
|
||||||
|
sensor_id = self._generate_sensor_id(sensor_idx, reading.get('readingId', 0), label)
|
||||||
|
|
||||||
|
# Formater la valeur
|
||||||
|
if isinstance(value, float):
|
||||||
|
if value == int(value):
|
||||||
|
formatted_value = f"{int(value)} {unit}".strip()
|
||||||
|
else:
|
||||||
|
formatted_value = f"{value:.2f} {unit}".strip()
|
||||||
|
else:
|
||||||
|
formatted_value = f"{value} {unit}".strip()
|
||||||
|
|
||||||
|
sensor_type = self.get_sensor_type(unit, label)
|
||||||
|
hardware_type = self._guess_hardware_type(hardware_name)
|
||||||
|
|
||||||
|
sensor = {
|
||||||
|
'id': sensor_id,
|
||||||
|
'name': label,
|
||||||
|
'value': formatted_value,
|
||||||
|
'raw_value': float(value) if value else 0.0,
|
||||||
|
'type': sensor_type,
|
||||||
|
'unit': unit,
|
||||||
|
'hardware': hardware_name,
|
||||||
|
'hardware_type': hardware_type,
|
||||||
|
'category': hardware_name,
|
||||||
|
'path': f"{hardware_name}/{label}",
|
||||||
|
}
|
||||||
|
sensors_list.append(sensor)
|
||||||
|
|
||||||
|
return sensors_list
|
||||||
|
|
||||||
|
def _generate_sensor_id(self, sensor_idx: int, reading_id: int, label: str) -> str:
|
||||||
|
"""Génère un ID unique pour un capteur"""
|
||||||
|
# Nettoyer le label pour l'ID
|
||||||
|
clean_label = label.replace(' ', '_').replace('[', '').replace(']', '')
|
||||||
|
clean_label = clean_label.replace('#', '').replace(':', '_').replace('/', '_')
|
||||||
|
return f"/hwinfo/{sensor_idx}_{reading_id}_{clean_label}"
|
||||||
|
|
||||||
|
def _guess_hardware_type(self, sensor_name: str) -> str:
|
||||||
|
"""Devine le type de hardware basé sur le nom du sensor"""
|
||||||
|
name_lower = sensor_name.lower()
|
||||||
|
|
||||||
|
if 'cpu' in name_lower or 'processor' in name_lower:
|
||||||
|
return 'CPU'
|
||||||
|
elif 'gpu' in name_lower or 'graphics' in name_lower or 'radeon' in name_lower or 'geforce' in name_lower:
|
||||||
|
return 'GPU'
|
||||||
|
elif 'memory' in name_lower or 'ram' in name_lower or 'dimm' in name_lower:
|
||||||
|
return 'RAM'
|
||||||
|
elif 'drive' in name_lower or 'disk' in name_lower or 'ssd' in name_lower or 'hdd' in name_lower or 's.m.a.r.t' in name_lower:
|
||||||
|
return 'Storage'
|
||||||
|
elif 'network' in name_lower or 'ethernet' in name_lower or 'wifi' in name_lower or 'réseau' in name_lower:
|
||||||
|
return 'Network'
|
||||||
|
elif 'system' in name_lower or 'système' in name_lower:
|
||||||
|
return 'System'
|
||||||
|
else:
|
||||||
|
return 'Other'
|
||||||
|
|
||||||
|
# === Gestion de l'historique ===
|
||||||
|
|
||||||
|
def update_history(self, sensors: List[Dict]):
|
||||||
|
"""Met à jour l'historique des capteurs pour les graphiques"""
|
||||||
|
for sensor in sensors:
|
||||||
|
sensor_id = sensor.get("id", "")
|
||||||
|
if not sensor_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw_value = sensor.get("raw_value", 0.0)
|
||||||
|
|
||||||
|
if sensor_id not in self.history:
|
||||||
|
self.history[sensor_id] = []
|
||||||
|
|
||||||
|
self.history[sensor_id].append(raw_value)
|
||||||
|
|
||||||
|
if len(self.history[sensor_id]) > self.max_history:
|
||||||
|
self.history[sensor_id] = self.history[sensor_id][-self.max_history:]
|
||||||
|
|
||||||
|
def get_history(self, sensor_id: str, count: int = 60) -> List[float]:
|
||||||
|
"""Récupère l'historique d'un capteur"""
|
||||||
|
if sensor_id not in self.history:
|
||||||
|
return []
|
||||||
|
return self.history[sensor_id][-count:]
|
||||||
266
output/Pc-monitor/_internal/plugins/librehardwaremonitor.py
Normal file
266
output/Pc-monitor/_internal/plugins/librehardwaremonitor.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
"""
|
||||||
|
Plugin pour LibreHardwareMonitor
|
||||||
|
Utilise l'API REST intégrée de LHM (port 8085 par défaut)
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
from .base import BasePlugin
|
||||||
|
|
||||||
|
|
||||||
|
class LibreHardwareMonitorPlugin(BasePlugin):
|
||||||
|
"""Plugin pour LibreHardwareMonitor"""
|
||||||
|
|
||||||
|
def __init__(self, config: dict):
|
||||||
|
super().__init__(config)
|
||||||
|
self.history = {}
|
||||||
|
self.max_history = 120 # 2 minutes à 1 sample/sec
|
||||||
|
|
||||||
|
def get_id(self) -> str:
|
||||||
|
return 'librehardwaremonitor'
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return 'LibreHardwareMonitor'
|
||||||
|
|
||||||
|
def get_default_port(self) -> int:
|
||||||
|
return 8085
|
||||||
|
|
||||||
|
def test_connection(self) -> Dict[str, Any]:
|
||||||
|
"""Teste la connexion à LibreHardwareMonitor"""
|
||||||
|
try:
|
||||||
|
url = f"{self.get_base_url()}/data.json"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Extraire la version si disponible
|
||||||
|
version = "Inconnue"
|
||||||
|
if data and "Text" in data:
|
||||||
|
version = data.get("Text", "LibreHardwareMonitor")
|
||||||
|
|
||||||
|
# Compter les capteurs
|
||||||
|
sensors = self.parse_sensors(data)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': f'Connecté - {len(sensors)} capteurs détectés',
|
||||||
|
'version': version,
|
||||||
|
'sensor_count': len(sensors)
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f'Impossible de se connecter à {self.get_base_url()}. Vérifiez que LibreHardwareMonitor est lancé avec le serveur web activé.'
|
||||||
|
}
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': 'Timeout - Le serveur ne répond pas'
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f'Erreur: {str(e)}'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_data(self) -> Optional[Dict]:
|
||||||
|
"""Récupère les données JSON depuis LHM Remote Server"""
|
||||||
|
try:
|
||||||
|
url = f"{self.get_base_url()}/data.json"
|
||||||
|
response = requests.get(url, timeout=5)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur LHM get_data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_hierarchy(self) -> List[dict]:
|
||||||
|
"""Récupère la hiérarchie des capteurs pour l'admin"""
|
||||||
|
data = self.get_data()
|
||||||
|
if not data:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return self._build_hierarchy(data)
|
||||||
|
|
||||||
|
def _build_hierarchy(self, data: Dict) -> List[dict]:
|
||||||
|
"""Construit la hiérarchie des capteurs"""
|
||||||
|
if not data or "Children" not in data:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def build_tree(node, level=0):
|
||||||
|
node_text = node.get("Text", "")
|
||||||
|
node_value = node.get("Value", "")
|
||||||
|
sensor_id = node.get("SensorId", "")
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"name": node_text,
|
||||||
|
"value": node_value,
|
||||||
|
"id": sensor_id,
|
||||||
|
"level": level,
|
||||||
|
"type": self._guess_sensor_type(node_text, node_value) if node_value else "group",
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
|
||||||
|
if "Children" in node and node["Children"]:
|
||||||
|
for child in node["Children"]:
|
||||||
|
result["children"].append(build_tree(child, level + 1))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
tree = []
|
||||||
|
for child in data.get("Children", []):
|
||||||
|
tree.append(build_tree(child))
|
||||||
|
|
||||||
|
return tree
|
||||||
|
|
||||||
|
def parse_sensors(self, data: Dict) -> List[Dict]:
|
||||||
|
"""Parse les données et retourne une liste plate de capteurs"""
|
||||||
|
sensors = []
|
||||||
|
|
||||||
|
if not data or "Children" not in data:
|
||||||
|
return sensors
|
||||||
|
|
||||||
|
def extract_sensors(node, parent_name="", hardware_name="", hardware_type=""):
|
||||||
|
node_text = node.get("Text", "")
|
||||||
|
current_path = f"{parent_name}/{node_text}" if parent_name else node_text
|
||||||
|
|
||||||
|
# Détecter le type de hardware au niveau 1
|
||||||
|
if not hardware_name and "Children" in node:
|
||||||
|
hw_type = self._guess_hardware_type(node_text)
|
||||||
|
hardware_name = node_text
|
||||||
|
hardware_type = hw_type
|
||||||
|
|
||||||
|
# Si le nœud a une valeur, c'est un capteur
|
||||||
|
if "Value" in node and node["Value"]:
|
||||||
|
sensor_id = node.get("SensorId", "")
|
||||||
|
if not sensor_id:
|
||||||
|
sensor_id = f"{current_path}".replace(" ", "-").lower()
|
||||||
|
|
||||||
|
value_str = node.get("Value", "")
|
||||||
|
raw_value = self._extract_numeric_value(value_str)
|
||||||
|
unit = self._extract_unit(value_str)
|
||||||
|
sensor_type = self._guess_sensor_type(node_text, value_str)
|
||||||
|
|
||||||
|
sensor = {
|
||||||
|
"id": sensor_id,
|
||||||
|
"name": node_text,
|
||||||
|
"value": value_str,
|
||||||
|
"raw_value": raw_value,
|
||||||
|
"type": sensor_type,
|
||||||
|
"unit": unit,
|
||||||
|
"hardware": hardware_name,
|
||||||
|
"hardware_type": hardware_type,
|
||||||
|
"category": parent_name.split("/")[0] if "/" in parent_name else parent_name,
|
||||||
|
"path": current_path,
|
||||||
|
"min": node.get("Min", ""),
|
||||||
|
"max": node.get("Max", ""),
|
||||||
|
}
|
||||||
|
sensors.append(sensor)
|
||||||
|
|
||||||
|
# Parcourir les enfants récursivement
|
||||||
|
if "Children" in node:
|
||||||
|
for child in node["Children"]:
|
||||||
|
extract_sensors(child, current_path, hardware_name, hardware_type)
|
||||||
|
|
||||||
|
for child in data.get("Children", []):
|
||||||
|
extract_sensors(child)
|
||||||
|
|
||||||
|
return sensors
|
||||||
|
|
||||||
|
def _guess_hardware_type(self, name: str) -> str:
|
||||||
|
"""Devine le type de hardware basé sur le nom"""
|
||||||
|
name_lower = name.lower()
|
||||||
|
|
||||||
|
if 'cpu' in name_lower or 'processor' in name_lower:
|
||||||
|
return 'CPU'
|
||||||
|
elif 'gpu' in name_lower or 'graphics' in name_lower or 'nvidia' in name_lower or 'amd' in name_lower or 'radeon' in name_lower or 'geforce' in name_lower:
|
||||||
|
return 'GPU'
|
||||||
|
elif 'memory' in name_lower or 'ram' in name_lower:
|
||||||
|
return 'RAM'
|
||||||
|
elif 'motherboard' in name_lower or 'mainboard' in name_lower:
|
||||||
|
return 'Motherboard'
|
||||||
|
elif 'storage' in name_lower or 'disk' in name_lower or 'ssd' in name_lower or 'hdd' in name_lower or 'nvme' in name_lower:
|
||||||
|
return 'Storage'
|
||||||
|
elif 'network' in name_lower or 'ethernet' in name_lower or 'wifi' in name_lower:
|
||||||
|
return 'Network'
|
||||||
|
elif 'battery' in name_lower:
|
||||||
|
return 'Battery'
|
||||||
|
else:
|
||||||
|
return 'Other'
|
||||||
|
|
||||||
|
def _guess_sensor_type(self, name: str, value: str) -> str:
|
||||||
|
"""Devine le type de capteur basé sur le nom et la valeur"""
|
||||||
|
name_lower = name.lower()
|
||||||
|
value_lower = value.lower()
|
||||||
|
|
||||||
|
if "°c" in value_lower or "temperature" in name_lower or "temp" in name_lower:
|
||||||
|
return "temperature"
|
||||||
|
elif "%" in value or "load" in name_lower or "usage" in name_lower:
|
||||||
|
return "percentage"
|
||||||
|
elif "rpm" in value_lower or "fan" in name_lower:
|
||||||
|
return "fan"
|
||||||
|
elif "mhz" in value_lower or "ghz" in value_lower or "clock" in name_lower:
|
||||||
|
return "frequency"
|
||||||
|
elif "w" in value_lower and "wh" not in value_lower:
|
||||||
|
return "power"
|
||||||
|
elif "v" in value_lower:
|
||||||
|
return "voltage"
|
||||||
|
elif "gb" in value_lower or "mb" in value_lower or "memory" in name_lower:
|
||||||
|
return "memory"
|
||||||
|
else:
|
||||||
|
return "generic"
|
||||||
|
|
||||||
|
def _extract_numeric_value(self, value_str: str) -> float:
|
||||||
|
"""Extrait la valeur numérique d'une chaîne"""
|
||||||
|
try:
|
||||||
|
# Enlever tous les caractères non numériques sauf le point et la virgule
|
||||||
|
clean = value_str.replace("°C", "").replace("%", "").replace("V", "").replace("W", "")
|
||||||
|
clean = clean.replace("MHz", "").replace("GHz", "").replace("GB", "").replace("MB", "")
|
||||||
|
clean = clean.replace("RPM", "").replace(" ", "").replace(",", ".").strip()
|
||||||
|
|
||||||
|
if clean:
|
||||||
|
return float(clean)
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
pass
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _extract_unit(self, value_str: str) -> str:
|
||||||
|
"""Extrait l'unité d'une valeur"""
|
||||||
|
value_str = value_str.strip()
|
||||||
|
|
||||||
|
units = ['°C', '°F', '%', 'MHz', 'GHz', 'GB', 'MB', 'KB', 'RPM', 'W', 'V', 'A', 'GB/s', 'MB/s']
|
||||||
|
for unit in units:
|
||||||
|
if unit in value_str:
|
||||||
|
return unit
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# === Gestion de l'historique ===
|
||||||
|
|
||||||
|
def update_history(self, sensors: List[Dict]):
|
||||||
|
"""Met à jour l'historique des capteurs pour les graphiques"""
|
||||||
|
for sensor in sensors:
|
||||||
|
sensor_id = sensor.get("id", "")
|
||||||
|
if not sensor_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw_value = sensor.get("raw_value")
|
||||||
|
if raw_value is None:
|
||||||
|
raw_value = self._extract_numeric_value(sensor.get("value", ""))
|
||||||
|
|
||||||
|
if raw_value == 0.0 and "0" not in sensor.get("value", ""):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if sensor_id not in self.history:
|
||||||
|
self.history[sensor_id] = []
|
||||||
|
|
||||||
|
self.history[sensor_id].append(raw_value)
|
||||||
|
|
||||||
|
if len(self.history[sensor_id]) > self.max_history:
|
||||||
|
self.history[sensor_id] = self.history[sensor_id][-self.max_history:]
|
||||||
|
|
||||||
|
def get_history(self, sensor_id: str, count: int = 60) -> List[float]:
|
||||||
|
"""Récupère l'historique d'un capteur"""
|
||||||
|
if sensor_id not in self.history:
|
||||||
|
return []
|
||||||
|
return self.history[sensor_id][-count:]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user