Files
newznab-tmux-NNTmux/app/Http/Controllers/Admin/AdminPageController.php
T
2026-02-11 14:02:26 +01:00

536 lines
19 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\BasePageController;
use App\Services\SystemMetricsService;
use App\Services\UserStatsService;
use Illuminate\Support\Facades\Log;
class AdminPageController extends BasePageController
{
protected UserStatsService $userStatsService;
protected SystemMetricsService $systemMetricsService;
public function __construct(UserStatsService $userStatsService, SystemMetricsService $systemMetricsService)
{
parent::__construct();
$this->userStatsService = $userStatsService;
$this->systemMetricsService = $systemMetricsService;
}
/**
* @throws \Exception
*/
public function index(): mixed
{
$this->setAdminPrefs();
// Cache user statistics for 2 minutes
$userStats = \Illuminate\Support\Facades\Cache::remember('admin_user_stats', 120, function () {
return [
'users_by_role' => $this->userStatsService->getUsersByRole(),
'downloads_per_hour' => $this->userStatsService->getDownloadsPerHour(168), // Last 7 days in hours
'downloads_per_minute' => $this->userStatsService->getDownloadsPerMinute(60),
'api_hits_per_hour' => $this->userStatsService->getApiHitsPerHour(168), // Last 7 days in hours
'api_hits_per_minute' => $this->userStatsService->getApiHitsPerMinute(60),
'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(),
'systemMetrics' => $this->getSystemMetrics(),
'recent_activity' => $this->getRecentUserActivity(),
]));
}
/**
* Get recent user activity from the user_activities table with caching
*
* @return array<string, mixed>
*/
protected function getRecentUserActivity(): array
{
return \Illuminate\Support\Facades\Cache::remember('admin_recent_activity', 60, function () {
$activities = \App\Models\UserActivity::orderBy('created_at', 'desc')
->limit(10)
->get();
return $activities->map(function ($activity) {
return (object) [
'type' => $activity->activity_type,
'message' => $activity->description,
'icon' => $activity->icon,
'icon_bg' => 'bg-'.$activity->color_class.'-100 dark:bg-'.$activity->color_class.'-900',
'icon_color' => 'text-'.$activity->color_class.'-600 dark:text-'.$activity->color_class.'-400',
'created_at' => $activity->created_at,
'username' => $activity->username,
'metadata' => $activity->metadata,
];
})->toArray();
});
}
/**
* API endpoint to get recent user activity (for auto-refresh)
*/
public function getRecentActivity(): mixed
{
$activities = $this->getRecentUserActivity();
return response()->json([
'success' => true,
'activities' => array_map(function ($activity) {
return [
'type' => $activity->type,
'message' => $activity->message,
'icon' => $activity->icon,
'icon_bg' => $activity->icon_bg,
'icon_color' => $activity->icon_color,
'created_at' => $activity->created_at->diffForHumans(),
'username' => $activity->username,
'metadata' => $activity->metadata,
];
}, $activities),
]);
}
/**
* Get default dashboard statistics with caching for expensive queries
*
* @return array<string, mixed>
*/
protected function getDefaultStats(): array
{
$today = now()->format('Y-m-d');
// Cache total releases count for 5 minutes (expensive query on large tables)
$releasesCount = \Illuminate\Support\Facades\Cache::remember('admin_stats_releases_count', 300, function () {
return \App\Models\Release::count();
});
// Cache users count for 5 minutes
$usersCount = \Illuminate\Support\Facades\Cache::remember('admin_stats_users_count', 300, function () {
return \App\Models\User::whereNull('deleted_at')->count();
});
// Cache groups count for 10 minutes (rarely changes)
$groupsCount = \Illuminate\Support\Facades\Cache::remember('admin_stats_groups_count', 600, function () {
return \App\Models\UsenetGroup::count();
});
// Cache active groups count for 10 minutes
$activeGroupsCount = \Illuminate\Support\Facades\Cache::remember('admin_stats_active_groups_count', 600, function () {
return \App\Models\UsenetGroup::where('active', 1)->count();
});
// Cache failed count for 5 minutes
$failedCount = \Illuminate\Support\Facades\Cache::remember('admin_stats_failed_count', 300, function () {
return \App\Models\DnzbFailure::count();
});
// Cache reported releases count for 5 minutes
$reportedCount = \Illuminate\Support\Facades\Cache::remember('admin_stats_reported_count', 300, function () {
return \App\Models\ReleaseReport::where('status', 'pending')->count();
});
// Today's counts can be cached for 1 minute since they change frequently
$releasesToday = \Illuminate\Support\Facades\Cache::remember('admin_stats_releases_today_'.$today, 60, function () use ($today) {
return \App\Models\Release::whereRaw('DATE(adddate) = ?', [$today])->count();
});
$usersToday = \Illuminate\Support\Facades\Cache::remember('admin_stats_users_today_'.$today, 60, function () use ($today) {
return \App\Models\User::whereRaw('DATE(created_at) = ?', [$today])->count();
});
return [
'releases' => $releasesCount,
'releases_today' => $releasesToday,
'users' => $usersCount,
'users_today' => $usersToday,
'groups' => $groupsCount,
'active_groups' => $activeGroupsCount,
'failed' => $failedCount,
'reported' => $reportedCount,
'disk_free' => $this->getDiskSpace(),
];
}
/**
* Get disk space information
*/
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';
}
}
/**
* Get system metrics (CPU and RAM usage) with caching
*
* @return array<string, mixed>
*/
protected function getSystemMetrics(): array
{
// Cache CPU info for 1 hour (doesn't change)
$cpuInfo = \Illuminate\Support\Facades\Cache::remember('admin_cpu_info', 3600, function () {
return $this->getCpuInfo();
});
// Cache current metrics for 30 seconds
$cpuUsage = \Illuminate\Support\Facades\Cache::remember('admin_cpu_usage', 30, function () {
return $this->getCpuUsage();
});
$ramUsage = \Illuminate\Support\Facades\Cache::remember('admin_ram_usage', 30, function () {
return $this->getRamUsage();
});
$loadAverage = \Illuminate\Support\Facades\Cache::remember('admin_load_average', 30, function () {
return $this->getLoadAverage();
});
// Cache historical data for 5 minutes (from database)
$cpuHistory24h = \Illuminate\Support\Facades\Cache::remember('admin_cpu_history_24h', 300, function () {
return $this->systemMetricsService->getHourlyMetrics('cpu', 24);
});
$cpuHistory30d = \Illuminate\Support\Facades\Cache::remember('admin_cpu_history_30d', 300, function () {
return $this->systemMetricsService->getDailyMetrics('cpu', 30);
});
$ramHistory24h = \Illuminate\Support\Facades\Cache::remember('admin_ram_history_24h', 300, function () {
return $this->systemMetricsService->getHourlyMetrics('ram', 24);
});
$ramHistory30d = \Illuminate\Support\Facades\Cache::remember('admin_ram_history_30d', 300, function () {
return $this->systemMetricsService->getDailyMetrics('ram', 30);
});
return [
'cpu' => [
'current' => $cpuUsage,
'label' => 'CPU Usage',
'history_24h' => $cpuHistory24h,
'history_30d' => $cpuHistory30d,
'cores' => $cpuInfo['cores'],
'threads' => $cpuInfo['threads'],
'model' => $cpuInfo['model'],
'load_average' => $loadAverage,
],
'ram' => [
'used' => $ramUsage['used'],
'total' => $ramUsage['total'],
'percentage' => $ramUsage['percentage'],
'label' => 'RAM Usage',
'history_24h' => $ramHistory24h,
'history_30d' => $ramHistory30d,
],
];
}
/**
* Get current CPU usage percentage
*/
protected function getCpuUsage(): float
{
try {
if (PHP_OS_FAMILY === 'Windows') {
// Windows command
$output = shell_exec('wmic cpu get loadpercentage');
if ($output) {
preg_match('/\d+/', $output, $matches);
return $matches[0] ?? 0;
}
} else {
// Linux command - get load average and convert to percentage
$load = sys_getloadavg();
if ($load !== false) {
$cpuCount = $this->getCpuCount();
return round(($load[0] / $cpuCount) * 100, 2);
}
}
} catch (\Exception $e) {
Log::warning('Could not get CPU usage: '.$e->getMessage());
}
return 0;
}
/**
* Get number of CPU cores
*/
protected function getCpuCount(): int
{
try {
if (PHP_OS_FAMILY === 'Windows') {
$output = shell_exec('wmic cpu get NumberOfLogicalProcessors');
if ($output) {
preg_match('/\d+/', $output, $matches);
return (int) ($matches[0] ?? 1);
}
} else {
$cpuinfo = file_get_contents('/proc/cpuinfo');
preg_match_all('/^processor/m', $cpuinfo, $matches);
return count($matches[0]) ?: 1;
}
} catch (\Exception $e) {
return 1;
}
return 1;
}
/**
* Get detailed CPU information (cores, threads, model)
*
* @return array<string, mixed>
*/
protected function getCpuInfo(): array
{
$info = [
'cores' => 0,
'threads' => 0,
'model' => 'Unknown',
];
try {
if (PHP_OS_FAMILY === 'Windows') {
// Get number of cores
$coresOutput = shell_exec('wmic cpu get NumberOfCores');
if ($coresOutput) {
preg_match('/\d+/', $coresOutput, $matches);
$info['cores'] = (int) ($matches[0] ?? 0);
}
// Get number of logical processors (threads)
$threadsOutput = shell_exec('wmic cpu get NumberOfLogicalProcessors');
if ($threadsOutput) {
preg_match('/\d+/', $threadsOutput, $matches);
$info['threads'] = (int) ($matches[0] ?? 0);
}
// Get CPU model
$modelOutput = shell_exec('wmic cpu get Name');
if ($modelOutput) {
$lines = explode("\n", trim($modelOutput));
if (isset($lines[1])) {
$info['model'] = trim($lines[1]);
}
}
} else {
// Linux
$cpuinfo = file_get_contents('/proc/cpuinfo');
// Get number of physical cores
preg_match_all('/^cpu cores\s*:\s*(\d+)/m', $cpuinfo, $coresMatches);
if (! empty($coresMatches[1])) {
$info['cores'] = (int) $coresMatches[1][0];
}
// Get number of logical processors (threads)
preg_match_all('/^processor/m', $cpuinfo, $processorMatches);
$info['threads'] = count($processorMatches[0]) ?: 0;
// Get CPU model
preg_match('/^model name\s*:\s*(.+)$/m', $cpuinfo, $modelMatches);
if (! empty($modelMatches[1])) {
$info['model'] = trim($modelMatches[1]);
}
// If cores is 0, try to get from physical id count
if ($info['cores'] === 0) {
preg_match_all('/^physical id\s*:\s*(\d+)/m', $cpuinfo, $physicalMatches);
$uniquePhysical = ! empty($physicalMatches[1]) ? count(array_unique($physicalMatches[1])) : 1;
$info['cores'] = (int) ($info['threads'] / $uniquePhysical);
}
}
} catch (\Exception $e) {
Log::warning('Could not get CPU info: '.$e->getMessage());
}
return $info;
}
/**
* Get system load average
*
* @return array<string, mixed>
*/
protected function getLoadAverage(): array
{
$loadAvg = [
'1min' => 0,
'5min' => 0,
'15min' => 0,
];
try {
if (PHP_OS_FAMILY === 'Windows') {
// Windows doesn't have load average, use CPU queue length instead
$output = shell_exec('wmic path Win32_PerfFormattedData_PerfOS_System get ProcessorQueueLength');
if ($output) {
preg_match('/\d+/', $output, $matches);
$queueLength = (int) ($matches[0] ?? 0);
// Approximate load average
$loadAvg['1min'] = round($queueLength / 2, 2);
$loadAvg['5min'] = round($queueLength / 2, 2);
$loadAvg['15min'] = round($queueLength / 2, 2);
}
} else {
// Linux has native load average
$load = sys_getloadavg();
if ($load !== false) {
$loadAvg['1min'] = round($load[0], 2);
$loadAvg['5min'] = round($load[1], 2);
$loadAvg['15min'] = round($load[2], 2);
}
}
} catch (\Exception $e) {
Log::warning('Could not get load average: '.$e->getMessage());
}
return $loadAvg;
}
/**
* Get RAM usage information
*
* @return array<string, mixed>
*/
protected function getRamUsage(): array
{
try {
if (PHP_OS_FAMILY === 'Windows') {
// Windows command
$output = shell_exec('wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /Value');
if ($output) {
preg_match('/FreePhysicalMemory=(\d+)/', $output, $free);
preg_match('/TotalVisibleMemorySize=(\d+)/', $output, $total);
if (isset($free[1]) && isset($total[1])) {
$freeKb = (float) $free[1];
$totalKb = (float) $total[1];
$usedKb = $totalKb - $freeKb;
return [
'used' => round($usedKb / 1024 / 1024, 2),
'total' => round($totalKb / 1024 / 1024, 2),
'percentage' => round(($usedKb / $totalKb) * 100, 2),
];
}
}
} else {
// Linux command
$meminfo = file_get_contents('/proc/meminfo');
preg_match('/MemTotal:\s+(\d+)/', $meminfo, $total);
preg_match('/MemAvailable:\s+(\d+)/', $meminfo, $available);
if (isset($total[1]) && isset($available[1])) {
$totalKb = (float) $total[1];
$availableKb = (float) $available[1];
$usedKb = $totalKb - $availableKb;
return [
'used' => round($usedKb / 1024 / 1024, 2),
'total' => round($totalKb / 1024 / 1024, 2),
'percentage' => round(($usedKb / $totalKb) * 100, 2),
];
}
}
} catch (\Exception $e) {
Log::warning('Could not get RAM usage: '.$e->getMessage());
}
return [
'used' => 0,
'total' => 0,
'percentage' => 0,
];
}
/**
* Get minute-to-minute user activity data (API endpoint)
*/
public function getUserActivityMinutes(): mixed
{
$downloadsPerMinute = $this->userStatsService->getDownloadsPerMinute(60);
$apiHitsPerMinute = $this->userStatsService->getApiHitsPerMinute(60);
return response()->json([
'downloads' => $downloadsPerMinute,
'api_hits' => $apiHitsPerMinute,
]);
}
/**
* Get current system metrics (API endpoint)
*/
public function getCurrentMetrics(): mixed
{
$cpuUsage = $this->getCpuUsage();
$ramUsage = $this->getRamUsage();
$cpuInfo = $this->getCpuInfo();
$loadAverage = $this->getLoadAverage();
return response()->json([
'success' => true,
'cpu' => [
'current' => $cpuUsage,
'cores' => $cpuInfo['cores'],
'threads' => $cpuInfo['threads'],
'model' => $cpuInfo['model'],
'load_average' => $loadAverage,
],
'ram' => [
'used' => $ramUsage['used'],
'total' => $ramUsage['total'],
'percentage' => $ramUsage['percentage'],
],
]);
}
/**
* Get historical system metrics (API endpoint)
*/
public function getHistoricalMetrics(): mixed
{
$timeRange = request('range', '24h'); // 24h or 30d
if ($timeRange === '30d') {
$cpuHistory = $this->systemMetricsService->getDailyMetrics('cpu', 30);
$ramHistory = $this->systemMetricsService->getDailyMetrics('ram', 30);
} else {
$cpuHistory = $this->systemMetricsService->getHourlyMetrics('cpu', 24);
$ramHistory = $this->systemMetricsService->getHourlyMetrics('ram', 24);
}
return response()->json([
'success' => true,
'cpu_history' => $cpuHistory,
'ram_history' => $ramHistory,
'range' => $timeRange,
]);
}
}