diff --git a/app/Http/Controllers/Admin/AdminPageController.php b/app/Http/Controllers/Admin/AdminPageController.php index f7419b937..dd09f7cb2 100644 --- a/app/Http/Controllers/Admin/AdminPageController.php +++ b/app/Http/Controllers/Admin/AdminPageController.php @@ -3,14 +3,81 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\BasePageController; +use App\Services\UserStatsService; class AdminPageController extends BasePageController { + protected UserStatsService $userStatsService; + + public function __construct(UserStatsService $userStatsService) + { + parent::__construct(); + $this->userStatsService = $userStatsService; + } + /** * @throws \Exception */ public function index() { - return $this->adminBasePage(); + $this->setAdminPrefs(); + + // Get user statistics + $userStats = [ + 'users_by_role' => $this->userStatsService->getUsersByRole(), + 'downloads_per_day' => $this->userStatsService->getDownloadsPerDay(7), + 'api_hits_per_day' => $this->userStatsService->getApiHitsPerDay(7), + 'summary' => $this->userStatsService->getSummaryStats(), + 'top_downloaders' => $this->userStatsService->getTopDownloaders(5), + ]; + + return view('admin.dashboard', array_merge($this->viewData, [ + 'meta_title' => 'Admin Home', + 'meta_description' => 'Admin home page', + 'userStats' => $userStats, + 'stats' => $this->getDefaultStats(), + ])); + } + + /** + * Get default dashboard statistics + * + * @return array + */ + protected function getDefaultStats(): array + { + $today = now()->format('Y-m-d'); + + return [ + 'releases' => \App\Models\Release::count(), + 'releases_today' => \App\Models\Release::whereRaw('DATE(adddate) = ?', [$today])->count(), + 'users' => \App\Models\User::whereNull('deleted_at')->count(), + 'users_today' => \App\Models\User::whereRaw('DATE(created_at) = ?', [$today])->count(), + 'groups' => \App\Models\UsenetGroup::count(), + 'active_groups' => \App\Models\UsenetGroup::where('active', 1)->count(), + 'failed' => \App\Models\DnzbFailure::count(), + 'disk_free' => $this->getDiskSpace(), + ]; + } + + /** + * Get disk space information + * + * @return string + */ + protected function getDiskSpace(): string + { + try { + $bytes = disk_free_space('/'); + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + + for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { + $bytes /= 1024; + } + + return round($bytes, 2) . ' ' . $units[$i]; + } catch (\Exception $e) { + return 'N/A'; + } } } diff --git a/app/Services/UserStatsService.php b/app/Services/UserStatsService.php new file mode 100644 index 000000000..84a15fffa --- /dev/null +++ b/app/Services/UserStatsService.php @@ -0,0 +1,136 @@ +join('roles', 'users.roles_id', '=', 'roles.id') + ->select('roles.name as role_name', DB::raw('COUNT(users.id) as count')) + ->whereNull('users.deleted_at') + ->groupBy('roles.id', 'roles.name') + ->get(); + + return $usersByRole->map(function ($item) { + return [ + 'role' => $item->role_name, + 'count' => $item->count, + ]; + })->toArray(); + } + + /** + * Get downloads per day for the last 7 days + * + * @return array + */ + public function getDownloadsPerDay(int $days = 7): array + { + $startDate = Carbon::now()->subDays($days - 1)->startOfDay(); + + $downloads = DB::table('user_downloads') + ->select(DB::raw('DATE(timestamp) as date'), DB::raw('COUNT(*) as count')) + ->where('timestamp', '>=', $startDate) + ->groupBy(DB::raw('DATE(timestamp)')) + ->orderBy('date', 'asc') + ->get(); + + // Fill in missing days with zero counts + $result = []; + for ($i = $days - 1; $i >= 0; $i--) { + $date = Carbon::now()->subDays($i)->format('Y-m-d'); + $found = $downloads->firstWhere('date', $date); + $result[] = [ + 'date' => Carbon::parse($date)->format('M d'), + 'count' => $found ? $found->count : 0, + ]; + } + + return $result; + } + + /** + * Get API hits per day for the last 7 days + * Note: This tracks users' last API access date + * + * @return array + */ + public function getApiHitsPerDay(int $days = 7): array + { + $startDate = Carbon::now()->subDays($days - 1)->startOfDay(); + + // Track API access by counting apiaccess field updates + $apiHits = DB::table('users') + ->select(DB::raw('DATE(apiaccess) as date'), DB::raw('COUNT(*) as count')) + ->where('apiaccess', '>=', $startDate) + ->whereNotNull('apiaccess') + ->groupBy(DB::raw('DATE(apiaccess)')) + ->orderBy('date', 'asc') + ->get(); + + // Fill in missing days with zero counts + $result = []; + for ($i = $days - 1; $i >= 0; $i--) { + $date = Carbon::now()->subDays($i)->format('Y-m-d'); + $found = $apiHits->firstWhere('date', $date); + $result[] = [ + 'date' => Carbon::parse($date)->format('M d'), + 'count' => $found ? $found->count : 0, + ]; + } + + return $result; + } + + /** + * Get summary statistics + * + * @return array + */ + public function getSummaryStats(): array + { + $today = Carbon::now()->startOfDay(); + + return [ + 'total_users' => User::whereNull('deleted_at')->count(), + 'downloads_today' => UserDownload::where('timestamp', '>=', $today)->count(), + 'downloads_week' => UserDownload::where('timestamp', '>=', Carbon::now()->subDays(7))->count(), + 'api_hits_today' => User::whereDate('apiaccess', '=', $today)->count(), + 'api_hits_week' => User::where('apiaccess', '>=', Carbon::now()->subDays(7))->count(), + ]; + } + + /** + * Get top downloaders + * + * @param int $limit + * @return array + */ + public function getTopDownloaders(int $limit = 5): array + { + $weekAgo = Carbon::now()->subDays(7); + + return DB::table('user_downloads') + ->join('users', 'user_downloads.users_id', '=', 'users.id') + ->select('users.username', DB::raw('COUNT(*) as download_count')) + ->where('user_downloads.timestamp', '>=', $weekAgo) + ->groupBy('users.id', 'users.username') + ->orderByDesc('download_count') + ->limit($limit) + ->get() + ->toArray(); + } +} + diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 301069180..b2926b053 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -83,6 +83,160 @@ + + @if(isset($userStats)) +
+

+ + User Statistics +

+ + +
+
+

Total Users

+

{{ number_format($userStats['summary']['total_users']) }}

+
+
+

Downloads Today

+

{{ number_format($userStats['summary']['downloads_today']) }}

+
+
+

Downloads (7d)

+

{{ number_format($userStats['summary']['downloads_week']) }}

+
+
+

API Hits Today

+

{{ number_format($userStats['summary']['api_hits_today']) }}

+
+
+

API Hits (7d)

+

{{ number_format($userStats['summary']['api_hits_week']) }}

+
+
+ + +
+ +
+

+ + Users by Role +

+
+ + + + + + + + + + @php + $totalUsers = collect($userStats['users_by_role'])->sum('count'); + @endphp + @foreach($userStats['users_by_role'] as $roleData) + + + + + + @endforeach + @if(empty($userStats['users_by_role'])) + + + + @endif + +
RoleCountPercentage
+ + {{ ucfirst($roleData['role']) }} + + + {{ number_format($roleData['count']) }} + + {{ $totalUsers > 0 ? number_format(($roleData['count'] / $totalUsers) * 100, 1) : 0 }}% +
+ No data available +
+
+
+ + +
+

+ + Top Downloaders (Last 7 Days) +

+
+ + + + + + + + + + @foreach($userStats['top_downloaders'] as $index => $downloader) + + + + + + @endforeach + @if(empty($userStats['top_downloaders'])) + + + + @endif + +
RankUsernameDownloads
+ @if($index === 0) + + @elseif($index === 1) + + @elseif($index === 2) + + @else + {{ $index + 1 }} + @endif + + {{ $downloader->username }} + + {{ number_format($downloader->download_count) }} +
+ No downloads in the last 7 days +
+
+
+ + +
+

+ + Downloads (Last 7 Days) +

+
+ +
+
+ + +
+

+ + API Hits (Last 7 Days) +

+
+ +
+
+
+
+ @endif +
@@ -162,3 +316,151 @@
@endsection +@push('scripts') +@if(isset($userStats)) + + +@endif +@endpush