diff --git a/Changes.md b/Changes.md index f36bb60..d98e67c 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,15 @@ --- +## 2026-07-26 – Feature: Ereignisanzeige aktualisiert sich automatisch (Issue #139) + +- Die Ereignisanzeige (`/ereignisse/nextcloud-import`) war statisch – neu geloggte Ereignisse erschienen erst nach manuellem Neuladen der Seite. +- **`src/Controllers/EventController.php`:** Neue Methode `nextcloudImportData()` liefert die aktuellen Ereignisse als JSON (Anzahl + serverseitig gerendertes HTML-Fragment), damit initiale Seite und Auto-Reload denselben Rendering-Code (`ereignisse/_nextcloud_import_body.php`) verwenden. +- **`src/Views/ereignisse/_nextcloud_import_body.php`:** Tabellen-/Leer-Markup aus `nextcloud_import.php` in ein Partial ausgelagert, das von initialer Seite und Datenendpunkt gemeinsam genutzt wird. +- **`src/Views/ereignisse/nextcloud_import.php`:** Neuer Ein-/Aus-Button oben rechts in der Kartenkopfzeile schaltet automatisches Neuladen per `setInterval` (alle 60s, ohne externe Bibliothek) ein/aus; Zustand wird per `localStorage` gemerkt. Ein echter Push-Mechanismus (z. B. Server-Sent Events) hätte dauerhafte PHP-FPM-Worker gebunden – bei diesem Anwendungsfall (Log-Anzeige) ist periodisches Polling die robustere Lösung. +- **`public/index.php`:** Neue Route `GET /ereignisse/nextcloud-import/data`. +- **`tests/58_EreignisAutoReloadTest.php`:** Neuer Test prüft Button, Polling-Code, Partial-Wiederverwendung und Routen-Registrierung. + ## 2026-07-21 – Fix: Zwei dauerhaft fehlschlagende Tests (Session-Cookie-Härtung, Nextcloud-SSRF) - **Test 54 (Session-Cookie-Härtung):** Der Test rief echtes `Auth::startSession()` auf. Da `tests/run.php` Ergebnisse laufend per `echo` ausgibt und Test 54 der einzige Test ist, der Sessions anfasst, war zum Testzeitpunkt bereits Output gesendet – PHP lehnt `session_set_cookie_params()`/`session_start()` danach mit „headers already sent" ab (Warnung + wirkungslos). Ein nachträglicher Output-Buffer im Test half nicht, da bereits vor dem Test unbuffered Output geflossen war. diff --git a/docs/funktionen.md b/docs/funktionen.md index c54da1c..5bc5025 100644 --- a/docs/funktionen.md +++ b/docs/funktionen.md @@ -16,6 +16,7 @@ Vollständige Kontenliste: [kontenrahmen.md](kontenrahmen.md) - KI-gestützter Kontierungsvorschlag (optional, siehe [konfiguration.md](konfiguration.md)) - Nextcloud-Import via WebDAV (optional) - Belege können nicht gelöscht werden, solange Buchungen darauf verweisen (GoBD) +- **Ereignisanzeige** (`/ereignisse/nextcloud-import`): Log der letzten 500 Import-Ereignisse (importiert/übersprungen/Fehler); aktualisiert sich per Ein-/Aus-Button oben rechts automatisch alle 60 Sekunden (Zustand wird im Browser gemerkt) --- diff --git a/public/index.php b/public/index.php index 09b54ef..3eb7d8a 100644 --- a/public/index.php +++ b/public/index.php @@ -186,8 +186,9 @@ $router->get('/api/buchungsvorlagen/{id}/modal', fn($p) = $router->post('/belege/{belegId}/vorlage/{vorlageId}/buchen', fn($p) => (new BuchungsvorlageController())->buchen($p)); // ── Ereignisse ─────────────────────────────────────────────── -$router->get('/ereignisse', fn($p) => (new EventController())->index($p)); -$router->get('/ereignisse/nextcloud-import', fn($p) => (new EventController())->nextcloudImport($p)); +$router->get('/ereignisse', fn($p) => (new EventController())->index($p)); +$router->get('/ereignisse/nextcloud-import', fn($p) => (new EventController())->nextcloudImport($p)); +$router->get('/ereignisse/nextcloud-import/data', fn($p) => (new EventController())->nextcloudImportData($p)); // ── Login / Logout ──────────────────────────────────────────── $router->get('/login', fn($p) => (new LoginController())->showLogin($p)); diff --git a/src/Controllers/EventController.php b/src/Controllers/EventController.php index c989236..c077783 100644 --- a/src/Controllers/EventController.php +++ b/src/Controllers/EventController.php @@ -22,8 +22,24 @@ class EventController extends BaseController $view->render('layout/base.php', [ 'pageTitle' => 'Ereignisse – Nextcloud-Import', 'content' => $this->renderView('ereignisse/nextcloud_import', [ - 'events' => $events, + 'events' => $events, + 'eventsHtml' => $this->renderView('ereignisse/_nextcloud_import_body', [ + 'events' => $events, + ]), ]), ]); } + + public function nextcloudImportData(array $params): void + { + $events = EventLog::findByChannel('nextcloud-import', 500); + + header('Content-Type: application/json'); + echo json_encode([ + 'count' => count($events), + 'html' => $this->renderView('ereignisse/_nextcloud_import_body', [ + 'events' => $events, + ]), + ], JSON_UNESCAPED_UNICODE); + } } diff --git a/src/Views/ereignisse/_nextcloud_import_body.php b/src/Views/ereignisse/_nextcloud_import_body.php new file mode 100644 index 0000000..2e19645 --- /dev/null +++ b/src/Views/ereignisse/_nextcloud_import_body.php @@ -0,0 +1,28 @@ + View::h($v); ?> + +
Keine Ereignisse vorhanden. Einträge entstehen, sobald der Nextcloud-Import Dateien importiert, überspringt oder auf Fehler stößt.
+ +
+ + + + + + + + + + + +
[]
+
+ diff --git a/src/Views/ereignisse/nextcloud_import.php b/src/Views/ereignisse/nextcloud_import.php index 3923423..4ad281f 100644 --- a/src/Views/ereignisse/nextcloud_import.php +++ b/src/Views/ereignisse/nextcloud_import.php @@ -1,35 +1,76 @@ - View::h($v); ?> -
Nextcloud-Import - Einträge (neueste zuerst) +
+ Einträge (neueste zuerst) + +
- -
Keine Ereignisse vorhanden. Einträge entstehen, sobald der Nextcloud-Import Dateien importiert, überspringt oder auf Fehler stößt.
- -
- - - - - - - - - - - -
[]
+
+
-
+ + diff --git a/tests/58_EreignisAutoReloadTest.php b/tests/58_EreignisAutoReloadTest.php new file mode 100644 index 0000000..d789707 --- /dev/null +++ b/tests/58_EreignisAutoReloadTest.php @@ -0,0 +1,65 @@ +assert($view !== false, 'View-Datei ereignisse/nextcloud_import.php ist lesbar'); + + $this->assert( + str_contains($view, 'id="events-autoreload-toggle"'), + 'Ein-/Aus-Button für Auto-Reload ist vorhanden' + ); + + $this->assert( + str_contains($view, 'localStorage'), + 'Zustand des Auto-Reload-Buttons wird persistiert (localStorage)' + ); + + $this->assert( + str_contains($view, 'setInterval') && str_contains($view, "'/ereignisse/nextcloud-import/data'"), + 'JavaScript pollt periodisch den Daten-Endpunkt' + ); + + $this->assert( + str_contains($view, "toggle.addEventListener('click'"), + 'Button schaltet Auto-Reload per Klick ein/aus' + ); + + // Partial für Tabellen-Body existiert und wird sowohl beim initialen + // Laden als auch für den Daten-Endpunkt verwendet + $partialFile = __DIR__ . '/../src/Views/ereignisse/_nextcloud_import_body.php'; + $this->assert(file_exists($partialFile), 'Partial _nextcloud_import_body.php existiert'); + + $controllerFile = __DIR__ . '/../src/Controllers/EventController.php'; + $controller = file_get_contents($controllerFile); + $this->assert( + str_contains($controller, 'function nextcloudImportData'), + 'EventController::nextcloudImportData() ist implementiert' + ); + $this->assert( + str_contains($controller, "'ereignisse/_nextcloud_import_body'"), + 'Daten-Endpunkt rendert dasselbe Partial wie die initiale Seite' + ); + + $routesFile = __DIR__ . '/../public/index.php'; + $routes = file_get_contents($routesFile); + $this->assert( + str_contains($routes, "'/ereignisse/nextcloud-import/data'") && str_contains($routes, 'nextcloudImportData'), + 'Route /ereignisse/nextcloud-import/data ist registriert' + ); + } +}