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:
2026-09-14 10:32:15 +02:00
parent 1f1b7dcd22
commit c185d099d6
25 changed files with 6646 additions and 437 deletions

1582
AD/LocalAdmins.php Normal file

File diff suppressed because it is too large Load Diff

203
Astreinte/Liens.php Normal file
View File

@@ -0,0 +1,203 @@
<?php
// Récupération dynamique de l'année en cours pour le planning d'astreinte
$currentYear = date('Y');
$planningUrl = "https://arcelormittal.sharepoint.com/sites/AMAL_AIM_INFRASTRUCTURES-EquipesystmeDK/_layouts/15/Doc.aspx?sourcedoc=%7BD5D491BE-0788-4F29-8888-B1F3720F125D%7D&file=Planning%20d%27astreinte%20" . $currentYear . ".xlsx&action=default&mobileredirect=true&DefaultItemOpen=1";
?>
<!DOCTYPE html>
<html lang="en">
<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</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">
<link rel="stylesheet" href="/css/preloader.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>
/* Petits ajustements de style pour les cartes de liens */
.link-card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
/* Force une bordure grise à 50% d'opacité/luminosité, stable en clair et sombre */
border: 1px solid rgba(128, 128, 128, 0.5) !important;
}
.link-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 15px rgba(0,0,0,0.15) !important;
}
.icon-box {
font-size: 2.5rem;
}
</style>
</head>
<body class="bg-light text-dark">
<?php
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.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">
<h1><span class="badge text-bg-secondary font-weight-bold" style="width:100%;"><?php echo $nav_links ?? "Ressources & Canaux Utiles"; ?></span></h1>
<div class="container-fluid mt-4">
<h3 class="mb-3 text-muted"><i class="bi bi-file-earmark-spreadsheet-fill text-success"></i> Documents & Suivis</h3>
<div class="row mb-5">
<div class="col-12 col-md-6 col-xl-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-success me-3">
<i class="bi bi-filetype-xlsx"></i>
</div>
<div>
<h5 class="card-title mb-1">Planning d'astreinte <?= $currentYear ?></h5>
<p class="card-text text-muted small mb-2">Accès direct au fichier SharePoint mis à jour.</p>
<a href="<?= $planningUrl ?>" target="_blank" class="btn btn-sm btn-outline-success stretched-link">Ouvrir le Planning</a>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-warning me-3">
<i class="bi bi-journal-check"></i>
</div>
<div>
<h5 class="card-title mb-1">Suivi des appels</h5>
<p class="card-text text-muted small mb-2">Accès direct à la liste SharePoint de suivi des appels d'astreinte.</p>
<a href="https://arcelormittal.sharepoint.com/sites/AMAL_AIM_INFRASTRUCTURES-EquipesystmeDK/Lists/Suivi%20des%20appels%20dastreinte/AllItems.aspx" target="_blank" class="btn btn-sm btn-outline-warning stretched-link">Ouvrir le Suivi</a>
</div>
</div>
</div>
</div>
</div>
<h3 class="mb-3 text-muted"><i class="bi bi-activity text-danger"></i> Supervision Zabbix</h3>
<div class="row mb-5">
<div class="col-12 col-md-6 col-xl-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-danger me-3">
<i class="bi bi-speedometer2"></i>
</div>
<div>
<h5 class="card-title mb-1">Zabbix Infra/Réseau IT</h5>
<p class="card-text text-muted small mb-2">Tableaux de bord de l'infrastructure et du réseau IT.</p>
<a href="https://web-zabbix02.appliarmony.net/zabbix.php?action=dashboard.list" target="_blank" class="btn btn-sm btn-outline-danger stretched-link">Accéder au Zabbix IT</a>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-danger me-3">
<i class="bi bi-cpu"></i>
</div>
<div>
<h5 class="card-title mb-1">Zabbix Infra OT</h5>
<p class="card-text text-muted small mb-2">Vue du dashboard de l'infrastructure OT (Dunkerque).</p>
<a href="https://web-zabbix.process.dkm/zabbix.php?action=dashboard.view&dashboardid=226&from=now-24h&to=now" target="_blank" class="btn btn-sm btn-outline-danger stretched-link">Accéder au Zabbix Infra OT</a>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-danger me-3">
<i class="bi bi-diagram-3"></i>
</div>
<div>
<h5 class="card-title mb-1">Zabbix Réseau OT</h5>
<p class="card-text text-muted small mb-2">Liste des tableaux de bord du réseau OT (Dunkerque).</p>
<a href="https://web-zabbix-netdkm.process.dkm/zabbix/zabbix.php?action=dashboard.list" target="_blank" class="btn btn-sm btn-outline-danger stretched-link">Accéder au Zabbix Réseau OT</a>
</div>
</div>
</div>
</div>
</div>
<h3 class="mb-3 text-muted"><i class="bi bi-microsoft-teams text-primary"></i> Canaux Teams - Lignes Rouges</h3>
<div class="row">
<div class="col-12 col-md-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-primary me-3">
<i class="bi bi-chat-left-text-fill"></i>
</div>
<div>
<h5 class="card-title mb-1">Ligne Rouge - DK</h5>
<p class="card-text text-muted small mb-2">Incidents système & réseaux Dunkerque.</p>
<a href="https://teams.microsoft.com/l/channel/19%3Aff678d50cd6b4628a927c34dbcba9fb2%40thread.tacv2/Ligne%20rouge%20-%20incidents%20syst%C3%A8me%2C%20r%C3%A9seaux%20DK?groupId=794700e9-307f-49cc-9de5-3fd08b933793&tenantId=37cd273a-1cec-4aae-a297-41480ea54f8d" target="_blank" class="btn btn-sm btn-outline-primary stretched-link">Rejoindre le canal</a>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-primary me-3">
<i class="bi bi-chat-left-text-fill"></i>
</div>
<div>
<h5 class="card-title mb-1">Ligne Rouge - DV</h5>
<p class="card-text text-muted small mb-2">Incidents système & réseaux Mardyck / Florange.</p>
<a href="https://teams.microsoft.com/l/channel/19%3A06a26356276049d48d90347c0a744434%40thread.tacv2/Ligne%20rouge%20-%20incidents%20syst%C3%A8me%2C%20r%C3%A9seaux%20DV?groupId=794700e9-307f-49cc-9de5-3fd08b933793&tenantId=37cd273a-1cec-4aae-a297-41480ea54f8d" target="_blank" class="btn btn-sm btn-outline-primary stretched-link">Rejoindre le canal</a>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-4 mb-3">
<div class="card h-100 shadow-sm link-card bg-light border-2 border-dark">
<div class="card-body d-flex align-items-center">
<div class="icon-box text-primary me-3">
<i class="bi bi-chat-left-text-fill"></i>
</div>
<div>
<h5 class="card-title mb-1">Ligne Rouge - MK</h5>
<p class="card-text text-muted small mb-2">Incidents système & réseaux Montataire.</p>
<a href="https://teams.microsoft.com/l/channel/19%3Aa5cd4954d623410c8b03115431979788%40thread.tacv2/Ligne%20rouge%20-%20incidents%20syst%C3%A8me%2C%20r%C3%A9seaux%20MK?groupId=794700e9-307f-49cc-9de5-3fd08b933793&tenantId=37cd273a-1cec-4aae-a297-41480ea54f8d" target="_blank" class="btn btn-sm btn-outline-primary stretched-link">Rejoindre le canal</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script src="/js/switch.js"></script>
</html>
<script src="/js/tableResize2.js"></script>

213
Astreinte/p1-p2.php Normal file
View File

@@ -0,0 +1,213 @@
<?php
// ServiceNow credentials
$snowUser = '9000056';
$snowPass = 'tQNpID6m$';
$baseUrl = 'https://arcelormittalprod.service-now.com/api/now/table/incident';
function snowValue($value): string
{
if (is_array($value)) {
return $value['display_value']
?? $value['value']
?? json_encode($value);
}
return (string)$value;
}
$query = implode('^', [
'active=true',
'incident_stateNOT IN7,6',
'priorityIN1,2',
'opened_atRELATIVEGE@hour@ago@72'
]);
$params = [
'sysparm_query' => $query,
'sysparm_display_value' => 'true',
'sysparm_fields' => 'sys_id,number,assignment_group,short_description,assigned_to,priority,opened_at',
'sysparm_order_byDESC' => 'opened_at',
];
$url = $baseUrl . '?' . http_build_query($params);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERPWD => $snowUser . ':' . $snowPass,
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
CURLOPT_HTTPHEADER => [
'Accept: application/json'
],
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_PROXY => 'gateway.zscalertwo.net:10299',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
$incidents = [];
$errorMessage = null;
if ($error) {
$errorMessage = "Erreur cURL : " . $error;
} elseif ($httpCode !== 200) {
$errorMessage = "Erreur ServiceNow HTTP " . $httpCode;
} else {
$data = json_decode($response, true);
$incidents = $data['result'] ?? [];
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-t">
<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</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">
<link rel="stylesheet" href="/css/preloader.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>
</head>
<body class="bg-light text-dark">
<?php
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.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">
<h1><span class="badge text-bg-secondary font-weight-bold" style="width:100%;"><?php echo $ti_20;?></span></h1>
<div class="container-fluid">
<?php if ($errorMessage): ?>
<div class="alert alert-danger">
<?= htmlspecialchars($errorMessage) ?>
</div>
<?php else: ?>
<?php
$p1 = 0; $p2 = 0;
foreach ($incidents as $inc) {
$p = $inc['priority'] ?? '';
if (str_contains($p, '1')) $p1++;
if (str_contains($p, '2')) $p2++;
}
?>
<div class="alert alert-info text-center">
<div class="d-inline-flex align-items-center justify-content-center gap-3">
<span><strong><?= count($incidents) ?></strong> <?= $w_incFound ?> :</span>
<span class="badge bg-danger text-white">P1 : <?= $p1 ?></span>
<span class="badge bg-warning text-black">P2 : <?= $p2 ?></span>
</div>
</div>
<table class='table table-bordered table-hover table-sm' id='t1' data-toggle="table" data-search="true"
data-show-columns="true" data-export-types="['xlsx','csv','json']"
data-show-export="true" data-sortable="true" data-sort-name="Opened" data-sort-order="desc">
<thead class="table-dark">
<tr>
<th data-field="Ticket" data-sortable="true">INC</th>
<th data-field="Priority" data-sortable="true">P1/P2</th>
<th data-field="Group" data-sortable="true"><?= $w_group ?></th>
<th data-field="Assigned" data-sortable="true"><?= $w_assigned ?></th>
<th data-field="Opened" data-sortable="true"><?= $w_opened ?></th>
<th data-field="Descr" data-sortable="true">Description</th>
</tr>
</thead>
<tbody>
<?php if (empty($incidents)): ?>
<tr>
<td colspan="6" class="text-center text-muted p-4">
Aucun incident trouvé.
</td>
</tr>
<?php else: ?>
<?php foreach ($incidents as $incident): ?>
<?php
$priority = $incident['priority'] ?? '';
$badgeClass = match (true) {
str_contains($priority, '1') => 'badge bg-danger text-white',
str_contains($priority, '2') => 'badge bg-warning text-black',
default => 'secondary'
};
?>
<tr>
<td>
<strong><a
href="https://arcelormittalprod.service-now.com/incident.do?sys_id=<?= urlencode($incident['sys_id']) ?>"
target="_blank"
class="fw-bold text-decoration-none"
>
<?= htmlspecialchars($incident['number'] ?? '') ?>
</a></strong>
</td>
<td>
<span class="<?= $badgeClass ?>">
<?= htmlspecialchars($priority) ?>
</span>
</td>
<td><small><?= htmlspecialchars(snowValue($incident['assignment_group']) ?? '') ?></small></td>
<td><small><?= htmlspecialchars(snowValue($incident['assigned_to']) ?? '') ?></small></td>
<td><?php
if (!empty($incident['opened_at'])) {
$date = new DateTime($incident['opened_at']);
echo htmlspecialchars($date->format('d-m H:i'));
}
?></td>
<td><?= htmlspecialchars($incident['short_description'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</body>
<script src="/js/switch.js"></script>
</HTML>
<script src="/js/tableResize2.js"></script>

View File

@@ -49,11 +49,17 @@
if(is_array($Qw)){ if(is_array($Qw)){
$OSType = "Windows"; $OSName = $Qw[0]['os']; $OSKernel = ""; $OSType = "Windows"; $OSName = $Qw[0]['os']; $OSKernel = "";
}else{ }else{
if($Qx[0]['Type'] == "AIX"){ if(is_array($Qx) ){
$OSType = "AIX"; $OSName = "AIX ".$Qx[0]['OSVersion'] ; $OSKernel = ""; if ($Qx[0]['Type'] == "AIX") {
}else{ $OSType = "AIX";
$OSType = "Linux"; $OSName = $Qx[0]['Type']." ".$Qx[0]['OSVersion'] ; $OSKernel = $Qx[0]['Kernel']; $OSName = "AIX " . $Qx[0]['OSVersion'];
} $OSKernel = "";
} else {
$OSType = "Linux";
$OSName = $Qx[0]['Type'] . " " . $Qx[0]['OSVersion'];
$OSKernel = $Qx[0]['Kernel'];
}
}else{$OSType='';}
} }
echo "<div id='type' data-value='".strtolower($OSType)."' hidden></div>"; echo "<div id='type' data-value='".strtolower($OSType)."' hidden></div>";
echo "<div id='computer' data-value='$server' hidden></div>"; echo "<div id='computer' data-value='$server' hidden></div>";
@@ -62,13 +68,14 @@
if($OSType == "Windows"){ if($OSType == "Windows"){
$memory = $Qw[0]['memory']." GB";$cpu = $Qw[0]['cpu']; $memory = $Qw[0]['memory']." GB";$cpu = $Qw[0]['cpu'];
}else{ }else{
if(is_array($Qx) ){
$cpu = $Qx[0]['CPU'] ; $cpu = $Qx[0]['CPU'] ;
if($OSType == "AIX"){ if($OSType == "AIX"){
$memory = round(str_replace(" Mo","",$Qx[0]['Memory'])/1024,2)." GB"; $memory = round(str_replace(" Mo","",$Qx[0]['Memory'])/1024,2)." GB";
}else{ }else{
$memory = $Qvm[0]['Memory'] ?? $Qx[0]['Memory'] ; $memory = $Qvm[0]['Memory'] ?? $Qx[0]['Memory'] ;
} }
} }}
echo "<div id='type' data-value='".strtolower($OSType)."' hidden></div>"; echo "<div id='type' data-value='".strtolower($OSType)."' hidden></div>";
echo "<div id='computer' data-value='$server' hidden></div>"; echo "<div id='computer' data-value='$server' hidden></div>";
?> ?>
@@ -254,9 +261,12 @@
} }
} }
if($OSType == "Linux"){ if($OSType == "Linux"){
$date = Invoke_Infra("select Next from rollup_linux_calendar where Server='$server'")[0]['Next']; $dr = Invoke_Infra("select Next from rollup_linux_calendar where Server='$server'");
if($date != ""){ if(is_array($dr)){
echo "&nbsp;<span class='badge rounded-pill bg-secondary text-light fs-5'>$w_nextRollup : $date </span>"; $date = $dr[0]['Next'];
if($date != ""){
echo "&nbsp;<span class='badge rounded-pill bg-secondary text-light fs-5'>$w_nextRollup : $date </span>";
}
} }
} }
?> ?>

View File

@@ -1,27 +1,29 @@
<?php <?php
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php"; include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php";
$server = $_GET['s'] ?? ''; $server = $_GET['s'] ?? '';
$ts = date("Y-m-d"); $ts = date("Y-m-d");
$data = Invoke_Entry01("SELECT dump FROM dump_dhcp where ts ='$ts' and server = '$server'"); $data = Invoke_Infra_long("SELECT dump FROM dump_dhcp where ts ='$ts' and server = '$server'");
$xmlRaw = $data[0]['dump'] ?? ''; $xmlRaw = $data[0]['dump'] ?? '';
if (!mb_check_encoding($xmlRaw, 'UTF-8')) {
$xmlRaw = mb_convert_encoding($xmlRaw, 'UTF-8', 'Windows-1252');
}
if (!$xmlRaw) { if ($xmlRaw === '') {
die("Aucune donnée trouvée pour le serveur $server à la date $ts"); die("Aucune donnée trouvée pour le serveur $server à la date $ts");
} }
$xmlString = mb_convert_encoding($xmlRaw, 'UTF-8', 'ISO-8859-1'); libxml_use_internal_errors(true);
if (!str_contains($xmlString, '<?xml')) { $xml = simplexml_load_string($xmlRaw);
$xmlString = '<?xml version="1.0" encoding="UTF-8"?>' . $xmlString; if ($xml === false) {
} echo '<pre>';
foreach (libxml_get_errors() as $e) {
echo trim($e->message) . " (ligne {$e->line}, col {$e->column})\n";
}
libxml_clear_errors();
exit;
}
try {
$xml = new SimpleXMLElement($xmlString);
} catch (Exception $e) {
echo "Erreur de parsing XML : " . $e->getMessage();
exit;
}
$definitions = []; $definitions = [];
foreach ($xml->xpath("//OptionDefinition") as $optDef) { foreach ($xml->xpath("//OptionDefinition") as $optDef) {

View File

@@ -3,7 +3,7 @@
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php"; include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php";
$ts = date("Y-m-d"); $ts = date("Y-m-d");
$serversData = Invoke_Entry01("SELECT Server,TS FROM dump_dhcp where ts ='$ts' order by server asc"); $serversData = Invoke_Infra("SELECT Server,TS FROM dump_dhcp where ts ='$ts' order by server asc");
?> ?>
<!DOCTYPE html> <!DOCTYPE html>

378
Storage/VIO.php Normal file
View File

@@ -0,0 +1,378 @@
<!DOCTYPE html>
<html lang="en">
<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>Infra Reports IT - VIO FC Status</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>
<style>
.card-header a {
text-decoration: none;
color: white;
display: block;
width: 100%;
}
.card-header a:hover {
color: #f0f0f0;
}
</style>
</head>
<body class="bg-light text-dark">
<?php
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
// Couple key = VIO name without its trailing number.
// e.g. "dun1vio1" and "dun1vio2" -> "dun1vio"
function vio_couple_key($vio) {
return preg_replace('/\d+$/', '', strtolower(trim($vio)));
}
// Parity of a physical fc port (fcname): "fcs0" -> even, "fcs1" -> odd, ...
function fcs_parity($fcs) {
if (preg_match('/(\d+)\s*$/', $fcs, $m)) {
return (intval($m[1]) % 2 === 0) ? 'even' : 'odd';
}
return 'unknown';
}
// Balancing rule for one client on a given couple.
// Returns null when balanced, otherwise a short problem label (FR).
// Expected: exactly 2 paths, on the 2 different VIOs, one even fcs + one odd fcs.
function evaluate_client_balance($paths) {
$nRows = count($paths);
$vios = array_unique(array_map(fn($p) => $p['vio'], $paths));
$evens = array_filter($paths, fn($p) => $p['parity'] === 'even');
$odds = array_filter($paths, fn($p) => $p['parity'] === 'odd');
$unknown = array_filter($paths, fn($p) => $p['parity'] === 'unknown');
if (count($unknown) > 0) {
return 'fcs illisible';
}
if ($nRows > 2) {
return $nRows . ' chemins (plus de 2)';
}
if (count($vios) < 2) {
return $nRows === 1 ? 'présent sur un seul VIO (chemin manquant)' : 'les 2 chemins sur le même VIO';
}
if (count($evens) !== 1 || count($odds) !== 1) {
return 'même parité sur les 2 VIO';
}
return null;
}
// Human-readable current distribution of a client's paths.
function format_client_paths($paths) {
$parts = [];
foreach ($paths as $p) {
$lbl = $p['parity'] === 'even' ? 'pair' : ($p['parity'] === 'odd' ? 'impair' : '?');
$parts[] = strtoupper($p['vio']) . ':' . $p['fcname'] . ' (' . $lbl . ')';
}
return implode(' | ', $parts);
}
// ---------------------------------------------------------------------------
// Data: FC status per VIO
// ---------------------------------------------------------------------------
$vioRows = Invoke_Infra("select * from X_fcstat_vio");
$groupedVios = [];
if (!empty($vioRows)) {
foreach ($vioRows as $row) {
$vioName = trim($row['vio'] ?? 'UNKNOWN');
$groupedVios[$vioName][] = $row;
}
}
// Group the VIOs into couples, and keep couples + members naturally sorted.
$couples = [];
foreach ($groupedVios as $vioName => $rows) {
$couples[vio_couple_key($vioName)][$vioName] = $rows;
}
ksort($couples, SORT_NATURAL | SORT_FLAG_CASE);
foreach ($couples as $ck => &$members) {
ksort($members, SORT_NATURAL | SORT_FLAG_CASE);
}
unset($members);
// ---------------------------------------------------------------------------
// Data: client mapping for the balancing check
// Error lines (inerr <> 0 or VIOErr <> 0) are excluded (shown on a dedicated page).
// ---------------------------------------------------------------------------
$cmdbRows = Invoke_Infra("select vio, clintname, fcname, vfcclientname, inerr, VIOErr from X_cmdb_VIO");
$clientPaths = []; // [coupleKey][clintname][] = ['vio','vfc','parity']
if (!empty($cmdbRows)) {
foreach ($cmdbRows as $r) {
if (intval($r['inerr'] ?? 0) !== 0 || intval($r['VIOErr'] ?? 0) !== 0) {
continue;
}
$vio = strtolower(trim($r['vio'] ?? ''));
$client = trim($r['clintname'] ?? '');
if ($vio === '' || $client === '') {
continue;
}
// Redundancy is driven by the PHYSICAL fc port on the VIO (fcname),
// not by vfcclientname (client-side virtual adapter, almost always fcs0/fcs1).
$fcname = strtolower(trim($r['fcname'] ?? ''));
$vfc = strtolower(trim($r['vfcclientname'] ?? ''));
$clientPaths[vio_couple_key($vio)][$client][] = [
'vio' => $vio,
'fcname' => $fcname,
'vfc' => $vfc,
'parity' => fcs_parity($fcname),
];
}
}
?>
<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">
<h1>
<span class="badge text-bg-secondary font-weight-bold" style="width:100%;">
VIO FC Status
</span>
</h1>
<div class="container-fluid mt-4">
<div id="accordion">
<?php if (!empty($couples)): ?>
<?php $vioIndex = 0; ?>
<?php foreach ($couples as $coupleKey => $coupleVios): ?>
<?php /* -------- VIO cards of the couple -------- */ ?>
<?php foreach ($coupleVios as $vioName => $rows): ?>
<?php
$vioIndex++;
$collapseId = "collapse_vio_" . $vioIndex;
usort($rows, function ($a, $b) {
$cmpFcs = strnatcasecmp($a['fcs'] ?? '', $b['fcs'] ?? '');
if ($cmpFcs !== 0) {
return $cmpFcs;
}
$cmpFw = strnatcasecmp($a['fw'] ?? '', $b['fw'] ?? '');
if ($cmpFw !== 0) {
return $cmpFw;
}
return strnatcasecmp($a['atype'] ?? '', $b['atype'] ?? '');
});
?>
<?php
$vioHasAlert = false;
foreach ($rows as $checkRow) {
$typeIsDown = strtolower(trim($checkRow['atype'] ?? '')) === 'down';
$tsIsOld = false;
if (!empty($checkRow['ts'])) {
try {
$tsDate = new DateTime($checkRow['ts']);
$now = new DateTime();
$diffSeconds = $now->getTimestamp() - $tsDate->getTimestamp();
$tsIsOld = $diffSeconds > 86400;
} catch (Exception $e) {
$tsIsOld = false;
}
}
if ($typeIsDown || $tsIsOld) {
$vioHasAlert = true;
break;
}
}
$cardColor = $vioHasAlert ? 'red' : 'green';
?>
<div class="card" style="background-color:<?php echo $cardColor; ?>;">
<h4 class="card-header">
<a class="btn text-white fs-6 w-100 text-start" data-bs-toggle="collapse" href="#<?php echo $collapseId; ?>">
<b><?php echo htmlspecialchars(strtoupper($vioName)); ?></b>
</a>
</h4>
</div>
<div id="<?php echo $collapseId; ?>" class="collapse" data-bs-parent="#accordion">
<div class="card-body bg-white border p-0">
<div class="table-responsive">
<table class="table table-bordered table-hover mb-0 text-center" data-toggle="table">
<thead class="table-light">
<tr>
<th data-sortable="true">FCS</th>
<th data-sortable="true">FW</th>
<th data-sortable="true">Speed</th>
<th data-sortable="true">Type</th>
<th data-sortable="true">Uports</th>
<th data-sortable="true">Last</th>
<th data-sortable="true">TFrames</th>
<th data-sortable="true">RFrames</th>
<th data-sortable="true">Fail</th>
<th data-sortable="true">LossC</th>
<th data-sortable="true">LossS</th>
<th data-sortable="true">InvWord</th>
<th data-sortable="true">InvCRC</th>
<th data-sortable="true">TS</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $row): ?>
<?php
$typeIsDown = strtolower(trim($row['atype'] ?? '')) == 'down';
$tsIsOld = false;
if (!empty($row['ts'])) {
try {
$tsDate = new DateTime($row['ts']);
$now = new DateTime();
$diffSeconds = $now->getTimestamp() - $tsDate->getTimestamp();
$tsIsOld = $diffSeconds > 86400;
} catch (Exception $e) {
$tsIsOld = false;
}
}
$rowClass = ($typeIsDown || $tsIsOld) ? 'table-danger fw-bold' : '';
$typeClass = $typeIsDown ? 'bg-danger text-white fw-bold' : '';
$tsClass = $tsIsOld ? 'bg-danger text-white fw-bold' : '';
?>
<tr class="<?php echo $rowClass; ?>">
<td><?php echo htmlspecialchars($row['fcs'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['fw'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['speed'] ?? ''); ?></td>
<td class="<?php echo $typeClass; ?>"><?php echo htmlspecialchars($row['atype'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['uports'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['last'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['tframes'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['rframes'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['fail'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['lossc'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['losss'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['invword'] ?? ''); ?></td>
<td><?php echo htmlspecialchars($row['invcrc'] ?? ''); ?></td>
<td class="<?php echo $tsClass; ?>" style="white-space: nowrap;"><?php echo htmlspecialchars($row['ts'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endforeach; ?>
<?php /* -------- Balancing block for this couple -------- */ ?>
<?php
$coupleClients = $clientPaths[$coupleKey] ?? [];
$flagged = [];
$okCount = 0;
foreach ($coupleClients as $clientName => $paths) {
$problem = evaluate_client_balance($paths);
if ($problem === null) {
$okCount++;
} else {
$flagged[$clientName] = ['problem' => $problem, 'paths' => $paths];
}
}
ksort($flagged, SORT_NATURAL | SORT_FLAG_CASE);
$hasData = !empty($coupleClients);
$balOk = $hasData && empty($flagged);
$balColor = !$hasData ? '#6c757d' : ($balOk ? 'green' : 'red');
?>
<div class="card mb-2" style="border-color:<?php echo $balColor; ?>;">
<div class="card-header text-white text-center fw-bold" style="background-color:<?php echo $balColor; ?>;">
<i class="bi bi-diagram-3"></i>
Équilibrage <?php echo htmlspecialchars(strtoupper($coupleKey)); ?>
<?php if (!$hasData): ?>
— aucune donnée client
<?php elseif ($balOk): ?>
— OK (<?php echo $okCount; ?> clients vérifiés)
<?php else: ?>
— <?php echo count($flagged); ?> client(s) à corriger (<?php echo $okCount; ?> OK)
<?php endif; ?>
</div>
<?php if ($hasData && !$balOk): ?>
<div class="card-body bg-white p-0">
<div class="table-responsive">
<table class="table table-bordered table-hover mb-0" data-toggle="table">
<thead class="table-light">
<tr>
<th data-sortable="true">Client</th>
<th data-sortable="true">Répartition actuelle</th>
<th data-sortable="true">Problème</th>
</tr>
</thead>
<tbody>
<?php foreach ($flagged as $clientName => $info): ?>
<tr class="table-danger">
<td class="fw-bold"><?php echo htmlspecialchars($clientName); ?></td>
<td><?php echo htmlspecialchars(format_client_paths($info['paths'])); ?></td>
<td><?php echo htmlspecialchars($info['problem']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
<hr>
<?php endforeach; ?>
<?php else: ?>
<div class="alert alert-warning">
No VIO FC data available.
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</body>
<script src="/js/switch.js"></script>
</html>

199
Veeam/veeam.php Normal file
View File

@@ -0,0 +1,199 @@
<!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</title>
<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>
</head>
<body class="bg-light text-dark">
<?php include $_SERVER['DOCUMENT_ROOT'] . "/include/all.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="flex: 0 0 230px;">
<?php include $_SERVER['DOCUMENT_ROOT'] . "/navbar.html"; ?>
</div>
<div class="col py-3">
<h2><span class="badge text-bg-secondary w-100" id="ERR">Veeam</span></h2>
<div class="container-fluid">
<table class='table table-bordered table-hover table-sm' id='t1'
data-toggle="table" data-search="true" data-show-columns="true"
data-show-export="true" data-sortable="true" data-height="620">
<thead>
<tr>
<th data-field='vm' data-sortable='true'><?php echo $w_VMs ;?></th>
<th data-field='LastRun' data-sortable='true'><?php echo $w_backuplu ;?></th>
<th data-field='Status' data-sortable='true'><?php echo $w_lastResult ;?></th>
<th data-field='LastGood' data-sortable='true'><?php echo $w_lastGoodBackup ;?></th>
<th data-field='Size' data-sortable='true'><?php echo $w_size ;?> (GB)</th>
<th data-field='Duration' data-sortable='true'>Duration</th>
<th data-field='Policy' data-sortable='true'><?php echo $w_policy ;?></th>
<th data-field='Error' data-sortable='true'>Message</th>
</tr>
</thead>
<tbody>
<?php
$conn = DB_INFRA();
$sql_vms = "SELECT name from cmdb_vms where CAST(lastinventory AS DATETIME) > DATEADD(DAY, -2, GETDATE()) ";
$rs_vms = odbc_exec($conn, $sql_vms);
$vmsae = [];
while ($row = odbc_fetch_array($rs_vms)){
$vmsae[] = $row['name'];
}
$conn = DB_INFRA();
$sql = "SELECT * FROM Veeam_Reporting";
$rs = odbc_exec($conn, $sql);
$total = 0; $er = 0; $outdated = 0; $excluded = 0; $success = 0; $war = 0;
$reportData = [];
// 1. Stockage et pré-calcul des poids de tri
while ($row = odbc_fetch_array($rs)) {
if(!in_array($row['ClientName'], $vmsae)) continue;
$total++;
$rowClass = "";
$sortWeight = 4; // Par défaut Success
$LastGoodStr = "NEVER";
// Calcul de l'ancienneté
$diffDays = 999;
if (!empty($row['LastSuccess']) && $row['LastSuccess'] != '1900-01-01') {
$date1 = date_create($row['LastSuccess']);
$diff = date_diff($date1, date_create(date("Y-m-d")));
$diffDays = (int)$diff->format("%R%a");
$LastGoodStr = date('Y-m-d H:i', strtotime($row['LastSuccess'])) . " (".$diffDays."J)";
if($diffDays > 365){$LastGoodStr = "NEVER";}
}
// Nouvelle logique de tri hiérarchique
if ($row['LastStatus'] == 'Failed' || $row['LastStatus'] == 'Unknown') {
$rowClass = "table-danger";
$sortWeight = 0;
$er++;
} elseif ($row['LastStatus'] == 'Warning') {
$rowClass = "table-warning";
$sortWeight = 1;
$war++;
} elseif ($diffDays >= 2) {
$rowClass = "table-info";
$sortWeight = 2;
$outdated++;
} elseif ($row['LastStatus'] == 'In Progress') {
$rowClass = "table-info";
$sortWeight = 3;
} else {
$rowClass = "table-success";
$sortWeight = 4;
$success++;
}
// On stocke les infos pour le tri PHP multiniveau
$reportData[] = [
'sort_weight' => $sortWeight,
'client_name' => $row['ClientName'],
'row_class' => $rowClass,
'last_run' => $row['LastRun'],
'last_status' => $row['LastStatus'],
'last_good' => $LastGoodStr,
'size' => $row['SizeGB'],
'duration' => $row['Duration'],
'policy_name' => $row['PolicyName'],
'error_message'=> $row['ErrorMessage']
];
}
// 2. Tri multiniveau en PHP : par poids d'abord, puis par nom de VM
usort($reportData, function($a, $b) {
if ($a['sort_weight'] === $b['sort_weight']) {
return strcasecmp($a['client_name'], $b['client_name']); // Tri alphabétique A-Z de la VM
}
return $a['sort_weight'] <=> $b['sort_weight']; // Tri ascendant du poids (0 à 4)
});
// 3. Rendu HTML des lignes triées
foreach ($reportData as $data) {
echo "<tr class='{$data['row_class']}'>";
echo "<td><b>{$data['client_name']}</b></td>";
echo "<td>" . date('Y-m-d H:i', strtotime($data['last_run'])) . "</td>";
// Conserve le data-sort pour permettre à l'utilisateur de retrier via bootstrap-table s'il clique sur la colonne
echo "<td data-sort='{$data['sort_weight']}'>{$data['last_status']}</td>";
echo "<td>{$data['last_good']}</td>";
echo "<td>{$data['size']}</td>";
echo "<td>{$data['duration']}</td>";
echo "<td>{$data['policy_name']}</td>";
echo "<td style='font-size: 0.8em;'><i>{$data['error_message']}</i></td>";
echo "</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php
$success = $total - $er - $outdated - $excluded -$war;
$summary = "Veeam (<span class='text-light'>$success OK</span> - <span class='text-danger'>$er Error</span> - <span class='text-warning'>$war Warning</span> - <span class='text-info'>$outdated $w_outdated</span>)";
?>
</body>
<script>
$(function() {
// Initialisation unique avec tri par défaut basé sur l'ordre du DOM généré par le PHP
$('#t1').bootstrapTable({
sortName: 'Status',
sortOrder: 'asc'
});
document.getElementById("ERR").innerHTML = "<?php echo $summary; ?>";
});
</script>
<script src="/js/switch.js"></script>
</html>
<script>
function resizeTable() {
const table = document.getElementById('t1');
// position du tableau dans la page
const top = table.getBoundingClientRect().top;
// marge basse
const bottomMargin = 0;
// hauteur disponible
const height = window.innerHeight - top - bottomMargin;
$('#t1').bootstrapTable('refreshOptions', {
height: height
});
}
$(function () {
resizeTable();
$(window).on('resize', resizeTable);
});
</script>

View File

@@ -84,7 +84,7 @@
$ts = $s['ts']; $ts = $s['ts'];
$lastBackup = (explode(".",$s['LastBackup'] ?? ''))[0]; $lastBackup = (explode(".",$s['LastBackup'] ?? ''))[0];
$location = $s['Location']; $location = $s['Location'];
$log = $s['Log']; $log = $s['Log'] ?? '';
$backuptype = $s['BackupType']; $backuptype = $s['BackupType'];
if(str_contains($log, "Length")) { if(str_contains($log, "Length")) {

View File

@@ -35,6 +35,7 @@
// Construction de la requête SQL // Construction de la requête SQL
$base_sql = "SELECT * FROM x_inventory WHERE type IS NOT NULL and hostname not in ('DUNAPPDNIM51','DUNAPPDNIM52') "; $base_sql = "SELECT * FROM x_inventory WHERE type IS NOT NULL and hostname not in ('DUNAPPDNIM51','DUNAPPDNIM52') ";
$base_sql = "SELECT * FROM x_inventory WHERE hostname not in ('DUNAPPDNIM51','DUNAPPDNIM52') AND hostname not like '%VIO%' ";
$conditions = []; $conditions = [];
if ($show_aix) { if ($show_aix) {
@@ -43,7 +44,7 @@
} }
if ($show_linux) { if ($show_linux) {
// D'après votre code, tout ce qui n'est pas 'AIX' est considéré comme Linux // D'après votre code, tout ce qui n'est pas 'AIX' est considéré comme Linux
$conditions[] = "type <> 'AIX'"; $conditions[] = "(type <> 'AIX' OR type IS NULL)";
} }
if (!empty($conditions)) { if (!empty($conditions)) {
@@ -66,6 +67,7 @@
usort($versions, 'version_compare'); usort($versions, 'version_compare');
$UTDBES = end($versions); $UTDBES = end($versions);
$UTDFI = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'fi'"))[0]['utd']; $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'];
?> ?>
@@ -108,6 +110,7 @@
<th data-field="Zabbix" data-sortable="true">Zabbix</th> <th data-field="Zabbix" data-sortable="true">Zabbix</th>
<th data-field="BES" data-sortable="true" data-visible="false">BES</th> <th data-field="BES" data-sortable="true" data-visible="false">BES</th>
<th data-field="FI" data-sortable="true" data-visible="false">Fusion Inv.</th> <th data-field="FI" data-sortable="true" data-visible="false">Fusion Inv.</th>
<th data-field="xs" data-sortable="true" data-visible="false">XenSam</th>
</tr> </tr>
</thead> </thead>
@@ -319,6 +322,26 @@
} else { } else {
echo "<td></td>"; echo "<td></td>";
} }
//XenSam
$s1s = $s['xsState'];
$s1v = $s['xsVersion'];
if ($typ == "LINUX") {
if ($s1s == "Y") {
if ($s1v == $UTDxs) {
echo "<td class='table-success'>$s1v $s1s</td>";
} else {
echo "<td class='table-warning'><b>$s1v</b> $s1s</td>";
}
} else {
if ($s1v == "") {
echo "<td class='table-danger text-center'>MISSING</td>";
} else {
echo "<td class='table-danger'>$s1v <b>$s1s</b></td>";
}
}
} else {
echo "<td></td>";
}
echo "</tr>"; echo "</tr>";
} }

View File

@@ -40,6 +40,14 @@
// 1. DATA PROCESSING (PHP) // 1. DATA PROCESSING (PHP)
// ========================================== // ==========================================
// Parity of a physical fc port (fcname): "fcs0" -> even, "fcs1" -> odd, ...
function fcs_parity($fcs) {
if (preg_match('/(\d+)\s*$/', strtolower(trim((string)$fcs)), $m)) {
return (intval($m[1]) % 2 === 0) ? 'even' : 'odd';
}
return 'unknown';
}
$rawRows = Invoke_Infra("SELECT * FROM x_cmdb_VIO"); $rawRows = Invoke_Infra("SELECT * FROM x_cmdb_VIO");
if (!$rawRows) $rawRows = []; if (!$rawRows) $rawRows = [];
@@ -68,6 +76,7 @@ foreach ($groupedData as $clientKey => $rows) {
$names = []; $inerrs = []; $vfcs = []; $vios = []; $names = []; $inerrs = []; $vfcs = []; $vios = [];
$clintids = []; $fcnames = []; $tss = []; $vioerrs = []; $clintids = []; $fcnames = []; $tss = []; $vioerrs = [];
$parities = [];
$hasError = false; $hasError = false;
$maxVioErr = 0; $maxVioErr = 0;
@@ -89,28 +98,45 @@ foreach ($groupedData as $clientKey => $rows) {
$clintids[] = $r['clintid'] ?? '-'; $clintids[] = $r['clintid'] ?? '-';
$fcnames[] = $r['fcname'] ?? '-'; $fcnames[] = $r['fcname'] ?? '-';
$tss[] = $r['ts'] ?? '-'; $tss[] = $r['ts'] ?? '-';
// Redundancy check is on the physical fc port (fcname).
$parities[] = fcs_parity($r['fcname'] ?? '');
}
// Parity issue = exactly 2 known fcs parities and they are identical
// (both even or both odd -> no fabric redundancy).
$hasParityIssue = false;
$knownParities = array_values(array_filter($parities, fn($p) => $p !== 'unknown'));
if (count($knownParities) === 2 && $knownParities[0] === $knownParities[1]) {
$hasParityIssue = true;
} }
$displayRows[] = [ $displayRows[] = [
'clintname' => $clientKey, 'clintname' => $clientKey,
'hasError' => $hasError, 'hasError' => $hasError,
'maxVioErr' => $maxVioErr, 'hasParityIssue' => $hasParityIssue,
'name' => implode('<br>', $names), 'maxVioErr' => $maxVioErr,
'inerr' => implode('<br>', $inerrs), 'name' => implode('<br>', $names),
'vfc' => implode('<br>', $vfcs), 'inerr' => implode('<br>', $inerrs),
'vio' => implode('<br>', $vios), 'vfc' => implode('<br>', $vfcs),
'clintid' => implode('<br>', $clintids), 'vio' => implode('<br>', $vios),
'fcname' => implode('<br>', $fcnames), 'clintid' => implode('<br>', $clintids),
'ts' => implode('<br>', $tss), 'fcname' => implode('<br>', $fcnames),
'vioerr' => implode('<br>', $vioerrs), 'ts' => implode('<br>', $tss),
'vioerr' => implode('<br>', $vioerrs),
]; ];
} }
// 4. Final Sort: 1. Warning (inerr), 2. VIOErr > 0, 3. Alpha // 4. Final Sort:
// 1. Top tier: InErr (table-warning) OR parity issue (table-danger)
// 2. VIOErr > 0
// 3. Alpha
usort($displayRows, function($a, $b) { usort($displayRows, function($a, $b) {
// Priority 1: InErr (table-warning) // Priority 1: warning OR parity issue -> floated to the top, same standing
if ($a['hasError'] && !$b['hasError']) return -1; $aTop = $a['hasError'] || $a['hasParityIssue'];
if (!$a['hasError'] && $b['hasError']) return 1; $bTop = $b['hasError'] || $b['hasParityIssue'];
if ($aTop && !$bTop) return -1;
if (!$aTop && $bTop) return 1;
// Priority 2: VIOErr count // Priority 2: VIOErr count
if ($a['maxVioErr'] > 0 && $b['maxVioErr'] == 0) return -1; if ($a['maxVioErr'] > 0 && $b['maxVioErr'] == 0) return -1;
@@ -155,7 +181,17 @@ usort($displayRows, function($a, $b) {
<tbody> <tbody>
<?php foreach ($displayRows as $row): ?> <?php foreach ($displayRows as $row): ?>
<?php $class = $row['hasError'] ? 'table-warning' : ''; ?> <?php
// Parity issue (red) takes precedence over inerr warning (yellow);
// both are floated to the top by the sort above.
if ($row['hasParityIssue']) {
$class = 'table-danger';
} elseif ($row['hasError']) {
$class = 'table-warning';
} else {
$class = '';
}
?>
<tr class="<?php echo $class; ?>"> <tr class="<?php echo $class; ?>">
<td class="fw-bold align-middle"><?php echo htmlspecialchars($row['clintname']); ?></td> <td class="fw-bold align-middle"><?php echo htmlspecialchars($row['clintname']); ?></td>
<td class="cell-multiline"><?php echo $row['name']; ?></td> <td class="cell-multiline"><?php echo $row['name']; ?></td>
@@ -164,7 +200,12 @@ usort($displayRows, function($a, $b) {
<td class="cell-multiline"><?php echo $row['vfc']; ?></td> <td class="cell-multiline"><?php echo $row['vfc']; ?></td>
<td class="cell-multiline"><?php echo $row['vio']; ?></td> <td class="cell-multiline"><?php echo $row['vio']; ?></td>
<td class="cell-multiline"><?php echo $row['clintid']; ?></td> <td class="cell-multiline"><?php echo $row['clintid']; ?></td>
<td class="cell-multiline"><?php echo $row['fcname']; ?></td> <td class="cell-multiline">
<?php echo $row['fcname']; ?>
<?php if ($row['hasParityIssue']): ?>
<br><span class="badge bg-danger">même parité</span>
<?php endif; ?>
</td>
<td class="cell-multiline small"><?php echo $row['ts']; ?></td> <td class="cell-multiline small"><?php echo $row['ts']; ?></td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@@ -188,4 +229,4 @@ usort($displayRows, function($a, $b) {
$(window).resize(function () { adjustTableHeight(); }); $(window).resize(function () { adjustTableHeight(); });
}); });
</script> </script>
</html> </html>

View File

@@ -40,6 +40,7 @@
<thead> <thead>
<tr> <tr>
<th data-field="Name" data-sortable="true"><?php echo $w_name; ?></th> <th data-field="Name" data-sortable="true"><?php echo $w_name; ?></th>
<th data-field="Edit" >Edit</th>
<th data-field="OS" data-sortable="true"><?php echo $w_os; ?></th> <th data-field="OS" data-sortable="true"><?php echo $w_os; ?></th>
<th data-field="Criticity" data-sortable="true"><?php echo $w_crit; ?></th> <th data-field="Criticity" data-sortable="true"><?php echo $w_crit; ?></th>
<th data-field="AD" data-sortable="true"><?php echo $w_ad; ?></th> <th data-field="AD" data-sortable="true"><?php echo $w_ad; ?></th>
@@ -97,7 +98,7 @@
$lastUpdate = htmlspecialchars($lastUpdate ?? '', ENT_QUOTES, 'UTF-8'); $lastUpdate = htmlspecialchars($lastUpdate ?? '', ENT_QUOTES, 'UTF-8');
$okText = htmlspecialchars($okText, ENT_QUOTES, 'UTF-8'); $okText = htmlspecialchars($okText, ENT_QUOTES, 'UTF-8');
if ($status === 'Y') { if ($status === 'Y' || $status === 'Veeam') {
if (empty($lastUpdate)) { if (empty($lastUpdate)) {
return "<td class='text-white' style='background: linear-gradient(90deg, rgba(25,135,84,1) 50%, rgba(255,193,7,1) 100%);'>$okText</td><td class='bg-warning text-black'>Missing</td>"; return "<td class='text-white' style='background: linear-gradient(90deg, rgba(25,135,84,1) 50%, rgba(255,193,7,1) 100%);'>$okText</td><td class='bg-warning text-black'>Missing</td>";
} }
@@ -159,11 +160,12 @@
// Row Rendering // Row Rendering
$serverName = htmlspecialchars($row['Server'] ?? '', ENT_QUOTES, 'UTF-8'); $serverName = htmlspecialchars($row['Server'] ?? '', ENT_QUOTES, 'UTF-8');
$serverUrl = urlencode($row['Server'] ?? ''); $serverName = str_replace('+', '', $serverName);
$serverUrl = str_replace('+', '',urlencode($row['Server'] ?? ''));
if ($isCompliant) { if ($isCompliant) {
$serverCell = "<td class='bg-success text-white'><b><a href='/crossover/Detail.php?server=$serverUrl' target='_blank' class='link-light'>$serverName</a></b></td>"; $serverCell = "<td class='bg-success text-white'><b><a href='/Inventory/Server-Detail.php?s=$serverUrl' target='_blank' class='link-light'>$serverName</a></b></td>";
} else { } else {
$serverCell = "<td><b><a href='/crossover/Detail.php?server=$serverUrl' target='_blank'>$serverName</a></b></td>"; $serverCell = "<td><b><a href='/Inventory/Server-Detail.php?s=$serverUrl' target='_blank'>$serverName</a></b></td>";
} }
$osCell = "<td class='bg-warning'>&nbsp;</td>"; $osCell = "<td class='bg-warning'>&nbsp;</td>";
@@ -173,7 +175,9 @@
} }
echo "<tr>"; echo "<tr>";
echo $serverCell . $osCell; echo $serverCell;
echo "<td><a href='Detail.php?server=$serverUrl' target='_blank' title='Edit'><i class='bi bi-pencil'></i></a></td>";
echo $osCell;
echo "<td>" . htmlspecialchars($row['crit'] ?? '') . "</td>"; echo "<td>" . htmlspecialchars($row['crit'] ?? '') . "</td>";
echo renderStatusCellWithDate($row['AD'], $row['ADlu'], 45); echo renderStatusCellWithDate($row['AD'], $row['ADlu'], 45);
echo renderStatusCellWithDate($row['GLPI'], $row['GLPIlu'], 7); echo renderStatusCellWithDate($row['GLPI'], $row['GLPIlu'], 7);
@@ -186,7 +190,7 @@
// BACKUP // BACKUP
if (($row['NBU'] ?? '') === 'Y') { echo renderStatusCellWithDate($row['NBU'], $row['NBUlu'], 30, 'OK (NBU)'); } if (($row['NBU'] ?? '') === 'Y') { echo renderStatusCellWithDate($row['NBU'], $row['NBUlu'], 30, 'OK (NBU)'); }
elseif (($row['DPM'] ?? '') === 'Y') { echo renderStatusCellWithDate($row['DPM'], $row['DPMlu'], 30, 'OK (DPM)'); } elseif (($row['NBU'] ?? '') === 'Veeam') { echo renderStatusCellWithDate($row['NBU'], $row['NBUlu'], 30, 'OK (Veeam)'); }
else { echo renderStatusCellWithDate($row['NBU'] ?? $row['DPM'] ?? null, null, 30); } else { echo renderStatusCellWithDate($row['NBU'] ?? $row['DPM'] ?? null, null, 30); }
// SCOM // SCOM

Binary file not shown.

View File

@@ -97,6 +97,36 @@
} }
} }
}
function Invoke_Infra_long($request)
{
error_reporting(E_ALL ^ E_WARNING);
$user = "INFRA_dbo";
$pwd = decypher("infra");
$server = "DUN-PRD-R1MSSQL.armony.net\PRD";
$database = "INFRA";
$conn = odbc_connect("Driver={ODBC Driver 18 for SQL Server};Server=$server;Database=$database;TrustServerCertificate=yes;", $user, $pwd);
if (!($conn)) {
echo "Pas de connexion";
}
$rs = odbc_exec($conn, $request);
odbc_longreadlen($rs, 1048576); // 1 Mo, large devant tes 289 Ko max
if (str_contains(strtoupper($request), "SELECT")) {
while ($row = odbc_fetch_array($rs)) {
$answer[] = $row;
}
}
if (isset($answer)) {
return $answer;
} else {
if ($rs) {
return "OK";
} else {
error_reporting(E_ALL);
return "ERROR : " . odbc_errormsg($conn);
}
}
} }
function Invoke_WebSelfInfra($request) function Invoke_WebSelfInfra($request)
{ {
@@ -222,6 +252,64 @@
} }
} }
} }
function Invoke_Arrets($request)
{
error_reporting(E_ALL ^ E_WARNING);
$user = "kpiamf_arrets_view";
$pwd = decypher("arrets");
$server = "SQL_DW_PRD_KPIAMF.appliarmony.net";
$database = "KPIAMF";
$conn = odbc_connect("Driver={SQL Server};Server=$server;Database=$database;", $user, $pwd);
if (!($conn)) {
echo "Pas de connexion";
}
$rs = odbc_exec($conn, $request);
if (str_contains(strtoupper($request), "SELECT")) {
while ($row = odbc_fetch_array($rs)) {
$answer[] = $row;
}
}
if (isset($answer)) {
return $answer;
} else {
if ($rs) {
return "OK";
} else {
error_reporting(E_ALL);
return "ERROR : " . odbc_errormsg($conn);
}
}
}
function Invoke_RH($request)
{
error_reporting(E_ALL ^ E_WARNING);
$user = "hrdata_mgr_view";
$pwd = decypher("rh");
$server = "SQL_DW_PRD_HRDATA.appliarmony.net";
$database = "HRDATA";
$conn = odbc_connect("Driver={SQL Server};Server=$server;Database=$database;", $user, $pwd);
if (!($conn)) {
echo "Pas de connexion";
}
$rs = odbc_exec($conn, $request);
if (str_contains(strtoupper($request), "SELECT")) {
while ($row = odbc_fetch_array($rs)) {
$answer[] = $row;
}
}
if (isset($answer)) {
return $answer;
} else {
if ($rs) {
return "OK";
} else {
error_reporting(E_ALL);
return "ERROR : " . odbc_errormsg($conn);
}
}
}
//DB Conn// //DB Conn//
function DB_SCCM(){ function DB_SCCM(){
@@ -406,9 +494,9 @@
} }
</style> </style>
<!-- // SNOWFLAKES -->
<canvas id="snow-canvas"></canvas> <canvas id="snow-canvas"></canvas>
<button id="snow-toggle-btn" title="Activer/Désactiver la neige">❄️</button> <button id="snow-toggle-btn" title="Activer/Désactiver la neige">❄️</button>
<script> <script>
(function() { (function() {
// --- 1. GESTION DES DATES (15 Déc au 5 Jan) --- // --- 1. GESTION DES DATES (15 Déc au 5 Jan) ---

View File

@@ -41,6 +41,9 @@
$nav_10_1 = "Servers"; $nav_10_1 = "Servers";
$nav_10_2 = "Workstations"; $nav_10_2 = "Workstations";
$nav_11 = "On Call";
$nav_11_1 = "ServiceNow P1 & P2";
$nav_links = "Links"; $nav_links = "Links";
$nav_search = "Server Search..."; $nav_search = "Server Search...";
@@ -64,6 +67,7 @@
$ti_17 = "HeartBeat Map for servers NOT in maintenance"; $ti_17 = "HeartBeat Map for servers NOT in maintenance";
$ti_18 = "Cluster Resources"; $ti_18 = "Cluster Resources";
$ti_19 = "Virtual Machines"; $ti_19 = "Virtual Machines";
$ti_20 = "Service-Now P1 & P2 Incidents. In progress, opened in the 72 last hours";
// words // words
$w_server = "Server"; $w_server = "Server";
@@ -157,3 +161,7 @@
$w_unclusteredVMs = "Unclustered VM(s)"; $w_unclusteredVMs = "Unclustered VM(s)";
$w_vmCpuChecked = "VM(s) With 'CPU Compatibility' checked"; $w_vmCpuChecked = "VM(s) With 'CPU Compatibility' checked";
$w_vmDynamicVHD = "VM(s) with dynamic VHD"; $w_vmDynamicVHD = "VM(s) with dynamic VHD";
$w_group = "Group";
$w_assigned = "Assigned to";
$w_opened = "Opened on";
$w_incFound = "Tickets found";

View File

@@ -40,6 +40,9 @@
$nav_10_1 = "Serveurs"; $nav_10_1 = "Serveurs";
$nav_10_2 = "Postes de Travail"; $nav_10_2 = "Postes de Travail";
$nav_11 = "Astreinte";
$nav_11_1 = "P1 et P2 ServiceNow";
$nav_links = "Liens"; $nav_links = "Liens";
$nav_search = "Recherche Serveur..."; $nav_search = "Recherche Serveur...";
@@ -63,6 +66,7 @@
$ti_17 = "Carte des signes de présence pour les Serveurs qui ne sont pas en maintenance"; $ti_17 = "Carte des signes de présence pour les Serveurs qui ne sont pas en maintenance";
$ti_18 = "Ressources des Grappes de Serveurs"; $ti_18 = "Ressources des Grappes de Serveurs";
$ti_19 = "Machines Virtuelles"; $ti_19 = "Machines Virtuelles";
$ti_20 = "Incidents Service-Now P1 & P2. En cours, ouverts depuis moins de 72h";
// words // words
$w_server = "Serveur"; $w_server = "Serveur";
@@ -156,3 +160,7 @@
$w_unclusteredVMs = "MV(s) non en ressource"; $w_unclusteredVMs = "MV(s) non en ressource";
$w_vmCpuChecked = "MV(s) avec 'Compatibilité CPU' coché"; $w_vmCpuChecked = "MV(s) avec 'Compatibilité CPU' coché";
$w_vmDynamicVHD = "MV(s) avec VHD dynamique"; $w_vmDynamicVHD = "MV(s) avec VHD dynamique";
$w_group = "Groupe";
$w_assigned = "Assigné à";
$w_opened = "Ouvert le";
$w_incFound = "Incidents ouverts";

1605
index.php

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,33 @@
let table = $('#t1'); const $table = $('#t1');
$(function () { // Function to calculate and apply the perfect height
let options = table.bootstrapTable('getOptions'); function updateTableHeight() {
options.height = document.getElementById('content').clientHeight - 170; // baseHeight uses the window inner height to stay stable
table.bootstrapTable('refreshOptions', options); const availableHeight = window.innerHeight - 170;
});
function tableresize() { // Ensure we don't drop below a minimum height (e.g., 300px)
let options = table.bootstrapTable('getOptions'); const finalHeight = Math.max(availableHeight, 300);
options.height = document.getElementById('content').clientHeight - 170;
table.bootstrapTable('refreshOptions', options); // Bootstrap Table natively supports setting just the height without rewriting all options
$table.bootstrapTable('resetView', {
height: finalHeight
});
} }
window.addEventListener("resize", tableresize); // Debounce function to prevent performance issues during active resizing
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Initialization when DOM is fully ready and Bootstrap Table is loaded
$(function () {
// Small timeout to let Bootstrap Table finalize its internal rendering first
setTimeout(updateTableHeight, 200);
});
// Throttled listener for window resize
window.addEventListener('resize', debounce(updateTableHeight, 150));

33
js/tableResize2.js Normal file
View File

@@ -0,0 +1,33 @@
const $table = $('#t1');
// Function to calculate and apply the perfect height
function updateTableHeight() {
// baseHeight uses the window inner height to stay stable
const availableHeight = window.innerHeight - 170;
// Ensure we don't drop below a minimum height (e.g., 300px)
const finalHeight = Math.max(availableHeight, 300);
// Bootstrap Table natively supports setting just the height without rewriting all options
$table.bootstrapTable('resetView', {
height: finalHeight
});
}
// Debounce function to prevent performance issues during active resizing
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Initialization when DOM is fully ready and Bootstrap Table is loaded
$(function () {
// Small timeout to let Bootstrap Table finalize its internal rendering first
setTimeout(updateTableHeight, 200);
});
// Throttled listener for window resize
window.addEventListener('resize', debounce(updateTableHeight, 150));

View File

@@ -60,6 +60,15 @@
<a href="/crossover/GlobalCrossover.php" class="nav-link px-0 align-middle"> <a href="/crossover/GlobalCrossover.php" class="nav-link px-0 align-middle">
<i class="fs-6 bi-table text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><?php echo $nav_2; ?></span></a> <i class="fs-6 bi-table text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><?php echo $nav_2; ?></span></a>
</li> </li>
<li>
<a href="#AD" data-bs-toggle="collapse" class="nav-link px-0 align-middle">
<i class="fs-6 bi bi-shield-lock me-1 text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7">AD</span><i class="bi bi-caret-down"></i> </a>
<ul class="collapse nav flex-column ms-1" id="AD" data-bs-parent="#menu">
<li class="w-100">
<a href="/AD/LocalAdmins.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Local Admins</span></a>
</li>
</ul>
</li>
<li> <li>
<a href="#SharedFolders" data-bs-toggle="collapse" class="nav-link px-0 align-middle"> <a href="#SharedFolders" data-bs-toggle="collapse" class="nav-link px-0 align-middle">
<i class="fs-6 bi bi-folder-symlink-fill text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><?php echo $nav_4; ?></span><i class="bi bi-caret-down"></i> </a> <i class="fs-6 bi bi-folder-symlink-fill text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><?php echo $nav_4; ?></span><i class="bi bi-caret-down"></i> </a>
@@ -99,8 +108,8 @@
<ul class="collapse nav flex-column ms-1" id="HyperV" data-bs-parent="#menu"> <ul class="collapse nav flex-column ms-1" id="HyperV" data-bs-parent="#menu">
<li class="w-100"> <li class="w-100">
<a href="/Hyper-V/Clusters.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_3_1 ;?></span></a> <a href="/Hyper-V/Clusters.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_3_1 ;?></span></a>
<a href="/Hyper-V/VMs-Backups.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_3_2 ;?></span></a>
<!-- <!--
<a href="/Hyper-V/VMs-Backups.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_3_2 ;?></span></a>
<a href="/Hyper-V/SYN-VMList.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Synergy VMs</span></a> <a href="/Hyper-V/SYN-VMList.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Synergy VMs</span></a>
<a href="/Hyper-V/Legacy-VMList.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Legacy Hyper-V VMs</span></a> <a href="/Hyper-V/Legacy-VMList.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Legacy Hyper-V VMs</span></a>
<a href="/Hyper-V/migration.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Migration FollowUp</span></a> <a href="/Hyper-V/migration.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Migration FollowUp</span></a>
@@ -118,6 +127,7 @@
<li class="w-100"> <li class="w-100">
<a href="/Storage/Dashboard.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_7_1; ?></span></a> <a href="/Storage/Dashboard.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_7_1; ?></span></a>
<a href="/Storage/switchssan.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_7_2; ?></span></a> <a href="/Storage/switchssan.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_7_2; ?></span></a>
<a href="/Storage/vio.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- VIOs</span></a>
</li> </li>
</ul> </ul>
@@ -156,6 +166,7 @@
<ul class="collapse nav flex-column ms-1" id="cluster" data-bs-parent="#menu"> <ul class="collapse nav flex-column ms-1" id="cluster" data-bs-parent="#menu">
<li class="w-100"> <li class="w-100">
<a href="/Inventory/ClusterResource.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_9_1; ?></span></a> <a href="/Inventory/ClusterResource.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_9_1; ?></span></a>
<a href="/Reports/Cluster_compliance.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- Compliance</span></a>
</li> </li>
</ul> </ul>
</li> </li>
@@ -178,6 +189,26 @@
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a href="#Backup" data-bs-toggle="collapse" class="nav-link px-0 align-middle">
<i class="fs-6 bi bi-server text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><small>Backup</small></span><i class="bi bi-caret-down"></i> </a>
<ul class="collapse nav flex-column ms-1" id="Backup" data-bs-parent="#menu">
<li class="w-100">
<a href="/Reports/backups.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Backups</span></a>
<a href="/Reports/Netbackup_Daily.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- NetBackup Daily</span></a>
</li>
</ul>
</li>
<li>
<a href="#astreinte" data-bs-toggle="collapse" class="nav-link px-0 align-middle">
<i class="fs-6 bi bi-headset text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><small><?php echo $nav_11; ?></small></span><i class="bi bi-caret-down"></i> </a>
<ul class="collapse nav flex-column ms-1" id="astreinte" data-bs-parent="#menu">
<li class="w-100">
<a href="/Astreinte/P1-P2.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- <?= $nav_11_1 ?></span></a>
<a href="/Astreinte/Liens.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- <?= $nav_links ?></span></a>
</li>
</ul>
</li>
<br> <br>
<br> <br>
<li class="nav-item"> <li class="nav-item">

410
reports/Backups.php Normal file
View File

@@ -0,0 +1,410 @@
<!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</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>
/* ---- Theme --------------------------------------------------------
switch.js only swaps -light/-dark classes and tags tables with
.table-dark. The accordion paints its own background, so we make it
transparent and let it inherit whatever the body is wearing. */
.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); }
.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); }
/* Sticky needs a non-clipping, non-scrolling ancestor chain. bootstrap-table
wraps the table in .fixed-table-body (overflow:auto), which would capture
the sticky thead and push it down inside the table instead of the page. */
.accordion, .accordion-item { overflow: visible; }
.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; }
/* ---- Sticky block header + sticky table head ---------------------- */
.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.5rem;min-width: 3.5em; }
.accordion-button .block-title { font-size: 2rem; font-weight: 600; letter-spacing: .02em; }
</style>
</head>
<body class="bg-light text-dark">
<?php include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php"; ?>
<?php
/* ============================================================================
Unified backup reporting : Veeam (target) + NetBackup (legacy)
During the migration a VM can still exist in both SQL tables.
It is displayed only on the side holding the most recent backup.
========================================================================== */
/* ---------------------------------------------------------------- helpers */
// Whole days between a date and today. Returns null when the date is unusable.
function days_since($dateStr) {
if (empty($dateStr)) return null;
$ts = strtotime($dateStr);
if ($ts === false || $ts < strtotime('1990-01-01')) return null;
return (int) floor((strtotime(date('Y-m-d')) - strtotime(date('Y-m-d', $ts))) / 86400);
}
// Backups do not run on Sunday night, so Sunday and Monday get a wider window.
function backup_tolerance_days() {
$dow = (int) date('w'); // 0 = Sunday, 1 = Monday
return ($dow === 0 || $dow === 1) ? 4 : 3;
}
function fmt_datetime($value) {
$ts = strtotime((string) $value);
return ($ts && $ts > strtotime('1990-01-01')) ? date('Y-m-d H:i', $ts) : '-';
}
function fmt_lastgood($dateStr) {
$days = days_since($dateStr);
if ($days === null || $days > 365) return 'NEVER';
return date('Y-m-d H:i', strtotime($dateStr)) . " ({$days}J)";
}
/* ------------------------------------------------------- active inventory */
$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; }
/* -------------------------------------------------------------- 1. Veeam */
$veeam = [];
$rows = Invoke_Infra("SELECT * FROM Veeam_Reporting");
foreach ($rows as $row) {
$key = strtoupper(trim($row['ClientName']));
if (!isset($active[$key])) continue;
$ageGood = days_since($row['LastSuccess']);
$status = trim((string) $row['LastStatus']);
if ($status === 'Failed' || $status === 'Unknown' || $status === '') {
$cat = 'error'; $cls = 'table-danger';
} elseif ($status === 'Warning') {
$cat = 'warning'; $cls = 'table-warning';
} elseif ($ageGood === null || $ageGood >= backup_tolerance_days()) {
$cat = 'outdated'; $cls = 'table-info';
} else {
$cat = 'ok'; $cls = 'table-success';
}
$veeam[$key] = [
'cat' => $cat,
'row_class' => $cls,
'ts' => strtotime((string) $row['LastRun']) ?: 0,
'vm' => $row['ClientName'],
'last_run' => fmt_datetime($row['LastRun']),
'status' => $status,
'last_good' => fmt_lastgood($row['LastSuccess']),
'size' => $row['SizeGB'],
'extra' => $row['Duration'],
'policy' => $row['PolicyName'],
'message' => $row['ErrorMessage'],
];
}
/* ---------------------------------------------------------- 2. NetBackup */
$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");
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;
$result = trim((string) $row['LastResult']);
$ageGood = days_since($row['LastKnownGood']);
if ($excluded) {
$cat = 'excluded'; $cls = 'table-secondary';
} elseif ($result !== 'OK') {
// A failed job with a still fresh good copy is only a warning.
if ($ageGood === null || $ageGood >= backup_tolerance_days()) {
$cat = 'error'; $cls = 'table-danger';
} else {
$cat = 'warning'; $cls = 'table-warning';
}
} elseif ($ageGood === null || $ageGood >= backup_tolerance_days()) {
$cat = 'outdated'; $cls = 'table-info';
} else {
$cat = 'ok'; $cls = 'table-success';
}
$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' => '',
];
}
/* ------------------------------------------- 3. Migration de-duplication */
// A VM present on both sides is kept where the most recent backup lives.
$migrated = 0;
foreach (array_keys($netbackup) as $key) {
if (!isset($veeam[$key])) continue;
if ($netbackup[$key]['excluded'] || $veeam[$key]['ts'] >= $netbackup[$key]['ts']) {
unset($netbackup[$key]); // Veeam has taken over
$migrated++;
} else {
unset($veeam[$key]); // NetBackup is still the live copy
}
}
/* -------------------------------------------------------- 4. Bucketing */
$buckets = [
'veeam' => ['error' => [], 'warning' => [], 'outdated' => [], 'ok' => []],
'nb' => ['error' => [], 'warning' => [], 'outdated' => [], 'ok' => [], 'excluded' => []],
];
foreach ($veeam as $e) { $buckets['veeam'][$e['cat']][] = $e; }
foreach ($netbackup as $e) { $buckets['nb'][$e['cat']][] = $e; }
foreach ($buckets as $src => $cats) {
foreach ($cats as $cat => $list) {
usort($list, fn($a, $b) => strcasecmp($a['vm'], $b['vm']));
$buckets[$src][$cat] = $list;
}
}
$veeamTotal = count($veeam);
$nbTotal = count($netbackup);
/* -------------------------------------------------------- 5. Rendering */
$blockMeta = [
'error' => ['label' => 'Error', 'badge' => 'bg-danger', 'icon' => 'bi-x-octagon'],
'warning' => ['label' => 'Warning', 'badge' => 'bg-warning text-dark', 'icon' => 'bi-exclamation-triangle'],
'outdated' => ['label' => 'Outdated', 'badge' => 'bg-info text-dark', 'icon' => 'bi-clock-history'],
'ok' => ['label' => 'OK', 'badge' => 'bg-success', 'icon' => 'bi-check-circle'],
'excluded' => ['label' => 'Excluded', 'badge' => 'bg-secondary', 'icon' => 'bi-slash-circle'],
];
/**
* Render one accordion item holding a bootstrap-table.
* $extraLabel is "Duration" for Veeam and "Host" for NetBackup.
*/
function render_block($blockId, $meta, $rows, $extraLabel, $expanded = false) {
$count = count($rows);
$collapsed = $expanded ? '' : 'collapsed';
$show = $expanded ? 'show' : '';
$aria = $expanded ? 'true' : 'false';
?>
<div class="accordion-item">
<h1 class="accordion-header">
<button class="accordion-button <?php echo $collapsed; ?>" type="button"
data-bs-toggle="collapse" data-bs-target="#c_<?php echo $blockId; ?>"
aria-expanded="<?php echo $aria; ?>" <?php echo $count ? '' : 'disabled'; ?>>
<i class="bi <?php echo $meta['icon']; ?> me-2"></i>
<span class="block-title"><?php echo $meta['label']; ?></span>
<span class="badge <?php echo $meta['badge']; ?> ms-3 block-count"><?php echo $count; ?></span>
</button>
</h1>
<div id="c_<?php echo $blockId; ?>" class="accordion-collapse collapse <?php echo $show; ?>">
<div class="accordion-body p-2">
<?php if ($count === 0) : ?>
<p class="text-muted mb-0">Aucune VM dans cette catégorie.</p>
<?php else : ?>
<table class="table table-bordered table-hover table-sm report-table"
id="t_<?php echo $blockId; ?>" data-sortable="true">
<thead>
<tr>
<th data-field="vm" data-sortable="true"><?php echo $GLOBALS['w_VMs']; ?></th>
<th data-field="lastrun" data-sortable="true"><?php echo $GLOBALS['w_backuplu']; ?></th>
<th data-field="status" data-sortable="true"><?php echo $GLOBALS['w_lastResult']; ?></th>
<th data-field="lastgood" data-sortable="true"><?php echo $GLOBALS['w_lastGoodBackup']; ?></th>
<th data-field="size" data-sortable="true"><?php echo $GLOBALS['w_size']; ?> (GB)</th>
<th data-field="extra" data-sortable="true"><?php echo $extraLabel; ?></th>
<th data-field="policy" data-sortable="true"><?php echo $GLOBALS['w_policy']; ?></th>
<th data-field="message" data-sortable="true">Message</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $r) : ?>
<tr class="<?php echo $r['row_class']; ?>">
<td><b><?php echo htmlspecialchars($r['vm']); ?></b></td>
<td><?php echo htmlspecialchars($r['last_run']); ?></td>
<td><?php echo htmlspecialchars($r['status']); ?></td>
<td><?php echo htmlspecialchars($r['last_good']); ?></td>
<td><?php echo htmlspecialchars((string) $r['size']); ?></td>
<td><?php echo htmlspecialchars((string) $r['extra']); ?></td>
<td><?php echo htmlspecialchars((string) $r['policy']); ?></td>
<td style="font-size:.8em;"><i><?php echo htmlspecialchars((string) $r['message']); ?></i></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</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">
<h2>
<span class="badge text-bg-secondary w-100" id="ERR">
VM Backup —
<span class="text-light"><?php echo $veeamTotal; ?> Veeam</span> /
<span class="text-light"><?php echo $nbTotal; ?> NetBackup</span> —
<span class="text-danger"><?php echo count($buckets['veeam']['error']) + count($buckets['nb']['error']); ?> Error</span> -
<span class="text-warning"><?php echo count($buckets['veeam']['warning']) + count($buckets['nb']['warning']); ?> Warning</span> -
<span class="text-info"><?php echo count($buckets['veeam']['outdated']) + count($buckets['nb']['outdated']); ?> Outdated</span>
</span>
</h2>
<div class="container-fluid">
<!-- ============================ VEEAM ============================ -->
<h4 class="mt-3 mb-2">
<i class="bi bi-shield-check me-1"></i> VEEAM
<small class="text-muted"><?php echo $veeamTotal; ?> VMs</small>
</h4>
<div class="accordion" id="acc_veeam">
<?php
foreach (['error', 'warning', 'outdated', 'ok'] as $cat) {
render_block('veeam_' . $cat, $blockMeta[$cat], $buckets['veeam'][$cat], 'Duration', false);
}
?>
</div>
<!-- ========================== NETBACKUP ========================== -->
<h4 class="mt-4 mb-2">
<i class="bi bi-hdd-stack me-1"></i> NETBACKUP
<small class="text-muted"><?php echo $nbTotal; ?> VMs</small>
</h4>
<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);
}
?>
</div>
</div>
</div>
</div>
</div>
<script src="/js/switch.js"></script>
<script>
$(function () {
// Tables are built lazily, after switch.js has already themed the page,
// so they have to pick up the current theme themselves.
function isDark() {
return document.body.classList.contains('bg-dark');
}
function syncTheme($table) {
$table.toggleClass('table-dark', isDark());
}
// Pin the table head right below its own (sticky) accordion header.
function updateStickyOffset($item) {
var h = $item.find('.accordion-header').outerHeight() || 56;
$item.css('--acc-header-h', h + 'px');
}
// bootstrap-table cannot measure a hidden container, so tables are
// initialised the first time their accordion block is opened.
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'));
});
// Keep tables in sync when the light switch is toggled after load.
$('#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>

View File

@@ -0,0 +1,450 @@
<!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 OT</title>
<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">
<link rel="stylesheet" href="/css/preloader.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>
html, body {
height: 100%;
overflow: hidden;
}
.main-row {
height: 100vh;
}
.left-menu {
flex: 0 0 230px;
height: 100vh;
overflow-y: hidden;
}
.right-content {
height: 100vh;
overflow-y: auto;
padding-top: 0 !important;
}
.sticky-dashboard-header {
position: sticky;
top: 0;
z-index: 1000;
padding-top: 1rem;
padding-bottom: .75rem;
}
.kpi-card { cursor: pointer; transition: transform .1s ease-in-out; }
.kpi-card:hover { transform: translateY(-2px); }
code.sig { font-size: .85em; }
.table td { vertical-align: middle; }
/* Chevron rotation for the collapsible unreachable list */
.collapse-toggle .bi-chevron-down { transition: transform .15s ease-in-out; }
.collapse-toggle[aria-expanded="true"] .bi-chevron-down { transform: rotate(180deg); }
</style>
</head>
<body>
<?php include $_SERVER['DOCUMENT_ROOT']."/include/all.php" ; ?>
<div class="container-fluid">
<div class="row flex-nowrap main-row">
<div class="col-auto px-sm-2 px-0 bg-dark left-menu">
<?php include $_SERVER['DOCUMENT_ROOT']."/navbar.html" ; ?>
</div>
<div class="col right-content">
<?php
// -----------------------------------------------------------
// Read the latest run from the compliance results table
//
// Cluster conformity is now keyed on the BUILD (BuildDrift):
// two nodes sharing the same build but a different UBR/KB are
// OK at cluster level. The UBR gap is kept as a severity /
// priority indicator. Hardware facts (BIOS + model) are read
// for display and light hardware-drift flagging.
// -----------------------------------------------------------
$conn = DB_INFRA();
$sql = "SELECT CONVERT(VARCHAR(36), RunId) AS RunId, ClusterName, NodeName, OsName,
MsPatchLevel, MsBuild, MsUbr, UbrGap, LastHotfix,
CONVERT(VARCHAR(10), LastHotfixOn, 120) AS LastHotfixOn,
SppSignature,
BiosVersion, CONVERT(VARCHAR(10), BiosDate, 120) AS BiosDate,
ServerModel, Manufacturer,
Reachable, MsDrift, BuildDrift, SppDrift, ErrorMessage,
CONVERT(VARCHAR(16), RunDate, 120) AS RunDate
FROM dbo.ClusterComplianceResults
WHERE RunId = (SELECT TOP 1 RunId FROM dbo.ClusterComplianceResults ORDER BY RunDate DESC)
AND (
Reachable = 0
OR ClusterName IN (
SELECT ClusterName
FROM dbo.ClusterComplianceResults
WHERE RunId = (SELECT TOP 1 RunId FROM dbo.ClusterComplianceResults ORDER BY RunDate DESC)
AND Reachable = 1
GROUP BY ClusterName
HAVING COUNT(DISTINCT NodeName) > 1
)
)
ORDER BY ClusterName, NodeName";
$rs = odbc_exec($conn, $sql);
$runId = "";
$runDate = "-";
$clusters = array();
if ($rs) {
while ($row = odbc_fetch_array($rs)) {
if ($runId === "") { $runId = $row['RunId']; $runDate = $row['RunDate']; }
$c = $row['ClusterName'];
if (!isset($clusters[$c])) {
$clusters[$c] = array(
'name'=>$c, 'nodes'=>array(),
'build'=>false, 'ms'=>false, 'spp'=>false,
'unreach'=>false, 'ubrgap'=>0
);
}
$clusters[$c]['nodes'][] = $row;
// Conformity fail = build drift. MS (Build.UBR) kept as info only.
if ($row['BuildDrift'] == 1) { $clusters[$c]['build'] = true; }
if ($row['MsDrift'] == 1) { $clusters[$c]['ms'] = true; }
if ($row['SppDrift'] == 1) { $clusters[$c]['spp'] = true; }
if ($row['Reachable'] == 0) { $clusters[$c]['unreach'] = true; }
// UbrGap is stored identically on every node row of the run.
$clusters[$c]['ubrgap'] = max($clusters[$c]['ubrgap'], (int)$row['UbrGap']);
}
}
// Classify each cluster: DRIFT > UNREACHABLE > OK
// DRIFT is now build-drift OR SPP-drift (NOT a pure UBR/KB gap).
$ok = array(); $drift = array(); $unreach = array(); $ubrgap_list = array();
foreach ($clusters as $name => $cl) {
if ($cl['build'] || $cl['spp']) {
$clusters[$name]['status'] = 'DRIFT';
if (count($cl['nodes']) > 1) {
$drift[] = $name;
}
} elseif ($cl['unreach']) {
$clusters[$name]['status'] = 'UNREACHABLE'; $unreach[] = $name;
} else {
if (count($cl['nodes']) > 1){
// Build + SPP aligned and fully reachable. A non-zero
// UBR gap means "conform at cluster level, but KB behind"
// -> isolated in its own priority list, out of the OK set.
if ((int)$clusters[$name]['ubrgap'] > 0) {
$clusters[$name]['status'] = 'UBR_GAP';
$ubrgap_list[] = $name;
} else {
$clusters[$name]['status'] = 'OK';
$ok[] = $name;
}
} else {
$clusters[$name]['status'] = 'OK';
}
}
}
$count_ok = count($ok);
$count_drift = count($drift);
$count_unreach = count($unreach);
$count_ubrgap = count($ubrgap_list);
// Biggest gaps first, so the priority list is correct regardless
// of any client-side sorting applied afterwards.
usort($ubrgap_list, function($a, $b) use ($clusters) {
return (int)$clusters[$b]['ubrgap'] <=> (int)$clusters[$a]['ubrgap'];
});
// Per-cluster summary (distinct MS levels, builds, SPP signatures,
// hardware facts, unreachable nodes, errors).
function cluster_summary($cl) {
$ms = array(); $builds = array(); $spp = array();
$models = array(); $bios = array(); $biosdates = array();
$unodes = array(); $errs = array();
foreach ($cl['nodes'] as $n) {
if ($n['Reachable'] == 1) {
if ($n['MsPatchLevel'] !== '' && $n['MsPatchLevel'] !== null) { $ms[] = $n['MsPatchLevel']; }
if ($n['MsBuild'] !== '' && $n['MsBuild'] !== null) { $builds[] = $n['MsBuild']; }
if ($n['SppSignature'] !== '' && $n['SppSignature'] !== null) { $spp[] = $n['SppSignature']; }
if ($n['ServerModel'] !== '' && $n['ServerModel'] !== null) { $models[] = $n['ServerModel']; }
if ($n['BiosVersion'] !== '' && $n['BiosVersion'] !== null) { $bios[] = $n['BiosVersion']; }
if ($n['BiosDate'] !== '' && $n['BiosDate'] !== null) { $biosdates[] = $n['BiosDate']; }
} else {
$unodes[] = $n['NodeName'];
if (!empty($n['ErrorMessage'])) { $errs[] = $n['ErrorMessage']; }
}
}
$bios_u = array_values(array_unique($bios));
return array(
'ms' => array_values(array_unique($ms)),
'builds' => array_values(array_unique($builds)),
'spp' => array_values(array_unique($spp)),
'models' => array_values(array_unique($models)),
'bios' => $bios_u,
'biosdates' => array_values(array_unique($biosdates)),
'bios_drift' => (count($bios_u) > 1),
'ubrgap' => (int)$cl['ubrgap'],
'unodes' => $unodes,
'errs' => array_values(array_unique($errs)),
'nodecount' => count($cl['nodes'])
);
}
// Small helper: a coloured badge for the UBR gap value.
function ubrgap_badge($gap) {
$gap = (int)$gap;
if ($gap <= 0) { return "<span class='badge text-bg-light text-muted'>0</span>"; }
$cls = ($gap >= 100) ? 'text-bg-danger' : 'text-bg-info';
return "<span class='badge $cls'>".$gap."</span>";
}
?>
<div class="sticky-dashboard-header bg-light">
<h1>
<span class="badge text-bg-secondary" style="width:100%;">
Conformité des Clusters &mdash; Build MS / SPP HP / Matériel
<small><small>(<?php echo htmlspecialchars($runDate); ?>)</small></small>
</span>
</h1>
<!-- ================= KPI cards ================= -->
<div class="row my-3">
<div class="col-md-3">
<a href="#sec_ok" class="text-decoration-none">
<div class="card text-bg-success shadow-sm kpi-card">
<div class="card-body py-2 d-flex justify-content-between align-items-center">
<div><h6 class="card-title mb-0"><i class="bi bi-check-circle me-1"></i>Clusters OK</h6></div>
<h2 class="fw-bold mb-0"><?php echo $count_ok; ?></h2>
</div>
</div>
</a>
</div>
<div class="col-md-3">
<a href="#sec_drift" class="text-decoration-none">
<div class="card text-bg-warning shadow-sm kpi-card">
<div class="card-body py-2 d-flex justify-content-between align-items-center">
<div><h6 class="card-title mb-0"><i class="bi bi-exclamation-triangle me-1"></i>Clusters en drift</h6></div>
<h2 class="fw-bold mb-0"><?php echo $count_drift; ?></h2>
</div>
</div>
</a>
</div>
<div class="col-md-3">
<a href="#sec_ubrgap" class="text-decoration-none">
<div class="card text-bg-info shadow-sm kpi-card">
<div class="card-body py-2 d-flex justify-content-between align-items-center">
<div><h6 class="card-title mb-0"><i class="bi bi-arrow-left-right me-1"></i>&Eacute;cart UBR (m&ecirc;me build)</h6></div>
<h2 class="fw-bold mb-0"><?php echo $count_ubrgap; ?></h2>
</div>
</div>
</a>
</div>
<div class="col-md-3">
<a href="#sec_unreach" class="text-decoration-none">
<div class="card text-bg-danger shadow-sm kpi-card">
<div class="card-body py-2 d-flex justify-content-between align-items-center">
<div><h6 class="card-title mb-0"><i class="bi bi-plug me-1"></i>Clusters injoignables</h6></div>
<h2 class="fw-bold mb-0"><?php echo $count_unreach; ?></h2>
</div>
</div>
</a>
</div>
</div>
</div>
<!-- ================= DRIFT table ================= -->
<h4 id="sec_drift" class="mt-4"><i class="bi bi-exclamation-triangle text-warning me-1"></i>Clusters en drift</h4>
<table class="table table-bordered table-hover table-sm" id="t_drift"
data-toggle="table" data-search="true" data-show-columns="true"
data-export-types="['xlsx','csv','json']" data-show-export="true"
data-sortable="true" style="width:99%;">
<thead>
<tr>
<th data-field="cluster" data-sortable="true">Cluster</th>
<th data-field="ecart" data-sortable="false">&Eacute;cart</th>
<th data-field="noeuds" data-sortable="true">N&oelig;uds</th>
<th data-field="build" data-sortable="false">Build(s)</th>
<th data-field="ubrgap" data-sortable="true">Gap UBR</th>
<th data-field="ms" data-sortable="false">Niveau(x) MS</th>
<th data-field="modele" data-sortable="false">Mod&egrave;le(s)</th>
<th data-field="bios" data-sortable="false">BIOS</th>
<th data-field="biosdate" data-sortable="false">Date BIOS</th>
<th data-field="spp" data-sortable="false">Signature(s) SPP</th>
<th data-field="detail" data-sortable="false">D&eacute;tail</th>
</tr>
</thead>
<tbody>
<?php foreach ($drift as $name): $cl = $clusters[$name]; $s = cluster_summary($cl); ?>
<tr>
<td><?php echo htmlspecialchars($name); ?></td>
<td>
<?php if ($cl['build']) { echo "<span class='badge text-bg-danger me-1'>BUILD</span>"; } ?>
<?php if ($cl['spp']) { echo "<span class='badge text-bg-warning text-dark me-1'>SPP</span>"; } ?>
<?php if ($s['bios_drift']) { echo "<span class='badge text-bg-secondary'>BIOS</span>"; } ?>
</td>
<td><?php echo $s['nodecount']; ?></td>
<td><?php echo htmlspecialchars(implode(', ', $s['builds'])); ?></td>
<td><?php echo ubrgap_badge($s['ubrgap']); ?></td>
<td><?php echo htmlspecialchars(implode(', ', $s['ms'])); ?></td>
<td><?php echo htmlspecialchars(implode(' / ', $s['models'])); ?></td>
<td><?php echo htmlspecialchars(implode(' / ', $s['bios'])); ?></td>
<td><?php echo htmlspecialchars(implode(' / ', $s['biosdates'])); ?></td>
<td><code class="sig"><?php echo htmlspecialchars(implode(' / ', $s['spp'])); ?></code></td>
<td>
<a class="btn btn-sm btn-outline-primary"
href="cluster_compliance_detail.php?cluster=<?php echo urlencode($name); ?>&run=<?php echo urlencode($runId); ?>">
<i class="bi bi-search"></i> D&eacute;tail
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- ================= UBR GAP table (same build, KB behind) ================= -->
<h4 id="sec_ubrgap" class="mt-4"><i class="bi bi-arrow-left-right text-info me-1"></i>&Eacute;cart UBR &mdash; m&ecirc;me build, KB &agrave; rattraper</h4>
<p class="text-muted small mb-2">
Ces clusters sont conformes au niveau cluster (build align&eacute;), mais un ou plusieurs n&oelig;uds
sont en retard de correctif. Tri&eacute;s par &eacute;cart d&eacute;croissant &mdash; les plus gros &agrave; corriger en priorit&eacute;.
</p>
<table class="table table-bordered table-hover table-sm" id="t_ubrgap"
data-toggle="table" data-search="true" data-show-columns="true"
data-export-types="['xlsx','csv','json']" data-show-export="true"
data-sortable="true" style="width:99%;">
<thead>
<tr>
<th data-field="cluster" data-sortable="true">Cluster</th>
<th data-field="ubrgap" data-sortable="true">Gap UBR</th>
<th data-field="noeuds" data-sortable="true">N&oelig;uds</th>
<th data-field="build" data-sortable="true">Build</th>
<th data-field="ms" data-sortable="false">Niveau(x) MS</th>
<th data-field="modele" data-sortable="false">Mod&egrave;le</th>
<th data-field="bios" data-sortable="false">BIOS</th>
<th data-field="detail" data-sortable="false">D&eacute;tail</th>
</tr>
</thead>
<tbody>
<?php foreach ($ubrgap_list as $name): $cl = $clusters[$name]; $s = cluster_summary($cl); ?>
<tr>
<td>
<?php echo htmlspecialchars($name); ?>
<?php if ($s['bios_drift']) { echo " <span class='badge text-bg-secondary'>BIOS</span>"; } ?>
</td>
<td><?php echo ubrgap_badge($s['ubrgap']); ?></td>
<td><?php echo $s['nodecount']; ?></td>
<td><?php echo htmlspecialchars(implode(', ', $s['builds'])); ?></td>
<td><?php echo htmlspecialchars(implode(', ', $s['ms'])); ?></td>
<td><?php echo htmlspecialchars(implode(' / ', $s['models'])); ?></td>
<td><?php echo htmlspecialchars(implode(' / ', $s['bios'])); ?></td>
<td>
<a class="btn btn-sm btn-outline-primary"
href="cluster_compliance_detail.php?cluster=<?php echo urlencode($name); ?>&run=<?php echo urlencode($runId); ?>">
<i class="bi bi-search"></i> D&eacute;tail
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- ================= OK table ================= -->
<h4 id="sec_ok" class="mt-4"><i class="bi bi-check-circle text-success me-1"></i>Clusters OK</h4>
<table class="table table-bordered table-hover table-sm" id="t_ok"
data-toggle="table" data-search="true" data-show-columns="true"
data-export-types="['xlsx','csv','json']" data-show-export="true"
data-sortable="true" style="width:99%;">
<thead>
<tr>
<th data-field="cluster" data-sortable="true">Cluster</th>
<th data-field="noeuds" data-sortable="true">N&oelig;uds</th>
<th data-field="build" data-sortable="true">Build</th>
<th data-field="ubrgap" data-sortable="true">Gap UBR</th>
<th data-field="ms" data-sortable="true">Niveau(x) MS</th>
<th data-field="modele" data-sortable="false">Mod&egrave;le</th>
<th data-field="bios" data-sortable="false">BIOS</th>
<th data-field="spp" data-sortable="false">Signature SPP</th>
</tr>
</thead>
<tbody>
<?php foreach ($ok as $name): $cl = $clusters[$name]; $s = cluster_summary($cl); ?>
<tr>
<td>
<?php echo htmlspecialchars($name); ?>
<?php if ($s['bios_drift']) { echo " <span class='badge text-bg-secondary'>BIOS</span>"; } ?>
</td>
<td><?php echo $s['nodecount']; ?></td>
<td><?php echo htmlspecialchars(implode(', ', $s['builds'])); ?></td>
<td><?php echo ubrgap_badge($s['ubrgap']); ?></td>
<td><?php echo htmlspecialchars(implode(', ', $s['ms'])); ?></td>
<td><?php echo htmlspecialchars(implode(' / ', $s['models'])); ?></td>
<td><?php echo htmlspecialchars(implode(' / ', $s['bios'])); ?></td>
<td><code class="sig"><?php echo htmlspecialchars(implode(', ', $s['spp'])); ?></code></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- ================= UNREACHABLE (collapsed list, at the end) ================= -->
<h4 id="sec_unreach" class="mt-4 mb-2">
<a class="collapse-toggle text-decoration-none text-reset" data-bs-toggle="collapse"
href="#collapseUnreach" role="button" aria-expanded="false" aria-controls="collapseUnreach">
<i class="bi bi-plug text-danger me-1"></i>Clusters injoignables
<span class="badge text-bg-danger align-middle"><?php echo $count_unreach; ?></span>
<i class="bi bi-chevron-down small ms-1"></i>
</a>
</h4>
<div class="collapse mb-4" id="collapseUnreach">
<?php if ($count_unreach == 0): ?>
<p class="text-muted small mb-0">Aucun cluster injoignable sur ce run.</p>
<?php else: ?>
<ul class="list-group">
<?php foreach ($unreach as $name): $cl = $clusters[$name]; $s = cluster_summary($cl); ?>
<li class="list-group-item">
<i class="bi bi-plug text-danger me-1"></i><strong><?php echo htmlspecialchars($name); ?></strong>
<?php if (!empty($s['unodes'])): ?>
<span class="text-muted">&mdash; <?php echo htmlspecialchars(implode(', ', $s['unodes'])); ?></span>
<?php endif; ?>
<?php if (!empty($s['errs'])): ?>
<div class="small text-muted mt-1"><?php echo htmlspecialchars(implode(' | ', $s['errs'])); ?></div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</div>
</div>
</div>
<script src="/js/switch.js"></script>
<script>
// Clicking the "Clusters injoignables" KPI card scrolls to the section
// and expands the collapsed list.
document.addEventListener('DOMContentLoaded', function () {
var card = document.querySelector('a[href="#sec_unreach"]');
var box = document.getElementById('collapseUnreach');
if (card && box) {
card.addEventListener('click', function () {
bootstrap.Collapse.getOrCreateInstance(box, { toggle: false }).show();
});
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,221 @@
<!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 OT</title>
<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">
<link rel="stylesheet" href="/css/preloader.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>
code.sig { font-size: .85em; }
.table td { vertical-align: middle; }
.pivot th, .pivot td { text-align: center; }
.pivot td.comp { text-align: left; font-weight: 500; }
</style>
</head>
<body>
<?php include $_SERVER['DOCUMENT_ROOT']."/include/all.php" ; ?>
<div class="container-fluid">
<div class="row flex-nowrap">
<div class="col-auto col-md-2 col-xl-2 px-sm-2 px-0 bg-dark" style="-ms-flex: 0 0 230px;flex: 0 0 230px;">
<?php include $_SERVER['DOCUMENT_ROOT']."/navbar.html" ; ?>
</div>
<div class="col py-3">
<?php
// -----------------------------------------------------------
// Helpers
// -----------------------------------------------------------
function majority($arr) {
if (count($arr) == 0) { return null; }
$c = array_count_values($arr);
arsort($c);
return key($c);
}
$cluster = isset($_GET['cluster']) ? $_GET['cluster'] : '';
$run = isset($_GET['run']) ? $_GET['run'] : '';
$conn = DB_INFRA();
$nodes = array();
$delta = array();
$runDate = '-';
if ($cluster !== '' && $run !== '') {
// --- Node facts (parameterized to avoid injection) -------
$sqlNodes = "SELECT NodeName, OsName, MsPatchLevel, LastHotfix,
CONVERT(VARCHAR(10), LastHotfixOn, 120) AS LastHotfixOn,
SppSignature, Reachable, MsDrift, SppDrift, ErrorMessage,
CONVERT(VARCHAR(16), RunDate, 120) AS RunDate
FROM dbo.ClusterComplianceResults
WHERE ClusterName = ? AND RunId = ?
ORDER BY NodeName";
$stmt = odbc_prepare($conn, $sqlNodes);
if ($stmt && odbc_execute($stmt, array($cluster, $run))) {
while ($r = odbc_fetch_array($stmt)) {
if ($runDate === '-' && !empty($r['RunDate'])) { $runDate = $r['RunDate']; }
$nodes[] = $r;
}
}
// --- SPP component delta ---------------------------------
$sqlDelta = "SELECT ComponentName, NodeName, ComponentVersion
FROM dbo.ClusterComplianceSppDelta
WHERE ClusterName = ? AND RunId = ?
ORDER BY ComponentName, NodeName";
$stmt2 = odbc_prepare($conn, $sqlDelta);
if ($stmt2 && odbc_execute($stmt2, array($cluster, $run))) {
while ($r = odbc_fetch_array($stmt2)) { $delta[] = $r; }
}
}
// Build the delta pivot : component x node
$deltaNodes = array();
$comps = array();
$matrix = array();
foreach ($delta as $d) {
$cp = $d['ComponentName'];
$nd = $d['NodeName'];
if (!in_array($nd, $deltaNodes)) { $deltaNodes[] = $nd; }
$comps[$cp] = true;
$matrix[$cp][$nd] = $d['ComponentVersion'];
}
sort($deltaNodes);
$comps = array_keys($comps);
sort($comps);
// MS majority for highlighting
$msVals = array();
foreach ($nodes as $n) {
if ($n['Reachable'] == 1 && $n['MsPatchLevel'] !== '' && $n['MsPatchLevel'] !== null) {
$msVals[] = $n['MsPatchLevel'];
}
}
$msMajority = majority($msVals);
?>
<h1>
<span class="badge text-bg-secondary" style="width:100%;">
D&eacute;tail cluster : <?php echo htmlspecialchars($cluster); ?>
<small><small>(<?php echo htmlspecialchars($runDate); ?>)</small></small>
</span>
</h1>
<a href="cluster_compliance.php" class="btn btn-sm btn-outline-secondary mb-3">
<i class="bi bi-arrow-left"></i> Retour au tableau de bord
</a>
<?php if (count($nodes) == 0): ?>
<div class="alert alert-warning">Aucune donn&eacute;e pour ce cluster / ce run.</div>
<?php else: ?>
<!-- ============ Node facts ============ -->
<h4 class="mt-3">&Eacute;tat des n&oelig;uds</h4>
<table class="table table-bordered table-sm">
<thead class="table-light">
<tr>
<th>N&oelig;ud</th>
<th>OS</th>
<th>Niveau MS</th>
<th>Signature SPP</th>
<th>&Eacute;tat</th>
<th>Erreur</th>
</tr>
</thead>
<tbody>
<?php foreach ($nodes as $n): ?>
<?php
$msCls = '';
if ($n['Reachable'] == 1 && $msMajority !== null && $n['MsPatchLevel'] !== $msMajority) {
$msCls = 'table-warning';
}
?>
<tr>
<td><?php echo htmlspecialchars($n['NodeName']); ?></td>
<td><?php echo htmlspecialchars($n['OsName']); ?></td>
<td class="<?php echo $msCls; ?>"><?php echo htmlspecialchars($n['MsPatchLevel']); ?></td>
<td><code class="sig"><?php echo htmlspecialchars($n['SppSignature']); ?></code></td>
<td>
<?php if ($n['Reachable'] == 1): ?>
<span class="badge text-bg-success">Joignable</span>
<?php else: ?>
<span class="badge text-bg-danger">Injoignable</span>
<?php endif; ?>
</td>
<td><small class="text-muted"><?php echo htmlspecialchars($n['ErrorMessage']); ?></small></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- ============ SPP delta pivot ============ -->
<h4 class="mt-4">Delta SPP HP (composants divergents)</h4>
<?php if (count($comps) == 0): ?>
<div class="alert alert-info mb-0">
Aucun &eacute;cart de composant HP relev&eacute; (drift Microsoft uniquement, ou signatures identiques).
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-bordered table-sm pivot">
<thead class="table-light">
<tr>
<th class="comp">Composant</th>
<?php foreach ($deltaNodes as $nd): ?>
<th><?php echo htmlspecialchars($nd); ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($comps as $cp): ?>
<?php
$rowVals = array();
foreach ($deltaNodes as $nd) {
$rowVals[] = isset($matrix[$cp][$nd]) ? $matrix[$cp][$nd] : '<absent>';
}
$maj = majority($rowVals);
?>
<tr>
<td class="comp"><?php echo htmlspecialchars($cp); ?></td>
<?php foreach ($deltaNodes as $nd): ?>
<?php
$v = isset($matrix[$cp][$nd]) ? $matrix[$cp][$nd] : '<absent>';
$cls = '';
if ($v === '<absent>') { $cls = 'table-danger'; }
elseif ($v !== $maj) { $cls = 'table-warning'; }
?>
<td class="<?php echo $cls; ?>"><?php echo htmlspecialchars($v); ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="small text-muted mt-1">
<span class="badge text-bg-warning text-dark">jaune</span> version diff&eacute;rente de la majorit&eacute; &nbsp;
<span class="badge text-bg-danger">rouge</span> composant absent du n&oelig;ud
</div>
<?php endif; ?>
<?php endif; /* nodes exist */ ?>
</div>
</div>
</div>
<script src="/js/switch.js"></script>
</body>
</html>

941
reports/Netbackup_Daily.php Normal file
View 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. &gt; 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 &gt; 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. &gt; 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>

View File

@@ -6,170 +6,391 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Page Title -->
<title>Infra Reports IT</title> <title>Infra Reports IT</title>
<link rel="shortcut icon" type="image/png" href="/include/favicon-32x32.png"> <link rel="shortcut icon" type="image/png" href="/include/favicon-32x32.png">
<!-- JQuery -->
<script src="/js/jquery-3.6.1.min.js"></script>
<!-- Bootstrap --> <!-- Bootstrap -->
<link rel="stylesheet" href="/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/bootstrap-icons/bootstrap-icons.css"> <link rel="stylesheet" href="/css/bootstrap-icons/bootstrap-icons.css">
<link rel="stylesheet" href="/css/preloader.css"> <link rel="stylesheet" href="/css/preloader.css">
<script src="/js/bootstrap.bundle.min.js"></script>
<!-- Bootstrap-tables -->
<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>
<script src="/js/bootstrap.bundle.min.js" defer></script>
<script src="/js/jquery-3.6.1.min.js" defer></script>
</head> </head>
<body class="bg-light text-dark"> <body class="bg-light text-dark">
<?php include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php"; ?>
<?php // DATA <?php
$windows = Invoke_Infra("select * from cmdb_srvall where DECOM is null and hostname not in (select server from maintenance_status where scom = 'Y' or zabbix = 'Y') and hostname <> '' order by hostname asc");
$linux = Invoke_aixcmdb("select h.hostname, h.hbtime, h.prevtime from heartbeat h left join srvall s on s.hostname = h.hostname where h.mainttime is null and h.decomtime is null and s.os_type = 'LINUX' order by h.hostname"); include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php";
$aix = Invoke_aixcmdb("select h.hostname, h.hbtime, h.prevtime from heartbeat h left join srvall s on s.hostname = h.hostname where h.mainttime is null and h.decomtime is null and s.os_type = 'AIX' order by h.hostname");
$other = Invoke_aixcmdb("select h.hostname, h.hbtime, h.prevtime from heartbeat h left join srvall s on s.hostname = h.hostname where h.mainttime is null and h.decomtime is null and s.os_type is null order by h.hostname"); /**
$lok = "<div class='row'>";$lko = "<div class='row'>";$lcount=0; * Récupère une valeur dans un tableau sans dépendre de la casse
foreach($linux as $s){ * utilisée par le pilote ODBC.
if($s['HBTIME'] < date('Y-m-d H:i:s', strtotime(' -11 minutes '))){ */
$lko .= "<div class='col-6'><span class='badge bg-danger'>".strtoupper($s['HOSTNAME'])." since ".explode(".",$s['PREVTIME'])[0]."</span></div>"; function getRowValue(array $row, string $key, $default = null)
$lcount++; {
}else{ if (array_key_exists($key, $row)) {
$lok .= "<div class='col-2'><span class='badge bg-success'>".strtoupper($s['HOSTNAME']." ")."</span></div>"; return $row[$key];
} }
}
if($lcount){$color = "DarkOrange";$msg = $lcount." issues";}else{$color = "green";$msg = "OK";} $upperKey = strtoupper($key);
$aok = "<div class='row'>";$ako = "<div class='row'>";$acount=0;
foreach($aix as $s){ if (array_key_exists($upperKey, $row)) {
if($s['HBTIME'] < date('Y-m-d H:i:s', strtotime(' -11 minutes '))){ return $row[$upperKey];
$ako .= "<div class='col-6'><span class='badge bg-danger'>".strtoupper($s['HOSTNAME'])." since".explode(".",$s['PREVTIME'])[0]."</span></div>"; }
$acount++;
}else{ $lowerKey = strtolower($key);
$aok .= "<div class='col-2'><span class='badge bg-success'>".strtoupper($s['HOSTNAME']." ")."</span></div>";
} if (array_key_exists($lowerKey, $row)) {
} return $row[$lowerKey];
$ook = "<div class='row'>";$oko = "<div class='row'>";$ocount=0; }
foreach($other as $s){
if($s['HBTIME'] < date('Y-m-d H:i:s', strtotime(' -11 minutes '))){ return $default;
$oko .= "<div class='col-6'><span class='badge bg-danger'>".strtoupper($s['HOSTNAME'])." since".explode(".",$s['PREVTIME'])[0]."</span></div>"; }
$ocount++;
}else{ /**
$ook .= "<div class='col-2'><span class='badge bg-success'>".strtoupper($s['HOSTNAME']." ")."</span></div>"; * Génère les listes de serveurs OK et en erreur.
} */
} function buildHeartbeatStatus(
$wok = "<div class='row'>";$wko = "<div class='row'>";$wcount=0; array $servers,
foreach($windows as $s){ string $hostnameKey,
if($s['heartbeat'] < date('Y-m-d H:i:s', strtotime(' -16 minutes '))){ string $heartbeatKey,
$wko .= "<div class='col-6'><span class='badge bg-danger'>".strtoupper($s['hostname'])." since ".explode(".",$s['heartbeat'])[0]."</span></div>"; string $previousHeartbeatKey,
$wcount++; string $limit
}else{ ): array {
$wok .= "<div class='col-2'><span class='badge bg-success'>".strtoupper($s['hostname']." ")."</span></div>"; $okItems = [];
$koItems = [];
$issueCount = 0;
foreach ($servers as $server) {
$hostname = trim((string)getRowValue($server, $hostnameKey, ''));
if ($hostname === '') {
continue;
}
$heartbeat = getRowValue($server, $heartbeatKey);
$previousHeartbeat = getRowValue(
$server,
$previousHeartbeatKey,
$heartbeat
);
$hostnameDisplay = htmlspecialchars(
strtoupper($hostname),
ENT_QUOTES,
'UTF-8'
);
/*
* Une date absente est considérée comme une erreur.
* Les dates au format Y-m-d H:i:s peuvent être comparées
* directement sous forme de chaînes.
*/
$isKo = empty($heartbeat) || $heartbeat < $limit;
if ($isKo) {
$previousDisplay = 'unknown';
if (!empty($previousHeartbeat)) {
$previousDisplay = explode('.', (string)$previousHeartbeat)[0];
$previousDisplay = htmlspecialchars(
$previousDisplay,
ENT_QUOTES,
'UTF-8'
);
}
$koItems[] = "
<div class=\"col-12 col-md-6 mb-1\">
<span class=\"badge bg-danger text-wrap\">
{$hostnameDisplay} since {$previousDisplay}
</span>
</div>
";
$issueCount++;
} else {
$okItems[] = "
<div class=\"col-6 col-md-4 col-xl-2 mb-1\">
<span class=\"badge bg-success text-wrap\">
{$hostnameDisplay}
</span>
</div>
";
}
}
return [
'ok' => implode('', $okItems),
'ko' => implode('', $koItems),
'issues' => $issueCount
];
}
/**
* Affiche une section de l'accordéon.
*/
function displayHeartbeatSection(
string $id,
string $title,
array $servers,
array $status,
string $deviceLabel
): void {
$count = count($servers);
$issues = $status['issues'];
$backgroundColor = $issues > 0 ? 'DarkOrange' : 'green';
$message = $issues > 0
? "<strong> → {$issues} issue(s)</strong>"
: 'OK';
?>
<div class="card mb-3" style="background-color: <?= $backgroundColor ?>;">
<h4 class="card-header">
<button
class="btn text-white fs-3 text-start w-100"
type="button"
data-bs-toggle="collapse"
data-bs-target="#<?= htmlspecialchars($id, ENT_QUOTES, 'UTF-8') ?>"
aria-expanded="false"
aria-controls="<?= htmlspecialchars($id, ENT_QUOTES, 'UTF-8') ?>"
>
<strong>
<?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?> :
<?= $count ?>
<?= htmlspecialchars($deviceLabel, ENT_QUOTES, 'UTF-8') ?>
<?= $message ?>
</strong>
</button>
</h4>
</div>
<div
id="<?= htmlspecialchars($id, ENT_QUOTES, 'UTF-8') ?>"
class="accordion-collapse collapse"
data-bs-parent="#accordion"
>
<div class="card card-body mb-3 bg-light">
<?php if ($issues > 0): ?>
<h5 class="mb-3">Issues</h5>
<div class="row">
<?= $status['ko'] ?>
</div>
<?php endif; ?>
<?php if ($status['ok'] !== ''): ?>
<?php if ($issues > 0): ?>
<hr>
<?php endif; ?>
<h5 class="mb-3">OK</h5>
<div class="row">
<?= $status['ok'] ?>
</div>
<?php endif; ?>
</div>
</div>
<?php
}
/*
* Seuils de heartbeat.
*/
$unixHeartbeatLimit = date(
'Y-m-d H:i:s',
time() - (11 * 60)
);
$windowsHeartbeatLimit = date(
'Y-m-d H:i:s',
time() - (16 * 60)
);
/*
* Serveurs Windows.
*/
$windows = Invoke_Infra("
SELECT
c.hostname,
c.heartbeat
FROM cmdb_srvall c
WHERE c.decom IS NULL
AND c.hostname <> ''
AND NOT EXISTS (
SELECT 1
FROM maintenance_status m
WHERE m.server = c.hostname
AND (
m.scom = 'Y'
OR m.zabbix = 'Y'
)
)
ORDER BY c.hostname ASC
");
/*
* Linux, AIX et autres :
* une seule requête au lieu de trois.
*/
$unixServers = Invoke_aixcmdb("
SELECT
h.hostname,
h.hbtime,
h.prevtime,
s.os_type
FROM heartbeat h
LEFT JOIN srvall s
ON s.hostname = h.hostname
WHERE h.mainttime IS NULL
AND h.decomtime IS NULL
AND (
s.os_type IN ('LINUX', 'AIX')
OR s.os_type IS NULL
)
ORDER BY h.hostname ASC
");
/*
* Répartition des résultats Unix.
*/
$linux = [];
$aix = [];
$other = [];
foreach ($unixServers as $server) {
$osType = strtoupper(
trim((string)getRowValue($server, 'os_type', ''))
);
switch ($osType) {
case 'LINUX':
$linux[] = $server;
break;
case 'AIX':
$aix[] = $server;
break;
default:
$other[] = $server;
break;
} }
} }
/*
* Génération des états.
*/
$windowsStatus = buildHeartbeatStatus(
$windows,
'hostname',
'heartbeat',
'heartbeat',
$windowsHeartbeatLimit
);
$linuxStatus = buildHeartbeatStatus(
$linux,
'hostname',
'hbtime',
'prevtime',
$unixHeartbeatLimit
);
$aixStatus = buildHeartbeatStatus(
$aix,
'hostname',
'hbtime',
'prevtime',
$unixHeartbeatLimit
);
$otherStatus = buildHeartbeatStatus(
$other,
'hostname',
'hbtime',
'prevtime',
$unixHeartbeatLimit
);
?> ?>
<!-- HTML -->
<div class="container-fluid" id="content"> <div class="container-fluid" id="content">
<div class="row flex-nowrap"> <div class="row flex-nowrap">
<!-- Left NAVBAR -->
<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;"> <!-- Left navbar -->
<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"; ?> <?php include $_SERVER['DOCUMENT_ROOT'] . "/navbar.html"; ?>
</div> </div>
<!-- Display --> <!-- Display -->
<div class="col py-3"> <main class="col py-3">
<!-- Page Title -->
<h1><span class="badge text-bg-secondary font-weight-bold" style="width:100%;"><?php echo $ti_17;?></span></h1> <!-- Page title -->
<h1>
<span
class="badge text-bg-secondary fw-bold"
style="width: 100%;"
>
<?= htmlspecialchars((string)$ti_17, ENT_QUOTES, 'UTF-8') ?>
</span>
</h1>
<!-- Main content --> <!-- Main content -->
<div class="container"> <div class="container">
<!-- TABLE --> <div class="accordion" id="accordion">
<div>
<div id="accordion">
<?php if($wcount){$color = "DarkOrange";$msg = "<b> --> ".$wcount." issue(s)</b>";}else{$color = "green";$msg = "OK";} ?>
<div class="card" style='background-color:<?php echo $color ?>;'>
<h4 class="card-header">
<a class="btn text-white fs-3" data-bs-toggle="collapse" href="#windows"><b>Windows : <?php echo count($windows)." $w_devices ". $msg; ?></b></a>
</h4>
</div>
<div id="windows" class="collapse" data-bs-parent="#accordion">
<?php echo "<br><h5>".$wko.'</div><br><br>'.$wok."</div></h5>"; ?>
</div>
<br>
<?php if($lcount){$color = "DarkOrange";$msg = "<b> --> ".$lcount." issue(s)</b>";}else{$color = "green";$msg = "OK";} ?> <?php
<div class="card" style='background-color:<?php echo $color ?>;'> displayHeartbeatSection(
<h4 class="card-header"> 'windows',
<a class="btn text-white fs-3" data-bs-toggle="collapse" href="#linux"><b>Linux : <?php echo count($linux)." $w_devices ". $msg; ?></b></a> 'Windows',
</h4> $windows,
</div> $windowsStatus,
<div id="linux" class="collapse" data-bs-parent="#accordion"> $w_devices
<?php echo "<br><h5>".$lko.'</div><br><br>'.$lok."</div></h5>"; ?> );
</div>
<br>
<?php if($acount){$color = "DarkOrange";$msg = "<b> --> ".$acount." issue(s)</b>";}else{$color = "green";$msg = "OK";} ?> displayHeartbeatSection(
<div class="card" style='background-color:<?php echo $color ?>;'> 'linux',
<h4 class="card-header text-white"> 'Linux',
<a class="btn text-white fs-3" data-bs-toggle="collapse" href="#aix"><b>AIX : <?php echo count($aix)." $w_devices ". $msg; ?></b></a> $linux,
</h4> $linuxStatus,
</div> $w_devices
<div id="aix" class="collapse" data-bs-parent="#accordion"> );
<?php echo "<br><h5>".$ako.'</div><br><br>'.$aok."</div></h5>"; ?>
</div>
<br>
<?php if($ocount){$color = "DarkOrange";$msg = "<b> --> ".$ocount." issue(s)</b>";}else{$color = "green";$msg = "OK";} ?> displayHeartbeatSection(
<div class="card" style='background-color:<?php echo $color ?>;'> 'aix',
<h4 class="card-header text-white"> 'AIX',
<a class="btn text-white fs-3" data-bs-toggle="collapse" href="#other"><b>Other : <?php echo count($other)." $w_devices ". $msg; ?></b></a> $aix,
</h4> $aixStatus,
</div> $w_devices
<div id="other" class="collapse" data-bs-parent="#accordion"> );
<?php echo "<br><h5>".$oko.'</div><br><br>'.$ook."</div></h5>"; ?>
</div>
<br>
displayHeartbeatSection(
'other',
'Other',
$other,
$otherStatus,
$w_devices
);
?>
</div>
</div> </div>
<!-- End of main content -->
</div> </div>
</div>
</main>
</div> </div>
</body> </div>
<script src="/js/switch.js"></script> <script src="/js/switch.js"></script>
</HTML>
<SCRIPT>
let table = $('#t1');
$(document).ready(function() {
table.DataTable({
scrollY: '50vh',
scrollCollapse: true,
paging: false,
});
});
$(function () {
let options = table.bootstrapTable('getOptions');
options.height= document.getElementById('content').clientHeight-170;
table.bootstrapTable('refreshOptions',options);
});
function tableresize() {
let options = table.bootstrapTable('getOptions');
options.height= document.getElementById('content').clientHeight-170;
table.bootstrapTable('refreshOptions',options);
}
window.addEventListener("resize", tableresize);
</SCRIPT>
</body>
</html>