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

View File

@@ -6,170 +6,391 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Page Title -->
<title>Infra Reports IT</title>
<link rel="shortcut icon" type="image/png" href="/include/favicon-32x32.png">
<!-- JQuery -->
<script src="/js/jquery-3.6.1.min.js"></script>
<!-- Bootstrap -->
<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>
<!-- 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>
<body class="bg-light text-dark">
<?php include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php"; ?>
<?php // DATA
$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");
$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;
foreach($linux as $s){
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>";
$lcount++;
}else{
$lok .= "<div class='col-2'><span class='badge bg-success'>".strtoupper($s['HOSTNAME']." ")."</span></div>";
}
}
if($lcount){$color = "DarkOrange";$msg = $lcount." issues";}else{$color = "green";$msg = "OK";}
$aok = "<div class='row'>";$ako = "<div class='row'>";$acount=0;
foreach($aix as $s){
if($s['HBTIME'] < date('Y-m-d H:i:s', strtotime(' -11 minutes '))){
$ako .= "<div class='col-6'><span class='badge bg-danger'>".strtoupper($s['HOSTNAME'])." since".explode(".",$s['PREVTIME'])[0]."</span></div>";
$acount++;
}else{
$aok .= "<div class='col-2'><span class='badge bg-success'>".strtoupper($s['HOSTNAME']." ")."</span></div>";
}
}
$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 '))){
$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>";
}
}
$wok = "<div class='row'>";$wko = "<div class='row'>";$wcount=0;
foreach($windows as $s){
if($s['heartbeat'] < date('Y-m-d H:i:s', strtotime(' -16 minutes '))){
$wko .= "<div class='col-6'><span class='badge bg-danger'>".strtoupper($s['hostname'])." since ".explode(".",$s['heartbeat'])[0]."</span></div>";
$wcount++;
}else{
$wok .= "<div class='col-2'><span class='badge bg-success'>".strtoupper($s['hostname']." ")."</span></div>";
<?php
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php";
/**
* Récupère une valeur dans un tableau sans dépendre de la casse
* utilisée par le pilote ODBC.
*/
function getRowValue(array $row, string $key, $default = null)
{
if (array_key_exists($key, $row)) {
return $row[$key];
}
$upperKey = strtoupper($key);
if (array_key_exists($upperKey, $row)) {
return $row[$upperKey];
}
$lowerKey = strtolower($key);
if (array_key_exists($lowerKey, $row)) {
return $row[$lowerKey];
}
return $default;
}
/**
* Génère les listes de serveurs OK et en erreur.
*/
function buildHeartbeatStatus(
array $servers,
string $hostnameKey,
string $heartbeatKey,
string $previousHeartbeatKey,
string $limit
): array {
$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="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"; ?>
</div>
<!-- Display -->
<div 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>
<main class="col py-3">
<!-- 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 -->
<div class="container">
<!-- TABLE -->
<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>
<div class="accordion" id="accordion">
<?php if($lcount){$color = "DarkOrange";$msg = "<b> --> ".$lcount." 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="#linux"><b>Linux : <?php echo count($linux)." $w_devices ". $msg; ?></b></a>
</h4>
</div>
<div id="linux" class="collapse" data-bs-parent="#accordion">
<?php echo "<br><h5>".$lko.'</div><br><br>'.$lok."</div></h5>"; ?>
</div>
<br>
<?php
displayHeartbeatSection(
'windows',
'Windows',
$windows,
$windowsStatus,
$w_devices
);
<?php if($acount){$color = "DarkOrange";$msg = "<b> --> ".$acount." issue(s)</b>";}else{$color = "green";$msg = "OK";} ?>
<div class="card" style='background-color:<?php echo $color ?>;'>
<h4 class="card-header text-white">
<a class="btn text-white fs-3" data-bs-toggle="collapse" href="#aix"><b>AIX : <?php echo count($aix)." $w_devices ". $msg; ?></b></a>
</h4>
</div>
<div id="aix" class="collapse" data-bs-parent="#accordion">
<?php echo "<br><h5>".$ako.'</div><br><br>'.$aok."</div></h5>"; ?>
</div>
<br>
displayHeartbeatSection(
'linux',
'Linux',
$linux,
$linuxStatus,
$w_devices
);
<?php if($ocount){$color = "DarkOrange";$msg = "<b> --> ".$ocount." issue(s)</b>";}else{$color = "green";$msg = "OK";} ?>
<div class="card" style='background-color:<?php echo $color ?>;'>
<h4 class="card-header text-white">
<a class="btn text-white fs-3" data-bs-toggle="collapse" href="#other"><b>Other : <?php echo count($other)." $w_devices ". $msg; ?></b></a>
</h4>
</div>
<div id="other" class="collapse" data-bs-parent="#accordion">
<?php echo "<br><h5>".$oko.'</div><br><br>'.$ook."</div></h5>"; ?>
</div>
<br>
displayHeartbeatSection(
'aix',
'AIX',
$aix,
$aixStatus,
$w_devices
);
displayHeartbeatSection(
'other',
'Other',
$other,
$otherStatus,
$w_devices
);
?>
</div>
</div>
<!-- End of main content -->
</div>
</div>
</main>
</div>
</body>
</div>
<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>