Le Gestionnaire des tâches ne garde rien en mémoire, ne t’alerte jamais et ne te dit pas si ton pare-feu est coupé. Dans ce tutoriel, tu construis System Dashboard, un moniteur système Windows complet en PHP 8, MySQL et JavaScript natif : temps réel, historique de 30 jours, alertes automatiques et audit de sécurité.
Ton PC rame, et tu ne sais pas pourquoi ? Ton disque C: est plein à 93 % sans que personne ne t’ait prévenu ? Un moniteur système Windows répond à ces questions en permanence : il mesure le processeur, la mémoire, les disques, le réseau et la batterie, enregistre tout dans une base de données et lève une alerte dès qu’un seuil est franchi.
Plutôt que d’installer un énième logiciel, on va le coder nous-mêmes. Résultat : une application web moderne, qui tourne en local sur WampServer, sans aucune dépendance externe (ni Composer, ni npm, ni build). Toutes les valeurs affichées sont réelles : elles viennent directement de Windows via WMI, et aucune donnée ne quitte ta machine.
Si tu as aimé notre application de diagnostic PC avec Flask et Python, ce moniteur système Windows va beaucoup plus loin : supervision continue, historique, alertes et audit de sécurité. Et si tu cherches simplement à surveiller un PC Windows en PHP, c’est exactement le bon point de départ.

Tu touches à tout ce qu’on attend d’un développeur back-end : Clean Architecture, injection de dépendances maison, API REST sécurisée (sessions, CSRF, Argon2id), base de données indexée, interface temps réel en JavaScript natif… et un sujet rarement abordé : dialoguer avec Windows depuis PHP. Débutant, tu repars avec des bases solides ; confirmé, tu trouveras des choix d’architecture et d’optimisation à discuter.
Ce que fait ton moniteur système Windows
System Dashboard n’est pas une simple page de démo : c’est un vrai outil de supervision, organisé en 13 modules accessibles depuis le menu latéral. Voici ce que tu obtiens à la fin du projet.
À quoi ça sert concrètement ?
- Comprendre une lenteur : l’historique montre quand le PC a ramé et quel processus était en cause.
- Anticiper les pannes : une alerte quand le disque dépasse 95 % ou quand la batterie s’use anormalement.
- Vérifier la sécurité d’un poste en un coup d’œil, pratique pour un petit parc de PC ou l’ordinateur de la famille.
- Apprendre : le projet est structuré comme une application professionnelle, idéal pour un portfolio.
Les technologies du moniteur système Windows
Le choix de la stack est volontairement minimaliste : uniquement des outils déjà présents sur le poste d’un développeur PHP sous Windows.
| Couche | Technologie | Pourquoi ce choix |
|---|---|---|
| Serveur web | Apache (WampServer) | Déjà installé chez la plupart des développeurs PHP sous Windows |
| Backend | PHP 8.3, sans framework | Enums, propriétés readonly, arguments nommés : le PHP moderne suffit largement |
| Accès système | WMI via l’extension COM | L’API officielle de Windows pour interroger le matériel et l’OS |
| Base de données | MySQL 8+ / MariaDB via PDO | Historique, utilisateurs, règles, alertes et journal |
| Frontend | HTML + CSS + JavaScript natif (modules ES) | Aucun build, aucune dépendance npm : tu ouvres, ça marche |
| Graphiques | Chart.js 4 (copie locale) | Léger, élégant, fonctionne sans connexion Internet |
| Architecture | Clean Architecture | Le métier ne dépend ni de Windows, ni de MySQL, ni du HTTP |
Pas de Composer, pas de npm, pas d’étape de compilation. Tout le code est lisible tel quel : c’est parfait pour comprendre ce qui se passe réellement, ligne par ligne.
Prérequis pour installer le moniteur système Windows
- Windows 10 ou 11
- WampServer avec PHP 8.2 ou plus (testé en PHP 8.3.14)
- Extensions PHP activées :
com_dotnet,pdo_mysql,curl,mbstring - MySQL 8+ ou MariaDB 10.6+
- Apache avec
mod_rewriteetAllowOverride All(réglages par défaut de Wamp)
Clic gauche sur l’icône Wamp → PHP → Extensions PHP → coche com_dotnet, puis redémarre les services. Sans elle, PHP est incapable de parler à Windows et l’application affichera une erreur explicite.
Installer le moniteur système Windows en 5 minutes
1. Récupérer le projet dans WampServer
Ouvre un terminal et clone le dépôt GitHub dans le dossier www de Wamp :
cd C:\wamp64\www
git clone https://github.com/LM-Code-Be/system-dashboard.git system-dashboardTerminal (cmd)Pas de Git ? Télécharge l’archive ZIP depuis GitHub (bouton Code → Download ZIP) et décompresse-la dans C:\wamp64\www\system-dashboard.
2. Ouvrir l’application et créer le compte administrateur
Rends-toi sur http://localhost/system-dashboard/. Au premier appel, l’API crée automatiquement la base de données et toutes ses tables : tu n’as rien à importer dans phpMyAdmin. Tu arrives directement sur l’écran de création du compte administrateur.

Choisis un mot de passe d’au moins 8 caractères, avec une lettre et un chiffre (la jauge de robustesse t’aide). Dès la validation, la supervision démarre.
3. (Optionnel) Adapter la connexion MySQL
Ton MySQL a un mot de passe root, ou écoute sur un autre port ? Crée le fichier backend/config/config.local.php. Il surcharge la configuration par défaut et n’est jamais versionné :
<?php
return [
'base_de_donnees' => [
'hote' => '127.0.0.1',
'port' => 3306,
'mot_de_passe' => 'mon-secret',
],
];backend/config/config.local.phpLe fichier config.php contient des valeurs par défaut sans danger ; le fichier config.local.php, ignoré par Git, contient ce qui est propre à ta machine. Tu peux ainsi publier ton code sans jamais publier ton mot de passe.
L’architecture du moniteur système Windows
L’arborescence des fichiers
La Clean Architecture expliquée simplement
Imagine un oignon. Au centre, le Domaine : ce qu’est une alerte, comment on calcule un score de santé. Autour, l’Application : les actions possibles (« se connecter », « lister les processus »). À l’extérieur, les détails techniques : l’Infrastructure (Windows, MySQL, fichiers de cache) et la Présentation (HTTP, JSON).
La règle d’or : les dépendances pointent toujours vers le centre. Le domaine ne sait pas que MySQL ou Windows existent. Comment récupère-t-il alors les données du système ? Grâce à une interface, qu’on appelle un « port » :
<?php
interface SondeSysteme
{
/** Informations d'identité de la machine (OS, modèle, BIOS…). */
public function informations(): array;
/** Métriques temps réel calculées par différentiel de compteurs. */
public function instantane(): Instantane;
public function processus(): array;
public function securite(): array;
// …
}backend/src/Domain/Port/SondeSysteme.phpLe domaine dit simplement : « j’ai besoin de quelque chose qui sache me donner un instantané du système ». L’infrastructure fournit l’implémentation concrète, SondeWindows. Demain, tu veux superviser un serveur Linux ? Tu écris une SondeLinux qui lit /proc, et rien d’autre ne change dans l’application.
Le branchement entre l’interface et son implémentation se fait à un seul endroit, le conteneur d’injection de dépendances :
private const LIAISONS = [
Port\SondeSysteme::class => SondeWindows::class,
Port\Cache::class => CacheFichier::class,
Port\Session::class => SessionPhp::class,
Depot\DepotMesures::class => Persistance\DepotMesuresPdo::class,
// …
];backend/src/Infrastructure/Conteneur.phpLe conteneur lit les constructeurs par réflexion (ReflectionClass) et fabrique automatiquement chaque objet avec ses dépendances, récursivement. Pas besoin de Symfony ou de PHP-DI : une centaine de lignes suffisent, et tu comprends enfin ce que font ces bibliothèques en coulisse.
Retiens surtout l’idée : séparer « ce que fait l’application » de « comment elle le fait techniquement ». C’est ce qui rend un code facile à tester, à comprendre et à faire évoluer sans tout casser.
Le routage Apache avec un seul .htaccess
Un unique fichier .htaccess à la racine sépare le frontend de l’API et interdit tout accès direct au backend (code source, logs, scripts) :
RewriteEngine On
RewriteBase /system-dashboard/
# 1. API REST : tout /api/* part vers le point d'entrée unique
RewriteRule ^api(/.*)?$ backend/public/index.php [QSA,END]
# 2. Accès direct interdit au backend, aux fichiers cachés et sensibles
RewriteRule ^backend(/|$) - [F,L]
RewriteRule \.(md|sql|log|bat|ps1|lock)$ - [F,L,NC]
# 3. Tout le reste → l'application monopage
RewriteRule ^$ frontend/index.html [END]
RewriteRule ^(.+)$ frontend/$1 [END].htaccessLe drapeau [END] stoppe immédiatement la réécriture (contrairement à [L] qui peut relancer un tour), et [F] renvoie un 403 Forbidden. Même si quelqu’un devine le chemin d’un fichier PHP du backend, Apache refusera de le servir.
Étape 1 : la base de données du moniteur système Windows
La base system_dashboard est créée automatiquement au premier lancement. Ses tables sont nommées en français, comme tout le code du projet :
| Table | Rôle |
|---|---|
utilisateurs | Comptes, rôles, verrouillage après échecs de connexion |
parametres | Réglages typés et bornés (seuils, fréquences, rétention…) |
mesures | L’historique des métriques système |
evenements | Le journal (sécurité, alertes, actions) |
regles_automatisation | Les règles « si … alors … » |
alertes | Les alertes levées et leur acquittement |
diagnostics | Les résultats des tests de performance |
geolocalisations_ip | Cache de géolocalisation des IP (7 jours) |
La table centrale, celle qui alimente tout l’historique :
CREATE TABLE IF NOT EXISTS mesures (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
horodatage DATETIME NOT NULL,
cpu_pourcentage DECIMAL(5,2) NOT NULL,
memoire_pourcentage DECIMAL(5,2) NOT NULL,
stockage_pourcentage DECIMAL(5,2) NOT NULL,
reseau_reception_ko_s DECIMAL(12,2) NULL,
temperature_celsius DECIMAL(5,2) NULL,
batterie_pourcentage TINYINT UNSIGNED NULL,
score_sante TINYINT UNSIGNED NULL,
-- …
PRIMARY KEY (id),
KEY idx_mesures_horodatage (horodatage)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;backend/database/schema.sqlTrois choix de conception méritent qu’on s’y arrête :
- L’index sur
horodatage: toutes les requêtes de l’historique filtrent par période (WHERE horodatage BETWEEN … AND …). Sans index, MySQL parcourrait des milliers de lignes à chaque affichage ; avec, il saute directement à la bonne plage. - Les colonnes
NULL: un PC fixe n’a pas de batterie, et beaucoup de portables n’exposent pas leur température. Une donnée absente vautNULL, jamais une fausse valeur comme 0 qui fausserait les moyennes. DECIMALplutôt queFLOAT: les pourcentages sont stockés de façon exacte, sans erreurs d’arrondi binaire dans les statistiques.
date_default_timezone_set('UTC') est appelé dans bootstrap.php, et c’est le navigateur qui convertit en heure locale. Résultat : aucun trou ni doublon dans l’historique au passage à l’heure d’été ou d’hiver.
Étape 2 : WMI PHP, interroger Windows depuis PHP
C’est la partie la plus originale du projet, et celle qu’on trouve rarement expliquée en français.
C’est quoi, WMI ?
WMI (Windows Management Instrumentation) est une base de données intégrée à Windows, interrogeable avec un langage très proche du SQL : le WQL. Processeur, mémoire, disques, services, batterie, antivirus… tout y est exposé sous forme de « classes » :
SELECT Name, PercentProcessorTime FROM Win32_PerfRawData_PerfOS_ProcessorRequête WQLEt PHP peut l’interroger grâce à l’extension COM (com_dotnet), qui permet d’instancier des composants Windows, exactement comme le ferait un script PowerShell ou VBScript.
Le client WMI
public function requete(string $wql, string $espace = 'root/cimv2'): array
{
// L'accès direct aux propriétés nommées est ~7× plus rapide que l'énumération Properties_.
$colonnes = preg_match('/^\s*SELECT\s+(.+?)\s+FROM\s/is', $wql, $m) && trim($m[1]) !== '*'
? array_map('trim', explode(',', $m[1]))
: null;
$resultats = [];
foreach ($this->service($espace)->ExecQuery($wql, 'WQL', self::DRAPEAUX_RAPIDES) as $objet) {
$ligne = [];
foreach ($colonnes as $colonne) {
$ligne[$colonne] = $this->convertir($objet->$colonne);
}
$resultats[] = $ligne;
}
return $resultats;
}backend/src/Infrastructure/Systeme/Windows/ClientWmi.phpCe qu’il faut comprendre :
ExecQueryenvoie la requête WQL au service WMI de Windows ;DRAPEAUX_RAPIDES(valeur 48 = ReturnImmediately + ForwardOnly) demande un parcours en flux, sans tout charger en mémoire ;- on ne lit que les colonnes citées dans le
SELECT: c’est environ 7 fois plus rapide que de parcourir toutes les propriétés de l’objet ; convertir()transforme les types COM (variants, dates CIM, tableaux) en types PHP propres.
L’astuce performance : les compteurs bruts
Windows propose des classes « formatées » (Win32_PerfFormattedData_*) qui donnent directement le pourcentage CPU… mais chacune met environ 300 ms à répondre, car WMI prend lui-même deux mesures et attend entre les deux. Avec une dizaine de métriques, l’interface mettrait 3 secondes à se rafraîchir : inutilisable.
La solution : lire les compteurs bruts (Win32_PerfRawData_*, environ 20 ms) et calculer soi-même la différence avec la mesure précédente, gardée en cache :
public function mesurer(): array
{
return $this->cache->memoriser(self::CLE_MESURE, 1.0, function (): array {
$precedent = $this->cache->lire(self::CLE_BRUTS);
$actuel = $this->echantillonner();
$age = is_array($precedent) ? $actuel['t'] - $precedent['t'] : null;
// Pas d'échantillon récent exploitable : on en prend deux, espacés de 500 ms
if ($age === null || $age < self::AGE_MIN_S || $age > self::AGE_MAX_S) {
usleep(500_000);
$precedent = $actuel;
$actuel = $this->echantillonner();
}
$this->cache->ecrire(self::CLE_BRUTS, $actuel);
return $this->calculer($precedent, $actuel);
});
}backend/src/Infrastructure/Systeme/Windows/EchantillonneurCompteurs.phpLe calcul du pourcentage processeur tient ensuite en une seule formule :
// % CPU = 100 × (1 − Δtemps_inactif / Δhorloge)
$dHorloge = $horloge - $avant['cpu'][$nom][1];
$valeur = $dHorloge > 0
? $this->borner(100 * (1 - ($inactif - $avant['cpu'][$nom][0]) / $dHorloge))
: 0.0;Calcul du % CPUWindows compte en permanence le temps que chaque cœur passe « à ne rien faire ». Entre deux mesures, si le processeur a été inactif 70 % du temps, il a donc travaillé 30 %. C’est exactement la méthode utilisée par le Gestionnaire des tâches : les valeurs affichées concordent. Résultat : un rafraîchissement temps réel complet répond en ~50 ms.

Un cache disque partagé, protégé par un verrou
Si tu ouvres trois onglets, ils interrogent tous l’API toutes les 3 secondes. Pour ne pas solliciter Windows trois fois, chaque donnée est mise en cache sur disque, avec un verrou exclusif (flock) qui garantit qu’un seul calcul a lieu à la fois :
public function memoriser(string $cle, float $dureeSecondes, callable $calcul): mixed
{
$entree = $this->lireEntree($cle);
if ($entree !== null && $entree['expire'] > microtime(true)) {
return $entree['valeur']; // encore frais : on le renvoie
}
$verrou = @fopen($this->chemin($cle) . '.lock', 'c');
flock($verrou, LOCK_EX); // un seul calcul à la fois
try {
// Un autre processus a peut-être calculé la valeur pendant l'attente du verrou.
$entree = $this->lireEntree($cle);
if ($entree !== null && $entree['expire'] > microtime(true)) {
return $entree['valeur'];
}
$valeur = $calcul();
$this->ecrireEntree($cle, $valeur, microtime(true) + $dureeSecondes);
return $valeur;
} finally {
flock($verrou, LOCK_UN);
fclose($verrou);
}
}backend/src/Infrastructure/Cache/CacheFichier.phpRemarque la double vérification après l’obtention du verrou : c’est le patron double-checked locking. Pendant qu’un onglet attendait, un autre a peut-être déjà calculé la valeur ; inutile de recommencer. La durée de cache s’adapte à la volatilité de l’information : 1 s pour les compteurs, 10 s pour les services, 1 h pour le matériel (ta carte graphique ne change pas toutes les secondes).

Étape 3 : le domaine, le cœur métier
Le score de santé sur 100
Un seul chiffre de 0 à 100 résume l’état du PC. Chaque facteur a un poids, et un dépassement de seuil (avertissement ou critique) lui retire des points :
private const POIDS = [
'cpu' => 20, 'memoire' => 20, 'stockage' => 20,
'temperature' => 15, 'pare_feu' => 10, 'antivirus' => 10, 'batterie' => 5,
];
private function facteurSeuil(string $cle, string $libelle, float $valeur, string $unite, array $seuils, string $prefixe): array
{
$avertissement = (float) ($seuils["seuil_{$prefixe}_avertissement"] ?? 80);
$critique = (float) ($seuils["seuil_{$prefixe}_critique"] ?? 95);
$statut = $valeur >= $critique ? 'critique' : ($valeur >= $avertissement ? 'avertissement' : 'ok');
return $this->construire($cle, $libelle, round($valeur, 1), $unite, $statut);
}backend/src/Domain/Service/CalculateurSante.phpCette classe ne fait aucun appel à Windows ni à MySQL : elle reçoit des valeurs et renvoie un score. C’est du code pur, déterministe, idéal pour écrire tes premiers tests unitaires avec PHPUnit. Les seuils, eux, viennent de la table parametres : tu les ajustes depuis l’interface, sans toucher au code.
Les règles d’automatisation
Une règle se lit comme une phrase : « si <métrique> <opérateur> <seuil> pendant <durée>, alors <action> ». Toute l’intelligence se trouve dans l’entité elle-même :
public function evaluer(?float $valeur, DateTimeImmutable $maintenant): bool
{
if (!$this->active || $valeur === null) {
return false;
}
// Condition fausse : on remet le chronomètre à zéro
if (!$this->operateur->comparer($valeur, $this->seuil)) {
$this->conditionDepuis = null;
return false;
}
// Condition vraie : depuis quand ?
$this->conditionDepuis ??= $maintenant;
$soutenue = ($maintenant->getTimestamp() - $this->conditionDepuis->getTimestamp()) >= $this->dureeSecondes;
// Anti-rafale : on ne redéclenche pas pendant le délai de réactivation
$enPause = $this->dernierDeclenchement !== null
&& ($maintenant->getTimestamp() - $this->dernierDeclenchement->getTimestamp()) < $this->delaiReactivationSecondes;
// …
}backend/src/Domain/Entite/RegleAutomatisation.phpDeux notions évitent les fausses alertes, le grand défaut des outils de supervision mal réglés :
- La durée soutenue : un pic de CPU d’une seconde au lancement d’un programme n’est pas un problème ; 60 secondes à 95 % en est un.
- Le délai de réactivation : une fois l’alerte levée, l’application ne te harcèle pas toutes les 3 secondes pendant que tu règles le souci.
Les opérateurs et les métriques sont des enums PHP 8.1 adossés à des chaînes : il devient impossible d’enregistrer une métrique qui n’existe pas, et la conversion depuis la base se fait avec Metrique::from().
enum Metrique: string
{
case Cpu = 'cpu';
case Memoire = 'memoire';
case Stockage = 'stockage';
case Temperature = 'temperature';
case Batterie = 'batterie';
// …
}backend/src/Domain/Enum/Metrique.phpÉtape 4 : l’API REST
Un seul point d’entrée
Toutes les requêtes /api/* arrivent dans backend/public/index.php, qui tient en trois lignes :
<?php
$conteneur = require dirname(__DIR__) . '/bootstrap.php';
$conteneur->noyau()
->gerer(static fn () => Requete::depuisGlobales())
->envoyer();backend/public/index.phpLa table de routage
Lisible d’un coup d’œil, avec le niveau d’accès de chaque route :
$r->ajouter('GET', '/session', [Auth::class, 'session'], Routeur::PUBLIC);
$r->ajouter('POST', '/session/connexion', [Auth::class, 'connecter'], Routeur::PUBLIC);
$r->ajouter('GET', '/temps-reel', [Surveillance::class, 'tempsReel']);
$r->ajouter('GET', '/systeme/{ressource}', [Surveillance::class, 'ressource']);
$r->ajouter('DELETE', '/processus/{pid}', [Surveillance::class, 'terminerProcessus'], Routeur::ADMINISTRATEUR);
$r->ajouter('GET', '/historique', [Historique::class, 'historique']);backend/src/Presentation/Http/routes.phpTrois niveaux d’accès : PUBLIC (sans connexion), connecté (par défaut) et ADMINISTRATEUR. Les verbes HTTP ont leur vrai sens : GET lit, POST crée, PUT modifie, DELETE supprime.
Le noyau : le chef d’orchestre
Pour chaque requête, le Noyau enchaîne les vérifications avant d’appeler le contrôleur. C’est un mini-pipeline de middlewares, sans framework :
$route = $this->routeur->resoudre($requete->methode, $requete->chemin);
// 1. Protection CSRF sur toute écriture
if (!$requete->estLecture() && !$session->verifierJetonCsrf($requete->entete('X-CSRF-Token'))) {
throw new AccesRefuse('Jeton de sécurité invalide ou expiré. Rechargez la page.');
}
// 2. Authentification
if ($route['acces'] !== Routeur::PUBLIC) {
$utilisateurId = $session->utilisateurId() ?? throw new NonAuthentifie();
// 3. Autorisation
if ($route['acces'] === Routeur::ADMINISTRATEUR && !$utilisateur->role->peutModifier()) {
throw new AccesRefuse();
}
}
// 4. Appel du contrôleur
return $controleur->$action($requete);backend/src/Presentation/Http/Noyau.phpLes exceptions métier sont converties en réponses JSON avec le bon code HTTP (401 non connecté, 403 interdit, 422 données invalides…). Toutes les réponses partagent le même format, ce qui simplifie énormément le frontend :
{ "succes": true, "donnees": { "cpu": { "pourcentage": 36.4 } } }
{ "succes": false, "erreur": { "message": "Identifiant ou mot de passe incorrect.", "details": {} } }Format des réponsesSi une table n’existe pas (erreur SQL 42S02), le noyau lance l’installateur puis rejoue la requête. C’est pour ça que l’installation se résume à « ouvre la page ».
Étape 5 : sécuriser ton moniteur système Windows
Une application capable de terminer des processus sur ton PC doit être sérieusement protégée. Voici les menaces prises en compte et la réponse apportée à chacune :
| Menace | Protection |
|---|---|
| Accès depuis le réseau | Require local (réglage par défaut de WampServer) : seule ta machine peut ouvrir l’application |
| Vol de mot de passe en base | Hachage Argon2id avec password_hash() |
| Force brute | Verrouillage du compte 15 min après 5 échecs |
| Vol de session | Cookie HttpOnly + SameSite=Strict, identifiant régénéré à la connexion |
| CSRF | Jeton obligatoire dans l’en-tête X-CSRF-Token pour tout POST / PUT / DELETE |
| Erreur de manipulation | Processus système et Wamp protégés contre l’arrêt |
| Lecture du code source | Le dossier backend/ n’est jamais servi par Apache |
Le cas d’utilisation de connexion illustre plusieurs de ces protections à la fois :
if ($utilisateur !== null && $utilisateur->estVerrouille($maintenant)) {
throw new NonAuthentifie("Compte temporairement verrouillé après plusieurs échecs. Réessayez dans {$minutes} minute(s).");
}
if ($utilisateur === null || !$utilisateur->actif || !$this->hacheur->verifier($motDePasse, $utilisateur->motDePasseHache)) {
if ($utilisateur !== null) {
$utilisateur->enregistrerEchec($this->tentativesMax, $this->minutesVerrouillage, $maintenant);
} else {
// Temps constant : on ne révèle pas si l'identifiant existe
password_verify($motDePasse, '$2y$10$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG');
}
throw new NonAuthentifie('Identifiant ou mot de passe incorrect.');
}backend/src/Application/CasUtilisation/Authentification/SeConnecter.phpQuand l’identifiant n’existe pas, on calcule quand même un hachage. Sinon, un attaquant pourrait deviner les comptes existants en mesurant le temps de réponse : une réponse plus rapide trahirait un compte inexistant. Le message d’erreur est d’ailleurs volontairement identique dans les deux cas.


Le module Sécurité applique la même philosophie au poste lui-même : il interroge l’espace WMI root/SecurityCenter2 et root/Microsoft/Windows/Defender pour vérifier l’antivirus, la protection en temps réel, les définitions, le pare-feu (profils domaine, privé, public), l’UAC, BitLocker, Secure Boot, et détecte les ports sensibles en écoute (RPC, SMB, MySQL…) avec un conseil de remédiation.
Étape 6 : le frontend temps réel du moniteur système Windows
Une application monopage sans framework
Le frontend utilise les modules ES natifs du navigateur (import / export). Chaque page est un module chargé à la demande avec import() dynamique quand tu cliques dans le menu : la première ouverture est ultra-rapide. Le routage repose sur le hash de l’URL (#/processeur, #/reseau…), ce qui ne demande aucun réglage serveur.
Le client API
Toutes les requêtes passent par une petite fonction qui gère le JSON, le jeton CSRF et les erreurs de manière centralisée :
async function requete(methode, chemin, corps) {
const entetes = { Accept: 'application/json' };
if (corps !== undefined) entetes['Content-Type'] = 'application/json';
if (methode !== 'GET' && jetonCsrf) entetes['X-CSRF-Token'] = jetonCsrf;
const reponse = await fetch(`api${chemin}`, {
method: methode,
headers: entetes,
body: corps !== undefined ? JSON.stringify(corps) : undefined,
credentials: 'same-origin',
});
const contenu = await reponse.json();
if (!reponse.ok || !contenu?.succes) {
// Session expirée : toute l'application est prévenue d'un coup
if (reponse.status === 401) {
window.dispatchEvent(new CustomEvent('sd:session-expiree'));
}
throw new ErreurApi(contenu?.erreur?.message, reponse.status);
}
return contenu.donnees;
}
export const api = {
get: (chemin) => requete('GET', chemin),
post: (chemin, corps = {}) => requete('POST', chemin, corps),
// …
};frontend/assets/js/core/api.jsLe flux temps réel et le patron observateur
Un seul objet interroge /temps-reel toutes les 3 secondes et diffuse chaque mesure à toutes les jauges et à tous les graphiques abonnés. C’est le patron observateur, construit sur l’EventTarget natif du navigateur :
class FluxTempsReel extends EventTarget {
/** Le rappel reçoit (mesure, historique) ; retourne la fonction de désabonnement. */
abonner(rappel) {
const ecouteur = (e) => rappel(e.detail.mesure, e.detail.historique);
this.addEventListener('mesure', ecouteur);
if (this.dernier) rappel(this.dernier, this.historique);
return () => this.removeEventListener('mesure', ecouteur);
}
async tick() {
if (document.hidden) return this.definirEtat('pause'); // onglet masqué : on économise
const mesure = await api.get('/temps-reel');
this.historique.push(mesure);
if (this.historique.length > TAILLE_HISTORIQUE) this.historique.shift();
this.dispatchEvent(new CustomEvent('mesure', { detail: { mesure, historique: this.historique } }));
}
}frontend/assets/js/core/temps-reel.jsUne page n’a plus qu’à écrire une ligne pour s’abonner…
const seDesabonner = flux.abonner((mesure) => jauge.definir(mesure.cpu.pourcentage));
// … et quand on quitte la page :
seDesabonner();frontend/assets/js/pages/processeur.js…et à appeler seDesabonner() en la quittant, pour éviter les fuites mémoire (un piège classique des SPA « faites main »).
Quand l’onglet est masqué, l’événement visibilitychange met le flux en pause. Ton PC n’est pas sollicité pour rien, et le badge « En direct » repasse au vert dès que tu reviens.


Étape 7 : alertes du moniteur système Windows et collecte 24 h/24
Le moteur de règles
À chaque mesure enregistrée, le MoteurRegles évalue toutes les règles actives et exécute l’action associée quand une condition est remplie :
public function evaluer(Instantane $instantane): array
{
$creees = [];
foreach ($this->regles->actives() as $regle) {
$valeur = $instantane->valeur($regle->metrique);
if ($regle->evaluer($valeur, $instantane->horodatage) && $valeur !== null) {
$alerte = $this->executer($regle, $valeur, $instantane);
if ($alerte !== null) {
$creees[] = $alerte->versTableau();
}
}
}
return $creees;
}backend/src/Application/Service/MoteurRegles.phpTrois actions sont possibles : lever une alerte (visible dans la cloche en haut de l’écran), écrire dans le journal, ou terminer un processus — par exemple un programme qui sature la mémoire, sauf s’il figure dans la liste des processus protégés.

Collecter 24 h/24, même interface fermée
Tant qu’un onglet est ouvert, l’historique se remplit tout seul. Pour enregistrer des mesures en permanence, un script crée une tâche planifiée Windows qui lance le collecteur en ligne de commande chaque minute :
backend\bin\planifier-collecte.batÀ exécuter en tant qu’administrateurEn coulisse, il s’appuie sur schtasks, l’outil natif de Windows :
schtasks /Create /TN "SystemDashboard-Collecteur" /SC MINUTE /MO 1 /RU SYSTEM /RL HIGHEST /F ^
/TR "\"%PHP%\" -c \"%INI%\" \"%RACINE%collecteur.php\""backend/bin/planifier-collecte.batLa tâche tourne sous le compte SYSTEM avec les privilèges les plus élevés : elle a donc accès aux compteurs protégés et fonctionne même quand personne n’est connecté. Les mesures de plus de 30 jours (valeur réglable) sont purgées automatiquement pour que la base reste légère.

Visite guidée du moniteur système Windows
L’interface existe en thème sombre et en thème clair (bouton soleil/lune en haut à droite). Voici les modules que nous n’avons pas encore vus.
Batterie : niveau, usure et tension
Sur un portable, la page Batterie compare la capacité pleine charge à la capacité d’origine pour estimer l’usure. Ici, 27 % d’usure : la batterie ne tient plus que 73 % de sa capacité neuve, un indicateur précieux avant de la remplacer.

Processus : tri, recherche et arrêt sécurisé
La liste se rafraîchit toutes les 3 secondes et se trie par processeur, mémoire, disque ou threads. Le bouton d’arrêt n’apparaît pas sur les processus système (cadenas) : impossible de tuer System ou httpd.exe par erreur.

Fichiers : qu’est-ce qui prend toute la place ?
Indique un dossier, clique sur Analyser : l’application parcourt l’arborescence et affiche les sous-dossiers les plus volumineux ainsi que la répartition par type de fichier.

Diagnostics : réseau et benchmarks
Sept tests à la demande : latence (ping), résolution DNS, temps de réponse web (avec temps de négociation TLS), débit descendant, et trois benchmarks maison (SHA-256 mono-cœur, copie mémoire, écriture/lecture disque synchronisée). Chaque résultat est historisé.

Le thème clair

Dépannage du moniteur système Windows : erreurs courantes
| Problème | Solution |
|---|---|
| « L’extension PHP com_dotnet est requise » | Active com_dotnet dans les extensions PHP de Wamp, puis redémarre tous les services |
| « Base de données inaccessible » | Vérifie que MySQL est démarré (icône Wamp verte) et les identifiants dans config.local.php |
Page blanche ou 404 sur /api | Active mod_rewrite dans Apache et vérifie AllowOverride All |
| L’interface ne reflète pas une modification | Recharge sans cache : Ctrl + F5 |
| Température « non disponible » | Ton matériel n’expose pas de capteur via WMI (très fréquent sur portable) : c’est normal |
Installé ailleurs que /system-dashboard/ | Adapte RewriteBase dans le .htaccess racine |
| Une erreur serveur 500 | Consulte backend/storage/logs/php-erreurs.log |
Améliorer ton moniteur système Windows
Le projet est volontairement conçu pour être étendu. Quelques idées pour le faire évoluer :
- Notifications : envoyer les alertes critiques par e-mail, Telegram ou Discord en ajoutant une valeur à l’enum
ActionAutomatisation. - Supervision de plusieurs PC : un petit agent sur chaque machine qui pousse ses mesures vers un serveur central.
- Support de Linux : écrire une
SondeLinuxqui lit/proc/statet/proc/meminfo— grâce à l’architecture, rien d’autre ne change. - Tests automatisés avec PHPUnit :
CalculateurSanteetRegleAutomatisationsont du code pur, parfaits pour débuter. - Temps réel par push avec les Server-Sent Events à la place de l’interrogation toutes les 3 secondes.
- Application de bureau avec Electron ou Tauri, pour une icône dans la barre des tâches.
FAQ : moniteur système Windows en PHP
System Dashboard est-il gratuit ?
Oui. Le code source est disponible gratuitement sur GitHub : tu peux l’utiliser, l’étudier, le modifier et le partager.
Peut-on surveiller un PC Windows avec PHP ?
Oui, grâce à l’extension COM (com_dotnet) qui donne accès à WMI, l’interface officielle de Windows pour interroger le matériel, les performances, les services et la sécurité.
System Dashboard fonctionne-t-il sur Mac ou Linux ?
Pas en l’état : la sonde utilise WMI, propre à Windows. L’architecture est cependant prévue pour ajouter une autre sonde, par exemple une SondeLinux qui lit /proc.
Mes données partent-elles sur Internet ?
Non. Seules deux fonctions contactent l’extérieur : la géolocalisation des connexions (désactivable dans les Paramètres) et les diagnostics réseau, qui ne s’exécutent que lorsque tu les lances.
Est-ce que le moniteur ralentit mon PC ?
Très peu : une mesure complète prend environ 50 ms toutes les 3 secondes grâce aux compteurs bruts et au cache, et le flux se met en pause quand l’onglet est masqué.
Puis-je consulter le tableau de bord depuis mon téléphone ?
Par défaut non : WampServer n’accepte que les connexions locales, et c’est voulu. Pour un accès distant, passe par un VPN comme WireGuard ou Tailscale plutôt que d’exposer l’application sur Internet.
Ça fonctionne avec MariaDB ?
Oui, à partir de MariaDB 10.6, ainsi qu’avec MySQL 8 et plus.
Conclusion
Tu as maintenant un vrai moniteur système Windows, codé de tes mains… et surtout, tu as vu comment construire une application PHP propre et professionnelle sans framework :
- une Clean Architecture qui isole le métier des détails techniques ;
- un accès à Windows via WMI, optimisé grâce aux compteurs bruts et à un cache verrouillé ;
- une API REST sécurisée (Argon2id, CSRF, verrouillage, rôles, protection contre les attaques temporelles) ;
- un frontend temps réel en JavaScript natif, basé sur le patron observateur ;
- un moteur de règles qui veille sur ta machine même quand tu n’es pas là.
Clone le projet, teste-le, casse-le, améliore-le — et si tu ajoutes les notifications Discord ou la supervision multi-PC, n’hésite pas à partager ton travail !
Envie d’autres projets PHP sans framework ? Découvre aussi comment créer une application de planning en PHP et MySQL, une application de gestion de stock en PHP/MySQL ou ton Netflix maison MediaCloud. Et si ton API renvoie une erreur 404 ou 500, notre guide des codes HTTP t’aidera à la corriger.

