Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill

- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 07:30:37 +01:00
commit 2e24a40d68
9633 changed files with 1300799 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
<x-layouts.admin :title="__('admin.activity_log_title')">
<h1 class="text-2xl font-bold mb-6">{{ __('admin.activity_log_title') }}</h1>
{{-- Filter --}}
<form method="GET" class="bg-white rounded-lg shadow p-4 mb-6">
<div class="flex flex-wrap gap-4 items-end">
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('admin.log_category') }}</label>
<select name="category" class="px-3 py-2 border border-gray-300 rounded-md text-sm">
<option value="">{{ __('admin.log_all_categories') }}</option>
<option value="auth" {{ request('category') === 'auth' ? 'selected' : '' }}>{{ __('admin.log_cat_auth') }}</option>
<option value="users" {{ request('category') === 'users' ? 'selected' : '' }}>{{ __('admin.log_cat_users') }}</option>
<option value="players" {{ request('category') === 'players' ? 'selected' : '' }}>{{ __('admin.log_cat_players') }}</option>
<option value="events" {{ request('category') === 'events' ? 'selected' : '' }}>{{ __('admin.log_cat_events') }}</option>
<option value="files" {{ request('category') === 'files' ? 'selected' : '' }}>{{ __('admin.log_cat_files') }}</option>
<option value="settings" {{ request('category') === 'settings' ? 'selected' : '' }}>{{ __('admin.log_cat_settings') }}</option>
<option value="dsgvo" {{ request('category') === 'dsgvo' ? 'selected' : '' }}>{{ __('admin.log_cat_dsgvo') }}</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('admin.log_from') }}</label>
<input type="date" name="from" value="{{ request('from') }}" class="px-3 py-2 border border-gray-300 rounded-md text-sm">
</div>
<div>
<label class="block text-xs font-medium text-gray-600 mb-1">{{ __('admin.log_to') }}</label>
<input type="date" name="to" value="{{ request('to') }}" class="px-3 py-2 border border-gray-300 rounded-md text-sm">
</div>
<div class="flex gap-2">
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium hover:bg-blue-700">{{ __('admin.log_filter') }}</button>
<a href="{{ route('admin.activity-logs.index') }}" class="bg-gray-200 text-gray-700 px-4 py-2 rounded-md text-sm font-medium hover:bg-gray-300">{{ __('admin.log_reset') }}</a>
</div>
</div>
</form>
{{-- Tabelle --}}
<div class="bg-white rounded-lg shadow overflow-hidden overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b">
<tr>
<th class="text-left px-4 py-3 font-medium text-gray-700">{{ __('admin.log_time') }}</th>
<th class="text-left px-4 py-3 font-medium text-gray-700">{{ __('admin.log_user') }}</th>
<th class="text-center px-4 py-3 font-medium text-gray-700">{{ __('admin.log_action') }}</th>
<th class="text-left px-4 py-3 font-medium text-gray-700">{{ __('admin.log_description') }}</th>
<th class="text-right px-4 py-3 font-medium text-gray-700">{{ __('admin.log_ip') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse ($logs as $log)
@php
$actionColors = [
'login' => 'bg-green-100 text-green-800',
'logout' => 'bg-gray-100 text-gray-800',
'login_failed' => 'bg-red-100 text-red-800',
'registered' => 'bg-blue-100 text-blue-800',
'created' => 'bg-blue-100 text-blue-800',
'updated' => 'bg-yellow-100 text-yellow-800',
'deleted' => 'bg-red-100 text-red-800',
'restored' => 'bg-green-100 text-green-800',
'toggled_active' => 'bg-yellow-100 text-yellow-800',
'role_changed' => 'bg-purple-100 text-purple-800',
'password_reset' => 'bg-orange-100 text-orange-800',
'parent_assigned' => 'bg-blue-100 text-blue-800',
'parent_removed' => 'bg-red-100 text-red-800',
'participant_status_changed' => 'bg-yellow-100 text-yellow-800',
'status_changed' => 'bg-yellow-100 text-yellow-800',
'uploaded' => 'bg-blue-100 text-blue-800',
'reverted' => 'bg-orange-100 text-orange-800',
'bot_blocked' => 'bg-red-100 text-red-800',
'dsgvo_consent_uploaded' => 'bg-blue-100 text-blue-800',
'dsgvo_consent_confirmed' => 'bg-green-100 text-green-800',
'dsgvo_consent_revoked' => 'bg-yellow-100 text-yellow-800',
'dsgvo_consent_removed' => 'bg-orange-100 text-orange-800',
'dsgvo_consent_rejected' => 'bg-red-100 text-red-800',
'account_self_deleted' => 'bg-red-100 text-red-800',
'child_auto_deactivated' => 'bg-red-100 text-red-800',
];
$color = $actionColors[$log->action] ?? 'bg-gray-100 text-gray-800';
$hasDetails = !empty($log->properties);
@endphp
<tr class="hover:bg-gray-50" x-data="{ open: false }">
<td class="px-4 py-3 text-gray-500 whitespace-nowrap">{{ $log->created_at->format('d.m.Y H:i') }}</td>
<td class="px-4 py-3 text-gray-900">{{ $log->user?->name ?? __('admin.log_system') }}</td>
<td class="px-4 py-3 text-center">
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium {{ $color }}">{{ $log->action }}</span>
</td>
<td class="px-4 py-3 text-gray-600">
<div>{{ $log->description }}</div>
@if ($hasDetails)
<button @click="open = !open" class="text-xs text-blue-600 hover:text-blue-800 mt-1 flex items-center gap-1">
<svg class="w-3 h-3 transition-transform" :class="open ? 'rotate-90' : ''" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
{{ __('admin.log_details') }}
</button>
<div x-show="open" x-cloak x-transition class="mt-2 bg-gray-50 rounded-md p-3 text-xs space-y-1">
@php
$oldData = $log->properties['old'] ?? [];
$newData = $log->properties['new'] ?? [];
$allKeys = array_unique(array_merge(array_keys($oldData), array_keys($newData)));
@endphp
@if (count($allKeys) > 0)
<table class="w-full">
<thead>
<tr class="text-gray-500">
<th class="text-left font-medium pr-3 pb-1">{{ __('admin.log_field') }}</th>
<th class="text-left font-medium pr-3 pb-1">{{ __('admin.log_old_value') }}</th>
<th class="text-left font-medium pb-1">{{ __('admin.log_new_value') }}</th>
</tr>
</thead>
<tbody>
@foreach ($allKeys as $key)
<tr>
<td class="pr-3 py-0.5 font-medium text-gray-600">{{ $key }}</td>
<td class="pr-3 py-0.5 {{ isset($oldData[$key]) ? 'text-red-600 line-through' : 'text-gray-400' }}">{{ $oldData[$key] ?? '' }}</td>
<td class="py-0.5 {{ isset($newData[$key]) ? 'text-green-700' : 'text-gray-400' }}">{{ $newData[$key] ?? '' }}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
@endif
</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<span class="text-gray-400 text-xs">{{ $log->ip_address }}</span>
@php
$revertableActions = ['deleted', 'toggled_active', 'role_changed', 'status_changed', 'participant_status_changed'];
$canRevert = in_array($log->action, $revertableActions) && $log->model_id;
@endphp
@if ($canRevert)
<form method="POST" action="{{ route('admin.activity-logs.revert', $log) }}"
onsubmit="return confirm(@js(__('admin.log_revert_confirm')))" class="inline ml-2">
@csrf
<button type="submit" class="text-xs text-orange-600 hover:text-orange-800 font-medium">
{{ __('admin.log_revert') }}
</button>
</form>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-4 py-8 text-center text-gray-500">{{ __('admin.log_empty') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">{{ $logs->links() }}</div>
</x-layouts.admin>