Move the entire audit subsystem (system audit, API audit, import audit, user lifecycle audit, frontend telemetry) from core into modules/audit/. Core decoupling via interface-based injection: - AuditRecorderInterface replaces SystemAuditService in 10+ core services - UserLifecycleAuditInterface / ImportAuditInterface for specialized flows - NullAuditRecorder fallback when audit module is disabled - ApiBootstrap/ApiResponse use null-safe callable resolvers Module structure (modules/audit/): - Manifest with routes, permissions, scheduler jobs, authorization policy - 9 services, 8 repositories, 6 domain enums, 4 job handlers - 33 page files, 4 JS files, 8 test files, migration scripts, i18n Core cleanup: - OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService surgically cleaned of audit-specific constants - Sidebar template cleared of hardcoded audit navigation - AuditModuleIsolationContractTest ensures no future core→module coupling All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5 clean, architecture contracts verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
192 lines
6.5 KiB
PHTML
192 lines
6.5 KiB
PHTML
<?php
|
|
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
|
|
|
/**
|
|
* @var array $auditLog
|
|
*/
|
|
|
|
$auditLog = $auditLog ?? [];
|
|
$outcome = SystemAuditOutcome::normalizeOr((string) ($auditLog['outcome'] ?? ''), SystemAuditOutcome::Success);
|
|
$outcomeVariant = $outcome->badgeVariant();
|
|
|
|
$metadataJson = trim((string) ($auditLog['metadata_json'] ?? ''));
|
|
$metadataPretty = '-';
|
|
if ($metadataJson !== '') {
|
|
$decoded = json_decode($metadataJson, true);
|
|
if (is_array($decoded)) {
|
|
$pretty = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
$metadataPretty = is_string($pretty) ? $pretty : $metadataJson;
|
|
} else {
|
|
$metadataPretty = $metadataJson;
|
|
}
|
|
}
|
|
|
|
$requestId = trim((string) ($auditLog['request_id'] ?? ''));
|
|
$eventType = trim((string) ($auditLog['event_type'] ?? ''));
|
|
$channel = strtoupper(trim((string) ($auditLog['channel'] ?? '')));
|
|
$errorCode = trim((string) ($auditLog['error_code'] ?? ''));
|
|
$method = strtoupper(trim((string) ($auditLog['method'] ?? '')));
|
|
$path = trim((string) ($auditLog['path'] ?? ''));
|
|
$targetType = trim((string) ($auditLog['target_type'] ?? ''));
|
|
$targetUuid = trim((string) ($auditLog['target_uuid'] ?? ''));
|
|
$ipHash = trim((string) ($auditLog['ip_hash'] ?? ''));
|
|
$userAgentHash = trim((string) ($auditLog['user_agent_hash'] ?? ''));
|
|
|
|
$actorLabel = trim((string) ($auditLog['actor_user_display_name'] ?? ''));
|
|
$actorEmail = trim((string) ($auditLog['actor_user_email'] ?? ''));
|
|
if ($actorLabel === '') {
|
|
$actorLabel = $actorEmail !== '' ? $actorEmail : '-';
|
|
}
|
|
|
|
$tenantLabel = trim((string) ($auditLog['actor_tenant_description'] ?? ''));
|
|
if ($tenantLabel === '') {
|
|
$tenantLabel = '-';
|
|
}
|
|
?>
|
|
|
|
<div class="app-details-container">
|
|
<section>
|
|
<?php
|
|
$breadcrumbs = [
|
|
['label' => t('Home'), 'path' => 'admin'],
|
|
['label' => t('System audit logs'), 'path' => 'admin/system-audit'],
|
|
['label' => t('View')],
|
|
];
|
|
require templatePath('partials/app-breadcrumb.phtml');
|
|
|
|
$titlebar = [
|
|
'title' => t('View system audit entry'),
|
|
'backHref' => 'admin/system-audit',
|
|
'backTitle' => t('Back'),
|
|
];
|
|
require templatePath('partials/app-details-titlebar.phtml');
|
|
?>
|
|
|
|
<div class="app-details-content">
|
|
<details open>
|
|
<summary><?php e(t('Event details')); ?></summary>
|
|
<hr>
|
|
<div class="grid">
|
|
<div>
|
|
<small><?php e(t('Event type')); ?></small>
|
|
<p><code><?php e($eventType !== '' ? $eventType : '-'); ?></code></p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('Outcome')); ?></small>
|
|
<p><span class="badge" data-variant="<?php e($outcomeVariant); ?>"><?php e(t($outcome->labelToken())); ?></span></p>
|
|
</div>
|
|
</div>
|
|
<div class="grid">
|
|
<div>
|
|
<small><?php e(t('Channel')); ?></small>
|
|
<p><?php e($channel !== '' ? $channel : '-'); ?></p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('Error code')); ?></small>
|
|
<p><?php e($errorCode !== '' ? $errorCode : '-'); ?></p>
|
|
</div>
|
|
</div>
|
|
<?php if ($method !== '' || $path !== ''): ?>
|
|
<div class="grid">
|
|
<div>
|
|
<small><?php e(t('Method')); ?></small>
|
|
<p><?php e($method !== '' ? $method : '-'); ?></p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('Path')); ?></small>
|
|
<p><code><?php e($path !== '' ? $path : '-'); ?></code></p>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</details>
|
|
|
|
<hr>
|
|
<details>
|
|
<summary><?php e(t('Scope')); ?></summary>
|
|
<hr>
|
|
<div class="grid">
|
|
<div>
|
|
<small><?php e(t('Actor')); ?></small>
|
|
<p>
|
|
<?php if (!empty($auditLog['actor_user_uuid'])): ?>
|
|
<a href="admin/users/edit/<?php e($auditLog['actor_user_uuid']); ?>"><?php e($actorLabel); ?></a>
|
|
<?php else: ?>
|
|
<?php e($actorLabel); ?>
|
|
<?php endif; ?>
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('Actor tenant')); ?></small>
|
|
<p>
|
|
<?php if (!empty($auditLog['actor_tenant_uuid'])): ?>
|
|
<a href="admin/tenants/edit/<?php e($auditLog['actor_tenant_uuid']); ?>"><?php e($tenantLabel); ?></a>
|
|
<?php else: ?>
|
|
<?php e($tenantLabel); ?>
|
|
<?php endif; ?>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="grid">
|
|
<div>
|
|
<small><?php e(t('Target type')); ?></small>
|
|
<p><?php e($targetType !== '' ? $targetType : '-'); ?></p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('Target UUID')); ?></small>
|
|
<p><code><?php e($targetUuid !== '' ? $targetUuid : '-'); ?></code></p>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<?php if ($metadataPretty !== '-'): ?>
|
|
<hr>
|
|
<details>
|
|
<summary><?php e(t('Metadata')); ?></summary>
|
|
<hr>
|
|
<textarea readonly rows="14"><?php e($metadataPretty); ?></textarea>
|
|
</details>
|
|
<?php endif; ?>
|
|
</div>
|
|
</section>
|
|
|
|
<aside id="app-details-aside-section">
|
|
<div class="app-details-aside-section">
|
|
<hgroup>
|
|
<strong><?php e(t('System audit')); ?></strong>
|
|
<p><small><?php e($requestId !== '' ? $requestId : '-'); ?></small></p>
|
|
</hgroup>
|
|
<hr>
|
|
<div class="grid">
|
|
<div>
|
|
<small><?php e(t('ID')); ?></small>
|
|
<p>
|
|
<span class="badge" data-variant="neutral" data-copy="true" data-copy-value="<?php e((string) ($auditLog['id'] ?? '')); ?>">
|
|
<?php e((string) ($auditLog['id'] ?? '-')); ?>
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('Created')); ?></small>
|
|
<p><?php e(dt((string) ($auditLog['created_at'] ?? '')) ?: '-'); ?></p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('Request ID')); ?></small>
|
|
<p>
|
|
<span class="badge" data-variant="neutral" data-copy="true" data-copy-value="<?php e($requestId); ?>">
|
|
<?php e($requestId !== '' ? substr($requestId, 0, 10) : '-'); ?>
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('IP hash')); ?></small>
|
|
<p><code><?php e($ipHash !== '' ? substr($ipHash, 0, 16) . '...' : '-'); ?></code></p>
|
|
</div>
|
|
<div>
|
|
<small><?php e(t('User agent hash')); ?></small>
|
|
<p><code><?php e($userAgentHash !== '' ? substr($userAgentHash, 0, 16) . '...' : '-'); ?></code></p>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</div>
|