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:
1582
AD/LocalAdmins.php
Normal file
1582
AD/LocalAdmins.php
Normal file
File diff suppressed because it is too large
Load Diff
203
Astreinte/Liens.php
Normal file
203
Astreinte/Liens.php
Normal 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
213
Astreinte/p1-p2.php
Normal 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>
|
||||
@@ -49,11 +49,17 @@
|
||||
if(is_array($Qw)){
|
||||
$OSType = "Windows"; $OSName = $Qw[0]['os']; $OSKernel = "";
|
||||
}else{
|
||||
if($Qx[0]['Type'] == "AIX"){
|
||||
$OSType = "AIX"; $OSName = "AIX ".$Qx[0]['OSVersion'] ; $OSKernel = "";
|
||||
}else{
|
||||
$OSType = "Linux"; $OSName = $Qx[0]['Type']." ".$Qx[0]['OSVersion'] ; $OSKernel = $Qx[0]['Kernel'];
|
||||
if(is_array($Qx) ){
|
||||
if ($Qx[0]['Type'] == "AIX") {
|
||||
$OSType = "AIX";
|
||||
$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='computer' data-value='$server' hidden></div>";
|
||||
@@ -62,13 +68,14 @@
|
||||
if($OSType == "Windows"){
|
||||
$memory = $Qw[0]['memory']." GB";$cpu = $Qw[0]['cpu'];
|
||||
}else{
|
||||
if(is_array($Qx) ){
|
||||
$cpu = $Qx[0]['CPU'] ;
|
||||
if($OSType == "AIX"){
|
||||
$memory = round(str_replace(" Mo","",$Qx[0]['Memory'])/1024,2)." GB";
|
||||
}else{
|
||||
$memory = $Qvm[0]['Memory'] ?? $Qx[0]['Memory'] ;
|
||||
}
|
||||
}
|
||||
}}
|
||||
echo "<div id='type' data-value='".strtolower($OSType)."' hidden></div>";
|
||||
echo "<div id='computer' data-value='$server' hidden></div>";
|
||||
?>
|
||||
@@ -254,11 +261,14 @@
|
||||
}
|
||||
}
|
||||
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(is_array($dr)){
|
||||
$date = $dr[0]['Next'];
|
||||
if($date != ""){
|
||||
echo " <span class='badge rounded-pill bg-secondary text-light fs-5'>$w_nextRollup : $date </span>";
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<hr>
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
<?php
|
||||
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php";
|
||||
|
||||
$server = $_GET['s'] ?? '';
|
||||
$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'] ?? '';
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
$xmlString = mb_convert_encoding($xmlRaw, 'UTF-8', 'ISO-8859-1');
|
||||
if (!str_contains($xmlString, '<?xml')) {
|
||||
$xmlString = '<?xml version="1.0" encoding="UTF-8"?>' . $xmlString;
|
||||
}
|
||||
|
||||
try {
|
||||
$xml = new SimpleXMLElement($xmlString);
|
||||
} catch (Exception $e) {
|
||||
echo "Erreur de parsing XML : " . $e->getMessage();
|
||||
libxml_use_internal_errors(true);
|
||||
$xml = simplexml_load_string($xmlRaw);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$definitions = [];
|
||||
foreach ($xml->xpath("//OptionDefinition") as $optDef) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php";
|
||||
|
||||
$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>
|
||||
|
||||
378
Storage/VIO.php
Normal file
378
Storage/VIO.php
Normal 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
199
Veeam/veeam.php
Normal 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>
|
||||
@@ -84,7 +84,7 @@
|
||||
$ts = $s['ts'];
|
||||
$lastBackup = (explode(".",$s['LastBackup'] ?? ''))[0];
|
||||
$location = $s['Location'];
|
||||
$log = $s['Log'];
|
||||
$log = $s['Log'] ?? '';
|
||||
$backuptype = $s['BackupType'];
|
||||
|
||||
if(str_contains($log, "Length")) {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
// 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 hostname not in ('DUNAPPDNIM51','DUNAPPDNIM52') AND hostname not like '%VIO%' ";
|
||||
$conditions = [];
|
||||
|
||||
if ($show_aix) {
|
||||
@@ -43,7 +44,7 @@
|
||||
}
|
||||
if ($show_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)) {
|
||||
@@ -66,6 +67,7 @@
|
||||
usort($versions, 'version_compare');
|
||||
$UTDBES = end($versions);
|
||||
$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="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="xs" data-sortable="true" data-visible="false">XenSam</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -319,6 +322,26 @@
|
||||
} else {
|
||||
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>";
|
||||
}
|
||||
|
||||
53
X/VIO.php
53
X/VIO.php
@@ -40,6 +40,14 @@
|
||||
// 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");
|
||||
if (!$rawRows) $rawRows = [];
|
||||
|
||||
@@ -68,6 +76,7 @@ foreach ($groupedData as $clientKey => $rows) {
|
||||
|
||||
$names = []; $inerrs = []; $vfcs = []; $vios = [];
|
||||
$clintids = []; $fcnames = []; $tss = []; $vioerrs = [];
|
||||
$parities = [];
|
||||
|
||||
$hasError = false;
|
||||
$maxVioErr = 0;
|
||||
@@ -89,11 +98,23 @@ foreach ($groupedData as $clientKey => $rows) {
|
||||
$clintids[] = $r['clintid'] ?? '-';
|
||||
$fcnames[] = $r['fcname'] ?? '-';
|
||||
$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[] = [
|
||||
'clintname' => $clientKey,
|
||||
'hasError' => $hasError,
|
||||
'hasParityIssue' => $hasParityIssue,
|
||||
'maxVioErr' => $maxVioErr,
|
||||
'name' => implode('<br>', $names),
|
||||
'inerr' => implode('<br>', $inerrs),
|
||||
@@ -106,11 +127,16 @@ foreach ($groupedData as $clientKey => $rows) {
|
||||
];
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Priority 1: InErr (table-warning)
|
||||
if ($a['hasError'] && !$b['hasError']) return -1;
|
||||
if (!$a['hasError'] && $b['hasError']) return 1;
|
||||
// Priority 1: warning OR parity issue -> floated to the top, same standing
|
||||
$aTop = $a['hasError'] || $a['hasParityIssue'];
|
||||
$bTop = $b['hasError'] || $b['hasParityIssue'];
|
||||
if ($aTop && !$bTop) return -1;
|
||||
if (!$aTop && $bTop) return 1;
|
||||
|
||||
// Priority 2: VIOErr count
|
||||
if ($a['maxVioErr'] > 0 && $b['maxVioErr'] == 0) return -1;
|
||||
@@ -155,7 +181,17 @@ usort($displayRows, function($a, $b) {
|
||||
|
||||
<tbody>
|
||||
<?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; ?>">
|
||||
<td class="fw-bold align-middle"><?php echo htmlspecialchars($row['clintname']); ?></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['vio']; ?></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>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<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="Criticity" data-sortable="true"><?php echo $w_crit; ?></th>
|
||||
<th data-field="AD" data-sortable="true"><?php echo $w_ad; ?></th>
|
||||
@@ -97,7 +98,7 @@
|
||||
$lastUpdate = htmlspecialchars($lastUpdate ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$okText = htmlspecialchars($okText, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
if ($status === 'Y') {
|
||||
if ($status === 'Y' || $status === 'Veeam') {
|
||||
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>";
|
||||
}
|
||||
@@ -159,11 +160,12 @@
|
||||
|
||||
// Row Rendering
|
||||
$serverName = htmlspecialchars($row['Server'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$serverUrl = urlencode($row['Server'] ?? '');
|
||||
$serverName = str_replace('+', '', $serverName);
|
||||
$serverUrl = str_replace('+', '',urlencode($row['Server'] ?? ''));
|
||||
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 {
|
||||
$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'> </td>";
|
||||
@@ -173,7 +175,9 @@
|
||||
}
|
||||
|
||||
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 renderStatusCellWithDate($row['AD'], $row['ADlu'], 45);
|
||||
echo renderStatusCellWithDate($row['GLPI'], $row['GLPIlu'], 7);
|
||||
@@ -186,7 +190,7 @@
|
||||
|
||||
// BACKUP
|
||||
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); }
|
||||
|
||||
// SCOM
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
{
|
||||
@@ -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//
|
||||
function DB_SCCM(){
|
||||
@@ -406,9 +494,9 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- // SNOWFLAKES -->
|
||||
<canvas id="snow-canvas"></canvas>
|
||||
<button id="snow-toggle-btn" title="Activer/Désactiver la neige">❄️</button>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// --- 1. GESTION DES DATES (15 Déc au 5 Jan) ---
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
$nav_10_1 = "Servers";
|
||||
$nav_10_2 = "Workstations";
|
||||
|
||||
$nav_11 = "On Call";
|
||||
$nav_11_1 = "ServiceNow P1 & P2";
|
||||
|
||||
$nav_links = "Links";
|
||||
$nav_search = "Server Search...";
|
||||
|
||||
@@ -64,6 +67,7 @@
|
||||
$ti_17 = "HeartBeat Map for servers NOT in maintenance";
|
||||
$ti_18 = "Cluster Resources";
|
||||
$ti_19 = "Virtual Machines";
|
||||
$ti_20 = "Service-Now P1 & P2 Incidents. In progress, opened in the 72 last hours";
|
||||
|
||||
// words
|
||||
$w_server = "Server";
|
||||
@@ -157,3 +161,7 @@
|
||||
$w_unclusteredVMs = "Unclustered VM(s)";
|
||||
$w_vmCpuChecked = "VM(s) With 'CPU Compatibility' checked";
|
||||
$w_vmDynamicVHD = "VM(s) with dynamic VHD";
|
||||
$w_group = "Group";
|
||||
$w_assigned = "Assigned to";
|
||||
$w_opened = "Opened on";
|
||||
$w_incFound = "Tickets found";
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
$nav_10_1 = "Serveurs";
|
||||
$nav_10_2 = "Postes de Travail";
|
||||
|
||||
$nav_11 = "Astreinte";
|
||||
$nav_11_1 = "P1 et P2 ServiceNow";
|
||||
|
||||
$nav_links = "Liens";
|
||||
$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_18 = "Ressources des Grappes de Serveurs";
|
||||
$ti_19 = "Machines Virtuelles";
|
||||
$ti_20 = "Incidents Service-Now P1 & P2. En cours, ouverts depuis moins de 72h";
|
||||
|
||||
// words
|
||||
$w_server = "Serveur";
|
||||
@@ -156,3 +160,7 @@
|
||||
$w_unclusteredVMs = "MV(s) non en ressource";
|
||||
$w_vmCpuChecked = "MV(s) avec 'Compatibilité CPU' coché";
|
||||
$w_vmDynamicVHD = "MV(s) avec VHD dynamique";
|
||||
$w_group = "Groupe";
|
||||
$w_assigned = "Assigné à";
|
||||
$w_opened = "Ouvert le";
|
||||
$w_incFound = "Incidents ouverts";
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
let table = $('#t1');
|
||||
const $table = $('#t1');
|
||||
|
||||
$(function () {
|
||||
let options = table.bootstrapTable('getOptions');
|
||||
options.height = document.getElementById('content').clientHeight - 170;
|
||||
table.bootstrapTable('refreshOptions', options);
|
||||
});
|
||||
// Function to calculate and apply the perfect height
|
||||
function updateTableHeight() {
|
||||
// baseHeight uses the window inner height to stay stable
|
||||
const availableHeight = window.innerHeight - 170;
|
||||
|
||||
function tableresize() {
|
||||
let options = table.bootstrapTable('getOptions');
|
||||
options.height = document.getElementById('content').clientHeight - 170;
|
||||
table.bootstrapTable('refreshOptions', options);
|
||||
// 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
|
||||
});
|
||||
}
|
||||
|
||||
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
33
js/tableResize2.js
Normal 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));
|
||||
33
navbar.html
33
navbar.html
@@ -60,6 +60,15 @@
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
@@ -99,8 +108,8 @@
|
||||
<ul class="collapse nav flex-column ms-1" id="HyperV" data-bs-parent="#menu">
|
||||
<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/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/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>
|
||||
@@ -118,6 +127,7 @@
|
||||
<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/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>
|
||||
</ul>
|
||||
@@ -156,6 +166,7 @@
|
||||
<ul class="collapse nav flex-column ms-1" id="cluster" data-bs-parent="#menu">
|
||||
<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="/Reports/Cluster_compliance.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- Compliance</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -178,6 +189,26 @@
|
||||
</li>
|
||||
</ul>
|
||||
</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>
|
||||
<li class="nav-item">
|
||||
|
||||
410
reports/Backups.php
Normal file
410
reports/Backups.php
Normal 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>
|
||||
450
reports/Cluster_Compliance.php
Normal file
450
reports/Cluster_Compliance.php
Normal 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 — 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>Écart UBR (mê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">Écart</th>
|
||||
<th data-field="noeuds" data-sortable="true">Nœ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è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é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é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>Écart UBR — même build, KB à rattraper</h4>
|
||||
<p class="text-muted small mb-2">
|
||||
Ces clusters sont conformes au niveau cluster (build aligné), mais un ou plusieurs nœuds
|
||||
sont en retard de correctif. Triés par écart décroissant — les plus gros à corriger en priorité.
|
||||
</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œ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èle</th>
|
||||
<th data-field="bios" data-sortable="false">BIOS</th>
|
||||
<th data-field="detail" data-sortable="false">Dé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é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œ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è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">— <?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>
|
||||
221
reports/Cluster_Compliance_detail.php
Normal file
221
reports/Cluster_Compliance_detail.php
Normal 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é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ée pour ce cluster / ce run.</div>
|
||||
<?php else: ?>
|
||||
|
||||
<!-- ============ Node facts ============ -->
|
||||
<h4 class="mt-3">État des nœuds</h4>
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Nœud</th>
|
||||
<th>OS</th>
|
||||
<th>Niveau MS</th>
|
||||
<th>Signature SPP</th>
|
||||
<th>É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 écart de composant HP relevé (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érente de la majorité
|
||||
<span class="badge text-bg-danger">rouge</span> composant absent du nœ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
941
reports/Netbackup_Daily.php
Normal file
@@ -0,0 +1,941 @@
|
||||
<?php ob_start();?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>Web Infra Reports IT - NetBackup Daily</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/include/favicon-32x32.png">
|
||||
|
||||
<script src="/js/jquery-3.6.1.min.js"></script>
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/css/bootstrap-icons/bootstrap-icons.css">
|
||||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/css/bootstrap-table.min.css">
|
||||
<script src="/js/bootstrap-table.min.js"></script>
|
||||
<script src="/js/bootstrap-table-fr-FR.min.js"></script>
|
||||
<script src="/js/tableExport.min.js"></script>
|
||||
<script src="/js/bootstrap-table-export.min.js"></script>
|
||||
<script src="/js/libs/js-xlsx/xlsx.core.min.js"></script>
|
||||
|
||||
<style>
|
||||
.accordion,
|
||||
.accordion-item,
|
||||
.accordion-button,
|
||||
.accordion-button:not(.collapsed),
|
||||
.accordion-body {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.accordion-item {
|
||||
border-color: rgba(128, 128, 128, .35);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.accordion-button:not(.collapsed) {
|
||||
box-shadow: none;
|
||||
background-color: rgba(128, 128, 128, .12);
|
||||
}
|
||||
|
||||
.accordion-button:focus {
|
||||
box-shadow: none;
|
||||
border-color: rgba(128, 128, 128, .35);
|
||||
}
|
||||
|
||||
.accordion-button::after {
|
||||
filter: grayscale(1) opacity(.75);
|
||||
}
|
||||
|
||||
body.bg-dark .accordion-button::after {
|
||||
filter: invert(1) grayscale(1) opacity(.85);
|
||||
}
|
||||
|
||||
.accordion-body,
|
||||
.accordion-body .fixed-table-container,
|
||||
.accordion-body .fixed-table-body,
|
||||
.accordion-body .fixed-table-toolbar {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.accordion-body .fixed-table-container {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.accordion-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
position: sticky;
|
||||
top: var(--acc-header-h, 56px);
|
||||
z-index: 2;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
body.bg-dark .accordion-header,
|
||||
body.bg-dark .report-table thead th {
|
||||
background-color: #212529;
|
||||
}
|
||||
|
||||
.block-count {
|
||||
font-size: 1.25rem;
|
||||
min-width: 3.2em;
|
||||
}
|
||||
|
||||
.accordion-button .block-title {
|
||||
font-size: 1.45rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
|
||||
.master-card {
|
||||
border-left-width: 6px !important;
|
||||
border-left-style: solid !important;
|
||||
}
|
||||
|
||||
.master-ok { border-left-color: #198754 !important; }
|
||||
.master-warning { border-left-color: #ffc107 !important; }
|
||||
.master-critical { border-left-color: #dc3545 !important; }
|
||||
|
||||
.metric {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: .72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
min-height: 86px;
|
||||
}
|
||||
|
||||
.warning-size {
|
||||
color: #b7791f;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body.bg-dark .warning-size {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
body.bg-dark .card {
|
||||
--bs-card-bg: #2b3035;
|
||||
--bs-card-color: #f8f9fa;
|
||||
--bs-card-border-color: #495057;
|
||||
background-color: #2b3035;
|
||||
color: #f8f9fa;
|
||||
border-color: #495057;
|
||||
}
|
||||
|
||||
body.bg-dark .master-card,
|
||||
body.bg-dark .summary-card {
|
||||
background-color: #2b3035 !important;
|
||||
color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
body.bg-dark .card .text-muted,
|
||||
body.bg-dark .master-card .text-muted,
|
||||
body.bg-dark .summary-card .text-muted {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
body.bg-dark .metric-label {
|
||||
color: #adb5bd;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* More readable OK green, especially on the grey title badge. */
|
||||
.title-ok {
|
||||
color: #00d084 !important;
|
||||
}
|
||||
|
||||
body.bg-dark .title-ok {
|
||||
color: #5ee6a8 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.accordion-button .block-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.block-count {
|
||||
font-size: 1rem;
|
||||
min-width: 2.8em;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.report-table {
|
||||
font-size: .82rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-light text-dark">
|
||||
<?php include $_SERVER['DOCUMENT_ROOT'] . "/include/all.php"; ?>
|
||||
|
||||
<?php
|
||||
/* ============================================================================
|
||||
Helpers
|
||||
========================================================================== */
|
||||
|
||||
function h($value) {
|
||||
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
function db_int($row, $key) {
|
||||
return isset($row[$key]) && $row[$key] !== null ? (int)$row[$key] : 0;
|
||||
}
|
||||
|
||||
function fmt_dt($value) {
|
||||
if (empty($value)) return '-';
|
||||
$ts = strtotime((string)$value);
|
||||
return $ts ? date('d/m/Y H:i', $ts) : '-';
|
||||
}
|
||||
|
||||
function fmt_gb_from_kb($kb) {
|
||||
if ($kb === null || $kb === '') return '-';
|
||||
return number_format(((float)$kb) / 1048576, 1, ',', ' ') . ' GB';
|
||||
}
|
||||
|
||||
// PURGE Random datas //
|
||||
Invoke_Infra("delete from netbackupjobs where master not like '%-%'");
|
||||
|
||||
/* ============================================================================
|
||||
SQL commun : point de départ par master
|
||||
Dernier jour (dans les 3 jours precedents) ayant un Infra/full,
|
||||
puis premier JobID Infra/full de ce jour.
|
||||
========================================================================== */
|
||||
|
||||
$startPointCte = "
|
||||
WITH Masters AS
|
||||
(
|
||||
SELECT DISTINCT master
|
||||
FROM dbo.NetBackupJobs
|
||||
),
|
||||
LatestFullDay AS
|
||||
(
|
||||
SELECT
|
||||
master,
|
||||
MAX(CAST(start_date AS date)) AS full_day
|
||||
FROM dbo.NetBackupJobs
|
||||
WHERE
|
||||
start_date >= CAST(DATEADD(DAY, -3, GETDATE()) AS date)
|
||||
AND start_date < CAST(GETDATE() AS date)
|
||||
AND policy LIKE '%Infra%'
|
||||
AND schedule_type = 'full'
|
||||
GROUP BY master
|
||||
),
|
||||
StartPoints AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
MIN(j.job) AS start_job
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN LatestFullDay d
|
||||
ON d.master = j.master
|
||||
AND CAST(j.start_date AS date) = d.full_day
|
||||
WHERE
|
||||
j.policy LIKE '%Infra%'
|
||||
AND j.schedule_type = 'full'
|
||||
GROUP BY j.master
|
||||
)
|
||||
";
|
||||
|
||||
/* ============================================================================
|
||||
1. Résumé par master
|
||||
========================================================================== */
|
||||
|
||||
$summarySql = $startPointCte . ",
|
||||
Jobs AS
|
||||
(
|
||||
SELECT j.*
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
),
|
||||
LargeIncrementals AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
COUNT(DISTINCT j.client) AS large_incremental_clients
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
WHERE
|
||||
j.schedule_type = 'incremental'
|
||||
AND j.status = 0
|
||||
AND j.policy NOT LIKE '%Hyper%'
|
||||
AND j.policy NOT LIKE '%standard%'
|
||||
AND j.policy NOT LIKE '%WE%'
|
||||
AND j.size_kb > 73400320
|
||||
GROUP BY j.master
|
||||
)
|
||||
SELECT
|
||||
m.master,
|
||||
s.start_job,
|
||||
|
||||
COUNT(j.job) AS total_jobs,
|
||||
|
||||
COALESCE(SUM(CASE WHEN j.status <= 1 THEN 1 ELSE 0 END), 0) AS jobs_ok,
|
||||
COALESCE(SUM(CASE WHEN j.status > 1 THEN 1 ELSE 0 END), 0) AS jobs_error,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type IN ('full','incremental') THEN 1 ELSE 0 END
|
||||
), 0) AS scheduled_jobs,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type IN ('full','incremental')
|
||||
AND j.status > 1 THEN 1 ELSE 0 END
|
||||
), 0) AS scheduled_errors,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type = 'user backup' THEN 1 ELSE 0 END
|
||||
), 0) AS user_jobs,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE WHEN j.schedule_type = 'user backup'
|
||||
AND j.status > 1 THEN 1 ELSE 0 END
|
||||
), 0) AS user_errors,
|
||||
|
||||
COALESCE(li.large_incremental_clients, 0) AS large_incremental_clients
|
||||
|
||||
FROM Masters m
|
||||
LEFT JOIN StartPoints s
|
||||
ON s.master = m.master
|
||||
LEFT JOIN Jobs j
|
||||
ON j.master = m.master
|
||||
LEFT JOIN LargeIncrementals li
|
||||
ON li.master = m.master
|
||||
GROUP BY
|
||||
m.master,
|
||||
s.start_job,
|
||||
li.large_incremental_clients
|
||||
ORDER BY m.master
|
||||
";
|
||||
|
||||
$summary = Invoke_Infra($summarySql);
|
||||
if (!is_array($summary)) $summary = [];
|
||||
|
||||
/* ============================================================================
|
||||
2. Jobs en erreur + description NBRC
|
||||
========================================================================== */
|
||||
|
||||
$errorSql = "
|
||||
WITH LatestFullDay AS
|
||||
(
|
||||
SELECT
|
||||
master,
|
||||
MAX(CAST(start_date AS date)) AS full_day
|
||||
FROM dbo.NetBackupJobs
|
||||
WHERE
|
||||
start_date >= CAST(DATEADD(DAY, -3, GETDATE()) AS date)
|
||||
AND start_date < CAST(GETDATE() AS date)
|
||||
AND policy LIKE '%Infra%'
|
||||
AND schedule_type = 'full'
|
||||
GROUP BY master
|
||||
),
|
||||
StartPoints AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
MIN(j.job) AS start_job
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN LatestFullDay d
|
||||
ON d.master = j.master
|
||||
AND CAST(j.start_date AS date) = d.full_day
|
||||
WHERE
|
||||
j.policy LIKE '%Infra%'
|
||||
AND j.schedule_type = 'full'
|
||||
GROUP BY j.master
|
||||
)
|
||||
SELECT
|
||||
j.master,
|
||||
j.job,
|
||||
j.client,
|
||||
j.policy,
|
||||
j.schedule_type,
|
||||
j.status,
|
||||
r.RC_DESC AS status_desc,
|
||||
j.start_date,
|
||||
j.end_date,
|
||||
j.size_kb
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
LEFT JOIN dbo.NBRC r
|
||||
ON r.RC = j.status
|
||||
WHERE
|
||||
j.status > 1
|
||||
ORDER BY
|
||||
j.master,
|
||||
CASE
|
||||
WHEN j.schedule_type IN ('full','incremental') THEN 0
|
||||
WHEN j.schedule_type = 'user backup' THEN 1
|
||||
ELSE 2
|
||||
END,
|
||||
j.client,
|
||||
j.job
|
||||
";
|
||||
|
||||
$errors = Invoke_Infra($errorSql);
|
||||
if (!is_array($errors)) $errors = [];
|
||||
|
||||
/* ============================================================================
|
||||
3. Warnings : incrémentales > 70 GB
|
||||
Règle métier exacte :
|
||||
- incremental
|
||||
- status = 0
|
||||
- policy NOT LIKE Hyper / standard / WE
|
||||
- size > 70 GB
|
||||
- à partir du point de départ du master
|
||||
|
||||
On garde une ligne par master/client, correspondant au plus gros job du client.
|
||||
========================================================================== */
|
||||
|
||||
$largeSql = "
|
||||
WITH LatestFullDay AS
|
||||
(
|
||||
SELECT
|
||||
master,
|
||||
MAX(CAST(start_date AS date)) AS full_day
|
||||
FROM dbo.NetBackupJobs
|
||||
WHERE
|
||||
start_date >= CAST(DATEADD(DAY, -3, GETDATE()) AS date)
|
||||
AND start_date < CAST(GETDATE() AS date)
|
||||
AND policy LIKE '%Infra%'
|
||||
AND schedule_type = 'full'
|
||||
GROUP BY master
|
||||
),
|
||||
StartPoints AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
MIN(j.job) AS start_job
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN LatestFullDay d
|
||||
ON d.master = j.master
|
||||
AND CAST(j.start_date AS date) = d.full_day
|
||||
WHERE
|
||||
j.policy LIKE '%Infra%'
|
||||
AND j.schedule_type = 'full'
|
||||
GROUP BY j.master
|
||||
),
|
||||
Candidates AS
|
||||
(
|
||||
SELECT
|
||||
j.*,
|
||||
ROW_NUMBER() OVER
|
||||
(
|
||||
PARTITION BY j.master, j.client
|
||||
ORDER BY j.size_kb DESC, j.job DESC
|
||||
) AS rn
|
||||
FROM dbo.NetBackupJobs j
|
||||
INNER JOIN StartPoints s
|
||||
ON s.master = j.master
|
||||
AND j.job >= s.start_job
|
||||
WHERE
|
||||
j.schedule_type = 'incremental'
|
||||
AND j.status = 0
|
||||
AND j.policy NOT LIKE '%Hyper%'
|
||||
AND j.policy NOT LIKE '%standard%'
|
||||
AND j.policy NOT LIKE '%WE%'
|
||||
AND j.size_kb > 73400320
|
||||
)
|
||||
SELECT
|
||||
master,
|
||||
job,
|
||||
client,
|
||||
policy,
|
||||
schedule_type,
|
||||
status,
|
||||
start_date,
|
||||
end_date,
|
||||
size_kb
|
||||
FROM Candidates
|
||||
WHERE rn = 1
|
||||
ORDER BY master, size_kb DESC, client
|
||||
";
|
||||
|
||||
$largeIncrementals = Invoke_Infra($largeSql);
|
||||
if (!is_array($largeIncrementals)) $largeIncrementals = [];
|
||||
|
||||
/* ============================================================================
|
||||
Préparation des données
|
||||
========================================================================== */
|
||||
|
||||
$scheduledErrors = [];
|
||||
$userErrors = [];
|
||||
|
||||
foreach ($errors as $row) {
|
||||
$type = strtolower(trim((string)($row['schedule_type'] ?? '')));
|
||||
|
||||
if ($type === 'full' || $type === 'incremental') {
|
||||
$scheduledErrors[] = $row;
|
||||
} elseif ($type === 'user backup') {
|
||||
$userErrors[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$mastersOk = 0;
|
||||
$mastersWarning = 0;
|
||||
$mastersCritical = 0;
|
||||
$totalJobs = 0;
|
||||
$totalErrors = 0;
|
||||
|
||||
foreach ($summary as &$row) {
|
||||
$total = db_int($row, 'total_jobs');
|
||||
$ok = db_int($row, 'jobs_ok');
|
||||
$err = db_int($row, 'jobs_error');
|
||||
$large = db_int($row, 'large_incremental_clients');
|
||||
$noStart = !isset($row['start_job']) || $row['start_job'] === null || $row['start_job'] === '';
|
||||
|
||||
$totalJobs += $total;
|
||||
$totalErrors += $err;
|
||||
|
||||
if ($noStart || $total === 0 || ($total > 0 && $ok === 0)) {
|
||||
$row['_state'] = 'critical';
|
||||
$mastersCritical++;
|
||||
|
||||
if ($noStart) {
|
||||
$row['_state_text'] = 'Aucun point de départ Infra trouvé';
|
||||
} elseif ($total === 0) {
|
||||
$row['_state_text'] = 'Aucune sauvegarde détectée';
|
||||
} else {
|
||||
$row['_state_text'] = 'Toutes les sauvegardes sont en erreur';
|
||||
}
|
||||
} elseif ($err > 0 || $large > 0) {
|
||||
$row['_state'] = 'warning';
|
||||
$mastersWarning++;
|
||||
|
||||
$parts = [];
|
||||
if ($err > 0) $parts[] = $err . ' erreur(s)';
|
||||
if ($large > 0) $parts[] = $large . ' incr. > 70 GB';
|
||||
$row['_state_text'] = implode(' / ', $parts);
|
||||
} else {
|
||||
$row['_state'] = 'ok';
|
||||
$row['_state_text'] = 'OK';
|
||||
$mastersOk++;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$masterCount = count($summary);
|
||||
$totalLarge = count($largeIncrementals);
|
||||
|
||||
/* ============================================================================
|
||||
Helpers de rendu
|
||||
========================================================================== */
|
||||
|
||||
function render_master_card($m) {
|
||||
$state = $m['_state'] ?? 'ok';
|
||||
|
||||
$borderClass = [
|
||||
'ok' => 'master-ok',
|
||||
'warning' => 'master-warning',
|
||||
'critical' => 'master-critical',
|
||||
][$state];
|
||||
|
||||
$badgeClass = [
|
||||
'ok' => 'text-bg-success',
|
||||
'warning' => 'bg-warning text-dark',
|
||||
'critical' => 'text-bg-danger',
|
||||
][$state];
|
||||
|
||||
$largeClass = db_int($m, 'large_incremental_clients') > 0 ? 'text-warning' : 'text-success';
|
||||
?>
|
||||
<div class="card shadow-sm mb-2 master-card <?php echo $borderClass; ?>">
|
||||
<div class="card-body py-2 px-3">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<div>
|
||||
<div class="fw-bold fs-5"><?php echo h($m['master'] ?? ''); ?></div>
|
||||
<span class="badge <?php echo $badgeClass; ?>">
|
||||
<?php echo h($m['_state_text'] ?? ''); ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="small text-muted">
|
||||
Start job :
|
||||
<strong><?php echo h($m['start_job'] ?? '-'); ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (db_int($m, 'total_jobs') > 0) : ?>
|
||||
<div class="row g-2 mt-2">
|
||||
<div class="col-4 col-md-2">
|
||||
<div class="metric"><?php echo db_int($m, 'total_jobs'); ?></div>
|
||||
<div class="metric-label">Total</div>
|
||||
</div>
|
||||
<div class="col-4 col-md-2">
|
||||
<div class="metric text-success"><?php echo db_int($m, 'jobs_ok'); ?></div>
|
||||
<div class="metric-label">OK</div>
|
||||
</div>
|
||||
<div class="col-4 col-md-2">
|
||||
<div class="metric text-danger"><?php echo db_int($m, 'jobs_error'); ?></div>
|
||||
<div class="metric-label">Erreurs</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="metric">
|
||||
<?php echo db_int($m, 'scheduled_errors'); ?> /
|
||||
<?php echo db_int($m, 'scheduled_jobs'); ?>
|
||||
</div>
|
||||
<div class="metric-label">Full / Incr.</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="metric">
|
||||
<?php echo db_int($m, 'user_errors'); ?> /
|
||||
<?php echo db_int($m, 'user_jobs'); ?>
|
||||
</div>
|
||||
<div class="metric-label">User backup</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 small">
|
||||
Incr. > 70 GB :
|
||||
<strong class="<?php echo $largeClass; ?>">
|
||||
<?php echo db_int($m, 'large_incremental_clients'); ?>
|
||||
</strong>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
function render_error_table($id, $rows, $emptyText = 'Aucun job dans cette catégorie.') {
|
||||
?>
|
||||
<?php if (count($rows) === 0) : ?>
|
||||
<p class="text-muted mb-0"><?php echo h($emptyText); ?></p>
|
||||
<?php else : ?>
|
||||
<table
|
||||
class="table table-bordered table-hover table-sm report-table"
|
||||
id="<?php echo h($id); ?>"
|
||||
data-sortable="true"
|
||||
data-search="true"
|
||||
data-show-export="true"
|
||||
data-export-types='["csv","excel"]'
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-field="master" data-sortable="true">Master</th>
|
||||
<th data-field="client" data-sortable="true">Client</th>
|
||||
<th data-field="policy" data-sortable="true">Policy</th>
|
||||
<th data-field="schedule" data-sortable="true">Schedule</th>
|
||||
<th data-field="status" data-sortable="true">Status</th>
|
||||
<th data-field="description" data-sortable="true">Description</th>
|
||||
<th data-field="start" data-sortable="true">Début</th>
|
||||
<th data-field="end" data-sortable="true">Fin</th>
|
||||
<th data-field="size" data-sortable="true">Taille</th>
|
||||
<th data-field="job" data-sortable="true">Job</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<tr class="table-danger">
|
||||
<td><?php echo h($r['master'] ?? ''); ?></td>
|
||||
<td><strong><?php echo h($r['client'] ?? ''); ?></strong></td>
|
||||
<td><?php echo h($r['policy'] ?? ''); ?></td>
|
||||
<td><?php echo h($r['schedule_type'] ?? ''); ?></td>
|
||||
<td><strong><?php echo h($r['status'] ?? ''); ?></strong></td>
|
||||
<td><?php echo h($r['status_desc'] ?? 'Description non trouvée'); ?></td>
|
||||
<td><?php echo h(fmt_dt($r['start_date'] ?? null)); ?></td>
|
||||
<td><?php echo h(fmt_dt($r['end_date'] ?? null)); ?></td>
|
||||
<td><?php echo h(fmt_gb_from_kb($r['size_kb'] ?? null)); ?></td>
|
||||
<td><?php echo h($r['job'] ?? ''); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif;
|
||||
}
|
||||
|
||||
function render_warning_table($id, $rows) {
|
||||
?>
|
||||
<?php if (count($rows) === 0) : ?>
|
||||
<p class="text-muted mb-0">Aucune incrémentale > 70 GB.</p>
|
||||
<?php else : ?>
|
||||
<table
|
||||
class="table table-bordered table-hover table-sm report-table"
|
||||
id="<?php echo h($id); ?>"
|
||||
data-sortable="true"
|
||||
data-search="true"
|
||||
data-show-export="true"
|
||||
data-export-types='["csv","excel"]'
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-field="master" data-sortable="true">Master</th>
|
||||
<th data-field="client" data-sortable="true">Client</th>
|
||||
<th data-field="policy" data-sortable="true">Policy</th>
|
||||
<th data-field="size" data-sortable="true">Taille</th>
|
||||
<th data-field="status" data-sortable="true">Status</th>
|
||||
<th data-field="start" data-sortable="true">Début</th>
|
||||
<th data-field="end" data-sortable="true">Fin</th>
|
||||
<th data-field="job" data-sortable="true">Job</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $r) : ?>
|
||||
<tr class="table-warning">
|
||||
<td><?php echo h($r['master'] ?? ''); ?></td>
|
||||
<td><strong><?php echo h($r['client'] ?? ''); ?></strong></td>
|
||||
<td><?php echo h($r['policy'] ?? ''); ?></td>
|
||||
<td class="warning-size"><?php echo h(fmt_gb_from_kb($r['size_kb'] ?? null)); ?></td>
|
||||
<td><span class="badge text-bg-success"><?php echo h($r['status'] ?? '0'); ?> - OK</span></td>
|
||||
<td><?php echo h(fmt_dt($r['start_date'] ?? null)); ?></td>
|
||||
<td><?php echo h(fmt_dt($r['end_date'] ?? null)); ?></td>
|
||||
<td><?php echo h($r['job'] ?? ''); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif;
|
||||
}
|
||||
|
||||
function render_accordion_block($id, $title, $icon, $badgeClass, $count, $renderer, $expanded = false) {
|
||||
$collapsed = $expanded ? '' : 'collapsed';
|
||||
$show = $expanded ? 'show' : '';
|
||||
$aria = $expanded ? 'true' : 'false';
|
||||
?>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button <?php echo $collapsed; ?>"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#c_<?php echo h($id); ?>"
|
||||
aria-expanded="<?php echo $aria; ?>">
|
||||
<i class="bi <?php echo h($icon); ?> me-2"></i>
|
||||
<span class="block-title"><?php echo h($title); ?></span>
|
||||
<span class="badge <?php echo h($badgeClass); ?> ms-3 block-count"><?php echo (int)$count; ?></span>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="c_<?php echo h($id); ?>" class="accordion-collapse collapse <?php echo $show; ?>">
|
||||
<div class="accordion-body p-2">
|
||||
<?php $renderer(); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid" id="content">
|
||||
<div class="row flex-nowrap">
|
||||
|
||||
<div class="col-auto col-md-2 col-xl-2 px-sm-2 px-0 bg-dark vh-100 position-sticky top-0"
|
||||
style="-ms-flex:0 0 230px; flex:0 0 230px;">
|
||||
<?php include $_SERVER['DOCUMENT_ROOT'] . "/navbar.html"; ?>
|
||||
</div>
|
||||
|
||||
<div class="col py-3">
|
||||
|
||||
<!-- ================================================================
|
||||
Titre global
|
||||
================================================================ -->
|
||||
|
||||
<h2>
|
||||
<span class="badge text-bg-secondary w-100 text-wrap" id="ERR">
|
||||
NetBackup Daily —
|
||||
<span class="text-light"><?php echo $masterCount; ?> masters</span> —
|
||||
<span class="title-ok"><?php echo $mastersOk; ?> OK</span> -
|
||||
<span class="text-warning"><?php echo $mastersWarning; ?> Warning</span> -
|
||||
<span class="text-danger"><?php echo $mastersCritical; ?> Critical</span>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div class="container-fluid px-0">
|
||||
|
||||
<!-- ================================================================
|
||||
Compteurs globaux
|
||||
================================================================ -->
|
||||
|
||||
<div class="row g-2 mt-1 mb-3">
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold"><?php echo $totalJobs; ?></div>
|
||||
<div class="text-muted small">Jobs analysés</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold text-danger"><?php echo $totalErrors; ?></div>
|
||||
<div class="text-muted small">Jobs en erreur</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold text-warning"><?php echo $totalLarge; ?></div>
|
||||
<div class="text-muted small">Incr. > 70 GB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card shadow-sm summary-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="fs-3 fw-bold text-danger"><?php echo $mastersCritical; ?></div>
|
||||
<div class="text-muted small">Masters critiques</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
Résumé par master
|
||||
================================================================ -->
|
||||
|
||||
<h4 class="mt-3 mb-2">
|
||||
<i class="bi bi-hdd-stack me-1"></i>
|
||||
Résumé par master
|
||||
</h4>
|
||||
|
||||
<div class="row">
|
||||
<?php foreach ($summary as $m) : ?>
|
||||
<div class="col-12 col-xl-6">
|
||||
<?php render_master_card($m); ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
Détails
|
||||
================================================================ -->
|
||||
|
||||
<h4 class="mt-4 mb-2">
|
||||
<i class="bi bi-list-check me-1"></i>
|
||||
Détails
|
||||
</h4>
|
||||
|
||||
<div class="accordion mb-4" id="acc_netbackup_daily">
|
||||
|
||||
<?php
|
||||
render_accordion_block(
|
||||
'scheduled_errors',
|
||||
'Full / Incremental en erreur',
|
||||
'bi-x-octagon',
|
||||
'bg-danger',
|
||||
count($scheduledErrors),
|
||||
function () use ($scheduledErrors) {
|
||||
render_error_table('t_scheduled_errors', $scheduledErrors);
|
||||
},
|
||||
count($scheduledErrors) > 0
|
||||
);
|
||||
|
||||
render_accordion_block(
|
||||
'user_errors',
|
||||
'User Backup en erreur',
|
||||
'bi-person-exclamation',
|
||||
'bg-danger',
|
||||
count($userErrors),
|
||||
function () use ($userErrors) {
|
||||
render_error_table('t_user_errors', $userErrors);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
render_accordion_block(
|
||||
'large_incrementals',
|
||||
'Incrémentales > 70 GB',
|
||||
'bi-exclamation-triangle',
|
||||
'bg-warning text-dark',
|
||||
count($largeIncrementals),
|
||||
function () use ($largeIncrementals) {
|
||||
render_warning_table('t_large_incrementals', $largeIncrementals);
|
||||
},
|
||||
count($largeIncrementals) > 0
|
||||
);
|
||||
?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/switch.js"></script>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
function isDark() {
|
||||
return document.body.classList.contains('bg-dark');
|
||||
}
|
||||
|
||||
function syncTheme($table) {
|
||||
$table.toggleClass('table-dark', isDark());
|
||||
}
|
||||
|
||||
function updateStickyOffset($item) {
|
||||
var h = $item.find('.accordion-header').outerHeight() || 56;
|
||||
$item.css('--acc-header-h', h + 'px');
|
||||
}
|
||||
|
||||
function initTable($table) {
|
||||
if ($table.length === 0) return;
|
||||
|
||||
if (!$table.data('bt-initialized')) {
|
||||
$table.bootstrapTable();
|
||||
$table.data('bt-initialized', true);
|
||||
} else {
|
||||
$table.bootstrapTable('resetView');
|
||||
}
|
||||
|
||||
syncTheme($table);
|
||||
updateStickyOffset($table.closest('.accordion-item'));
|
||||
}
|
||||
|
||||
$('.accordion-collapse').on('shown.bs.collapse', function () {
|
||||
initTable($(this).find('table.report-table'));
|
||||
});
|
||||
|
||||
// Initialise aussi les accordéons ouverts par défaut au chargement.
|
||||
$('.accordion-collapse.show').each(function () {
|
||||
initTable($(this).find('table.report-table'));
|
||||
});
|
||||
|
||||
$('#lightSwitch').on('change', function () {
|
||||
setTimeout(function () {
|
||||
$('table.report-table').each(function () {
|
||||
syncTheme($(this));
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
$(window).on('resize', function () {
|
||||
$('.accordion-item').each(function () {
|
||||
updateStickyOffset($(this));
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>";
|
||||
|
||||
<?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>
|
||||
";
|
||||
}
|
||||
}
|
||||
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>";
|
||||
|
||||
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 -->
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
Reference in New Issue
Block a user