diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 1dd998646..5b8505970 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,6 +2,7 @@ namespace App\Console; +use App\Jobs\NodeStatistics; use App\Models\ActivityLog; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Database\Console\PruneCommand; @@ -32,6 +33,8 @@ class Kernel extends ConsoleKernel $schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping(); $schedule->command(CleanServiceBackupFilesCommand::class)->daily(); + $schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping(); + if (config('backups.prune_age')) { // Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted. $schedule->command(PruneOrphanedBackupsCommand::class)->everyThirtyMinutes(); diff --git a/app/Filament/Resources/NodeResource/Pages/EditNode.php b/app/Filament/Resources/NodeResource/Pages/EditNode.php index 94ea3a8a4..c6fd0e451 100644 --- a/app/Filament/Resources/NodeResource/Pages/EditNode.php +++ b/app/Filament/Resources/NodeResource/Pages/EditNode.php @@ -3,12 +3,11 @@ namespace App\Filament\Resources\NodeResource\Pages; use App\Filament\Resources\NodeResource; -use App\Filament\Resources\NodeResource\Widgets\NodeMemoryChart; -use App\Filament\Resources\NodeResource\Widgets\NodeStorageChart; use App\Models\Node; use App\Services\Nodes\NodeUpdateService; use Filament\Actions; use Filament\Forms; +use Filament\Forms\Components\Fieldset; use Filament\Forms\Components\Grid; use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Tabs; @@ -17,6 +16,7 @@ use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\ToggleButtons; +use Filament\Forms\Components\View; use Filament\Forms\Get; use Filament\Forms\Set; use Filament\Notifications\Notification; @@ -41,6 +41,32 @@ class EditNode extends EditRecord ->persistTabInQueryString() ->columnSpanFull() ->tabs([ + Tab::make('') + ->label('Overview') + ->icon('tabler-chart-area-line-filled') + ->columns(6) + ->schema([ + Fieldset::make() + ->label('Node Information') + ->columns(4) + ->schema([ + Placeholder::make('') + ->label('Wings Version') + ->content(fn (Node $node) => $node->systemInformation()['version']), + Placeholder::make('') + ->label('CPU Threads') + ->content(fn (Node $node) => $node->systemInformation()['cpu_count']), + Placeholder::make('') + ->label('Architecture') + ->content(fn (Node $node) => $node->systemInformation()['architecture']), + Placeholder::make('') + ->label('Kernel') + ->content(fn (Node $node) => $node->systemInformation()['kernel_version']), + ]), + View::make('filament.components.node-cpu-chart')->columnSpan(3), + View::make('filament.components.node-memory-chart')->columnSpan(3), + // TODO: Make purdy View::make('filament.components.node-storage-chart')->columnSpan(3), + ]), Tab::make('Basic Settings') ->icon('tabler-server') ->schema([ @@ -437,16 +463,17 @@ class EditNode extends EditRecord ]; } - protected function getFooterWidgets(): array - { - return [ - NodeStorageChart::class, - NodeMemoryChart::class, - ]; - } - protected function afterSave(): void { $this->fillForm(); } + + protected function getColumnSpan() + { + return null; + } + protected function getColumnStart() + { + return null; + } } diff --git a/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php b/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php new file mode 100644 index 000000000..ecc6bc592 --- /dev/null +++ b/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php @@ -0,0 +1,81 @@ +record; + $threads = $node->systemInformation()['cpu_count']; + + $cpu = collect(cache()->get("nodes.$node->id.cpu_percent")) + ->slice(-10) + ->map(fn ($value, $key) => [ + 'cpu' => number_format($value * $threads, 2), + 'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'), + ]) + ->all(); + + return [ + 'datasets' => [ + [ + 'data' => array_column($cpu, 'cpu'), + 'backgroundColor' => [ + 'rgba(96, 165, 250, 0.3)', + ], + 'tension' => '0.3', + 'fill' => true, + ], + ], + 'labels' => array_column($cpu, 'timestamp'), + ]; + } + + protected function getType(): string + { + return 'line'; + } + + protected function getOptions(): RawJs + { + return RawJs::make(<<<'JS' + { + scales: { + y: { + min: 0, + }, + }, + plugins: { + legend: { + display: false, + } + } + } + JS); + } + + public function getHeading(): string + { + /** @var Node $node */ + $node = $this->record; + $threads = $node->systemInformation()['cpu_count']; + + $cpu = number_format(collect(cache()->get("nodes.$node->id.cpu_percent"))->last() * $threads, 2); + $max = number_format($threads * 100) . '%'; + + return 'CPU - ' . $cpu . '% Of ' . $max; + } +} diff --git a/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php b/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php index 8ed87046a..3d8762447 100644 --- a/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php +++ b/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php @@ -3,66 +3,83 @@ namespace App\Filament\Resources\NodeResource\Widgets; use App\Models\Node; +use Carbon\Carbon; +use Filament\Support\RawJs; use Filament\Widgets\ChartWidget; use Illuminate\Database\Eloquent\Model; class NodeMemoryChart extends ChartWidget { - protected static ?string $heading = 'Memory'; - - protected static ?string $pollingInterval = '60s'; + protected static ?string $pollingInterval = '5s'; + protected static ?string $maxHeight = '300px'; public ?Model $record = null; - protected static ?array $options = [ - 'scales' => [ - 'x' => [ - 'grid' => [ - 'display' => false, - ], - 'ticks' => [ - 'display' => false, - ], - ], - 'y' => [ - 'grid' => [ - 'display' => false, - ], - 'ticks' => [ - 'display' => false, - ], - ], - ], - ]; - protected function getData(): array { /** @var Node $node */ $node = $this->record; - $total = ($node->statistics()['memory_total'] ?? 0) / 1024 / 1024 / 1024; - $used = ($node->statistics()['memory_used'] ?? 0) / 1024 / 1024 / 1024; - $unused = $total - $used; + $memUsed = collect(cache()->get("nodes.$node->id.memory_used"))->slice(-10) + ->map(fn ($value, $key) => [ + 'memory' => config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, + 'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'), + ]) + ->all(); return [ 'datasets' => [ [ - 'label' => 'Data Cool', - 'data' => [$used, $unused], + 'data' => array_column($memUsed, 'memory'), 'backgroundColor' => [ - 'rgb(255, 99, 132)', - 'rgb(54, 162, 235)', - 'rgb(255, 205, 86)', + 'rgba(96, 165, 250, 0.3)', ], + 'tension' => '0.3', + 'fill' => true, ], - // 'backgroundColor' => [], ], - 'labels' => ['Used', 'Unused'], + 'labels' => array_column($memUsed, 'timestamp'), ]; } protected function getType(): string { - return 'pie'; + return 'line'; + } + + protected function getOptions(): RawJs + { + return RawJs::make(<<<'JS' + { + scales: { + y: { + min: 0, + }, + }, + plugins: { + legend: { + display: false, + } + } + } + JS); + } + + public function getHeading(): string + { + /** @var Node $node */ + $node = $this->record; + $latestMemoryUsed = collect(cache()->get("nodes.$node->id.memory_used"))->last(); + $totalMemory = collect(cache()->get("nodes.$node->id.memory_total"))->last(); + + $used = config('panel.use_binary_prefix') + ? number_format($latestMemoryUsed / 1024 / 1024 / 1024, 2) .' GiB' + : number_format($latestMemoryUsed / 1000 / 1000 / 1000, 2) . ' GB'; + + $total = config('panel.use_binary_prefix') + ? number_format($totalMemory / 1024 / 1024 / 1024, 2) .' GiB' + : number_format($totalMemory / 1000 / 1000 / 1000, 2) . ' GB'; + + return 'Memory - ' . $used . ' Of ' . $total; } } diff --git a/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php b/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php index bcfbfcf4f..b841d84ef 100644 --- a/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php +++ b/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php @@ -9,8 +9,8 @@ use Illuminate\Database\Eloquent\Model; class NodeStorageChart extends ChartWidget { protected static ?string $heading = 'Storage'; - protected static ?string $pollingInterval = '60s'; + protected static ?string $maxHeight = '300px'; public ?Model $record = null; @@ -47,7 +47,6 @@ class NodeStorageChart extends ChartWidget return [ 'datasets' => [ [ - 'label' => 'Data Cool', 'data' => [$used, $unused], 'backgroundColor' => [ 'rgb(255, 99, 132)', @@ -55,7 +54,6 @@ class NodeStorageChart extends ChartWidget 'rgb(255, 205, 86)', ], ], - // 'backgroundColor' => [], ], 'labels' => ['Used', 'Unused'], ]; diff --git a/app/Jobs/NodeStatistics.php b/app/Jobs/NodeStatistics.php new file mode 100644 index 000000000..19fae9b9d --- /dev/null +++ b/app/Jobs/NodeStatistics.php @@ -0,0 +1,46 @@ +statistics(); + $timestamp = now()->getTimestamp(); + + foreach ($stats as $key => $value) { + $cacheKey = "nodes.{$node->id}.$key"; + $data = cache()->get($cacheKey, []); + + // Add current timestamp and value to the data array + $data[$timestamp] = $value; + + // Update the cache with the new data, expires in 1 minute + cache()->put($cacheKey, $data, now()->addMinute()); + } + } + } + +} diff --git a/resources/views/filament/components/node-cpu-chart.blade.php b/resources/views/filament/components/node-cpu-chart.blade.php new file mode 100644 index 000000000..d2627c42d --- /dev/null +++ b/resources/views/filament/components/node-cpu-chart.blade.php @@ -0,0 +1,3 @@ + + @livewire(\App\Filament\Resources\NodeResource\Widgets\NodeCpuChart::class, ['record'=> $getRecord()]) + diff --git a/resources/views/filament/components/node-memory-chart.blade.php b/resources/views/filament/components/node-memory-chart.blade.php new file mode 100644 index 000000000..cb934d007 --- /dev/null +++ b/resources/views/filament/components/node-memory-chart.blade.php @@ -0,0 +1,3 @@ + + @livewire(\App\Filament\Resources\NodeResource\Widgets\NodeMemoryChart::class, ['record'=> $getRecord()]) + diff --git a/resources/views/filament/components/node-storage-chart.blade.php b/resources/views/filament/components/node-storage-chart.blade.php new file mode 100644 index 000000000..ea7b5358c --- /dev/null +++ b/resources/views/filament/components/node-storage-chart.blade.php @@ -0,0 +1,3 @@ + + @livewire(\App\Filament\Resources\NodeResource\Widgets\NodeStorageChart::class, ['record'=> $getRecord()]) +