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.
This commit is contained in:
e025532
2026-09-24 15:36:09 +02:00
parent c185d099d6
commit db223b805c
7 changed files with 847 additions and 127 deletions

View File

@@ -1,3 +1,4 @@
<?php ob_start(); ?>
<!DOCTYPE html>
<html lang="fr">
@@ -73,13 +74,22 @@
<?php
/* ============================================================================
Unified backup reporting : Veeam (target) + NetBackup (legacy)
During the migration a VM can still exist in both SQL tables.
Unified backup reporting : Veeam + NetBackupJobs (dun-bkp-01)
During the migration a VM can still exist in both backup sources.
It is displayed only on the side holding the most recent backup.
========================================================================== */
/* ---------------------------------------------------------------- helpers */
// NetBackup can store a client as FQDN while CMDB/Veeam often use a short name.
// Use the short hostname as the common comparison key.
function normalize_host($name) {
$name = strtoupper(trim((string) $name));
if ($name === '') return '';
$parts = explode('.', $name, 2);
return $parts[0];
}
// Whole days between a date and today. Returns null when the date is unusable.
function days_since($dateStr) {
if (empty($dateStr)) return null;
@@ -110,14 +120,14 @@ function fmt_lastgood($dateStr) {
$activeRows = Invoke_Infra("SELECT name FROM cmdb_vms
WHERE CAST(lastinventory AS DATETIME) > 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) {
<div class="accordion mb-4" id="acc_nb">
<?php
foreach (['error', 'warning', 'outdated', 'ok', 'excluded'] as $cat) {
render_block('nb_' . $cat, $blockMeta[$cat], $buckets['nb'][$cat], $GLOBALS['w_host'], false);
render_block('nb_' . $cat, $blockMeta[$cat], $buckets['nb'][$cat], 'Schedule', false);
}
?>
</div>
@@ -408,3 +550,4 @@ function render_block($blockId, $meta, $rows, $extraLabel, $expanded = false) {
</body>
</html>
<?php ob_end_flush(); ?>