From 006792a8af11b9cbf951136b2b363a0eba94e3e6 Mon Sep 17 00:00:00 2001 From: Clemens Creutzburg Date: Sun, 22 Mar 2026 09:05:53 +0100 Subject: [PATCH] Anpassung Tenant Admin --- README.md | 10 +- docs/installationshandbuch.md | 19 +- saas-app/README.md | 23 +- saas-app/public/admin.php | 278 +++++++++------------- saas-app/public/admin/logout/index.php | 5 + saas-app/public/admin/migration/index.php | 3 +- saas-app/public/app-support.php | 124 ++++++---- saas-app/public/index.php | 106 ++++----- saas-app/public/install.php | 34 +-- saas-app/public/logout/index.php | 7 + 10 files changed, 289 insertions(+), 320 deletions(-) create mode 100644 saas-app/public/admin/logout/index.php create mode 100644 saas-app/public/logout/index.php diff --git a/README.md b/README.md index 282933e..cc1d596 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Dieses Repository ist jetzt `SaaS-first` organisiert. - `saas-app/` enthaelt die neue Zielanwendung fuer die mandantenfaehige Kaffeeliste. -- `docs/` enthaelt Architektur-, Installations- und Migrationsdokumentation. +- `docs/` enthaelt Architektur- und Installationsdokumentation. - `legacy-app/` enthaelt den bisherigen PHP-Bestand als archivierte Referenz fuer - Fachlogik und Datenmigration. + fachliche Orientierung. ## Produktkern @@ -35,7 +35,7 @@ unter `saas-app/public/install/`. Nach dem Setup gibt es jetzt zwei zentrale Web-Einstiege: - `saas-app/public/admin/login/` fuer den Global-Admin -- `saas-app/public/admin/migration/` fuer die webbasierte Legacy-Migration +- `saas-app/public/admin/` fuer die zentrale Verwaltung ## Hilfsskripte @@ -47,8 +47,8 @@ Nach dem Setup gibt es jetzt zwei zentrale Web-Einstiege: ## Hinweise Zum Umbau -- `legacy-app/` ist absichtlich nicht geloescht, sondern als Referenz fuer die - Daten- und Fachmigration verschoben. +- `legacy-app/` ist absichtlich nicht geloescht, sondern als Referenz fuer + fachliche Detailfragen verschoben. - Das aktuelle `saas-app/` ist eine konsistente Zielarchitektur mit ueberarbeiteten Views, Modulgrenzen und Betriebsdokumentation. - Ein vollstaendiges Laravel-Bootstrap mit Composer und Runtime bleibt der diff --git a/docs/installationshandbuch.md b/docs/installationshandbuch.md index a72ab70..6b7f941 100644 --- a/docs/installationshandbuch.md +++ b/docs/installationshandbuch.md @@ -93,7 +93,7 @@ eigene Umgebung angepasst werden: 4. Sicherstellen, dass `open_basedir` nicht nur auf `public/` eingeschraenkt ist. PHP muss mindestens auf den kompletten Ordner `saas-app/` zugreifen duerfen, obwohl der Document-Root auf `public/` zeigt. 5. Den Installer unter `/install/` aufrufen und die Einrichtung durchfuehren. 6. Danach den Global-Admin unter `/admin/login` anmelden. -7. Bei Bedarf die Legacy-Datenmigration unter `/admin/migration` starten. +7. In der zentralen Verwaltung unter `/admin/` die ersten Tenants anlegen. 8. Nach erfolgreicher Einrichtung den Installer sperren. 9. Die Anwendung einmal per Browser aufrufen und die Grundseiten pruefen. @@ -124,22 +124,13 @@ Typische Aufgaben: Empfehlung: pro Aufgabe einen klar benannten Cron-Eintrag anlegen und die Ausgabe in Logdateien schreiben. -## Migration Aus Dem Legacy-System +## Zentrale Verwaltung -Wenn Daten aus der alten Root-Anwendung uebernommen werden sollen, folgt die -Reihenfolge: - -1. Mitglieder und Rollen migrieren. -2. Einzahlungen und Kaffeebuchungen uebernehmen. -3. Hinweise, FAQ und Inhaltsseiten importieren. -4. Mandanten-Zuordnung pruefen. -5. Danach alte Root-Seiten nur noch lesend oder gar nicht mehr betreiben. - -Der neue Webweg dafuer ist jetzt: +Nach der Installation stehen diese Web-Einstiege bereit: - `/admin/login` fuer den Global-Admin -- `/admin/tenants` fuer die zentrale Tenant-Verwaltung -- `/admin/migration` fuer die Legacy-Datenmigration +- `/admin/` fuer die zentrale Verwaltungsuebersicht +- `/admin/tenants` fuer die Tenant-Verwaltung ## Betriebscheck diff --git a/saas-app/README.md b/saas-app/README.md index ee2a8fe..418da8e 100644 --- a/saas-app/README.md +++ b/saas-app/README.md @@ -22,7 +22,7 @@ Kurzfassung: 1. Webserver auf `public/` ausrichten. 2. Im Browser `https://deine-domain.tld/install/` aufrufen. 3. `.env`, DB-Zugang und Tenancy-Werte ueber den Installer speichern. -4. SQL-Bundle erzeugen und wenn moeglich Migrationen direkt ausfuehren. +4. SQL-Bundle erzeugen und wenn moeglich das Datenbankschema direkt ausfuehren. 5. Installer sperren. 6. Einen ersten Mandanten und erste Benutzer anlegen. 7. Cron-Jobs fuer Queue, Import, Export und Benachrichtigungen einrichten. @@ -45,19 +45,13 @@ Skripte verarbeitet: Wenn `pdo_mysql` lokal oder auf dem Hosting nicht verfuegbar ist, muss das erzeugte SQL-Bundle manuell gegen MySQL/MariaDB ausgefuehrt werden. -## Migration Aus Dem Legacy-System +## Zentrale Verwaltung -Die fachliche Roadmap und der Uebergang aus dem alten Root-System sind in -`../docs/implementation-foundation.md` beschrieben. +Nach der Installation erfolgt die zentrale Administration ueber: -Der relevante Kern der alten Anwendung besteht im Wesentlichen aus: - -- Dashboard und Kontostand -- Mitgliederverwaltung -- Kaffee-Striche -- Einzahlungen -- Hinweise und Inhalte -- Exporte und operative Hilfsfunktionen +- `public/admin/login/` +- `public/admin/` +- `public/admin/tenants/` ## Hosting-Hinweise @@ -70,6 +64,5 @@ Der relevante Kern der alten Anwendung besteht im Wesentlichen aus: ## Aktueller Stand -Das Verzeichnis ist als Zielarchitektur vorbereitet. Es ersetzt den Legacy-Root -noch nicht vollstaendig, sondern dient als naechster konsistenter Zielzustand -fuer die SaaS-Umstellung. +Das Verzeichnis ist als Zielarchitektur fuer den Neuaufbau vorbereitet und +dient als konsistenter Zielzustand fuer die SaaS-Umstellung. diff --git a/saas-app/public/admin.php b/saas-app/public/admin.php index ccee97b..f7654a2 100644 --- a/saas-app/public/admin.php +++ b/saas-app/public/admin.php @@ -22,13 +22,39 @@ function admin_badge(string $label, string $tone = 'neutral'): string return '' . admin_h($label) . ''; } -$path = parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/admin/login'), PHP_URL_PATH); +function admin_summary_metrics(array $tenants): array +{ + $tenantCount = count($tenants); + $activeTenants = 0; + $memberCount = 0; + $adminCount = 0; + $providerCount = 0; + + foreach ($tenants as $tenant) { + if (($tenant['status'] ?? '') === 'active') { + $activeTenants++; + } + + $memberCount += (int) ($tenant['member_count'] ?? 0); + $adminCount += (int) ($tenant['admin_count'] ?? 0); + $providerCount += (int) ($tenant['provider_count'] ?? 0); + } + + return [ + ['label' => 'Mandanten gesamt', 'value' => (string) $tenantCount, 'detail' => 'Mandanten im zentralen Portfolio.'], + ['label' => 'Aktive Mandanten', 'value' => (string) $activeTenants, 'detail' => 'Bereiche mit aktivem Status.'], + ['label' => 'Mitglieder gesamt', 'value' => (string) $memberCount, 'detail' => 'Aktive Zuordnungen über alle Mandanten.'], + ['label' => 'SSO-Provider', 'value' => (string) $providerCount, 'detail' => 'Aktive Identitätsanbieter für die Anmeldung.'], + ['label' => 'Tenant-Admins', 'value' => (string) $adminCount, 'detail' => 'Verwaltungszugänge in den Mandanten.'], + ]; +} + +$path = parse_url((string) ($_SERVER['REQUEST_URI'] ?? '/admin/login/'), PHP_URL_PATH); $path = is_string($path) ? rtrim($path, '/') : '/admin/login'; $page = match ($path) { - '/admin', '/admin/' => 'tenants', + '/admin', '/admin/' => 'overview', '/admin/login' => 'login', '/admin/tenants' => 'tenants', - '/admin/migration' => 'migration', '/admin/logout' => 'logout', default => 'login', }; @@ -40,27 +66,15 @@ $pdo = null; $adminLogin = ['state' => app_admin_login_state(), 'message' => null, 'error' => null]; $tenants = []; $editingTenant = null; -$migrationResult = null; -$migrationErrors = []; -$env = app_env(); -$migrationValues = [ - 'SOURCE_CONNECTION' => 'sqlsrv', - 'SOURCE_HOST' => '127.0.0.1', - 'SOURCE_PORT' => '1433', - 'SOURCE_DATABASE' => '', - 'SOURCE_USERNAME' => '', - 'SOURCE_PASSWORD' => '', - 'TARGET_TENANT_KEY' => 'legacy-import', - 'TARGET_TENANT_NAME' => 'Legacy Import', -]; +$summaryMetrics = []; if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && $page === 'logout') { if (hash_equals($_SESSION['admin_csrf'], (string) ($_POST['csrf'] ?? ''))) { app_logout(); - app_flash('Global-Admin wurde abgemeldet.', 'success'); + app_flash('Der Global-Admin wurde abgemeldet.', 'success'); } - app_redirect('/admin/login'); + app_redirect('/admin/login/'); } try { @@ -74,7 +88,7 @@ if ($page === 'login' && $pdo instanceof PDO) { $admin = app_admin_user(); } -if (in_array($page, ['tenants', 'migration'], true)) { +if (in_array($page, ['overview', 'tenants'], true)) { $admin = app_require_platform_admin(); if (!$pdo instanceof PDO) { @@ -82,60 +96,22 @@ if (in_array($page, ['tenants', 'migration'], true)) { } } -if ($page === 'tenants' && $pdo instanceof PDO) { +if (in_array($page, ['overview', 'tenants'], true) && $pdo instanceof PDO) { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && !hash_equals($_SESSION['admin_csrf'], (string) ($_POST['csrf'] ?? ''))) { - app_flash('CSRF-Status ungueltig. Bitte Seite neu laden.', 'error'); - app_redirect('/admin/tenants'); + app_flash('Die Sitzung ist abgelaufen. Bitte lade die Seite neu.', 'error'); + app_redirect('/admin/tenants/'); } app_handle_platform_tenant_action($pdo); $tenants = app_admin_tenant_list($pdo); + $summaryMetrics = admin_summary_metrics($tenants); - if (isset($_GET['edit']) && $_GET['edit'] !== '') { - $editingTenant = app_query_one($pdo, 'SELECT id, tenant_key, name, status FROM tenants WHERE id = :id LIMIT 1', ['id' => (string) $_GET['edit']]); - } -} - -if ($page === 'migration') { - foreach (array_keys($migrationValues) as $key) { - if (isset($_POST[$key])) { - $migrationValues[$key] = trim((string) $_POST[$key]); - } - } - - if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && (string) ($_POST['action'] ?? '') === 'run-legacy-migration') { - if (!hash_equals($_SESSION['admin_csrf'], (string) ($_POST['csrf'] ?? ''))) { - $migrationErrors[] = 'CSRF-Status ungueltig. Bitte Seite neu laden.'; - } elseif (!$pdo instanceof PDO) { - $migrationErrors[] = $dbError ?? 'Die Zieldatenbank ist aktuell nicht erreichbar.'; - } else { - try { - $migrationResult = scripts_migrate_legacy_data( - [ - 'connection' => $migrationValues['SOURCE_CONNECTION'], - 'server' => $migrationValues['SOURCE_HOST'], - 'port' => $migrationValues['SOURCE_PORT'], - 'database' => $migrationValues['SOURCE_DATABASE'], - 'username' => $migrationValues['SOURCE_USERNAME'], - 'password' => $migrationValues['SOURCE_PASSWORD'], - ], - [ - 'connection' => $env['DB_CONNECTION'] ?? 'mysql', - 'server' => $env['DB_HOST'] ?? '127.0.0.1', - 'port' => $env['DB_PORT'] ?? '3306', - 'database' => $env['DB_DATABASE'] ?? '', - 'username' => $env['DB_USERNAME'] ?? '', - 'password' => $env['DB_PASSWORD'] ?? '', - ], - [ - 'tenant_key' => $migrationValues['TARGET_TENANT_KEY'], - 'tenant_name' => $migrationValues['TARGET_TENANT_NAME'], - ] - ); - } catch (Throwable $exception) { - $migrationErrors[] = $exception->getMessage(); - } - } + if ($page === 'tenants' && isset($_GET['edit']) && $_GET['edit'] !== '') { + $editingTenant = app_query_one( + $pdo, + 'SELECT id, tenant_key, name, status FROM tenants WHERE id = :id LIMIT 1', + ['id' => (string) $_GET['edit']] + ); } } @@ -147,7 +123,7 @@ if ($page === 'migration') { Kaffeeliste Admin @@ -155,14 +131,14 @@ if ($page === 'migration') {
Kaffeeliste Admin -

Global-Admin Login, Tenant-Verwaltung und Legacy-Migration direkt ueber PHP-Webseiten.

+

Zentrale Verwaltung für Mandanten, Zugänge und Betriebsstatus.