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:
@@ -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(); ?>
|
||||
|
||||
Reference in New Issue
Block a user