- 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.
1382 lines
34 KiB
PHP
1382 lines
34 KiB
PHP
<?php
|
|
include $_SERVER['DOCUMENT_ROOT']."/include/all.php";
|
|
|
|
/* ============================================================
|
|
Helpers
|
|
============================================================ */
|
|
|
|
function mysqlScalar($conn, $query, $field) {
|
|
$result = $conn->query($query);
|
|
|
|
if (!$result) {
|
|
return 0;
|
|
}
|
|
|
|
$row = mysqli_fetch_assoc($result);
|
|
return (int)($row[$field] ?? 0);
|
|
}
|
|
|
|
function fetchInfraRows($conn, $query) {
|
|
$rows = [];
|
|
|
|
if ($conn instanceof PDO) {
|
|
$stmt = $conn->query($query);
|
|
return $stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
|
|
}
|
|
|
|
if (function_exists('sqlsrv_query')) {
|
|
$stmt = @sqlsrv_query($conn, $query);
|
|
|
|
if ($stmt !== false) {
|
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
|
$rows[] = $row;
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
}
|
|
|
|
if (function_exists('odbc_exec')) {
|
|
$stmt = @odbc_exec($conn, $query);
|
|
|
|
if ($stmt !== false) {
|
|
while ($row = odbc_fetch_array($stmt)) {
|
|
$rows[] = $row;
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
function normalizeDateValue($value) {
|
|
if ($value instanceof DateTimeInterface) {
|
|
return $value->format('Y-m-d');
|
|
}
|
|
|
|
if ($value === null || trim((string)$value) === '') {
|
|
return null;
|
|
}
|
|
|
|
return substr((string)$value, 0, 10);
|
|
}
|
|
|
|
function normalizeOsKey($os) {
|
|
$os = trim((string)$os);
|
|
|
|
if ($os === '') {
|
|
return 'Unknown';
|
|
}
|
|
|
|
// Windows Server / Client
|
|
if (preg_match('/Windows/i', $os)) {
|
|
|
|
// Server versions
|
|
if (preg_match('/2003/', $os)) return 'Windows 2003';
|
|
if (preg_match('/2008/', $os)) return 'Windows 2008';
|
|
if (preg_match('/2012/', $os)) return 'Windows 2012';
|
|
if (preg_match('/2016/', $os)) return 'Windows 2016';
|
|
if (preg_match('/2019/', $os)) return 'Windows 2019';
|
|
if (preg_match('/2022/', $os)) return 'Windows 2022';
|
|
if (preg_match('/2025/', $os)) return 'Windows 2025';
|
|
|
|
// Clients
|
|
if (preg_match('/Windows\s*10/i', $os)) return 'Windows 10';
|
|
if (preg_match('/Windows\s*11/i', $os)) return 'Windows 11';
|
|
|
|
return 'Windows';
|
|
}
|
|
|
|
if (preg_match('/Windows\s+(10|11)/i', $os, $match)) {
|
|
return 'Windows ' . $match[1];
|
|
}
|
|
|
|
// RedHat / RHEL
|
|
// Examples handled:
|
|
// Linux / RedHat 8.8-0.8 -> RedHat 8.8
|
|
// Linux / RedHat 8-8.0-8 -> RedHat 8.8
|
|
// Red Hat Enterprise Linux 8.10 -> RedHat 8.10
|
|
if (preg_match('/Red\s*Hat|RedHat|RHEL/i', $os)) {
|
|
if (preg_match('/Red\s*Hat\s+(\d+)[\.\-](\d+)/i', $os, $match)) {
|
|
return 'RedHat ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/RedHat\s+(\d+)[\.\-](\d+)/i', $os, $match)) {
|
|
return 'RedHat ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/RHEL\s+(\d+)[\.\-](\d+)/i', $os, $match)) {
|
|
return 'RedHat ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/release\s+(\d+)[\.\-](\d+)/i', $os, $match)) {
|
|
return 'RedHat ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)[\.\-](\d+)/', $os, $match)) {
|
|
return 'RedHat ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'RedHat ' . $match[1];
|
|
}
|
|
|
|
return 'RedHat';
|
|
}
|
|
|
|
// AlmaLinux
|
|
if (preg_match('/AlmaLinux/i', $os)) {
|
|
if (preg_match('/AlmaLinux\s+(\d+)[\.\-](\d+)/i', $os, $match)) {
|
|
return 'AlmaLinux ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/release\s+(\d+)[\.\-](\d+)/i', $os, $match)) {
|
|
return 'AlmaLinux ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)[\.\-](\d+)/', $os, $match)) {
|
|
return 'AlmaLinux ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'AlmaLinux ' . $match[1];
|
|
}
|
|
|
|
return 'AlmaLinux';
|
|
}
|
|
|
|
// Rocky Linux
|
|
if (preg_match('/Rocky/i', $os)) {
|
|
if (preg_match('/(\d+)[\.\-](\d+)/', $os, $match)) {
|
|
return 'Rocky Linux ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'Rocky Linux ' . $match[1];
|
|
}
|
|
|
|
return 'Rocky Linux';
|
|
}
|
|
|
|
// CentOS
|
|
if (preg_match('/CentOS/i', $os)) {
|
|
if (preg_match('/(\d+)[\.\-](\d+)/', $os, $match)) {
|
|
return 'CentOS ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'CentOS ' . $match[1];
|
|
}
|
|
|
|
return 'CentOS';
|
|
}
|
|
|
|
// Ubuntu
|
|
if (preg_match('/Ubuntu/i', $os)) {
|
|
if (preg_match('/(\d+)\.(\d+)/', $os, $match)) {
|
|
return 'Ubuntu ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'Ubuntu ' . $match[1];
|
|
}
|
|
|
|
return 'Ubuntu';
|
|
}
|
|
|
|
// Debian
|
|
if (preg_match('/Debian/i', $os)) {
|
|
if (preg_match('/(\d+)\.(\d+)/', $os, $match)) {
|
|
return 'Debian ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'Debian ' . $match[1];
|
|
}
|
|
|
|
return 'Debian';
|
|
}
|
|
|
|
// SUSE / SLES
|
|
if (preg_match('/SUSE|SLES/i', $os)) {
|
|
if (preg_match('/(\d+).*?(?:SP\s*|\.)(\d+)/i', $os, $match)) {
|
|
return 'SLES ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'SLES ' . $match[1];
|
|
}
|
|
|
|
return 'SLES';
|
|
}
|
|
|
|
// Oracle Linux
|
|
if (preg_match('/Oracle Linux/i', $os)) {
|
|
if (preg_match('/(\d+)\.(\d+)/', $os, $match)) {
|
|
return 'Oracle Linux ' . $match[1] . '.' . $match[2];
|
|
}
|
|
|
|
if (preg_match('/(\d+)/', $os, $match)) {
|
|
return 'Oracle Linux ' . $match[1];
|
|
}
|
|
|
|
return 'Oracle Linux';
|
|
}
|
|
|
|
// AIX
|
|
if (preg_match('/AIX\s+6100/i', $os)) {
|
|
return 'AIX <7.2';
|
|
}
|
|
|
|
if (preg_match('/AIX\s+7100/i', $os)) {
|
|
return 'AIX <7.2';
|
|
}
|
|
|
|
if (preg_match('/AIX\s+7200/i', $os)) {
|
|
return 'AIX 7.2';
|
|
}
|
|
|
|
if (preg_match('/AIX\s+7300/i', $os)) {
|
|
return 'AIX 7.3';
|
|
}
|
|
|
|
if (preg_match('/AIX/i', $os)) {
|
|
return 'AIX <7.2';
|
|
}
|
|
|
|
if (preg_match('/Unix/i', $os)) {
|
|
return 'Unix';
|
|
}
|
|
|
|
return $os;
|
|
}
|
|
|
|
function isWindowsOs($os) {
|
|
return preg_match('/Windows/i', (string)$os) === 1;
|
|
}
|
|
|
|
function isAixOs($os) {
|
|
return preg_match('/AIX/i', (string)$os) === 1;
|
|
}
|
|
|
|
function isLinuxOs($os) {
|
|
$os = trim((string)$os);
|
|
|
|
if ($os === '') {
|
|
return false;
|
|
}
|
|
|
|
if (isWindowsOs($os) || isAixOs($os)) {
|
|
return false;
|
|
}
|
|
|
|
if (preg_match('/Unix/i', $os)) {
|
|
return false;
|
|
}
|
|
|
|
return preg_match('/Linux|Red Hat|RedHat|RHEL|AlmaLinux|Rocky|CentOS|Ubuntu|Debian|SUSE|SLES|Oracle|Data Domain OS|/i', $os) === 1;
|
|
}
|
|
|
|
function hasS1Agent($value) {
|
|
$value = trim((string)$value);
|
|
|
|
if ($value === '') {
|
|
return false;
|
|
}
|
|
|
|
if (in_array($value, ['N', 'Non supported OS'], true)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function incrementCounter(&$array, $key) {
|
|
if (!isset($array[$key])) {
|
|
$array[$key] = 0;
|
|
}
|
|
|
|
$array[$key]++;
|
|
}
|
|
|
|
function getLifecycleStatus($osKey, $lifecycleMap) {
|
|
$match = null;
|
|
$bestLength = 0;
|
|
|
|
if (isset($lifecycleMap[$osKey])) {
|
|
$match = $lifecycleMap[$osKey];
|
|
} else {
|
|
foreach ($lifecycleMap as $key => $data) {
|
|
if (stripos($osKey, $key) === 0 && strlen($key) > $bestLength) {
|
|
$match = $data;
|
|
$bestLength = strlen($key);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$match || empty($match['SupportEndDate'])) {
|
|
return [
|
|
'label' => 'Unknown',
|
|
'color' => '#6c757d',
|
|
'end' => null
|
|
];
|
|
}
|
|
|
|
$today = new DateTimeImmutable('today');
|
|
$warningLimit = $today->modify('+1 year');
|
|
$supportEndDate = new DateTimeImmutable($match['SupportEndDate']);
|
|
|
|
if ($supportEndDate < $today) {
|
|
return [
|
|
'label' => 'Expired',
|
|
'color' => '#dc3545',
|
|
'end' => $supportEndDate->format('Y-m-d')
|
|
];
|
|
}
|
|
|
|
if ($supportEndDate < $warningLimit) {
|
|
return [
|
|
'label' => 'Warning',
|
|
'color' => '#fd7e14',
|
|
'end' => $supportEndDate->format('Y-m-d')
|
|
];
|
|
}
|
|
|
|
return [
|
|
'label' => 'Supported',
|
|
'color' => '#198754',
|
|
'end' => $supportEndDate->format('Y-m-d')
|
|
];
|
|
}
|
|
|
|
function sortOsCounter(&$counter) {
|
|
uksort($counter, function($a, $b) {
|
|
return strnatcasecmp($a, $b);
|
|
});
|
|
}
|
|
|
|
function chartFromCounter($counter, $lifecycleMap = null, $limit = null, $groupOthers = false) {
|
|
uksort($counter, function($a, $b) use ($lifecycleMap) {
|
|
$statusA = $lifecycleMap ? getLifecycleStatus($a, $lifecycleMap) : null;
|
|
$statusB = $lifecycleMap ? getLifecycleStatus($b, $lifecycleMap) : null;
|
|
|
|
$dateA = ($statusA && !empty($statusA['end'])) ? $statusA['end'] : '0000-00-00';
|
|
$dateB = ($statusB && !empty($statusB['end'])) ? $statusB['end'] : '0000-00-00';
|
|
|
|
return strcmp($dateB, $dateA); // décroissant
|
|
});
|
|
|
|
if ($limit !== null && count($counter) > $limit) {
|
|
$limited = array_slice($counter, 0, $limit, true);
|
|
$remaining = array_slice($counter, $limit, null, true);
|
|
|
|
if ($groupOthers && count($remaining) > 0) {
|
|
$limited['Others'] = array_sum($remaining);
|
|
}
|
|
|
|
$counter = $limited;
|
|
}
|
|
|
|
$labels = [];
|
|
$data = [];
|
|
$colors = [];
|
|
|
|
$palette = [
|
|
'#198754',
|
|
'#fd7e14',
|
|
'#0d6efd',
|
|
'#6f42c1',
|
|
'#20c997',
|
|
'#dc3545',
|
|
'#6c757d',
|
|
'#0dcaf0',
|
|
'#6610f2',
|
|
'#d63384',
|
|
'#2f855a',
|
|
'#495057'
|
|
];
|
|
|
|
$i = 0;
|
|
|
|
foreach ($counter as $key => $value) {
|
|
$labels[] = $key;
|
|
$data[] = $value;
|
|
|
|
if ($lifecycleMap !== null && $key !== 'Others') {
|
|
$colors[] = getLifecycleStatus($key, $lifecycleMap)['color'];
|
|
} else {
|
|
$colors[] = $palette[$i % count($palette)];
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
return [
|
|
'labels' => $labels,
|
|
'data' => $data,
|
|
'colors' => $colors
|
|
];
|
|
}
|
|
|
|
/* ============================================================
|
|
Data loading
|
|
============================================================ */
|
|
|
|
$connEntry = DB_ENTRY02();
|
|
|
|
$globalRows = [];
|
|
$result = $connEntry->query("
|
|
SELECT
|
|
Server,
|
|
AD,
|
|
GLPI,
|
|
SCCM,
|
|
EPO,
|
|
NBU,
|
|
OS,
|
|
crit,
|
|
dpt,
|
|
virtual,
|
|
S1
|
|
FROM GlobalCrossover
|
|
WHERE OS IS NOT NULL
|
|
AND LTRIM(RTRIM(OS)) <> ''
|
|
");
|
|
|
|
if ($result) {
|
|
while ($row = mysqli_fetch_assoc($result)) {
|
|
$globalRows[] = $row;
|
|
}
|
|
}
|
|
|
|
$connInfra = DB_INFRA();
|
|
|
|
$lifecycleMap = [];
|
|
$lifecycleRows = fetchInfraRows($connInfra, "
|
|
SELECT
|
|
OSKey,
|
|
OSFamily,
|
|
SupportEndDate,
|
|
Comment
|
|
FROM dbo.OSLifeCycle
|
|
");
|
|
|
|
foreach ($lifecycleRows as $row) {
|
|
$key = trim((string)($row['OSKey'] ?? ''));
|
|
|
|
if ($key === '') {
|
|
continue;
|
|
}
|
|
|
|
$lifecycleMap[$key] = [
|
|
'OSFamily' => $row['OSFamily'] ?? '',
|
|
'SupportEndDate' => normalizeDateValue($row['SupportEndDate'] ?? null),
|
|
'Comment' => $row['Comment'] ?? ''
|
|
];
|
|
}
|
|
|
|
/* ============================================================
|
|
KPI + chart data
|
|
============================================================ */
|
|
|
|
$totalWindows = 0;
|
|
$sccmOk = 0;
|
|
|
|
$s1Eligible = 0;
|
|
$s1Ok = 0;
|
|
|
|
$windowsAll = [];
|
|
$linuxAix = [];
|
|
|
|
$typeCounter = [
|
|
'Physical' => 0,
|
|
'Virtual' => 0
|
|
];
|
|
|
|
$statusCounter = [
|
|
'Supported' => 0,
|
|
'Warning' => 0,
|
|
'Expired' => 0,
|
|
'Unknown' => 0
|
|
];
|
|
|
|
$osRowsForTable = [];
|
|
|
|
foreach ($globalRows as $row) {
|
|
$os = $row['OS'] ?? '';
|
|
$osKey = normalizeOsKey($os);
|
|
$department = strtoupper(trim((string)($row['dpt'] ?? '')));
|
|
|
|
if (isWindowsOs($os)) {
|
|
$totalWindows++;
|
|
|
|
if (trim((string)($row['SCCM'] ?? '')) === 'Y') {
|
|
$sccmOk++;
|
|
}
|
|
|
|
incrementCounter($windowsAll, $osKey);
|
|
}
|
|
|
|
// SentinelOne KPI = Windows + Linux only. AIX excluded.
|
|
if (isWindowsOs($os) || isLinuxOs($os)) {
|
|
$s1Eligible++;
|
|
|
|
if (hasS1Agent($row['S1'] ?? '')) {
|
|
$s1Ok++;
|
|
}
|
|
}
|
|
|
|
if (isLinuxOs($os) || isAixOs($os)) {
|
|
incrementCounter($linuxAix, $osKey);
|
|
}
|
|
|
|
if (($row['virtual'] ?? '') === 'Y') {
|
|
incrementCounter($typeCounter, 'Virtual');
|
|
}
|
|
|
|
if (($row['virtual'] ?? '') === 'N') {
|
|
incrementCounter($typeCounter, 'Physical');
|
|
}
|
|
|
|
$status = getLifecycleStatus($osKey, $lifecycleMap);
|
|
incrementCounter($statusCounter, $status['label']);
|
|
|
|
if (!isset($osRowsForTable[$osKey])) {
|
|
$osRowsForTable[$osKey] = [
|
|
'OSKey' => $osKey,
|
|
'Count' => 0,
|
|
'Status' => $status['label'],
|
|
'EndDate' => $status['end'] ?? '-'
|
|
];
|
|
}
|
|
|
|
$osRowsForTable[$osKey]['Count']++;
|
|
}
|
|
|
|
$sccmPercent = $totalWindows > 0 ? round($sccmOk * 100 / $totalWindows, 2) : 0;
|
|
$s1Percent = $s1Eligible > 0 ? round($s1Ok * 100 / $s1Eligible, 2) : 0;
|
|
|
|
$totalType = array_sum($typeCounter);
|
|
$virtualPercent = $totalType > 0 ? round($typeCounter['Virtual'] * 100 / $totalType, 1) : 0;
|
|
$physicalPercent = $totalType > 0 ? round($typeCounter['Physical'] * 100 / $totalType, 1) : 0;
|
|
|
|
$chartWindowsAll = chartFromCounter($windowsAll, $lifecycleMap);
|
|
$chartLinuxAix = chartFromCounter($linuxAix, $lifecycleMap, 14, true);
|
|
|
|
$chartSupport = [
|
|
'labels' => array_keys($statusCounter),
|
|
'data' => array_values($statusCounter),
|
|
'colors' => ['#198754', '#fd7e14', '#dc3545', '#6c757d']
|
|
];
|
|
|
|
/* ============================================================
|
|
Existing cards data
|
|
============================================================ */
|
|
|
|
$nbADactive = mysqlScalar($connEntry, "SELECT COUNT(*) AS total FROM adcomputers", "total");
|
|
$nbADinactive = mysqlScalar($connEntry, "SELECT COUNT(*) AS total FROM adcomputers WHERE enabled = 'False'", "total");
|
|
|
|
$nbnessus = mysqlScalar($connEntry, "
|
|
SELECT COUNT(*) AS total
|
|
FROM GlobalCrossover
|
|
WHERE EPO IS NOT NULL
|
|
AND LTRIM(RTRIM(EPO)) <> ''
|
|
AND EPO NOT IN ('Non supported OS', 'N')
|
|
", "total");
|
|
|
|
$connGlpi = DB_GLPI();
|
|
$nbglpi = mysqlScalar($connGlpi, "
|
|
SELECT COUNT(name) AS total
|
|
FROM glpi_computers
|
|
WHERE entities_id = 6
|
|
AND is_deleted = 0
|
|
AND states_ID = 2
|
|
AND computertypes_id IN (7,19)
|
|
AND name <> ''
|
|
", "total");
|
|
mysqli_close($connGlpi);
|
|
|
|
$connNbu = DB_ENTRY01();
|
|
$nbnbu = mysqlScalar($connNbu, "SELECT COUNT(DISTINCT server) AS total FROM nb_jobs_full", "total");
|
|
mysqli_close($connNbu);
|
|
|
|
uasort($osRowsForTable, function($a, $b) {
|
|
$dateA = ($a['EndDate'] === '-' || empty($a['EndDate'])) ? '9999-12-31' : $a['EndDate'];
|
|
$dateB = ($b['EndDate'] === '-' || empty($b['EndDate'])) ? '9999-12-31' : $b['EndDate'];
|
|
|
|
$dateCompare = strcmp($dateA, $dateB);
|
|
|
|
if ($dateCompare !== 0) {
|
|
return $dateCompare;
|
|
}
|
|
|
|
return $b['Count'] <=> $a['Count'];
|
|
});
|
|
?>
|
|
<!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-1.13.1/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/chart.min.js"></script>
|
|
|
|
<style>
|
|
:root {
|
|
--page-bg: #f4f7fb;
|
|
--card-bg: #ffffff;
|
|
--border: #e6eaf0;
|
|
--text-main: #0f172a;
|
|
--text-muted: #64748b;
|
|
--shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
|
--shadow-soft: 0 4px 14px rgba(15, 23, 42, 0.06);
|
|
}
|
|
|
|
body {
|
|
background: var(--page-bg);
|
|
}
|
|
|
|
.main-content {
|
|
padding: 22px 26px;
|
|
overflow-x: hidden;
|
|
background: var(--page-bg);
|
|
color: var(--text-main);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
body.text-light .main-content {
|
|
background: #1f2429;
|
|
color: #f8fafc;
|
|
}
|
|
|
|
.page-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 1.65rem;
|
|
font-weight: 800;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
body.text-light .page-title {
|
|
color: #f8fafc;
|
|
}
|
|
|
|
.last-update {
|
|
color: var(--text-muted);
|
|
font-size: .85rem;
|
|
}
|
|
|
|
.kpi-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, minmax(150px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
.kpi-card {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
box-shadow: var(--shadow);
|
|
padding: 20px 18px;
|
|
min-height: 150px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.kpi-icon {
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 18px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 1.55rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.kpi-title {
|
|
font-size: .92rem;
|
|
font-weight: 800;
|
|
margin-bottom: 8px;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.kpi-value {
|
|
font-size: 1.75rem;
|
|
font-weight: 900;
|
|
line-height: 1.1;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.kpi-sub {
|
|
font-size: .82rem;
|
|
color: var(--text-muted);
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.kpi-progress {
|
|
height: 5px;
|
|
background: #eef2f7;
|
|
border-radius: 999px;
|
|
overflow: hidden;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.kpi-progress > div {
|
|
height: 100%;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.env-line {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto auto;
|
|
gap: 10px;
|
|
align-items: center;
|
|
font-size: .88rem;
|
|
margin-top: 10px;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.env-line small {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.dashboard-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 2.35fr;
|
|
gap: 18px;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
.panel {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 14px;
|
|
box-shadow: var(--shadow);
|
|
padding: 18px;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.panel-title {
|
|
font-size: 1.15rem;
|
|
font-weight: 850;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding-bottom: 12px;
|
|
border-bottom: 1px solid var(--border);
|
|
margin-bottom: 12px;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.chart-wrap {
|
|
height: 260px;
|
|
position: relative;
|
|
}
|
|
|
|
.linux-chart-wrap {
|
|
height: 315px;
|
|
}
|
|
|
|
.support-panel {
|
|
padding-bottom: 20px;
|
|
}
|
|
|
|
.support-grid {
|
|
display: grid;
|
|
grid-template-columns: 285px 1fr 1.55fr;
|
|
gap: 20px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.support-chart {
|
|
height: 295px;
|
|
position: relative;
|
|
}
|
|
|
|
.status-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 14px;
|
|
}
|
|
|
|
.status-card {
|
|
border-radius: 14px;
|
|
padding: 18px;
|
|
border: 1px solid var(--border);
|
|
background: #fff;
|
|
box-shadow: var(--shadow-soft);
|
|
min-height: 142px;
|
|
}
|
|
|
|
.status-card .label {
|
|
font-weight: 850;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.status-card .value {
|
|
font-size: 1.6rem;
|
|
font-weight: 900;
|
|
}
|
|
|
|
.status-card .sub {
|
|
color: var(--text-muted);
|
|
font-size: .82rem;
|
|
}
|
|
|
|
.status-supported {
|
|
background: #ecfdf5;
|
|
border-color: #bbf7d0;
|
|
color: #15803d;
|
|
}
|
|
|
|
.status-warning {
|
|
background: #fff7ed;
|
|
border-color: #fed7aa;
|
|
color: #ea580c;
|
|
}
|
|
|
|
.status-expired {
|
|
background: #fef2f2;
|
|
border-color: #fecaca;
|
|
color: #dc2626;
|
|
}
|
|
|
|
.status-unknown {
|
|
background: #f8fafc;
|
|
border-color: #e2e8f0;
|
|
color: #64748b;
|
|
}
|
|
|
|
.os-table {
|
|
width: 100%;
|
|
font-size: .84rem;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.os-table th {
|
|
color: var(--text-muted);
|
|
font-weight: 800;
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 8px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.os-table td {
|
|
padding: 8px;
|
|
border-bottom: 1px solid #f1f5f9;
|
|
vertical-align: middle;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.badge-status {
|
|
border-radius: 999px;
|
|
padding: 4px 10px;
|
|
font-size: .74rem;
|
|
font-weight: 800;
|
|
display: inline-block;
|
|
}
|
|
|
|
.badge-Supported {
|
|
background: #dcfce7;
|
|
color: #15803d;
|
|
}
|
|
|
|
.badge-Warning {
|
|
background: #ffedd5;
|
|
color: #ea580c;
|
|
}
|
|
|
|
.badge-Expired {
|
|
background: #fee2e2;
|
|
color: #dc2626;
|
|
}
|
|
|
|
.badge-Unknown {
|
|
background: #e2e8f0;
|
|
color: #475569;
|
|
}
|
|
|
|
.footer-note {
|
|
color: var(--text-muted);
|
|
font-size: .82rem;
|
|
}
|
|
|
|
body.text-light .kpi-card,
|
|
body.text-light .panel {
|
|
background: #ffffff;
|
|
color: #0f172a;
|
|
}
|
|
|
|
body.text-light .kpi-card *,
|
|
body.text-light .panel *,
|
|
body.text-light .os-table,
|
|
body.text-light .os-table th,
|
|
body.text-light .os-table td {
|
|
color: inherit;
|
|
}
|
|
|
|
body.text-light .kpi-title,
|
|
body.text-light .kpi-value,
|
|
body.text-light .panel-title,
|
|
body.text-light .os-table td {
|
|
color: #0f172a;
|
|
}
|
|
|
|
body.text-light .kpi-sub,
|
|
body.text-light .last-update,
|
|
body.text-light .os-table th,
|
|
body.text-light .footer-note {
|
|
color: #64748b;
|
|
}
|
|
|
|
body.text-light .badge-Supported {
|
|
color: #15803d;
|
|
}
|
|
|
|
body.text-light .badge-Warning {
|
|
color: #ea580c;
|
|
}
|
|
|
|
body.text-light .badge-Expired {
|
|
color: #dc2626;
|
|
}
|
|
|
|
body.text-light .badge-Unknown {
|
|
color: #475569;
|
|
}
|
|
|
|
body.text-light .status-supported {
|
|
color: #15803d;
|
|
}
|
|
|
|
body.text-light .status-warning {
|
|
color: #ea580c;
|
|
}
|
|
|
|
body.text-light .status-expired {
|
|
color: #dc2626;
|
|
}
|
|
|
|
body.text-light .status-unknown {
|
|
color: #64748b;
|
|
}
|
|
|
|
@media (max-width: 1900px) {
|
|
.kpi-grid {
|
|
grid-template-columns: repeat(4, 1fr);
|
|
}
|
|
|
|
.dashboard-grid {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
|
|
.dashboard-grid .panel:nth-child(3) {
|
|
grid-column: span 2;
|
|
}
|
|
|
|
.support-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.status-cards {
|
|
grid-template-columns: repeat(4, 1fr);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1200px) {
|
|
.kpi-grid,
|
|
.dashboard-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.dashboard-grid .panel:nth-child(3) {
|
|
grid-column: span 1;
|
|
}
|
|
|
|
.status-cards {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
.os-table-scroll {
|
|
max-height: 355px; /* environ 10 lignes */
|
|
overflow-y: auto;
|
|
padding-right: 6px;
|
|
}
|
|
|
|
.os-table-scroll thead th {
|
|
position: sticky;
|
|
top: 0;
|
|
background: #ffffff;
|
|
z-index: 2;
|
|
}
|
|
|
|
body.text-light .os-table-scroll thead th {
|
|
background: #ffffff;
|
|
}
|
|
|
|
.dashboard-grid-two {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 18px;
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
.large-chart-wrap {
|
|
height: 360px;
|
|
}
|
|
|
|
@media (max-width: 1400px) {
|
|
.dashboard-grid-two {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<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 vh-100 position-sticky top-0" style="-ms-flex:0 0 230px;flex:0 0 230px;">
|
|
<?php include $_SERVER['DOCUMENT_ROOT']."/navbar.html"; ?>
|
|
</div>
|
|
|
|
<main class="col main-content" style="zoom:80%">
|
|
|
|
<section class="kpi-grid">
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-icon" style="background:#0d6efd;">
|
|
<i class="bi bi-server"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="kpi-title">Active Directory</div>
|
|
<div class="kpi-value"><?php echo $nbADactive; ?></div>
|
|
<div class="kpi-sub">
|
|
<?php echo $w_devices; ?> · <?php echo $nbADinactive; ?> inactive(s)
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-icon" style="background:#6f42c1;">
|
|
<i class="bi bi-display"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="kpi-title">GLPI</div>
|
|
<div class="kpi-value"><?php echo $nbglpi; ?></div>
|
|
<div class="kpi-sub">Devices</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-icon" style="background:#0d6efd;">
|
|
<i class="bi bi-windows"></i>
|
|
</div>
|
|
|
|
<div style="width:100%;">
|
|
<div class="kpi-title">SCCM <span style="color:#64748b;">(Windows)</span></div>
|
|
<div class="kpi-value"><?php echo $sccmPercent; ?>%</div>
|
|
<div class="kpi-sub"><?php echo $sccmOk; ?> / <?php echo $totalWindows; ?> Windows</div>
|
|
|
|
<div class="kpi-progress">
|
|
<div style="width:<?php echo $sccmPercent; ?>%;background:#0d6efd;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-icon" style="background:#198754;">
|
|
<i class="bi bi-shield-check"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="kpi-title">NESSUS</div>
|
|
<div class="kpi-value"><?php echo $nbnessus; ?></div>
|
|
<div class="kpi-sub">Agents</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-icon" style="background:#6f42c1;">
|
|
<i class="bi bi-shield-lock"></i>
|
|
</div>
|
|
|
|
<div style="width:100%;">
|
|
<div class="kpi-title">SentinelOne</div>
|
|
<div class="kpi-value"><?php echo $s1Percent; ?>%</div>
|
|
<div class="kpi-sub"><?php echo $s1Ok; ?> / <?php echo $s1Eligible; ?> Windows + Linux</div>
|
|
|
|
<div class="kpi-progress">
|
|
<div style="width:<?php echo $s1Percent; ?>%;background:#6f42c1;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-icon" style="background:#fd7e14;">
|
|
<i class="bi bi-database-fill-lock"></i>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="kpi-title">NetBackup</div>
|
|
<div class="kpi-value"><?php echo $nbnbu; ?></div>
|
|
<div class="kpi-sub">Clients</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card">
|
|
<div class="kpi-icon" style="background:#0d6efd;">
|
|
<i class="bi bi-pc-display"></i>
|
|
</div>
|
|
|
|
<div style="width:100%;">
|
|
<div class="kpi-title">Environment</div>
|
|
|
|
<div class="env-line">
|
|
<span><i class="bi bi-circle-fill" style="color:#198754;"></i> Virtual</span>
|
|
<b><?php echo $typeCounter['Virtual']; ?></b>
|
|
<small><?php echo $virtualPercent; ?>%</small>
|
|
</div>
|
|
|
|
<div class="env-line">
|
|
<span><i class="bi bi-circle-fill" style="color:#fd7e14;"></i> Physical</span>
|
|
<b><?php echo $typeCounter['Physical']; ?></b>
|
|
<small><?php echo $physicalPercent; ?>%</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<section class="dashboard-grid dashboard-grid-two">
|
|
|
|
<div class="panel">
|
|
<div class="panel-title">
|
|
<i class="bi bi-windows text-primary"></i>
|
|
Windows
|
|
</div>
|
|
|
|
<div class="chart-wrap large-chart-wrap">
|
|
<canvas id="ChartWindows"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="panel-title">
|
|
<i class="bi bi-ubuntu text-warning"></i>
|
|
Linux / AIX
|
|
</div>
|
|
|
|
<div class="chart-wrap large-chart-wrap">
|
|
<canvas id="ChartLinux"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
<section class="panel support-panel">
|
|
<div class="panel-title">
|
|
<i class="bi bi-shield-check text-primary"></i>
|
|
OS Support Status
|
|
</div>
|
|
|
|
<div class="support-grid">
|
|
|
|
<div class="support-chart">
|
|
<canvas id="ChartSupport"></canvas>
|
|
</div>
|
|
|
|
<div class="status-cards">
|
|
<?php foreach ($statusCounter as $label => $count): ?>
|
|
<?php
|
|
$class = 'status-' . strtolower($label);
|
|
$percent = count($globalRows) > 0 ? round($count * 100 / count($globalRows), 1) : 0;
|
|
?>
|
|
|
|
<div class="status-card <?php echo $class; ?>">
|
|
<div class="label"><?php echo $label; ?></div>
|
|
<div class="value"><?php echo $count; ?></div>
|
|
<div class="sub"><?php echo $percent; ?>%</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="os-table-scroll">
|
|
<table class="os-table">
|
|
<thead>
|
|
<tr>
|
|
<th>OS</th>
|
|
<th>End of Support</th>
|
|
<th>Status</th>
|
|
<th class="text-end">Devices</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<?php foreach ($osRowsForTable as $row): ?>
|
|
<tr>
|
|
<td><?php echo htmlspecialchars($row['OSKey']); ?></td>
|
|
<td><?php echo htmlspecialchars($row['EndDate']); ?></td>
|
|
<td>
|
|
<span class="badge-status badge-<?php echo htmlspecialchars($row['Status']); ?>">
|
|
<?php echo htmlspecialchars($row['Status']); ?>
|
|
</span>
|
|
</td>
|
|
<td class="text-end"><?php echo (int)$row['Count']; ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</section>
|
|
|
|
|
|
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/js/switch.js"></script>
|
|
|
|
<script>
|
|
const chartWindowsAll = <?php echo json_encode($chartWindowsAll); ?>;
|
|
const chartLinuxAix = <?php echo json_encode($chartLinuxAix); ?>;
|
|
const chartSupport = <?php echo json_encode($chartSupport); ?>;
|
|
|
|
if (window.Chart && Chart.defaults) {
|
|
if (Chart.defaults.font) {
|
|
Chart.defaults.font.family = "'Segoe UI', Arial, sans-serif";
|
|
Chart.defaults.color = '#475569';
|
|
} else if (Chart.defaults.global) {
|
|
Chart.defaults.global.defaultFontFamily = "'Segoe UI', Arial, sans-serif";
|
|
Chart.defaults.global.defaultFontColor = '#475569';
|
|
}
|
|
}
|
|
|
|
function createDoughnut(canvasId, chartData, cutout = '62%', legendPosition = 'right') {
|
|
const canvas = document.getElementById(canvasId);
|
|
|
|
if (!canvas || !window.Chart) {
|
|
console.error('Chart.js not loaded or canvas missing:', canvasId);
|
|
return;
|
|
}
|
|
|
|
new Chart(canvas, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: chartData.labels,
|
|
datasets: [{
|
|
data: chartData.data,
|
|
backgroundColor: chartData.colors,
|
|
borderColor: '#ffffff',
|
|
borderWidth: 3
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
|
|
// Chart.js v3/v4
|
|
cutout: cutout,
|
|
|
|
// Chart.js v2
|
|
cutoutPercentage: parseInt(cutout, 10),
|
|
|
|
legend: {
|
|
display: true,
|
|
position: legendPosition,
|
|
labels: {
|
|
boxWidth: 12,
|
|
fontColor: '#334155',
|
|
padding: 14
|
|
}
|
|
},
|
|
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: legendPosition,
|
|
labels: {
|
|
boxWidth: 12,
|
|
boxHeight: 12,
|
|
color: '#334155',
|
|
padding: 14
|
|
}
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
const dataset = context.dataset || context.chart.data.datasets[0];
|
|
const value = Number(context.raw ?? dataset.data[context.dataIndex]);
|
|
const total = dataset.data.reduce((a, b) => Number(a) + Number(b), 0);
|
|
const percent = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
|
|
const label = context.label ?? context.chart.data.labels[context.dataIndex];
|
|
|
|
return `${label}: ${value} (${percent}%)`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
tooltips: {
|
|
callbacks: {
|
|
label: function(tooltipItem, data) {
|
|
const dataset = data.datasets[tooltipItem.datasetIndex];
|
|
const value = Number(dataset.data[tooltipItem.index]);
|
|
const total = dataset.data.reduce((a, b) => Number(a) + Number(b), 0);
|
|
const percent = total > 0 ? ((value / total) * 100).toFixed(1) : 0;
|
|
const label = data.labels[tooltipItem.index];
|
|
|
|
return `${label}: ${value} (${percent}%)`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
createDoughnut('ChartWindows', chartWindowsAll, '58%', 'right');
|
|
createDoughnut('ChartLinux', chartLinuxAix, '58%', 'right');
|
|
createDoughnut('ChartSupport', chartSupport, '58%', 'right');
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|