diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f83df96 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,297 @@ +# CHANGELOG + +## 2025-12-03 — SNMP Live-Dashboard (Architektur: Service + API) + +### Übersicht + +Implementierung eines **dualen SNMP-Status-Systems**: +1. **Server-seitig (Initial Load):** `DashboardController` → `SnmpServerStatusService` +2. **Client-seitig (Live-Updates):** Browser-JavaScript → API-Endpunkt (`snmp_status.php`) + +Ergebnis: **Beste User Experience** (sofortige Daten beim Laden) + **Redundanz** (Live-Polling läuft weiter, auch wenn Service fehlt). + +--- + +## Komponente 1: Service (`app/Services/Snmp/SnmpServerStatusService.php`) — modernisiert + +### Was wurde geändert? + +Die alte Version ist zu streng gewesen (wirft Exception wenn "Physical Memory" nicht exakt gefunden wird). +Neue Version hat **intelligente Fallback-Logik**: + +**RAM-Erkennung (in dieser Reihenfolge):** +1. Heuristik: Suche nach "Physical Memory" (exakt) +2. Fallback 1: Suche nach "physical" ODER "memory" ODER "ram" (Case-insensitive) +3. Fallback 2: Nimm den **kleinsten Storage-Eintrag** (wahrscheinlich RAM) +4. Falls alles fehlschlägt: Exception → abgefangen im Controller → 0% angezeigt + +**Disk-Erkennung (in dieser Reihenfolge):** +1. Heuristik: Suche nach "C:\\" (Windows, exakt) +2. Fallback 1: Suche nach "C:" ODER "root" ODER "/" (Case-insensitive) +3. Fallback 2: Nimm den **größten Storage-Eintrag > 1 GB** (wahrscheinlich Hauptlaufwerk) +4. Falls alles fehlschlägt: Exception → abgefangen im Controller → 0% angezeigt + +### Featureset + +- ✅ Robuste Fehlerbehandlung (aussagekräftige `RuntimeException` für Debugging) +- ✅ Intelligente Fallbacks bei unerwarteten OID-Beschreibungen +- ✅ Unterstützung Windows + Linux +- ✅ Prüfung auf SNMP-Erweiterung und Konfiguration +- ✅ Uptime in lesbares Format konvertieren (z. B. "1 Tage, 10:17:36") +- ✅ CPU-Durchschnitt über alle Kerne + +--- + +## Komponente 2: Controller (`app/Controllers/DashboardController.php`) — neu strukturiert + +### Was wurde geändert? + +Der Controller hatte nur eine Zeile geändert; jetzt **fehlertolerante Abfrage**: + +```php +try { + $serverStatus = $this->snmpService->getServerStatus(); +} catch (\RuntimeException $e) { + error_log('SNMP-Fehler beim initialen Laden - ' . $e->getMessage()); + // Fallback-Werte werden verwendet (siehe oben) +} +``` + +**Effekt:** +- Wenn Service fehlschlägt: Dashboard wird trotzdem geladen (mit 0% oder 'n/a') +- Fehler wird geloggt (PHP Error-Log) +- Live-Polling (API) läuft trotzdem weiter und kann Daten liefern +- Bessere User Experience statt "Error 500" + +--- + +## Komponente 3: API (`public/api/snmp_status.php`) + +Siehe vorherige CHANGELOG-Einträge. Wichtig: +- **Identische Fallback-Logik** wie der Service +- **Session-Validierung** (nur Admins) +- **Caching** (10 Sekunden) +- **Detailliertes Logging** in `public/logs/snmp_api.log` + +--- + +## Komponente 4: View & JavaScript (`public/views/dashboard.php`) + +Siehe vorherige CHANGELOG-Einträge. + +--- + +## Architektur: Dual-Layer System + +### Layer 1: Initial Load (Server-seitig) + +``` +Nutzer öffnet Dashboard + ↓ +DashboardController::show() + ↓ +SnmpServerStatusService::getServerStatus() + ↓ +SNMP-Abfrage (mit Fallbacks) + ↓ +Daten sofort in View angezeigt + ↓ +(Bei Fehler: Fallback-Werte wie 0%, 'n/a') + ↓ +Logging in PHP Error-Log +``` + +**Vorteile:** +- ✅ Daten sind **sofort** sichtbar (gute UX) +- ✅ Fallbacks verhindern "Error 500" +- ✅ Service wird nur 1x pro Seitenladung aufgerufen (sparsam) + +--- + +### Layer 2: Live-Updates (Client-seitig) + +``` +Browser lädt Dashboard + ↓ +JavaScript addEventListener('DOMContentLoaded') + ↓ +Sofort erste Abfrage: fetch('api/snmp_status.php') + ↓ +Antwort: JSON mit aktuellen Daten + ↓ +updateUI() aktualisiert die Karten + ↓ +Alle 5 Sekunden wiederholt (setInterval) + ↓ +Logging in public/logs/snmp_api.log (Server-seitig) +``` + +**Vorteile:** +- ✅ **Live-Updates** ohne Seite zu reload-en +- ✅ **Session-Schutz** (nur Admins können Endpunkt aufrufen) +- ✅ **Caching** reduziert SNMP-Last (10s TTL) +- ✅ **Fallback-Logik** im API unabhängig vom Service +- ✅ **Redundanz:** Wenn Service fehlt, läuft API trotzdem + +--- + +## Logging + +### PHP Error-Log (Service-Fehler) + +- **Ort:** Abhängig von PHP-Konfiguration (meist `/var/log/php-errors.log` oder Windows Event-Log) +- **Format:** Standard PHP Error-Log +- **Inhalt:** SNMP-Fehler beim initialen Laden (z. B. "SNMP-Konfiguration ist unvollständig") +- **Trigger:** Nur wenn Service-Abfrage fehlschlägt + +**Beispiel:** +``` +[03-Dec-2025 12:05:00 UTC] DashboardController: SNMP-Fehler beim initialen Laden - SNMP-Konfiguration ist unvollständig (host fehlt). +``` + +### SNMP API Log (`public/logs/snmp_api.log`) + +- **Ort:** `public/logs/snmp_api.log` (wird automatisch angelegt) +- **Format:** `[YYYY-MM-DD HH:MM:SS] Nachricht` +- **Inhalt:** + - Cache-Hits/Misses + - SNMP-Konfiguration + - Alle Storage-Einträge + - Erkannte Disk/RAM mit Prozentsätzen + - Fallback-Aktionen + - Finale Werte + - Fehler + +**Beispiel:** +``` +[2025-12-03 12:05:00] --- SNMP-Abfrage gestartet --- +[2025-12-03 12:05:00] SNMP-Host: 127.0.0.1, Community: public_ro, Timeout: 2s +[2025-12-03 12:05:00] Uptime OID: 1.3.6.1.2.1.1.3.0, Raw: "Timeticks: (1234567) 14 days, 6:14:27.67" +[2025-12-03 12:05:00] Storage[1]: Desc='Physical Memory', Size=16777216, Used=8388608, Units=1024 +[2025-12-03 12:05:00] Speicher erkannt (Index 1): Physical Memory → 50.00% +[2025-12-03 12:05:00] Storage[2]: Desc='C:\\ ', Size=536870912, Used=268435456, Units=512 +[2025-12-03 12:05:00] Datenträger erkannt (Index 2): C:\\ → 50.00% +[2025-12-03 12:05:00] RESULT: CPU=25, Mem=50.00, Disk=50.00 +[2025-12-03 12:05:00] Cache geschrieben, TTL: 10s +``` + +--- + +## Fehlerszenarien & Behavior + +### Szenario 1: SNMP läuft, alles OK +``` +Service: ✅ Daten sofort angezeigt +API: ✅ Live-Updates alle 5s +Logs: ✅ Beide Logs ganz normal +``` + +### Szenario 2: SNMP-Erweiterung fehlt +``` +Service: ❌ Exception → abgefangen → 0%, 'n/a' angezeigt +API: ❌ Exception → abgefangen → {"error": "snmp_extension_missing"} +Logs: ⚠️ Beide Logs zeigen Fehler +User-View: "Metriken werden angezeigt (0%), aber nicht aktualisiert" +Aktion: Admin sieht Fehler im Log und installiert SNMP +``` + +### Szenario 3: SNMP antwortet, aber Beschreibungen sind unbekannt +``` +Service: ✅ Fallback-Logik findet RAM/Disk trotzdem +API: ✅ Fallback-Logik findet RAM/Disk trotzdem +Logs: ℹ️ `Fallback RAM gefunden` / `Fallback Disk gefunden` +User-View: ✅ Daten werden angezeigt +``` + +### Szenario 4: Service fehlt, API läuft +``` +Service: ❌ Exception beim Laden +API: ✅ Live-Updates funktionieren normal +User-View: "Beim Laden: 0%, nach 5s: aktuelle Werte" +Gut genug! +``` + +--- + +## Testing-Anleitung + +### 1. Initialer Load testen +```bash +# Browser öffnen, als Admin einloggen +# Dashboard öffnen +# → Sollten Werte sichtbar sein (entweder echte oder 0%) +``` + +### 2. Service-Fehler simulieren +```php +// In DashboardController.php: Service-Aufruf kommentieren +// $serverStatus = [... Fallback-Werte ...]; +// → Dashboard sollte trotzdem laden (mit 0%) +``` + +### 3. API testen +```bash +# Browser DevTools → Network → api/snmp_status.php +# → Sollte JSON zurückgeben +# Bei 401 → Session fehlt (erwartet wenn nicht angemeldet) +# Sollte aber funktionieren wenn angemeldet +``` + +### 4. Logs prüfen +```bash +# PHP Error-Log +error_log() Output ansehen + +# SNMP API Log +cat public/logs/snmp_api.log +# Sollte Einträge zeigen (mit Timestamps) +``` + +### 5. Cache prüfen +```bash +# Temp-Datei prüfen +# Windows: %TEMP%\snmp_status_cache.json +# Linux: /tmp/snmp_status_cache.json +# → Sollte JSON enthalten +``` + +--- + +## Known Issues & Limitations + +1. **Disk/RAM-Heuristik:** Bei sehr ungewöhnlichen Storage-Labels können Fallbacks greifen, die nicht ideal sind + - **Lösung:** Log prüfen (`Storage[X]:` Einträge) und ggf. Heuristiken anpassen + +2. **Cache-Speicher:** Erfordert Schreibzugriff auf `sys_get_temp_dir()` + - **Lösung:** Falls nicht verfügbar → Cache-Code entfernen oder APCu/Redis nutzen + +3. **OS-Feld:** Hardcoded auf "Windows Server" (TODO: Dynamisch per OID 1.3.6.1.2.1.1.1.0) + +--- + +## Performance + +- **Service-Abfrage:** 1x pro Seitenladung (~100-500ms je nach SNMP-Timeout) +- **API-Abfrage:** Alle 5s, aber gecacht für 10s → effektiv alle 10s eine echte SNMP-Abfrage +- **JavaScript Polling:** 5s Intervall (Browser-seitig, keine Last auf Server) +- **Gesamt:** Sehr effizient, auch bei vielen gleichzeitigen Nutzern + +--- + +## Summary für Kollegen + +✅ **Live-Dashboard mit zwei Ebenen:** +1. Initial Load via Service (sofortige Daten) +2. Live-Polling via API (kontinuierliche Updates) + +✅ **Robuste Fallback-Logik** für RAM und Disk (findet die Werte auch bei unbekannten Labels) + +✅ **Dual Logging:** +- PHP Error-Log für Service-Fehler +- `public/logs/snmp_api.log` für API-Aktivitäten + +✅ **Session-Geschützt:** Nur Admins können Status abrufen + +✅ **Gecacht:** 10 Sekunden TTL reduziert SNMP-Load + +✅ **Error-tolerant:** Dashboard funktioniert auch wenn SNMP fehlt (zeigt 0%, wartet auf Live-Updates) diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index 0785f4d..d181cb9 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -6,12 +6,19 @@ declare(strict_types=1); namespace App\Controllers; use App\Services\Ldap\LdapAuthService; +use App\Services\Logging\LoggingService; +use Throwable; /** * Zuständig für alles rund um den Login: * - Login-Formular anzeigen * - Login-Daten verarbeiten (Authentifizierung gegen LDAP/AD) * - Logout durchführen + * + * NEU: + * - Statt direkt HTML auszugeben oder header()-Redirects zu setzen, + * liefert der Controller "View-Results" zurück, die von index.php + * und einem zentralen Layout verarbeitet werden. */ class AuthController { @@ -21,8 +28,11 @@ class AuthController /** @var LdapAuthService Service, der die eigentliche LDAP/AD-Authentifizierung übernimmt */ private LdapAuthService $ldapAuthService; + /** @var LoggingService Logger für technische Fehler */ + private LoggingService $logger; + /** - * Übergibt die Konfiguration an den Controller und initialisiert den LDAP-Authentifizierungsservice. + * Übergibt die Konfiguration an den Controller und initialisiert Services. * * @param array $config Vollständige Konfiguration aus config.php */ @@ -32,40 +42,46 @@ class AuthController $this->config = $config; // LdapAuthService mit dem Teilbereich "ldap" aus der Konfiguration initialisieren. - // Wenn 'ldap' nicht gesetzt ist, wird ein leeres Array übergeben (Fail fast erfolgt dann im Service). $this->ldapAuthService = new LdapAuthService($config['ldap'] ?? []); + + // LoggingService mit dem Teilbereich "logging" aus der Konfiguration initialisieren. + $this->logger = new LoggingService($config['logging'] ?? []); } /** * Zeigt das Login-Formular an. * Optional kann eine Fehlermeldung übergeben werden, die in der View dargestellt wird. + * + * @return array View-Result für das zentrale Layout */ - public function showLoginForm(?string $errorMessage = null): void + public function showLoginForm(?string $errorMessage = null): array { // Pfad zur Login-View (Template-Datei) ermitteln. $viewPath = __DIR__ . '/../../public/views/login.php'; - // Variable für die View vorbereiten (wird in der eingebundenen Datei verwendet). - $error = $errorMessage; - - // Falls die View-Datei (noch) nicht existiert, einen Fallback-HTML-Output verwenden. - if (file_exists($viewPath) === false) { - $this->renderInlineLogin($error); - return; - } - - // View-Datei einbinden. Variablen aus dieser Methode (z. B. $error) sind dort verfügbar. - require $viewPath; + // Wichtig: Die View erwartet aktuell die Variable $error. + return [ + 'view' => $viewPath, + 'data' => [ + 'error' => $errorMessage, + 'loginPage' => true, + ], + 'pageTitle' => 'Login', + // Beim Login ist typischerweise kein Menüpunkt aktiv. + 'activeMenu' => null, + ]; } /** * Verarbeitet das Login-Formular: * - Liest Benutzername und Passwort aus $_POST * - Ruft den LdapAuthService zur Authentifizierung auf - * - Setzt bei Erfolg die Session und leitet zum Dashboard weiter - * - Zeigt bei Fehlern erneut das Login-Formular mit Fehlermeldung an + * - Liefert bei Erfolg ein Redirect-Result zum Dashboard + * - Liefert bei Fehlern ein View-Result für das Login-Formular mit Fehlermeldung + * + * @return array View-Result ODER Redirect-Result */ - public function processLogin(): void + public function processLogin(): array { // Formulardaten aus dem POST-Request lesen. $username = trim($_POST['username'] ?? ''); @@ -76,17 +92,32 @@ class AuthController // true = Authentifizierung erfolgreich // false = Anmeldedaten fachlich ungültig (Benutzer/Passwort falsch) $authenticated = $this->ldapAuthService->authenticate($username, $password); - } catch (\Throwable $exception) { - // Technischer Fehler (z. B. LDAP-Server nicht erreichbar, falsche Konfiguration). - // In diesem Fall wird eine technische Fehlermeldung im Login-Formular angezeigt. - $this->showLoginForm('Technischer Fehler bei der Anmeldung: ' . $exception->getMessage()); - return; + } catch (Throwable $exception) { + // HIER ist vorher dein Fehler entstanden: + // - showLoginForm() wurde nur aufgerufen, das Ergebnis aber ignoriert + // - danach kam ein "return;" ohne Rückgabewert → Rückgabetyp array wurde verletzt + + // Technischen Fehler ausführlich ins Log schreiben + $this->logger->logException( + 'Technischer Fehler bei der Anmeldung.', + $exception, + [ + 'route' => 'login.submit', + 'username' => $username, + 'remote_addr'=> $_SERVER['REMOTE_ADDR'] ?? null, + ] + ); + + // Für den Benutzer nur eine allgemeine, aber verständliche Meldung anzeigen + return $this->showLoginForm( + 'Technischer Fehler bei der Anmeldung. Bitte versuchen Sie es später erneut ' + . 'oder wenden Sie sich an den Administrator.' + ); } // Fachlich fehlgeschlagene Anmeldung (z. B. falsches Passwort). if ($authenticated === false) { - $this->showLoginForm('Benutzername oder Passwort ist ungültig.'); - return; + return $this->showLoginForm('Benutzername oder Passwort ist ungültig.'); } // Ab hier ist die Anmeldung erfolgreich. @@ -96,22 +127,27 @@ class AuthController $sessionKey = $this->config['security']['session_key_user'] ?? 'admin_user'; // Benutzerinformationen in der Session hinterlegen. - // Diese Daten werden später von requireLogin() ausgewertet. + // Diese Daten werden später von requireLogin() bzw. im Layout ausgewertet. $_SESSION[$sessionKey] = [ - 'username' => $username, - 'login_at' => date('c'), // ISO-8601 Datum/Zeit der Anmeldung + 'username' => $username, + 'login_at' => date('c'), // ISO-8601 Datum/Zeit der Anmeldung ]; // Nach erfolgreicher Anmeldung zum Dashboard weiterleiten. - header('Location: index.php?route=dashboard'); - exit; + // Kein direkter header()-Aufruf, sondern ein Redirect-Result + // für die zentrale Steuerung in index.php. + return [ + 'redirect' => 'index.php?route=dashboard', + ]; } /** * Meldet den aktuell eingeloggten Benutzer ab, indem der entsprechende Session-Eintrag entfernt wird, - * und leitet anschließend zurück auf die Login-Seite. + * und liefert anschließend ein Redirect-Result zurück auf die Login-Seite. + * + * @return array Redirect-Result */ - public function logout(): void + public function logout(): array { // Session-Key für den eingeloggten Benutzer aus der Konfiguration lesen. $sessionKey = $this->config['security']['session_key_user'] ?? 'admin_user'; @@ -119,49 +155,9 @@ class AuthController // Eintrag aus der Session entfernen → Benutzer gilt als ausgeloggt. unset($_SESSION[$sessionKey]); - // Zur Login-Seite zurückleiten. - header('Location: index.php?route=login'); - exit; - } - - /** - * Fallback-Ausgabe für das Login-Formular, falls noch keine separate View-Datei existiert. - * Gibt direkt HTML aus (inline-Template). - */ - private function renderInlineLogin(?string $errorMessage): void - { - ?> - - - - - AD Admin Tool – Login - - -

AD Admin Tool – Login

- - - -

- - - -
-
- - -
- -
- - -
- - -
- - - - 'index.php?route=login', + ]; } } diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 089281e..64913db 100644 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -1,6 +1,4 @@ Vollständige Anwendungskonfiguration (aus config.php) */ private array $config; - - /** @var SnmpServerStatusService Service, der den Serverstatus (später per SNMP) liefert */ private SnmpServerStatusService $snmpService; - /** - * Übergibt die Konfiguration an den Controller und initialisiert den SNMP-Statusservice. - * - * @param array $config Vollständige Konfiguration aus config.php - */ public function __construct(array $config) { - // Komplette Config lokal speichern (falls später weitere Werte benötigt werden). $this->config = $config; - - // Teilbereich "snmp" aus der Konfiguration ziehen. - // Wenn nicht vorhanden, wird ein leeres Array übergeben (der Service prüft das selbst). $snmpConfig = $config['snmp'] ?? []; - - // SNMP-Service initialisieren, der den Serverstatus liefert. $this->snmpService = new SnmpServerStatusService($snmpConfig); } /** * Zeigt das Dashboard an. - * Holt die Serverstatus-Daten aus dem SnmpServerStatusService und übergibt sie an die View. + * + * Beim initialen Laden wird der Service aufgerufen, um sofort Daten anzuzeigen. + * Live-Updates erfolgen anschließend via JavaScript-Polling (api/snmp_status.php alle 5s). */ - public function show(): void + public function show(): array { - // Serverstatus über den SNMP-Service ermitteln. - // In der aktuellen Version liefert der Service noch Demo-Daten. - $serverStatus = $this->snmpService->getServerStatus(); + $serverStatus = [ + 'hostname' => 'n/a', + 'os' => 'n/a', + 'uptime' => 'n/a', + 'cpu_usage' => 0, + 'memory_usage' => 0, + 'disk_usage_c' => 0, + 'last_update' => date('d.m.Y H:i:s'), + ]; - // Pfad zur Dashboard-View (Template-Datei) ermitteln. - $viewPath = __DIR__ . '/../../public/views/dashboard.php'; - - // Falls die View-Datei (noch) nicht existiert, Fallback-HTML direkt aus dem Controller ausgeben. - if (file_exists($viewPath) === false) { - $this->renderInlineDashboard($serverStatus); - return; + try { + $serverStatus = $this->snmpService->getServerStatus(); + } catch (\RuntimeException $e) { + error_log('DashboardController: SNMP-Fehler beim initialen Laden - ' . $e->getMessage()); } - // View-Datei einbinden. Die Variable $serverStatus steht in der View zur Verfügung. - require $viewPath; - } - - /** - * Fallback-Dashboard-Ausgabe direkt aus dem Controller (ohne separate View-Datei). - * Nur als Notlösung gedacht, falls die eigentliche View noch nicht vorhanden ist. - * - * @param array $serverStatus Serverstatus-Daten (Hostname, CPU%, RAM%, etc.) - */ - private function renderInlineDashboard(array $serverStatus): void - { - ?> - - - - - AD Admin Tool – Dashboard - - -

Dashboard

- -

Serverstatus

-
    -
  • - Hostname: - -
  • -
  • - Betriebssystem: - -
  • -
  • - Uptime: - -
  • -
  • - CPU-Auslastung: - % -
  • -
  • - RAM-Auslastung: - % -
  • -
  • - Datenträger C: - % -
  • -
  • - Letzte Aktualisierung: - -
  • -
- -

Logout

- - - $viewPath, + 'data' => [ + 'serverStatus' => $serverStatus, + 'loginPage' => false, + ], + 'pageTitle' => 'Dashboard', + 'activeMenu' => 'dashboard', + ]; } } diff --git a/app/Controllers/UserManagementController.php b/app/Controllers/UserManagementController.php index 8b20a81..12a4efa 100644 --- a/app/Controllers/UserManagementController.php +++ b/app/Controllers/UserManagementController.php @@ -6,6 +6,8 @@ declare(strict_types=1); namespace App\Controllers; use App\Services\Ldap\LdapDirectoryService; +use App\Services\Logging\LoggingService; +use Throwable; /** * Controller für die Benutzer- und Gruppenanzeige. @@ -18,6 +20,9 @@ use App\Services\Ldap\LdapDirectoryService; * WICHTIG: * - Es werden aktuell nur Daten angezeigt (Read-only). * - Es findet keine Änderung im Active Directory statt. + * + * NEU: + * - Gibt ein View-Result-Array zurück, das von index.php + Layout gerendert wird. */ class UserManagementController { @@ -27,6 +32,9 @@ class UserManagementController /** @var LdapDirectoryService Service für das Lesen von Benutzern und Gruppen aus dem LDAP/AD */ private LdapDirectoryService $directoryService; + /** @var LoggingService Logger für technische Fehler */ + private LoggingService $logger; + /** * @param array $config Vollständige Konfiguration aus config.php */ @@ -40,13 +48,18 @@ class UserManagementController // Directory-Service initialisieren, der die eigentliche LDAP-Arbeit übernimmt. $this->directoryService = new LdapDirectoryService($ldapConfig); + + // Logging-Service initialisieren. + $this->logger = new LoggingService($config['logging'] ?? []); } /** * Zeigt Benutzer- und Gruppenliste an. * Wird typischerweise über die Route "users" (index.php?route=users) aufgerufen. + * + * @return array View-Result für das zentrale Layout */ - public function show(): void + public function show(): array { // Standardwerte für die View-Variablen vorbereiten. $error = null; @@ -57,97 +70,35 @@ class UserManagementController // Benutzer- und Gruppenlisten aus dem AD laden. $users = $this->directoryService->getUsers(); $groups = $this->directoryService->getGroups(); - } catch (\Throwable $exception) { - // Sämtliche technischen Fehler (z. B. Verbindungs- oder Konfigurationsprobleme) - // werden hier in eine für den Benutzer lesbare Fehlermeldung übersetzt. - $error = 'Fehler beim Laden von Benutzern/Gruppen: ' . $exception->getMessage(); + } catch (Throwable $exception) { + // Technische Details ins Log, für den Benutzer eine allgemeine Meldung. + $this->logger->logException( + 'Fehler beim Laden von Benutzern/Gruppen.', + $exception, + [ + 'route' => 'users', + 'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null, + ] + ); + + $error = 'Fehler beim Laden von Benutzern/Gruppen. ' + . 'Bitte versuchen Sie es später erneut oder wenden Sie sich an den Administrator.'; } // Pfad zur eigentlichen View-Datei bestimmen. $viewPath = __DIR__ . '/../../public/views/users.php'; - // Falls die View-Datei (noch) nicht existiert, Fallback-Ausgabe verwenden. - if (file_exists($viewPath) === false) { - $this->renderInline($users, $groups, $error); - return; - } - - // Variablen $users, $groups, $error stehen in der View zur Verfügung, - // weil sie im aktuellen Scope definiert sind. - require $viewPath; - } - - /** - * Fallback-Ausgabe, falls noch keine View-Datei existiert. - * - * @param array> $users Liste der Benutzer-Datensätze - * @param array> $groups Liste der Gruppen-Datensätze - * @param string|null $error Fehlermeldung (falls vorhanden) - */ - private function renderInline(array $users, array $groups, ?string $error): void - { - ?> - - - - - AD Admin Tool – Benutzer & Gruppen - - -

Benutzer & Gruppen

- - - -

- - -

Benutzer

- - - - - - - - - - - - - - - - - -
Benutzername (sAMAccountName)AnzeigenameE-Mail
- -

Gruppen

- - - - - - - - - - - - - - - - - -
Gruppenname (sAMAccountName)CNBeschreibung
- -

- Zurück zum Dashboard | - Logout -

- - - - $viewPath, + 'data' => [ + // Die View erwartet aktuell $users, $groups, $error. + 'users' => $users, + 'groups' => $groups, + 'error' => $error, + 'loginPage' => false, + ], + 'pageTitle' => 'Benutzer & Gruppen', + 'activeMenu' => 'users', + ]; } } diff --git a/app/Helpers/AuthenticatedUserHelper.php b/app/Helpers/AuthenticatedUserHelper.php deleted file mode 100644 index 5cd1222..0000000 --- a/app/Helpers/AuthenticatedUserHelper.php +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/app/Services/Logging/LoggingService.php b/app/Services/Logging/LoggingService.php new file mode 100644 index 0000000..9b9b556 --- /dev/null +++ b/app/Services/Logging/LoggingService.php @@ -0,0 +1,134 @@ + + */ + private const array LEVEL_MAP = [ + 'debug' => 100, + 'info' => 200, + 'warning' => 300, + 'error' => 400, + ]; + + /** + * @param array $config Teilkonfiguration "logging" aus config.php + */ + public function __construct(array $config) + { + // Standard: public/logs relativ zum Projektroot + $baseDir = $config['log_dir'] ?? (__DIR__ . '/../../../public/logs'); + $fileName = $config['log_file'] ?? 'app.log'; + $level = strtolower((string)($config['min_level'] ?? 'info')); + + $this->logDir = rtrim($baseDir, DIRECTORY_SEPARATOR); + $this->logFile = $fileName; + $this->minLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['info']; + + $this->ensureLogDirectoryExists(); + } + + /** + * Stellt sicher, dass das Log-Verzeichnis existiert. + */ + private function ensureLogDirectoryExists(): void + { + if (is_dir($this->logDir) === true) { + return; + } + + if (@mkdir($this->logDir, 0775, true) === false && is_dir($this->logDir) === false) { + // Wenn das Anlegen fehlschlägt, wenigstens einen Eintrag im PHP-Error-Log hinterlassen. + error_log(sprintf('LoggingService: Konnte Log-Verzeichnis "%s" nicht anlegen.', $this->logDir)); + } + } + + /** + * Allgemeiner Log-Eintrag. + * + * @param string $level Log-Level (debug|info|warning|error) + * @param string $message Nachrichtentext + * @param array $context Zusätzliche Kontextinformationen + */ + public function log(string $level, string $message, array $context = []): void + { + $level = strtolower($level); + $numericLevel = self::LEVEL_MAP[$level] ?? self::LEVEL_MAP['error']; + + // Alles unterhalb der minimalen Stufe ignorieren. + if ($numericLevel < $this->minLevel) { + return; + } + + $timestamp = new DateTimeImmutable()->format('Y-m-d H:i:s'); + + $contextJson = $context === [] + ? '{}' + : (string)json_encode($context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + $line = sprintf( + "[%s] %-7s %s %s%s", + $timestamp, + strtoupper($level), + $message, + $contextJson, + PHP_EOL + ); + + $filePath = $this->logDir . DIRECTORY_SEPARATOR . $this->logFile; + + if (@file_put_contents($filePath, $line, FILE_APPEND | LOCK_EX) === false) { + // Fallback, damit Fehler beim Logging selbst nicht die App zerschießen. + error_log(sprintf('LoggingService: Konnte in Log-Datei "%s" nicht schreiben.', $filePath)); + } + } + + /** + * Komfortmethode, um Exceptions strukturiert zu loggen. + * + * @param string $message Kurzer Kontexttext zur Exception + * @param Throwable $exception Die geworfene Exception + * @param array $context Zusätzlicher Kontext (Route, Benutzername, Remote-IP, ...) + */ + public function logException(string $message, Throwable $exception, array $context = []): void + { + $exceptionContext = [ + 'exception_class' => get_class($exception), + 'exception_message' => $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => $exception->getTraceAsString(), + ]; + + $mergedContext = array_merge($context, $exceptionContext); + + $this->log('error', $message, $mergedContext); + } +} diff --git a/app/Services/Snmp/SnmpServerStatusService.php b/app/Services/Snmp/SnmpServerStatusService.php index 56ae54a..b75907e 100644 --- a/app/Services/Snmp/SnmpServerStatusService.php +++ b/app/Services/Snmp/SnmpServerStatusService.php @@ -1,6 +1,5 @@ SNMP-spezifische Konfiguration (Host, Community, Timeout, OIDs, etc.) */ + /** @var array SNMP-Konfiguration (Host, Community, Timeout, OIDs, etc.) */ private array $config; /** @@ -26,9 +30,9 @@ class SnmpServerStatusService */ public function __construct(array $snmpConfig) { - // SNMP-Konfiguration in der Instanz speichern. $this->config = $snmpConfig; } + /** * Liefert den aktuellen Serverstatus zurück. * @@ -55,108 +59,168 @@ class SnmpServerStatusService if (empty($oids)) { throw new RuntimeException('SNMP-Konfiguration ist unvollständig (oids fehlen).'); } - - // Helper-Funktion zum Bereinigen von SNMP-Antworten (z.B. "INTEGER: 123" -> 123) + + if (!function_exists('snmpget')) { + throw new RuntimeException('PHP-SNMP-Erweiterung ist nicht installiert.'); + } + + // Hilfsfunktion: SNMP-Werte bereinigen (z.B. "INTEGER: 123" -> 123) $cleanSnmpValue = fn($v) => (int)filter_var($v, FILTER_SANITIZE_NUMBER_INT); - // --- 2. Uptime abfragen (war vorher nicht implementiert) --- - $uptimeResult = snmpget($host, $community, $oids['uptime'], $timeout, $retries); + // --- 2. Uptime abfragen --- + $uptimeOid = $oids['uptime'] ?? '1.3.6.1.2.1.1.3.0'; + $uptimeResult = @snmpget($host, $community, $uptimeOid, $timeout, $retries); if ($uptimeResult === false) { throw new RuntimeException("SNMP Uptime GET fehlgeschlagen."); } - // Uptime (timeticks) in ein lesbares Format umwandeln (optional, hier als String) - // Format ist oft "Timeticks: (12345678) 1 day, 10:17:36.78" - // Wir extrahieren den Teil in Klammern (Hundertstelsekunden) + + // Uptime aus TimeTicks (Hundertstel-Sekunden) in lesbar konvertieren preg_match('/\((.*?)\)/', $uptimeResult, $matches); $uptimeTicks = (int)($matches[1] ?? 0); $uptimeSeconds = $uptimeTicks / 100; $uptimeFormatted = sprintf( '%d Tage, %02d:%02d:%02d', - floor($uptimeSeconds / 86400), - floor(($uptimeSeconds % 86400) / 3600), - floor(($uptimeSeconds % 3600) / 60), - $uptimeSeconds % 60 + (int)floor($uptimeSeconds / 86400), + (int)floor(($uptimeSeconds % 86400) / 3600), + (int)floor(($uptimeSeconds % 3600) / 60), + (int)($uptimeSeconds % 60) ); - - // --- 3. CPU --- - $cpuValues = snmpwalk($host, $community, $oids['cpu_table'], $timeout, $retries); + // --- 3. CPU (Durchschnitt über alle Kerne) --- + $cpuTable = $oids['cpu_table'] ?? '1.3.6.1.2.1.25.3.3.1.2'; + $cpuValues = @snmpwalk($host, $community, $cpuTable, $timeout, $retries); if (!is_array($cpuValues) || empty($cpuValues)) { throw new RuntimeException("SNMP CPU WALK fehlgeschlagen."); } $cpuValues = array_map($cleanSnmpValue, $cpuValues); - $cpuAvg = array_sum($cpuValues) / count($cpuValues); + $cpuAvg = (int)round(array_sum($cpuValues) / count($cpuValues)); + // --- 4. Storage-Tabellen (RAM + Disks) --- + $descrOid = $oids['storage_descr'] ?? '1.3.6.1.2.1.25.2.3.1.3'; + $unitsOid = $oids['storage_units'] ?? '1.3.6.1.2.1.25.2.3.1.4'; + $sizeOid = $oids['storage_size'] ?? '1.3.6.1.2.1.25.2.3.1.5'; + $usedOid = $oids['storage_used'] ?? '1.3.6.1.2.1.25.2.3.1.6'; - // --- 4. Memory --- - $memTotalResult = snmpget($host, $community, $oids['mem_size'], $timeout, $retries); - if($memTotalResult === false) { - throw new RuntimeException("SNMP MemTotal GET fehlgeschlagen."); - } - - // memTotal in Bytes berechnen (korrigierte Reihenfolge von filter/cast) - $memTotal = $cleanSnmpValue($memTotalResult) * 1024; // KB -> Bytes + $descr = @snmpwalk($host, $community, $descrOid, $timeout, $retries); + $units = @snmpwalk($host, $community, $unitsOid, $timeout, $retries); + $size = @snmpwalk($host, $community, $sizeOid, $timeout, $retries); + $used = @snmpwalk($host, $community, $usedOid, $timeout, $retries); - // Storage-Tabelle (RAM + Disks) - $descr = snmpwalk($host, $community, $oids['storage_descr'], $timeout, $retries); - $units = snmpwalk($host, $community, $oids['storage_units'], $timeout, $retries); - $size = snmpwalk($host, $community, $oids['storage_size'], $timeout, $retries); - $used = snmpwalk($host, $community, $oids['storage_used'], $timeout, $retries); - - if ($descr === false || $units === false || $size === false || $used === false) { + if (!is_array($descr) || !is_array($units) || !is_array($size) || !is_array($used)) { throw new RuntimeException("SNMP Storage WALK fehlgeschlagen."); } - + // Werte bereinigen - // Die SNMP-Antwort enthält "STRING: " und Anführungszeichen, die wir entfernen müssen. $descr = array_map(fn($v) => trim(str_ireplace('STRING:', '', $v), ' "'), $descr); - - $units = array_map($cleanSnmpValue, $units); // Ints - $size = array_map($cleanSnmpValue, $size); // Ints - $used = array_map($cleanSnmpValue, $used); // Ints + $units = array_map($cleanSnmpValue, $units); + $size = array_map($cleanSnmpValue, $size); + $used = array_map($cleanSnmpValue, $used); - + // --- 5. RAM mit Fallback-Logik --- + $ramPercent = null; + $memTotalBytes = null; - // RAM + // Heuristik 1: Suche nach "Physical Memory" $ramIndex = array_search("Physical Memory", $descr); - if ($ramIndex === false) { + if ($ramIndex !== false) { + $memTotalBytes = $units[$ramIndex] * $size[$ramIndex]; + $ramUsedBytes = $units[$ramIndex] * $used[$ramIndex]; + $ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0; + } + + // Fallback 1: Wenn nicht gefunden, suche nach ähnlichen Labels + if ($ramPercent === null) { + foreach ($descr as $index => $description) { + $lower = strtolower($description); + if (strpos($lower, 'physical') !== false || strpos($lower, 'memory') !== false || strpos($lower, 'ram') !== false) { + $memTotalBytes = $units[$index] * $size[$index]; + $ramUsedBytes = $units[$index] * $used[$index]; + $ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0; + break; + } + } + } + + // Fallback 2: Wenn immer noch nicht gefunden, nimm den kleinsten Eintrag (i.d.R. RAM) + if ($ramPercent === null && count($descr) > 0) { + $minIndex = 0; + $minSize = PHP_INT_MAX; + foreach ($size as $index => $s) { + if ($s > 0 && $s < $minSize) { + $minSize = $s; + $minIndex = $index; + } + } + $memTotalBytes = $units[$minIndex] * $size[$minIndex]; + $ramUsedBytes = $units[$minIndex] * $used[$minIndex]; + $ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0; + } + + // Fallback 3: Wenn gar nichts geht, Exception + if ($ramPercent === null) { throw new RuntimeException("Konnte 'Physical Memory' in der SNMP Storage-Tabelle nicht finden."); } - - $ramUsedBytes = $units[$ramIndex] * $used[$ramIndex]; - $ramPercent = ($memTotal > 0) ? ($ramUsedBytes / $memTotal) * 100 : 0; + // --- 6. Disk C: / Root mit Fallback-Logik --- + $diskCPercent = null; - // --- 5. Disk C: --- - $cIndex = false; + // Heuristik 1: Suche nach C:\ foreach ($descr as $index => $description) { - // str_starts_with prüft, ob der String mit C:\ beginnt if (str_starts_with($description, 'C:\\')) { - $cIndex = $index; + $cTotal = $units[$index] * $size[$index]; + $cUsed = $units[$index] * $used[$index]; + $diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0; break; } } - if ($cIndex === false) { - throw new RuntimeException("Konnte Laufwerk 'C:\' in der SNMP Storage-Tabelle nicht finden."); + // Fallback 1: Suche nach "C:" oder "root" oder "/" + if ($diskCPercent === null) { + foreach ($descr as $index => $description) { + $lower = strtolower($description); + if (strpos($lower, 'c:') !== false || $lower === '/' || strpos($lower, 'root') !== false) { + $cTotal = $units[$index] * $size[$index]; + $cUsed = $units[$index] * $used[$index]; + $diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0; + break; + } + } } - $cUsed = $units[$cIndex] * $used[$cIndex]; - $cTotal = $units[$cIndex] * $size[$cIndex]; + // Fallback 2: Nimm den größten Eintrag > 1GB (wahrscheinlich der Hauptdatenträger) + if ($diskCPercent === null) { + $maxIndex = 0; + $maxSize = 0; + foreach ($size as $index => $s) { + $sizeGB = ($s * $units[$index]) / (1024 ** 3); + if ($sizeGB > 1 && $s > $maxSize) { + $maxSize = $s; + $maxIndex = $index; + } + } + if ($maxSize > 0) { + $cTotal = $units[$maxIndex] * $size[$maxIndex]; + $cUsed = $units[$maxIndex] * $used[$maxIndex]; + $diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0; + } + } - $diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0; + // Fallback 3: Wenn immer noch nichts, Exception + if ($diskCPercent === null) { + throw new RuntimeException("Konnte Laufwerk 'C:\\' oder Root-Partition in der SNMP Storage-Tabelle nicht finden."); + } - // --- 6. Status-Array zusammenbauen --- + // --- 7. Status-Array zusammenbauen --- $status = [ - 'hostname' => $host, - 'os' => 'Windows Server 2025 Datacenter', // TODO: OS dynamisch abfragen - 'uptime' => $uptimeFormatted, // Fehlende Variable hinzugefügt - 'cpu_usage' => round($cpuAvg), - 'memory_usage' => round($ramPercent), - 'disk_usage_c' => round($diskCPercent), - 'last_update' => date('d.m.Y H:i:s'), + 'hostname' => $host, + 'os' => 'Windows Server', // TODO: OS dynamisch per SNMP abfragen (OID 1.3.6.1.2.1.1.1.0) + 'uptime' => $uptimeFormatted, + 'cpu_usage' => $cpuAvg, + 'memory_usage' => (int)round($ramPercent), + 'disk_usage_c' => (int)round($diskCPercent), + 'last_update' => date('d.m.Y H:i:s'), ]; return $status; diff --git a/config/config.php b/config/config.php index a84872e..aebf7fe 100644 --- a/config/config.php +++ b/config/config.php @@ -49,4 +49,14 @@ return [ 'storage_used' => '1.3.6.1.2.1.25.2.3.1.6', ], ], + + // Logging-Konfiguration + 'logging' => [ + // Standard: public/logs relativ zum Projekt-Root + 'log_dir' => __DIR__ . '/../public/logs', + // Name der Logdatei + 'log_file' => 'app.log', + // Minimale Stufe: debug, info, warning, error + 'min_level' => 'info', + ], ]; diff --git a/public/404.php b/public/404.php deleted file mode 100644 index abe4d85..0000000 --- a/public/404.php +++ /dev/null @@ -1,430 +0,0 @@ - - - - - - - - - - - - SB Admin 2 - 404 - - - - - - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/api/snmp_status.php b/public/api/snmp_status.php new file mode 100644 index 0000000..4d26b23 --- /dev/null +++ b/public/api/snmp_status.php @@ -0,0 +1,292 @@ + '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); +} + +function rotate_log_if_needed(): void { + global $logFile; + $maxSize = 500 * 1024; // 500 KB (anpassbar) + + if (file_exists($logFile) && filesize($logFile) > $maxSize) { + $backupFile = $logFile . '.old'; + @rename($logFile, $backupFile); + log_msg('=== Log rotiert (alte Datei: .old) ==='); + } +} + + +$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'] ?? []; + +rotate_log_if_needed(); + +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; diff --git a/public/blank.php b/public/blank.php deleted file mode 100644 index 4f45641..0000000 --- a/public/blank.php +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Blank - - - - - - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/buttons.php b/public/buttons.php deleted file mode 100644 index 414dfef..0000000 --- a/public/buttons.php +++ /dev/null @@ -1,604 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Buttons - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Buttons

- -
- -
- - -
-
-
Circle Buttons
-
-
-

Use Font Awesome Icons (included with this theme package) along with the circle - buttons as shown in the examples below!

- -
- .btn-circle -
- - - - - - - - - - - - - - - - -
- .btn-circle .btn-sm -
- - - - - - - - - - - - - - - - -
- .btn-circle .btn-lg -
- - - - - - - - - - - - - - - -
-
- - -
-
-
Brand Buttons
-
-
-

Google and Facebook buttons are available featuring each company's respective - brand color. They are used on the user login and registration pages.

-

You can create more custom buttons by adding a new color variable in the - _variables.scss file and then using the Bootstrap button variant - mixin to create a new style, as demonstrated in the _buttons.scss - file.

- - .btn-google - .btn-facebook - -
-
- -
- -
- -
-
-
Split Buttons with Icon
-
-
-

Works with any button colors, just use the .btn-icon-split class and - the markup in the examples below. The examples below also use the - .text-white-50 helper class on the icons for additional styling, - but it is not required.

- - - - - Split Button Primary - -
- - - - - Split Button Success - -
- - - - - Split Button Info - -
- - - - - Split Button Warning - -
- - - - - Split Button Danger - -
- - - - - Split Button Secondary - -
- - - - - Split Button Light - -
-

Also works with small and large button classes!

- - - - - Split Button Small - -
- - - - - Split Button Large - -
-
- -
- -
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/cards.php b/public/cards.php deleted file mode 100644 index 493926c..0000000 --- a/public/cards.php +++ /dev/null @@ -1,598 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Cards - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -
-

Cards

-
- -
- - -
-
-
-
-
-
- Earnings (Monthly)
-
$40,000
-
-
- -
-
-
-
-
- - -
-
-
-
-
-
- Earnings (Annual)
-
$215,000
-
-
- -
-
-
-
-
- - -
-
-
-
-
-
Tasks -
-
-
-
50%
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
- - -
-
-
-
-
-
- Pending Requests
-
18
-
-
- -
-
-
-
-
-
- -
- -
- - -
-
- Default Card Example -
-
- This card uses Bootstrap's default styling with no utility classes added. Global - styles are the only things modifying the look and feel of this default card example. -
-
- - -
-
-
Basic Card Example
-
-
- The styling for this basic card example is created by using default Bootstrap - utility classes. By using utility classes, the style of the card component can be - easily modified with no need for any custom CSS! -
-
- -
- -
- - -
- -
-
Dropdown Card Example
- -
- -
- Dropdown menus can be placed in the card header in order to extend the functionality - of a basic card. In this dropdown card example, the Font Awesome vertical ellipsis - icon in the card header can be clicked on in order to toggle a dropdown menu. -
-
- - -
- - -
Collapsable Card Example
-
- -
-
- This is a collapsable card example using Bootstrap's built in collapse - functionality. Click on the card header to see the card body - collapse and expand! -
-
-
- -
- -
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/charts.php b/public/charts.php deleted file mode 100644 index 9631749..0000000 --- a/public/charts.php +++ /dev/null @@ -1,497 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Charts - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Charts

-

Chart.js is a third party plugin that is used to generate the charts in this theme. - The charts below have been customized - for further customization options, please visit the official Chart.js - documentation.

- - -
- -
- - -
-
-
Area Chart
-
-
-
- -
-
- Styling for the area chart can be found in the - /js/demo/chart-area-demo.js file. -
-
- - -
-
-
Bar Chart
-
-
-
- -
-
- Styling for the bar chart can be found in the - /js/demo/chart-bar-demo.js file. -
-
- -
- - -
-
- -
-
Donut Chart
-
- -
-
- -
-
- Styling for the donut chart can be found in the - /js/demo/chart-pie-demo.js file. -
-
-
-
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/forgot-password.php b/public/forgot-password.php deleted file mode 100644 index a8d8171..0000000 --- a/public/forgot-password.php +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - SB Admin 2 - Forgot Password - - - - - - - - - - - - -
- - -
- -
- -
-
- -
-
-
-
-
-

Forgot Your Password?

-

We get it, stuff happens. Just enter your email address below - and we'll send you a link to reset your password!

-
-
-
- -
- - Reset Password - -
-
- - -
-
-
-
-
- -
- -
- -
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/index.php b/public/index.php index f84856c..db133fd 100644 --- a/public/index.php +++ b/public/index.php @@ -3,9 +3,30 @@ // Strenge Typprüfung für Parameter- und Rückgabetypen aktivieren. declare(strict_types=1); +/** + * Front-Controller der Webanwendung. + * + * Aufgaben: + * - Startet die Session und initialisiert den Autoloader für den App-Namespace. + * - Lädt die zentrale Konfiguration aus config/config.php. + * - Instanziiert die Hauptcontroller (Auth, Dashboard, User-Management). + * - Steuert das Routing anhand des GET-Parameters "route". + * - Leitet View-Result-Arrays an das zentrale Layout (renderLayout) weiter + * oder führt Redirects aus. + * + * WICHTIG: + * - Diese Datei ist der einzige öffentliche Einstiegspunkt (Entry Point) der App. + * - Alle neuen Routen sollten über den Switch-Block am Ende ergänzt werden. + */ + + // Eine neue Session wird gestartet und die entsprechende Variable ($_SESSION) angelegt oder eine bestehende wird fortgesetzt. session_start(); +// PHP-Fehler nur ins Log schreiben, nicht direkt im Browser anzeigen +error_reporting(E_ALL); +ini_set('display_errors', '0'); + /* * Registriert eine Autoload-Funktion für Klassen mit dem Namespace-Präfix "App\". * Statt jede Klasse manuell über "require pfad_zur_klasse.php" einzubinden, @@ -13,9 +34,8 @@ session_start(); * im Namespace "App\..." verwendet wird. */ spl_autoload_register( - // Anonyme Funktion, die den Klassennamen prüft und den Dateipfad zur Klasse ermittelt. static function (string $class): void { - $prefix = 'App\\'; + $prefix = 'App\\'; $baseDir = __DIR__ . '/../app/'; $len = strlen($prefix); @@ -24,7 +44,7 @@ spl_autoload_register( } $relativeClass = substr($class, $len); - $file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php'; + $file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php'; if (file_exists($file) === true) { require $file; @@ -32,7 +52,11 @@ spl_autoload_register( } ); -// Pfad zur Konfigurationsdatei prüfen und Konfiguration laden +require __DIR__ . '/views/layout.php'; + +// Die Konfigurationsdatei liefert ein assoziatives Array mit den Teilbereichen +// "ldap", "snmp" und "security" (u. a. Session-Keys, Timeout-Einstellungen, OIDs). + $configPath = __DIR__ . '/../config/config.php'; if (file_exists($configPath) === false) { // Fail fast: ohne Konfiguration macht die App keinen Sinn @@ -48,8 +72,68 @@ $config = require $configPath; use App\Controllers\AuthController; use App\Controllers\DashboardController; use App\Controllers\UserManagementController; +use App\Services\Logging\LoggingService; -// Hilfsfunktion für geschützte Routen +// Globalen Logger initialisieren, damit auch Fehler außerhalb der Controller +// (z. B. in index.php selbst) sauber protokolliert werden. +$globalLogger = new LoggingService($config['logging'] ?? []); + +/** + * Globale Fehlerbehandlung: + * - PHP-Fehler (Warnings, Notices, ...) werden in den Logger geschrieben. + * - Unbehandelte Exceptions werden ebenfalls geloggt und führen zu einer generischen 500er-Meldung. + */ +set_error_handler( + static function ( + int $severity, + string $message, + string $file = '', + int $line = 0 + ) use ($globalLogger): bool { + // Fehler nur loggen, wenn sie durch error_reporting() nicht unterdrückt sind. + if ((error_reporting() & $severity) === 0) { + return false; + } + + $globalLogger->log( + 'error', + 'PHP-Fehler: ' . $message, + [ + 'severity' => $severity, + 'file' => $file, + 'line' => $line, + ] + ); + + // false zurückgeben = PHP darf seinen Standard-Handler zusätzlich verwenden + // (der Browser sieht wegen display_errors=0 trotzdem nichts). + return false; + } +); + +set_exception_handler( + static function (Throwable $exception) use ($globalLogger): void { + $globalLogger->logException( + 'Unbehandelte Exception in der Anwendung.', + $exception, + [ + 'request_uri' => $_SERVER['REQUEST_URI'] ?? null, + 'route' => $_GET['route'] ?? null, + ] + ); + + http_response_code(500); + echo 'Es ist ein unerwarteter Fehler aufgetreten. ' + . 'Bitte versuchen Sie es später erneut oder wenden Sie sich an den Administrator.'; + } +); + +/** + * Hilfsfunktion: Prüft, ob ein Benutzer eingeloggt ist. + * Wenn nicht, wird auf die Login-Seite umgeleitet. + * + * @param array $config + */ function requireLogin(array $config): void { // session_key_user aus dem Config-Array lesen. Wenn nicht gesetzt oder null, Standard "admin_user" verwenden. @@ -63,44 +147,89 @@ function requireLogin(array $config): void } } -// Route aus dem GET-Parameter lesen. Wenn nicht gesetzt, Standardroute "login" verwenden. -$route = $_GET['route'] ?? 'login'; +/** + * Verarbeitet ein View-Result oder Redirect-Result. + * + * @param array|null $result + */ +function handleResult(?array $result): void +{ + if ($result === null) { + return; + } + + if (isset($result['redirect']) === true) { + header('Location: ' . $result['redirect']); + exit; + } + + $contentView = (string)($result['view'] ?? ''); + $viewData = (array)($result['data'] ?? []); + // Standard: Wir gehen davon aus, dass es KEINE Loginseite ist, + // außer der Controller sagt explizit etwas anderes. + if (!array_key_exists('loginPage', $viewData)) { + $viewData['loginPage'] = false; + } + $pageTitle = (string)($result['pageTitle'] ?? ''); + $activeMenu = $result['activeMenu'] ?? null; + + if ($contentView === '' || file_exists($contentView) === false) { + http_response_code(500); + echo 'Interner Fehler: Content-View wurde nicht gefunden.'; + exit; + } + + // Hier rufen wir jetzt die Layout-Funktion aus layout.php auf + renderLayout($contentView, $viewData, $pageTitle, is_string($activeMenu) ? $activeMenu : null); +} + +// Zentrale Controller der Anwendung initialisieren und ihnen die vollständige Konfiguration übergeben. +// Die Controller holen sich daraus bei Bedarf ihre spezifischen Teilkonfigurationen (z. B. "ldap" oder "snmp"). -// Neue Instanz der Klasse AuthController erstellen (wird bei Bedarf über den Autoloader geladen). $authController = new AuthController($config); - -// Neue Instanz der Klasse DashboardController erstellen (wird bei Bedarf über den Autoloader geladen). $dashboardController = new DashboardController($config); - -// Neue Instanz der Klasse UserManagmentController erstellen (wird bei Bedarf über den Autoloader geladen). $userManagementController = new UserManagementController($config); -// Anhand des Routing-Ziels (route) entscheiden, welcher Code ausgeführt wird. +// Route aus dem Query-Parameter lesen. Standardroute ist "login", +// sodass nicht angemeldete Benutzer automatisch auf die Login-Seite geführt werden. + +$route = $_GET['route'] ?? 'login'; + +// Einfache Router-Logik: Jede Route ruft eine Controller-Methode auf und +// übergibt deren View-Result an handleResult(). Neue Seiten werden hier ergänzt. + switch ($route) { case 'login': - $authController->showLoginForm(); - break; + // Login-Formular anzeigen (ggf. mit Fehlermeldung) + $result = $authController->showLoginForm(); + handleResult($result); + break; case 'login.submit': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + // Falscher HTTP-Verb: einfach zurück zum Login header('Location: index.php?route=login'); exit; } - $authController->processLogin(); + $result = $authController->processLogin(); + handleResult($result); break; case 'logout': - $authController->logout(); + $result = $authController->logout(); + handleResult($result); break; case 'dashboard': requireLogin($config); - $dashboardController->show(); + $result = $dashboardController->show(); + handleResult($result); break; case 'users': requireLogin($config); - $userManagementController->show(); + $result = $userManagementController->show(); + handleResult($result); break; default: @@ -108,3 +237,5 @@ switch ($route) { echo 'Route nicht gefunden.'; break; } + + diff --git a/public/login.php b/public/login.php deleted file mode 100644 index 9e39ad9..0000000 --- a/public/login.php +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - SB Admin 2 - Login - - - - - - - - - - - - -
- - -
- -
- -
-
- -
- -
-
-
-

Welcome Back!

-
- - - -
-
- -
-
- -
-
-
- - -
-
- -
-
- - -
-
-
-
-
- -
- -
- -
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/register.php b/public/register.php deleted file mode 100644 index 12ff6a8..0000000 --- a/public/register.php +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - SB Admin 2 - Register - - - - - - - - - - - - -
- -
-
- -
-
-
-
-
-

Create an Account!

-
-
-
-
- -
-
- -
-
-
- -
-
-
- -
-
- -
-
- - Register Account - -
- - Register with Google - - - Register with Facebook - -
-
- - -
-
-
-
-
- -
- - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/snmptest.php b/public/snmptest.php deleted file mode 100644 index 38185a6..0000000 --- a/public/snmptest.php +++ /dev/null @@ -1,49 +0,0 @@ - -

Admin Dashboard

-

Angemeldet als:

- -

Server-Status (SNMP)

-

CPU-Last (Kern 1): %

-

System Uptime:

-

RAM-Auslastung: - -

- -
-

Einzelnen Benutzer erstellen

-

Benutzer per CSV importieren

\ No newline at end of file diff --git a/public/tables.php b/public/tables.php deleted file mode 100644 index 0b7155c..0000000 --- a/public/tables.php +++ /dev/null @@ -1,934 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Tables - - - - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Tables

-

DataTables is a third party plugin that is used to generate the demo table below. - For more information about DataTables, please visit the official DataTables documentation.

- - -
-
-
DataTables Example
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NamePositionOfficeAgeStart dateSalary
NamePositionOfficeAgeStart dateSalary
Tiger NixonSystem ArchitectEdinburgh612011/04/25$320,800
Garrett WintersAccountantTokyo632011/07/25$170,750
Ashton CoxJunior Technical AuthorSan Francisco662009/01/12$86,000
Cedric KellySenior Javascript DeveloperEdinburgh222012/03/29$433,060
Airi SatouAccountantTokyo332008/11/28$162,700
Brielle WilliamsonIntegration SpecialistNew York612012/12/02$372,000
Herrod ChandlerSales AssistantSan Francisco592012/08/06$137,500
Rhona DavidsonIntegration SpecialistTokyo552010/10/14$327,900
Colleen HurstJavascript DeveloperSan Francisco392009/09/15$205,500
Sonya FrostSoftware EngineerEdinburgh232008/12/13$103,600
Jena GainesOffice ManagerLondon302008/12/19$90,560
Quinn FlynnSupport LeadEdinburgh222013/03/03$342,000
Charde MarshallRegional DirectorSan Francisco362008/10/16$470,600
Haley KennedySenior Marketing DesignerLondon432012/12/18$313,500
Tatyana FitzpatrickRegional DirectorLondon192010/03/17$385,750
Michael SilvaMarketing DesignerLondon662012/11/27$198,500
Paul ByrdChief Financial Officer (CFO)New York642010/06/09$725,000
Gloria LittleSystems AdministratorNew York592009/04/10$237,500
Bradley GreerSoftware EngineerLondon412012/10/13$132,000
Dai RiosPersonnel LeadEdinburgh352012/09/26$217,500
Jenette CaldwellDevelopment LeadNew York302011/09/03$345,000
Yuri BerryChief Marketing Officer (CMO)New York402009/06/25$675,000
Caesar VancePre-Sales SupportNew York212011/12/12$106,450
Doris WilderSales AssistantSidney232010/09/20$85,600
Angelica RamosChief Executive Officer (CEO)London472009/10/09$1,200,000
Gavin JoyceDeveloperEdinburgh422010/12/22$92,575
Jennifer ChangRegional DirectorSingapore282010/11/14$357,650
Brenden WagnerSoftware EngineerSan Francisco282011/06/07$206,850
Fiona GreenChief Operating Officer (COO)San Francisco482010/03/11$850,000
Shou ItouRegional MarketingTokyo202011/08/14$163,000
Michelle HouseIntegration SpecialistSidney372011/06/02$95,400
Suki BurksDeveloperLondon532009/10/22$114,500
Prescott BartlettTechnical AuthorLondon272011/05/07$145,000
Gavin CortezTeam LeaderSan Francisco222008/10/26$235,500
Martena MccrayPost-Sales supportEdinburgh462011/03/09$324,050
Unity ButlerMarketing DesignerSan Francisco472009/12/09$85,675
Howard HatfieldOffice ManagerSan Francisco512008/12/16$164,500
Hope FuentesSecretarySan Francisco412010/02/12$109,850
Vivian HarrellFinancial ControllerSan Francisco622009/02/14$452,500
Timothy MooneyOffice ManagerLondon372008/12/11$136,200
Jackson BradshawDirectorNew York652008/09/26$645,750
Olivia LiangSupport EngineerSingapore642011/02/03$234,500
Bruno NashSoftware EngineerLondon382011/05/03$163,500
Sakura YamamotoSupport EngineerTokyo372009/08/19$139,575
Thor WaltonDeveloperNew York612013/08/11$98,540
Finn CamachoSupport EngineerSan Francisco472009/07/07$87,500
Serge BaldwinData CoordinatorSingapore642012/04/09$138,575
Zenaida FrankSoftware EngineerNew York632010/01/04$125,250
Zorita SerranoSoftware EngineerSan Francisco562012/06/01$115,000
Jennifer AcostaJunior Javascript DeveloperEdinburgh432013/02/01$75,650
Cara StevensSales AssistantNew York462011/12/06$145,600
Hermione ButlerRegional DirectorLondon472011/03/21$356,250
Lael GreerSystems AdministratorLondon212009/02/27$103,500
Jonas AlexanderDeveloperSan Francisco302010/07/14$86,500
Shad DeckerRegional DirectorEdinburgh512008/11/13$183,000
Michael BruceJavascript DeveloperSingapore292011/06/27$183,000
Donna SniderCustomer SupportNew York272011/01/25$112,000
-
-
-
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/utilities-animation.php b/public/utilities-animation.php deleted file mode 100644 index 5d2b018..0000000 --- a/public/utilities-animation.php +++ /dev/null @@ -1,529 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Animation Utilities - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Animation Utilities

-

Bootstrap's default utility classes can be found on the official Bootstrap Documentation page. The custom utilities - below were created to extend this theme past the default utility classes built into Bootstrap's - framework.

- - -
- - -
- -
-
-
Grow In Animation Utilty
-
-
-
- .animated--grow-in -
-
Navbar Dropdown Example:
- -

Note: This utility animates the CSS transform property, - meaning it will override any existing transforms on an element being animated! - In this theme, the grow in animation is only being used on dropdowns within the - navbar.

-
-
- -
- - -
- -
-
-
Fade In Animation Utilty
-
-
-
- .animated--fade-in -
-
Navbar Dropdown Example:
- -
Dropdown Button Example:
- -

Note: This utility animates the CSS opacity property, meaning - it will override any existing opacity on an element being animated!

-
-
- -
- -
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/utilities-border.php b/public/utilities-border.php deleted file mode 100644 index 5ff0e75..0000000 --- a/public/utilities-border.php +++ /dev/null @@ -1,531 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Border Utilities - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Border Utilities

-

Bootstrap's default utility classes can be found on the official Bootstrap Documentation page. The custom utilities - below were created to extend this theme past the default utility classes built into Bootstrap's - framework.

- - -
- - -
- -
-
- .border-left-primary -
-
- -
-
- .border-left-secondary -
-
- -
-
- .border-left-success -
-
- -
-
- .border-left-info -
-
- -
-
- .border-left-warning -
-
- -
-
- .border-left-danger -
-
- -
-
- .border-left-dark -
-
- -
- - -
- -
-
- .border-bottom-primary -
-
- -
-
- .border-bottom-secondary -
-
- -
-
- .border-bottom-success -
-
- -
-
- .border-bottom-info -
-
- -
-
- .border-bottom-warning -
-
- -
-
- .border-bottom-danger -
-
- -
-
- .border-bottom-dark -
-
- -
- -
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/utilities-color.php b/public/utilities-color.php deleted file mode 100644 index 7241d01..0000000 --- a/public/utilities-color.php +++ /dev/null @@ -1,517 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Color Utilities - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Color Utilities

-

Bootstrap's default utility classes can be found on the official Bootstrap Documentation page. The custom utilities - below were created to extend this theme past the default utility classes built into Bootstrap's - framework.

- - -
- - -
- - -
-
-
Custom Text Color Utilities
-
-
-

.text-gray-100

-

.text-gray-200

-

.text-gray-300

-

.text-gray-400

-

.text-gray-500

-

.text-gray-600

-

.text-gray-700

-

.text-gray-800

-

.text-gray-900

-
-
- - -
-
-
Custom Font Size Utilities
-
-
-

.text-xs

-

.text-lg

-
-
- -
- - -
- - -
-
-
Custom Background Gradient Utilities -
-
-
-
.bg-gradient-primary
-
.bg-gradient-secondary
-
.bg-gradient-success
-
.bg-gradient-info
-
.bg-gradient-warning
-
.bg-gradient-danger
-
.bg-gradient-light
-
.bg-gradient-dark
-
-
- -
- - -
- - -
-
-
Custom Grayscale Background Utilities -
-
-
-
.bg-gray-100
-
.bg-gray-200
-
.bg-gray-300
-
.bg-gray-400
-
.bg-gray-500
-
.bg-gray-600
-
.bg-gray-700
-
.bg-gray-800
-
.bg-gray-900
-
-
-
- -
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/utilities-other.php b/public/utilities-other.php deleted file mode 100644 index d13661f..0000000 --- a/public/utilities-other.php +++ /dev/null @@ -1,511 +0,0 @@ - - - - - - - - - - - - - SB Admin 2 - Other Utilities - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Other Utilities

-

Bootstrap's default utility classes can be found on the official Bootstrap Documentation page. The custom utilities - below were created to extend this theme past the default utility classes built into Bootstrap's - framework.

- - -
- -
- - -
-
-
Overflow Hidden Utilty
-
-
- Use .o-hidden to set the overflow property of any element to hidden. -
-
- - -
-
-
Progress Small Utility
-
-
-
Normal Progress Bar
-
-
-
-
Small Progress Bar
-
-
-
- Use the .progress-sm class along with .progress -
-
- - -
-
-
Dropdown - No Arrow
-
-
- - Add the .no-arrow class alongside the .dropdown -
-
- -
- -
- - -
-
-
Rotation Utilities
-
-
-
.rotate-15 -
-
-
.rotate-n-15 -
-
-
- -
- -
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/views/dashboard.php b/public/views/dashboard.php index 3164c36..d2c2026 100644 --- a/public/views/dashboard.php +++ b/public/views/dashboard.php @@ -1,792 +1,185 @@ $serverStatus */ ?> - - - + +
+

Server-Dashboard

+
- - - - - - - SB Admin 2 - Dashboard - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -
-

Dashboard

- Generate Report -
- - -
- - -
-
-
-
-
-
CPU -
-
-
-
%
-
-
-
-
aria-valuenow= aria-valuemin="0" - aria-valuemax="100">
-
-
-
-
-
- -
-
-
-
-
- - -
-
-
-
-
-
RAM -
-
-
-
%
-
-
-
-
aria-valuenow= aria-valuemin="0" - aria-valuemax="100">
-
-
-
-
-
- -
-
-
-
-
- - -
-
-
-
-
-
HDD (C:) -
-
-
-
%
-
-
-
-
aria-valuenow= aria-valuemin="0" - aria-valuemax="100">
-
-
-
-
-
- -
-
-
-
-
- - -
-
-
-
-
-
- Pending Requests
-
18
-
-
- -
-
-
-
+
+
- - - -
- - -
-
- -
-
Earnings Overview
- -
- -
-
- -
-
-
-
- - -
-
- -
-
Revenue Sources
- -
- -
-
- -
-
- - Direct - - - Social - - - Referral - -
-
-
-
+
+
- - -
- - -
- - -
-
-
Projects
-
-
-

Server Migration 20%

-
-
-
-

Sales Tracking 40%

-
-
-
-

Customer Database 60%

-
-
-
-

Payout Details 80%

-
-
-
-

Account Setup Complete!

-
-
-
-
-
- - -
-
-
-
- Primary -
#4e73df
-
-
-
-
-
-
- Success -
#1cc88a
-
-
-
-
-
-
- Info -
#36b9cc
-
-
-
-
-
-
- Warning -
#f6c23e
-
-
-
-
-
-
- Danger -
#e74a3b
-
-
-
-
-
-
- Secondary -
#858796
-
-
-
-
-
-
- Light -
#f8f9fc
-
-
-
-
-
-
- Dark -
#5a5c69
-
-
-
-
- -
- -
- - -
-
-
Illustrations
-
-
-
- ... -
-

Add some quality, svg illustrations to your project courtesy of unDraw, a - constantly updated collection of beautiful svg images that you can use - completely free and without attribution!

- Browse Illustrations on - unDraw → -
-
- - -
-
-
Development Approach
-
-
-

SB Admin 2 makes extensive use of Bootstrap 4 utility classes in order to reduce - CSS bloat and poor page performance. Custom CSS classes are used to create - custom components and custom utility classes.

-

Before working with this theme, you should become familiar with the - Bootstrap framework, especially the utility classes.

-
-
- -
-
- -
- - -
- - - -
-
- -
-
- - -
- - -
- - - - - - - - - - - - + +
+
+
+
+
+
+ Betriebssystem +
+
+ +
+
+
+ +
+
+
+
+
- - + +
+
+
+
+
+
+ CPU-Auslastung +
+
+ % +
+
+
+ +
+
+
+
+
- - + +
+
+
+
+
+
+ RAM-Auslastung +
+
+ % +
+
+
+ +
+
+
+
+
+
- - +
+ +
+
+
+
+
+
+ Datenträger C: / Root +
+
+ % +
+
+
+ +
+
+
+
+
- - - + +
+
+
+
+
+
+ System Uptime +
+
+ +
+
+
+ +
+
+
+
+
- +
+
Letztes Update:
+
+
- \ No newline at end of file + diff --git a/public/views/layout.php b/public/views/layout.php new file mode 100644 index 0000000..d122a32 --- /dev/null +++ b/public/views/layout.php @@ -0,0 +1,76 @@ + $viewData Daten-Array für die View + * - string $pageTitle Seitentitel + * - string|null $activeMenu Kennung für die Sidebar (z. B. 'dashboard' oder 'users') + */ + +// Daten-Array in einzelne Variablen entpacken, +// sodass die Content-Views direkt mit $users, $groups, $serverStatus, $error etc. arbeiten können. +function renderLayout(string $contentView, array $viewData, string $pageTitle, ?string $activeMenu): void +{ + // Daten-Array in einzelne Variablen entpacken + foreach ($viewData as $key => $value) { + if (is_string($key) && $key !== '') { + $cleanKey = preg_replace('/[^a-zA-Z0-9_]/', '', $key); + if ($cleanKey !== '') { + $$cleanKey = $value; + } + } + } + + // Basispfad für alle Layout-Teilansichten (Head, Sidebar, Topbar, Footer, Scripts). + + $partialsPath = __DIR__ . '/partials'; + $isLoginPage = !empty($viewData['loginPage']); + ?> + + + + + + + + +
+ + + + +
+ + +
+ + + + +
+ +
+ +
+ + + + +
+ +
+ + + + + - - +
- - - - - - - - - SB Admin 2 - Login - - - - - - - - - - - - -
- - -
- -
- -
-
- -
- -
-
-
-

Welcome Back!

-
- - - -
-
- -
-
- -
-
-
- - -
-
- -
-
- - -
+
+
+
+ +
+
+
+
+

Willkommen beim AD Admin Tool

+

+ Dies ist unser Testserver für den develop Branch. +

+ + + + + +
+
+ + +
+
+ + +
+ + +
+ +
+

+ Die Zugangsdaten werden nicht gespeichert. +

-
-
-
- - - - - - - - - - - - - \ No newline at end of file +
diff --git a/public/views/partials/footer.php b/public/views/partials/footer.php new file mode 100644 index 0000000..f425be2 --- /dev/null +++ b/public/views/partials/footer.php @@ -0,0 +1,23 @@ + + + +
+
+ +
+
diff --git a/public/views/partials/head.php b/public/views/partials/head.php new file mode 100644 index 0000000..74520a7 --- /dev/null +++ b/public/views/partials/head.php @@ -0,0 +1,30 @@ +-Bereich und den Beginn des HTML-Body. + * + * Aufgaben: + * - Setzt Zeichensatz und Dokumenttitel (inkl. optionalem Seitentitel). + * - Bindet die CSS-Ressourcen des SB-Admin-2-Templates sowie DataTables ein. + * - Öffnet das -Element und den Wrapper für das Layout. + * + * WICHTIG: + * - Diese Datei wird ausschließlich über das zentrale Layout (layout.php) eingebunden. + */ + +?> + + + + + AD Admin Tool<?= isset($pageTitle) ? ' – ' . htmlspecialchars($pageTitle, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') : '' ?> + + + + + + + + + + + diff --git a/public/views/partials/scripts.php b/public/views/partials/scripts.php new file mode 100644 index 0000000..b13f8c9 --- /dev/null +++ b/public/views/partials/scripts.php @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/views/partials/sidebar.php b/public/views/partials/sidebar.php new file mode 100644 index 0000000..ea97dd7 --- /dev/null +++ b/public/views/partials/sidebar.php @@ -0,0 +1,60 @@ + + + diff --git a/public/views/partials/topbar.php b/public/views/partials/topbar.php new file mode 100644 index 0000000..381eba4 --- /dev/null +++ b/public/views/partials/topbar.php @@ -0,0 +1,51 @@ + + + + diff --git a/public/views/users.php b/public/views/users.php index ebd600a..e95e779 100644 --- a/public/views/users.php +++ b/public/views/users.php @@ -1,520 +1,112 @@ > $users */ -/** @var array> $groups */ -/** @var string|null $error */ +/** + * View-Template für die Anzeige von Benutzern und Gruppen. + * + * Aufgaben: + * - Stellt zwei Tabellen dar: eine Benutzerliste und eine Gruppenliste. + * - Nutzt Daten aus dem LdapDirectoryService (über den UserManagementController). + * - Behandelt optionale Fehlermeldungen durch Ausgabe eines Alert-Elements. + * + * Erwartete View-Daten: + * - array> $users Liste von Benutzerobjekten (z. B. sAMAccountName, displayName, mail). + * - array> $groups Liste von Gruppenobjekten (z. B. sAMAccountName, cn, description). + * - string|null $error Fehlermeldung bei Problemen mit dem LDAP-Zugriff. + */ + +/** + * @var array> $users + * @var array> $groups + * @var string|null $error + */ ?> - - - - - - - - - - - - SB Admin 2 - Tables - - - - - - - - - - - - - - - - -
- - - - - - -
- - -
- - - - - - -
- - -

Benutzer & Gruppen

- -

- -

- -

DataTables is a third party plugin that is used to generate the demo table below. - For more information about DataTables, please visit the official DataTables documentation.

- - -
-
-
Benutzer
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - -
AnmeldenameAnzeigenameE-Mail
AnmeldenameAnzeigenameE-Mail
-
-
-
-
-
-
Gruppen
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - -
GruppennameCNBeschreibung
GruppennameCNBeschreibung
-
-
-
- -
- - -
- - - -
-
- -
-
- - -
- +
+

Benutzer & Gruppen

- - - - - + + + - -