Inventaire — Étape 4 : dashboard, graphes et historique

Étape 4 du projet inventaire : dashboard global du parc, KPI et graphique d'évolution avec Chart.js.

    5tq
  • Exercice

Tu as maintenant une fiche par PC. Cette étape ajoute la vue d'ensemble du parc, un graphique d'évolution et quelques KPI globaux.


🎯 Objectif de l'étape

Construire :

  1. Un dashboard (page d'accueil /inventaire) listant tous les PC avec leurs indicateurs clés.
  2. Sur la fiche PC, un graphique d'évolution de l'espace disque libre au fil des rapports.
  3. Quelques indicateurs globaux sur le parc (nombre de PC, RAM totale, espace disque cumulé, dernier rapport reçu).

Partie 1 — Lister tous les PC

Ajoute dans src/storage.php :

function lister_pcs(): array
{
    if (!is_dir(DATA_ROOT)) {
        return [];
    }

    $resultats = [];
    foreach (scandir(DATA_ROOT) as $entree) {
        if ($entree === '.' || $entree === '..') continue;
        $fichier = DATA_ROOT . "/$entree/latest.json";
        if (is_file($fichier)) {
            $data = json_decode(file_get_contents($fichier), true);
            if (is_array($data)) {
                $resultats[] = $data;
            }
        }
    }

    // Tri par hostname
    usort($resultats, fn($a, $b) => strcmp($a['hostname'] ?? '', $b['hostname'] ?? ''));
    return $resultats;
}

💡 scandir est volontairement simple ici. Sur un vrai parc avec 10 000 PC, on indexerait dans une vraie base. Sur notre parc-classe à 8 PC, ça marche très bien et ça reste lisible.


Partie 2 — Le dashboard

Crée public/dashboard.php :

<?php
declare(strict_types=1);
require __DIR__ . '/../src/storage.php';

$pcs = lister_pcs();

// Indicateurs globaux
$nb         = count($pcs);
$ramTotale  = 0;
$diskTotal  = 0;
$diskLibre  = 0;
$dernierRapport = null;

foreach ($pcs as $pc) {
    $ramTotale += (float)($pc['memory']['totalGo'] ?? 0);
    foreach (($pc['disks'] ?? []) as $d) {
        $diskTotal += (float)($d['totalGo'] ?? 0);
        $diskLibre += (float)($d['freeGo']  ?? 0);
    }
    $t = $pc['timestamp'] ?? null;
    if ($t && (!$dernierRapport || $t > $dernierRapport)) {
        $dernierRapport = $t;
    }
}

$titre = "Dashboard";
require __DIR__ . '/partials/header.php';
?>

<h1 class="h2 mb-4">Parc informatique — vue d'ensemble</h1>

<!-- KPI cards -->
<div class="row g-3 mb-4">
    <div class="col-md-3">
        <div class="card text-center">
            <div class="card-body">
                <div class="display-5"><?= $nb ?></div>
                <div class="text-secondary">PC inventoriés</div>
            </div>
        </div>
    </div>
    <div class="col-md-3">
        <div class="card text-center">
            <div class="card-body">
                <div class="display-5"><?= number_format($ramTotale, 0, ',', ' ') ?></div>
                <div class="text-secondary">Go de RAM cumulés</div>
            </div>
        </div>
    </div>
    <!-- À toi : disque total, disque libre, % de rapports < 24 h -->
</div>

<!-- Liste des PC -->
<h2 class="h5">PC</h2>
<div class="row g-3">
<?php foreach ($pcs as $pc):
    $hostname = $pc['hostname'] ?? '?';
    $ramT     = (float)($pc['memory']['totalGo'] ?? 0);
    $disque0  = $pc['disks'][0] ?? null;
    $usageDisk = ($disque0 && $disque0['totalGo'])
        ? round((1 - $disque0['freeGo']/$disque0['totalGo']) * 100)
        : 0;
?>
    <div class="col-md-6 col-lg-4">
        <a href="/inventaire/pc/<?= urlencode($hostname) ?>"
           class="card h-100 text-decoration-none">
            <div class="card-body">
                <h5 class="card-title"><?= htmlspecialchars($hostname) ?></h5>
                <p class="text-secondary small mb-2">
                    <?= htmlspecialchars($pc['system']['os'] ?? '') ?>
                </p>
                <div class="d-flex justify-content-between">
                    <span>💾 <?= $ramT ?> Go</span>
                    <span>📀 <?= $usageDisk ?> % utilisé</span>
                </div>
            </div>
        </a>
    </div>
<?php endforeach; ?>
</div>

<?php require __DIR__ . '/partials/footer.php'; ?>

💡 Astuce : un <a> avec class="card" donne une carte cliquable avec toute la zone active. Très bon pour le tactile.


Partie 3 — Préparer l'historique pour le graphe

Dans src/storage.php, ajoute :

function charger_historique(string $hostname, int $limite = 30): array
{
    $dossier = DATA_ROOT . "/$hostname/history";
    if (!is_dir($dossier)) return [];

    $fichiers = glob($dossier . '/*.json');
    sort($fichiers); // ordre alphabétique = ordre chronologique grâce à Ymd-His

    // Ne garder que les N derniers
    $fichiers = array_slice($fichiers, -$limite);

    $rapports = [];
    foreach ($fichiers as $f) {
        $r = json_decode(file_get_contents($f), true);
        if (is_array($r)) $rapports[] = $r;
    }
    return $rapports;
}

⚠️ Pourquoi Ymd-His est important ? Parce que sort() trie en ordre alphabétique sur le nom de fichier. Avec un format Ymd-His (ex : 20260513-103015), l'ordre alphabétique = l'ordre chronologique. Si tu avais utilisé 13-05-2026, ton historique serait dans le désordre.


Partie 4 — Le graphique d'évolution avec Chart.js

Sur la fiche PC (public/pc.php), récupère l'historique :

$historique = charger_historique($hostname, 30);

// On prépare les données pour le JS
$labels    = [];
$freeDataC = [];
foreach ($historique as $h) {
    $labels[] = $h['timestamp'] ?? '';
    $diskC = null;
    foreach (($h['disks'] ?? []) as $d) {
        if (($d['letter'] ?? '') === 'C') { $diskC = $d; break; }
    }
    $freeDataC[] = $diskC ? (float)$diskC['freeGo'] : null;
}

Et ajoute dans la page un canvas + un petit script :

<section class="mb-4">
    <h2 class="h5">Espace libre — disque C: (Go)</h2>
    <canvas id="chartDisque" height="100"></canvas>
</section>

<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script>
const labels   = <?= json_encode($labels) ?>;
const freeData = <?= json_encode($freeDataC) ?>;

new Chart(document.getElementById('chartDisque'), {
    type: 'line',
    data: {
        labels: labels.map(t => new Date(t).toLocaleString('fr-BE', {
            day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit'
        })),
        datasets: [{
            label: 'Espace libre C: (Go)',
            data: freeData,
            tension: 0.3,
            fill: true,
            backgroundColor: 'rgba(99, 102, 241, 0.15)',
            borderColor: 'rgba(99, 102, 241, 1)',
            pointRadius: 3
        }]
    },
    options: {
        responsive: true,
        plugins: { legend: { display: false } },
        scales: {
            y: { beginAtZero: false, ticks: { color: '#9ca3af' } },
            x: { ticks: { color: '#9ca3af' } }
        }
    }
});
</script>

💡 Pour tester sans attendre, rejoue ton script PowerShell 4-5 fois d'affilée en modifiant un peu les valeurs (lance un téléchargement, ferme une appli…). Tu auras 5 points sur ton graphique.


Partie 5 — Tests intermédiaires

Trois choses à vérifier :

a) Performance

Sur le dashboard, ouvre les outils de développement → Réseau. Le HTML doit charger en moins de 200 ms même avec 50 PC simulés. Si c'est plus lent : tu fais probablement de la lecture JSON inutilement en boucle. Mets un var_dump(memory_get_peak_usage()) pour t'en rendre compte.

b) Cas vides

Renomme temporairement le dossier data/pcs/ en data/pcs.bak/ et recharge /inventaire. Tu dois voir un message « Aucun PC inventorié » et non un crash.

c) Mode sombre

Bascule data-bs-theme="dark"light dans header.php. Le rendu doit rester propre dans les deux modes. Ajuste app.css si besoin pour les zones non couvertes par Bootstrap (par exemple le tracé du graphe).


Partie 6 — Indicateur d'écoresponsabilité (au choix)

Ajoute au moins l'un des indicateurs suivants au dashboard :

  • % de PC avec un disque > 85 % plein (besoin d'action).
  • Moyenne de RAM utilisée sur la dernière semaine (parc sous-utilisé ?).
  • Nombre d'applications inutiles potentielles (apps installées mais jamais dans le top processus de la semaine).
  • Âge moyen des derniers rapports (parc bien suivi ou abandonné).

🤖 Travailler avec l'IA — pour choisir une visualisation

Bon prompt :

J'ai pour chaque PC une liste de rapports horodatés contenant disks[i].freeGo. Je veux un seul indicateur visuel pour le dashboard, qui montre d'un coup d'œil les PC dont l'espace disque diminue rapidement. Propose-moi 3 façons de visualiser ça avec Chart.js, avec leurs avantages et inconvénients (pas de code). Je choisirai après.

L'IA propose : sparklines dans chaque carte, heatmap, classement, bar chart différentiel… Tu choisis. Ensuite tu re-prompts pour implémenter celle que tu as retenue.

C'est la séquence : explorer → choisir → implémenter. Trois prompts, pas un.


✅ Critères de réussite

  • [ ] Le dashboard montre la liste des PC et au moins 3 KPI.
  • [ ] Les cartes sont cliquables et mènent à la fiche PC correspondante.
  • [ ] Le graphique Chart.js s'affiche correctement (axes lisibles).
  • [ ] Le graphique se met à jour quand tu envoies un nouveau rapport.
  • [ ] Tu as au moins un indicateur d'écoresponsabilité.
  • [ ] Aucun crash sur un parc vide.

⚠️ Pièges fréquents

Piège Solution
Chart.js ne s'affiche pas Vérifier le <canvas> dans le DOM avant le <script>
Les dates s'affichent en anglais toLocaleString('fr-BE', …)
glob() renvoie false Le dossier n'existe pas — is_dir d'abord
Historique dans le désordre Format Ymd-His impératif (ou tri sur timestamp)
Le dashboard rame avec beaucoup de PC Ne lire que latest.json, pas tout l'historique

Suite

À l'étape 5, on ajoute ton module spécifique — celui qui fait que ton PC se distingue dans le parc. Réseau, sécurité, USB, etc., selon ton numéro.

Pour aller plus loin