sadsad
All checks were successful
Deploy / deploy-staging (push) Successful in 5s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-11 02:03:32 +02:00
parent 0f8f9567fe
commit f46de880f4
11 changed files with 178 additions and 22 deletions

View File

@@ -3,3 +3,8 @@ Wichtig: Modul-spezifischer Code/Assets ausschließlich unter /modules/<modul>/,
keine Änderungen an /public/assets/* für modul-spezifische Features.
Staging/Live: /config/<env> im Repo wird nach /app/<env>/config kopiert.
Modul-Assets müssen über /module/<modul>/asset geladen werden.
Setup-Regel: `Allgemein`, `Datenbank`, `Zugriffsrechte` und `Cron Einstellungen` kommen immer aus dem globalen Setup-System.
Nur `Custom Settings` darf modulspezifisch sein.
Für Zeitzonen und Debug nach Möglichkeit die globalen Helfer aus `src/App/functions.php` nutzen.
Global bereits im Setup vorhanden sind Navigation, Debug-Feld, DB-Logik, Zugriffsschutz, Cron-/Scheduler-Logik, Setup-Aktionen, Statusblöcke und Zeitzonen-Vererbung.
Ein neues Modul soll dafür nur noch `setup.fields`, optionale `scheduler_jobs` / `interval_tasks` und bei Bedarf `setup_actions` / `setup_status` liefern.

View File

@@ -84,17 +84,168 @@ Modulspezifische Assets:
- Mining-Checker: Seitenheader-Box, Submenü-Box, Bereichs-Box, Karten-Boxen, Karten-Boxen, Bereichs-Box
- Modulverwaltung: Seitenheader-Box, Submenü-Box, danach Karten-Boxen
8) Sicherheits-/Netzwerk-Constraints
8) Globales Setup-System (VERBINDLICH)
- Modul-Setup wird zentral über `partials/landingpages/modules/setup.php` gerendert.
- Die Bereiche
- `Allgemein`
- `Datenbank`
- `Zugriffsrechte`
- `Cron Einstellungen`
muessen fuer alle Module aus dieser gemeinsamen Setup-Logik kommen.
- Nur `Custom Settings` darf modulspezifischen Inhalt enthalten.
- Modul-spezifische Sonderlayouts fuer die Bereiche `Allgemein`, `Datenbank`, `Zugriffsrechte` oder `Cron Einstellungen` sind nicht erlaubt.
- Wenn sich das Verhalten eines dieser Bereiche ändert, muss die Änderung zentral erfolgen, so dass sie automatisch fuer alle Module gilt.
Was global im Setup bereits verfügbar ist:
- gemeinsame Setup-Navigation mit festen Unterseiten
- rechte Aktionsseite mit
- `Nexus Übersicht`
- `Zurück zum Modul`
- gemeinsames Speichern pro Bereich
- gemeinsames Rendering von
- Textfeldern
- Zahlenfeldern
- Checkboxen
- Selects
- Multiselects
- Textareas
- globale `Debug aktivieren`-Option pro Modul im Bereich `Allgemein`
- gemeinsame Datenbank-Logik mit
- `Eigene Modul-DB nutzen`
- Anzeige/Verbergen der Custom-DB-Felder
- optional mehreren DB-Gruppen wie `db.*` und `metadata_db.*`
- `Verbindung testen`
- `Standardwerte laden`
- gemeinsame Auth-/Zugriffslogik mit
- `Login erforderlich`
- erlaubten Benutzern
- erlaubten Gruppen
- bekannten Keycloak-Benutzern und -Gruppen
- gemeinsame Cron-/Scheduler-Logik mit
- Anzeige von Intervall-Tasks
- Anzeige von Cron-Jobs
- Bearbeiten von Cron-Einträgen im Modal
- Cron-Test direkt aus dem Setup
- Mehrfacheinträgen bei `mode = multi`
- Modul-Zeitzonen-Override fuer Crons
- Vererbung globaler Cron-Zeitzonen-Defaults
- gemeinsame Darstellung von Setup-Aktionen und Statusblöcken
- globale Zeitzonen-Datalist aus `nexus_timezones`
Was ein Modul für das Setup nur noch liefern muss:
- `module.json` mit `setup.fields`
- optional `setup.sections.database`
- optional `interval_tasks`
- optional `scheduler_jobs`
- optional `auth`
- optional Bootstrap-Funktionen:
- `setup_actions`
- `run_setup_action`
- `setup_status`
- `runtime_settings`
- `save_runtime_settings`
Was nicht mehr jedes Modul selbst bauen darf:
- eigene Setup-Seitenstruktur fuer `Allgemein`, `Datenbank`, `Zugriffsrechte`, `Cron Einstellungen`
- eigene DB-Toggle-Logik fuer Standard/Custom
- eigene Cron-Editor-Grundlogik
- eigene Debug-UI-Grundlogik
- eigene globale Zeitzonen-Defaults
Setup-Navigation:
- Setup-Routen laufen zentral ueber:
- `/modules/setup/<modul>/general`
- `/modules/setup/<modul>/database`
- `/modules/setup/<modul>/access`
- `/modules/setup/<modul>/cron`
- `/modules/setup/<modul>/custom`
- `Setup` gehoert in der Modulansicht in die rechte Aktionsseite der Submenü-Box.
Steuerung per `module.json`:
- Ein Modul kann ueber `setup.sections.database: true|false` steuern, ob der Menüpunkt `Datenbank` angezeigt wird.
- Wenn `setup.sections.database` fehlt, kann die zentrale Setup-Logik den Punkt implizit aktivieren, sobald DB-Felder vorhanden sind.
- Modulfelder fuer `Allgemein`, `Datenbank`, `Cron` und `Custom Settings` werden zentral nach Feldnamen aufgeteilt:
- `debug_enabled` -> `Allgemein`
- `use_separate_db` und `db.*` / `metadata_db.*` -> `Datenbank`
- `schedule_timezone` -> `Cron Einstellungen`
- alle übrigen Setup-Felder -> `Custom Settings`
Weitere anerkannte Setup-Bausteine:
- `interval_tasks`
- fuer automatische Aufgaben beim Modulaufruf
- `scheduler_jobs`
- fuer zentrale Cron-Jobs ueber den Nexus-Scheduler
- `auth`
- fuer den initialen Modulschutz
- `db_defaults`
- fuer vorbefuellte Standard-DB-Werte im Datenbankbereich
- `metadata_db_defaults`
- fuer weitere globale DB-Gruppen im Datenbankbereich
Speicherregel:
- Beim Speichern eines Setup-Bereichs duerfen nur die in diesem Bereich sichtbaren Felder aktualisiert werden.
- Felder aus anderen Bereichen duerfen nicht mit `null`, `0` oder leeren Strings ueberschrieben werden.
Datenbankbereich:
- `Eigene Modul-DB nutzen` ist der zentrale Standard-Schalter fuer Module mit optionaler eigener DB.
- Wenn der Schalter deaktiviert ist, duerfen keine Custom-DB-Eingabefelder sichtbar sein.
- Datenbankaktionen und Tabellenstatus gehoeren in den Menüpunkt `Datenbank`, nicht in `Custom Settings`.
9) Globale Zeitzonen-Logik (VERBINDLICH)
- Globale Nexus-Einstellungen liegen unter `/settings`.
- Dort werden zentral gepflegt:
- `Anzeige-Zeitzone`
- `Standard-Zeitzone für Crons`
- `Anzeige-Zeitzone`:
- hat eine `Custom`-Checkbox
- ohne Custom wird die System-Zeitzone verwendet
- die aktive Zeitzone soll angezeigt werden
- `Standard-Zeitzone für Crons`:
- ist der globale Default fuer Modul-Crons
- Module duerfen diese Zeitzone im Setup uebersteuern
- einzelne Cron-Einträge duerfen sie ebenfalls uebersteuern
Modul-Cron-Verhalten:
- Im Modul-Setup gibt es keinen nackten Pflicht-Default mehr, der immer direkt eingegeben werden muss.
- Stattdessen:
- Anzeige des aktuell wirksamen Modul-Cron-Defaults
- `Custom-Zeitzone verwenden` fuer Modul-Override
- einzelne Cron-Einträge koennen ebenfalls `Custom-Zeitzone verwenden`
- Ohne Override erbt ein Cron-Eintrag die Modul-Zeitzone, und die Modul-Zeitzone erbt den globalen Nexus-Cron-Default.
Globale PHP-Helfer:
- Neue Module sollen fuer zentrale Zeit-/Debug-Defaults nach Moeglichkeit die globalen Funktionen aus `src/App/functions.php` nutzen:
- `nexus_settings()`
- `nexus_save_settings(array $settings)`
- `nexus_system_timezone_name()`
- `nexus_display_timezone_name()`
- `nexus_cron_timezone_name()`
- `module_debug_enabled(string $module)`
- `module_debug_push(string $module, array $entry)`
- `module_debug_clear(string $module)`
Regel fuer neue Module:
- Keine Zeitzone wie `Europe/Berlin` hart im Modul als Standard erzwingen, wenn dafuer ein globaler Nexus-Default existiert.
- Fuer Anzeige-/Formatierungslogik nach Moeglichkeit `nexus_display_timezone_name()` nutzen.
- Fuer Cron-Fallbacks nach Moeglichkeit `nexus_cron_timezone_name()` nutzen.
10) Globales Debug-System
- Das Debug-Popup ist eine globale Infrastruktur aus dem zentralen Layout.
- Aktivierung bleibt jedoch pro Modul ueber `debug_enabled` im Modul-Setup.
- Das Debug-Symbol darf nur sichtbar sein, wenn fuer das aktuelle Modul Debug aktiv ist.
- Module sollen keine eigene separate Debug-Oberflaeche bauen, wenn der globale Debug-Stream genutzt werden kann.
11) Sicherheits-/Netzwerk-Constraints
- Zugriff im Heimnetz (192.168.178.0/24) per Nginx begrenzt.
- SSH-Hosts nur Heimnetz.
9) Pi Control Besonderheiten (konkret)
12) Pi Control Besonderheiten (konkret)
- Worker/Jobs unter /tools/pi_control/
- Check-Updates & Cron nutzen die gleichen SSH-Routinen.
- Host-Karten, Befehle und Konsole sind UI im Modul.
- Update/Upgrade-Checks liefern Debug-Ausgaben, die als Tooltip oder Debugzeile angezeigt werden.
10) Zusammenfassung (kurz)
13) Zusammenfassung (kurz)
Nexus ist modular, mit strikter Trennung zwischen globalem Layout und modulspezifischem Code.
Staging/Live haben eigene /app/<env>/config-Strukturen; /config/<env> im Repo wird beim Deployment kopiert.
Modul-Assets gehören ausschließlich in den Modul-Ordner und werden dort geladen.

View File

@@ -574,7 +574,7 @@ $mm->registerFunction($moduleName, 'format_datetime_for_display', static functio
});
$mm->registerFunction($moduleName, 'local_now_input_value', static function (): string {
return (new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin')))->format('Y-m-d\TH:i');
return (new \DateTimeImmutable('now', new \DateTimeZone(nexus_display_timezone_name())))->format('Y-m-d\TH:i');
});
$mm->registerFunction($moduleName, 'alpha_vantage_extract_global_quote', static function (array $entry): ?array {

View File

@@ -103,7 +103,7 @@ if ($daily === []) {
$aggregate = static function (array $points, string $format): array {
$result = [];
$timezone = new DateTimeZone('Europe/Berlin');
$timezone = new DateTimeZone(nexus_display_timezone_name());
foreach ($points as $point) {
$date = DateTimeImmutable::createFromFormat('Y-m-d', (string) ($point['date'] ?? ''), $timezone);
if (!$date instanceof DateTimeImmutable) {

View File

@@ -840,7 +840,7 @@ final class DashboardPage
private function normalizeDateTimeLocal(?string $value): string
{
$timezone = new \DateTimeZone('Europe/Berlin');
$timezone = new \DateTimeZone(nexus_display_timezone_name());
$value = trim((string) $value);
if ($value === '') {
return (new \DateTimeImmutable('now', $timezone))->format('Y-m-d H:i:s');

View File

@@ -298,7 +298,7 @@ final class InstrumentPage
private function normalizeDateTimeLocal(?string $value): string
{
$timezone = new \DateTimeZone('Europe/Berlin');
$timezone = new \DateTimeZone(nexus_display_timezone_name());
$value = trim((string) $value);
if ($value === '') {
return (new \DateTimeImmutable('now', $timezone))->format('Y-m-d H:i:s');

View File

@@ -68,7 +68,7 @@ $mm->registerFunction($moduleName, 'settings', static function (): array {
'preferred_currencies' => $preferredCurrencies,
'currency_catalog' => $currencyCatalog,
'currency_catalog_synced_at' => trim((string) ($saved['currency_catalog_synced_at'] ?? '')),
'schedule_timezone' => trim((string) ($saved['schedule_timezone'] ?? 'Europe/Berlin')) ?: 'Europe/Berlin',
'schedule_timezone' => trim((string) ($saved['schedule_timezone'] ?? nexus_cron_timezone_name())) ?: nexus_cron_timezone_name(),
];
});

View File

@@ -991,11 +991,11 @@ final class FxRatesService
private function scheduleTimezone(): DateTimeZone
{
$timezone = trim((string) ($this->settings['schedule_timezone'] ?? 'Europe/Berlin'));
$timezone = trim((string) ($this->settings['schedule_timezone'] ?? nexus_cron_timezone_name()));
try {
return new DateTimeZone($timezone);
} catch (\Throwable) {
return new DateTimeZone('Europe/Berlin');
return new DateTimeZone(nexus_cron_timezone_name());
}
}

View File

@@ -34,9 +34,9 @@ $mm->registerFunction($moduleName, 'runtime_settings', static function (): array
$repository = new MiningRepository($pdo, $config->tablePrefix(), null, $ownerSub);
$settings = $repository->getSettings($projectKey) ?? [];
$displayTimezone = trim((string) ($settings['display_timezone'] ?? 'Europe/Berlin'));
$displayTimezone = trim((string) ($settings['display_timezone'] ?? nexus_display_timezone_name()));
if ($displayTimezone === '') {
$displayTimezone = 'Europe/Berlin';
$displayTimezone = nexus_display_timezone_name();
}
$baselineMeasuredAt = trim((string) ($settings['baseline_measured_at'] ?? ''));
@@ -72,9 +72,9 @@ $mm->registerFunction($moduleName, 'save_runtime_settings', static function (arr
$repository->ensureProject($projectKey, strtoupper(str_replace('-', ' ', $projectKey)));
$existing = $repository->getSettings($projectKey) ?? [];
$displayTimezone = trim((string) ($existing['display_timezone'] ?? 'Europe/Berlin'));
$displayTimezone = trim((string) ($existing['display_timezone'] ?? nexus_display_timezone_name()));
if ($displayTimezone === '') {
$displayTimezone = 'Europe/Berlin';
$displayTimezone = nexus_display_timezone_name();
}
try {

View File

@@ -326,7 +326,7 @@ final class Router
'daily_cost_currency' => 'EUR',
'report_currency' => 'EUR',
'crypto_currency' => 'DOGE',
'display_timezone' => 'Europe/Berlin',
'display_timezone' => nexus_display_timezone_name(),
'fx_max_age_hours' => self::FX_FETCH_MAX_AGE_HOURS,
'module_theme_mode' => 'inherit',
'module_theme_accent' => 'teal',
@@ -525,7 +525,7 @@ final class Router
'daily_cost_currency' => $backup['settings']['daily_cost_currency'],
'report_currency' => $backup['settings']['report_currency'] ?? 'EUR',
'crypto_currency' => $backup['settings']['crypto_currency'] ?? 'DOGE',
'display_timezone' => $backup['settings']['display_timezone'] ?? 'Europe/Berlin',
'display_timezone' => $backup['settings']['display_timezone'] ?? nexus_display_timezone_name(),
'fx_max_age_hours' => self::FX_FETCH_MAX_AGE_HOURS,
'module_theme_mode' => $backup['settings']['module_theme_mode'] ?? 'inherit',
'module_theme_accent' => $backup['settings']['module_theme_accent'] ?? 'teal',
@@ -1158,13 +1158,13 @@ final class Router
'daily_cost_currency' => 'EUR',
'report_currency' => 'EUR',
'crypto_currency' => 'DOGE',
'display_timezone' => 'Europe/Berlin',
'display_timezone' => nexus_display_timezone_name(),
'module_theme_mode' => 'inherit',
'module_theme_accent' => 'teal',
'preferred_currencies' => $this->preferredCurrencies(),
];
if (!$this->isValidTimezone((string) ($base['display_timezone'] ?? ''))) {
$base['display_timezone'] = 'Europe/Berlin';
$base['display_timezone'] = nexus_display_timezone_name();
}
if (!in_array((string) ($base['module_theme_mode'] ?? ''), ['inherit', 'custom'], true)) {
$base['module_theme_mode'] = 'inherit';
@@ -1187,7 +1187,7 @@ final class Router
{
$existingSettings = $this->repository()->getSettings($projectKey) ?? [];
$displayTimezone = $this->requiredTimezone(
$input['display_timezone'] ?? ($existingSettings['display_timezone'] ?? 'Europe/Berlin'),
$input['display_timezone'] ?? ($existingSettings['display_timezone'] ?? nexus_display_timezone_name()),
'display_timezone'
);
$settings = [
@@ -2069,7 +2069,7 @@ final class Router
{
$settings = $this->repository()->getSettings($projectKey);
$timezone = is_array($settings) ? (string) ($settings['display_timezone'] ?? '') : '';
return $this->isValidTimezone($timezone) ? $timezone : 'Europe/Berlin';
return $this->isValidTimezone($timezone) ? $timezone : nexus_display_timezone_name();
}
private function utcTimezone(): \DateTimeZone

View File

@@ -103,7 +103,7 @@ final class MiningRepository
'daily_cost_currency' => $settings['daily_cost_currency'],
'report_currency' => $settings['report_currency'] ?? 'EUR',
'crypto_currency' => $settings['crypto_currency'] ?? 'DOGE',
'display_timezone' => $settings['display_timezone'] ?? 'Europe/Berlin',
'display_timezone' => $settings['display_timezone'] ?? nexus_display_timezone_name(),
'fx_max_age_hours' => $settings['fx_max_age_hours'] ?? 3,
'module_theme_mode' => $settings['module_theme_mode'] ?? 'inherit',
'module_theme_accent' => $settings['module_theme_accent'] ?? 'teal',