- 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.
942 lines
28 KiB
PHP
942 lines
28 KiB
PHP
<?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>
|