diff --git a/core/Repository/Stats/AdminStatsRepository.php b/core/Repository/Stats/AdminStatsRepository.php index 5c550d7..b5a0987 100644 --- a/core/Repository/Stats/AdminStatsRepository.php +++ b/core/Repository/Stats/AdminStatsRepository.php @@ -886,9 +886,9 @@ class AdminStatsRepository implements AdminStatsRepositoryInterface $lifecycleRestore7dCount = 0; $lifecycleTopReasonRows = []; $lifecycleRecentRiskRows = []; - $lifecycleAll7dHref = 'admin/user-lifecycle-audit'; - $lifecycleFailed7dHref = 'admin/user-lifecycle-audit'; - $lifecycleRestore7dHref = 'admin/user-lifecycle-audit'; + $lifecycleAll7dHref = 'admin/settings/user-lifecycle'; + $lifecycleFailed7dHref = 'admin/settings/user-lifecycle'; + $lifecycleRestore7dHref = 'admin/settings/user-lifecycle'; if ($userLifecycleAuditStatsAvailable) { $lifecycleRuns7dCount = (int) (DB::selectValue( @@ -920,16 +920,16 @@ class AdminStatsRepository implements AdminStatsRepositoryInterface $lifecycleActionRestore ) ?? 0); - $lifecycleAll7dHref = 'admin/user-lifecycle-audit?' . http_build_query([ + $lifecycleAll7dHref = 'admin/settings/user-lifecycle?' . http_build_query([ 'created_from' => $lifecycleCreatedFrom, 'created_to' => $lifecycleCreatedTo, ]); - $lifecycleFailed7dHref = 'admin/user-lifecycle-audit?' . http_build_query([ + $lifecycleFailed7dHref = 'admin/settings/user-lifecycle?' . http_build_query([ 'statuses' => $lifecycleStatusFailed . ',' . $lifecycleStatusSkipped, 'created_from' => $lifecycleCreatedFrom, 'created_to' => $lifecycleCreatedTo, ]); - $lifecycleRestore7dHref = 'admin/user-lifecycle-audit?' . http_build_query([ + $lifecycleRestore7dHref = 'admin/settings/user-lifecycle?' . http_build_query([ 'actions' => $lifecycleActionRestore, 'created_from' => $lifecycleCreatedFrom, 'created_to' => $lifecycleCreatedTo, diff --git a/docs/reference-benutzer-lifecycle-policy.md b/docs/reference-benutzer-lifecycle-policy.md index ab90f74..5e1bdd5 100644 --- a/docs/reference-benutzer-lifecycle-policy.md +++ b/docs/reference-benutzer-lifecycle-policy.md @@ -86,7 +86,7 @@ Wichtige Regeln: - Delete ist fail-closed: ohne erfolgreich gespeicherten Snapshot wird nicht gelöscht. - Snapshot wird verschlüsselt in `snapshot_enc` gespeichert. - Snapshot enthält nur whitelisted Profilfelder (keine Secrets/Tokens). -- Audit-Retention: 365 Tage (`POST /admin/user-lifecycle-audit/purge`). +- Audit-Retention: 365 Tage (`POST /admin/settings/user-lifecycle/audit-purge`). ## Wiederherstellung (ohne Assignments) diff --git a/modules/audit/module.php b/modules/audit/module.php index fd9c39a..7bf65a5 100644 --- a/modules/audit/module.php +++ b/modules/audit/module.php @@ -31,12 +31,11 @@ return [ ['path' => 'admin/import-audit/view', 'target' => 'audit/import-audit/view'], ['path' => 'admin/import-audit/data', 'target' => 'audit/import-audit/data'], ['path' => 'admin/import-audit/purge', 'target' => 'audit/import-audit/purge'], - // User lifecycle audit pages - ['path' => 'admin/user-lifecycle-audit', 'target' => 'audit/user-lifecycle-audit/index'], - ['path' => 'admin/user-lifecycle-audit/view', 'target' => 'audit/user-lifecycle-audit/view'], - ['path' => 'admin/user-lifecycle-audit/data', 'target' => 'audit/user-lifecycle-audit/data'], - ['path' => 'admin/user-lifecycle-audit/purge', 'target' => 'audit/user-lifecycle-audit/purge'], - ['path' => 'admin/user-lifecycle-audit/restore', 'target' => 'audit/user-lifecycle-audit/restore'], + // User lifecycle audit endpoints embedded in admin/settings/user-lifecycle + ['path' => 'admin/settings/user-lifecycle/audit-data', 'target' => 'audit/settings-user-lifecycle/audit-data'], + ['path' => 'admin/settings/user-lifecycle/audit-fragment', 'target' => 'audit/settings-user-lifecycle/audit-fragment'], + ['path' => 'admin/settings/user-lifecycle/audit-restore', 'target' => 'audit/settings-user-lifecycle/audit-restore'], + ['path' => 'admin/settings/user-lifecycle/audit-purge', 'target' => 'audit/settings-user-lifecycle/audit-purge'], // Frontend telemetry ['path' => 'admin/frontend-telemetry/ingest', 'target' => 'audit/frontend-telemetry/ingest'], ], @@ -72,13 +71,13 @@ return [ 'permission' => 'audit.imports.view', 'order' => 220, ], + ], + 'settings.user_lifecycle.panel' => [ [ - 'key' => 'audit-user-lifecycle-audit', - 'group' => 'admin-logs', - 'label' => 'nav.audit.user_lifecycle', - 'path' => 'audit/user-lifecycle-audit', + 'key' => 'audit-settings-user-lifecycle-panel', + 'template' => 'templates/settings-user-lifecycle-panel.phtml', 'permission' => 'audit.user_lifecycle.view', - 'order' => 230, + 'order' => 100, ], ], ], diff --git a/modules/audit/pages/audit/user-lifecycle-audit/data().php b/modules/audit/pages/audit/settings-user-lifecycle/audit-data().php similarity index 100% rename from modules/audit/pages/audit/user-lifecycle-audit/data().php rename to modules/audit/pages/audit/settings-user-lifecycle/audit-data().php diff --git a/modules/audit/pages/audit/user-lifecycle-audit/view($id).php b/modules/audit/pages/audit/settings-user-lifecycle/audit-fragment($id).php similarity index 57% rename from modules/audit/pages/audit/user-lifecycle-audit/view($id).php rename to modules/audit/pages/audit/settings-user-lifecycle/audit-fragment($id).php index 9e522ce..f4fac22 100644 --- a/modules/audit/pages/audit/user-lifecycle-audit/view($id).php +++ b/modules/audit/pages/audit/settings-user-lifecycle/audit-fragment($id).php @@ -4,31 +4,29 @@ use MintyPHP\Http\SessionStoreInterface; use MintyPHP\Module\Audit\AuditAuthorizationPolicy; use MintyPHP\Module\Audit\Domain\UserLifecycleAction; use MintyPHP\Module\Audit\Service\UserLifecycleAuditService; -use MintyPHP\Module\Audit\Support\AuditPageHelper; use MintyPHP\Service\Access\UiAccessService; +use MintyPHP\Support\Guard; $session = app(SessionStoreInterface::class)->all(); +Guard::requireLogin(); +Guard::requireAbilityOrForbidden(AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_VIEW); -$auditLog = AuditPageHelper::findOrRedirect( - AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_VIEW, - UserLifecycleAuditService::class, - 'audit/user-lifecycle-audit', - 'Lifecycle audit entry not found', - 'user_lifecycle_audit_not_found', - 'View user lifecycle audit entry', - $id ?? null -); -$breadcrumbs = [ - ['label' => t('Home'), 'path' => 'admin'], - ['label' => t('User lifecycle logs'), 'path' => 'audit/user-lifecycle-audit'], - ['label' => t('View')], -]; +$auditId = (int) ($id ?? 0); +$auditLog = null; +if ($auditId > 0) { + $auditLog = app(UserLifecycleAuditService::class)->find($auditId); +} +if (!is_array($auditLog)) { + http_response_code(404); + return; +} $auditService = app(UserLifecycleAuditService::class); $snapshot = null; if ((string) ($auditLog['action'] ?? '') === UserLifecycleAction::Delete->value) { $snapshot = $auditService->decryptSnapshot($auditLog); } +$viewAuth = is_array($viewAuth ?? null) ? $viewAuth : []; $viewAuth['page'] = app(UiAccessService::class)->pageCapabilities( (int) ($session['user']['id'] ?? 0), ['can_restore' => AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_RESTORE] diff --git a/modules/audit/pages/audit/settings-user-lifecycle/audit-fragment(none).phtml b/modules/audit/pages/audit/settings-user-lifecycle/audit-fragment(none).phtml new file mode 100644 index 0000000..2598307 --- /dev/null +++ b/modules/audit/pages/audit/settings-user-lifecycle/audit-fragment(none).phtml @@ -0,0 +1,177 @@ + $auditLog + * @var ?array $snapshot + */ + +$auditLog = is_array($auditLog ?? null) ? $auditLog : []; +$snapshot = is_array($snapshot ?? null) ? $snapshot : null; + +$status = UserLifecycleStatus::normalizeOr((string) ($auditLog['status'] ?? ''), UserLifecycleStatus::Failed); +$statusVariant = $status->badgeVariant(); +$action = UserLifecycleAction::normalizeOr((string) ($auditLog['action'] ?? ''), UserLifecycleAction::Deactivate); +$triggerType = UserLifecycleTriggerType::normalizeOr((string) ($auditLog['trigger_type'] ?? ''), UserLifecycleTriggerType::System); +$pageAuth = is_array($viewAuth['page'] ?? null) ? $viewAuth['page'] : []; +$canRestore = (bool) ($pageAuth['can_restore'] ?? false); +$isRestorable = $canRestore + && $action === UserLifecycleAction::Delete + && $status === UserLifecycleStatus::Success + && empty($auditLog['restored_at']) + && !empty($auditLog['snapshot_enc']); +?> + +
+
+

+

:

+
+ +
+
+ +
+
+
+ +

+
+
+ +

labelToken())); ?>

+
+
+
+
+ +

labelToken())); ?>

+
+
+ +

labelToken())); ?>

+
+
+
+
+ +

+
+
+ +

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

+
+
+ +

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

+
+
+ +

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

+
+
+ +

+
+
+
+
+ +

+
+
+ +

+
+
+
+
+ +

+
+
+ +

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

+
+
+ +

+
+
+
+ +

+ + + + + + - + +

+
+ +
+
+ + +
+ +
+
diff --git a/modules/audit/pages/audit/user-lifecycle-audit/purge().php b/modules/audit/pages/audit/settings-user-lifecycle/audit-purge().php similarity index 81% rename from modules/audit/pages/audit/user-lifecycle-audit/purge().php rename to modules/audit/pages/audit/settings-user-lifecycle/audit-purge().php index 677eb7d..7912bfa 100644 --- a/modules/audit/pages/audit/user-lifecycle-audit/purge().php +++ b/modules/audit/pages/audit/settings-user-lifecycle/audit-purge().php @@ -8,14 +8,14 @@ use MintyPHP\Support\Flash; AuditPageHelper::requirePurgePost( SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE, - 'audit/user-lifecycle-audit', + 'admin/settings/user-lifecycle', 'user_lifecycle_audit' ); $deleted = app(UserLifecycleAuditService::class)->purgeExpired(); Flash::success( sprintf(t('%d user lifecycle audit entries purged'), $deleted), - 'audit/user-lifecycle-audit', + 'admin/settings/user-lifecycle', 'user_lifecycle_audit_purged' ); -Router::redirect('audit/user-lifecycle-audit'); +Router::redirect('admin/settings/user-lifecycle'); diff --git a/modules/audit/pages/audit/user-lifecycle-audit/restore($id).php b/modules/audit/pages/audit/settings-user-lifecycle/audit-restore($id).php similarity index 72% rename from modules/audit/pages/audit/user-lifecycle-audit/restore($id).php rename to modules/audit/pages/audit/settings-user-lifecycle/audit-restore($id).php index f601f5c..c0a431c 100644 --- a/modules/audit/pages/audit/user-lifecycle-audit/restore($id).php +++ b/modules/audit/pages/audit/settings-user-lifecycle/audit-restore($id).php @@ -14,12 +14,12 @@ $auditId = (int) ($id ?? 0); $errorBag = formErrors(); if (strtoupper((string) (requestInput()->method())) !== 'POST') { - Router::redirect('audit/user-lifecycle-audit'); + Router::redirect('admin/settings/user-lifecycle'); } if (!Session::checkCsrfToken()) { $errorBag->addGlobal(t('Form expired, please try again')); - flashFormErrors($errorBag, 'audit/user-lifecycle-audit', 'user_lifecycle_restore'); - Router::redirect('audit/user-lifecycle-audit'); + flashFormErrors($errorBag, 'admin/settings/user-lifecycle', 'user_lifecycle_restore'); + Router::redirect('admin/settings/user-lifecycle'); } $result = app(\MintyPHP\Service\User\UserLifecycleRestoreService::class)->restoreFromLifecycleAudit( @@ -40,9 +40,9 @@ if (!($result['ok'] ?? false)) { } else { $errorBag->addGlobal(t('User restore failed')); } - flashFormErrors($errorBag, "audit/user-lifecycle-audit/view/$auditId", 'user_lifecycle_restore'); - Router::redirect("audit/user-lifecycle-audit/view/$auditId"); + flashFormErrors($errorBag, 'admin/settings/user-lifecycle', 'user_lifecycle_restore'); + Router::redirect('admin/settings/user-lifecycle'); } -Flash::success(t('User restored'), "audit/user-lifecycle-audit/view/$auditId", 'user_restored'); -Router::redirect("audit/user-lifecycle-audit/view/$auditId"); +Flash::success(t('User restored'), 'admin/settings/user-lifecycle', 'user_restored'); +Router::redirect('admin/settings/user-lifecycle'); diff --git a/modules/audit/pages/audit/user-lifecycle-audit/filter-schema.php b/modules/audit/pages/audit/settings-user-lifecycle/filter-schema.php similarity index 100% rename from modules/audit/pages/audit/user-lifecycle-audit/filter-schema.php rename to modules/audit/pages/audit/settings-user-lifecycle/filter-schema.php diff --git a/modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml b/modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml deleted file mode 100644 index ca33265..0000000 --- a/modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - -
-
-
- - $searchConfig, - 'filterSchema' => $clientFilterSchema, - 'filterChipMeta' => $filterChipMeta, - 'gridLang' => $gridLang, - 'labels' => [ - 'created' => t('Created'), - 'status' => t('Status'), - 'action' => t('Action'), - 'trigger' => t('Trigger'), - 'targetEmail' => t('Target email'), - 'targetUuid' => t('Target UUID'), - 'reasonCode' => t('Reason code'), - 'restoredAt' => t('Restored at'), - ], -]; -?> - - - diff --git a/modules/audit/pages/audit/user-lifecycle-audit/view(default).phtml b/modules/audit/pages/audit/user-lifecycle-audit/view(default).phtml deleted file mode 100644 index d65e6b4..0000000 --- a/modules/audit/pages/audit/user-lifecycle-audit/view(default).phtml +++ /dev/null @@ -1,206 +0,0 @@ -badgeVariant(); - -$action = UserLifecycleAction::normalizeOr((string) ($auditLog['action'] ?? ''), UserLifecycleAction::Deactivate); -$triggerType = UserLifecycleTriggerType::normalizeOr((string) ($auditLog['trigger_type'] ?? ''), UserLifecycleTriggerType::System); -$pageAuth = is_array($viewAuth['page'] ?? null) ? $viewAuth['page'] : []; -$canRestore = (bool) ($pageAuth['can_restore'] ?? false); -$isRestorable = $canRestore - && $action === UserLifecycleAction::Delete - && $status === UserLifecycleStatus::Success - && empty($auditLog['restored_at']) - && !empty($auditLog['snapshot_enc']); -?> - -
-
- t('View user lifecycle audit entry'), - 'backHref' => 'audit/user-lifecycle-audit', - 'backTitle' => t('Back'), - ]; - require templatePath('partials/app-details-titlebar.phtml'); - ?> - -
-
- -
-
-
- -

-
-
- -

labelToken())); ?>

-
-
-
-
- -

labelToken())); ?>

-
-
- -

labelToken())); ?>

-
-
-
-
- -

-
-
- -

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

-
-
- -

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

-
-
- -

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

-
-
- -

-
-
-
-
- -

-
-
- -

-
-
-
-
- -

-
-
- -

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

-
-
- -

-
-
-
- -

- - - - - - - - -

-
- -
-
- - -
- -
-
-
- - -
diff --git a/modules/audit/pages/audit/user-lifecycle-audit/index().php b/modules/audit/templates/settings-user-lifecycle-panel.phtml similarity index 58% rename from modules/audit/pages/audit/user-lifecycle-audit/index().php rename to modules/audit/templates/settings-user-lifecycle-panel.phtml index 765c099..e47e532 100644 --- a/modules/audit/pages/audit/user-lifecycle-audit/index().php +++ b/modules/audit/templates/settings-user-lifecycle-panel.phtml @@ -1,32 +1,12 @@ all(); -Guard::requireLogin(); -Guard::requireAbility(AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_VIEW); -$viewAuth['page'] = app(UiAccessService::class)->pageCapabilities( - (int) ($session['user']['id'] ?? 0), - ['can_purge' => SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE] -); +$canPurge = (bool) ($canUpdateSettings ?? false); -Buffer::set('title', t('User lifecycle logs')); -$breadcrumbs = [ - ['label' => t('Home'), 'path' => 'admin'], - ['label' => t('User lifecycle logs')], -]; -Buffer::set('grid_lang', json_encode(gridLang(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); - -$filterSchema = require __DIR__ . '/filter-schema.php'; +$filterSchema = require __DIR__ . '/../pages/audit/settings-user-lifecycle/filter-schema.php'; $filterState = gridParseFilters(requestInput()->queryAll(), [ ...gridSchemaQuery($filterSchema), 'actions' => [ @@ -49,8 +29,6 @@ $filterState = gridParseFilters(requestInput()->queryAll(), [ ], 'actor_user_ids' => ['type' => 'csv_ids', 'max' => 200, 'return' => 'array'], ]); -$filterOptions = app(UserLifecycleAuditService::class)->filterOptions(200); - $activeActions = is_array($filterState['actions'] ?? null) ? $filterState['actions'] : []; $activeStatuses = is_array($filterState['statuses'] ?? null) ? $filterState['statuses'] : []; $activeTriggerTypes = is_array($filterState['trigger_types'] ?? null) ? $filterState['trigger_types'] : []; @@ -76,16 +54,6 @@ foreach (UserLifecycleAction::values() as $action) { 'description' => $actionLabelMap[$action] ?? $action, ]; } -foreach ((array) ($filterOptions['actions'] ?? []) as $action) { - $action = strtolower(trim((string) $action)); - if ($action === '') { - continue; - } - $lifecycleActionItems[$action] = [ - 'id' => $action, - 'description' => $actionLabelMap[$action] ?? $action, - ]; -} $lifecycleActionItems = array_values($lifecycleActionItems); $lifecycleStatusItems = []; @@ -95,16 +63,6 @@ foreach (UserLifecycleStatus::values() as $status) { 'description' => $statusLabelMap[$status] ?? $status, ]; } -foreach ((array) ($filterOptions['statuses'] ?? []) as $status) { - $status = strtolower(trim((string) $status)); - if ($status === '') { - continue; - } - $lifecycleStatusItems[$status] = [ - 'id' => $status, - 'description' => $statusLabelMap[$status] ?? $status, - ]; -} $lifecycleStatusItems = array_values($lifecycleStatusItems); $lifecycleTriggerItems = []; @@ -114,48 +72,14 @@ foreach (UserLifecycleTriggerType::values() as $triggerType) { 'description' => $triggerLabelMap[$triggerType] ?? $triggerType, ]; } -foreach ((array) ($filterOptions['trigger_types'] ?? []) as $triggerType) { - $triggerType = strtolower(trim((string) $triggerType)); - if ($triggerType === '') { - continue; - } - $lifecycleTriggerItems[$triggerType] = [ - 'id' => $triggerType, - 'description' => $triggerLabelMap[$triggerType] ?? $triggerType, - ]; -} $lifecycleTriggerItems = array_values($lifecycleTriggerItems); $lifecycleActorItems = []; -foreach ((array) ($filterOptions['actors'] ?? []) as $actorOption) { - if (!is_array($actorOption)) { - continue; - } - $actorId = (int) ($actorOption['id'] ?? 0); - if ($actorId <= 0) { - continue; - } - $actorDisplayName = trim((string) ($actorOption['display_name'] ?? '')); - $actorEmail = trim((string) ($actorOption['email'] ?? '')); - $actorExists = (bool) ($actorOption['exists'] ?? false); - $label = $actorDisplayName !== '' ? $actorDisplayName : $actorEmail; - if ($label === '') { - $label = $actorExists - ? sprintf(t('User #%d'), $actorId) - : sprintf(t('User #%d (deleted)'), $actorId); - } - - $id = (string) $actorId; - $lifecycleActorItems[$id] = [ - 'id' => $id, - 'description' => $label, - ]; -} foreach ($activeActorUserIds as $actorId) { if (!isset($lifecycleActorItems[$actorId])) { $lifecycleActorItems[$actorId] = [ 'id' => $actorId, - 'description' => sprintf(t('User #%d (deleted)'), (int) $actorId), + 'description' => sprintf(t('User #%d'), (int) $actorId), ]; } } @@ -182,11 +106,14 @@ $listFilterContext = gridBuildListFilterContext($filterSchema, [ 'toolbar_state_overrides' => $toolbarFilterState, 'toolbar_option_sets' => $toolbarOptionSets, ]); -$toolbarFilterSchema = $listFilterContext['toolbarFilterSchema']; -$toolbarFilterState = $listFilterContext['toolbarFilterState']; -$searchToolbarFilterSchema = $listFilterContext['searchToolbarFilterSchema']; -$drawerToolbarFilterSchema = $listFilterContext['drawerToolbarFilterSchema']; -$toolbarOptionSets = $listFilterContext['toolbarOptionSets']; +$toolbarFilterSchema = is_array($listFilterContext['toolbarFilterSchema'] ?? null) ? $listFilterContext['toolbarFilterSchema'] : []; +$searchToolbarFilterSchema = is_array($listFilterContext['searchToolbarFilterSchema'] ?? null) ? $listFilterContext['searchToolbarFilterSchema'] : []; +$drawerToolbarFilterSchema = is_array($listFilterContext['drawerToolbarFilterSchema'] ?? null) ? $listFilterContext['drawerToolbarFilterSchema'] : []; +$toolbarFilterState = is_array($listFilterContext['toolbarFilterState'] ?? null) ? $listFilterContext['toolbarFilterState'] : []; +$toolbarOptionSets = is_array($listFilterContext['toolbarOptionSets'] ?? null) ? $listFilterContext['toolbarOptionSets'] : []; +$clientFilterSchema = is_array($listFilterContext['clientFilterSchema'] ?? null) ? $listFilterContext['clientFilterSchema'] : []; +$searchConfig = $listFilterContext['searchConfig'] ?? null; + $filterChipMeta = [ 'search' => [ 'label' => t('Search'), @@ -219,5 +146,50 @@ $filterChipMeta = [ 'to_param' => 'created_to', ], ]; -$clientFilterSchema = $listFilterContext['clientFilterSchema']; -$searchConfig = $listFilterContext['searchConfig']; + +$listTitle = t('User lifecycle logs'); +ob_start(); +$listPurgeEnabled = $canPurge; +$listPurgeFormId = 'settings-user-lifecycle-audit-purge-form'; +$listPurgeAction = endpointUrl('admin/settings/user-lifecycle/audit-purge'); +$listPurgeConfirmMessage = t('Purge entries older than 365 days?'); +$listPurgeButtonLabel = t('Purge user lifecycle logs'); +require templatePath('partials/app-list-purge-action.phtml'); +$listTitleActionsHtml = ob_get_clean(); + +$filterUiNamespace = 'settings-user-lifecycle'; +$filterToolbarId = 'settings-user-lifecycle-audit-toolbar'; +$filterDrawerToolbarId = 'settings-user-lifecycle-audit-drawer-toolbar'; +$filterDrawerTitleId = 'settings-user-lifecycle-audit-filter-drawer-title'; + +$pageConfig = [ + 'dataUrl' => endpointUrl('admin/settings/user-lifecycle/audit-data'), + 'gridSearch' => $searchConfig, + 'filterSchema' => $clientFilterSchema, + 'filterChipMeta' => $filterChipMeta, + 'gridLang' => gridLang(), + 'labels' => [ + 'created' => t('Created'), + 'status' => t('Status'), + 'action' => t('Action'), + 'trigger' => t('Trigger'), + 'targetEmail' => t('Target email'), + 'targetUuid' => t('Target UUID'), + 'reasonCode' => t('Reason code'), + 'restoredAt' => t('Restored at'), + ], +]; +?> + +
+ + + +
+
+
+ + + + +
diff --git a/modules/audit/web/js/pages/admin-user-lifecycle-audit-index.js b/modules/audit/web/js/pages/settings-user-lifecycle-audit-panel.js similarity index 62% rename from modules/audit/web/js/pages/admin-user-lifecycle-audit-index.js rename to modules/audit/web/js/pages/settings-user-lifecycle-audit-panel.js index f1681aa..0a00bae 100644 --- a/modules/audit/web/js/pages/admin-user-lifecycle-audit-index.js +++ b/modules/audit/web/js/pages/settings-user-lifecycle-audit-panel.js @@ -1,12 +1,14 @@ import { createListPageModule } from '/js/core/app-list-page-module.js'; +import { initDetailDrawer } from '/js/components/app-detail-drawer.js'; createListPageModule({ - configId: 'admin-user-lifecycle-audit-index', - moduleId: 'admin-user-lifecycle-audit-index', + configId: 'settings-user-lifecycle-audit-panel', + moduleId: 'settings-user-lifecycle-audit-panel', missingGridMessage: 'User lifecycle audit grid init failed: Grid.js missing', initErrorMessage: 'User lifecycle audit grid init failed', setup: ({ config, gridjs, appBase, initListPage }) => { const labels = config.labels ?? {}; + const dataUrl = String(config.dataUrl || 'audit/settings-user-lifecycle/audit-data'); const gridSearch = config.gridSearch ?? null; const filterSchema = config.filterSchema ?? []; const filterChipMeta = config.filterChipMeta ?? []; @@ -14,8 +16,8 @@ createListPageModule({ const { gridConfig } = initListPage({ grid: { gridjs, - container: '#user-lifecycle-audit-grid', - dataUrl: 'audit/user-lifecycle-audit/data', + container: '#settings-user-lifecycle-audit-grid', + dataUrl, appBase, columns: [ { name: labels.created || 'Created', sort: true }, @@ -29,7 +31,18 @@ createListPageModule({ return gridjs.html(`${cell.label || '-'}`); }, }, - { name: labels.action || 'Action', sort: true }, + { + name: labels.action || 'Action', + sort: true, + formatter: (cell, row) => { + const id = row?.cells?.[8]?.data; + const label = String(cell || '-'); + if (!id) { + return gridjs.html(label); + } + return gridjs.html(`${label}`); + }, + }, { name: labels.trigger || 'Trigger', sort: true }, { name: labels.targetEmail || 'Target email', sort: false }, { name: labels.targetUuid || 'Target UUID', sort: false }, @@ -51,14 +64,17 @@ createListPageModule({ row.restored_at || '-', row.id, ]), - rowInteraction: { linkColumn: 2 }, + rowInteraction: { linkColumn: false }, search: gridSearch, filterSchema, urlSync: true, + rowDataset: (row) => ({ + 'audit-id': row?.cells?.[8]?.data ? String(row.cells[8].data) : '', + }), rowDblClick: { getUrl: (rowData) => { const id = rowData?.cells?.[8]?.data; - return id ? new URL(`audit/user-lifecycle-audit/view/${id}`, appBase).toString() : ''; + return id ? new URL(`admin/settings/user-lifecycle#lifecycle-audit/${id}`, appBase).toString() : ''; }, }, }, @@ -69,6 +85,15 @@ createListPageModule({ }, }) || {}; + initDetailDrawer({ + gridConfig, + triggerSelector: '[data-lifecycle-drawer-trigger]', + rowUuidAttr: 'audit-id', + fetchUrl: (id) => new URL(`audit/settings-user-lifecycle/audit-fragment/${id}`, appBase).toString(), + fullUrl: (id) => new URL(`admin/settings/user-lifecycle#lifecycle-audit/${id}`, appBase).toString(), + hashPrefix: 'lifecycle-audit', + }); + return gridConfig; }, }); diff --git a/pages/admin/settings/user-lifecycle(default).phtml b/pages/admin/settings/user-lifecycle(default).phtml index c3efde5..5978f91 100644 --- a/pages/admin/settings/user-lifecycle(default).phtml +++ b/pages/admin/settings/user-lifecycle(default).phtml @@ -17,6 +17,12 @@ $userInactivityDeleteDaysDesc = $settings['user_inactivity_delete_days']['descri $canUpdateSettings = (bool) ($canUpdateSettings ?? false); $isReadOnly = !$canUpdateSettings; $readonlyAttr = $isReadOnly ? 'readonly' : ''; +$layoutAuth = is_array($viewAuth['layout'] ?? null) ? $viewAuth['layout'] : []; +$layoutNav = is_array($layoutNav ?? null) ? $layoutNav : []; +$moduleSlots = is_array($layoutNav['moduleSlots'] ?? null) ? $layoutNav['moduleSlots'] : []; +$moduleLifecyclePanelSlots = is_array($moduleSlots['settings.user_lifecycle.panel'] ?? null) + ? $moduleSlots['settings.user_lifecycle.panel'] + : []; ?>
@@ -86,6 +92,23 @@ $readonlyAttr = $isReadOnly ? 'readonly' : ''; + + + +