PHP_AdminTool_Projekt/public/api/snmp_status.php
Tom c03a99d07a Updated Snmp and Interface
Ich habe das UI Testweise nochmal erweitert und die Art wie wir die SNMP Daten abrufen und anzeigen nochmal überarbeitet. Es gibt jetzt eine kleine API die über Javascript in Intervallen die Daten aktualisiert. In der Datei Changelog sind nochmal die genauen Änderungen erklärt.
2025-12-03 17:06:10 +01:00

279 lines
9.8 KiB
PHP

<?php
declare(strict_types=1);
/**
* SNMP-Status-API für das Dashboard.
*
* Nur authentifizierte Admins dürfen auf diesen Endpunkt zugreifen.
* Wird alle 5s vom JavaScript im Dashboard aufgerufen.
*
* Sicherheit: Session-Validierung + Admin-Rollenprüfung.
* Performance: Datei-basiertes Caching (10 Sekunden) um SNMP-Last zu reduzieren.
* Logging: Alle Fehler und wichtigen Daten werden in `public/logs/snmp_api.log` geschrieben.
*/
session_start();
// === Authentifizierung + Autorisierung ===
$sessionKeyUser = 'admin_user'; // aus config
if (!isset($_SESSION[$sessionKeyUser])) {
http_response_code(401);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'unauthorized']);
exit;
}
header('Content-Type: application/json; charset=utf-8');
// === Logging-Setup ===
$logDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'logs';
if (!is_dir($logDir)) {
@mkdir($logDir, 0755, true);
}
$logFile = $logDir . DIRECTORY_SEPARATOR . 'snmp_api.log';
function log_msg(string $msg): void {
global $logFile;
$timestamp = date('Y-m-d H:i:s');
@file_put_contents($logFile, "[$timestamp] $msg\n", FILE_APPEND);
}
$configPath = __DIR__ . '/../../config/config.php';
if (!is_readable($configPath)) {
log_msg('ERROR: config.php nicht lesbar');
echo json_encode(['error' => 'config_not_found']);
exit;
}
$config = require $configPath;
$snmp = $config['snmp'] ?? [];
log_msg('--- SNMP-Abfrage gestartet ---');
// === Cache-Logik (Datei) ===
// Cache-Datei wird nach 10 Sekunden als ungültig betrachtet
$cacheDir = sys_get_temp_dir();
$cacheFile = $cacheDir . DIRECTORY_SEPARATOR . 'snmp_status_cache.json';
$cacheTTL = 10; // Sekunden
if (file_exists($cacheFile)) {
$cacheAge = time() - filemtime($cacheFile);
if ($cacheAge < $cacheTTL) {
// Cache ist noch gültig
$cached = file_get_contents($cacheFile);
if ($cached !== false) {
log_msg("Cache HIT (Alter: {$cacheAge}s)");
echo $cached;
exit;
}
} else {
log_msg("Cache abgelaufen (Alter: {$cacheAge}s), neue Abfrage");
}
}
$host = $snmp['host'] ?? '127.0.0.1';
$community = $snmp['community'] ?? 'public';
$timeout = (int)($snmp['timeout'] ?? 2);
$retries = (int)($snmp['retries'] ?? 1);
log_msg("SNMP-Host: $host, Community: $community, Timeout: {$timeout}s");
if (!function_exists('snmpget')) {
log_msg('ERROR: SNMP-Erweiterung nicht installiert');
echo json_encode(['error' => 'snmp_extension_missing']);
exit;
}
snmp_set_quick_print(true);
snmp_set_oid_output_format(SNMP_OID_OUTPUT_NUMERIC);
// Hilfsfunktion: sichere snmpget-Rückgabe (numeric oder null)
function sget(string $host, string $community, string $oid, int $timeout, int $retries)
{
$v = @snmpget($host, $community, $oid, $timeout, $retries);
if ($v === false || $v === null) return null;
return is_string($v) ? trim($v) : $v;
}
// System-Uptime (TimeTicks)
$uptimeOid = $snmp['oids']['uptime'] ?? '1.3.6.1.2.1.1.3.0';
$upticksRaw = @sget($host, $community, $uptimeOid, $timeout, $retries);
$upticks = $upticksRaw !== null ? (int)preg_replace('/[^0-9]/', '', (string)$upticksRaw) : null;
log_msg("Uptime OID: $uptimeOid, Raw: " . ($upticksRaw ?? 'null'));
function format_uptime(?int $ticks): ?string
{
if ($ticks === null) return null;
// ticks sind Hundertstel-Sekunden
$seconds = (int)floor($ticks / 100);
$days = intdiv($seconds, 86400);
$seconds -= $days * 86400;
$hours = intdiv($seconds, 3600);
$seconds -= $hours * 3600;
$minutes = intdiv($seconds, 60);
$seconds -= $minutes * 60;
$parts = [];
if ($days) $parts[] = $days . ' Tage';
if ($hours) $parts[] = $hours . ' Std';
if ($minutes) $parts[] = $minutes . ' Min';
$parts[] = $seconds . ' Sek';
return implode(' ', $parts);
}
$uptimeStr = format_uptime($upticks);
// CPU: ersten Eintrag aus der CPU-Tabelle lesen
$cpuTable = $snmp['oids']['cpu_table'] ?? '1.3.6.1.2.1.25.3.3.1.2';
$cpuOid = $cpuTable . '.1';
$cpuRaw = @sget($host, $community, $cpuOid, $timeout, $retries);
$cpu = $cpuRaw !== null ? (float)preg_replace('/[^0-9.]/', '', (string)$cpuRaw) : null;
log_msg("CPU OID: $cpuOid, Raw: " . ($cpuRaw ?? 'null') . ", Parsed: " . ($cpu ?? 'null'));
// Storage-Tabellen: Versuche, C: (Windows) oder Root ("/") zu finden
$descrOid = $snmp['oids']['storage_descr'] ?? '1.3.6.1.2.1.25.2.3.1.3';
$unitsOid = $snmp['oids']['storage_units'] ?? '1.3.6.1.2.1.25.2.3.1.4';
$sizeOid = $snmp['oids']['storage_size'] ?? '1.3.6.1.2.1.25.2.3.1.5';
$usedOid = $snmp['oids']['storage_used'] ?? '1.3.6.1.2.1.25.2.3.1.6';
log_msg("Starte Storage-Walk - Descr: $descrOid, Units: $unitsOid, Size: $sizeOid, Used: $usedOid");
$descrWalk = @snmprealwalk($host, $community, $descrOid);
$unitsWalk = @snmprealwalk($host, $community, $unitsOid);
$sizeWalk = @snmprealwalk($host, $community, $sizeOid);
$usedWalk = @snmprealwalk($host, $community, $usedOid);
if (!is_array($descrWalk)) {
log_msg('WARNING: snmprealwalk für Beschreibungen fehlgeschlagen');
$descrWalk = [];
}
if (!is_array($unitsWalk)) {
log_msg('WARNING: snmprealwalk für Units fehlgeschlagen');
$unitsWalk = [];
}
if (!is_array($sizeWalk)) {
log_msg('WARNING: snmprealwalk für Größe fehlgeschlagen');
$sizeWalk = [];
}
if (!is_array($usedWalk)) {
log_msg('WARNING: snmprealwalk für Used fehlgeschlagen');
$usedWalk = [];
}
log_msg('Storage-Einträge gefunden: ' . count($descrWalk));
$diskPercent = null;
$memPercent = null;
$storageEntries = [];
// Sammeln aller Storage-Einträge für Debug-Logging
if (is_array($descrWalk) && count($descrWalk) > 0) {
foreach ($descrWalk as $descrOidFull => $descrRaw) {
// Index extrahieren
if (!preg_match('/\.(\d+)$/', $descrOidFull, $m)) continue;
$idx = $m[1];
$descr = trim((string)$descrRaw, ' "');
// Suche nach passenden Einträgen in anderen Walks mit gleichem Index
$units = null;
$size = null;
$used = null;
foreach ($unitsWalk as $oid => $val) {
if (preg_match('/\.(\d+)$/', $oid, $m2) && $m2[1] === $idx) {
$units = (int)preg_replace('/[^0-9]/', '', (string)$val);
break;
}
}
foreach ($sizeWalk as $oid => $val) {
if (preg_match('/\.(\d+)$/', $oid, $m2) && $m2[1] === $idx) {
$size = (int)preg_replace('/[^0-9]/', '', (string)$val);
break;
}
}
foreach ($usedWalk as $oid => $val) {
if (preg_match('/\.(\d+)$/', $oid, $m2) && $m2[1] === $idx) {
$used = (int)preg_replace('/[^0-9]/', '', (string)$val);
break;
}
}
log_msg("Storage[$idx]: Desc='$descr', Size=$size, Used=$used, Units=$units");
if ($size === null || $units === null || $used === null || $size === 0 || $units === 0) {
log_msg("Storage[$idx]: SKIP (fehlende oder ungültige Daten)");
continue;
}
$totalBytes = $size * $units;
$usedBytes = $used * $units;
$percent = $totalBytes > 0 ? ($usedBytes / $totalBytes) * 100 : 0;
$storageEntries[] = [
'idx' => $idx,
'descr' => $descr,
'percent' => $percent,
'totalGB' => $totalBytes / (1024 ** 3),
'usedGB' => $usedBytes / (1024 ** 3),
];
// Heuristik 1: Suche nach C: oder Root
$lower = strtolower($descr);
if ($diskPercent === null && (strpos($lower, 'c:') !== false || strpos($lower, 'c:\\') !== false || strpos($lower, 'c:/') !== false || $lower === '/' || strpos($lower, 'root') !== false)) {
$diskPercent = $percent;
log_msg("Datenträger erkannt (Index $idx): $descr$percent%");
}
// Heuristik 2: Suche nach Physical Memory
if ($memPercent === null && ((strpos($lower, 'physical') !== false && strpos($lower, 'memory') !== false) || strpos($lower, 'ram') !== false || strpos($lower, 'physical memory') !== false)) {
$memPercent = $percent;
log_msg("Speicher erkannt (Index $idx): $descr$percent%");
}
}
}
// Fallback 1: Wenn keine Disk gefunden, nimm den größten Storage-Eintrag > 1GB
if ($diskPercent === null && count($storageEntries) > 0) {
log_msg('Fallback: Suche Disk (größter Eintrag > 1GB)');
usort($storageEntries, fn($a, $b) => $b['totalGB'] <=> $a['totalGB']);
foreach ($storageEntries as $entry) {
if ($entry['totalGB'] > 1) {
$diskPercent = $entry['percent'];
log_msg('Fallback Disk gefunden (Index ' . $entry['idx'] . '): ' . $entry['descr'] . ' → ' . $diskPercent . '%');
break;
}
}
}
// Fallback 2: Wenn kein Speicher gefunden, nimm den kleinsten Eintrag (meist Physical Memory)
if ($memPercent === null && count($storageEntries) > 0) {
log_msg('Fallback: Suche Memory (kleinster Eintrag)');
usort($storageEntries, fn($a, $b) => $a['totalGB'] <=> $b['totalGB']);
$memPercent = $storageEntries[0]['percent'];
log_msg('Fallback Memory gefunden (Index ' . $storageEntries[0]['idx'] . '): ' . $storageEntries[0]['descr'] . ' → ' . $memPercent . '%');
}
$result = [
'hostname' => @sget($host, $community, '1.3.6.1.2.1.1.5.0', $timeout, $retries) ?? null,
'uptime' => $uptimeStr,
'upticks' => $upticks,
'cpu_usage' => $cpu,
'memory_usage' => $memPercent !== null ? round($memPercent, 2) : null,
'disk_usage_c' => $diskPercent !== null ? round($diskPercent, 2) : null,
'last_update' => time(),
];
log_msg('RESULT: CPU=' . $result['cpu_usage'] . ', Mem=' . $result['memory_usage'] . ', Disk=' . $result['disk_usage_c']);
$resultJson = json_encode($result);
// === Cache schreiben ===
@file_put_contents($cacheFile, $resultJson);
log_msg('Cache geschrieben, TTL: ' . $cacheTTL . 's');
echo $resultJson;
exit;