feat(audit): CSV/Excel export for system-audit via core primitive
Third consumer of the generic export primitive. Adds a
GET /admin/system-audit/export endpoint, the reusable export dropdown
to the list titlebar, and wires initListExport() into the page JS.
- pages/audit/system-audit/export().php: guard + ABILITY_SYSTEM_AUDIT_VIEW,
reuses the existing filter-schema.php via gridParseFiltersFromSchemaFile
so exported rows match the grid under the same filters, caps the limit
at 5000, and declares 11 ExportColumns (ID, created, event, status,
channel, actor, actor email, target type/uuid, request id, error code)
with translated enum labels for outcome/channel.
- module.php: new admin/system-audit/export route.
- index(default).phtml: app-list-export-dropdown partial placed above the
existing purge action; exportUrl added to pageConfig via
lurl('admin/system-audit/export').
- admin-system-audit-index.js: imports initListExport and invokes it
whenever config.exportUrl is present.
- i18n de/en: new "Actor email" key (required by TranslationKeysTest).
Gates: PHPUnit 1880 OK, PHPStan 0 errors, module:sync ok.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,5 +68,6 @@
|
||||
"Lifecycle risks (7d)": "Lifecycle-Risiken (7d)",
|
||||
"Top lifecycle reason codes (7d)": "Top-Lifecycle-Fehlercodes (7d)",
|
||||
"Recent lifecycle risks (7d)": "Aktuelle Lifecycle-Risiken (7d)",
|
||||
"Audit & logs": "Audit & Protokolle"
|
||||
"Audit & logs": "Audit & Protokolle",
|
||||
"Actor email": "Akteur-E-Mail"
|
||||
}
|
||||
|
||||
@@ -68,5 +68,6 @@
|
||||
"Lifecycle risks (7d)": "Lifecycle risks (7d)",
|
||||
"Top lifecycle reason codes (7d)": "Top lifecycle reason codes (7d)",
|
||||
"Recent lifecycle risks (7d)": "Recent lifecycle risks (7d)",
|
||||
"Audit & logs": "Audit & logs"
|
||||
"Audit & logs": "Audit & logs",
|
||||
"Actor email": "Actor email"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
['path' => 'admin/system-audit', 'target' => 'audit/system-audit/index'],
|
||||
['path' => 'admin/system-audit/view', 'target' => 'audit/system-audit/view'],
|
||||
['path' => 'admin/system-audit/data', 'target' => 'audit/system-audit/data'],
|
||||
['path' => 'admin/system-audit/export', 'target' => 'audit/system-audit/export'],
|
||||
['path' => 'admin/system-audit/purge', 'target' => 'audit/system-audit/purge'],
|
||||
// Import audit pages
|
||||
['path' => 'admin/import-audit', 'target' => 'audit/import-audit/index'],
|
||||
|
||||
57
modules/audit/pages/audit/system-audit/export().php
Normal file
57
modules/audit/pages/audit/system-audit/export().php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditChannel;
|
||||
use MintyPHP\Module\Audit\Domain\SystemAuditOutcome;
|
||||
use MintyPHP\Module\Audit\Service\SystemAuditService;
|
||||
use MintyPHP\Service\Export\ExportColumn;
|
||||
use MintyPHP\Support\Guard;
|
||||
|
||||
Guard::requireLogin();
|
||||
Guard::requireAbilityOrForbidden(AuditAuthorizationPolicy::ABILITY_SYSTEM_AUDIT_VIEW);
|
||||
exportRequireGetRequest();
|
||||
|
||||
$request = requestInput();
|
||||
|
||||
$filters = gridParseFiltersFromSchemaFile(__DIR__ . '/filter-schema.php');
|
||||
$filters['limit'] = exportCapLimit($request->query('limit'), 5000);
|
||||
$filters['offset'] = 0;
|
||||
|
||||
$result = app(SystemAuditService::class)->listPaged($filters);
|
||||
|
||||
$resolveOutcomeLabel = static function (string $raw): string {
|
||||
$outcome = SystemAuditOutcome::normalizeOr($raw, SystemAuditOutcome::Success);
|
||||
return (string) t($outcome->labelToken());
|
||||
};
|
||||
|
||||
$resolveChannelLabel = static function (string $raw): string {
|
||||
$channel = SystemAuditChannel::normalizeOr($raw, SystemAuditChannel::Web);
|
||||
return strtoupper($channel->labelToken());
|
||||
};
|
||||
|
||||
$resolveActor = static function (array $row): string {
|
||||
$name = trim((string) ($row['actor_user_display_name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
return $name;
|
||||
}
|
||||
$email = trim((string) ($row['actor_user_email'] ?? ''));
|
||||
return $email !== '' ? $email : '';
|
||||
};
|
||||
|
||||
$columns = [
|
||||
new ExportColumn(t('ID'), static fn (array $r): string => (string) ($r['id'] ?? '')),
|
||||
new ExportColumn(t('Created'), static fn (array $r): string => (string) dt((string) ($r['created_at'] ?? ''), 'Y-m-d H:i:s')),
|
||||
new ExportColumn(t('Event'), static fn (array $r): string => (string) ($r['event_type'] ?? '')),
|
||||
new ExportColumn(t('Status'), static fn (array $r): string => $resolveOutcomeLabel((string) ($r['outcome'] ?? ''))),
|
||||
new ExportColumn(t('Channel'), static fn (array $r): string => $resolveChannelLabel((string) ($r['channel'] ?? ''))),
|
||||
new ExportColumn(t('Actor'), static fn (array $r): string => $resolveActor($r)),
|
||||
new ExportColumn(t('Actor email'), static fn (array $r): string => (string) ($r['actor_user_email'] ?? '')),
|
||||
new ExportColumn(t('Target type'), static fn (array $r): string => (string) ($r['target_type'] ?? '')),
|
||||
new ExportColumn(t('Target UUID'), static fn (array $r): string => (string) ($r['target_uuid'] ?? '')),
|
||||
new ExportColumn(t('Request ID'), static fn (array $r): string => (string) ($r['request_id'] ?? '')),
|
||||
new ExportColumn(t('Error code'), static fn (array $r): string => (string) ($r['error_code'] ?? '')),
|
||||
];
|
||||
|
||||
$filename = 'system-audit-' . date('Ymd-His') . '.csv';
|
||||
|
||||
exportSendCsv($filename, (array) ($result['rows'] ?? []), $columns, exportResolveFlavor($request));
|
||||
@@ -15,6 +15,7 @@ $searchConfig = is_array($searchConfig ?? null) ? $searchConfig : null;
|
||||
$listTitle = t('System audit logs');
|
||||
ob_start();
|
||||
?>
|
||||
<?php require templatePath('partials/app-list-export-dropdown.phtml'); ?>
|
||||
<?php
|
||||
$listPurgeEnabled = $canPurgeSystemAudit;
|
||||
$listPurgeFormId = 'system-audit-purge-form';
|
||||
@@ -45,6 +46,7 @@ $pageConfig = [
|
||||
'filterSchema' => $clientFilterSchema,
|
||||
'filterChipMeta' => $filterChipMeta,
|
||||
'gridLang' => $gridLang,
|
||||
'exportUrl' => lurl('admin/system-audit/export'),
|
||||
'labels' => [
|
||||
'created' => t('Created'),
|
||||
'status' => t('Status'),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createListPageModule } from '/js/core/app-list-page-module.js';
|
||||
import { withCurrentListReturn } from '/js/pages/app-list-utils.js';
|
||||
import { initListExport } from '/js/core/app-list-export.js';
|
||||
|
||||
createListPageModule({
|
||||
configId: 'admin-system-audit-index',
|
||||
@@ -97,6 +98,10 @@ createListPageModule({
|
||||
},
|
||||
}) || {};
|
||||
|
||||
if (gridConfig && config.exportUrl) {
|
||||
initListExport({ gridConfig, exportUrl: config.exportUrl });
|
||||
}
|
||||
|
||||
return gridConfig;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user