Add VIO FC Status monitoring page and enhance server inventory filtering
- Introduced `Storage/VIO.php` for monitoring VIO Fibre Channel status with client balancing checks and table sorting. - Updated `/include/en.php` and `/include/fr.php` to include new translations for ServiceNow and VIO-related navigation elements. - Improved SQL query and filtering logic in `/X/Inventory.php` to exclude VIO entries and add XenSam version data. - Enhanced `/reports/heartbeat.php` with a new mechanism for handling server heartbeat status, allowing better granularity and readability.
This commit is contained in:
941
reports/Netbackup_Daily.php
Normal file
941
reports/Netbackup_Daily.php
Normal file
@@ -0,0 +1,941 @@
|
||||
<?php ob_start();?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>Web Infra Reports IT - NetBackup Daily</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/include/favicon-32x32.png">
|
||||
|
||||
<script src="/js/jquery-3.6.1.min.js"></script>
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/css/bootstrap-icons/bootstrap-icons.css">
|
||||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/css/bootstrap-table.min.css">
|
||||
<script src="/js/bootstrap-table.min.js"></script>
|
||||
<script src="/js/bootstrap-table-fr-FR.min.js"></script>
|
||||
<script src="/js/tableExport.min.js"></script>
|
||||
<script src="/js/bootstrap-table-export.min.js"></script>
|
||||
<script src="/js/libs/js-xlsx/xlsx.core.min.js"></script>
|
||||
|
||||
<style>
|
||||
.accordion,
|
||||
.accordion-item,
|
||||
.accordion-button,
|
||||
.accordion-button:not(.collapsed),
|
||||
.accordion-body {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.accordion-item {
|
||||
border-color: rgba(128, 128, 128, .35);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.accordion-button:not(.collapsed) {
|
||||
box-shadow: none;
|
||||
background-color: rgba(128, 128, 128, .12);
|
||||
}
|
||||
|
||||
.accordion-button:focus {
|
||||
box-shadow: none;
|
||||
border-color: rgba(128, 128, 128, .35);
|
||||
}
|
||||
|
||||
.accordion-button::after {
|
||||
filter: grayscale(1) opacity(.75);
|
||||
}
|
||||
|
||||
body.bg-dark .accordion-button::after {
|
||||
filter: invert(1) grayscale(1) opacity(.85);
|
||||
}
|
||||
|
||||
.accordion-body,
|
||||
.accordion-body .fixed-table-container,
|
||||
.accordion-body .fixed-table-body,
|
||||
.accordion-body .fixed-table-toolbar {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.accordion-body .fixed-table-container {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.accordion-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
position: sticky;
|
||||
top: var(--acc-header-h, 56px);
|
||||
z-index: 2;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
body.bg-dark .accordion-header,
|
||||
body.bg-dark .report-table thead th {
|
||||
background-color: #212529;
|
||||
}
|
||||
|
||||
.block-count {
|
||||
font-size: 1.25rem;
|
||||
min-width: 3.2em;
|
||||
}
|
||||
|
||||
.accordion-button .block-title {
|
||||
font-size: 1.45rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
|
||||
.master-card {
|
||||
border-left-width: 6px !important;
|
||||
border-left-style: solid !important;
|
||||
}
|
||||
|
||||
.master-ok { border-left-color: #198754 !important; }
|
||||
.master-warning { border-left-color: #ffc107 !important; }
|
||||
.master-critical { border-left-color: #dc3545 !important; }
|
||||
|
||||
.metric {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: .72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
min-height: 86px;
|
||||
}
|
||||
|
||||
.warning-size {
|
||||
color: #b7791f;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body.bg-dark .warning-size {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
body.bg-dark .card {
|
||||
--bs-card-bg: #2b3035;
|
||||
--bs-card-color: #f8f9fa;
|
||||
--bs-card-border-color: #495057;
|
||||
background-color: #2b3035;
|
||||
color: #f8f9fa;
|
||||
border-color: #495057;
|
||||
}
|
||||
|
||||
body.bg-dark .master-card,
|
||||
body.bg-dark .summary-card {
|
||||
background-color: #2b3035 !important;
|
||||
color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
body.bg-dark .card .text-muted,
|
||||
body.bg-dark .master-card .text-muted,
|
||||
body.bg-dark .summary-card .text-muted {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
body.bg-dark .metric-label {
|
||||
color: #adb5bd;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* More readable OK green, especially on the grey title badge. */
|
||||
.title-ok {
|
||||
color: #00d084 !important;
|
||||
}
|
||||
|
||||
body.bg-dark .title-ok {
|
||||
color: #5ee6a8 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.accordion-button .block-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.block-count {
|
||||
font-size: 1rem;
|
||||
min-width: 2.8em;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.report-table {
|
||||
font-size: .82rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-light text-dark">
|
||||
<?php include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php"; ?>
|
||||
|
||||
<?php
|
||||
/* ============================================================================
|
||||
Helpers
|
||||
========================================================================== */
|
||||
|
||||
function h($value) {
|
||||
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function db_int($row, $key) {
|
||||
return isset($row[$key]) && $row[$key] !== null ? (int)$row[$key] : 0;
|
||||
}
|
||||
|
||||
function fmt_dt($value) {
|
||||
if (empty($value)) return '-';
|
||||
$ts = strtotime((string)$value);
|
||||
return $ts ? date('d/m/Y H:i', $ts) : '-';
|
||||
}
|
||||
|
||||
function fmt_gb_from_kb($kb) {
|
||||
if ($kb === null || $kb === '') return '-';
|
||||
return number_format(((float)$kb) / 1048576, 1, ',', ' ') . ' GB';
|
||||
}
|
||||
|
||||
// PURGE Random datas //
|
||||
Invoke_Infra("delete from netbackupjobs where master not like '%-%'");
|
||||
|
||||
/* ============================================================================
|
||||
SQL commun : point de départ par master
|
||||
Dernier jour (dans les 3 jours precedents) ayant un Infra/full,
|
||||
puis premier JobID Infra/full de ce jour.
|
||||
========================================================================== */
|
||||
|
||||
$startPointCte = "
|
||||
WITH Masters AS
|
||||
(
|
||||
SELECT DISTINCT master
|
||||
FROM dbo.NetBackupJobs
|
||||
),
|
||||
LatestFullDay AS
|
||||
(
|
||||
SELECT
|
||||
master,
|
||||
MAX(CAST(start_date AS date)) AS full_day
|
||||
FROM dbo.NetBackupJobs
|
||||
WHERE
|
||||
start_date >= CAST(DATEADD(DAY, -3, GETDATE()) AS date)
|
||||
AND start_date < CAST(GETDATE() AS date)
|
||||
AND policy LIKE '%Infra%'
|
||||
AND schedule_type = 'full'
|
||||
GROUP BY master
|
||||
),
|
||||
StartPoints AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
MIN(j.job) AS start_job
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN LatestFullDay d
|
||||
ON d.master = j.master
|
||||
AND CAST(j.start_date AS date) = d.full_day
|
||||
WHERE
|
||||
j.policy LIKE '%Infra%'
|
||||
AND j.schedule_type = 'full'
|
||||
GROUP BY j.master
|
||||
)
|
||||
";
|
||||
|
||||
/* ============================================================================
|
||||
1. Résumé par master
|
||||
========================================================================== */
|
||||
|
||||
$summarySql = $startPointCte . ",
|
||||
Jobs AS
|
||||
(
|
||||
SELECT j.*
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
),
|
||||
LargeIncrementals AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
COUNT(DISTINCT j.client) AS large_incremental_clients
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
WHERE
|
||||
j.schedule_type = 'incremental'
|
||||
AND j.status = 0
|
||||
AND j.policy NOT LIKE '%Hyper%'
|
||||
AND j.policy NOT LIKE '%standard%'
|
||||
AND j.policy NOT LIKE '%WE%'
|
||||
AND j.size_kb > 73400320
|
||||
GROUP BY j.master
|
||||
)
|
||||
SELECT
|
||||
m.master,
|
||||
s.start_job,
|
||||
|
||||
COUNT(j.job) AS total_jobs,
|
||||
|
||||
COALESCE(SUM(CASE WHEN j.status <= 1 THEN 1 ELSE 0 END), 0) AS jobs_ok,
|
||||
COALESCE(SUM(CASE WHEN j.status > 1 THEN 1 ELSE 0 END), 0) AS jobs_error,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type IN ('full','incremental') THEN 1 ELSE 0 END
|
||||
), 0) AS scheduled_jobs,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type IN ('full','incremental')
|
||||
AND j.status > 1 THEN 1 ELSE 0 END
|
||||
), 0) AS scheduled_errors,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type = 'user backup' THEN 1 ELSE 0 END
|
||||
), 0) AS user_jobs,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type = 'user backup'
|
||||
AND j.status > 1 THEN 1 ELSE 0 END
|
||||
), 0) AS user_errors,
|
||||
|
||||
COALESCE(li.large_incremental_clients, 0) AS large_incremental_clients
|
||||
|
||||
FROM Masters m
|
||||
LEFT JOIN StartPoints s
|
||||
ON s.master = m.master
|
||||
LEFT JOIN Jobs j
|
||||
ON j.master = m.master
|
||||
LEFT JOIN LargeIncrementals li
|
||||
ON li.master = m.master
|
||||
GROUP BY
|
||||
m.master,
|
||||
s.start_job,
|
||||
li.large_incremental_clients
|
||||
ORDER BY m.master
|
||||
";
|
||||
|
||||
$summary = Invoke_Infra($summarySql);
|
||||
if (!is_array($summary)) $summary = [];
|
||||
|
||||
/* ============================================================================
|
||||
2. Jobs en erreur + description NBRC
|
||||
========================================================================== */
|
||||
|
||||
$errorSql = "
|
||||
WITH LatestFullDay AS
|
||||
(
|
||||
SELECT
|
||||
master,
|
||||
MAX(CAST(start_date AS date)) AS full_day
|
||||
FROM dbo.NetBackupJobs
|
||||
WHERE
|
||||
start_date >= CAST(DATEADD(DAY, -3, GETDATE()) AS date)
|
||||
AND start_date < CAST(GETDATE() AS date)
|
||||
AND policy LIKE '%Infra%'
|
||||
AND schedule_type = 'full'
|
||||
GROUP BY master
|
||||
),
|
||||
StartPoints AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
MIN(j.job) AS start_job
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN LatestFullDay d
|
||||
ON d.master = j.master
|
||||
AND CAST(j.start_date AS date) = d.full_day
|
||||
WHERE
|
||||
j.policy LIKE '%Infra%'
|
||||
AND j.schedule_type = 'full'
|
||||
GROUP BY j.master
|
||||
)
|
||||
SELECT
|
||||
j.master,
|
||||
j.job,
|
||||
j.client,
|
||||
j.policy,
|
||||
j.schedule_type,
|
||||
j.status,
|
||||
r.RC_DESC AS status_desc,
|
||||
j.start_date,
|
||||
j.end_date,
|
||||
j.size_kb
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
LEFT JOIN dbo.NBRC r
|
||||
ON r.RC = j.status
|
||||
WHERE
|
||||
j.status > 1
|
||||
ORDER BY
|
||||
j.master,
|
||||
CASE
|
||||
WHEN j.schedule_type IN ('full','incremental') THEN 0
|
||||
WHEN j.schedule_type = 'user backup' THEN 1
|
||||
ELSE 2
|
||||
END,
|
||||
j.client,
|
||||
j.job
|
||||
";
|
||||
|
||||
$errors = Invoke_Infra($errorSql);
|
||||
if (!is_array($errors)) $errors = [];
|
||||
|
||||
/* ============================================================================
|
||||
3. Warnings : incrémentales > 70 GB
|
||||
Règle métier exacte :
|
||||
- incremental
|
||||
- status = 0
|
||||
- policy NOT LIKE Hyper / standard / WE
|
||||
- size > 70 GB
|
||||
- à partir du point de départ du master
|
||||
|
||||
On garde une ligne par master/client, correspondant au plus gros job du client.
|
||||
========================================================================== */
|
||||
|
||||
$largeSql = "
|
||||
WITH LatestFullDay AS
|
||||
(
|
||||
SELECT
|
||||
master,
|
||||
MAX(CAST(start_date AS date)) AS full_day
|
||||
FROM dbo.NetBackupJobs
|
||||
WHERE
|
||||
start_date >= CAST(DATEADD(DAY, -3, GETDATE()) AS date)
|
||||
AND start_date < CAST(GETDATE() AS date)
|
||||
AND policy LIKE '%Infra%'
|
||||
AND schedule_type = 'full'
|
||||
GROUP BY master
|
||||
),
|
||||
StartPoints AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
MIN(j.job) AS start_job
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN LatestFullDay d
|
||||
ON d.master = j.master
|
||||
AND CAST(j.start_date AS date) = d.full_day
|
||||
WHERE
|
||||
j.policy LIKE '%Infra%'
|
||||
AND j.schedule_type = 'full'
|
||||
GROUP BY j.master
|
||||
),
|
||||
Candidates AS
|
||||
(
|
||||
SELECT
|
||||
j.*,
|
||||
ROW_NUMBER() OVER
|
||||
(
|
||||
PARTITION BY j.master, j.client
|
||||
ORDER BY j.size_kb DESC, j.job DESC
|
||||
) AS rn
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
WHERE
|
||||
j.schedule_type = 'incremental'
|
||||
AND j.status = 0
|
||||
AND j.policy NOT LIKE '%Hyper%'
|
||||
AND j.policy NOT LIKE '%standard%'
|
||||
AND j.policy NOT LIKE '%WE%'
|
||||
AND j.size_kb > 73400320
|
||||
)
|
||||
SELECT
|
||||
master,
|
||||
job,
|
||||
client,
|
||||
policy,
|
||||
schedule_type,
|
||||
status,
|
||||
start_date,
|
||||
end_date,
|
||||
size_kb
|
||||
FROM Candidates
|
||||
WHERE rn = 1
|
||||
ORDER BY master, size_kb DESC, client
|
||||
";
|
||||
|
||||
$largeIncrementals = Invoke_Infra($largeSql);
|
||||
if (!is_array($largeIncrementals)) $largeIncrementals = [];
|
||||
|
||||
/* ============================================================================
|
||||
Préparation des données
|
||||
========================================================================== */
|
||||
|
||||
$scheduledErrors = [];
|
||||
$userErrors = [];
|
||||
|
||||
foreach ($errors as $row) {
|
||||
$type = strtolower(trim((string)($row['schedule_type'] ?? '')));
|
||||
|
||||
if ($type === 'full' || $type === 'incremental') {
|
||||
$scheduledErrors[] = $row;
|
||||
} elseif ($type === 'user backup') {
|
||||
$userErrors[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$mastersOk = 0;
|
||||
$mastersWarning = 0;
|
||||
$mastersCritical = 0;
|
||||
$totalJobs = 0;
|
||||
$totalErrors = 0;
|
||||
|
||||
foreach ($summary as &$row) {
|
||||
$total = db_int($row, 'total_jobs');
|
||||
$ok = db_int($row, 'jobs_ok');
|
||||
$err = db_int($row, 'jobs_error');
|
||||
$large = db_int($row, 'large_incremental_clients');
|
||||
$noStart = !isset($row['start_job']) || $row['start_job'] === null || $row['start_job'] === '';
|
||||
|
||||
$totalJobs += $total;
|
||||
$totalErrors += $err;
|
||||
|
||||
if ($noStart || $total === 0 || ($total > 0 && $ok === 0)) {
|
||||
$row['_state'] = 'critical';
|
||||
$mastersCritical++;
|
||||
|
||||
if ($noStart) {
|
||||
$row['_state_text'] = 'Aucun point de départ Infra trouvé';
|
||||
} elseif ($total === 0) {
|
||||
$row['_state_text'] = 'Aucune sauvegarde détectée';
|
||||
} else {
|
||||
$row['_state_text'] = 'Toutes les sauvegardes sont en erreur';
|
||||
}
|
||||
} elseif ($err > 0 || $large > 0) {
|
||||
$row['_state'] = 'warning';
|
||||
$mastersWarning++;
|
||||
|
||||
$parts = [];
|
||||
if ($err > 0) $parts[] = $err . ' erreur(s)';
|
||||
if ($large > 0) $parts[] = $large . ' incr. > 70 GB';
|
||||
$row['_state_text'] = implode(' / ', $parts);
|
||||
} else {
|
||||
$row['_state'] = 'ok';
|
||||
$row['_state_text'] = 'OK';
|
||||
$mastersOk++;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$masterCount = count($summary);
|
||||
$totalLarge = count($largeIncrementals);
|
||||
|
||||
/* ============================================================================
|
||||
Helpers de rendu
|
||||
========================================================================== */
|
||||
|
||||
function render_master_card($m) {
|
||||
$state = $m['_state'] ?? 'ok';
|
||||
|
||||
$borderClass = [
|
||||
'ok' => 'master-ok',
|
||||
'warning' => 'master-warning',
|
||||
'critical' => 'master-critical',
|
||||
][$state];
|
||||
|
||||
$badgeClass = [
|
||||
'ok' => 'text-bg-success',
|
||||
'warning' => 'bg-warning text-dark',
|
||||
'critical' => 'text-bg-danger',
|
||||
][$state];
|
||||
|
||||
$largeClass = db_int($m, 'large_incremental_clients') > 0 ? 'text-warning' : 'text-success';
|
||||
?>
|
||||
<div class="card shadow-sm mb-2 master-card <?php echo $borderClass; ?>">
|
||||
<div class="card-body py-2 px-3">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<div>
|
||||
<div class="fw-bold fs-5"><?php echo h($m['master'] ?? ''); ?></div>
|
||||
<span class="badge <?php echo $badgeClass; ?>">
|
||||
<?php echo h($m['_state_text'] ?? ''); ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="small text-muted">
|
||||
Start job :
|
||||
<strong><?php echo h($m['start_job'] ?? '-'); ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (db_int($m, 'total_jobs') > 0) : ?>
|
||||
<div class="row g-2 mt-2">
|
||||
<div class="col-4 col-md-2">
|
||||
<div class="metric"><?php echo db_int($m, 'total_jobs'); ?></div>
|
||||
<div class="metric-label">Total</div>
|
||||
</div>
|
||||
<div class="col-4 col-md-2">
|
||||
<div class="metric text-success"><?php echo db_int($m, 'jobs_ok'); ?></div>
|
||||
<div class="metric-label">OK</div>
|
||||
</div>
|
||||
<div class="col-4 col-md-2">
|
||||
<div class="metric text-danger"><?php echo db_int($m, 'jobs_error'); ?></div>
|
||||
<div class="metric-label">Erreurs</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="metric">
|
||||
<?php echo db_int($m, 'scheduled_errors'); ?> /
|
||||
<?php echo db_int($m, 'scheduled_jobs'); ?>
|
||||
</div>
|
||||
<div class="metric-label">Full / Incr.</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="metric">
|
||||
<?php echo db_int($m, 'user_errors'); ?> /
|
||||
<?php echo db_int($m, 'user_jobs'); ?>
|
||||
</div>
|
||||
<div class="metric-label">User backup</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 small">
|
||||
Incr. > 70 GB :
|
||||
<strong class="<?php echo $largeClass; ?>">
|
||||
<?php echo db_int($m, 'large_incremental_clients'); ?>
|
||||
</strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
function render_error_table($id, $rows, $emptyText = 'Aucun job dans cette catégorie.') {
|
||||
?>
|
||||
<?php if (count($rows) === 0) : ?>
|
||||
<p class="text-muted mb-0"><?php echo h($emptyText); ?></p>
|
||||
<?php else : ?>
|
||||
<table
|
||||
class="table table-bordered table-hover table-sm report-table"
|
||||
id="<?php echo h($id); ?>"
|
||||
data-sortable="true"
|
||||
data-search="true"
|
||||
data-show-export="true"
|
||||
data-export-types='["csv","excel"]'
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-field="master" data-sortable="true">Master</th>
|
||||
<th data-field="client" data-sortable="true">Client</th>
|
||||
<th data-field="policy" data-sortable="true">Policy</th>
|
||||
<th data-field="schedule" data-sortable="true">Schedule</th>
|
||||
<th data-field="status" data-sortable="true">Status</th>
|
||||
<th data-field="description" data-sortable="true">Description</th>
|
||||
<th data-field="start" data-sortable="true">Début</th>
|
||||
<th data-field="end" data-sortable="true">Fin</th>
|
||||
<th data-field="size" data-sortable="true">Taille</th>
|
||||
<th data-field="job" data-sortable="true">Job</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<tr class="table-danger">
|
||||
<td><?php echo h($r['master'] ?? ''); ?></td>
|
||||
<td><strong><?php echo h($r['client'] ?? ''); ?></strong></td>
|
||||
<td><?php echo h($r['policy'] ?? ''); ?></td>
|
||||
<td><?php echo h($r['schedule_type'] ?? ''); ?></td>
|
||||
<td><strong><?php echo h($r['status'] ?? ''); ?></strong></td>
|
||||
<td><?php echo h($r['status_desc'] ?? 'Description non trouvée'); ?></td>
|
||||
<td><?php echo h(fmt_dt($r['start_date'] ?? null)); ?></td>
|
||||
<td><?php echo h(fmt_dt($r['end_date'] ?? null)); ?></td>
|
||||
<td><?php echo h(fmt_gb_from_kb($r['size_kb'] ?? null)); ?></td>
|
||||
<td><?php echo h($r['job'] ?? ''); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif;
|
||||
}
|
||||
|
||||
function render_warning_table($id, $rows) {
|
||||
?>
|
||||
<?php if (count($rows) === 0) : ?>
|
||||
<p class="text-muted mb-0">Aucune incrémentale > 70 GB.</p>
|
||||
<?php else : ?>
|
||||
<table
|
||||
class="table table-bordered table-hover table-sm report-table"
|
||||
id="<?php echo h($id); ?>"
|
||||
data-sortable="true"
|
||||
data-search="true"
|
||||
data-show-export="true"
|
||||
data-export-types='["csv","excel"]'
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-field="master" data-sortable="true">Master</th>
|
||||
<th data-field="client" data-sortable="true">Client</th>
|
||||
<th data-field="policy" data-sortable="true">Policy</th>
|
||||
<th data-field="size" data-sortable="true">Taille</th>
|
||||
<th data-field="status" data-sortable="true">Status</th>
|
||||
<th data-field="start" data-sortable="true">Début</th>
|
||||
<th data-field="end" data-sortable="true">Fin</th>
|
||||
<th data-field="job" data-sortable="true">Job</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<tr class="table-warning">
|
||||
<td><?php echo h($r['master'] ?? ''); ?></td>
|
||||
<td><strong><?php echo h($r['client'] ?? ''); ?></strong></td>
|
||||
<td><?php echo h($r['policy'] ?? ''); ?></td>
|
||||
<td class="warning-size"><?php echo h(fmt_gb_from_kb($r['size_kb'] ?? null)); ?></td>
|
||||
<td><span class="badge text-bg-success"><?php echo h($r['status'] ?? '0'); ?> - OK</span></td>
|
||||
<td><?php echo h(fmt_dt($r['start_date'] ?? null)); ?></td>
|
||||
<td><?php echo h(fmt_dt($r['end_date'] ?? null)); ?></td>
|
||||
<td><?php echo h($r['job'] ?? ''); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif;
|
||||
}
|
||||
|
||||
function render_accordion_block($id, $title, $icon, $badgeClass, $count, $renderer, $expanded = false) {
|
||||
$collapsed = $expanded ? '' : 'collapsed';
|
||||
$show = $expanded ? 'show' : '';
|
||||
$aria = $expanded ? 'true' : 'false';
|
||||
?>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button <?php echo $collapsed; ?>"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#c_<?php echo h($id); ?>"
|
||||
aria-expanded="<?php echo $aria; ?>">
|
||||
<i class="bi <?php echo h($icon); ?> me-2"></i>
|
||||
<span class="block-title"><?php echo h($title); ?></span>
|
||||
<span class="badge <?php echo h($badgeClass); ?> ms-3 block-count"><?php echo (int)$count; ?></span>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="c_<?php echo h($id); ?>" class="accordion-collapse collapse <?php echo $show; ?>">
|
||||
<div class="accordion-body p-2">
|
||||
<?php $renderer(); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid" id="content">
|
||||
<div class="row flex-nowrap">
|
||||
|
||||
<div class="col-auto col-md-2 col-xl-2 px-sm-2 px-0 bg-dark vh-100 position-sticky top-0"
|
||||
style="-ms-flex:0 0 230px; flex:0 0 230px;">
|
||||
<?php include $_SERVER['DOCUMENT_ROOT'] . "/navbar.html"; ?>
|
||||
</div>
|
||||
|
||||
<div class="col py-3">
|
||||
|
||||
<!-- ================================================================
|
||||
Titre global
|
||||
================================================================ -->
|
||||
|
||||
<h2>
|
||||
<span class="badge text-bg-secondary w-100 text-wrap" id="ERR">
|
||||
NetBackup Daily —
|
||||
<span class="text-light"><?php echo $masterCount; ?> masters</span> —
|
||||
<span class="title-ok"><?php echo $mastersOk; ?> OK</span> -
|
||||
<span class="text-warning"><?php echo $mastersWarning; ?> Warning</span> -
|
||||
<span class="text-danger"><?php echo $mastersCritical; ?> Critical</span>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div class="container-fluid px-0">
|
||||
|
||||
<!-- ================================================================
|
||||
Compteurs globaux
|
||||
================================================================ -->
|
||||
|
||||
<div class="row g-2 mt-1 mb-3">
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold"><?php echo $totalJobs; ?></div>
|
||||
<div class="text-muted small">Jobs analysés</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold text-danger"><?php echo $totalErrors; ?></div>
|
||||
<div class="text-muted small">Jobs en erreur</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold text-warning"><?php echo $totalLarge; ?></div>
|
||||
<div class="text-muted small">Incr. > 70 GB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold text-danger"><?php echo $mastersCritical; ?></div>
|
||||
<div class="text-muted small">Masters critiques</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
Résumé par master
|
||||
================================================================ -->
|
||||
|
||||
<h4 class="mt-3 mb-2">
|
||||
<i class="bi bi-hdd-stack me-1"></i>
|
||||
Résumé par master
|
||||
</h4>
|
||||
|
||||
<div class="row">
|
||||
<?php foreach ($summary as $m) : ?>
|
||||
<div class="col-12 col-xl-6">
|
||||
<?php render_master_card($m); ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
Détails
|
||||
================================================================ -->
|
||||
|
||||
<h4 class="mt-4 mb-2">
|
||||
<i class="bi bi-list-check me-1"></i>
|
||||
Détails
|
||||
</h4>
|
||||
|
||||
<div class="accordion mb-4" id="acc_netbackup_daily">
|
||||
|
||||
<?php
|
||||
render_accordion_block(
|
||||
'scheduled_errors',
|
||||
'Full / Incremental en erreur',
|
||||
'bi-x-octagon',
|
||||
'bg-danger',
|
||||
count($scheduledErrors),
|
||||
function () use ($scheduledErrors) {
|
||||
render_error_table('t_scheduled_errors', $scheduledErrors);
|
||||
},
|
||||
count($scheduledErrors) > 0
|
||||
);
|
||||
|
||||
render_accordion_block(
|
||||
'user_errors',
|
||||
'User Backup en erreur',
|
||||
'bi-person-exclamation',
|
||||
'bg-danger',
|
||||
count($userErrors),
|
||||
function () use ($userErrors) {
|
||||
render_error_table('t_user_errors', $userErrors);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
render_accordion_block(
|
||||
'large_incrementals',
|
||||
'Incrémentales > 70 GB',
|
||||
'bi-exclamation-triangle',
|
||||
'bg-warning text-dark',
|
||||
count($largeIncrementals),
|
||||
function () use ($largeIncrementals) {
|
||||
render_warning_table('t_large_incrementals', $largeIncrementals);
|
||||
},
|
||||
count($largeIncrementals) > 0
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/switch.js"></script>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
function isDark() {
|
||||
return document.body.classList.contains('bg-dark');
|
||||
}
|
||||
|
||||
function syncTheme($table) {
|
||||
$table.toggleClass('table-dark', isDark());
|
||||
}
|
||||
|
||||
function updateStickyOffset($item) {
|
||||
var h = $item.find('.accordion-header').outerHeight() || 56;
|
||||
$item.css('--acc-header-h', h + 'px');
|
||||
}
|
||||
|
||||
function initTable($table) {
|
||||
if ($table.length === 0) return;
|
||||
|
||||
if (!$table.data('bt-initialized')) {
|
||||
$table.bootstrapTable();
|
||||
$table.data('bt-initialized', true);
|
||||
} else {
|
||||
$table.bootstrapTable('resetView');
|
||||
}
|
||||
|
||||
syncTheme($table);
|
||||
updateStickyOffset($table.closest('.accordion-item'));
|
||||
}
|
||||
|
||||
$('.accordion-collapse').on('shown.bs.collapse', function () {
|
||||
initTable($(this).find('table.report-table'));
|
||||
});
|
||||
|
||||
// Initialise aussi les accordéons ouverts par défaut au chargement.
|
||||
$('.accordion-collapse.show').each(function () {
|
||||
initTable($(this).find('table.report-table'));
|
||||
});
|
||||
|
||||
$('#lightSwitch').on('change', function () {
|
||||
setTimeout(function () {
|
||||
$('table.report-table').each(function () {
|
||||
syncTheme($(this));
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$(window).on('resize', function () {
|
||||
$('.accordion-item').each(function () {
|
||||
updateStickyOffset($(this));
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user