Salut, c’est repartie pour un nouveau tutoriel sur Flask et Python. Dans ce tutoriel, je vous guide pas à pas dans la création d’une application web professionnelle de diagnostic système pour techniciens IT. Vous apprendrez à créer une interface moderne permettant de monitorer, gérer et diagnostiquer des PC Windows à distance.

Introduction

En tant que développeur et technicien IT depuis plusieurs années, j’ai souvent eu besoin d’outils pour diagnostiquer rapidement les problèmes sur les postes de travail Windows. Les solutions existantes sont soit trop complexes, soit payantes, soit ne répondent pas exactement aux besoins.

C’est pourquoi j’ai décidé de créer HelpDesk Info, une application web légère et moderne qui permet de:

– ✅ Collecter toutes les informations système en un clin d’œil

– ✅ Gérer les programmes installés (avec désinstallation à distance)

– ✅ Contrôler les services Windows

– ✅ Monitorer les processus en temps réel

– ✅ Exporter des rapports de diagnostic

Ce que nous verrons

Coté backend, nous verrons comment créer une application Flask structurée et professionnelle, comment utiliser psutil pour collecter des informations système, comment interagir avec le registre Windows via winreg, comment exécuter des commandes PowerShell depuis Python, comment Implémenter une gestion d’erreurs robuste et enfin comment configurer un système de logging professionnel.

Côté frontend, nous verrons comment créer une interface moderne avec Bootstrap 5, comment implémenter des animations et transitions CSS, comment utiliser DataTables pour des tableaux interactifs et enfin comment créer un système de notifications toast personnalisé

Nous utiliserons une architecture MVC pour applications Flask, la sécurisation des entrées utilisateur, et de stechniques de gestion des erreurs et exceptions

Stack Technique
Stack Technique Frontend + Backend

Architecture de l’application

Vue synthétique de la pile technologique : composants Frontend (client) et Backend (serveur)

Frontend (Client)
UI, navigation, tableaux, interactions
UI/UX
Bootstrap 5.3.2 Framework CSS responsive
Bootstrap Icons Bibliothèque d’icônes moderne
DataTables 1.13.6 Tables interactives
jQuery 3.7.1 Manipulation DOM
CSS personnalisé Design moderne + animations
Astuce : tu peux intégrer des composants réutilisables (navbar, sidebar, cards) dans /components.
Backend (Serveur)
API, monitoring, appels système, réseau
Services
Flask 3.0+ Framework web Python
psutil 5.9+ Monitoring système
requests 2.31+ Requêtes HTTP
Modules natifs winreg, subprocess, platform, socket
Conseil : expose une API JSON (ex: /api/system) et consomme-la côté frontend via fetch().

Étape 1 : Mise en place de l’environnement

1) Créer le projet

Ouvrez un terminal et créez le dossier principal:

mkdir helpdesk-info
cd helpdesk-info

2) Créer / activer l’environnement virtuel (Windows)

Un environnement virtuel isole les dépendances de votre projet:

python -m venv venv
venv\Scripts\activate

3) Installer les dépendances

Créez le fichier fichier requirements.txt

Flask>=3.0.0
psutil>=5.9.0
requests>=2.31.0

Puis installez les dépendances:

pip install -r requirements.txt

Ces étapes peuvent se faire depuis votre terminal ou votre cmd sur windows

Étape 2 : Structure du projet

Arborescence :

helpdesk-info/
├── static/
│   └── style.css
├── templates/
│   └── index.html
├── main.py
└── requirements.txt

Création rapide (Windows) :

mkdir static templates
type nul > main.py
type nul > static\style.css
type nul > templates\index.html

Étape 3 : Backend Flask (collecte système)

1) Base Flask + imports

Ouvrez main.py et commençons par les imports et la configuration. Dans main.py, initialise Flask, les imports et les logs :

"""
HelpDesk Info - Application de diagnostic PC professionnel
Auteur: Michael - LM-Code (https://lm-code.be)
"""

from flask import Flask, render_template, request, send_file, jsonify
import platform
import socket
import psutil
import requests
import datetime
import os
import subprocess
import winreg
import logging
from io import BytesIO

# Configuration de l'application Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['JSON_AS_ASCII'] = False

# Configuration du système de logs
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('helpdesk.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

SECRET_KEY sécurise les sessions Flask, JSON_AS_ASCII=False permet les caractères accentués dans le JSON et logging crée un fichier de log pour tracer les actions.

2) Fonctions clés

  • get_size() : convertit les octets (RAM, disque…)
  • get_system_info() : récupère hostname, OS, CPU, RAM, disques, réseau, IP publique (ipify)
def get_size(bytes_value, suffix="B"):
    """
    Convertit une taille en octets vers un format lisible.

    Exemple: 1536 → "1.50 KB"
    """
    factor = 1024
    for unit in ["", "K", "M", "G", "T"]:
        if bytes_value < factor:
            return f"{bytes_value:.2f} {unit}{suffix}"
        bytes_value /= factor
    return f"{bytes_value:.2f} P{suffix}"

3) Route principale /

La fonction la plus importante : elle collecte toutes les données:

def get_system_info():
    """
    Collecte complète des informations système.

    Returns:
        dict: Toutes les informations système formatées
    """
    # IP publique via service externe
    try:
        public_ip = requests.get("https://api.ipify.org", timeout=5).text
        logger.info(f"IP publique récupérée: {public_ip}")
    except Exception as e:
        logger.warning(f"Impossible de récupérer l'IP publique: {e}")
        public_ip = "Non disponible"

    # Temps de démarrage et uptime
    boot_time = datetime.datetime.fromtimestamp(psutil.boot_time())
    uptime = datetime.datetime.now() - boot_time

    # Informations des disques
    disk_info = []
    for part in psutil.disk_partitions():
        try:
            usage = psutil.disk_usage(part.mountpoint)
            disk_info.append({
                "device": part.device,
                "mountpoint": part.mountpoint,
                "fstype": part.fstype,
                "total": get_size(usage.total),
                "used": get_size(usage.used),
                "free": get_size(usage.free),
                "percent": usage.percent
            })
        except PermissionError:
            continue

    # Informations réseau
    net_info = []
    mac_address = "Non disponible"
    for interface_name, interface_addresses in psutil.net_if_addrs().items():
        for address in interface_addresses:
            if str(address.family) == 'AddressFamily.AF_INET':
                net_info.append({
                    "interface": interface_name,
                    "ip": address.address,
                    "netmask": address.netmask if address.netmask else "N/A"
                })

    # Dictionnaire de retour
    return {
        "hostname": socket.gethostname(),
        "username": os.getlogin(),
        "os": f"{platform.system()} {platform.release()}",
        "architecture": platform.machine(),
        "cpu": platform.processor(),
        "cores_physical": psutil.cpu_count(logical=False),
        "cores_total": psutil.cpu_count(logical=True),
        "cpu_usage": psutil.cpu_percent(interval=1),
        "ram_total": get_size(psutil.virtual_memory().total),
        "ram_used": get_size(psutil.virtual_memory().used),
        "ram_available": get_size(psutil.virtual_memory().available),
        "boot_time": boot_time.strftime("%Y-%m-%d %H:%M:%S"),
        "uptime": str(uptime).split('.')[0],
        "ip_public": public_ip,
        "mac_address": mac_address,
        "disks": disk_info,
        "network": net_info
    }
  • GET : affiche les infos systèm
  • POST : récupère le texte “problème” décrit par l’utilisateur
  • psutil.cpu_percent(): Utilisation CPU en temps réel
  • psutil.virtual_memory(): Statistiques mémoire RAM
  • psutil.disk_partitions(): Liste des disques
  • Gestion des erreurs avec try/except pour éviter les crashs

4) Lancer l’app

En bas du fichier :

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=True, threaded=True)

Étape 4 : Frontend (Bootstrap + onglets)

Dans templates/index.html :

  • Charger Bootstrap 5.3.2 + DataTables + Bootstrap Icons + ton CSS
  • Créer un header (card + dégradé)
  • Ajouter des onglets :
    • Infos Système
    • Programmes
    • Services
    • Processus

Dans l’onglet “Infos Système” :

  • affichage des champs (readonly)
  • textarea “Décrivez votre problème”
  • boutons : Exporter / Envoyer

Étape 5 : CSS

Dans static/style.css :

  • variables CSS (couleurs, ombres, transitions)
  • fond en dégradé
  • header avec effet shimmer
  • onglets stylisés
  • boutons avec hover + ombre

Étape 6 : Programmes (Windows Registry)

Backend :

  • get_installed_programs() via winreg
  • endpoint /programs qui renvoie JSON

Frontend :

  • table DataTables #programsTable
  • chargement via jQuery $.getJSON('/programs')

Étape 7 : Services Windows (PowerShell)

Backend :

  • get_services() via Get-Service
  • endpoint /services (JSON)
  • endpoint /service_action (POST) : start/stop/restart avec validation + logs

Frontend :

  • table DataTables + badges Running/Stopped
  • boutons actions : start/stop/restart

Étape 8 : Processus (psutil)

Backend :

def get_processes():
    """Récupère la liste des processus actifs"""
    processes = []

    for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_info']):
        try:
            info = proc.info
            info['memory'] = get_size(info['memory_info'].rss)
            del info['memory_info']
            processes.append(info)
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue

    return processes

@app.route("/processes")
def processes():
    """Endpoint API pour les processus"""
    try:
        return jsonify(get_processes())
    except Exception as e:
        logger.error(f"Erreur: {e}")
        return jsonify([]), 500
  • endpoint /processes (JSON)
  • endpoint /kill_process (POST) : terminate/kill avec confirmation + gestion AccessDenied
@app.route("/kill_process", methods=["POST"])
def kill_process():
    """Termine un processus par son PID"""
    try:
        data = request.get_json()

        if not data or 'pid' not in data:
            return jsonify({"success": False, "message": "PID manquant"}), 400

        pid = int(data.get("pid"))

        if not psutil.pid_exists(pid):
            return jsonify({"success": False, "message": "Processus inexistant"}), 404

        logger.info(f"Terminaison du processus PID: {pid}")

        process = psutil.Process(pid)
        process_name = process.name()

        process.terminate()

        try:
            process.wait(timeout=5)
            return jsonify({
                "success": True,
                "message": f"Processus '{process_name}' (PID: {pid}) terminé"
            })
        except psutil.TimeoutExpired:
            process.kill()
            return jsonify({
                "success": True,
                "message": f"Processus forcé à se terminer"
            })

    except psutil.AccessDenied:
        return jsonify({
            "success": False,
            "message": "Accès refusé. Privilèges administrateur requis."
        }), 403
    except Exception as e:
        logger.error(f"Erreur: {e}")
        return jsonify({"success": False, "message": str(e)}), 500

Frontend :

  • table DataTables + mise en couleur CPU
  • bouton “Tuer” (confirm)
// Fonction de terminaison de processus
function killProcess(pid, name) {
  if (!confirm(`Terminer "${name}" (PID: ${pid}) ?\n\nCela peut causer une perte de données.`)) {
    return;
  }

  showToast(`Arrêt du processus ${pid} en cours...`, 'info');

  fetch('/kill_process', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ pid })
  })
  .then(res => res.json())
  .then(data => {
    if (data.success) {
      showToast(data.message, 'success');
      setTimeout(() => $('#proc-tab').click(), 1500);
    } else {
      showToast(data.message, 'error');
    }
  });
}

// Chargement des processus
$('#proc-tab').on('shown.bs.tab', () => {
  loadTable('/processes', '#processesTable', [
    { data: 'pid' },
    { data: 'name' },
    {
      data: 'cpu_percent',
      render: function(data) {
        const percentage = parseFloat(data) || 0;
        let colorClass = 'text-success';
        if (percentage > 50) colorClass = 'text-warning';
        if (percentage > 80) colorClass = 'text-danger';
        return `<span class="${colorClass} fw-bold">${percentage.toFixed(1)}%</span>`;
      }
    },
    { data: 'memory' },
    {
      data: null,
      render: function(data, type, row) {
        return `<button class="btn btn-sm btn-danger" onclick="killProcess(${row.pid}, '${row.name}')" title="Terminer">
          <i class="bi bi-x-circle"></i> Tuer
        </button>`;
      }
    }
  ]);
});

Étape 9 : Export du rapport

Backend :

  • endpoint /export
  • génère un .txt (infos + listes) en mémoire (BytesIO)
  • retourne send_file(..., as_attachment=True)

Et voilà, notre projet est prêt. Merci d’avoir suivi ce tutoriel jusqu’au bout! J’espère que vous avez appris des techniques utiles pour vos futurs projets. N’hésitez pas à adapter ce projet à vos besoins spécifiques. Laissez vos avis en commentaire ou si vous avez des questions.

Il est important de télécharger le code source tout en suivant le tuto.

Comments

No comments yet. Why don’t you start the discussion?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *