Embed user lifecycle audit into settings page

This commit is contained in:
2026-04-26 18:03:42 +02:00
parent 01f5265eff
commit 144d8410a4
22 changed files with 345 additions and 434 deletions

View File

@@ -886,9 +886,9 @@ class AdminStatsRepository implements AdminStatsRepositoryInterface
$lifecycleRestore7dCount = 0; $lifecycleRestore7dCount = 0;
$lifecycleTopReasonRows = []; $lifecycleTopReasonRows = [];
$lifecycleRecentRiskRows = []; $lifecycleRecentRiskRows = [];
$lifecycleAll7dHref = 'admin/user-lifecycle-audit'; $lifecycleAll7dHref = 'admin/settings/user-lifecycle';
$lifecycleFailed7dHref = 'admin/user-lifecycle-audit'; $lifecycleFailed7dHref = 'admin/settings/user-lifecycle';
$lifecycleRestore7dHref = 'admin/user-lifecycle-audit'; $lifecycleRestore7dHref = 'admin/settings/user-lifecycle';
if ($userLifecycleAuditStatsAvailable) { if ($userLifecycleAuditStatsAvailable) {
$lifecycleRuns7dCount = (int) (DB::selectValue( $lifecycleRuns7dCount = (int) (DB::selectValue(
@@ -920,16 +920,16 @@ class AdminStatsRepository implements AdminStatsRepositoryInterface
$lifecycleActionRestore $lifecycleActionRestore
) ?? 0); ) ?? 0);
$lifecycleAll7dHref = 'admin/user-lifecycle-audit?' . http_build_query([ $lifecycleAll7dHref = 'admin/settings/user-lifecycle?' . http_build_query([
'created_from' => $lifecycleCreatedFrom, 'created_from' => $lifecycleCreatedFrom,
'created_to' => $lifecycleCreatedTo, 'created_to' => $lifecycleCreatedTo,
]); ]);
$lifecycleFailed7dHref = 'admin/user-lifecycle-audit?' . http_build_query([ $lifecycleFailed7dHref = 'admin/settings/user-lifecycle?' . http_build_query([
'statuses' => $lifecycleStatusFailed . ',' . $lifecycleStatusSkipped, 'statuses' => $lifecycleStatusFailed . ',' . $lifecycleStatusSkipped,
'created_from' => $lifecycleCreatedFrom, 'created_from' => $lifecycleCreatedFrom,
'created_to' => $lifecycleCreatedTo, 'created_to' => $lifecycleCreatedTo,
]); ]);
$lifecycleRestore7dHref = 'admin/user-lifecycle-audit?' . http_build_query([ $lifecycleRestore7dHref = 'admin/settings/user-lifecycle?' . http_build_query([
'actions' => $lifecycleActionRestore, 'actions' => $lifecycleActionRestore,
'created_from' => $lifecycleCreatedFrom, 'created_from' => $lifecycleCreatedFrom,
'created_to' => $lifecycleCreatedTo, 'created_to' => $lifecycleCreatedTo,

View File

@@ -86,7 +86,7 @@ Wichtige Regeln:
- Delete ist fail-closed: ohne erfolgreich gespeicherten Snapshot wird nicht gelöscht. - Delete ist fail-closed: ohne erfolgreich gespeicherten Snapshot wird nicht gelöscht.
- Snapshot wird verschlüsselt in `snapshot_enc` gespeichert. - Snapshot wird verschlüsselt in `snapshot_enc` gespeichert.
- Snapshot enthält nur whitelisted Profilfelder (keine Secrets/Tokens). - 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) ## Wiederherstellung (ohne Assignments)

View File

@@ -31,12 +31,11 @@ return [
['path' => 'admin/import-audit/view', 'target' => 'audit/import-audit/view'], ['path' => 'admin/import-audit/view', 'target' => 'audit/import-audit/view'],
['path' => 'admin/import-audit/data', 'target' => 'audit/import-audit/data'], ['path' => 'admin/import-audit/data', 'target' => 'audit/import-audit/data'],
['path' => 'admin/import-audit/purge', 'target' => 'audit/import-audit/purge'], ['path' => 'admin/import-audit/purge', 'target' => 'audit/import-audit/purge'],
// User lifecycle audit pages // User lifecycle audit endpoints embedded in admin/settings/user-lifecycle
['path' => 'admin/user-lifecycle-audit', 'target' => 'audit/user-lifecycle-audit/index'], ['path' => 'admin/settings/user-lifecycle/audit-data', 'target' => 'audit/settings-user-lifecycle/audit-data'],
['path' => 'admin/user-lifecycle-audit/view', 'target' => 'audit/user-lifecycle-audit/view'], ['path' => 'admin/settings/user-lifecycle/audit-fragment', 'target' => 'audit/settings-user-lifecycle/audit-fragment'],
['path' => 'admin/user-lifecycle-audit/data', 'target' => 'audit/user-lifecycle-audit/data'], ['path' => 'admin/settings/user-lifecycle/audit-restore', 'target' => 'audit/settings-user-lifecycle/audit-restore'],
['path' => 'admin/user-lifecycle-audit/purge', 'target' => 'audit/user-lifecycle-audit/purge'], ['path' => 'admin/settings/user-lifecycle/audit-purge', 'target' => 'audit/settings-user-lifecycle/audit-purge'],
['path' => 'admin/user-lifecycle-audit/restore', 'target' => 'audit/user-lifecycle-audit/restore'],
// Frontend telemetry // Frontend telemetry
['path' => 'admin/frontend-telemetry/ingest', 'target' => 'audit/frontend-telemetry/ingest'], ['path' => 'admin/frontend-telemetry/ingest', 'target' => 'audit/frontend-telemetry/ingest'],
], ],
@@ -72,13 +71,13 @@ return [
'permission' => 'audit.imports.view', 'permission' => 'audit.imports.view',
'order' => 220, 'order' => 220,
], ],
],
'settings.user_lifecycle.panel' => [
[ [
'key' => 'audit-user-lifecycle-audit', 'key' => 'audit-settings-user-lifecycle-panel',
'group' => 'admin-logs', 'template' => 'templates/settings-user-lifecycle-panel.phtml',
'label' => 'nav.audit.user_lifecycle',
'path' => 'audit/user-lifecycle-audit',
'permission' => 'audit.user_lifecycle.view', 'permission' => 'audit.user_lifecycle.view',
'order' => 230, 'order' => 100,
], ],
], ],
], ],

View File

@@ -4,31 +4,29 @@ use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Module\Audit\AuditAuthorizationPolicy; use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
use MintyPHP\Module\Audit\Domain\UserLifecycleAction; use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
use MintyPHP\Module\Audit\Service\UserLifecycleAuditService; use MintyPHP\Module\Audit\Service\UserLifecycleAuditService;
use MintyPHP\Module\Audit\Support\AuditPageHelper;
use MintyPHP\Service\Access\UiAccessService; use MintyPHP\Service\Access\UiAccessService;
use MintyPHP\Support\Guard;
$session = app(SessionStoreInterface::class)->all(); $session = app(SessionStoreInterface::class)->all();
Guard::requireLogin();
Guard::requireAbilityOrForbidden(AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_VIEW);
$auditLog = AuditPageHelper::findOrRedirect( $auditId = (int) ($id ?? 0);
AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_VIEW, $auditLog = null;
UserLifecycleAuditService::class, if ($auditId > 0) {
'audit/user-lifecycle-audit', $auditLog = app(UserLifecycleAuditService::class)->find($auditId);
'Lifecycle audit entry not found', }
'user_lifecycle_audit_not_found', if (!is_array($auditLog)) {
'View user lifecycle audit entry', http_response_code(404);
$id ?? null return;
); }
$breadcrumbs = [
['label' => t('Home'), 'path' => 'admin'],
['label' => t('User lifecycle logs'), 'path' => 'audit/user-lifecycle-audit'],
['label' => t('View')],
];
$auditService = app(UserLifecycleAuditService::class); $auditService = app(UserLifecycleAuditService::class);
$snapshot = null; $snapshot = null;
if ((string) ($auditLog['action'] ?? '') === UserLifecycleAction::Delete->value) { if ((string) ($auditLog['action'] ?? '') === UserLifecycleAction::Delete->value) {
$snapshot = $auditService->decryptSnapshot($auditLog); $snapshot = $auditService->decryptSnapshot($auditLog);
} }
$viewAuth = is_array($viewAuth ?? null) ? $viewAuth : [];
$viewAuth['page'] = app(UiAccessService::class)->pageCapabilities( $viewAuth['page'] = app(UiAccessService::class)->pageCapabilities(
(int) ($session['user']['id'] ?? 0), (int) ($session['user']['id'] ?? 0),
['can_restore' => AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_RESTORE] ['can_restore' => AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_RESTORE]

View File

@@ -0,0 +1,177 @@
<?php
use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType;
use MintyPHP\Session;
/**
* @var array<string, mixed> $auditLog
* @var ?array<string, mixed> $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']);
?>
<article class="app-details-content">
<header>
<h3><?php e(t('View user lifecycle audit entry')); ?></h3>
<p><small><?php e(t('ID')); ?>: <?php e((string) ($auditLog['id'] ?? '-')); ?></small></p>
</header>
<hr>
<details open>
<summary><?php e(t('Lifecycle event')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Created')); ?></small>
<p><?php e(dt((string) ($auditLog['created_at'] ?? '')) ?: '-'); ?></p>
</div>
<div>
<small><?php e(t('Status')); ?></small>
<p><span class="badge" data-variant="<?php e($statusVariant); ?>"><?php e(t($status->labelToken())); ?></span></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Action')); ?></small>
<p><?php e(t($action->labelToken())); ?></p>
</div>
<div>
<small><?php e(t('Trigger')); ?></small>
<p><?php e(t($triggerType->labelToken())); ?></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Reason code')); ?></small>
<p><?php e((string) ($auditLog['reason_code'] ?? '') !== '' ? (string) $auditLog['reason_code'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Run UUID')); ?></small>
<p><?php e((string) ($auditLog['run_uuid'] ?? '') !== '' ? (string) $auditLog['run_uuid'] : '-'); ?></p>
</div>
</div>
</details>
<hr>
<details open>
<summary><?php e(t('Target')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Target UUID')); ?></small>
<p><?php e((string) ($auditLog['target_user_uuid'] ?? '') !== '' ? (string) $auditLog['target_user_uuid'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Target email')); ?></small>
<p><?php e((string) ($auditLog['target_user_email'] ?? '') !== '' ? (string) $auditLog['target_user_email'] : '-'); ?></p>
</div>
</div>
</details>
<hr>
<details>
<summary><?php e(t('Policy')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Deactivate users after inactivity (days)')); ?></small>
<p><?php e((string) ((int) ($auditLog['policy_deactivate_days'] ?? 0))); ?></p>
</div>
<div>
<small><?php e(t('Delete inactive users after (days)')); ?></small>
<p><?php e((string) ((int) ($auditLog['policy_delete_days'] ?? 0))); ?></p>
</div>
</div>
</details>
<?php if ($snapshot !== null): ?>
<hr>
<details>
<summary><?php e(t('Snapshot summary')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('First name')); ?></small>
<p><?php e((string) ($snapshot['first_name'] ?? '') !== '' ? (string) $snapshot['first_name'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Last name')); ?></small>
<p><?php e((string) ($snapshot['last_name'] ?? '') !== '' ? (string) $snapshot['last_name'] : '-'); ?></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Email')); ?></small>
<p><?php e((string) ($snapshot['email'] ?? '') !== '' ? (string) $snapshot['email'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Locale')); ?></small>
<p><?php e((string) ($snapshot['locale'] ?? '') !== '' ? (string) $snapshot['locale'] : '-'); ?></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Theme')); ?></small>
<p><?php e((string) ($snapshot['theme'] ?? '') !== '' ? (string) $snapshot['theme'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Snapshot version')); ?></small>
<p><?php e((string) ((int) ($auditLog['snapshot_version'] ?? 1))); ?></p>
</div>
</div>
</details>
<?php endif; ?>
<hr>
<details open>
<summary><?php e(t('Restore status')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Restored at')); ?></small>
<p><?php e(dt((string) ($auditLog['restored_at'] ?? '')) ?: '-'); ?></p>
</div>
<div>
<small><?php e(t('Restored by')); ?></small>
<p><?php e((string) ($auditLog['restored_by_user_display_name'] ?? '') !== '' ? (string) $auditLog['restored_by_user_display_name'] : '-'); ?></p>
</div>
</div>
<div>
<small><?php e(t('Restored user')); ?></small>
<p>
<?php if (!empty($auditLog['restored_user_uuid'])): ?>
<a href="admin/users/edit/<?php e((string) $auditLog['restored_user_uuid']); ?>">
<?php e((string) ($auditLog['restored_user_display_name'] ?? $auditLog['restored_user_uuid'])); ?>
</a>
<?php else: ?>
-
<?php endif; ?>
</p>
</div>
<?php if ($isRestorable): ?>
<hr>
<form method="post" action="<?php e(endpointUrl('admin/settings/user-lifecycle/audit-restore')); ?>/<?php e((string) ($auditLog['id'] ?? 0)); ?>">
<?php Session::getCsrfInput(); ?>
<button type="submit" class="warning" data-confirm-message="<?php e(t('Restore this user from lifecycle snapshot?')); ?>">
<?php e(t('Restore user')); ?>
</button>
</form>
<?php endif; ?>
</details>
</article>

View File

@@ -8,14 +8,14 @@ use MintyPHP\Support\Flash;
AuditPageHelper::requirePurgePost( AuditPageHelper::requirePurgePost(
SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE, SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE,
'audit/user-lifecycle-audit', 'admin/settings/user-lifecycle',
'user_lifecycle_audit' 'user_lifecycle_audit'
); );
$deleted = app(UserLifecycleAuditService::class)->purgeExpired(); $deleted = app(UserLifecycleAuditService::class)->purgeExpired();
Flash::success( Flash::success(
sprintf(t('%d user lifecycle audit entries purged'), $deleted), sprintf(t('%d user lifecycle audit entries purged'), $deleted),
'audit/user-lifecycle-audit', 'admin/settings/user-lifecycle',
'user_lifecycle_audit_purged' 'user_lifecycle_audit_purged'
); );
Router::redirect('audit/user-lifecycle-audit'); Router::redirect('admin/settings/user-lifecycle');

View File

@@ -14,12 +14,12 @@ $auditId = (int) ($id ?? 0);
$errorBag = formErrors(); $errorBag = formErrors();
if (strtoupper((string) (requestInput()->method())) !== 'POST') { if (strtoupper((string) (requestInput()->method())) !== 'POST') {
Router::redirect('audit/user-lifecycle-audit'); Router::redirect('admin/settings/user-lifecycle');
} }
if (!Session::checkCsrfToken()) { if (!Session::checkCsrfToken()) {
$errorBag->addGlobal(t('Form expired, please try again')); $errorBag->addGlobal(t('Form expired, please try again'));
flashFormErrors($errorBag, 'audit/user-lifecycle-audit', 'user_lifecycle_restore'); flashFormErrors($errorBag, 'admin/settings/user-lifecycle', 'user_lifecycle_restore');
Router::redirect('audit/user-lifecycle-audit'); Router::redirect('admin/settings/user-lifecycle');
} }
$result = app(\MintyPHP\Service\User\UserLifecycleRestoreService::class)->restoreFromLifecycleAudit( $result = app(\MintyPHP\Service\User\UserLifecycleRestoreService::class)->restoreFromLifecycleAudit(
@@ -40,9 +40,9 @@ if (!($result['ok'] ?? false)) {
} else { } else {
$errorBag->addGlobal(t('User restore failed')); $errorBag->addGlobal(t('User restore failed'));
} }
flashFormErrors($errorBag, "audit/user-lifecycle-audit/view/$auditId", 'user_lifecycle_restore'); flashFormErrors($errorBag, 'admin/settings/user-lifecycle', 'user_lifecycle_restore');
Router::redirect("audit/user-lifecycle-audit/view/$auditId"); Router::redirect('admin/settings/user-lifecycle');
} }
Flash::success(t('User restored'), "audit/user-lifecycle-audit/view/$auditId", 'user_restored'); Flash::success(t('User restored'), 'admin/settings/user-lifecycle', 'user_restored');
Router::redirect("audit/user-lifecycle-audit/view/$auditId"); Router::redirect('admin/settings/user-lifecycle');

View File

@@ -1,66 +0,0 @@
<?php
$pageAuth = is_array($viewAuth['page'] ?? null) ? $viewAuth['page'] : [];
$canPurge = (bool) ($pageAuth['can_purge'] ?? false);
$toolbarFilterSchema = is_array($toolbarFilterSchema ?? null) ? $toolbarFilterSchema : [];
$searchToolbarFilterSchema = is_array($searchToolbarFilterSchema ?? null) ? $searchToolbarFilterSchema : [];
$drawerToolbarFilterSchema = is_array($drawerToolbarFilterSchema ?? null) ? $drawerToolbarFilterSchema : [];
$toolbarFilterState = is_array($toolbarFilterState ?? null) ? $toolbarFilterState : [];
$toolbarOptionSets = is_array($toolbarOptionSets ?? null) ? $toolbarOptionSets : [];
$filterChipMeta = is_array($filterChipMeta ?? null) ? $filterChipMeta : [];
$clientFilterSchema = is_array($clientFilterSchema ?? null) ? $clientFilterSchema : [];
$searchConfig = is_array($searchConfig ?? null) ? $searchConfig : null;
?>
<?php
$listTitle = t('User lifecycle logs');
ob_start();
?>
<?php
$listPurgeEnabled = $canPurge;
$listPurgeFormId = 'user-lifecycle-audit-purge-form';
$listPurgeAction = 'audit/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');
?>
<?php
$listTitleActionsHtml = ob_get_clean();
require templatePath('partials/app-list-titlebar.phtml');
?>
<?php
$filterUiNamespace = 'user-lifecycle';
$filterToolbarId = 'user-lifecycle-audit-toolbar';
$filterDrawerToolbarId = 'user-lifecycle-drawer-toolbar';
$filterDrawerTitleId = 'user-lifecycle-filter-drawer-title';
require templatePath('partials/app-list-filters.phtml');
?>
<div class="app-list-table">
<div id="user-lifecycle-audit-grid"></div>
</div>
<?php
$gridLang = json_decode(appBufferValue('grid_lang'), true);
if (!is_array($gridLang)) {
$gridLang = [];
}
$pageConfig = [
'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'),
],
];
?>
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
<script type="application/json" id="page-config-admin-user-lifecycle-audit-index"><?php gridJsonForJs($pageConfig); ?></script>
<script type="module" src="<?php e(assetVersion('modules/audit/js/pages/admin-user-lifecycle-audit-index.js')); ?>"></script>

View File

@@ -1,206 +0,0 @@
<?php
use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType;
/**
* @var array $auditLog
* @var ?array $snapshot
*/
use MintyPHP\Session;
$auditLog = $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']);
?>
<div class="app-details-container">
<section>
<?php
$titlebar = [
'title' => t('View user lifecycle audit entry'),
'backHref' => 'audit/user-lifecycle-audit',
'backTitle' => t('Back'),
];
require templatePath('partials/app-details-titlebar.phtml');
?>
<div class="app-details-content">
<details open>
<summary><?php e(t('Lifecycle event')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Created')); ?></small>
<p><?php e(dt((string) ($auditLog['created_at'] ?? '')) ?: '-'); ?></p>
</div>
<div>
<small><?php e(t('Status')); ?></small>
<p><span class="badge" data-variant="<?php e($statusVariant); ?>"><?php e(t($status->labelToken())); ?></span></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Action')); ?></small>
<p><?php e(t($action->labelToken())); ?></p>
</div>
<div>
<small><?php e(t('Trigger')); ?></small>
<p><?php e(t($triggerType->labelToken())); ?></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Reason code')); ?></small>
<p><?php e((string) ($auditLog['reason_code'] ?? '') !== '' ? (string) $auditLog['reason_code'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Run UUID')); ?></small>
<p><?php e((string) ($auditLog['run_uuid'] ?? '') !== '' ? (string) $auditLog['run_uuid'] : '-'); ?></p>
</div>
</div>
</details>
<hr>
<details open>
<summary><?php e(t('Target')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Target UUID')); ?></small>
<p><?php e((string) ($auditLog['target_user_uuid'] ?? '') !== '' ? (string) $auditLog['target_user_uuid'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Target email')); ?></small>
<p><?php e((string) ($auditLog['target_user_email'] ?? '') !== '' ? (string) $auditLog['target_user_email'] : '-'); ?></p>
</div>
</div>
</details>
<hr>
<details>
<summary><?php e(t('Policy')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Deactivate users after inactivity (days)')); ?></small>
<p><?php e((string) ((int) ($auditLog['policy_deactivate_days'] ?? 0))); ?></p>
</div>
<div>
<small><?php e(t('Delete inactive users after (days)')); ?></small>
<p><?php e((string) ((int) ($auditLog['policy_delete_days'] ?? 0))); ?></p>
</div>
</div>
</details>
<?php if ($snapshot !== null): ?>
<hr>
<details>
<summary><?php e(t('Snapshot summary')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('First name')); ?></small>
<p><?php e((string) ($snapshot['first_name'] ?? '') !== '' ? (string) $snapshot['first_name'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Last name')); ?></small>
<p><?php e((string) ($snapshot['last_name'] ?? '') !== '' ? (string) $snapshot['last_name'] : '-'); ?></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Email')); ?></small>
<p><?php e((string) ($snapshot['email'] ?? '') !== '' ? (string) $snapshot['email'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Locale')); ?></small>
<p><?php e((string) ($snapshot['locale'] ?? '') !== '' ? (string) $snapshot['locale'] : '-'); ?></p>
</div>
</div>
<div class="grid">
<div>
<small><?php e(t('Theme')); ?></small>
<p><?php e((string) ($snapshot['theme'] ?? '') !== '' ? (string) $snapshot['theme'] : '-'); ?></p>
</div>
<div>
<small><?php e(t('Snapshot version')); ?></small>
<p><?php e((string) ((int) ($auditLog['snapshot_version'] ?? 1))); ?></p>
</div>
</div>
</details>
<?php endif; ?>
<hr>
<details open>
<summary><?php e(t('Restore status')); ?></summary>
<hr>
<div class="grid">
<div>
<small><?php e(t('Restored at')); ?></small>
<p><?php e(dt((string) ($auditLog['restored_at'] ?? '')) ?: '-'); ?></p>
</div>
<div>
<small><?php e(t('Restored by')); ?></small>
<p><?php e((string) ($auditLog['restored_by_user_display_name'] ?? '') !== '' ? (string) $auditLog['restored_by_user_display_name'] : '-'); ?></p>
</div>
</div>
<div>
<small><?php e(t('Restored user')); ?></small>
<p>
<?php if (!empty($auditLog['restored_user_uuid'])): ?>
<a href="admin/users/edit/<?php e((string) $auditLog['restored_user_uuid']); ?>">
<?php e((string) ($auditLog['restored_user_display_name'] ?? $auditLog['restored_user_uuid'])); ?>
</a>
<?php else: ?>
-
<?php endif; ?>
</p>
</div>
<?php if ($isRestorable): ?>
<hr>
<form method="post" action="audit/user-lifecycle-audit/restore/<?php e((string) ($auditLog['id'] ?? 0)); ?>">
<?php Session::getCsrfInput(); ?>
<button type="submit" class="warning" data-confirm-message="<?php e(t('Restore this user from lifecycle snapshot?')); ?>">
<?php e(t('Restore user')); ?>
</button>
</form>
<?php endif; ?>
</details>
</div>
</section>
<aside id="app-details-aside-section">
<div class="app-details-aside-section">
<hgroup>
<strong><?php e(t('User lifecycle logs')); ?></strong>
<p><small><?php e((string) ($auditLog['run_uuid'] ?? '') !== '' ? (string) $auditLog['run_uuid'] : '-'); ?></small></p>
</hgroup>
<hr>
<div>
<small><?php e(t('ID')); ?></small>
<p><span class="badge" data-variant="neutral"><?php e((string) ($auditLog['id'] ?? '-')); ?></span></p>
</div>
<div>
<small><?php e(t('Status')); ?></small>
<p><span class="badge" data-variant="<?php e($statusVariant); ?>"><?php e(t($status->labelToken())); ?></span></p>
</div>
<div>
<small><?php e(t('Action')); ?></small>
<p><?php e(t($action->labelToken())); ?></p>
</div>
</div>
</aside>
</div>

View File

@@ -1,32 +1,12 @@
<?php <?php
use MintyPHP\Buffer;
use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
use MintyPHP\Module\Audit\Domain\UserLifecycleAction; use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus; use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType; use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType;
use MintyPHP\Module\Audit\Service\UserLifecycleAuditService;
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
use MintyPHP\Service\Access\UiAccessService;
use MintyPHP\Support\Guard;
$session = app(SessionStoreInterface::class)->all(); $canPurge = (bool) ($canUpdateSettings ?? false);
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]
);
Buffer::set('title', t('User lifecycle logs')); $filterSchema = require __DIR__ . '/../pages/audit/settings-user-lifecycle/filter-schema.php';
$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';
$filterState = gridParseFilters(requestInput()->queryAll(), [ $filterState = gridParseFilters(requestInput()->queryAll(), [
...gridSchemaQuery($filterSchema), ...gridSchemaQuery($filterSchema),
'actions' => [ 'actions' => [
@@ -49,8 +29,6 @@ $filterState = gridParseFilters(requestInput()->queryAll(), [
], ],
'actor_user_ids' => ['type' => 'csv_ids', 'max' => 200, 'return' => 'array'], 'actor_user_ids' => ['type' => 'csv_ids', 'max' => 200, 'return' => 'array'],
]); ]);
$filterOptions = app(UserLifecycleAuditService::class)->filterOptions(200);
$activeActions = is_array($filterState['actions'] ?? null) ? $filterState['actions'] : []; $activeActions = is_array($filterState['actions'] ?? null) ? $filterState['actions'] : [];
$activeStatuses = is_array($filterState['statuses'] ?? null) ? $filterState['statuses'] : []; $activeStatuses = is_array($filterState['statuses'] ?? null) ? $filterState['statuses'] : [];
$activeTriggerTypes = is_array($filterState['trigger_types'] ?? null) ? $filterState['trigger_types'] : []; $activeTriggerTypes = is_array($filterState['trigger_types'] ?? null) ? $filterState['trigger_types'] : [];
@@ -76,16 +54,6 @@ foreach (UserLifecycleAction::values() as $action) {
'description' => $actionLabelMap[$action] ?? $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); $lifecycleActionItems = array_values($lifecycleActionItems);
$lifecycleStatusItems = []; $lifecycleStatusItems = [];
@@ -95,16 +63,6 @@ foreach (UserLifecycleStatus::values() as $status) {
'description' => $statusLabelMap[$status] ?? $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); $lifecycleStatusItems = array_values($lifecycleStatusItems);
$lifecycleTriggerItems = []; $lifecycleTriggerItems = [];
@@ -114,48 +72,14 @@ foreach (UserLifecycleTriggerType::values() as $triggerType) {
'description' => $triggerLabelMap[$triggerType] ?? $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); $lifecycleTriggerItems = array_values($lifecycleTriggerItems);
$lifecycleActorItems = []; $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) { foreach ($activeActorUserIds as $actorId) {
if (!isset($lifecycleActorItems[$actorId])) { if (!isset($lifecycleActorItems[$actorId])) {
$lifecycleActorItems[$actorId] = [ $lifecycleActorItems[$actorId] = [
'id' => $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_state_overrides' => $toolbarFilterState,
'toolbar_option_sets' => $toolbarOptionSets, 'toolbar_option_sets' => $toolbarOptionSets,
]); ]);
$toolbarFilterSchema = $listFilterContext['toolbarFilterSchema']; $toolbarFilterSchema = is_array($listFilterContext['toolbarFilterSchema'] ?? null) ? $listFilterContext['toolbarFilterSchema'] : [];
$toolbarFilterState = $listFilterContext['toolbarFilterState']; $searchToolbarFilterSchema = is_array($listFilterContext['searchToolbarFilterSchema'] ?? null) ? $listFilterContext['searchToolbarFilterSchema'] : [];
$searchToolbarFilterSchema = $listFilterContext['searchToolbarFilterSchema']; $drawerToolbarFilterSchema = is_array($listFilterContext['drawerToolbarFilterSchema'] ?? null) ? $listFilterContext['drawerToolbarFilterSchema'] : [];
$drawerToolbarFilterSchema = $listFilterContext['drawerToolbarFilterSchema']; $toolbarFilterState = is_array($listFilterContext['toolbarFilterState'] ?? null) ? $listFilterContext['toolbarFilterState'] : [];
$toolbarOptionSets = $listFilterContext['toolbarOptionSets']; $toolbarOptionSets = is_array($listFilterContext['toolbarOptionSets'] ?? null) ? $listFilterContext['toolbarOptionSets'] : [];
$clientFilterSchema = is_array($listFilterContext['clientFilterSchema'] ?? null) ? $listFilterContext['clientFilterSchema'] : [];
$searchConfig = $listFilterContext['searchConfig'] ?? null;
$filterChipMeta = [ $filterChipMeta = [
'search' => [ 'search' => [
'label' => t('Search'), 'label' => t('Search'),
@@ -219,5 +146,50 @@ $filterChipMeta = [
'to_param' => 'created_to', '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'),
],
];
?>
<section class="app-settings-user-lifecycle-audit" data-app-component="settings-user-lifecycle-audit">
<?php require templatePath('partials/app-list-titlebar.phtml'); ?>
<?php require templatePath('partials/app-list-filters.phtml'); ?>
<div class="app-list-table">
<div id="settings-user-lifecycle-audit-grid"></div>
</div>
<script src="<?php e(assetVersion('vendor/gridjs/gridjs.umd.js')); ?>"></script>
<script type="application/json" id="page-config-settings-user-lifecycle-audit-panel"><?php gridJsonForJs($pageConfig); ?></script>
<script type="module" src="<?php e(assetVersion('modules/audit/js/pages/settings-user-lifecycle-audit-panel.js')); ?>"></script>
</section>

View File

@@ -1,12 +1,14 @@
import { createListPageModule } from '/js/core/app-list-page-module.js'; import { createListPageModule } from '/js/core/app-list-page-module.js';
import { initDetailDrawer } from '/js/components/app-detail-drawer.js';
createListPageModule({ createListPageModule({
configId: 'admin-user-lifecycle-audit-index', configId: 'settings-user-lifecycle-audit-panel',
moduleId: 'admin-user-lifecycle-audit-index', moduleId: 'settings-user-lifecycle-audit-panel',
missingGridMessage: 'User lifecycle audit grid init failed: Grid.js missing', missingGridMessage: 'User lifecycle audit grid init failed: Grid.js missing',
initErrorMessage: 'User lifecycle audit grid init failed', initErrorMessage: 'User lifecycle audit grid init failed',
setup: ({ config, gridjs, appBase, initListPage }) => { setup: ({ config, gridjs, appBase, initListPage }) => {
const labels = config.labels ?? {}; const labels = config.labels ?? {};
const dataUrl = String(config.dataUrl || 'audit/settings-user-lifecycle/audit-data');
const gridSearch = config.gridSearch ?? null; const gridSearch = config.gridSearch ?? null;
const filterSchema = config.filterSchema ?? []; const filterSchema = config.filterSchema ?? [];
const filterChipMeta = config.filterChipMeta ?? []; const filterChipMeta = config.filterChipMeta ?? [];
@@ -14,8 +16,8 @@ createListPageModule({
const { gridConfig } = initListPage({ const { gridConfig } = initListPage({
grid: { grid: {
gridjs, gridjs,
container: '#user-lifecycle-audit-grid', container: '#settings-user-lifecycle-audit-grid',
dataUrl: 'audit/user-lifecycle-audit/data', dataUrl,
appBase, appBase,
columns: [ columns: [
{ name: labels.created || 'Created', sort: true }, { name: labels.created || 'Created', sort: true },
@@ -29,7 +31,18 @@ createListPageModule({
return gridjs.html(`<span class="badge" data-variant="${cell.variant || 'neutral'}">${cell.label || '-'}</span>`); return gridjs.html(`<span class="badge" data-variant="${cell.variant || 'neutral'}">${cell.label || '-'}</span>`);
}, },
}, },
{ 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(`<a href="#" class="app-grid-link-cell" data-lifecycle-drawer-trigger>${label}</a>`);
},
},
{ name: labels.trigger || 'Trigger', sort: true }, { name: labels.trigger || 'Trigger', sort: true },
{ name: labels.targetEmail || 'Target email', sort: false }, { name: labels.targetEmail || 'Target email', sort: false },
{ name: labels.targetUuid || 'Target UUID', sort: false }, { name: labels.targetUuid || 'Target UUID', sort: false },
@@ -51,14 +64,17 @@ createListPageModule({
row.restored_at || '-', row.restored_at || '-',
row.id, row.id,
]), ]),
rowInteraction: { linkColumn: 2 }, rowInteraction: { linkColumn: false },
search: gridSearch, search: gridSearch,
filterSchema, filterSchema,
urlSync: true, urlSync: true,
rowDataset: (row) => ({
'audit-id': row?.cells?.[8]?.data ? String(row.cells[8].data) : '',
}),
rowDblClick: { rowDblClick: {
getUrl: (rowData) => { getUrl: (rowData) => {
const id = rowData?.cells?.[8]?.data; 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; return gridConfig;
}, },
}); });

View File

@@ -17,6 +17,12 @@ $userInactivityDeleteDaysDesc = $settings['user_inactivity_delete_days']['descri
$canUpdateSettings = (bool) ($canUpdateSettings ?? false); $canUpdateSettings = (bool) ($canUpdateSettings ?? false);
$isReadOnly = !$canUpdateSettings; $isReadOnly = !$canUpdateSettings;
$readonlyAttr = $isReadOnly ? 'readonly' : ''; $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']
: [];
?> ?>
<div class="app-details-container"> <div class="app-details-container">
@@ -86,6 +92,23 @@ $readonlyAttr = $isReadOnly ? 'readonly' : '';
<?php Session::getCsrfInput(); ?> <?php Session::getCsrfInput(); ?>
</form> </form>
<?php foreach ($moduleLifecyclePanelSlots as $slot): ?>
<?php
if (!is_array($slot)) {
continue;
}
$slotPermission = trim((string) ($slot['permission'] ?? ''));
if ($slotPermission !== '' && empty($layoutAuth[$slotPermission])) {
continue;
}
$slotTemplate = trim((string) ($slot['template'] ?? ''));
if ($slotTemplate === '' || !is_file($slotTemplate)) {
continue;
}
include $slotTemplate;
?>
<?php endforeach; ?>
</section> </section>
<aside id="app-details-aside-section"> <aside id="app-details-aside-section">
<div class="app-details-aside-section"></div> <div class="app-details-aside-section"></div>

View File

@@ -603,7 +603,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
<tr> <tr>
<td> <td>
<?php if ($lifecycleId > 0): ?> <?php if ($lifecycleId > 0): ?>
<a href="admin/user-lifecycle-audit/view/<?php e((string) $lifecycleId); ?>"><?php e((string) ($row['action'] ?? '')); ?></a> <a href="admin/settings/user-lifecycle#lifecycle-audit/<?php e((string) $lifecycleId); ?>"><?php e((string) ($row['action'] ?? '')); ?></a>
<?php else: ?> <?php else: ?>
<?php e((string) ($row['action'] ?? '')); ?> <?php e((string) ($row['action'] ?? '')); ?>
<?php endif; ?> <?php endif; ?>

View File

@@ -74,8 +74,7 @@ class AuthzUiActionContractTest extends TestCase
'modules/audit/pages/audit/api-audit/index().php', 'modules/audit/pages/audit/api-audit/index().php',
'modules/audit/pages/audit/system-audit/index().php', 'modules/audit/pages/audit/system-audit/index().php',
'modules/audit/pages/audit/import-audit/index().php', 'modules/audit/pages/audit/import-audit/index().php',
'modules/audit/pages/audit/user-lifecycle-audit/index().php', 'modules/audit/pages/audit/settings-user-lifecycle/audit-fragment($id).php',
'modules/audit/pages/audit/user-lifecycle-audit/view($id).php',
]; ];
foreach ($actions as $action) { foreach ($actions as $action) {

View File

@@ -55,16 +55,6 @@ trait AuthzUiContractSupport
'modules/audit/pages/audit/system-audit/index().php', 'modules/audit/pages/audit/system-audit/index().php',
null, null,
], ],
'user lifecycle index' => [
'modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml',
'modules/audit/pages/audit/user-lifecycle-audit/index().php',
null,
],
'user lifecycle view' => [
'modules/audit/pages/audit/user-lifecycle-audit/view(default).phtml',
'modules/audit/pages/audit/user-lifecycle-audit/view($id).php',
null,
],
]; ];
} }

View File

@@ -84,8 +84,8 @@ class AuthzUiTemplateContractTest extends TestCase
'modules/audit/pages/audit/import-audit/index(default).phtml', 'modules/audit/pages/audit/import-audit/index(default).phtml',
'pages/admin/scheduled-jobs/index(default).phtml', 'pages/admin/scheduled-jobs/index(default).phtml',
'pages/admin/stats/index(default).phtml', 'pages/admin/stats/index(default).phtml',
'modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml', 'modules/audit/templates/settings-user-lifecycle-panel.phtml',
'modules/audit/pages/audit/user-lifecycle-audit/view(default).phtml', 'modules/audit/pages/audit/settings-user-lifecycle/audit-fragment(none).phtml',
'pages/admin/users/index(default).phtml', 'pages/admin/users/index(default).phtml',
'pages/admin/users/create(default).phtml', 'pages/admin/users/create(default).phtml',
'pages/admin/tenants/edit(default).phtml', 'pages/admin/tenants/edit(default).phtml',

View File

@@ -56,7 +56,7 @@ trait ListContractFiles
'pages/admin/scheduled-jobs/runs-data($id).php', 'pages/admin/scheduled-jobs/runs-data($id).php',
'modules/audit/pages/audit/api-audit/data().php', 'modules/audit/pages/audit/api-audit/data().php',
'modules/audit/pages/audit/import-audit/data().php', 'modules/audit/pages/audit/import-audit/data().php',
'modules/audit/pages/audit/user-lifecycle-audit/data().php', 'modules/audit/pages/audit/settings-user-lifecycle/audit-data().php',
'modules/audit/pages/audit/system-audit/data().php', 'modules/audit/pages/audit/system-audit/data().php',
]; ];
} }
@@ -80,7 +80,7 @@ trait ListContractFiles
'pages/admin/scheduled-jobs/runs-data($id).php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/runs-filter-schema.php'", 'pages/admin/scheduled-jobs/runs-data($id).php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/runs-filter-schema.php'",
'modules/audit/pages/audit/api-audit/data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'", 'modules/audit/pages/audit/api-audit/data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'",
'modules/audit/pages/audit/import-audit/data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'", 'modules/audit/pages/audit/import-audit/data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'",
'modules/audit/pages/audit/user-lifecycle-audit/data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'", 'modules/audit/pages/audit/settings-user-lifecycle/audit-data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'",
'modules/audit/pages/audit/system-audit/data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'", 'modules/audit/pages/audit/system-audit/data().php' => "gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php'",
]; ];
} }
@@ -103,7 +103,7 @@ trait ListContractFiles
'pages/admin/scheduled-jobs/index(default).phtml', 'pages/admin/scheduled-jobs/index(default).phtml',
'modules/audit/pages/audit/api-audit/index(default).phtml', 'modules/audit/pages/audit/api-audit/index(default).phtml',
'modules/audit/pages/audit/import-audit/index(default).phtml', 'modules/audit/pages/audit/import-audit/index(default).phtml',
'modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml', 'modules/audit/templates/settings-user-lifecycle-panel.phtml',
'modules/audit/pages/audit/system-audit/index(default).phtml', 'modules/audit/pages/audit/system-audit/index(default).phtml',
]; ];
} }
@@ -126,7 +126,7 @@ trait ListContractFiles
'pages/admin/scheduled-jobs/index().php', 'pages/admin/scheduled-jobs/index().php',
'modules/audit/pages/audit/api-audit/index().php', 'modules/audit/pages/audit/api-audit/index().php',
'modules/audit/pages/audit/import-audit/index().php', 'modules/audit/pages/audit/import-audit/index().php',
'modules/audit/pages/audit/user-lifecycle-audit/index().php', 'modules/audit/templates/settings-user-lifecycle-panel.phtml',
'modules/audit/pages/audit/system-audit/index().php', 'modules/audit/pages/audit/system-audit/index().php',
]; ];
} }
@@ -148,7 +148,7 @@ trait ListContractFiles
'pages/admin/scheduled-jobs/index(default).phtml', 'pages/admin/scheduled-jobs/index(default).phtml',
'modules/audit/pages/audit/api-audit/index(default).phtml', 'modules/audit/pages/audit/api-audit/index(default).phtml',
'modules/audit/pages/audit/import-audit/index(default).phtml', 'modules/audit/pages/audit/import-audit/index(default).phtml',
'modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml', 'modules/audit/templates/settings-user-lifecycle-panel.phtml',
'modules/audit/pages/audit/system-audit/index(default).phtml', 'modules/audit/pages/audit/system-audit/index(default).phtml',
]; ];
} }

View File

@@ -19,7 +19,7 @@ final class ListTitlebarContractFiles
'pages/admin/scheduled-jobs/index(default).phtml', 'pages/admin/scheduled-jobs/index(default).phtml',
'modules/audit/pages/audit/api-audit/index(default).phtml', 'modules/audit/pages/audit/api-audit/index(default).phtml',
'modules/audit/pages/audit/import-audit/index(default).phtml', 'modules/audit/pages/audit/import-audit/index(default).phtml',
'modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml', 'modules/audit/templates/settings-user-lifecycle-panel.phtml',
'modules/audit/pages/audit/system-audit/index(default).phtml', 'modules/audit/pages/audit/system-audit/index(default).phtml',
'modules/addressbook/pages/address-book/index(default).phtml', 'modules/addressbook/pages/address-book/index(default).phtml',
'pages/search/index(default).phtml', 'pages/search/index(default).phtml',

View File

@@ -28,7 +28,7 @@ class ListUiSharedPartialsContractTest extends TestCase
'pages/admin/scheduled-jobs/index(default).phtml', 'pages/admin/scheduled-jobs/index(default).phtml',
'modules/audit/pages/audit/api-audit/index(default).phtml', 'modules/audit/pages/audit/api-audit/index(default).phtml',
'modules/audit/pages/audit/import-audit/index(default).phtml', 'modules/audit/pages/audit/import-audit/index(default).phtml',
'modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml', 'modules/audit/templates/settings-user-lifecycle-panel.phtml',
'modules/audit/pages/audit/system-audit/index(default).phtml', 'modules/audit/pages/audit/system-audit/index(default).phtml',
]; ];
} }

View File

@@ -35,7 +35,7 @@ final class StatusTaxonomyContractFiles
'SystemAuditOutcome::values()', 'SystemAuditOutcome::values()',
'SystemAuditChannel::values()', 'SystemAuditChannel::values()',
], ],
'modules/audit/pages/audit/user-lifecycle-audit/filter-schema.php' => [ 'modules/audit/pages/audit/settings-user-lifecycle/filter-schema.php' => [
'gridEnumSanitizer(UserLifecycleAction::class)', 'gridEnumSanitizer(UserLifecycleAction::class)',
'gridEnumSanitizer(UserLifecycleStatus::class)', 'gridEnumSanitizer(UserLifecycleStatus::class)',
'gridEnumSanitizer(UserLifecycleTriggerType::class)', 'gridEnumSanitizer(UserLifecycleTriggerType::class)',
@@ -65,7 +65,7 @@ final class StatusTaxonomyContractFiles
// System audit: data + export endpoints delegate badge/label resolution // System audit: data + export endpoints delegate badge/label resolution
// to SystemAuditRowPresenter, which is the enforced source of truth. // to SystemAuditRowPresenter, which is the enforced source of truth.
'modules/audit/lib/Module/Audit/Service/SystemAuditRowPresenter.php', 'modules/audit/lib/Module/Audit/Service/SystemAuditRowPresenter.php',
'modules/audit/pages/audit/user-lifecycle-audit/data().php', 'modules/audit/pages/audit/settings-user-lifecycle/audit-data().php',
'modules/audit/pages/audit/import-audit/data().php', 'modules/audit/pages/audit/import-audit/data().php',
'pages/admin/scheduled-jobs/data().php', 'pages/admin/scheduled-jobs/data().php',
'pages/admin/scheduled-jobs/runs-data($id).php', 'pages/admin/scheduled-jobs/runs-data($id).php',
@@ -98,14 +98,14 @@ final class StatusTaxonomyContractFiles
'core/Repository/Tenant/TenantRepository.php', 'core/Repository/Tenant/TenantRepository.php',
'core/Repository/Stats/AdminStatsRepository.php', 'core/Repository/Stats/AdminStatsRepository.php',
'modules/audit/pages/audit/system-audit/data().php', 'modules/audit/pages/audit/system-audit/data().php',
'modules/audit/pages/audit/user-lifecycle-audit/data().php', 'modules/audit/pages/audit/settings-user-lifecycle/audit-data().php',
'modules/audit/pages/audit/import-audit/data().php', 'modules/audit/pages/audit/import-audit/data().php',
'pages/admin/scheduled-jobs/data().php', 'pages/admin/scheduled-jobs/data().php',
'pages/admin/scheduled-jobs/runs-data($id).php', 'pages/admin/scheduled-jobs/runs-data($id).php',
'pages/admin/mail-log/data().php', 'pages/admin/mail-log/data().php',
'pages/admin/tenants/data().php', 'pages/admin/tenants/data().php',
'modules/audit/pages/audit/system-audit/index().php', 'modules/audit/pages/audit/system-audit/index().php',
'modules/audit/pages/audit/user-lifecycle-audit/index().php', 'modules/audit/templates/settings-user-lifecycle-panel.phtml',
'modules/audit/pages/audit/import-audit/index().php', 'modules/audit/pages/audit/import-audit/index().php',
'pages/admin/scheduled-jobs/index().php', 'pages/admin/scheduled-jobs/index().php',
'pages/admin/mail-log/index().php', 'pages/admin/mail-log/index().php',