feat: Konfigurierbares Cron-Prüfintervall und Dokumentation
- Admin-Seite mit Einstellung für Cron-Prüfintervall (10-3600 Sek) - checkStuckCronJobs() nutzt jetzt konfigurierbare Einstellung - README.md mit vollständiger Integrations-Dokumentation - Sichere Nutzung ohne Fatal Error wenn Modul nicht installiert - Version 1.3.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d119448d93
commit
197d87c663
5 changed files with 391 additions and 3 deletions
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
|
||||||
|
|
||||||
|
## [1.3.0] - 2026-02-23
|
||||||
|
|
||||||
|
### Neu
|
||||||
|
- **Konfigurierbares Prüfintervall**: Im Admin-Center kann eingestellt werden, wie oft auf hängende Cron-Jobs geprüft wird (10-3600 Sekunden)
|
||||||
|
|
||||||
|
### Verbessert
|
||||||
|
- Admin-Seite mit Einstellungs-Formular erweitert
|
||||||
|
|
||||||
## [1.2.0] - 2026-02-23
|
## [1.2.0] - 2026-02-23
|
||||||
|
|
||||||
### Neu
|
### Neu
|
||||||
|
|
|
||||||
333
README.md
Normal file
333
README.md
Normal file
|
|
@ -0,0 +1,333 @@
|
||||||
|
# GlobalNotify - Dolibarr Benachrichtigungsmodul
|
||||||
|
|
||||||
|
Ein zentrales Benachrichtigungssystem für Dolibarr, das Meldungen von allen Modulen sammelt und in einem schwebenden Widget anzeigt.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Schwebendes Widget**: Messenger-artiges Panel, immer sichtbar (unten links)
|
||||||
|
- **Verschiebbar**: FAB-Button kann frei positioniert werden (Position wird gespeichert)
|
||||||
|
- **Benachrichtigungston**: Akustisches Signal bei neuen Nachrichten
|
||||||
|
- **Shake-Animation**: Visuelle Aufmerksamkeit bei neuen Benachrichtigungen
|
||||||
|
- **Farbkodierung**:
|
||||||
|
- Grau: Keine Benachrichtigungen
|
||||||
|
- Rot: Benachrichtigungen vorhanden
|
||||||
|
- Pulsierend rot: Dringende Benachrichtigungen (Fehler/Aktionen)
|
||||||
|
- **Historie**: Gelesene Benachrichtigungen bleiben in der Historie verfügbar
|
||||||
|
- **Cron-Überwachung**: Automatische Erkennung hängender Cron-Jobs
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Modul nach `/custom/globalnotify/` kopieren
|
||||||
|
2. In Dolibarr unter Einstellungen → Module → GlobalNotify aktivieren
|
||||||
|
3. Konfiguration unter Einstellungen → Module → GlobalNotify → Setup
|
||||||
|
|
||||||
|
## Einstellungen
|
||||||
|
|
||||||
|
- **Cron-Prüfintervall**: Wie oft auf hängende Cron-Jobs geprüft wird (10-3600 Sekunden)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration in andere Module
|
||||||
|
|
||||||
|
### Grundprinzip
|
||||||
|
|
||||||
|
GlobalNotify bietet eine einfache API zum Erstellen von Benachrichtigungen. **Wichtig**: Module sollten immer prüfen, ob GlobalNotify verfügbar ist, bevor sie es verwenden.
|
||||||
|
|
||||||
|
### Sichere Integration (empfohlen)
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Sendet eine Benachrichtigung über GlobalNotify (falls verfügbar)
|
||||||
|
*
|
||||||
|
* @param string $type 'error', 'warning', 'info', 'success', 'action'
|
||||||
|
* @param string $title Kurzer Titel
|
||||||
|
* @param string $message Detaillierte Nachricht
|
||||||
|
* @param string $actionUrl URL für Aktions-Button (optional)
|
||||||
|
* @param string $actionLabel Label für Aktions-Button (optional)
|
||||||
|
* @return bool True wenn gesendet, false wenn GlobalNotify nicht verfügbar
|
||||||
|
*/
|
||||||
|
function mymodule_notify($type, $title, $message, $actionUrl = '', $actionLabel = '')
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
// Prüfe ob GlobalNotify aktiv ist
|
||||||
|
if (!isModEnabled('globalnotify')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Versuche die Klasse zu laden
|
||||||
|
$classFile = dol_buildpath('/globalnotify/class/globalnotify.class.php', 0);
|
||||||
|
if (!file_exists($classFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $classFile;
|
||||||
|
|
||||||
|
if (!class_exists('GlobalNotify')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benachrichtigung senden
|
||||||
|
try {
|
||||||
|
switch ($type) {
|
||||||
|
case 'error':
|
||||||
|
GlobalNotify::error('mymodule', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
GlobalNotify::warning('mymodule', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
GlobalNotify::info('mymodule', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
break;
|
||||||
|
case 'action':
|
||||||
|
GlobalNotify::actionRequired('mymodule', $title, $message, $actionUrl, $actionLabel ?: 'Aktion erforderlich');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GlobalNotify::info('mymodule', $title, $message, $actionUrl, $actionLabel);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
dol_syslog('GlobalNotify error: '.$e->getMessage(), LOG_WARNING);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direkte Nutzung der Klasse
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// GlobalNotify einbinden (mit Sicherheitsprüfung)
|
||||||
|
if (isModEnabled('globalnotify')) {
|
||||||
|
dol_include_once('/globalnotify/class/globalnotify.class.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Irgendwo im Code...
|
||||||
|
if (class_exists('GlobalNotify')) {
|
||||||
|
$notify = new GlobalNotify($db);
|
||||||
|
|
||||||
|
// Fehler-Benachrichtigung (höchste Priorität 10)
|
||||||
|
$notify->addNotification(
|
||||||
|
'mymodule', // Modul-Name
|
||||||
|
GlobalNotify::TYPE_ERROR, // Typ
|
||||||
|
'Import fehlgeschlagen', // Titel
|
||||||
|
'Die Datei konnte nicht gelesen werden: file.pdf', // Nachricht
|
||||||
|
dol_buildpath('/mymodule/list.php', 1), // Aktions-URL
|
||||||
|
'Details anzeigen', // Aktions-Label
|
||||||
|
10, // Priorität (1-10)
|
||||||
|
0 // User-ID (0 = alle Admins)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Statische Helper-Methoden
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// Nur wenn GlobalNotify verfügbar
|
||||||
|
if (class_exists('GlobalNotify')) {
|
||||||
|
// Fehler (Priorität 10)
|
||||||
|
GlobalNotify::error('mymodule', 'Titel', 'Nachricht', 'url', 'Button-Text');
|
||||||
|
|
||||||
|
// Warnung (Priorität 7)
|
||||||
|
GlobalNotify::warning('mymodule', 'Titel', 'Nachricht', 'url', 'Button-Text');
|
||||||
|
|
||||||
|
// Info (Priorität 3)
|
||||||
|
GlobalNotify::info('mymodule', 'Titel', 'Nachricht', 'url', 'Button-Text');
|
||||||
|
|
||||||
|
// Aktion erforderlich (Priorität 9)
|
||||||
|
GlobalNotify::actionRequired('mymodule', 'Titel', 'Nachricht', 'url', 'Button-Text');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benachrichtigungstypen
|
||||||
|
|
||||||
|
| Typ | Konstante | Priorität | Verwendung |
|
||||||
|
|-----|-----------|-----------|------------|
|
||||||
|
| error | `TYPE_ERROR` | 10 | Kritische Fehler, System-Probleme |
|
||||||
|
| warning | `TYPE_WARNING` | 7 | Warnungen, potentielle Probleme |
|
||||||
|
| info | `TYPE_INFO` | 3 | Informationen, Status-Updates |
|
||||||
|
| success | `TYPE_SUCCESS` | 3 | Erfolgreiche Aktionen |
|
||||||
|
| action | `TYPE_ACTION` | 9 | Benutzer-Aktion erforderlich |
|
||||||
|
|
||||||
|
### Beispiel: Cron-Job mit Fehlerbenachrichtigung
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
class MyCronJob
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// ... Cron-Logik ...
|
||||||
|
|
||||||
|
if ($errorCount > 0) {
|
||||||
|
$this->notifyAdmin(
|
||||||
|
'warning',
|
||||||
|
'Import mit Warnungen',
|
||||||
|
"{$errorCount} Dateien konnten nicht importiert werden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->notifyAdmin(
|
||||||
|
'error',
|
||||||
|
'Cron-Job fehlgeschlagen',
|
||||||
|
$e->getMessage(),
|
||||||
|
dol_buildpath('/mymodule/admin/setup.php', 1),
|
||||||
|
'Einstellungen prüfen'
|
||||||
|
);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function notifyAdmin($type, $title, $message, $url = '', $label = '')
|
||||||
|
{
|
||||||
|
if (!isModEnabled('globalnotify')) {
|
||||||
|
// Fallback: Nur ins Log schreiben
|
||||||
|
dol_syslog("MyModule: {$title} - {$message}", LOG_WARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dol_include_once('/globalnotify/class/globalnotify.class.php');
|
||||||
|
|
||||||
|
if (class_exists('GlobalNotify')) {
|
||||||
|
switch ($type) {
|
||||||
|
case 'error':
|
||||||
|
GlobalNotify::error('mymodule', $title, $message, $url, $label);
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
GlobalNotify::warning('mymodule', $title, $message, $url, $label);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GlobalNotify::info('mymodule', $title, $message, $url, $label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benachrichtigungen verwalten
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
dol_include_once('/globalnotify/class/globalnotify.class.php');
|
||||||
|
|
||||||
|
$notify = new GlobalNotify($db);
|
||||||
|
|
||||||
|
// Alle ungelesenen Benachrichtigungen holen
|
||||||
|
$unread = $notify->getAllNotifications($user->id, true);
|
||||||
|
|
||||||
|
// Gelesene Benachrichtigungen (Historie)
|
||||||
|
$history = $notify->getReadNotifications($user->id, 20);
|
||||||
|
|
||||||
|
// Als gelesen markieren
|
||||||
|
$notify->markAsRead($notificationId);
|
||||||
|
|
||||||
|
// Alle als gelesen markieren
|
||||||
|
$notify->markAllAsRead();
|
||||||
|
|
||||||
|
// Nur für ein bestimmtes Modul
|
||||||
|
$notify->markAllAsRead('mymodule');
|
||||||
|
|
||||||
|
// Benachrichtigung löschen
|
||||||
|
$notify->deleteNotification($notificationId);
|
||||||
|
|
||||||
|
// Alle Benachrichtigungen eines Moduls löschen
|
||||||
|
$notify->clearModuleNotifications('mymodule');
|
||||||
|
|
||||||
|
// Ungelesene Anzahl für Badge
|
||||||
|
$count = $notify->getUnreadCount();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Immer Verfügbarkeit prüfen
|
||||||
|
|
||||||
|
```php
|
||||||
|
// RICHTIG
|
||||||
|
if (isModEnabled('globalnotify') && class_exists('GlobalNotify')) {
|
||||||
|
GlobalNotify::error('mymodule', 'Fehler', 'Details');
|
||||||
|
}
|
||||||
|
|
||||||
|
// FALSCH - kann zu Fatal Error führen
|
||||||
|
GlobalNotify::error('mymodule', 'Fehler', 'Details');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Nicht zu viele Benachrichtigungen
|
||||||
|
|
||||||
|
GlobalNotify speichert maximal 50 Benachrichtigungen pro Modul. Vermeiden Sie:
|
||||||
|
- Benachrichtigungen in Schleifen
|
||||||
|
- Benachrichtigungen für jeden einzelnen Datensatz
|
||||||
|
- Wiederholte identische Benachrichtigungen
|
||||||
|
|
||||||
|
### 3. Aussagekräftige Informationen
|
||||||
|
|
||||||
|
```php
|
||||||
|
// RICHTIG
|
||||||
|
GlobalNotify::error(
|
||||||
|
'bankimport',
|
||||||
|
'Bank-Login fehlgeschlagen',
|
||||||
|
'Sparkasse Musterstadt: Falsches Passwort (3. Versuch)',
|
||||||
|
dol_buildpath('/bankimport/admin/setup.php', 1),
|
||||||
|
'Zugangsdaten prüfen'
|
||||||
|
);
|
||||||
|
|
||||||
|
// FALSCH
|
||||||
|
GlobalNotify::error('bankimport', 'Fehler', 'Es ist ein Fehler aufgetreten');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Sinnvolle Aktions-URLs
|
||||||
|
|
||||||
|
Wenn eine Benachrichtigung eine Aktion erfordert, fügen Sie immer eine URL hinzu:
|
||||||
|
|
||||||
|
```php
|
||||||
|
GlobalNotify::actionRequired(
|
||||||
|
'importzugferd',
|
||||||
|
'Lieferant nicht gefunden',
|
||||||
|
'Rechnung von "Neue Firma GmbH" konnte nicht zugeordnet werden',
|
||||||
|
dol_buildpath('/importzugferd/pending.php?id=123', 1),
|
||||||
|
'Rechnung prüfen'
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technische Details
|
||||||
|
|
||||||
|
### Speicherung
|
||||||
|
|
||||||
|
Benachrichtigungen werden in `llx_const` als JSON gespeichert:
|
||||||
|
- Key: `GLOBALNOTIFY_<MODULNAME>` (z.B. `GLOBALNOTIFY_BANKIMPORT`)
|
||||||
|
- Value: JSON-Array mit Benachrichtigungen
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
|
||||||
|
- Cron-Prüfung: Konfigurierbar (Standard: 60 Sekunden)
|
||||||
|
- Client-Refresh: Alle 2 Minuten via AJAX
|
||||||
|
|
||||||
|
### Hooks
|
||||||
|
|
||||||
|
Das Modul nutzt folgende Hooks:
|
||||||
|
- `printTopRightMenu` - Widget-Anzeige
|
||||||
|
- `addMoreActionsButtons` - Modul-Status-Checks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
Siehe [CHANGELOG.md](CHANGELOG.md)
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
GPL v3 - siehe [LICENSE](LICENSE)
|
||||||
|
|
||||||
|
## Autor
|
||||||
|
|
||||||
|
Eduard Wisch - Data IT Solution
|
||||||
|
https://data-it-solution.de
|
||||||
|
|
@ -56,6 +56,20 @@ $action = GETPOST('action', 'aZ09');
|
||||||
* Actions
|
* Actions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if ($action == 'update') {
|
||||||
|
$cronCheckInterval = GETPOST('GLOBALNOTIFY_CRON_CHECK_INTERVAL', 'int');
|
||||||
|
if ($cronCheckInterval < 10) {
|
||||||
|
$cronCheckInterval = 10; // Minimum 10 seconds
|
||||||
|
}
|
||||||
|
if ($cronCheckInterval > 3600) {
|
||||||
|
$cronCheckInterval = 3600; // Maximum 1 hour
|
||||||
|
}
|
||||||
|
dolibarr_set_const($db, 'GLOBALNOTIFY_CRON_CHECK_INTERVAL', $cronCheckInterval, 'chaine', 0, '', $conf->entity);
|
||||||
|
setEventMessages("Einstellungen gespeichert", null, 'mesgs');
|
||||||
|
header("Location: ".$_SERVER["PHP_SELF"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($action == 'clearall') {
|
if ($action == 'clearall') {
|
||||||
// Clear all notifications
|
// Clear all notifications
|
||||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."const WHERE name LIKE 'GLOBALNOTIFY_%'";
|
$sql = "DELETE FROM ".MAIN_DB_PREFIX."const WHERE name LIKE 'GLOBALNOTIFY_%'";
|
||||||
|
|
@ -84,6 +98,33 @@ llxHeader('', $page_name);
|
||||||
|
|
||||||
print load_fiche_titre($page_name, '', 'title_setup');
|
print load_fiche_titre($page_name, '', 'title_setup');
|
||||||
|
|
||||||
|
// Settings Form
|
||||||
|
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
|
||||||
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||||
|
print '<input type="hidden" name="action" value="update">';
|
||||||
|
|
||||||
|
print '<table class="noborder centpercent">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th colspan="2">Einstellungen</th>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
// Cron check interval
|
||||||
|
$cronCheckInterval = getDolGlobalInt('GLOBALNOTIFY_CRON_CHECK_INTERVAL', 60);
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
print '<td><label for="GLOBALNOTIFY_CRON_CHECK_INTERVAL">Cron-Prüfintervall (Sekunden)</label>';
|
||||||
|
print '<br><small class="opacitymedium">Wie oft soll auf hängende Cron-Jobs geprüft werden? (10-3600 Sekunden)</small></td>';
|
||||||
|
print '<td><input type="number" name="GLOBALNOTIFY_CRON_CHECK_INTERVAL" id="GLOBALNOTIFY_CRON_CHECK_INTERVAL" value="'.$cronCheckInterval.'" min="10" max="3600" style="width:100px"> Sekunden</td>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
|
||||||
|
print '<br><div class="center">';
|
||||||
|
print '<input type="submit" class="button button-save" value="Speichern">';
|
||||||
|
print '</div>';
|
||||||
|
print '</form>';
|
||||||
|
|
||||||
|
print '<br>';
|
||||||
|
|
||||||
// Get all notifications
|
// Get all notifications
|
||||||
$notify = new GlobalNotify($db);
|
$notify = new GlobalNotify($db);
|
||||||
$allNotifications = $notify->getAllNotifications(0, false); // All, including read
|
$allNotifications = $notify->getAllNotifications(0, false); // All, including read
|
||||||
|
|
|
||||||
|
|
@ -270,9 +270,15 @@ class ActionsGlobalNotify extends CommonHookActions
|
||||||
{
|
{
|
||||||
global $conf;
|
global $conf;
|
||||||
|
|
||||||
// Only check once per minute (cache)
|
// Get configurable check interval (default 60 seconds)
|
||||||
|
$checkInterval = getDolGlobalInt('GLOBALNOTIFY_CRON_CHECK_INTERVAL', 60);
|
||||||
|
if ($checkInterval < 10) {
|
||||||
|
$checkInterval = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only check based on configured interval (cache)
|
||||||
$lastCheck = getDolGlobalInt('GLOBALNOTIFY_CRON_LASTCHECK');
|
$lastCheck = getDolGlobalInt('GLOBALNOTIFY_CRON_LASTCHECK');
|
||||||
if ($lastCheck > (time() - 60)) {
|
if ($lastCheck > (time() - $checkInterval)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dolibarr_set_const($this->db, 'GLOBALNOTIFY_CRON_LASTCHECK', time(), 'chaine', 0, '', $conf->entity);
|
dolibarr_set_const($this->db, 'GLOBALNOTIFY_CRON_LASTCHECK', time(), 'chaine', 0, '', $conf->entity);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class modGlobalNotify extends DolibarrModules
|
||||||
$this->descriptionlong = "Provides a unified notification bell in the top bar that collects and displays alerts from any module (cron errors, warnings, action required, etc.)";
|
$this->descriptionlong = "Provides a unified notification bell in the top bar that collects and displays alerts from any module (cron errors, warnings, action required, etc.)";
|
||||||
$this->editor_name = 'Data IT Solution';
|
$this->editor_name = 'Data IT Solution';
|
||||||
$this->editor_url = 'https://data-it-solution.de';
|
$this->editor_url = 'https://data-it-solution.de';
|
||||||
$this->version = '1.2.0';
|
$this->version = '1.3.0';
|
||||||
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
|
||||||
$this->picto = 'bell';
|
$this->picto = 'bell';
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue