Embed user lifecycle audit into settings page
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -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]
|
||||
@@ -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>
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,32 +1,12 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Buffer;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
|
||||
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
|
||||
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();
|
||||
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'),
|
||||
],
|
||||
];
|
||||
?>
|
||||
|
||||
<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>
|
||||
@@ -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(`<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.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;
|
||||
},
|
||||
});
|
||||
@@ -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']
|
||||
: [];
|
||||
?>
|
||||
|
||||
<div class="app-details-container">
|
||||
@@ -86,6 +92,23 @@ $readonlyAttr = $isReadOnly ? 'readonly' : '';
|
||||
|
||||
<?php Session::getCsrfInput(); ?>
|
||||
</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>
|
||||
<aside id="app-details-aside-section">
|
||||
<div class="app-details-aside-section"></div>
|
||||
|
||||
@@ -603,7 +603,7 @@ $canViewAuditTab = (bool) ($auditStatsAvailable ?? false) && (
|
||||
<tr>
|
||||
<td>
|
||||
<?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 e((string) ($row['action'] ?? '')); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -74,8 +74,7 @@ class AuthzUiActionContractTest extends TestCase
|
||||
'modules/audit/pages/audit/api-audit/index().php',
|
||||
'modules/audit/pages/audit/system-audit/index().php',
|
||||
'modules/audit/pages/audit/import-audit/index().php',
|
||||
'modules/audit/pages/audit/user-lifecycle-audit/index().php',
|
||||
'modules/audit/pages/audit/user-lifecycle-audit/view($id).php',
|
||||
'modules/audit/pages/audit/settings-user-lifecycle/audit-fragment($id).php',
|
||||
];
|
||||
|
||||
foreach ($actions as $action) {
|
||||
|
||||
@@ -55,16 +55,6 @@ trait AuthzUiContractSupport
|
||||
'modules/audit/pages/audit/system-audit/index().php',
|
||||
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,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ class AuthzUiTemplateContractTest extends TestCase
|
||||
'modules/audit/pages/audit/import-audit/index(default).phtml',
|
||||
'pages/admin/scheduled-jobs/index(default).phtml',
|
||||
'pages/admin/stats/index(default).phtml',
|
||||
'modules/audit/pages/audit/user-lifecycle-audit/index(default).phtml',
|
||||
'modules/audit/pages/audit/user-lifecycle-audit/view(default).phtml',
|
||||
'modules/audit/templates/settings-user-lifecycle-panel.phtml',
|
||||
'modules/audit/pages/audit/settings-user-lifecycle/audit-fragment(none).phtml',
|
||||
'pages/admin/users/index(default).phtml',
|
||||
'pages/admin/users/create(default).phtml',
|
||||
'pages/admin/tenants/edit(default).phtml',
|
||||
|
||||
@@ -56,7 +56,7 @@ trait ListContractFiles
|
||||
'pages/admin/scheduled-jobs/runs-data($id).php',
|
||||
'modules/audit/pages/audit/api-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',
|
||||
];
|
||||
}
|
||||
@@ -80,7 +80,7 @@ trait ListContractFiles
|
||||
'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/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'",
|
||||
];
|
||||
}
|
||||
@@ -103,7 +103,7 @@ trait ListContractFiles
|
||||
'pages/admin/scheduled-jobs/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/user-lifecycle-audit/index(default).phtml',
|
||||
'modules/audit/templates/settings-user-lifecycle-panel.phtml',
|
||||
'modules/audit/pages/audit/system-audit/index(default).phtml',
|
||||
];
|
||||
}
|
||||
@@ -126,7 +126,7 @@ trait ListContractFiles
|
||||
'pages/admin/scheduled-jobs/index().php',
|
||||
'modules/audit/pages/audit/api-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',
|
||||
];
|
||||
}
|
||||
@@ -148,7 +148,7 @@ trait ListContractFiles
|
||||
'pages/admin/scheduled-jobs/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/user-lifecycle-audit/index(default).phtml',
|
||||
'modules/audit/templates/settings-user-lifecycle-panel.phtml',
|
||||
'modules/audit/pages/audit/system-audit/index(default).phtml',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ final class ListTitlebarContractFiles
|
||||
'pages/admin/scheduled-jobs/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/user-lifecycle-audit/index(default).phtml',
|
||||
'modules/audit/templates/settings-user-lifecycle-panel.phtml',
|
||||
'modules/audit/pages/audit/system-audit/index(default).phtml',
|
||||
'modules/addressbook/pages/address-book/index(default).phtml',
|
||||
'pages/search/index(default).phtml',
|
||||
|
||||
@@ -28,7 +28,7 @@ class ListUiSharedPartialsContractTest extends TestCase
|
||||
'pages/admin/scheduled-jobs/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/user-lifecycle-audit/index(default).phtml',
|
||||
'modules/audit/templates/settings-user-lifecycle-panel.phtml',
|
||||
'modules/audit/pages/audit/system-audit/index(default).phtml',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ final class StatusTaxonomyContractFiles
|
||||
'SystemAuditOutcome::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(UserLifecycleStatus::class)',
|
||||
'gridEnumSanitizer(UserLifecycleTriggerType::class)',
|
||||
@@ -65,7 +65,7 @@ final class StatusTaxonomyContractFiles
|
||||
// System audit: data + export endpoints delegate badge/label resolution
|
||||
// to SystemAuditRowPresenter, which is the enforced source of truth.
|
||||
'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',
|
||||
'pages/admin/scheduled-jobs/data().php',
|
||||
'pages/admin/scheduled-jobs/runs-data($id).php',
|
||||
@@ -98,14 +98,14 @@ final class StatusTaxonomyContractFiles
|
||||
'core/Repository/Tenant/TenantRepository.php',
|
||||
'core/Repository/Stats/AdminStatsRepository.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',
|
||||
'pages/admin/scheduled-jobs/data().php',
|
||||
'pages/admin/scheduled-jobs/runs-data($id).php',
|
||||
'pages/admin/mail-log/data().php',
|
||||
'pages/admin/tenants/data().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',
|
||||
'pages/admin/scheduled-jobs/index().php',
|
||||
'pages/admin/mail-log/index().php',
|
||||
|
||||
Reference in New Issue
Block a user