- 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.
379 lines
15 KiB
PHP
379 lines
15 KiB
PHP
<!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>
|