mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-02 14:36:17 +00:00
2.x - firewall/metrics/services/cronjobs
This commit is contained in:
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use Filament\Actions\Action;
|
||||
|
||||
class Create extends Page
|
||||
{
|
||||
protected static ?string $slug = 'servers/create';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Create Server';
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('create', Server::class) ?? false;
|
||||
}
|
||||
|
||||
protected function getExtraAttributes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\CreateServer::class],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/create-server.html')
|
||||
->openUrlInNewTab(),
|
||||
];
|
||||
}
|
||||
}
|
96
app/Web/Pages/Servers/CronJobs/Index.php
Normal file
96
app/Web/Pages/Servers/CronJobs/Index.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\CronJobs;
|
||||
|
||||
use App\Actions\CronJob\CreateCronJob;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/cronjobs';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Cron Jobs';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [CronJob::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\CronJobsList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/cronjobs.html')
|
||||
->openUrlInNewTab(),
|
||||
Action::make('create')
|
||||
->authorize(fn () => auth()->user()?->can('create', [CronJob::class, $this->server]))
|
||||
->icon('heroicon-o-plus')
|
||||
->modalWidth(MaxWidth::ExtraLarge)
|
||||
->form([
|
||||
TextInput::make('command')
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['command'])
|
||||
->helperText(fn () => view('web.components.link', [
|
||||
'href' => 'https://vitodeploy.com/servers/cronjobs.html',
|
||||
'external' => true,
|
||||
'text' => 'How the command should look like?',
|
||||
])),
|
||||
Select::make('user')
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['user'])
|
||||
->options([
|
||||
'vito' => 'vito',
|
||||
'root' => 'root',
|
||||
]),
|
||||
Select::make('frequency')
|
||||
->options(config('core.cronjob_intervals'))
|
||||
->reactive()
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['frequency']),
|
||||
TextInput::make('custom')
|
||||
->label('Custom Frequency (Cron)')
|
||||
->rules(fn (callable $get) => CreateCronJob::rules($get())['custom'])
|
||||
->visible(fn (callable $get) => $get('frequency') === 'custom')
|
||||
->placeholder('0 * * * *'),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
try {
|
||||
app(CreateCronJob::class)->create($this->server, $data);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
67
app/Web/Pages/Servers/CronJobs/Widgets/CronJobsList.php
Normal file
67
app/Web/Pages/Servers/CronJobs/Widgets/CronJobsList.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\CronJobs\Widgets;
|
||||
|
||||
use App\Actions\CronJob\DeleteCronJob;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class CronJobsList extends Widget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return CronJob::query()->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('command')
|
||||
->limit(40)
|
||||
->tooltip(fn (CronJob $cronJob) => $cronJob->command)
|
||||
->searchable()
|
||||
->copyable(),
|
||||
TextColumn::make('created_at')
|
||||
->formatStateUsing(fn (CronJob $cronJob) => $cronJob->created_at_by_timezone)
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
->color('danger')
|
||||
->hiddenLabel()
|
||||
->requiresConfirmation()
|
||||
->authorize(fn (CronJob $record) => auth()->user()->can('delete', $record))
|
||||
->action(function (CronJob $record) {
|
||||
try {
|
||||
app(DeleteCronJob::class)->delete($this->server, $record);
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ public function getTable(): Table
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-rectangle-stack')
|
||||
->modalHeading('Backup Files')
|
||||
->color('secondary')
|
||||
->color('gray')
|
||||
->tooltip('Show backup files')
|
||||
->authorize(fn (Backup $record) => auth()->user()->can('viewAny', [BackupFile::class, $record]))
|
||||
->modalContent(fn (Backup $record) => view('web.components.dynamic-widget', [
|
||||
|
@ -65,7 +65,7 @@ private function passwordAction(): Action
|
||||
return Action::make('password')
|
||||
->hiddenLabel()
|
||||
->icon('heroicon-o-key')
|
||||
->color('secondary')
|
||||
->color('gray')
|
||||
->modalHeading('Database user\'s password')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->tooltip('Show the password')
|
||||
|
95
app/Web/Pages/Servers/Firewall/Index.php
Normal file
95
app/Web/Pages/Servers/Firewall/Index.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall;
|
||||
|
||||
use App\Actions\FirewallRule\CreateRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/firewall';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Firewall';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [FirewallRule::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\RulesList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/firewall.html')
|
||||
->openUrlInNewTab(),
|
||||
Action::make('create')
|
||||
->authorize(fn () => auth()->user()?->can('create', [FirewallRule::class, $this->server]))
|
||||
->label('Create a Rule')
|
||||
->icon('heroicon-o-plus')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->form([
|
||||
Select::make('type')
|
||||
->native(false)
|
||||
->options([
|
||||
'allow' => 'Allow',
|
||||
'deny' => 'Deny',
|
||||
])
|
||||
->rules(CreateRule::rules()['type']),
|
||||
Select::make('protocol')
|
||||
->native(false)
|
||||
->options([
|
||||
'tcp' => 'TCP',
|
||||
'udp' => 'UDP',
|
||||
])
|
||||
->rules(CreateRule::rules()['protocol']),
|
||||
TextInput::make('port')
|
||||
->rules(CreateRule::rules()['port']),
|
||||
TextInput::make('source')
|
||||
->rules(CreateRule::rules()['source']),
|
||||
TextInput::make('mask')
|
||||
->rules(CreateRule::rules()['mask']),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
try {
|
||||
app(CreateRule::class)->create($this->server, $data);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
72
app/Web/Pages/Servers/Firewall/Widgets/RulesList.php
Normal file
72
app/Web/Pages/Servers/Firewall/Widgets/RulesList.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Firewall\Widgets;
|
||||
|
||||
use App\Actions\FirewallRule\DeleteRule;
|
||||
use App\Models\FirewallRule;
|
||||
use App\Models\Server;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class RulesList extends Widget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return FirewallRule::query()->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('type')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase'])
|
||||
->color(fn (FirewallRule $record) => $record->type === 'allow' ? 'green' : 'red'),
|
||||
TextColumn::make('protocol')
|
||||
->sortable()
|
||||
->extraAttributes(['class' => 'uppercase']),
|
||||
TextColumn::make('port')
|
||||
->sortable(),
|
||||
TextColumn::make('source')
|
||||
->sortable(),
|
||||
TextColumn::make('mask')
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
->color('danger')
|
||||
->hiddenLabel()
|
||||
->requiresConfirmation()
|
||||
->authorize(fn (FirewallRule $record) => auth()->user()->can('delete', $record))
|
||||
->action(function (FirewallRule $record) {
|
||||
try {
|
||||
app(DeleteRule::class)->delete($this->server, $record);
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
@ -52,6 +52,12 @@ protected function getHeaderActions(): array
|
||||
]);
|
||||
|
||||
return [
|
||||
\Filament\Actions\Action::make('read-the-docs')
|
||||
->label('Read the Docs')
|
||||
->icon('heroicon-o-document-text')
|
||||
->color('gray')
|
||||
->url('https://vitodeploy.com/servers/create-server.html')
|
||||
->openUrlInNewTab(),
|
||||
\Filament\Actions\Action::make('create')
|
||||
->label('Create a Server')
|
||||
->icon('heroicon-o-plus')
|
||||
@ -96,6 +102,8 @@ protected function getHeaderActions(): array
|
||||
)
|
||||
->placeholder('Select profile')
|
||||
->native(false)
|
||||
->live()
|
||||
->reactive()
|
||||
->selectablePlaceholder(false)
|
||||
->visible(fn ($get) => $get('provider') !== ServerProvider::CUSTOM),
|
||||
Grid::make()
|
||||
@ -110,7 +118,7 @@ protected function getHeaderActions(): array
|
||||
return [];
|
||||
}
|
||||
|
||||
return \App\Models\ServerProvider::regions($get('serer_provider'));
|
||||
return \App\Models\ServerProvider::regions($get('server_provider'));
|
||||
})
|
||||
->loadingMessage('Loading regions...')
|
||||
->disabled(fn ($get) => ! $get('server_provider'))
|
||||
@ -219,6 +227,7 @@ protected function getHeaderActions(): array
|
||||
),
|
||||
]),
|
||||
])
|
||||
->modalSubmitActionLabel('Create')
|
||||
->action(function ($input) {
|
||||
$this->authorize('create', Server::class);
|
||||
|
||||
|
@ -86,7 +86,7 @@ public function getTable(): Table
|
||||
Action::make('download')
|
||||
->hiddenLabel()
|
||||
->tooltip('Download')
|
||||
->color('secondary')
|
||||
->color('gray')
|
||||
->icon('heroicon-o-archive-box-arrow-down')
|
||||
->authorize(fn ($record) => auth()->user()->can('view', $record))
|
||||
->action(fn (ServerLog $record) => $record->download()),
|
||||
|
39
app/Web/Pages/Servers/Metrics/Index.php
Normal file
39
app/Web/Pages/Servers/Metrics/Index.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Models\Server;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/metrics';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Metrics';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [Metric::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\FilterForm::class, ['server' => $this->server]],
|
||||
[Widgets\MetricDetails::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
86
app/Web/Pages/Servers/Metrics/Widgets/FilterForm.php
Normal file
86
app/Web/Pages/Servers/Metrics/Widgets/FilterForm.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics\Widgets;
|
||||
|
||||
use App\Actions\Monitoring\GetMetrics;
|
||||
use App\Models\Server;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\ViewField;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Widgets\Widget;
|
||||
|
||||
class FilterForm extends Widget implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static string $view = 'web.components.form';
|
||||
|
||||
public ?array $data = [
|
||||
'period' => '1h',
|
||||
'from' => null,
|
||||
'to' => null,
|
||||
];
|
||||
|
||||
public function updated($name, $value): void
|
||||
{
|
||||
if ($value !== 'custom') {
|
||||
$this->dispatch('updateFilters', filters: $this->data);
|
||||
}
|
||||
|
||||
if ($value === 'custom' && $this->data['from'] && $this->data['to']) {
|
||||
$this->dispatch('updateFilters', filters: $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
public Server $server;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Grid::make()
|
||||
->columns(3)
|
||||
->schema([
|
||||
Select::make('period')
|
||||
->live()
|
||||
->reactive()
|
||||
->options([
|
||||
'10m' => '10 Minutes',
|
||||
'30m' => '30 Minutes',
|
||||
'1h' => '1 Hour',
|
||||
'12h' => '12 Hours',
|
||||
'1d' => '1 Day',
|
||||
'7d' => '7 Days',
|
||||
'custom' => 'Custom',
|
||||
])
|
||||
->rules(fn (Get $get) => GetMetrics::rules($get())['period']),
|
||||
DatePicker::make('from')
|
||||
->reactive()
|
||||
->visible(fn (Get $get) => $get('period') === 'custom')
|
||||
->maxDate(fn (Get $get) => now())
|
||||
->rules(fn (Get $get) => GetMetrics::rules($get())['from']),
|
||||
DatePicker::make('to')
|
||||
->reactive()
|
||||
->visible(fn (Get $get) => $get('period') === 'custom')
|
||||
->minDate(fn (Get $get) => $get('from') ?: now())
|
||||
->maxDate(now())
|
||||
->rules(fn (Get $get) => GetMetrics::rules($get())['to']),
|
||||
]),
|
||||
ViewField::make('data')
|
||||
->reactive()
|
||||
->view('web.components.dynamic-widget', [
|
||||
'widget' => Metrics::class,
|
||||
'params' => [
|
||||
'server' => $this->server,
|
||||
'filters' => $this->data,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
}
|
83
app/Web/Pages/Servers/Metrics/Widgets/MetricDetails.php
Normal file
83
app/Web/Pages/Servers/Metrics/Widgets/MetricDetails.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics\Widgets;
|
||||
|
||||
use App\Models\Metric;
|
||||
use App\Models\Server;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Infolists\Components\Grid;
|
||||
use Filament\Infolists\Components\Section;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Infolists\Concerns\InteractsWithInfolists;
|
||||
use Filament\Infolists\Contracts\HasInfolists;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class MetricDetails extends Widget implements HasForms, HasInfolists
|
||||
{
|
||||
use InteractsWithForms;
|
||||
use InteractsWithInfolists;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static string $view = 'web.components.infolist';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return $infolist
|
||||
->record($this->server->metrics()->latest()->first())
|
||||
->schema([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->heading('Memory')
|
||||
->description('More details on memory')
|
||||
->columnSpan(1)
|
||||
->schema([
|
||||
TextEntry::make('memory_total')
|
||||
->label('Total Memory')
|
||||
->alignRight()
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->memory_total_in_bytes, 2))
|
||||
->inlineLabel(),
|
||||
TextEntry::make('memory_used')
|
||||
->label('Used Memory')
|
||||
->alignRight()
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->memory_used_in_bytes, 2))
|
||||
->inlineLabel(),
|
||||
TextEntry::make('memory_free')
|
||||
->label('Free Memory')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->memory_free_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
]),
|
||||
Section::make()
|
||||
->heading('Disk')
|
||||
->description('More details on disk')
|
||||
->columnSpan(1)
|
||||
->schema([
|
||||
TextEntry::make('disk_total')
|
||||
->label('Total Disk')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->disk_total_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
TextEntry::make('disk_used')
|
||||
->label('Used Disk')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->disk_used_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
TextEntry::make('disk_free')
|
||||
->label('Free Disk')
|
||||
->formatStateUsing(fn (Metric $record) => Number::fileSize($record->disk_free_in_bytes, 2))
|
||||
->alignRight()
|
||||
->inlineLabel(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
48
app/Web/Pages/Servers/Metrics/Widgets/Metrics.php
Normal file
48
app/Web/Pages/Servers/Metrics/Widgets/Metrics.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Metrics\Widgets;
|
||||
|
||||
use App\Actions\Monitoring\GetMetrics;
|
||||
use App\Models\Metric;
|
||||
use App\Models\Server;
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
use Illuminate\Support\Number;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class Metrics extends BaseWidget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public array $filters = [];
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
#[On('updateFilters')]
|
||||
public function updateFilters(array $filters): void
|
||||
{
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
protected function getStats(): array
|
||||
{
|
||||
/** @var Metric $lastMetric */
|
||||
$lastMetric = $this->server
|
||||
->metrics()
|
||||
->latest()
|
||||
->first();
|
||||
$metrics = app(GetMetrics::class)->filter($this->server, $this->filters);
|
||||
|
||||
return [
|
||||
Stat::make('CPU Load', $lastMetric?->load ?? 0)
|
||||
->color('success')
|
||||
->chart($metrics->pluck('load')->toArray()),
|
||||
Stat::make('Memory Usage', Number::fileSize($lastMetric->memory_used_in_bytes, 2))
|
||||
->color('warning')
|
||||
->chart($metrics->pluck('memory_used')->toArray()),
|
||||
Stat::make('Disk Usage', Number::fileSize($lastMetric->disk_used_in_bytes, 2))
|
||||
->color('primary')
|
||||
->chart($metrics->pluck('disk_used')->toArray()),
|
||||
];
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
@ -57,7 +58,8 @@ protected function getHeaderActions(): array
|
||||
ActionGroup::make($phps)
|
||||
->authorize(fn () => auth()->user()?->can('create', [Service::class, $this->server]))
|
||||
->label('Install PHP')
|
||||
->icon('heroicon-o-plus')
|
||||
->icon('heroicon-o-chevron-up-down')
|
||||
->iconPosition(IconPosition::After)
|
||||
->dropdownPlacement('bottom-end')
|
||||
->color('primary')
|
||||
->button(),
|
||||
|
96
app/Web/Pages/Servers/SSHKeys/Index.php
Normal file
96
app/Web/Pages/Servers/SSHKeys/Index.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\SSHKeys;
|
||||
|
||||
use App\Actions\SshKey\CreateSshKey;
|
||||
use App\Actions\SshKey\DeployKeyToServer;
|
||||
use App\Models\Server;
|
||||
use App\Models\SshKey;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/ssh-keys';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'SSH Keys';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAnyServer', [SshKey::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\SshKeysList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('deploy')
|
||||
->label('Deploy a Key')
|
||||
->authorize(fn () => auth()->user()?->can('createServer', [SshKey::class, $this->server]))
|
||||
->icon('heroicon-o-rocket-launch')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->form([
|
||||
Select::make('type')
|
||||
->options([
|
||||
'existing' => 'An existing key',
|
||||
'new' => 'A new key',
|
||||
])
|
||||
->reactive()
|
||||
->default('existing'),
|
||||
Select::make('key_id')
|
||||
->label('Key')
|
||||
->options(auth()->user()->sshKeys()->pluck('name', 'id')->toArray())
|
||||
->visible(fn ($get) => $get('type') === 'existing')
|
||||
->rules(DeployKeyToServer::rules(auth()->user(), $this->server)['key_id']),
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->visible(fn ($get) => $get('type') === 'new')
|
||||
->rules(CreateSshKey::rules()['name']),
|
||||
Textarea::make('public_key')
|
||||
->label('Public Key')
|
||||
->visible(fn ($get) => $get('type') === 'new')
|
||||
->rules(CreateSshKey::rules()['public_key']),
|
||||
])
|
||||
->modalSubmitActionLabel('Deploy')
|
||||
->action(function (array $data) {
|
||||
$this->validate();
|
||||
|
||||
try {
|
||||
if (! isset($data['key_id'])) {
|
||||
$data['key_id'] = app(CreateSshKey::class)->create(auth()->user(), $data)->id;
|
||||
}
|
||||
|
||||
app(DeployKeyToServer::class)->deploy($this->server, $data);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
71
app/Web/Pages/Servers/SSHKeys/Widgets/SshKeysList.php
Normal file
71
app/Web/Pages/Servers/SSHKeys/Widgets/SshKeysList.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\SSHKeys\Widgets;
|
||||
|
||||
use App\Actions\SshKey\DeleteKeyFromServer;
|
||||
use App\Models\Server;
|
||||
use App\Models\SshKey;
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class SshKeysList extends TableWidget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return SshKey::query()->whereHas(
|
||||
'servers',
|
||||
fn (Builder $query) => $query->where('server_id', $this->server->id)
|
||||
);
|
||||
}
|
||||
|
||||
protected static ?string $heading = '';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('user.name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
Action::make('delete')
|
||||
->icon('heroicon-o-trash')
|
||||
->tooltip('Delete')
|
||||
->color('danger')
|
||||
->hiddenLabel()
|
||||
->requiresConfirmation()
|
||||
->authorize(fn (SshKey $record) => auth()->user()->can('deleteServer', [SshKey::class, $this->server]))
|
||||
->action(function (SshKey $record) {
|
||||
try {
|
||||
app(DeleteKeyFromServer::class)->delete($this->server, $record);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
93
app/Web/Pages/Servers/Services/Index.php
Normal file
93
app/Web/Pages/Servers/Services/Index.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Services;
|
||||
|
||||
use App\Actions\Service\Install;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
use PageHasServer;
|
||||
|
||||
protected static ?string $slug = 'servers/{server}/services';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Services';
|
||||
|
||||
public Server $server;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('viewAny', [Service::class, static::getServerFromRoute()]) ?? false;
|
||||
}
|
||||
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[Widgets\ServicesList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
$availableServices = [];
|
||||
foreach (config('core.service_handlers') as $key => $addOn) {
|
||||
if (! $this->server->services()->where('name', $key)->exists()) {
|
||||
$availableServices[$key] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
Action::make('install')
|
||||
->label('Install Service')
|
||||
->icon('heroicon-o-archive-box-arrow-down')
|
||||
->modalWidth(MaxWidth::Large)
|
||||
->authorize(fn () => auth()->user()?->can('create', [Service::class, $this->server]))
|
||||
->form([
|
||||
Select::make('name')
|
||||
->searchable()
|
||||
->options($availableServices)
|
||||
->reactive()
|
||||
->rules(fn ($get) => Install::rules($get())['name']),
|
||||
Select::make('version')
|
||||
->options(function (callable $get) {
|
||||
if (! $get('name')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect(config("core.service_versions.{$get('name')}"))
|
||||
->mapWithKeys(fn ($version) => [$version => $version]);
|
||||
})
|
||||
->rules(fn ($get) => Install::rules($get())['version'])
|
||||
->reactive(),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
$this->validate();
|
||||
|
||||
try {
|
||||
app(Install::class)->install($this->server, $data);
|
||||
|
||||
$this->redirect(self::getUrl(['server' => $this->server]));
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
117
app/Web/Pages/Servers/Services/Widgets/ServicesList.php
Normal file
117
app/Web/Pages/Servers/Services/Widgets/ServicesList.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Services\Widgets;
|
||||
|
||||
use App\Actions\Service\Manage;
|
||||
use App\Actions\Service\Uninstall;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Web\Pages\Servers\Services\Index;
|
||||
use Exception;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\ActionGroup;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ServicesList extends TableWidget
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return Service::query()->where('server_id', $this->server->id);
|
||||
}
|
||||
|
||||
protected static ?string $heading = 'Installed Services';
|
||||
|
||||
protected function getTableColumns(): array
|
||||
{
|
||||
return [
|
||||
ImageColumn::make('image_url')
|
||||
->label('Service')
|
||||
->size(24),
|
||||
TextColumn::make('name')
|
||||
->sortable(),
|
||||
TextColumn::make('version')
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->color(fn (Service $service) => Service::$statusColors[$service->status])
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label('Installed At')
|
||||
->formatStateUsing(fn ($record) => $record->created_at_by_timezone),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getTable(): Table
|
||||
{
|
||||
return $this->table
|
||||
->actions([
|
||||
ActionGroup::make([
|
||||
$this->serviceAction('start'),
|
||||
$this->serviceAction('stop'),
|
||||
$this->serviceAction('restart'),
|
||||
$this->serviceAction('disable'),
|
||||
$this->serviceAction('enable'),
|
||||
$this->uninstallAction(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
private function serviceAction(string $type): Action
|
||||
{
|
||||
return Action::make($type)
|
||||
->authorize(fn (Service $service) => auth()->user()?->can($type, $service))
|
||||
->label(ucfirst($type).' Service')
|
||||
->action(function (Service $service) use ($type) {
|
||||
try {
|
||||
app(Manage::class)->$type($service);
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
});
|
||||
}
|
||||
|
||||
private function uninstallAction(): Action
|
||||
{
|
||||
return Action::make('uninstall')
|
||||
->authorize(fn (Service $service) => auth()->user()?->can('delete', $service))
|
||||
->label('Uninstall Service')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->action(function (Service $service) {
|
||||
try {
|
||||
app(Uninstall::class)->uninstall($service);
|
||||
|
||||
$this->redirect(Index::getUrl(['server' => $this->server]));
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title($e->getMessage())
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
});
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\Web\Components\Page;
|
||||
use App\Web\Pages\Servers\Sites\Widgets\SitesList;
|
||||
use App\Web\Traits\PageHasServer;
|
||||
use Filament\Actions\CreateAction;
|
||||
|
||||
@ -29,7 +28,7 @@ public static function canAccess(): bool
|
||||
public function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
[SitesList::class, ['server' => $this->server]],
|
||||
[Widgets\SitesList::class, ['server' => $this->server]],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,260 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Widgets;
|
||||
|
||||
use App\Actions\Server\CreateServer as CreateServerAction;
|
||||
use App\Enums\ServerProvider;
|
||||
use App\Enums\ServerType;
|
||||
use App\Enums\Webserver;
|
||||
use App\Models\Server;
|
||||
use App\Web\Fields\AlertField;
|
||||
use App\Web\Fields\ProviderField;
|
||||
use App\Web\Pages\Servers\View;
|
||||
use App\Web\Pages\Settings\ServerProviders\Actions\Create;
|
||||
use Filament\Forms\Components\Actions;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Widgets\Widget;
|
||||
use Throwable;
|
||||
|
||||
class CreateServer extends Widget implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static string $view = 'web.components.form';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
public ?string $provider = ServerProvider::HETZNER;
|
||||
|
||||
public ?string $server_provider = '';
|
||||
|
||||
public ?string $region = '';
|
||||
|
||||
public ?string $plan = '';
|
||||
|
||||
public ?string $public_key = '';
|
||||
|
||||
public ?string $name = '';
|
||||
|
||||
public ?string $ip = '';
|
||||
|
||||
public ?string $port = '';
|
||||
|
||||
public ?string $os = '';
|
||||
|
||||
public ?string $type = ServerType::REGULAR;
|
||||
|
||||
public ?string $webserver = Webserver::NGINX;
|
||||
|
||||
public ?string $database = '';
|
||||
|
||||
public ?string $php = '';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
$publicKey = __('servers.create.public_key_text', [
|
||||
'public_key' => get_public_key_content(),
|
||||
]);
|
||||
|
||||
return $form
|
||||
->schema([
|
||||
ProviderField::make('provider')
|
||||
->label('Select a provider')
|
||||
->default(ServerProvider::CUSTOM)
|
||||
->live()
|
||||
->reactive()
|
||||
->afterStateUpdated(function (callable $set) {
|
||||
$set('server_provider', null);
|
||||
$set('region', null);
|
||||
$set('plan', null);
|
||||
})
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['provider']),
|
||||
AlertField::make('alert')
|
||||
->warning()
|
||||
->message(__('servers.create.public_key_warning'))
|
||||
->visible(fn ($get) => $this->provider === ServerProvider::CUSTOM),
|
||||
Select::make('server_provider')
|
||||
->visible(fn ($get) => $this->provider !== ServerProvider::CUSTOM)
|
||||
->label('Server provider connection')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['server_provider'])
|
||||
->options(function ($get) {
|
||||
return \App\Models\ServerProvider::getByProjectId(auth()->user()->current_project_id)
|
||||
->where('provider', $this->provider)
|
||||
->pluck('profile', 'id');
|
||||
})
|
||||
->live()
|
||||
->suffixAction(
|
||||
Action::make('connect')
|
||||
->form(Create::form())
|
||||
->modalHeading('Connect to a new server provider')
|
||||
->modalSubmitActionLabel('Connect')
|
||||
->icon('heroicon-o-wifi')
|
||||
->tooltip('Connect to a new server provider')
|
||||
->modalWidth(MaxWidth::Medium)
|
||||
->authorize(fn () => auth()->user()->can('create', \App\Models\ServerProvider::class))
|
||||
// TODO: remove this after filament #14319 is fixed
|
||||
->url(\App\Web\Pages\Settings\ServerProviders\Index::getUrl())
|
||||
->action(fn (array $data) => Create::action($data))
|
||||
)
|
||||
->placeholder('Select profile')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->visible(fn ($get) => $this->provider !== ServerProvider::CUSTOM),
|
||||
Grid::make()
|
||||
->schema([
|
||||
Select::make('region')
|
||||
->label('Region')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['region'])
|
||||
->live()
|
||||
->reactive()
|
||||
->options(function () {
|
||||
if (! $this->server_provider) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return \App\Models\ServerProvider::regions($this->server_provider);
|
||||
})
|
||||
->loadingMessage('Loading regions...')
|
||||
->disabled(fn ($get) => ! $this->server_provider)
|
||||
->placeholder(fn ($get) => $this->server_provider ? 'Select region' : 'Select connection first')
|
||||
->searchable(),
|
||||
Select::make('plan')
|
||||
->label('Plan')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['plan'])
|
||||
->reactive()
|
||||
->options(function () {
|
||||
if (! $this->server_provider || ! $this->region) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return \App\Models\ServerProvider::plans($this->server_provider, $this->region);
|
||||
})
|
||||
->loadingMessage('Loading plans...')
|
||||
->disabled(fn ($get) => ! $this->region)
|
||||
->placeholder(fn ($get) => $this->region ? 'Select plan' : 'Select plan first')
|
||||
->searchable(),
|
||||
])
|
||||
->visible(fn ($get) => $this->provider !== ServerProvider::CUSTOM),
|
||||
TextInput::make('public_key')
|
||||
->label('Public Key')
|
||||
->default($publicKey)
|
||||
->suffixAction(
|
||||
Action::make('copy')
|
||||
->icon('heroicon-o-clipboard-document-list')
|
||||
->tooltip('Copy')
|
||||
->action(function ($livewire, $state) {
|
||||
$livewire->js(
|
||||
'window.navigator.clipboard.writeText("'.$state.'");'
|
||||
);
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Copied!')
|
||||
->send();
|
||||
})
|
||||
)
|
||||
->helperText('Run this command on your server as root user')
|
||||
->disabled()
|
||||
->visible(fn ($get) => $this->provider === ServerProvider::CUSTOM),
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['name']),
|
||||
Grid::make()
|
||||
->schema([
|
||||
TextInput::make('ip')
|
||||
->label('SSH IP Address')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['ip']),
|
||||
TextInput::make('port')
|
||||
->label('SSH Port')
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['port']),
|
||||
])
|
||||
->visible(fn ($get) => $this->provider === ServerProvider::CUSTOM),
|
||||
Grid::make()
|
||||
->schema([
|
||||
Select::make('os')
|
||||
->label('OS')
|
||||
->native(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['os'])
|
||||
->options(
|
||||
collect(config('core.operating_systems'))
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
),
|
||||
Select::make('type')
|
||||
->label('Server Type')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['type'])
|
||||
->options(
|
||||
collect(config('core.server_types'))
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
)
|
||||
->default(ServerType::REGULAR),
|
||||
]),
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Select::make('webserver')
|
||||
->label('Webserver')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['webserver'] ?? [])
|
||||
->options(
|
||||
collect(config('core.webservers'))->mapWithKeys(fn ($value) => [$value => $value])
|
||||
),
|
||||
Select::make('database')
|
||||
->label('Database')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['database'] ?? [])
|
||||
->options(
|
||||
collect(config('core.databases_name'))
|
||||
->mapWithKeys(fn ($value, $key) => [
|
||||
$key => $value.' '.config('core.databases_version')[$key],
|
||||
])
|
||||
),
|
||||
Select::make('php')
|
||||
->label('PHP')
|
||||
->native(false)
|
||||
->selectablePlaceholder(false)
|
||||
->rules(fn ($get) => CreateServerAction::rules($this->all())['php'] ?? [])
|
||||
->options(
|
||||
collect(config('core.php_versions'))
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
),
|
||||
]),
|
||||
Actions::make([
|
||||
Action::make('create')
|
||||
->label('Create Server')
|
||||
->button()
|
||||
->action(fn () => $this->submit()),
|
||||
]),
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
|
||||
public function submit(): void
|
||||
{
|
||||
$this->authorize('create', Server::class);
|
||||
|
||||
$this->validate();
|
||||
|
||||
try {
|
||||
$server = app(CreateServerAction::class)->create(auth()->user(), $this->all()['data']);
|
||||
|
||||
$this->redirect(View::getUrl(['server' => $server]));
|
||||
} catch (Throwable $e) {
|
||||
Notification::make()
|
||||
->title($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ public function infolist(Infolist $infolist): Infolist
|
||||
TextEntry::make('last_updated_check')
|
||||
->label('Last Updated Check')
|
||||
->inlineLabel()
|
||||
->state(fn (Server $record) => $record->getDateTimeByTimezone($record->last_update_check) ?? '-')
|
||||
->state(fn (Server $record) => $record->last_update_check?->ago())
|
||||
->suffixAction(
|
||||
Action::make('check-update')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
|
@ -18,7 +18,7 @@ protected function getStats(): array
|
||||
|
||||
if ($this->server->webserver()) {
|
||||
$stats[] = Stat::make('Sites', $this->server->sites()->count())
|
||||
->icon('heroicon-o-globe-alt');
|
||||
->icon('heroicon-o-cursor-arrow-ripple');
|
||||
}
|
||||
|
||||
if ($this->server->database()) {
|
||||
|
@ -3,23 +3,24 @@
|
||||
namespace App\Web\Pages\Settings\Projects\Widgets;
|
||||
|
||||
use App\Models\Project;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class SelectProject extends Widget implements HasForms
|
||||
class SelectProject extends Widget
|
||||
{
|
||||
use InteractsWithForms;
|
||||
protected static string $view = 'web.widgets.select-project';
|
||||
|
||||
protected static string $view = 'web.components.form';
|
||||
public ?Project $currentProject;
|
||||
|
||||
public Collection $projects;
|
||||
|
||||
public int|string|null $project;
|
||||
|
||||
protected function getFormSchema(): array
|
||||
public function mount(): void
|
||||
{
|
||||
$options = Project::query()
|
||||
$this->currentProject = auth()->user()->currentProject;
|
||||
$this->projects = Project::query()
|
||||
->where(function (Builder $query) {
|
||||
if (auth()->user()->isAdmin()) {
|
||||
return;
|
||||
@ -27,35 +28,14 @@ protected function getFormSchema(): array
|
||||
$query->where('user_id', auth()->id())
|
||||
->orWhereHas('users', fn ($query) => $query->where('user_id', auth()->id()));
|
||||
})
|
||||
->get()
|
||||
->mapWithKeys(fn ($project) => [$project->id => $project->name])
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
Select::make('project')
|
||||
->name('project')
|
||||
->model($this->project)
|
||||
->label('Project')
|
||||
->searchable()
|
||||
->options($options)
|
||||
->searchPrompt('Select a project...')
|
||||
->extraAttributes(['class' => '-mx-2 pointer-choices'])
|
||||
->selectablePlaceholder(false)
|
||||
->live(),
|
||||
];
|
||||
->get();
|
||||
}
|
||||
|
||||
public function updatedProject($value): void
|
||||
public function updateProject(Project $project): void
|
||||
{
|
||||
$project = Project::query()->findOrFail($value);
|
||||
$this->authorize('view', $project);
|
||||
auth()->user()->update(['current_project_id' => $value]);
|
||||
auth()->user()->update(['current_project_id' => $project->id]);
|
||||
|
||||
$this->redirect('/app');
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->project = auth()->user()->current_project_id;
|
||||
$this->redirect('/');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user