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