Erweiterung Log Export und Login / Logout Log

This commit is contained in:
Erik Thiele
2026-05-22 15:53:32 +02:00
parent 820753f089
commit 0fa79eb7f1
2 changed files with 26 additions and 1 deletions

View File

@@ -494,6 +494,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($action === 'login') { if ($action === 'login') {
$found = findUserByEmail($pdo, trim((string)($_POST['email'] ?? ''))); $found = findUserByEmail($pdo, trim((string)($_POST['email'] ?? '')));
if ($found && password_verify((string)($_POST['password'] ?? ''), $found['password_hash'])) { if ($found && password_verify((string)($_POST['password'] ?? ''), $found['password_hash'])) {
addAuditLog($pdo, (int)$found['id'], 'Anmeldung erfolgreich.');
session_regenerate_id(true); session_regenerate_id(true);
$_SESSION['user'] = ['id' => (int)$found['id'], 'firstname' => $found['firstname'] ?? '', 'lastname' => $found['lastname'] ?? '', 'name' => displayName($found), 'email' => $found['email'], 'role' => $found['role']]; $_SESSION['user'] = ['id' => (int)$found['id'], 'firstname' => $found['firstname'] ?? '', 'lastname' => $found['lastname'] ?? '', 'name' => displayName($found), 'email' => $found['email'], 'role' => $found['role']];
header('Location: /?page=dashboard'); header('Location: /?page=dashboard');
@@ -803,6 +804,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'SQL-Dump ist ohne Datenbank nicht verfuegbar.'; $error = 'SQL-Dump ist ohne Datenbank nicht verfuegbar.';
} }
if ($user && $action === 'export_logs_csv' && $user['role'] === 'admin') {
if ($pdo) {
addAuditLog($pdo, (int)$user['id'], 'Logansicht als CSV exportiert.');
$filename = 'log-export-' . date('Y-m-d-His') . '.csv';
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$output = fopen('php://output', 'wb');
if ($output !== false) {
fwrite($output, "\xEF\xBB\xBF");
$rows = $pdo->query('(SELECT w.created_at AS ts, CONCAT(a.firstname, " ", a.lastname) AS actor, CONCAT(w.hours, " Stunden für ", m.firstname, " ", m.lastname, " gebucht: ", w.note) AS action FROM work_logs w JOIN users a ON a.id = w.actor_id JOIN users m ON m.id = w.member_id) UNION ALL (SELECT l.created_at AS ts, CONCAT(a.firstname, " ", a.lastname) AS actor, l.action FROM audit_logs l JOIN users a ON a.id = l.actor_id) ORDER BY ts DESC')->fetchAll(PDO::FETCH_ASSOC);
if ($rows) {
fputcsv($output, array_keys($rows[0]));
foreach ($rows as $row) {
fputcsv($output, $row);
}
}
fclose($output);
}
exit;
}
$error = 'Log-Export ist ohne Datenbank nicht verfuegbar.';
}
if ($user && $action === 'import_sql_dump' && $user['role'] === 'admin') { if ($user && $action === 'import_sql_dump' && $user['role'] === 'admin') {
if (!$pdo) { if (!$pdo) {
$error = 'SQL-Wiederherstellung ist ohne Datenbank nicht verfuegbar.'; $error = 'SQL-Wiederherstellung ist ohne Datenbank nicht verfuegbar.';
@@ -902,6 +926,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
if ($user && $action === 'logout') { if ($user && $action === 'logout') {
addAuditLog($pdo, (int)$user['id'], 'Abmeldung erfolgt.');
session_destroy(); session_destroy();
header('Location: /?page=login'); header('Location: /?page=login');
exit; exit;

View File

@@ -671,7 +671,7 @@ function renderAppShell(string $page): void
<div class="col-lg-12"><div class="card"><div class="card-body admin-actions"><h3 class="section-title"><i class="ti ti-file-import"></i><span>CSV-Import</span></h3><p class="text-secondary">Importiert Benutzerdaten aus einer CSV-Datei mit den Spalten <strong>firstname</strong>, <strong>lastname</strong>, <strong>email</strong>, <strong>role</strong> und <strong>hours_worked</strong>.</p><div class="text-secondary small mb-3"><strong>Beispiel-Kopfzeile:</strong> <code>firstname,lastname,email,role,hours_worked</code></div><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="import_csv"><div class="mb-3"><input class="form-control" name="csv_file" type="file" accept=".csv,text/csv" required></div><button class="btn btn-primary" type="submit">Benutzerdaten aus CSV importieren</button></form></div></div></div> <div class="col-lg-12"><div class="card"><div class="card-body admin-actions"><h3 class="section-title"><i class="ti ti-file-import"></i><span>CSV-Import</span></h3><p class="text-secondary">Importiert Benutzerdaten aus einer CSV-Datei mit den Spalten <strong>firstname</strong>, <strong>lastname</strong>, <strong>email</strong>, <strong>role</strong> und <strong>hours_worked</strong>.</p><div class="text-secondary small mb-3"><strong>Beispiel-Kopfzeile:</strong> <code>firstname,lastname,email,role,hours_worked</code></div><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="import_csv"><div class="mb-3"><input class="form-control" name="csv_file" type="file" accept=".csv,text/csv" required></div><button class="btn btn-primary" type="submit">Benutzerdaten aus CSV importieren</button></form></div></div></div>
<div class="col-lg-12"><div class="card"><div class="card-body admin-actions"><h3 class="section-title"><i class="ti ti-rotate-clockwise-2"></i><span>Arbeitsstunden zurücksetzen</span></h3><p class="text-secondary">Setzt alle gebuchten Arbeitsstunden für alle Mitglieder auf 0. Diese Aktion ist für den Jahreswechsel gedacht.</p><form method="post" onsubmit="return confirm('Wirklich alle Arbeitsstunden auf 0 zurücksetzen?');"><input type="hidden" name="action" value="reset_all_hours"><button class="btn btn-outline-danger" type="submit">Alle Arbeitsstunden auf 0 setzen</button></form></div></div></div> <div class="col-lg-12"><div class="card"><div class="card-body admin-actions"><h3 class="section-title"><i class="ti ti-rotate-clockwise-2"></i><span>Arbeitsstunden zurücksetzen</span></h3><p class="text-secondary">Setzt alle gebuchten Arbeitsstunden für alle Mitglieder auf 0. Diese Aktion ist für den Jahreswechsel gedacht.</p><form method="post" onsubmit="return confirm('Wirklich alle Arbeitsstunden auf 0 zurücksetzen?');"><input type="hidden" name="action" value="reset_all_hours"><button class="btn btn-outline-danger" type="submit">Alle Arbeitsstunden auf 0 setzen</button></form></div></div></div>
</div> </div>
<div class="row row-cards mt-2"><div class="col-lg-12"><div class="card"><div class="card-body"><h3 class="section-title"><i class="ti ti-history"></i><span>Logansicht</span></h3><ul class="list-group list-group-flush"><?php foreach (($logs['entries'] ?? []) as $entry): ?><li class="list-group-item px-0"><strong><?= htmlspecialchars($entry['ts']) ?></strong><br><?= htmlspecialchars($entry['actor']) ?>: <?= htmlspecialchars($entry['action']) ?></li><?php endforeach; ?><?php if (empty($logs['entries'])): ?><li class="list-group-item px-0 text-secondary">Keine Logeinträge vorhanden.</li><?php endif; ?></ul><?php if (($logs['total_pages'] ?? 1) > 1): ?><div class="d-flex align-items-center justify-content-between gap-3 mt-3"><a class="btn btn-outline-secondary<?= ($logs['page'] ?? 1) <= 1 ? ' disabled' : '' ?>" href="/?page=administration&log_page=<?= max(1, (int)($logs['page'] ?? 1) - 1) ?>">Zurück</a><div class="text-secondary small">Seite <?= (int)($logs['page'] ?? 1) ?> von <?= (int)($logs['total_pages'] ?? 1) ?>, insgesamt <?= (int)($logs['total'] ?? 0) ?> Einträge</div><a class="btn btn-outline-secondary<?= ($logs['page'] ?? 1) >= ($logs['total_pages'] ?? 1) ? ' disabled' : '' ?>" href="/?page=administration&log_page=<?= min((int)($logs['total_pages'] ?? 1), (int)($logs['page'] ?? 1) + 1) ?>">Weiter</a></div><?php endif; ?></div></div></div></div> <div class="row row-cards mt-2"><div class="col-lg-12"><div class="card"><div class="card-body admin-actions"><h3 class="section-title"><i class="ti ti-history"></i><span>Logansicht</span></h3><form method="post" class="mb-3"><input type="hidden" name="action" value="export_logs_csv"><button class="btn btn-primary" type="submit">Log als CSV exportieren</button></form><ul class="list-group list-group-flush"><?php foreach (($logs['entries'] ?? []) as $entry): ?><li class="list-group-item px-0"><strong><?= htmlspecialchars($entry['ts']) ?></strong><br><?= htmlspecialchars($entry['actor']) ?>: <?= htmlspecialchars($entry['action']) ?></li><?php endforeach; ?><?php if (empty($logs['entries'])): ?><li class="list-group-item px-0 text-secondary">Keine Logeinträge vorhanden.</li><?php endif; ?></ul><?php if (($logs['total_pages'] ?? 1) > 1): ?><div class="d-flex align-items-center justify-content-between gap-3 mt-3"><a class="btn btn-outline-secondary<?= ($logs['page'] ?? 1) <= 1 ? ' disabled' : '' ?>" href="/?page=administration&log_page=<?= max(1, (int)($logs['page'] ?? 1) - 1) ?>">Zurück</a><div class="text-secondary small">Seite <?= (int)($logs['page'] ?? 1) ?> von <?= (int)($logs['total_pages'] ?? 1) ?>, insgesamt <?= (int)($logs['total'] ?? 0) ?> Einträge</div><a class="btn btn-outline-secondary<?= ($logs['page'] ?? 1) >= ($logs['total_pages'] ?? 1) ? ' disabled' : '' ?>" href="/?page=administration&log_page=<?= min((int)($logs['total_pages'] ?? 1), (int)($logs['page'] ?? 1) + 1) ?>">Weiter</a></div><?php endif; ?></div></div></div></div>
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>