diff --git a/Customimport/salesforcelanguageimport.html b/Customimport/salesforcelanguageimport.html deleted file mode 100644 index 72035a3f..00000000 --- a/Customimport/salesforcelanguageimport.html +++ /dev/null @@ -1,993 +0,0 @@ - - - - - -Salesforce XLIFF Translation Tool - - - - - - - - -

Salesforce XLIFF Translation Tool

- -
-

How to use this tool

-
    -
  1. Export the required target languages from Salesforce in XLIFF format.
  2. -
  3. Salesforce may provide several language files in a ZIP archive. You can upload the Salesforce ZIP directly or select extracted XLIFF files.
  4. -
  5. Under Salesforce XLIFF export files, select the ZIP or the individual XLIFF files for the languages you want to translate. The tool automatically reads all XLIFF files from a ZIP and detects each file's target-language value.
  6. -
  7. Prepare one Excel or CSV target translation file and upload it under Target translation.
  8. -
  9. Check the language matches, create the preview, and then download the translated files as a ZIP archive.
  10. -
  11. Extract the generated ZIP and import each completed target-language XLIFF file into Salesforce separately. Salesforce accepts one translation file per import.
  12. -
- -

Required columns: Header names are case-insensitive. The English headers object, field, and target are recommended. The existing German aliases Objekt and Feld also remain supported.

- - - - - - - - - - - - - - - - - - - - - - - - -
objectfieldtargetlanguage
AccountNameKontonamede
AccountNameNom du comptefr
- -

The optional language column lets one target translation file contain several languages. Its values must match the XLIFF target-language values, for example de, fr, or de-DE. If the column is omitted and several Salesforce XLIFF files are loaded, select the one target XLIFF file to which the translation data belongs.

-

Important: Each matched target language is exported as a separate XLIFF file inside the generated ZIP. A language file is omitted when no matching translation can be applied. The result contains only changed translation units with a non-empty target value. Rows with an empty target, rows where field and target are identical, and translations identical to the current XLIFF source or target are omitted.

-
- -
-
- -
No Salesforce XLIFF export files selected.
-
- -
-
- -
No target translation file selected.
- -
- -
-
- -
- -
- - -
- -
- -
- - - - - - - - - - - - -
FileLanguageObjectFieldCurrent targetResult target
-
- - - - - \ No newline at end of file diff --git a/config/apps.php b/config/apps.php index 9d22c3d3..08dd4229 100644 --- a/config/apps.php +++ b/config/apps.php @@ -69,10 +69,10 @@ return [ 'options' => [], 'assets' => [ 'styles' => [ - ['href' => '/assets/apps/user-self-management/app.css'], + ['href' => '/system-app-assets/index.php?app=user-self-management&path=assets/app.css'], ], 'scripts' => [ - ['src' => '/assets/apps/user-self-management/app.js'], + ['src' => '/system-app-assets/index.php?app=user-self-management&path=assets/app.js'], ], ], ], @@ -116,10 +116,10 @@ return [ ], 'assets' => [ 'styles' => [ - ['href' => '/assets/apps/user-management/app.css'], + ['href' => '/system-app-assets/index.php?app=user-management&path=assets/app.css'], ], 'scripts' => [ - ['src' => '/assets/apps/user-management/app.js'], + ['src' => '/system-app-assets/index.php?app=user-management&path=assets/app.js'], ], ], ], @@ -148,10 +148,10 @@ return [ ], 'assets' => [ 'styles' => [ - ['href' => '/assets/apps/cron-management/app.css'], + ['href' => '/system-app-assets/index.php?app=cron-management&path=assets/app.css'], ], 'scripts' => [ - ['src' => '/assets/apps/cron-management/app.js'], + ['src' => '/system-app-assets/index.php?app=cron-management&path=assets/app.js'], ], ], ], diff --git a/modules/README.md b/custom/apps/README.md similarity index 90% rename from modules/README.md rename to custom/apps/README.md index f3796e36..c9f99854 100644 --- a/modules/README.md +++ b/custom/apps/README.md @@ -1,6 +1,6 @@ # Module -Klassische Module bleiben in diesem Projekt unter `modules//`. +Installierbare und fachliche Apps liegen in diesem Projekt unter `custom/apps//`. Die neue Desktop-Shell ist nur die UI-Schicht. Modul-Businesslogik wird nicht in den Desktop-Core verschoben. @@ -15,13 +15,13 @@ Ein Desktop-Modul kann zusaetzlich diese Projektdateien besitzen: - `docs/README.md` fuer modulspezifische Hinweise, API und Sonderregeln - `module.json` fuer Setup, Desktop-Freigabe, Widget-Funktionen und Cron-Endpunkte -Module-Assets werden in diesem Projekt nicht direkt aus `public/assets/apps/...` dupliziert, sondern ueber den generischen Endpoint `/module-assets/index.php` aus dem jeweiligen Modul ausgeliefert. +Custom-App-Assets werden nicht direkt aus `public/` geladen, sondern ueber den generischen Endpoint `/module-assets/index.php` aus dem jeweiligen App-Ordner ausgeliefert. ## Trennung zwischen Systemtools und Modulen - `Systemtools` sind globale Verwaltungs- und Setup-Apps und nicht installierbar - aktuelle Beispiele: `User Management`, `User Self Management`, `Cron Tool` -- `Module` sind installierbare Fachanwendungen +- `Custom Apps` sind installierbare Fachanwendungen - aktuelle Beispiele: `Mining-Checker`, `Waehrungs-Checker`, `Boersenchecker`, `Pi-hole` Diese Trennung wird ueber App-Metadaten gesteuert: diff --git a/modules/boersenchecker/api/chart_data.php b/custom/apps/boersenchecker/api/chart_data.php similarity index 100% rename from modules/boersenchecker/api/chart_data.php rename to custom/apps/boersenchecker/api/chart_data.php diff --git a/modules/boersenchecker/api/index.php b/custom/apps/boersenchecker/api/index.php similarity index 100% rename from modules/boersenchecker/api/index.php rename to custom/apps/boersenchecker/api/index.php diff --git a/modules/boersenchecker/assets/boersenchecker-native.js b/custom/apps/boersenchecker/assets/boersenchecker-native.js similarity index 100% rename from modules/boersenchecker/assets/boersenchecker-native.js rename to custom/apps/boersenchecker/assets/boersenchecker-native.js diff --git a/modules/boersenchecker/assets/boersenchecker.css b/custom/apps/boersenchecker/assets/boersenchecker.css similarity index 100% rename from modules/boersenchecker/assets/boersenchecker.css rename to custom/apps/boersenchecker/assets/boersenchecker.css diff --git a/modules/boersenchecker/assets/boersenchecker.js b/custom/apps/boersenchecker/assets/boersenchecker.js similarity index 100% rename from modules/boersenchecker/assets/boersenchecker.js rename to custom/apps/boersenchecker/assets/boersenchecker.js diff --git a/modules/boersenchecker/bootstrap.php b/custom/apps/boersenchecker/bootstrap.php similarity index 100% rename from modules/boersenchecker/bootstrap.php rename to custom/apps/boersenchecker/bootstrap.php diff --git a/modules/boersenchecker/config/module.php b/custom/apps/boersenchecker/config/module.php similarity index 100% rename from modules/boersenchecker/config/module.php rename to custom/apps/boersenchecker/config/module.php diff --git a/modules/boersenchecker/design.json b/custom/apps/boersenchecker/design.json similarity index 100% rename from modules/boersenchecker/design.json rename to custom/apps/boersenchecker/design.json diff --git a/modules/boersenchecker/desktop.php b/custom/apps/boersenchecker/desktop.php similarity index 100% rename from modules/boersenchecker/desktop.php rename to custom/apps/boersenchecker/desktop.php diff --git a/modules/boersenchecker/docs/README.md b/custom/apps/boersenchecker/docs/README.md similarity index 92% rename from modules/boersenchecker/docs/README.md rename to custom/apps/boersenchecker/docs/README.md index fbfca4c2..557594cb 100644 --- a/modules/boersenchecker/docs/README.md +++ b/custom/apps/boersenchecker/docs/README.md @@ -4,7 +4,7 @@ Importiertes Altmodul fuer Depotverwaltung, Aktienstammdaten, Kursverlauf, Alpha ## Einbindung -- Desktop-App ueber `modules/boersenchecker/desktop.php` +- Desktop-App ueber `custom/apps/boersenchecker/desktop.php` - oeffentlicher App-Einstieg ueber `public/apps/boersenchecker/index.php` - API-Endpunkt fuer Chartdaten ueber `public/api/boersenchecker/index.php?path=v1/chart-data` diff --git a/modules/boersenchecker/module.json b/custom/apps/boersenchecker/module.json similarity index 100% rename from modules/boersenchecker/module.json rename to custom/apps/boersenchecker/module.json diff --git a/modules/boersenchecker/pages/index.php b/custom/apps/boersenchecker/pages/index.php similarity index 100% rename from modules/boersenchecker/pages/index.php rename to custom/apps/boersenchecker/pages/index.php diff --git a/modules/boersenchecker/partials/dashboard.php b/custom/apps/boersenchecker/partials/dashboard.php similarity index 100% rename from modules/boersenchecker/partials/dashboard.php rename to custom/apps/boersenchecker/partials/dashboard.php diff --git a/modules/boersenchecker/partials/home.php b/custom/apps/boersenchecker/partials/home.php similarity index 100% rename from modules/boersenchecker/partials/home.php rename to custom/apps/boersenchecker/partials/home.php diff --git a/modules/boersenchecker/partials/instruments.php b/custom/apps/boersenchecker/partials/instruments.php similarity index 100% rename from modules/boersenchecker/partials/instruments.php rename to custom/apps/boersenchecker/partials/instruments.php diff --git a/modules/boersenchecker/src/Support/DashboardPage.php b/custom/apps/boersenchecker/src/Support/DashboardPage.php similarity index 100% rename from modules/boersenchecker/src/Support/DashboardPage.php rename to custom/apps/boersenchecker/src/Support/DashboardPage.php diff --git a/modules/boersenchecker/src/Support/HomePage.php b/custom/apps/boersenchecker/src/Support/HomePage.php similarity index 100% rename from modules/boersenchecker/src/Support/HomePage.php rename to custom/apps/boersenchecker/src/Support/HomePage.php diff --git a/modules/boersenchecker/src/Support/InstrumentPage.php b/custom/apps/boersenchecker/src/Support/InstrumentPage.php similarity index 100% rename from modules/boersenchecker/src/Support/InstrumentPage.php rename to custom/apps/boersenchecker/src/Support/InstrumentPage.php diff --git a/modules/boersenchecker/src/Support/InstrumentRegistry.php b/custom/apps/boersenchecker/src/Support/InstrumentRegistry.php similarity index 100% rename from modules/boersenchecker/src/Support/InstrumentRegistry.php rename to custom/apps/boersenchecker/src/Support/InstrumentRegistry.php diff --git a/modules/fx-rates/api/index.php b/custom/apps/fx-rates/api/index.php similarity index 100% rename from modules/fx-rates/api/index.php rename to custom/apps/fx-rates/api/index.php diff --git a/modules/fx-rates/assets/css/app.css b/custom/apps/fx-rates/assets/css/app.css similarity index 100% rename from modules/fx-rates/assets/css/app.css rename to custom/apps/fx-rates/assets/css/app.css diff --git a/modules/fx-rates/assets/js/app.js b/custom/apps/fx-rates/assets/js/app.js similarity index 100% rename from modules/fx-rates/assets/js/app.js rename to custom/apps/fx-rates/assets/js/app.js diff --git a/modules/fx-rates/bootstrap.php b/custom/apps/fx-rates/bootstrap.php similarity index 100% rename from modules/fx-rates/bootstrap.php rename to custom/apps/fx-rates/bootstrap.php diff --git a/modules/fx-rates/config/example.config.php b/custom/apps/fx-rates/config/example.config.php similarity index 100% rename from modules/fx-rates/config/example.config.php rename to custom/apps/fx-rates/config/example.config.php diff --git a/modules/fx-rates/config/module.php b/custom/apps/fx-rates/config/module.php similarity index 100% rename from modules/fx-rates/config/module.php rename to custom/apps/fx-rates/config/module.php diff --git a/modules/fx-rates/design.json b/custom/apps/fx-rates/design.json similarity index 100% rename from modules/fx-rates/design.json rename to custom/apps/fx-rates/design.json diff --git a/modules/fx-rates/desktop.php b/custom/apps/fx-rates/desktop.php similarity index 100% rename from modules/fx-rates/desktop.php rename to custom/apps/fx-rates/desktop.php diff --git a/modules/fx-rates/docs/README.md b/custom/apps/fx-rates/docs/README.md similarity index 94% rename from modules/fx-rates/docs/README.md rename to custom/apps/fx-rates/docs/README.md index 178435a3..035e77e9 100644 --- a/modules/fx-rates/docs/README.md +++ b/custom/apps/fx-rates/docs/README.md @@ -4,7 +4,7 @@ Das Modul `fx-rates` stellt gespeicherte Waehrungskurse, Historie, Umrechnung un ## Kernpunkte -- Desktop-App unter `modules/fx-rates/desktop.php` +- Desktop-App unter `custom/apps/fx-rates/desktop.php` - API unter `/api/fx-rates/...` - Widget fuer den rechten Desktop-Infobereich ueber `config/widgets.php` - externer Provider-Token wird aus dem alten Bestand als Default uebernommen diff --git a/modules/fx-rates/module.json b/custom/apps/fx-rates/module.json similarity index 100% rename from modules/fx-rates/module.json rename to custom/apps/fx-rates/module.json diff --git a/modules/fx-rates/pages/index.php b/custom/apps/fx-rates/pages/index.php similarity index 100% rename from modules/fx-rates/pages/index.php rename to custom/apps/fx-rates/pages/index.php diff --git a/modules/fx-rates/src/Api/Router.php b/custom/apps/fx-rates/src/Api/Router.php similarity index 100% rename from modules/fx-rates/src/Api/Router.php rename to custom/apps/fx-rates/src/Api/Router.php diff --git a/modules/fx-rates/src/Domain/FxRatesService.php b/custom/apps/fx-rates/src/Domain/FxRatesService.php similarity index 100% rename from modules/fx-rates/src/Domain/FxRatesService.php rename to custom/apps/fx-rates/src/Domain/FxRatesService.php diff --git a/modules/fx-rates/src/Infrastructure/ConnectionFactory.php b/custom/apps/fx-rates/src/Infrastructure/ConnectionFactory.php similarity index 100% rename from modules/fx-rates/src/Infrastructure/ConnectionFactory.php rename to custom/apps/fx-rates/src/Infrastructure/ConnectionFactory.php diff --git a/modules/fx-rates/src/Infrastructure/FxRatesRepository.php b/custom/apps/fx-rates/src/Infrastructure/FxRatesRepository.php similarity index 100% rename from modules/fx-rates/src/Infrastructure/FxRatesRepository.php rename to custom/apps/fx-rates/src/Infrastructure/FxRatesRepository.php diff --git a/modules/fx-rates/src/Infrastructure/ModuleConfig.php b/custom/apps/fx-rates/src/Infrastructure/ModuleConfig.php similarity index 100% rename from modules/fx-rates/src/Infrastructure/ModuleConfig.php rename to custom/apps/fx-rates/src/Infrastructure/ModuleConfig.php diff --git a/modules/fx-rates/src/Infrastructure/SettingsStore.php b/custom/apps/fx-rates/src/Infrastructure/SettingsStore.php similarity index 100% rename from modules/fx-rates/src/Infrastructure/SettingsStore.php rename to custom/apps/fx-rates/src/Infrastructure/SettingsStore.php diff --git a/modules/fx-rates/storage/.gitkeep b/custom/apps/fx-rates/storage/.gitkeep similarity index 100% rename from modules/fx-rates/storage/.gitkeep rename to custom/apps/fx-rates/storage/.gitkeep diff --git a/modules/mining-checker/api/index.php b/custom/apps/mining-checker/api/index.php similarity index 100% rename from modules/mining-checker/api/index.php rename to custom/apps/mining-checker/api/index.php diff --git a/modules/mining-checker/assets/css/.gitkeep b/custom/apps/mining-checker/assets/css/.gitkeep similarity index 100% rename from modules/mining-checker/assets/css/.gitkeep rename to custom/apps/mining-checker/assets/css/.gitkeep diff --git a/modules/mining-checker/assets/css/app.css b/custom/apps/mining-checker/assets/css/app.css similarity index 100% rename from modules/mining-checker/assets/css/app.css rename to custom/apps/mining-checker/assets/css/app.css diff --git a/modules/mining-checker/assets/js/.gitkeep b/custom/apps/mining-checker/assets/js/.gitkeep similarity index 100% rename from modules/mining-checker/assets/js/.gitkeep rename to custom/apps/mining-checker/assets/js/.gitkeep diff --git a/modules/mining-checker/assets/js/app.js b/custom/apps/mining-checker/assets/js/app.js similarity index 100% rename from modules/mining-checker/assets/js/app.js rename to custom/apps/mining-checker/assets/js/app.js diff --git a/modules/mining-checker/bootstrap.php b/custom/apps/mining-checker/bootstrap.php similarity index 100% rename from modules/mining-checker/bootstrap.php rename to custom/apps/mining-checker/bootstrap.php diff --git a/modules/mining-checker/config/example.config.php b/custom/apps/mining-checker/config/example.config.php similarity index 100% rename from modules/mining-checker/config/example.config.php rename to custom/apps/mining-checker/config/example.config.php diff --git a/modules/mining-checker/config/module.php b/custom/apps/mining-checker/config/module.php similarity index 100% rename from modules/mining-checker/config/module.php rename to custom/apps/mining-checker/config/module.php diff --git a/modules/mining-checker/design.json b/custom/apps/mining-checker/design.json similarity index 85% rename from modules/mining-checker/design.json rename to custom/apps/mining-checker/design.json index c5427d41..fa85d7f6 100644 --- a/modules/mining-checker/design.json +++ b/custom/apps/mining-checker/design.json @@ -4,7 +4,7 @@ "description": "Erfassung, OCR-Auswertung und Analyse von DOGE-Mining-Messwerten als eingebettetes Modul.", "actions": [ { "label": "Nexus Übersicht", "href": "/modules" }, - { "label": "Setup", "href": "/modules/setup/mining-checker", "variant": "secondary" } + { "label": "Setup", "href": "/apps/mining-checker?view=setup", "variant": "secondary" } ], "sections": [ { "key": "overview", "label": "Übersicht" }, diff --git a/modules/mining-checker/desktop.php b/custom/apps/mining-checker/desktop.php similarity index 100% rename from modules/mining-checker/desktop.php rename to custom/apps/mining-checker/desktop.php diff --git a/modules/mining-checker/docs/README.md b/custom/apps/mining-checker/docs/README.md similarity index 99% rename from modules/mining-checker/docs/README.md rename to custom/apps/mining-checker/docs/README.md index 89af70a6..f2149bd0 100644 --- a/modules/mining-checker/docs/README.md +++ b/custom/apps/mining-checker/docs/README.md @@ -7,7 +7,7 @@ Das Modul erfasst DOGE-Mining-Messpunkte, analysiert OCR-Vorschlaege aus Screens ## Ordnerstruktur ```text -modules/mining-checker/ +custom/apps/mining-checker/ |-- api/ |-- assets/ | |-- css/ diff --git a/modules/mining-checker/module.json b/custom/apps/mining-checker/module.json similarity index 100% rename from modules/mining-checker/module.json rename to custom/apps/mining-checker/module.json diff --git a/modules/mining-checker/pages/index.php b/custom/apps/mining-checker/pages/index.php similarity index 100% rename from modules/mining-checker/pages/index.php rename to custom/apps/mining-checker/pages/index.php diff --git a/modules/mining-checker/partials/.gitkeep b/custom/apps/mining-checker/partials/.gitkeep similarity index 100% rename from modules/mining-checker/partials/.gitkeep rename to custom/apps/mining-checker/partials/.gitkeep diff --git a/modules/mining-checker/sql/migrations/001_init.sql b/custom/apps/mining-checker/sql/migrations/001_init.sql similarity index 100% rename from modules/mining-checker/sql/migrations/001_init.sql rename to custom/apps/mining-checker/sql/migrations/001_init.sql diff --git a/modules/mining-checker/sql/migrations/002_seed_doge_main.sql b/custom/apps/mining-checker/sql/migrations/002_seed_doge_main.sql similarity index 100% rename from modules/mining-checker/sql/migrations/002_seed_doge_main.sql rename to custom/apps/mining-checker/sql/migrations/002_seed_doge_main.sql diff --git a/modules/mining-checker/sql/migrations/003_timezone_utc.sql b/custom/apps/mining-checker/sql/migrations/003_timezone_utc.sql similarity index 100% rename from modules/mining-checker/sql/migrations/003_timezone_utc.sql rename to custom/apps/mining-checker/sql/migrations/003_timezone_utc.sql diff --git a/modules/mining-checker/sql/migrations/004_merge_cost_plans_into_purchased_miners.sql b/custom/apps/mining-checker/sql/migrations/004_merge_cost_plans_into_purchased_miners.sql similarity index 100% rename from modules/mining-checker/sql/migrations/004_merge_cost_plans_into_purchased_miners.sql rename to custom/apps/mining-checker/sql/migrations/004_merge_cost_plans_into_purchased_miners.sql diff --git a/modules/mining-checker/sql/migrations/005_module_theme_settings.sql b/custom/apps/mining-checker/sql/migrations/005_module_theme_settings.sql similarity index 100% rename from modules/mining-checker/sql/migrations/005_module_theme_settings.sql rename to custom/apps/mining-checker/sql/migrations/005_module_theme_settings.sql diff --git a/modules/mining-checker/sql/migrations/006_user_scope_owner_sub.sql b/custom/apps/mining-checker/sql/migrations/006_user_scope_owner_sub.sql similarity index 100% rename from modules/mining-checker/sql/migrations/006_user_scope_owner_sub.sql rename to custom/apps/mining-checker/sql/migrations/006_user_scope_owner_sub.sql diff --git a/modules/mining-checker/sql/schema.mysql.sql b/custom/apps/mining-checker/sql/schema.mysql.sql similarity index 100% rename from modules/mining-checker/sql/schema.mysql.sql rename to custom/apps/mining-checker/sql/schema.mysql.sql diff --git a/modules/mining-checker/sql/schema.pgsql.sql b/custom/apps/mining-checker/sql/schema.pgsql.sql similarity index 100% rename from modules/mining-checker/sql/schema.pgsql.sql rename to custom/apps/mining-checker/sql/schema.pgsql.sql diff --git a/modules/mining-checker/sql/schema.sql b/custom/apps/mining-checker/sql/schema.sql similarity index 100% rename from modules/mining-checker/sql/schema.sql rename to custom/apps/mining-checker/sql/schema.sql diff --git a/modules/mining-checker/sql/seed.sql b/custom/apps/mining-checker/sql/seed.sql similarity index 100% rename from modules/mining-checker/sql/seed.sql rename to custom/apps/mining-checker/sql/seed.sql diff --git a/modules/mining-checker/src/Api/Router.php b/custom/apps/mining-checker/src/Api/Router.php similarity index 100% rename from modules/mining-checker/src/Api/Router.php rename to custom/apps/mining-checker/src/Api/Router.php diff --git a/modules/mining-checker/src/Domain/AnalyticsService.php b/custom/apps/mining-checker/src/Domain/AnalyticsService.php similarity index 100% rename from modules/mining-checker/src/Domain/AnalyticsService.php rename to custom/apps/mining-checker/src/Domain/AnalyticsService.php diff --git a/modules/mining-checker/src/Domain/FxService.php b/custom/apps/mining-checker/src/Domain/FxService.php similarity index 100% rename from modules/mining-checker/src/Domain/FxService.php rename to custom/apps/mining-checker/src/Domain/FxService.php diff --git a/modules/mining-checker/src/Domain/OcrService.php b/custom/apps/mining-checker/src/Domain/OcrService.php similarity index 100% rename from modules/mining-checker/src/Domain/OcrService.php rename to custom/apps/mining-checker/src/Domain/OcrService.php diff --git a/modules/mining-checker/src/Domain/SeedData.php b/custom/apps/mining-checker/src/Domain/SeedData.php similarity index 100% rename from modules/mining-checker/src/Domain/SeedData.php rename to custom/apps/mining-checker/src/Domain/SeedData.php diff --git a/modules/mining-checker/src/Domain/SeedImporter.php b/custom/apps/mining-checker/src/Domain/SeedImporter.php similarity index 100% rename from modules/mining-checker/src/Domain/SeedImporter.php rename to custom/apps/mining-checker/src/Domain/SeedImporter.php diff --git a/modules/mining-checker/src/Infrastructure/ConnectionFactory.php b/custom/apps/mining-checker/src/Infrastructure/ConnectionFactory.php similarity index 100% rename from modules/mining-checker/src/Infrastructure/ConnectionFactory.php rename to custom/apps/mining-checker/src/Infrastructure/ConnectionFactory.php diff --git a/modules/mining-checker/src/Infrastructure/MiningRepository.php b/custom/apps/mining-checker/src/Infrastructure/MiningRepository.php similarity index 100% rename from modules/mining-checker/src/Infrastructure/MiningRepository.php rename to custom/apps/mining-checker/src/Infrastructure/MiningRepository.php diff --git a/modules/mining-checker/src/Infrastructure/ModuleConfig.php b/custom/apps/mining-checker/src/Infrastructure/ModuleConfig.php similarity index 100% rename from modules/mining-checker/src/Infrastructure/ModuleConfig.php rename to custom/apps/mining-checker/src/Infrastructure/ModuleConfig.php diff --git a/modules/mining-checker/src/Infrastructure/SchemaManager.php b/custom/apps/mining-checker/src/Infrastructure/SchemaManager.php similarity index 100% rename from modules/mining-checker/src/Infrastructure/SchemaManager.php rename to custom/apps/mining-checker/src/Infrastructure/SchemaManager.php diff --git a/modules/mining-checker/src/Legacy/LegacyModuleStore.php b/custom/apps/mining-checker/src/Legacy/LegacyModuleStore.php similarity index 100% rename from modules/mining-checker/src/Legacy/LegacyModuleStore.php rename to custom/apps/mining-checker/src/Legacy/LegacyModuleStore.php diff --git a/modules/mining-checker/src/Legacy/UserScope.php b/custom/apps/mining-checker/src/Legacy/UserScope.php similarity index 100% rename from modules/mining-checker/src/Legacy/UserScope.php rename to custom/apps/mining-checker/src/Legacy/UserScope.php diff --git a/modules/mining-checker/src/Support/ApiException.php b/custom/apps/mining-checker/src/Support/ApiException.php similarity index 100% rename from modules/mining-checker/src/Support/ApiException.php rename to custom/apps/mining-checker/src/Support/ApiException.php diff --git a/modules/mining-checker/src/Support/DebugState.php b/custom/apps/mining-checker/src/Support/DebugState.php similarity index 100% rename from modules/mining-checker/src/Support/DebugState.php rename to custom/apps/mining-checker/src/Support/DebugState.php diff --git a/modules/mining-checker/src/Support/DebugTrace.php b/custom/apps/mining-checker/src/Support/DebugTrace.php similarity index 100% rename from modules/mining-checker/src/Support/DebugTrace.php rename to custom/apps/mining-checker/src/Support/DebugTrace.php diff --git a/modules/mining-checker/src/Support/Http.php b/custom/apps/mining-checker/src/Support/Http.php similarity index 100% rename from modules/mining-checker/src/Support/Http.php rename to custom/apps/mining-checker/src/Support/Http.php diff --git a/modules/mining-checker/storage/uploads/.gitkeep b/custom/apps/mining-checker/storage/uploads/.gitkeep similarity index 100% rename from modules/mining-checker/storage/uploads/.gitkeep rename to custom/apps/mining-checker/storage/uploads/.gitkeep diff --git a/modules/pihole/api/index.php b/custom/apps/pihole/api/index.php similarity index 100% rename from modules/pihole/api/index.php rename to custom/apps/pihole/api/index.php diff --git a/modules/pihole/assets/pihole-native.js b/custom/apps/pihole/assets/pihole-native.js similarity index 100% rename from modules/pihole/assets/pihole-native.js rename to custom/apps/pihole/assets/pihole-native.js diff --git a/modules/pihole/assets/pihole.css b/custom/apps/pihole/assets/pihole.css similarity index 100% rename from modules/pihole/assets/pihole.css rename to custom/apps/pihole/assets/pihole.css diff --git a/modules/pihole/assets/pihole.js b/custom/apps/pihole/assets/pihole.js similarity index 100% rename from modules/pihole/assets/pihole.js rename to custom/apps/pihole/assets/pihole.js diff --git a/modules/pihole/assets/pihole_instances.js b/custom/apps/pihole/assets/pihole_instances.js similarity index 100% rename from modules/pihole/assets/pihole_instances.js rename to custom/apps/pihole/assets/pihole_instances.js diff --git a/modules/pihole/bootstrap.php b/custom/apps/pihole/bootstrap.php similarity index 100% rename from modules/pihole/bootstrap.php rename to custom/apps/pihole/bootstrap.php diff --git a/modules/pihole/config/module.php b/custom/apps/pihole/config/module.php similarity index 100% rename from modules/pihole/config/module.php rename to custom/apps/pihole/config/module.php diff --git a/modules/pihole/design.json b/custom/apps/pihole/design.json similarity index 100% rename from modules/pihole/design.json rename to custom/apps/pihole/design.json diff --git a/modules/pihole/desktop.php b/custom/apps/pihole/desktop.php similarity index 100% rename from modules/pihole/desktop.php rename to custom/apps/pihole/desktop.php diff --git a/modules/pihole/docs/README.md b/custom/apps/pihole/docs/README.md similarity index 100% rename from modules/pihole/docs/README.md rename to custom/apps/pihole/docs/README.md diff --git a/modules/pihole/module.json b/custom/apps/pihole/module.json similarity index 100% rename from modules/pihole/module.json rename to custom/apps/pihole/module.json diff --git a/modules/pihole/pages/index.php b/custom/apps/pihole/pages/index.php similarity index 100% rename from modules/pihole/pages/index.php rename to custom/apps/pihole/pages/index.php diff --git a/modules/pihole/partials/dashboard.php b/custom/apps/pihole/partials/dashboard.php similarity index 100% rename from modules/pihole/partials/dashboard.php rename to custom/apps/pihole/partials/dashboard.php diff --git a/modules/pihole/partials/instances.php b/custom/apps/pihole/partials/instances.php similarity index 100% rename from modules/pihole/partials/instances.php rename to custom/apps/pihole/partials/instances.php diff --git a/modules/pihole/partials/lists.php b/custom/apps/pihole/partials/lists.php similarity index 100% rename from modules/pihole/partials/lists.php rename to custom/apps/pihole/partials/lists.php diff --git a/modules/pihole/partials/queries.php b/custom/apps/pihole/partials/queries.php similarity index 100% rename from modules/pihole/partials/queries.php rename to custom/apps/pihole/partials/queries.php diff --git a/modules/salesforce-translation-import/assets/css/app.css b/custom/apps/salesforce-translation-import/assets/css/app.css similarity index 100% rename from modules/salesforce-translation-import/assets/css/app.css rename to custom/apps/salesforce-translation-import/assets/css/app.css diff --git a/modules/salesforce-translation-import/assets/js/app.js b/custom/apps/salesforce-translation-import/assets/js/app.js similarity index 100% rename from modules/salesforce-translation-import/assets/js/app.js rename to custom/apps/salesforce-translation-import/assets/js/app.js diff --git a/modules/salesforce-translation-import/desktop.php b/custom/apps/salesforce-translation-import/desktop.php similarity index 100% rename from modules/salesforce-translation-import/desktop.php rename to custom/apps/salesforce-translation-import/desktop.php diff --git a/modules/salesforce-translation-import/docs/README.md b/custom/apps/salesforce-translation-import/docs/README.md similarity index 87% rename from modules/salesforce-translation-import/docs/README.md rename to custom/apps/salesforce-translation-import/docs/README.md index 9bea346a..454a9c87 100644 --- a/modules/salesforce-translation-import/docs/README.md +++ b/custom/apps/salesforce-translation-import/docs/README.md @@ -4,7 +4,7 @@ Desktop-App fuer den Import von Salesforce-XLIFF-Dateien mit Zieluebersetzungen ## Einbindung -- Desktop-App ueber `modules/salesforce-translation-import/desktop.php` +- Desktop-App ueber `custom/apps/salesforce-translation-import/desktop.php` - oeffentlicher App-Einstieg ueber `public/apps/salesforce-translation-import/index.php` - Darstellung als `native-module` mit modul-eigenem `app.js` und `app.css` diff --git a/modules/salesforce-translation-import/module.json b/custom/apps/salesforce-translation-import/module.json similarity index 100% rename from modules/salesforce-translation-import/module.json rename to custom/apps/salesforce-translation-import/module.json diff --git a/modules/salesforce-translation-import/pages/index.php b/custom/apps/salesforce-translation-import/pages/index.php similarity index 100% rename from modules/salesforce-translation-import/pages/index.php rename to custom/apps/salesforce-translation-import/pages/index.php diff --git a/docs/ANLEITUNG.md b/docs/ANLEITUNG.md index b08bec32..703b095f 100644 --- a/docs/ANLEITUNG.md +++ b/docs/ANLEITUNG.md @@ -63,7 +63,7 @@ Dort koennen derzeit insbesondere verwaltet werden: ## Module -Klassische Module liegen unter `modules//` und koennen als normale `App` im Desktop erscheinen. +Installierbare Fach-Apps liegen unter `custom/apps//` und koennen als normale `App` im Desktop erscheinen. Aktuell gilt: diff --git a/docs/CONTENT.md b/docs/CONTENT.md index 711cf8b8..99050114 100644 --- a/docs/CONTENT.md +++ b/docs/CONTENT.md @@ -83,7 +83,7 @@ Stand dieser Datei: - Startmenue mit `User Setting Bereich`, `Funktion-Bereich` und `Auswahlbereich` - rechter `Infobereich` mit benutzerbezogener Sichtbarkeit - `User Self Management` als Setup-App -- erster echter Modulpfad fuer klassische Module mit Modul-Discovery aus `modules//` +- erster echter App-Pfad fuer installierbare Fach-Apps mit Discovery aus `custom/apps//` - `Mining-Checker` als erstes angebundenes klassisches Modul - `Waehrungs-Checker` als zweites angebundenes klassisches Modul mit Desktop-Widget fuer Kursaktualisierung - versionierte API auf demselben Host unter `(staging.)desktop.kusche.berlin/api/v1/...` @@ -103,23 +103,24 @@ Aus [README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/docs/READ - `src/Desktop/` enthaelt zentrale Desktop-Mechaniken - die API bleibt auf demselben Host wie die Desktop-Shell und wird unter `/api/v1/...` versioniert - `partials/desktop/` enthaelt Shell-Templates -- `modules/` bleibt Zielort fuer klassische Module +- `custom/apps/` bleibt Zielort fuer installierbare Fach-Apps +- `system/apps/` bleibt Zielort fuer System-Tools - `temp/nexus-module-import/` ist Rohbasis fuer importierte Nexus-Module - Keycloak bleibt das Auth-System ### Module -Aus [modules/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/modules/README.md): +Aus [custom/apps/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/custom/apps/README.md): -- klassische Module bleiben unter `modules//` +- installierbare Fach-Apps liegen unter `custom/apps//` - Businesslogik wird nicht in den Desktop-Core verschoben - ein Desktop-Modul kann eigene `desktop.php`, `pages/`, `api/` und `assets/` Strukturen mitbringen - Modul-Assets muessen nicht in den globalen Desktop-Asset-Baum kopiert werden ### Aktueller Modulstand -- `Mining-Checker` ist das erste Modul, das als echte Desktop-App aus `modules/mining-checker/` angebunden wird -- `Waehrungs-Checker` ist das zweite Modul, das als echte Desktop-App aus `modules/fx-rates/` angebunden wird +- `Mining-Checker` ist eine Desktop-App aus `custom/apps/mining-checker/` +- `Waehrungs-Checker` ist eine Desktop-App aus `custom/apps/fx-rates/` - die Desktop-Shell kann Moduldefinitionen zentral erkennen und als `App` bereitstellen - wiederverwendbare Modul-Helfer liegen im globalen Kern, die Fachlogik bleibt im Modul diff --git a/docs/README.md b/docs/README.md index a3866974..299e17f5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,7 +25,8 @@ Zentraler Dokumentationsindex fuer das Projekt. - `src/Desktop/` zentrale Desktop-Mechaniken - `src/ModulesCore/` gemeinsame Helfer fuer Modul-Discovery, Modul-HTTP und wiederverwendbare Modul-Anbindung - `partials/desktop/` Shell-Template -- `modules/` Zielort fuer klassische Module +- `custom/apps/` Zielort fuer installierbare Fach-Apps +- `system/apps/` Zielort fuer nicht installierbare System-Tools - `temp/nexus-module-import/` Rohbasis fuer importierte Nexus-Module ## Fachliche Hinweise @@ -49,10 +50,10 @@ Es gilt: Aktuell wichtig: -- der Mining-Checker ist das erste als echtes Desktop-Modul angebundene Modul unter `modules/mining-checker/` -- der Waehrungs-Checker ist als zweites echtes Desktop-Modul unter `modules/fx-rates/` angebunden -- der Salesforce Translation Import ist als weitere Desktop-App unter `modules/salesforce-translation-import/` angebunden -- der Boersenchecker ist als weiteres importiertes Desktop-Modul unter `modules/boersenchecker/` angebunden +- der Mining-Checker ist als echte Desktop-App unter `custom/apps/mining-checker/` angebunden +- der Waehrungs-Checker ist als Desktop-App unter `custom/apps/fx-rates/` angebunden +- der Salesforce Translation Import ist als Desktop-App unter `custom/apps/salesforce-translation-import/` angebunden +- der Boersenchecker ist als importierte Desktop-App unter `custom/apps/boersenchecker/` angebunden - Modul-Assets koennen ueber einen gemeinsamen Auslieferungsweg aus dem Modul selbst geladen werden - die API bleibt vorerst auf demselben Host und wird nicht auf `api.desktop.kusche.berlin` ausgelagert - das gemeinsame Admin-Debug-Widget neben der Uhr ist der Standard fuer Live-Debugging in Desktop und Native-Apps diff --git a/docs/Umsetzungsanweisung/02_ORDNERSTRUKTUR_UND_GRUNDREGELN.md b/docs/Umsetzungsanweisung/02_ORDNERSTRUKTUR_UND_GRUNDREGELN.md index eb3afda6..57e1d5bd 100644 --- a/docs/Umsetzungsanweisung/02_ORDNERSTRUKTUR_UND_GRUNDREGELN.md +++ b/docs/Umsetzungsanweisung/02_ORDNERSTRUKTUR_UND_GRUNDREGELN.md @@ -10,7 +10,8 @@ Die Ordnerstruktur soll sich so nah wie sinnvoll an der bestehenden Nexus-Strukt - `/api/` -> Backend- und API-Endpunkte - `/src/` -> Kernlogik, Services, Utilities - `/config/` -> Umgebungs-Configs -- `/modules//` -> klassische Module +- `/custom/apps//` -> installierbare Fach-Apps +- `/system/apps//` -> System-Tools und interne App-Quellen - `/partials/structure/` -> globale Layout-Bausteine - `/partials/landingpages/` -> Seiten oder Views - `/tools/` -> Worker, CLI, Hilfstools @@ -48,7 +49,7 @@ Die klassische Modulstruktur bleibt erhalten. Das bedeutet: -- Module bleiben unter `/modules//` +- installierbare Fach-Apps bleiben unter `/custom/apps//` - der neue Desktop-Layer ist nur die neue Oberfläche - Module dürfen nicht ungeordnet in den Desktop-Core verschoben werden - gemeinsame Desktop-Mechaniken liegen nicht in Modulen, sondern im globalen Kern diff --git a/docs/Umsetzungsanweisung/03_MIGRATION_BESTEHENDE_MODULE.md b/docs/Umsetzungsanweisung/03_MIGRATION_BESTEHENDE_MODULE.md index 536d96f6..ec3f2585 100644 --- a/docs/Umsetzungsanweisung/03_MIGRATION_BESTEHENDE_MODULE.md +++ b/docs/Umsetzungsanweisung/03_MIGRATION_BESTEHENDE_MODULE.md @@ -19,7 +19,7 @@ Die Übernahme erfolgt zweistufig: Mindestens: -- `/modules/` +- `/custom/apps/` - relevante globale Dokumentationsdateien - gegebenenfalls Referenzdateien aus `/src/`, wenn Modul-Helfer benötigt werden diff --git a/docs/WEITERENTWICKLUNG.md b/docs/WEITERENTWICKLUNG.md index e95fe894..d0df8839 100644 --- a/docs/WEITERENTWICKLUNG.md +++ b/docs/WEITERENTWICKLUNG.md @@ -56,7 +56,8 @@ Verbindlich ist: ## Architekturregeln -- `modules//` bleibt Ort fuer klassische Module +- `custom/apps//` bleibt Ort fuer installierbare Fach-Apps +- `system/apps//` bleibt Ort fuer System-Tools und nicht installierbare App-Quellen - globale Desktop-Mechaniken liegen im gemeinsamen Kern - globale Modul-Helfer duerfen im gemeinsamen Kern liegen, Fachlogik aber nicht - globale Debug-Steuerung fuer Admins liegt im Desktop-Core; Aktivierung erfolgt ueber das gemeinsame Debug-Widget neben der Uhr diff --git a/public/admin/cron/index.php b/public/admin/cron/index.php index 239b6d81..9b1dfda1 100644 --- a/public/admin/cron/index.php +++ b/public/admin/cron/index.php @@ -4,377 +4,6 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; -use App\ConfigLoader; -use App\CronManagementService; -use App\KeycloakAuth; -use ModulesCore\ModuleRegistry; +use App\AppPaths; -if (session_status() !== PHP_SESSION_ACTIVE) { - session_start(); -} - -$projectRoot = dirname(__DIR__, 3); -$auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak')); -if (!$auth->isAuthenticated()) { - header('Location: /auth/keycloak', true, 302); - exit; -} - -$currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; -$currentGroups = array_map( - static fn (string $group): string => strtolower(trim($group, '/')), - array_values(array_map('strval', (array) ($currentUser['groups'] ?? []))) -); -$isPartial = !empty($_GET['partial']); - -if (!in_array('administrators', $currentGroups, true)) { - http_response_code(403); - $forbidden = '

Kein Zugriff auf Cron Tool.

'; - if ($isPartial) { - echo $forbidden; - return; - } - - ?> - - - - - Cron Tool - - - - - saveGlobal([ - 'base_url' => (string) ($_POST['base_url'] ?? ''), - 'default_timezone' => (string) ($_POST['default_timezone'] ?? ''), - ], $baseUrlFallback); - $messages[] = 'Globale Cron-Einstellungen gespeichert.'; - } elseif ($action === 'regenerate-token') { - $service->regenerateToken($baseUrlFallback); - $messages[] = 'Cron-Token wurde neu erzeugt.'; - } elseif ($action === 'run-due') { - $results = $service->runDue($baseUrlFallback); - if ($results === []) { - $messages[] = 'Aktuell war kein Cron-Job faellig.'; - } else { - foreach ($results as $result) { - $messages[] = sprintf( - '%s: %s', - (string) ($result['job_key'] ?? 'Cron-Job'), - (string) ($result['message'] ?? '') - ); - } - } - } elseif ($action === 'save-job') { - $jobKey = (string) ($_POST['job_key'] ?? ''); - $service->saveJob($jobKey, [ - 'enabled' => !empty($_POST['enabled']), - 'cron_expression' => (string) ($_POST['cron_expression'] ?? ''), - 'timezone' => (string) ($_POST['timezone'] ?? ''), - ], $baseUrlFallback); - $messages[] = 'Cron-Job gespeichert: ' . $jobKey; - } elseif ($action === 'toggle-job') { - $jobKey = (string) ($_POST['job_key'] ?? ''); - $service->saveJob($jobKey, [ - 'enabled' => !empty($_POST['enabled']), - 'cron_expression' => (string) ($_POST['cron_expression'] ?? ''), - 'timezone' => (string) ($_POST['timezone'] ?? ''), - ], $baseUrlFallback); - $messages[] = 'Status aktualisiert: ' . $jobKey; - } elseif ($action === 'run-now') { - $jobKey = (string) ($_POST['job_key'] ?? ''); - $result = $service->runNow($jobKey, $baseUrlFallback); - if (!empty($result['ok'])) { - $messages[] = (string) ($result['message'] ?? 'Cron-Job ausgefuehrt.'); - } else { - $errors[] = (string) ($result['message'] ?? 'Cron-Job fehlgeschlagen.'); - } - } - } catch (\Throwable $exception) { - $errors[] = $exception->getMessage(); - } - } -} - -$bootstrap = $service->bootstrap($baseUrlFallback); -$settings = is_array($bootstrap['settings'] ?? null) ? $bootstrap['settings'] : []; -$jobs = is_array($bootstrap['jobs'] ?? null) ? $bootstrap['jobs'] : []; -$h = static fn (?string $value): string => htmlspecialchars($value ?? '', ENT_QUOTES); - -ob_start(); -?> -
-
- - -
-
-
-
-

Cron Registry

-

Globale Scheduler-Verwaltung

-

Module hinterlegen ihre Cron-Endpunkte in der Manifest-Datei. Dieses Systemtool sammelt sie automatisch und verwaltet Zeitplaene zentral.

-
-
- Base URL: - Token aktiv - Admin only -
-
- - -
-
- Ergebnis -
    - -
  • - -
-
-
- - - -
-
- Bitte pruefen -
    - -
  • - -
-
-
- - -
-
-
-

Global

-

Runner-Einstellungen

-
-
- -
- - - - -
- Cron-Token - -

Der Token wird fuer interne Cron-Aufrufe per `X-Desktop-Cron-Token` genutzt.

-
-
- -
-
- -
- - - -
- -
- - - -
-
- -
-
-
-

Jobs

-

Erkannte Modul-Crons

-

Neue Cronjob-Typen entstehen ueber Modul-Metadaten. Ein einzelner Job kann hier angelegt, aktiviert und spaeter bearbeitet werden.

-
-
- -
- -
-
-
- -

-
-
- - - - nicht angelegt - -
-
- -
-
- Status - -
-
- Zeitplan - -
-
- Naechster Lauf - -
-
- Letzter Erfolg - -
-
- -
- Cron - Zeitzone - Endpoint -
- - -

Cron-Fehler:

- - -

- - -
-
- - - - - - - -
- -
- - - - -
- - -
- - -
- - -
Aktuell wurden noch keine Cron-Jobs aus Modulen registriert.
- -
-
-
-
-
-
- - - - - - - - - - - Cron Tool - - - - - - - - - +require AppPaths::systemAppPath(dirname(__DIR__, 3), 'cron-management') . '/page.php'; diff --git a/public/admin/users/index.php b/public/admin/users/index.php index 35dca2e2..96831aa1 100644 --- a/public/admin/users/index.php +++ b/public/admin/users/index.php @@ -4,441 +4,6 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; -use App\AccountGate; -use App\ConfigLoader; -use App\RegistrationService; -use ModulesCore\ModuleHttp; +use App\AppPaths; -if (session_status() !== PHP_SESSION_ACTIVE) { - session_start(); -} - -$projectRoot = dirname(__DIR__, 3); -ModuleHttp::requireDesktopAccess($projectRoot); - -$accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration')); -$registration = new RegistrationService($projectRoot, ConfigLoader::load($projectRoot, 'registration')); - -$currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; -$currentGroups = array_map( - static fn (string $group): string => strtolower(trim($group, '/')), - array_values(array_map('strval', (array) ($currentUser['groups'] ?? []))) -); -$isPartial = !empty($_GET['partial']); - -if (!in_array('administrators', $currentGroups, true)) { - http_response_code(403); - $forbidden = '

Kein Zugriff auf User Management.

'; - if ($isPartial) { - echo $forbidden; - return; - } - - ?> - - - - - User Management - - - - - approve($selectedId, [ - 'member_of_list' => $memberOfList, - 'admin_note' => (string) ($_POST['admin_note'] ?? ''), - ], (string) ($currentUser['username'] ?? 'administrator')); - - if (($result['success'] ?? false) !== true) { - $errors = array_merge($errors, array_values(array_map('strval', (array) ($result['errors'] ?? [])))); - } else { - $messages[] = 'Registrierung wurde freigeschaltet.'; - } - $section = 'pending'; - } elseif ($action === 'deactivate-user') { - $result = $registration->deactivateUser($manualUsername); - if (($result['success'] ?? false) === true) { - $messages[] = (string) ($result['message'] ?? ''); - } else { - $errors[] = (string) ($result['message'] ?? ''); - } - $section = 'users'; - } elseif ($action === 'activate-user') { - $groupDns = array_values(array_filter(array_map('strval', (array) ($_POST['manual_group_dns'] ?? [])))); - $result = $registration->activateExistingUser($manualUsername, $groupDns); - if (($result['success'] ?? false) === true) { - $messages[] = (string) ($result['message'] ?? ''); - } else { - $errors[] = (string) ($result['message'] ?? ''); - } - $section = 'users'; - } elseif ($action === 'send-reset-mail') { - $origin = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost'); - $result = $registration->sendPasswordReset($manualResetUsername, $origin); - if (($result['success'] ?? false) === true) { - $messages[] = (string) ($result['message'] ?? ''); - } else { - $errors[] = (string) ($result['message'] ?? ''); - } - $section = 'reset'; - } elseif ($action === 'set-password') { - if (strlen($manualResetPassword) < 12) { - $errors[] = 'Das neue Passwort muss mindestens 12 Zeichen lang sein.'; - } else { - $result = $registration->setUserPassword($manualResetUsername, $manualResetPassword); - if (($result['success'] ?? false) === true) { - $messages[] = (string) ($result['message'] ?? ''); - } else { - $errors[] = (string) ($result['message'] ?? ''); - } - } - $section = 'reset'; - } - } -} - -$pendingRequests = $registration->pendingApprovals(); -$availableGroups = $registration->availableGroups(); -$existingUsers = $registration->searchExistingUsers('', 250); -$selectedExistingUser = null; -if ($manualUsername !== '') { - foreach ($existingUsers as $existingUser) { - if (strtolower((string) ($existingUser['username'] ?? '')) === strtolower($manualUsername)) { - $selectedExistingUser = $existingUser; - break; - } - } -} -$selected = $selectedId !== '' - ? $registration->find($selectedId) - : ($pendingRequests[0] ?? null); - -if ($selectedId === '' && is_array($selected) && isset($selected['id'])) { - $selectedId = (string) $selected['id']; -} - -$h = static fn (?string $value): string => htmlspecialchars($value ?? '', ENT_QUOTES); -$sectionUrl = static function (string $targetSection, string $requestId = ''): string { - $query = ['section' => $targetSection]; - if ($requestId !== '') { - $query['id'] = $requestId; - } - - return '/admin/users/?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986); -}; - -$sectionMeta = [ - 'pending' => [ - 'label' => 'Freischaltungen', - 'title' => 'Offene Registrierungen', - 'description' => 'Neue Registrierungen pruefen, Gruppen zuweisen und Freischaltungen sauber dokumentieren.', - 'nav' => 'Anfragen sichten und direkt freigeben.', - ], - 'users' => [ - 'label' => 'Benutzerstatus', - 'title' => 'Bestehende Benutzer', - 'description' => 'LDAP-Benutzer aktivieren, deaktivieren und Gruppen fuer die Desktop-Nutzung zuweisen.', - 'nav' => 'Aktivierung und Sperrung vorhandener Konten.', - ], - 'reset' => [ - 'label' => 'Passwort-Reset', - 'title' => 'Passwortverwaltung', - 'description' => 'Reset-Mails ausloesen oder ein Passwort direkt als Administrator setzen.', - 'nav' => 'Ruecksetzen oder direkt neu vergeben.', - ], -]; - -ob_start(); -?> -
-
- - -
-
-
-
-

User Management

-

-

-
-
- Administrators only - Desktop Auth - Section: -
-
- - -
-
- Ergebnis -
    - -
  • - -
-
-
- - - -
-
- Bitte pruefen -
    - -
  • - -
-
-
- - - - - -
-
-
-
-

Queue

-

Wartende Registrierungen

-
- offen -
- -
- -
Aktuell gibt es keine offenen Registrierungen.
- - - - - - - - - -
-
- -
-
-
-

Approval

-

Freischaltung

-
-
- - -
-
Benutzer
-
E-Mail
-
Status
-
- -
- - - - - -
- Gruppen fuer Freischaltung -
- - - -
-
- - - -
- -
-
- -
Keine Anfrage ausgewaehlt.
- -
-
- -
-
-
-

Existing User

-

Bestehenden Benutzer verwalten

-
-
- -
- - - - - -
- Gruppen fuer Aktivierung -
- - - - -
-
- -
- - -
-
-
- -
-
-
-

Reset

-

Passwort zuruecksetzen

-
-
- -
- - - - - - - -
- - -
-
-
- -
-
-
-
- - - - - - User Management - - - - - - - - - +require AppPaths::systemAppPath(dirname(__DIR__, 3), 'user-management') . '/page.php'; diff --git a/public/api/boersenchecker/index.php b/public/api/boersenchecker/index.php index 1db92517..32df3c19 100644 --- a/public/api/boersenchecker/index.php +++ b/public/api/boersenchecker/index.php @@ -4,6 +4,7 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; +use App\AppPaths; use App\CronAuth; use ModulesCore\ModuleHttp; @@ -32,4 +33,4 @@ if (!(in_array($normalizedPath, $cronPaths, true) && CronAuth::isAuthorizedReque ModuleHttp::requireDesktopAccess($projectRoot, true); } -require dirname(__DIR__, 3) . '/modules/boersenchecker/api/index.php'; +require AppPaths::customAppPath(dirname(__DIR__, 3), 'boersenchecker') . '/api/index.php'; diff --git a/public/api/fx-rates/index.php b/public/api/fx-rates/index.php index a97c334a..353248bd 100644 --- a/public/api/fx-rates/index.php +++ b/public/api/fx-rates/index.php @@ -4,6 +4,7 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; +use App\AppPaths; use App\CronAuth; use ModulesCore\ModuleHttp; @@ -31,4 +32,4 @@ if (!(in_array($normalizedPath, $cronPaths, true) && CronAuth::isAuthorizedReque ModuleHttp::requireDesktopAccess($projectRoot, true); } -require $projectRoot . '/modules/fx-rates/api/index.php'; +require AppPaths::customAppPath($projectRoot, 'fx-rates') . '/api/index.php'; diff --git a/public/api/mining-checker/index.php b/public/api/mining-checker/index.php index b68ebf7c..8778fcfc 100644 --- a/public/api/mining-checker/index.php +++ b/public/api/mining-checker/index.php @@ -4,6 +4,7 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; +use App\AppPaths; use ModulesCore\ModuleHttp; session_start(); @@ -11,4 +12,4 @@ session_start(); $projectRoot = dirname(__DIR__, 3); ModuleHttp::requireDesktopAccess($projectRoot, true); -require $projectRoot . '/modules/mining-checker/api/index.php'; +require AppPaths::customAppPath($projectRoot, 'mining-checker') . '/api/index.php'; diff --git a/public/api/module-auth/mining-checker/index.php b/public/api/module-auth/mining-checker/index.php index 03423ef1..76e685e2 100644 --- a/public/api/module-auth/mining-checker/index.php +++ b/public/api/module-auth/mining-checker/index.php @@ -3,7 +3,10 @@ declare(strict_types=1); require_once dirname(__DIR__, 5) . '/src/App/bootstrap.php'; -require_once dirname(__DIR__, 5) . '/modules/mining-checker/bootstrap.php'; + +use App\AppPaths; + +require_once AppPaths::customAppPath(dirname(__DIR__, 5), 'mining-checker') . '/bootstrap.php'; use Modules\MiningChecker\Legacy\LegacyModuleStore; use Modules\MiningChecker\Legacy\UserScope; diff --git a/public/api/pihole/index.php b/public/api/pihole/index.php index f62eecbb..bbb70f65 100644 --- a/public/api/pihole/index.php +++ b/public/api/pihole/index.php @@ -2,4 +2,8 @@ declare(strict_types=1); -require dirname(__DIR__, 3) . '/modules/pihole/api/index.php'; +require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; + +use App\AppPaths; + +require AppPaths::customAppPath(dirname(__DIR__, 3), 'pihole') . '/api/index.php'; diff --git a/public/api/user-self-management/index.php b/public/api/user-self-management/index.php index 208054dc..fece9f85 100644 --- a/public/api/user-self-management/index.php +++ b/public/api/user-self-management/index.php @@ -4,107 +4,6 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; -use App\ConfigLoader; -use App\KeycloakAuth; -use App\LdapProvisioner; -use App\UserSelfManagementService; -use Desktop\AppRegistry; -use Desktop\AppVisibility; -use Desktop\SkinResolver; -use Desktop\WidgetRegistry; -use ModulesCore\ModuleRegistry; +use App\AppPaths; -session_start(); - -header('Content-Type: application/json; charset=utf-8'); - -$projectRoot = dirname(__DIR__, 3); -$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); - -if ($method === 'OPTIONS') { - header('Allow: GET, PUT, OPTIONS'); - exit; -} - -$keycloakConfig = ConfigLoader::load($projectRoot, 'keycloak'); -$auth = new KeycloakAuth($keycloakConfig); -$moduleRegistry = new ModuleRegistry($projectRoot . '/modules'); -$registry = new AppRegistry($projectRoot . '/config/apps.php', $moduleRegistry->desktopApps()); -$widgetRegistry = new WidgetRegistry($projectRoot . '/config/widgets.php'); -$service = new UserSelfManagementService($projectRoot); -$registrationConfig = ConfigLoader::load($projectRoot, 'registration'); -$ldap = new LdapProvisioner($registrationConfig); -$isAuthenticated = $auth->isAuthenticated(); -$authUser = $isAuthenticated && is_array($_SESSION['desktop_auth']['user'] ?? null) - ? $_SESSION['desktop_auth']['user'] - : []; -$authGroups = array_values(array_map('strval', (array) ($authUser['groups'] ?? []))); -$visibleApps = AppVisibility::filterByGroups($registry->all(), $authGroups); -$widgets = $widgetRegistry->all(); -$skins = SkinResolver::all(); - -if ($method === 'GET') { - respond($service->buildBootstrapPayload($authUser, $visibleApps, $widgets, $skins)); -} - -if ($method !== 'PUT') { - respond(['error' => 'Method not allowed.'], 405); -} - -$rawBody = file_get_contents('php://input'); -$decoded = json_decode($rawBody !== false ? $rawBody : '', true); - -if (!is_array($decoded)) { - respond(['error' => 'Invalid JSON payload.'], 400); -} - -$before = $service->load($authUser, $visibleApps, $widgets, $skins); -$after = $service->save($decoded, $authUser, $visibleApps, $widgets, $skins); -$payload = $service->buildBootstrapPayload($authUser, $visibleApps, $widgets, $skins); -$payload['preferences'] = $after; -$payload['meta']['requires_reload'] = requiresReload($before, $after); -$payload['meta']['ldap_profile_sync'] = [ - 'attempted' => false, - 'success' => false, - 'message' => 'LDAP-Sync nicht ausgefuehrt.', -]; - -$username = trim((string) ($authUser['username'] ?? '')); -if ($username !== '' && $ldap->isEnabled()) { - $payload['meta']['ldap_profile_sync']['attempted'] = true; - $ldapResult = $ldap->updateUserProfile($username, is_array($after['profile'] ?? null) ? $after['profile'] : []); - $payload['meta']['ldap_profile_sync']['success'] = (bool) ($ldapResult['success'] ?? false); - $payload['meta']['ldap_profile_sync']['message'] = (string) ($ldapResult['message'] ?? 'LDAP-Sync ohne Rueckmeldung.'); - if (isset($ldapResult['updated_fields']) && is_array($ldapResult['updated_fields'])) { - $payload['meta']['ldap_profile_sync']['updated_fields'] = array_values(array_map('strval', $ldapResult['updated_fields'])); - } -} - -respond($payload); - -/** - * @param array $payload - */ -function respond(array $payload, int $statusCode = 200): never -{ - http_response_code($statusCode); - echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); - exit; -} - -/** - * @param array $before - * @param array $after - */ -function requiresReload(array $before, array $after): bool -{ - $beforeSkin = (string) ($before['desktop']['active_skin'] ?? ''); - $afterSkin = (string) ($after['desktop']['active_skin'] ?? ''); - $beforeApps = array_values(array_map('strval', (array) ($before['apps']['enabled_ids'] ?? []))); - $afterApps = array_values(array_map('strval', (array) ($after['apps']['enabled_ids'] ?? []))); - - sort($beforeApps); - sort($afterApps); - - return $beforeSkin !== $afterSkin || $beforeApps !== $afterApps; -} +require AppPaths::systemAppPath(dirname(__DIR__, 3), 'user-self-management') . '/api.php'; diff --git a/public/apps/boersenchecker/index.php b/public/apps/boersenchecker/index.php index 04de601c..28dccde0 100644 --- a/public/apps/boersenchecker/index.php +++ b/public/apps/boersenchecker/index.php @@ -2,4 +2,8 @@ declare(strict_types=1); -require dirname(__DIR__, 3) . '/modules/boersenchecker/pages/index.php'; +require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; + +use App\AppPaths; + +require AppPaths::customAppPath(dirname(__DIR__, 3), 'boersenchecker') . '/pages/index.php'; diff --git a/public/apps/fx-rates/index.php b/public/apps/fx-rates/index.php index 4582f1e7..e2a46119 100644 --- a/public/apps/fx-rates/index.php +++ b/public/apps/fx-rates/index.php @@ -2,4 +2,8 @@ declare(strict_types=1); -require dirname(__DIR__, 3) . '/modules/fx-rates/pages/index.php'; +require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; + +use App\AppPaths; + +require AppPaths::customAppPath(dirname(__DIR__, 3), 'fx-rates') . '/pages/index.php'; diff --git a/public/apps/mining-checker/index.php b/public/apps/mining-checker/index.php index 1b3212c8..1ba63f89 100644 --- a/public/apps/mining-checker/index.php +++ b/public/apps/mining-checker/index.php @@ -2,4 +2,8 @@ declare(strict_types=1); -require dirname(__DIR__, 3) . '/modules/mining-checker/pages/index.php'; +require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; + +use App\AppPaths; + +require AppPaths::customAppPath(dirname(__DIR__, 3), 'mining-checker') . '/pages/index.php'; diff --git a/public/apps/pihole/index.php b/public/apps/pihole/index.php index e317d39c..e15d065f 100644 --- a/public/apps/pihole/index.php +++ b/public/apps/pihole/index.php @@ -2,4 +2,8 @@ declare(strict_types=1); -require dirname(__DIR__, 3) . '/modules/pihole/pages/index.php'; +require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; + +use App\AppPaths; + +require AppPaths::customAppPath(dirname(__DIR__, 3), 'pihole') . '/pages/index.php'; diff --git a/public/apps/salesforce-translation-import/index.php b/public/apps/salesforce-translation-import/index.php index 9ad5e7b1..fb62e3ef 100644 --- a/public/apps/salesforce-translation-import/index.php +++ b/public/apps/salesforce-translation-import/index.php @@ -2,4 +2,8 @@ declare(strict_types=1); -require dirname(__DIR__, 3) . '/modules/salesforce-translation-import/pages/index.php'; +require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; + +use App\AppPaths; + +require AppPaths::customAppPath(dirname(__DIR__, 3), 'salesforce-translation-import') . '/pages/index.php'; diff --git a/public/apps/user-self-management/index.php b/public/apps/user-self-management/index.php index 8e9c7a58..39a2312c 100644 --- a/public/apps/user-self-management/index.php +++ b/public/apps/user-self-management/index.php @@ -4,36 +4,6 @@ declare(strict_types=1); require_once dirname(__DIR__, 3) . '/src/App/bootstrap.php'; -session_start(); +use App\AppPaths; -$assetVersion = static function (string $publicPath): string { - $filesystemPath = dirname(__DIR__, 2) . $publicPath; - - if (!is_file($filesystemPath)) { - return $publicPath; - } - - $mtime = filemtime($filesystemPath); - - return $mtime === false ? $publicPath : $publicPath . '?v=' . $mtime; -}; -?> - - - - - User Self Management - - - -
- - - - +require AppPaths::systemAppPath(dirname(__DIR__, 3), 'user-self-management') . '/page.php'; diff --git a/public/module-assets/index.php b/public/module-assets/index.php index b41be763..add6c3e8 100644 --- a/public/module-assets/index.php +++ b/public/module-assets/index.php @@ -4,6 +4,7 @@ declare(strict_types=1); require_once dirname(__DIR__, 2) . '/src/App/bootstrap.php'; +use App\AppPaths; use ModulesCore\ModuleRegistry; $projectRoot = dirname(__DIR__, 2); @@ -15,7 +16,7 @@ if ($module === '' || $path === '' || str_contains($path, '..')) { exit('Invalid module asset request.'); } -$registry = new ModuleRegistry($projectRoot . '/modules'); +$registry = new ModuleRegistry(AppPaths::customAppsRoot($projectRoot)); $modulePath = $registry->modulePath($module); if ($modulePath === null) { diff --git a/public/system-app-assets/index.php b/public/system-app-assets/index.php new file mode 100644 index 00000000..7b2ae3d5 --- /dev/null +++ b/public/system-app-assets/index.php @@ -0,0 +1,51 @@ + 'text/css; charset=utf-8', + 'js' => 'application/javascript; charset=utf-8', + 'json' => 'application/json; charset=utf-8', + 'svg' => 'image/svg+xml', + 'png' => 'image/png', + 'jpg', 'jpeg' => 'image/jpeg', + 'gif' => 'image/gif', + 'webp' => 'image/webp', + default => 'application/octet-stream', +}; + +$mtime = filemtime($assetPath); +if ($mtime !== false) { + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $mtime) . ' GMT'); +} +header('Content-Type: ' . $contentType); +header('Cache-Control: public, max-age=300'); +readfile($assetPath); diff --git a/src/App/App.php b/src/App/App.php index 3ae17882..6b748173 100644 --- a/src/App/App.php +++ b/src/App/App.php @@ -27,7 +27,7 @@ final class App { $keycloakConfig = ConfigLoader::load($this->projectRoot, 'keycloak'); $auth = new KeycloakAuth($keycloakConfig); - $moduleRegistry = new ModuleRegistry($this->projectRoot . '/modules'); + $moduleRegistry = new ModuleRegistry(AppPaths::customAppsRoot($this->projectRoot)); $registry = new AppRegistry($this->projectRoot . '/config/apps.php', $moduleRegistry->desktopApps()); $widgetRegistry = new WidgetRegistry($this->projectRoot . '/config/widgets.php', $moduleRegistry->widgets()); $skins = SkinResolver::all(); diff --git a/src/App/AppPaths.php b/src/App/AppPaths.php new file mode 100644 index 00000000..5cd4d4c6 --- /dev/null +++ b/src/App/AppPaths.php @@ -0,0 +1,28 @@ +projectRoot . '/modules/' . trim($moduleName, '/'); + return AppPaths::customAppPath($this->projectRoot, $moduleName); } private function ensureBooted(string $moduleName): void @@ -520,7 +521,7 @@ namespace { return []; } - $path = dirname(__DIR__, 2) . '/modules/' . $module . '/design.json'; + $path = AppPaths::customAppPath(dirname(__DIR__, 2), $module) . '/design.json'; if (!is_file($path)) { return []; } @@ -624,7 +625,7 @@ namespace { if (!function_exists('module_tpl')) { function module_tpl(string $module, string $name, array $data = []): void { - $path = dirname(__DIR__, 2) . '/modules/' . $module . '/partials/' . $name . '.php'; + $path = AppPaths::customAppPath(dirname(__DIR__, 2), $module) . '/partials/' . $name . '.php'; if (!is_file($path)) { echo ''; return; diff --git a/start.md b/start.md index ad4d1464..9d199e43 100644 --- a/start.md +++ b/start.md @@ -60,7 +60,8 @@ Danach bei Bedarf die fachlichen Projektanweisungen lesen: ### Bereichs-Doku -- [modules/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/modules/README.md) +- [custom/apps/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/custom/apps/README.md) +- [system/apps/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/system/apps/README.md) - [skins/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berlin/skins/README.md) ### Weitere fachliche Anweisungen diff --git a/system/apps/README.md b/system/apps/README.md new file mode 100644 index 00000000..b10ab977 --- /dev/null +++ b/system/apps/README.md @@ -0,0 +1,22 @@ +# System Apps + +System-Tools und nicht installierbare Kern-Apps liegen in diesem Projekt unter `system/apps//`. + +Ziel der Struktur: + +- alle quellspezifischen Dateien einer System-App an einem Ort +- keine JS- und CSS-Quellen direkt in `public/` +- `public/` enthaelt nur Web-Einstiegspunkte und Auslieferungs-Skripte + +Typische Bestandteile einer System-App: + +- `page.php` fuer den HTML- oder Partial-Einstieg +- `api.php` fuer app-spezifische API-Endpunkte, falls noetig +- `assets/app.css` +- `assets/app.js` + +Aktuelle Beispiele: + +- `user-management` +- `user-self-management` +- `cron-management` diff --git a/public/assets/apps/cron-management/app.css b/system/apps/cron-management/assets/app.css similarity index 100% rename from public/assets/apps/cron-management/app.css rename to system/apps/cron-management/assets/app.css diff --git a/public/assets/apps/cron-management/app.js b/system/apps/cron-management/assets/app.js similarity index 100% rename from public/assets/apps/cron-management/app.js rename to system/apps/cron-management/assets/app.js diff --git a/system/apps/cron-management/page.php b/system/apps/cron-management/page.php new file mode 100644 index 00000000..5ce4e698 --- /dev/null +++ b/system/apps/cron-management/page.php @@ -0,0 +1,381 @@ +isAuthenticated()) { + header('Location: /auth/keycloak', true, 302); + exit; +} + +$currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : []; +$currentGroups = array_map( + static fn (string $group): string => strtolower(trim($group, '/')), + array_values(array_map('strval', (array) ($currentUser['groups'] ?? []))) +); +$isPartial = !empty($_GET['partial']); + +if (!in_array('administrators', $currentGroups, true)) { + http_response_code(403); + $forbidden = '

Kein Zugriff auf Cron Tool.

'; + if ($isPartial) { + echo $forbidden; + return; + } + + ?> + + + + + Cron Tool + + + + + saveGlobal([ + 'base_url' => (string) ($_POST['base_url'] ?? ''), + 'default_timezone' => (string) ($_POST['default_timezone'] ?? ''), + ], $baseUrlFallback); + $messages[] = 'Globale Cron-Einstellungen gespeichert.'; + } elseif ($action === 'regenerate-token') { + $service->regenerateToken($baseUrlFallback); + $messages[] = 'Cron-Token wurde neu erzeugt.'; + } elseif ($action === 'run-due') { + $results = $service->runDue($baseUrlFallback); + if ($results === []) { + $messages[] = 'Aktuell war kein Cron-Job faellig.'; + } else { + foreach ($results as $result) { + $messages[] = sprintf( + '%s: %s', + (string) ($result['job_key'] ?? 'Cron-Job'), + (string) ($result['message'] ?? '') + ); + } + } + } elseif ($action === 'save-job') { + $jobKey = (string) ($_POST['job_key'] ?? ''); + $service->saveJob($jobKey, [ + 'enabled' => !empty($_POST['enabled']), + 'cron_expression' => (string) ($_POST['cron_expression'] ?? ''), + 'timezone' => (string) ($_POST['timezone'] ?? ''), + ], $baseUrlFallback); + $messages[] = 'Cron-Job gespeichert: ' . $jobKey; + } elseif ($action === 'toggle-job') { + $jobKey = (string) ($_POST['job_key'] ?? ''); + $service->saveJob($jobKey, [ + 'enabled' => !empty($_POST['enabled']), + 'cron_expression' => (string) ($_POST['cron_expression'] ?? ''), + 'timezone' => (string) ($_POST['timezone'] ?? ''), + ], $baseUrlFallback); + $messages[] = 'Status aktualisiert: ' . $jobKey; + } elseif ($action === 'run-now') { + $jobKey = (string) ($_POST['job_key'] ?? ''); + $result = $service->runNow($jobKey, $baseUrlFallback); + if (!empty($result['ok'])) { + $messages[] = (string) ($result['message'] ?? 'Cron-Job ausgefuehrt.'); + } else { + $errors[] = (string) ($result['message'] ?? 'Cron-Job fehlgeschlagen.'); + } + } + } catch (\Throwable $exception) { + $errors[] = $exception->getMessage(); + } + } +} + +$bootstrap = $service->bootstrap($baseUrlFallback); +$settings = is_array($bootstrap['settings'] ?? null) ? $bootstrap['settings'] : []; +$jobs = is_array($bootstrap['jobs'] ?? null) ? $bootstrap['jobs'] : []; +$h = static fn (?string $value): string => htmlspecialchars($value ?? '', ENT_QUOTES); + +ob_start(); +?> +
+
+ + +
+
+
+
+

Cron Registry

+

Globale Scheduler-Verwaltung

+

Module hinterlegen ihre Cron-Endpunkte in der Manifest-Datei. Dieses Systemtool sammelt sie automatisch und verwaltet Zeitplaene zentral.

+
+
+ Base URL: + Token aktiv + Admin only +
+
+ + +
+
+ Ergebnis +
    + +
  • + +
+
+
+ + + +
+
+ Bitte pruefen +
    + +
  • + +
+
+
+ + +
+
+
+

Global

+

Runner-Einstellungen

+
+
+ +
+ + + + +
+ Cron-Token + +

Der Token wird fuer interne Cron-Aufrufe per `X-Desktop-Cron-Token` genutzt.

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

Jobs

+

Erkannte Modul-Crons

+

Neue Cronjob-Typen entstehen ueber Modul-Metadaten. Ein einzelner Job kann hier angelegt, aktiviert und spaeter bearbeitet werden.

+
+
+ +
+ +
+
+
+ +

+
+
+ + + + nicht angelegt + +
+
+ +
+
+ Status + +
+
+ Zeitplan + +
+
+ Naechster Lauf + +
+
+ Letzter Erfolg + +
+
+ +
+ Cron + Zeitzone + Endpoint +
+ + +

Cron-Fehler:

+ + +

+ + +
+
+ + + + + + + +
+ +
+ + + + +
+ + +
+ + +
+ + +
Aktuell wurden noch keine Cron-Jobs aus Modulen registriert.
+ +
+
+
+
+
+
+ + + + + + + + + + + Cron Tool + + + + + + + + + diff --git a/public/assets/apps/user-management/app.css b/system/apps/user-management/assets/app.css similarity index 100% rename from public/assets/apps/user-management/app.css rename to system/apps/user-management/assets/app.css diff --git a/public/assets/apps/user-management/app.js b/system/apps/user-management/assets/app.js similarity index 100% rename from public/assets/apps/user-management/app.js rename to system/apps/user-management/assets/app.js diff --git a/system/apps/user-management/page.php b/system/apps/user-management/page.php new file mode 100644 index 00000000..1de66fc0 --- /dev/null +++ b/system/apps/user-management/page.php @@ -0,0 +1,444 @@ + strtolower(trim($group, '/')), + array_values(array_map('strval', (array) ($currentUser['groups'] ?? []))) +); +$isPartial = !empty($_GET['partial']); + +if (!in_array('administrators', $currentGroups, true)) { + http_response_code(403); + $forbidden = '

Kein Zugriff auf User Management.

'; + if ($isPartial) { + echo $forbidden; + return; + } + + ?> + + + + + User Management + + + + + approve($selectedId, [ + 'member_of_list' => $memberOfList, + 'admin_note' => (string) ($_POST['admin_note'] ?? ''), + ], (string) ($currentUser['username'] ?? 'administrator')); + + if (($result['success'] ?? false) !== true) { + $errors = array_merge($errors, array_values(array_map('strval', (array) ($result['errors'] ?? [])))); + } else { + $messages[] = 'Registrierung wurde freigeschaltet.'; + } + $section = 'pending'; + } elseif ($action === 'deactivate-user') { + $result = $registration->deactivateUser($manualUsername); + if (($result['success'] ?? false) === true) { + $messages[] = (string) ($result['message'] ?? ''); + } else { + $errors[] = (string) ($result['message'] ?? ''); + } + $section = 'users'; + } elseif ($action === 'activate-user') { + $groupDns = array_values(array_filter(array_map('strval', (array) ($_POST['manual_group_dns'] ?? [])))); + $result = $registration->activateExistingUser($manualUsername, $groupDns); + if (($result['success'] ?? false) === true) { + $messages[] = (string) ($result['message'] ?? ''); + } else { + $errors[] = (string) ($result['message'] ?? ''); + } + $section = 'users'; + } elseif ($action === 'send-reset-mail') { + $origin = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost'); + $result = $registration->sendPasswordReset($manualResetUsername, $origin); + if (($result['success'] ?? false) === true) { + $messages[] = (string) ($result['message'] ?? ''); + } else { + $errors[] = (string) ($result['message'] ?? ''); + } + $section = 'reset'; + } elseif ($action === 'set-password') { + if (strlen($manualResetPassword) < 12) { + $errors[] = 'Das neue Passwort muss mindestens 12 Zeichen lang sein.'; + } else { + $result = $registration->setUserPassword($manualResetUsername, $manualResetPassword); + if (($result['success'] ?? false) === true) { + $messages[] = (string) ($result['message'] ?? ''); + } else { + $errors[] = (string) ($result['message'] ?? ''); + } + } + $section = 'reset'; + } + } +} + +$pendingRequests = $registration->pendingApprovals(); +$availableGroups = $registration->availableGroups(); +$existingUsers = $registration->searchExistingUsers('', 250); +$selectedExistingUser = null; +if ($manualUsername !== '') { + foreach ($existingUsers as $existingUser) { + if (strtolower((string) ($existingUser['username'] ?? '')) === strtolower($manualUsername)) { + $selectedExistingUser = $existingUser; + break; + } + } +} +$selected = $selectedId !== '' + ? $registration->find($selectedId) + : ($pendingRequests[0] ?? null); + +if ($selectedId === '' && is_array($selected) && isset($selected['id'])) { + $selectedId = (string) $selected['id']; +} + +$h = static fn (?string $value): string => htmlspecialchars($value ?? '', ENT_QUOTES); +$sectionUrl = static function (string $targetSection, string $requestId = ''): string { + $query = ['section' => $targetSection]; + if ($requestId !== '') { + $query['id'] = $requestId; + } + + return '/admin/users/?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986); +}; + +$sectionMeta = [ + 'pending' => [ + 'label' => 'Freischaltungen', + 'title' => 'Offene Registrierungen', + 'description' => 'Neue Registrierungen pruefen, Gruppen zuweisen und Freischaltungen sauber dokumentieren.', + 'nav' => 'Anfragen sichten und direkt freigeben.', + ], + 'users' => [ + 'label' => 'Benutzerstatus', + 'title' => 'Bestehende Benutzer', + 'description' => 'LDAP-Benutzer aktivieren, deaktivieren und Gruppen fuer die Desktop-Nutzung zuweisen.', + 'nav' => 'Aktivierung und Sperrung vorhandener Konten.', + ], + 'reset' => [ + 'label' => 'Passwort-Reset', + 'title' => 'Passwortverwaltung', + 'description' => 'Reset-Mails ausloesen oder ein Passwort direkt als Administrator setzen.', + 'nav' => 'Ruecksetzen oder direkt neu vergeben.', + ], +]; + +ob_start(); +?> +
+
+ + +
+
+
+
+

User Management

+

+

+
+
+ Administrators only + Desktop Auth + Section: +
+
+ + +
+
+ Ergebnis +
    + +
  • + +
+
+
+ + + +
+
+ Bitte pruefen +
    + +
  • + +
+
+
+ + + + + +
+
+
+
+

Queue

+

Wartende Registrierungen

+
+ offen +
+ +
+ +
Aktuell gibt es keine offenen Registrierungen.
+ + + + + + + + + +
+
+ +
+
+
+

Approval

+

Freischaltung

+
+
+ + +
+
Benutzer
+
E-Mail
+
Status
+
+ +
+ + + + + +
+ Gruppen fuer Freischaltung +
+ + + +
+
+ + + +
+ +
+
+ +
Keine Anfrage ausgewaehlt.
+ +
+
+ +
+
+
+

Existing User

+

Bestehenden Benutzer verwalten

+
+
+ +
+ + + + + +
+ Gruppen fuer Aktivierung +
+ + + + +
+
+ +
+ + +
+
+
+ +
+
+
+

Reset

+

Passwort zuruecksetzen

+
+
+ +
+ + + + + + + +
+ + +
+
+
+ +
+
+
+
+ + + + + + User Management + + + + + + + + + diff --git a/system/apps/user-self-management/api.php b/system/apps/user-self-management/api.php new file mode 100644 index 00000000..5799f2bb --- /dev/null +++ b/system/apps/user-self-management/api.php @@ -0,0 +1,111 @@ +desktopApps()); +$widgetRegistry = new WidgetRegistry($projectRoot . '/config/widgets.php', $moduleRegistry->widgets()); +$service = new UserSelfManagementService($projectRoot); +$registrationConfig = ConfigLoader::load($projectRoot, 'registration'); +$ldap = new LdapProvisioner($registrationConfig); +$isAuthenticated = $auth->isAuthenticated(); +$authUser = $isAuthenticated && is_array($_SESSION['desktop_auth']['user'] ?? null) + ? $_SESSION['desktop_auth']['user'] + : []; +$authGroups = array_values(array_map('strval', (array) ($authUser['groups'] ?? []))); +$visibleApps = AppVisibility::filterByGroups($registry->all(), $authGroups); +$widgets = $widgetRegistry->all(); +$skins = SkinResolver::all(); + +if ($method === 'GET') { + respond($service->buildBootstrapPayload($authUser, $visibleApps, $widgets, $skins)); +} + +if ($method !== 'PUT') { + respond(['error' => 'Method not allowed.'], 405); +} + +$rawBody = file_get_contents('php://input'); +$decoded = json_decode($rawBody !== false ? $rawBody : '', true); + +if (!is_array($decoded)) { + respond(['error' => 'Invalid JSON payload.'], 400); +} + +$before = $service->load($authUser, $visibleApps, $widgets, $skins); +$after = $service->save($decoded, $authUser, $visibleApps, $widgets, $skins); +$payload = $service->buildBootstrapPayload($authUser, $visibleApps, $widgets, $skins); +$payload['preferences'] = $after; +$payload['meta']['requires_reload'] = requiresReload($before, $after); +$payload['meta']['ldap_profile_sync'] = [ + 'attempted' => false, + 'success' => false, + 'message' => 'LDAP-Sync nicht ausgefuehrt.', +]; + +$username = trim((string) ($authUser['username'] ?? '')); +if ($username !== '' && $ldap->isEnabled()) { + $payload['meta']['ldap_profile_sync']['attempted'] = true; + $ldapResult = $ldap->updateUserProfile($username, is_array($after['profile'] ?? null) ? $after['profile'] : []); + $payload['meta']['ldap_profile_sync']['success'] = (bool) ($ldapResult['success'] ?? false); + $payload['meta']['ldap_profile_sync']['message'] = (string) ($ldapResult['message'] ?? 'LDAP-Sync ohne Rueckmeldung.'); + if (isset($ldapResult['updated_fields']) && is_array($ldapResult['updated_fields'])) { + $payload['meta']['ldap_profile_sync']['updated_fields'] = array_values(array_map('strval', $ldapResult['updated_fields'])); + } +} + +respond($payload); + +/** + * @param array $payload + */ +function respond(array $payload, int $statusCode = 200): never +{ + http_response_code($statusCode); + echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); + exit; +} + +/** + * @param array $before + * @param array $after + */ +function requiresReload(array $before, array $after): bool +{ + $beforeSkin = (string) ($before['desktop']['active_skin'] ?? ''); + $afterSkin = (string) ($after['desktop']['active_skin'] ?? ''); + $beforeApps = array_values(array_map('strval', (array) ($before['apps']['enabled_ids'] ?? []))); + $afterApps = array_values(array_map('strval', (array) ($after['apps']['enabled_ids'] ?? []))); + + sort($beforeApps); + sort($afterApps); + + return $beforeSkin !== $afterSkin || $beforeApps !== $afterApps; +} diff --git a/public/assets/apps/user-self-management/app.css b/system/apps/user-self-management/assets/app.css similarity index 100% rename from public/assets/apps/user-self-management/app.css rename to system/apps/user-self-management/assets/app.css diff --git a/public/assets/apps/user-self-management/app.js b/system/apps/user-self-management/assets/app.js similarity index 100% rename from public/assets/apps/user-self-management/app.js rename to system/apps/user-self-management/assets/app.js diff --git a/system/apps/user-self-management/page.php b/system/apps/user-self-management/page.php new file mode 100644 index 00000000..2b5762c1 --- /dev/null +++ b/system/apps/user-self-management/page.php @@ -0,0 +1,40 @@ + + + + + + User Self Management + + + +
+ + + +