From db223b805cf1963010572c4ff1493c4fb81ae8ed Mon Sep 17 00:00:00 2001 From: e025532 Date: Thu, 24 Sep 2026 15:36:09 +0200 Subject: [PATCH] Add Veeam and NetBackup support to inventory and reporting - Introduced Veeam and NetBackup version tracking in `Inventory.php` for backup validation. - Enhanced backup report in `Backups.php` with improved SQL queries and support for short hostname normalization. - Updated `navbar.html` and related navigation components for better usability and accessibility. - Refactored ActiveDirectory scripts for better modularity and event handling. - Added `AGENTS.md` and `modele.php` as templates for documentation and new page creation. --- AD/LocalAdmins.php | 139 +++++++++------------- AGENTS.md | 222 +++++++++++++++++++++++++++++++++++ X/Inventory.php | 45 ++++++- modele.php | 67 +++++++++++ modele_full.php | 278 ++++++++++++++++++++++++++++++++++++++++++++ navbar.html | 10 +- reports/Backups.php | 213 +++++++++++++++++++++++++++------ 7 files changed, 847 insertions(+), 127 deletions(-) create mode 100644 AGENTS.md create mode 100644 modele.php create mode 100644 modele_full.php diff --git a/AD/LocalAdmins.php b/AD/LocalAdmins.php index 9401cc7..e5d397d 100644 --- a/AD/LocalAdmins.php +++ b/AD/LocalAdmins.php @@ -1223,12 +1223,6 @@ true ); - } else { - - $table.bootstrapTable( - 'resetView' - ); - } @@ -1236,8 +1230,9 @@ /* - * Laisse Bootstrap Table construire son DOM - * avant de mesurer la hauteur disponible. + * Laisse Bootstrap Table construire (ou reconstruire) + * son DOM avant de mesurer la hauteur disponible. + * updateTableHeight fait lui-même le resetView avec la hauteur. */ setTimeout( function () { @@ -1280,87 +1275,54 @@ } - const $table = - $(target).find('table.report-table'); - - /* - * On laisse Bootstrap terminer complètement - * l'affichage de l'onglet avant de toucher à Bootstrap Table. + * IMPORTANT : on cible la table par son id. + * En mode hauteur fixe, Bootstrap Table clone le thead dans une + * seconde à laquelle il recopie la classe d'origine + * (report-table). Sélectionner par classe renverrait donc AUSSI ce + * clone (placé avant l'originale dans le DOM), et .data('bt-initialized') + * lirait le clone -> fausse "première init" -> double init -> tableau vidé. + * Le clone ne reçoit jamais d'id : [id] ne matche que la vraie table. */ - setTimeout( - function () { + const $table = + $(target).find('table.report-table[id]'); - function initTable($table) { - - if ($table.length === 0) { - return; - } + if ($table.length === 0) { + return; + } - /* - * Important : - * ne jamais initialiser Bootstrap Table - * pendant que la table est cachée. - */ - if (!$table.is(':visible')) { - return; - } + if (!$table.data('bt-initialized')) { + + /* + * Première ouverture de l'onglet : + * on initialise et on dimensionne pendant que la table est visible. + */ + initTable($table); + + } else { + + /* + * Onglet déjà construit : display:none / show conserve tout le rendu + * de Bootstrap Table. On ne refait PAS de resetView, qui viderait + * le corps du tableau. + */ + syncTheme($table); - if (!$table.data('bt-initialized')) { + /* + * Sauf si un resize a eu lieu pendant que l'onglet était masqué : + * dans ce cas seulement, on recalcule la hauteur maintenant. + */ + if ($table.data('needs-resize')) { - /* - * Première ouverture de l'onglet : - * Bootstrap Table construit son DOM. - */ - $table.bootstrapTable(); + $table.data('needs-resize', false); - $table.data( - 'bt-initialized', - true - ); + updateTableHeight($table); - } else { + } - /* - * Table déjà initialisée : - * on force Bootstrap Table à recalculer son affichage. - */ - $table.bootstrapTable( - 'resetView' - ); - - } - - - syncTheme($table); - - - /* - * Une fois le DOM Bootstrap Table construit, - * calcul de la hauteur disponible. - */ - setTimeout( - function () { - - $table.bootstrapTable( - 'resetView' - ); - - updateTableHeight( - $table - ); - - }, - 50 - ); - - } - - }, - 10 - ); + } } ); @@ -1377,7 +1339,7 @@ setTimeout( function () { - $('table.report-table').each( + $('table.report-table[id]').each( function () { syncTheme( @@ -1518,19 +1480,32 @@ debounce( function () { - $('table.report-table:visible').each( + $('table.report-table[id]').each( function () { const $table = $(this); - if ($table.data('bt-initialized')) { + if (!$table.data('bt-initialized')) { + return; + } + + + if ($table.is(':visible')) { updateTableHeight( $table ); + } else { + + /* + * On ne peut pas mesurer un onglet masqué : + * on marque la table pour recalcul à sa prochaine ouverture. + */ + $table.data('needs-resize', true); + } } @@ -1550,7 +1525,7 @@ setTimeout( function () { - $('table.report-table:visible').each( + $('table.report-table[id]:visible').each( function () { const $table = diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f672b2b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,222 @@ +# AGENTS.md — Instructions Codex + +## Contexte + +Ce projet est un environnement de développement hébergé sur une infrastructure +professionnelle. + +Le code présent dans ce workspace peut être modifié par Codex. + +IMPORTANT : +Les bases de données auxquelles l'application peut accéder contiennent des +DONNÉES DE PRODUCTION. + +La protection et l'intégrité de ces données sont prioritaires. + +--- + +## 1. Périmètre autorisé + +Codex peut : + +- Lire tous les fichiers du workspace courant. +- Rechercher librement dans les fichiers du workspace. +- Créer, modifier, renommer ou supprimer des fichiers dans le workspace + lorsque cela est nécessaire à la tâche demandée. +- Exécuter des commandes locales nécessaires à l'analyse du projet. +- Utiliser Git pour examiner : + - status + - diff + - log + - historique +- Analyser et modifier du code PHP, JavaScript, HTML, CSS, SQL et PowerShell + présent dans le workspace. + +Codex doit rester dans le workspace courant : + + E:\Sites\web-infra-reports + +Ne jamais modifier un fichier situé en dehors de ce workspace sans demander +explicitement l'autorisation. + +--- + +## 2. Bases de données — RÈGLE CRITIQUE + +Les bases de données accessibles depuis cette application doivent être +considérées comme des bases de PRODUCTION. + +### Opérations autorisées + +Codex peut effectuer des opérations SQL strictement en lecture seule : + + SELECT + +Il peut également : + +- analyser les requêtes SQL présentes dans le code ; +- écrire ou modifier du SQL dans les fichiers source ; +- proposer des requêtes SQL ; +- examiner la structure des données lorsqu'elle est accessible en lecture. + +### Opérations interdites + +Codex ne doit JAMAIS exécuter sur une base de données une commande susceptible +de modifier les données, la structure ou la configuration. + +Cela comprend notamment : + + INSERT + UPDATE + DELETE + MERGE + TRUNCATE + DROP + ALTER + CREATE + GRANT + REVOKE + +Ne jamais exécuter une procédure stockée ou une fonction SQL susceptible +d'avoir des effets de bord. + +En particulier : + + EXEC + EXECUTE + +ne doivent être utilisés que s'il est certain que l'opération est strictement +en lecture seule. + +En cas de doute : + + NE PAS EXÉCUTER LA REQUÊTE. + +Demander confirmation à l'utilisateur. + +--- + +## 3. Credentials et secrets + +Ne jamais : + +- afficher inutilement un mot de passe ; +- recopier un mot de passe dans un nouveau fichier ; +- ajouter un secret dans Git ; +- modifier les credentials existants sans demande explicite ; +- transmettre un credential ou un secret vers Internet. + +Si des credentials sont découverts dans le code, les considérer comme +confidentiels. + +Ne pas les inclure dans les réponses sauf nécessité absolue. + +--- + +## 4. Infrastructure externe + +Ne jamais modifier directement : + +- un autre site IIS ; +- une configuration IIS externe au workspace ; +- Active Directory ; +- DNS ; +- un serveur distant ; +- une machine virtuelle ; +- un partage réseau ; +- une tâche planifiée ; +- une configuration système Windows ; +- une base de données ; +- une ressource de production. + +Codex peut analyser le code qui effectue ces opérations et le modifier dans +le workspace. + +L'exécution réelle d'une opération d'administration doit être laissée à +l'utilisateur sauf demande explicite. + +--- + +## 5. Commandes PowerShell et shell + +Les commandes purement consultatives peuvent être exécutées librement. + +Exemples : + + rg + Get-Content + Select-String + Get-ChildItem + git status + git diff + git log + +Avant d'exécuter une commande ayant potentiellement des effets de bord, +vérifier qu'elle ne peut pas modifier une ressource de production. + +En cas de doute, demander confirmation. + +--- + +## 6. Modifications du code + +Avant une modification importante : + +1. comprendre le code existant ; +2. rechercher les dépendances et les appels concernés ; +3. conserver le comportement existant qui n'est pas concerné par la demande ; +4. privilégier une modification minimale plutôt qu'une réécriture inutile. + +Après modification : + +1. vérifier les fichiers modifiés ; +2. rechercher les éventuelles références impactées ; +3. examiner le diff ; +4. signaler clairement les modifications réalisées ; +5. signaler les points qui n'ont pas pu être testés. + +Ne jamais considérer qu'une modification est déployée en production. + +--- + +## 7. Tests + +Les tests locaux sans effet de bord sont autorisés. + +Ne jamais utiliser une ressource de production pour tester une opération +d'écriture. + +Si une fonctionnalité nécessite normalement une écriture en base de données, +modifier le code si demandé mais NE PAS effectuer l'écriture réelle. + +Indiquer à l'utilisateur que cette partie doit être testée manuellement dans +un environnement approprié. + +--- + +## 8. Accès Internet + +Ne pas envoyer vers un service externe : + +- code source propriétaire ; +- credentials ; +- noms d'utilisateurs ; +- données provenant des bases ; +- informations internes à l'entreprise. + +Si une recherche Internet est nécessaire, demander l'autorisation lorsque +Codex le requiert et limiter la requête aux informations strictement +nécessaires. + +--- + +## Principe général + +Codex dispose d'une autonomie importante sur le CODE contenu dans ce +workspace. + +Codex ne dispose d'aucune autonomie pour modifier les DONNÉES ou +l'INFRASTRUCTURE de production. + +Si une action peut avoir un effet de bord sur une ressource extérieure au +workspace, demander confirmation avant de l'exécuter. \ No newline at end of file diff --git a/X/Inventory.php b/X/Inventory.php index dc36e9e..04cd1f6 100644 --- a/X/Inventory.php +++ b/X/Inventory.php @@ -68,10 +68,12 @@ $UTDBES = end($versions); $UTDFI = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'fi'"))[0]['utd']; $UTDxs = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'xs'"))[0]['utd']; + $UTDveeam = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'veeam'"))[0]['utd']; + $UTDnetbackup = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'netbackup'"))[0]['utd']; ?> -
+
@@ -111,6 +113,8 @@
+ + @@ -342,7 +346,46 @@ } else { echo ""; } + //Veeam + $s1s = $s['VeeamState']; + $s1v = $s['VeeamVersion']; + if ($s1s == "Y") { + if ($s1v == $UTDveeam) { + echo ""; + } else { + echo ""; + } + }elseif($s1s == "E"){ + echo ""; + }elseif($s1s == "N"){ + echo ""; + } else { + if ($s1v == "") { + echo ""; + } else { + echo ""; + } + } + //Netbackup + $s1s = $s['nbState']; + $s1v = $s['nbVersion']; + if ($s1s == "Y") { + if ($s1v == $UTDnetbackup) { + echo ""; + } else { + echo ""; + } + }elseif($s1s == "E"){ + echo ""; + }elseif($s1s == "N"){ + } else { + if ($s1v == "") { + echo ""; + } else { + echo ""; + } + } echo ""; } diff --git a/modele.php b/modele.php new file mode 100644 index 0000000..87a8dfd --- /dev/null +++ b/modele.php @@ -0,0 +1,67 @@ + + + + + + + <?= $escapedPageTitle ?> - Web Infra Reports IT + + + + + + + + + + + + + +
+
+
+ +
+ +
+

+ +
+ +
+
+
+
+ + + + + + diff --git a/modele_full.php b/modele_full.php new file mode 100644 index 0000000..af300a1 --- /dev/null +++ b/modele_full.php @@ -0,0 +1,278 @@ + + + + + + + <?= $escape($pageTitle) ?> - Web Infra Reports IT + + + + + + + + + + + + + + + + +
+
+
+ +
+
+

+

Exemples réutilisables — données fictives et actions de démonstration.

+ + + +
+

Cartes de synthèse

+
+ +
+
+ +
+
+
+ +
+
+ + +
+

Tableau avec recherche, tri et export

+
+
BES Fusion Inv. XenSamVeeamNetbackup
$s1v $s1s$s1v $s1sExcludedNope :)MISSING$s1v $s1s$s1v $s1s$s1v $s1sExcludedMISSING$s1v $s1s
+ + + + + + + + + + + + + + + + + + + +
Inventaire fictif : six serveurs de démonstration.
ServeurSystèmeÉtatUtilisation (%)Action
+ + + + +
+

Boutons et fenêtre modale

+
+
+ + + + + + +
+
+ + + + +
+

+
+
+ + +
+

Formulaire

+
+
+
+
+
+
+
Go
+
+
+
+
+
+

+
+
+
+ + +
+

Alertes et indicateurs

+
+
+
Information : exemple de message contextuel.
+
Succès : vérification terminée.
+
Attention : un élément nécessite une vérification.
+
Erreur : exemple de message d’échec.
+
ConformeÀ vérifierErreurInconnu
+
Capacité utilisée : 65 %
+
65 %
+
+

Répartition des serveurs

+
4 disponibles, 1 à vérifier, 1 indisponible.
+
  • 4Disponibles
  • 1À vérifier
  • 1Indisponibles
+
+
+
+ + +
+

Onglets et accordéon

+
+ +
+

Zone de synthèse : placer ici les informations principales.

+
Environnement
Démonstration
Source
Données fictives intégrées à la page
+
+
+
+

+
Ce bloc peut accueillir un tableau secondaire, une liste ou un détail technique.
+
+
+
+
+ + + + + + + + + + diff --git a/navbar.html b/navbar.html index da5dfae..0c7a5c7 100644 --- a/navbar.html +++ b/navbar.html @@ -60,15 +60,6 @@ -
  • - - AD - -
  • @@ -157,6 +148,7 @@
  • - - + - Local Admins
  • diff --git a/reports/Backups.php b/reports/Backups.php index 12b9352..4e2cb4e 100644 --- a/reports/Backups.php +++ b/reports/Backups.php @@ -1,3 +1,4 @@ + @@ -73,13 +74,22 @@ DATEADD(DAY, -2, GETDATE())"); $active = []; -foreach ($activeRows as $r) { $active[strtoupper(trim($r['name']))] = true; } +foreach ($activeRows as $r) { $active[normalize_host($r['name'])] = true; } /* -------------------------------------------------------------- 1. Veeam */ $veeam = []; $rows = Invoke_Infra("SELECT * FROM Veeam_Reporting"); foreach ($rows as $row) { - $key = strtoupper(trim($row['ClientName'])); + $key = normalize_host($row['ClientName']); if (!isset($active[$key])) continue; $ageGood = days_since($row['LastSuccess']); @@ -149,55 +159,187 @@ foreach ($rows as $row) { } /* ---------------------------------------------------------- 2. NetBackup */ +/* + Source principale : dbo.NetBackupJobs + Master pris en compte : dun-bkp-01 uniquement. + + Pour chaque client NetBackup : + - le dernier FULL/INCREMENTAL donne l'état courant ; + - le dernier FULL/INCREMENTAL avec status <= 1 donne "Last Good Backup" ; + - dbo.NBRC fournit la description du code retour. + + La page reste une synthèse VM : seuls les clients présents dans cmdb_vms + avec un inventaire récent sont retenus. +*/ $netbackup = []; -$rows = Invoke_Infra("SELECT * FROM VMs_Backup - WHERE (Owner LIKE 'DUN-VMH%' OR Owner LIKE 'MDK-VMH%') - AND name NOT LIKE 'WS%' - AND owner NOT LIKE '%WKG%' - AND owner NOT LIKE '%VMH-WM%' - ORDER BY name"); + +$rows = Invoke_Infra(" +WITH ScheduledJobs AS +( + SELECT + j.master, + j.job, + j.client, + j.policy, + j.schedule_type, + j.status, + j.start_date, + j.end_date, + j.size_kb, + + COALESCE(j.end_date, j.start_date) AS run_date, + + MAX( + CASE + WHEN j.status <= 1 + THEN COALESCE(j.end_date, j.start_date) + ELSE NULL + END + ) OVER ( + PARTITION BY j.client + ) AS last_success, + + ROW_NUMBER() OVER + ( + PARTITION BY j.client + ORDER BY + COALESCE(j.end_date, j.start_date) DESC, + j.job DESC + ) AS rn + + FROM dbo.NetBackupJobs j + + WHERE + j.master like 'dun-bkp-01%' + AND j.schedule_type IN ('full', 'incremental') +), +Latest AS +( + SELECT * + FROM ScheduledJobs + WHERE rn = 1 +) +SELECT + l.master, + l.job, + l.client, + l.policy, + l.schedule_type, + l.status, + l.start_date, + l.end_date, + l.run_date, + l.last_success, + l.size_kb, + r.RC_DESC AS status_desc + +FROM Latest l + +LEFT JOIN dbo.NBRC r + ON r.RC = l.status + +ORDER BY l.client +"); + +if (!is_array($rows)) $rows = []; + foreach ($rows as $row) { - $key = strtoupper(trim($row['Name'])); - $excluded = trim((string) $row['Exclusion']) !== ''; - // Excluded VMs are listed even when no longer inventoried, like on the old page. - if (!$excluded && !isset($active[$key])) continue; + $key = normalize_host($row['client']); - $result = trim((string) $row['LastResult']); - $ageGood = days_since($row['LastKnownGood']); + // This synthesis page is VM-oriented. + if ($key === '' || !isset($active[$key])) continue; - if ($excluded) { - $cat = 'excluded'; $cls = 'table-secondary'; - } elseif ($result !== 'OK') { - // A failed job with a still fresh good copy is only a warning. + $statusCode = (int) $row['status']; + $ageGood = days_since($row['last_success']); + + if ($statusCode > 1) { + // Same policy as before: + // a failed latest run remains only a warning while a recent good copy exists. if ($ageGood === null || $ageGood >= backup_tolerance_days()) { - $cat = 'error'; $cls = 'table-danger'; + $cat = 'error'; + $cls = 'table-danger'; } else { - $cat = 'warning'; $cls = 'table-warning'; + $cat = 'warning'; + $cls = 'table-warning'; } } elseif ($ageGood === null || $ageGood >= backup_tolerance_days()) { - $cat = 'outdated'; $cls = 'table-info'; + $cat = 'outdated'; + $cls = 'table-info'; } else { - $cat = 'ok'; $cls = 'table-success'; + $cat = 'ok'; + $cls = 'table-success'; } + $statusDesc = trim((string)($row['status_desc'] ?? '')); + + if ($statusCode <= 1) { + $statusText = 'OK'; + } else { + $statusText = 'Error ' . $statusCode; + if ($statusDesc !== '') { + $statusText .= ' - ' . $statusDesc; + } + } + + $runDate = $row['run_date'] ?? $row['start_date'] ?? null; + $netbackup[$key] = [ 'cat' => $cat, 'row_class' => $cls, - 'excluded' => $excluded, - 'ts' => strtotime(trim($row['LastBackup'] . ' ' . $row['TimeStamp'])) ?: 0, - 'vm' => $row['Name'], - 'last_run' => $excluded ? 'Tag NoBackup' : fmt_datetime($row['LastBackup'] . ' ' . $row['TimeStamp']), - 'status' => $excluded ? 'Tag NoBackup' : ($result !== '' ? $result : 'Unknown'), - 'last_good' => $excluded ? 'Tag NoBackup' : fmt_lastgood($row['LastKnownGood']), - 'size' => $excluded ? '' : $row['LastSize'], - 'extra' => $row['Owner'], - 'policy' => $row['Policy'], - 'message' => '', + 'excluded' => false, + 'ts' => strtotime((string)$runDate) ?: 0, + 'vm' => $row['client'], + 'last_run' => fmt_datetime($runDate), + 'status' => $statusText, + 'last_good' => fmt_lastgood($row['last_success']), + 'size' => ($row['size_kb'] === null || $row['size_kb'] === '') + ? '' + : number_format(((float)$row['size_kb']) / 1048576, 1, '.', ''), + 'extra' => $row['schedule_type'], + 'policy' => $row['policy'], + 'message' => ($statusCode > 1) ? $statusDesc : '', ]; } +/* + Keep only the explicit NoBackup exclusions from the old VMs_Backup table. + NetBackupJobs cannot represent a deliberate exclusion because no backup job + exists for an intentionally excluded VM. +*/ +$excludedRows = Invoke_Infra(" + SELECT name, Exclusion, Owner, Policy + FROM VMs_Backup + WHERE Exclusion IS NOT NULL + AND LTRIM(RTRIM(Exclusion)) <> '' +"); + +if (is_array($excludedRows)) { + foreach ($excludedRows as $row) { + + $key = normalize_host($row['name']); + + // Do not overwrite a VM that has actual dun-bkp-01 job history. + if ($key === '' || isset($netbackup[$key])) continue; + + $netbackup[$key] = [ + 'cat' => 'excluded', + 'row_class' => 'table-secondary', + 'excluded' => true, + 'ts' => 0, + 'vm' => $row['name'], + 'last_run' => 'Tag NoBackup', + 'status' => 'Tag NoBackup', + 'last_good' => 'Tag NoBackup', + 'size' => '', + 'extra' => '', + 'policy' => $row['Policy'], + 'message' => trim((string)$row['Exclusion']), + ]; + } +} + /* ------------------------------------------- 3. Migration de-duplication */ // A VM present on both sides is kept where the most recent backup lives. @@ -345,7 +487,7 @@ function render_block($blockId, $meta, $rows, $extraLabel, $expanded = false) {
    @@ -408,3 +550,4 @@ function render_block($blockId, $meta, $rows, $extraLabel, $expanded = false) { +