From e67b0944664aa3f95a672561ef1f70a22303531a Mon Sep 17 00:00:00 2001 From: data Date: Sun, 8 Mar 2026 09:21:53 +0100 Subject: [PATCH] fix: AJAX-URL dynamisch aus Script-Pfad ermitteln MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit urlRoot wird einmalig aus dem src-Attribut des globalnotify-Scripts abgeleitet, damit AJAX-Calls unabhängig vom Installationspfad funktionieren (z.B. /dolibarr/custom/... statt /custom/...). Behebt 404-Fehler bei Aktionen wie "Alle als gelesen markieren". Zusätzlich CLAUDE.md Projektdokumentation erstellt. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 124 +++++++++++++++++++++++++++++++++++++++++++++ js/globalnotify.js | 10 +++- 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9161db6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,124 @@ +# GlobalNotify - Dolibarr Custom Module + +Globales Benachrichtigungssystem als schwebendes Widget (FAB) in der unteren linken Ecke. +Sammelt Alerts von allen Modulen (Cron-Fehler, Warnungen, Aktionen) und zeigt sie einheitlich an. + +## Modul-Metadaten + +- **Modul-Nummer**: 500100 +- **Version**: 1.4.0 +- **Autor**: Eduard Wisch / Data IT Solution +- **Hooks**: `main`, `toprightmenu` +- **Nur für Admins** (`$user->admin`) + +## Projektstruktur + +``` +globalnotify/ + admin/setup.php # Admin-Seite: Einstellungen + Übersicht + ajax/action.php # AJAX-Endpunkt für JS-Aktionen + class/ + globalnotify.class.php # Kern-Klasse: Notification CRUD + actions_globalnotify.class.php # Hook-Klasse: Widget-Rendering + Cron-Checks + core/modules/modGlobalNotify.class.php # Modul-Deskriptor + css/globalnotify.css # Widget-Styles (Dark Mode Support) + js/globalnotify.js # Widget-Logik (Drag, AJAX, Sound) + langs/de_DE/globalnotify.lang # Deutsche Übersetzungen + langs/en_US/globalnotify.lang # Englische Übersetzungen +``` + +## Architektur + +### Speicherung +Notifications werden als JSON-Arrays in `llx_const` gespeichert (NICHT in eigenen Tabellen): +- Schlüssel: `GLOBALNOTIFY_{MODULE}` (z.B. `GLOBALNOTIFY_CRON`, `GLOBALNOTIFY_BANKIMPORT`) +- Wert: JSON-Array mit Notification-Objekten +- Max 50 Notifications pro Modul +- Interne Settings: `GLOBALNOTIFY_CRON_LASTCHECK`, `GLOBALNOTIFY_CRON_CHECK_INTERVAL` + +### Notification-Typen (Konstanten in GlobalNotify) +- `TYPE_ERROR` = `'error'` — Priorität 10 +- `TYPE_ACTION` = `'action'` — Priorität 9 +- `TYPE_WARNING` = `'warning'` — Priorität 7 +- `TYPE_INFO` = `'info'` — Priorität 3 +- `TYPE_SUCCESS` = `'success'` + +### Notification-Objekt (JSON-Struktur) +```json +{ + "id": "module_uniqid", + "module": "cron", + "type": "error", + "title": "Kurztitel", + "message": "Detailbeschreibung", + "action_url": "/cron/list.php", + "action_label": "Button-Text", + "priority": 10, + "user_id": 0, + "created": 1709000000, + "read": false +} +``` + +## API: Notifications senden (für andere Module) + +```php +// Statische Helper (empfohlen): +dol_include_once('/globalnotify/class/globalnotify.class.php'); +GlobalNotify::error('meinmodul', 'Titel', 'Nachricht', $actionUrl, $actionLabel); +GlobalNotify::warning('meinmodul', 'Titel', 'Nachricht'); +GlobalNotify::info('meinmodul', 'Titel', 'Nachricht'); +GlobalNotify::actionRequired('meinmodul', 'Titel', 'Nachricht', $actionUrl); + +// Oder manuell mit voller Kontrolle: +$notify = new GlobalNotify($db); +$notify->addNotification($module, $type, $title, $message, $actionUrl, $actionLabel, $priority, $userId); +``` + +## AJAX-Endpunkt (`ajax/action.php`) + +POST-Requests mit `action` Parameter: +- `dismiss` — Einzelne Notification als gelesen markieren (`id` Parameter) +- `delete` — Notification löschen (`id` Parameter) +- `markallread` — Alle als gelesen markieren +- `getall` — Alle ungelesenen abrufen +- `getcount` — Ungelesene Anzahl abrufen + +Authentifizierung: Nur `$user->admin`, CSRF-Token über `TOKEN` JS-Variable. + +## JavaScript (globalnotify.js) + +- **`GlobalNotify.urlRoot`**: Wird beim Laden einmalig aus dem Script-src-Pfad ermittelt (z.B. `/dolibarr`), damit AJAX-Calls unabhängig vom Installationspfad funktionieren +- **Drag & Drop**: FAB ist verschiebbar, Position wird in `localStorage` gespeichert +- **Click vs. Drag**: Erkennung über Distanz (<5px) und Zeit (<200ms) +- **Auto-Refresh**: Alle 2 Minuten via `getcount` AJAX-Call +- **Sound**: Web Audio API Doppel-Ton bei neuen Notifications +- **Animationen**: Shake + Bounce bei neuen Notifications + +## Automatische Cron-Prüfungen (in Hook-Klasse) + +1. **Hängende Jobs**: `processing=1` seit >30 Min → `TYPE_ERROR` +2. **Verpasste Jobs**: `datenextrun` >2 Std. in der Vergangenheit → `TYPE_WARNING` +3. **Cleanup**: Notifications für deaktivierte Cronjobs werden automatisch als gelesen markiert +4. **Cache**: Prüfintervall konfigurierbar via `GLOBALNOTIFY_CRON_CHECK_INTERVAL` (Default: 60s, Min: 10s, Max: 3600s) + +## Modul-spezifische Checks + +- **BankImport**: Prüft `BankImportCron::getCronStatus()` auf `paused`, `tan_required`, `login_error`, `fetch_error`, `session_expired` +- **ImportZugferd**: Prüft auf hängende `ImportZugferdScheduled` Cronjobs + +## Admin-Seite (`admin/setup.php`) + +- Einstellung: Cron-Prüfintervall +- Aktion: Alle Benachrichtigungen löschen (löscht alle `GLOBALNOTIFY_*` Konstanten) +- Aktion: Hängende Cron-Jobs zurücksetzen (`processing=0`) +- Tabelle: Cron-Job Übersicht mit Status +- Tabelle: Alle Benachrichtigungen mit Typ, Modul, Datum, Gelesen-Status + +## Wichtige Konventionen + +- Duplikat-Erkennung: Vor dem Erstellen wird geprüft, ob eine gleichartige ungelesene Notification existiert +- Prioritäts-Sortierung: Höchste Priorität + neuestes Datum zuerst +- `user_id=0` bedeutet: sichtbar für alle Admins +- Dark Mode wird via `prefers-color-scheme: dark` CSS Media Query unterstützt +- Keine eigenen DB-Tabellen, alles über `llx_const` diff --git a/js/globalnotify.js b/js/globalnotify.js index 699fcd0..73acddf 100755 --- a/js/globalnotify.js +++ b/js/globalnotify.js @@ -7,6 +7,14 @@ var GlobalNotify = { isOpen: false, isDragging: false, dragOffset: { x: 0, y: 0 }, + urlRoot: (function() { + var scripts = document.querySelectorAll('script[src*="globalnotify"]'); + if (scripts.length > 0) { + var match = scripts[0].getAttribute('src').match(/^(.*?)\/custom\/globalnotify\//); + if (match) return match[1]; + } + return ''; + })(), /** * Toggle panel visibility @@ -208,7 +216,7 @@ var GlobalNotify = { * AJAX helper */ ajaxCall: function(action, params, callback) { - var url = (typeof DOL_URL_ROOT !== 'undefined' ? DOL_URL_ROOT : '') + '/custom/globalnotify/ajax/action.php'; + var url = this.urlRoot + '/custom/globalnotify/ajax/action.php'; var body = 'action=' + encodeURIComponent(action); for (var key in params) {