authorizeAccess(); } protected function authorizeAccess(): void { $user = Auth::user(); if (! $user || ! $user->isSuperAdmin()) { abort(403, 'Access denied. Only Super Admin can view impersonation logs.'); } } public function getTitle(): string { return 'Impersonation Logs'; } protected function getHeaderActions(): array { return [ Action::make('export_csv') ->label('Export CSV') ->icon('heroicon-o-arrow-down-tray') ->color('success') ->action(fn () => $this->exportCsv()), Action::make('refresh') ->label('Refresh') ->icon('heroicon-o-arrow-path') ->color('gray') ->action(fn () => $this->resetTable()), ]; } protected function getHeaderWidgets(): array { return [ \App\Filament\Widgets\ImpersonationStatsOverview::class, ]; } public function table(Table $table): Table { return $table ->query( ImpersonationLog::query() ->with(['admin', 'targetUser']) ->latest('start_time') ) ->columns([ TextColumn::make('id') ->label('ID') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('admin.name') ->label('Admin') ->description(fn (ImpersonationLog $record): string => $record->admin?->email ?? 'Unknown') ->sortable() ->searchable() ->weight('medium'), TextColumn::make('targetUser.name') ->label('Target User') ->description(fn (ImpersonationLog $record): string => $record->targetUser?->email ?? 'Unknown') ->sortable() ->searchable() ->weight('medium'), TextColumn::make('start_time') ->label('Started') ->dateTime('M j, Y g:i A') ->sortable() ->description(fn (ImpersonationLog $record): string => $record->start_time->diffForHumans()), TextColumn::make('end_time') ->label('Ended') ->dateTime('M j, Y g:i A') ->sortable() ->placeholder('Active session') ->description(fn (ImpersonationLog $record): string => $record->end_time ? $record->end_time->diffForHumans() : 'Still active' ), TextColumn::make('duration_in_minutes') ->label('Duration') ->formatStateUsing(function ($record) { return match(true) { !$record->duration_in_minutes => 'Active', $record->duration_in_minutes < 60 => "{$record->duration_in_minutes}m", default => round($record->duration_in_minutes / 60, 1) . 'h', }; }) ->sortable() ->alignCenter(), TextColumn::make('status') ->label('Status') ->formatStateUsing(function ($record) { return match ($record->status) { 'active' => 'Active', 'completed' => 'Completed', 'force_terminated' => 'Terminated', 'expired' => 'Expired', default => 'Unknown', }; }) ->badge() ->color(fn ($record) => match ($record->status) { 'active' => 'success', 'completed' => 'primary', 'force_terminated' => 'danger', 'expired' => 'warning', default => 'gray', }), TextColumn::make('ip_address') ->label('IP Address') ->searchable() ->toggleable(isToggledHiddenByDefault: true) ->copyable() ->fontFamily('mono'), TextColumn::make('pages_visited_count') ->label('Pages') ->formatStateUsing(fn (ImpersonationLog $record): int => is_array($record->pages_visited) ? count($record->pages_visited) : 0 ) ->alignCenter() ->toggleable(), TextColumn::make('actions_taken_count') ->label('Actions') ->formatStateUsing(fn (ImpersonationLog $record): int => is_array($record->actions_taken) ? count($record->actions_taken) : 0 ) ->alignCenter() ->toggleable(), ]) ->defaultSort('start_time', 'desc') ->filters([ SelectFilter::make('status') ->label('Status') ->options([ 'active' => 'Active', 'completed' => 'Completed', 'force_terminated' => 'Force Terminated', 'expired' => 'Expired', ]), SelectFilter::make('admin_id') ->label('Admin') ->searchable() ->getSearchResultsUsing(function (string $search): array { return User::where('level', UserLevel::SUPERADMIN->value) ->where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%") ->limit(50) ->pluck('name', 'id') ->toArray(); }) ->getOptionLabelUsing(function ($value): string { return User::find($value)?->name ?? 'Unknown'; }), SelectFilter::make('target_user_id') ->label('Target User') ->searchable() ->getSearchResultsUsing(function (string $search): array { return User::where('name', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%") ->limit(50) ->pluck('name', 'id') ->toArray(); }) ->getOptionLabelUsing(function ($value): string { return User::find($value)?->name ?? 'Unknown'; }), Filter::make('date_range') ->schema([ DatePicker::make('start_date') ->label('Start Date') ->required(), DatePicker::make('end_date') ->label('End Date') ->required(), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['start_date'], fn (Builder $query, $date): Builder => $query->whereDate('start_time', '>=', $date) ) ->when( $data['end_date'], fn (Builder $query, $date): Builder => $query->whereDate('start_time', '<=', $date) ); }), Filter::make('ip_address') ->schema([ TextInput::make('ip') ->label('IP Address') ->placeholder('192.168.1.1') ->required(), ]) ->query(function (Builder $query, array $data): Builder { return $query->when( $data['ip'], fn (Builder $query, $ip): Builder => $query->where('ip_address', 'like', "%{$ip}%") ); }), ]) ->recordActions([ Action::make('view_details') ->label('View Details') ->icon('heroicon-o-eye') ->color('primary') ->slideOver() ->modalContent(fn (ImpersonationLog $record): View => view( 'filament.modals.impersonation-log-details-slideover', ['record' => $record] )) ->modalHeading('Impersonation Session Details') ->action(fn () => null), Action::make('terminate') ->label('Terminate') ->icon('heroicon-o-x-circle') ->color('danger') ->requiresConfirmation() ->modalHeading('Terminate Impersonation Session') ->modalDescription('This will immediately terminate the active impersonation session.') ->modalSubmitActionLabel('Terminate Session') ->visible(fn (ImpersonationLog $record): bool => $record->isActive()) ->action(function (ImpersonationLog $record) { $record->update([ 'end_time' => now(), 'status' => 'force_terminated', ]); }), ]) ->toolbarActions([ BulkActionGroup::make([ BulkAction::make('terminate_active') ->label('Terminate Active Sessions') ->icon('heroicon-o-x-circle') ->color('danger') ->requiresConfirmation() ->deselectRecordsAfterCompletion() ->action(function (Collection $records) { $records->filter(fn (ImpersonationLog $record) => $record->isActive()) ->each(function (ImpersonationLog $record) { $record->update([ 'end_time' => now(), 'status' => 'force_terminated', ]); }); }), DeleteBulkAction::make(), ]), ]) ->emptyStateHeading('No impersonation logs found') ->emptyStateDescription('No impersonation sessions have been recorded yet.') ->emptyStateActions([ Action::make('refresh') ->label('Refresh') ->icon('heroicon-o-arrow-path') ->action(fn () => $this->resetTable()), ]); } public function exportCsv() { $logs = ImpersonationLog::query() ->with(['admin', 'targetUser']) ->latest('start_time') ->get(); $filename = 'impersonation_logs_' . now()->format('Y_m_d_H_i_s') . '.csv'; // Create a temporary file $handle = fopen('php://temp', 'r+'); // Add BOM for Excel UTF-8 support fwrite($handle, "\xEF\xBB\xBF"); // Write headers $headers = [ 'ID', 'Admin Name', 'Admin Email', 'Target Name', 'Target Email', 'Start Time', 'End Time', 'Duration (minutes)', 'Status', 'IP Address', 'User Agent', 'Pages Visited', 'Actions Taken', ]; fputcsv($handle, $headers); // Write data rows foreach ($logs as $log) { fputcsv($handle, [ $log->id, $log->admin?->name ?? 'Unknown', $log->admin?->email ?? 'Unknown', $log->targetUser?->name ?? 'Unknown', $log->targetUser?->email ?? 'Unknown', $log->start_time->toDateTimeString(), $log->end_time?->toDateTimeString() ?? 'Active', $log->duration_in_minutes ?? 'Active', $log->status, $log->ip_address, $log->user_agent, is_array($log->pages_visited) ? json_encode($log->pages_visited) : '[]', is_array($log->actions_taken) ? json_encode($log->actions_taken) : '[]', ]); } // Rewind the file pointer rewind($handle); // Get the CSV content $csvContent = stream_get_contents($handle); // Close the file handle fclose($handle); // Return a download response return response()->streamDownload( function () use ($csvContent) { echo $csvContent; }, $filename, [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', ] ); } }