- 2.x - sites finishing

This commit is contained in:
Saeed Vaziry
2024-10-06 16:06:51 +02:00
parent 3c50e2c947
commit c24b4b7333
82 changed files with 1250 additions and 345 deletions

View File

@ -6,7 +6,7 @@
use App\Models\Queue;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateQueue
@ -16,15 +16,13 @@ class CreateQueue
*/
public function create(mixed $queueable, array $input): void
{
$this->validate($input);
$queue = new Queue([
'server_id' => $queueable instanceof Server ? $queueable->id : $queueable->server_id,
'site_id' => $queueable instanceof Site ? $queueable->id : null,
'command' => $input['command'],
'user' => $input['user'],
'auto_start' => $input['auto_start'],
'auto_restart' => $input['auto_restart'],
'auto_start' => $input['auto_start'] ? 1 : 0,
'auto_restart' => $input['auto_restart'] ? 1 : 0,
'numprocs' => $input['numprocs'],
'status' => QueueStatus::CREATING,
]);
@ -48,26 +46,18 @@ public function create(mixed $queueable, array $input): void
})->onConnection('ssh');
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
public static function rules(Server $server): array
{
$rules = [
return [
'command' => [
'required',
],
'user' => [
'required',
'in:root,'.config('core.ssh_user'),
],
'auto_start' => [
'required',
'in:0,1',
],
'auto_restart' => [
'required',
'in:0,1',
Rule::in([
'root',
$server->ssh_user,
]),
],
'numprocs' => [
'required',
@ -75,7 +65,5 @@ protected function validate(array $input): void
'min:1',
],
];
Validator::make($input, $rules)->validate();
}
}

View File

@ -3,12 +3,15 @@
namespace App\Actions\Queue;
use App\Models\Queue;
use App\SSH\Services\ProcessManager\ProcessManager;
class DeleteQueue
{
public function delete(Queue $queue): void
{
$queue->server->processManager()->handler()->delete($queue->id, $queue->site_id);
/** @var ProcessManager $processManager */
$processManager = $queue->server->processManager()->handler();
$processManager->delete($queue->id, $queue->site_id);
$queue->delete();
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace App\Actions\Queue;
use App\Enums\QueueStatus;
use App\Models\Queue;
use App\Models\Server;
use App\SSH\Services\ProcessManager\ProcessManager;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class EditQueue
{
/**
* @throws ValidationException
*/
public function edit(Queue $queue, array $input): void
{
$queue->fill([
'command' => $input['command'],
'user' => $input['user'],
'auto_start' => $input['auto_start'] ? 1 : 0,
'auto_restart' => $input['auto_restart'] ? 1 : 0,
'numprocs' => $input['numprocs'],
'status' => QueueStatus::RESTARTING,
]);
$queue->save();
dispatch(function () use ($queue) {
/** @var ProcessManager $processManager */
$processManager = $queue->server->processManager()->handler();
$processManager->delete($queue->id, $queue->site_id);
$processManager->create(
$queue->id,
$queue->command,
$queue->user,
$queue->auto_start,
$queue->auto_restart,
$queue->numprocs,
$queue->getLogFile(),
$queue->site_id
);
$queue->status = QueueStatus::RUNNING;
$queue->save();
})->catch(function () use ($queue) {
$queue->status = QueueStatus::FAILED;
$queue->save();
})->onConnection('ssh');
}
public static function rules(Server $server): array
{
return [
'command' => [
'required',
],
'user' => [
'required',
Rule::in([
'root',
$server->ssh_user,
]),
],
'numprocs' => [
'required',
'numeric',
'min:1',
],
];
}
}

View File

@ -4,10 +4,10 @@
use App\Enums\SslStatus;
use App\Enums\SslType;
use App\Models\ServerLog;
use App\Models\Site;
use App\Models\Ssl;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
@ -18,7 +18,10 @@ class CreateSSL
*/
public function create(Site $site, array $input): void
{
$this->validate($input);
$site->ssls()
->where('type', $input['type'])
->where('status', SslStatus::FAILED)
->delete();
$ssl = new Ssl([
'site_id' => $site->id,
@ -32,6 +35,7 @@ public function create(Site $site, array $input): void
if (isset($input['aliases']) && $input['aliases']) {
$ssl->domains = array_merge($ssl->domains, $site->aliases);
}
$ssl->log_id = ServerLog::log($site->server, 'create-ssl', '', $site)->id;
$ssl->save();
dispatch(function () use ($site, $ssl) {
@ -47,10 +51,7 @@ public function create(Site $site, array $input): void
})->onConnection('ssh');
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
public static function rules(array $input): array
{
$rules = [
'type' => [
@ -61,9 +62,13 @@ protected function validate(array $input): void
if (isset($input['type']) && $input['type'] == SslType::CUSTOM) {
$rules['certificate'] = 'required';
$rules['private'] = 'required';
$rules['expires_at'] = 'required|date_format:Y-m-d|after_or_equal:'.now();
$rules['expires_at'] = [
'required',
'date_format:Y-m-d',
'after_or_equal:'.now(),
];
}
Validator::make($input, $rules)->validate();
return $rules;
}
}

View File

@ -3,12 +3,15 @@
namespace App\Actions\SSL;
use App\Models\Ssl;
use App\SSH\Services\Webserver\Webserver;
class DeleteSSL
{
public function delete(Ssl $ssl): void
{
$ssl->site->server->webserver()->handler()->removeSSL($ssl);
/** @var Webserver $webserver */
$webserver = $ssl->site->server->webserver()->handler();
$webserver->removeSSL($ssl);
$ssl->delete();
}
}

View File

@ -51,7 +51,7 @@ public function init(Server $server, ?string $asUser = null): self
return $this;
}
public function setLog(ServerLog $log): self
public function setLog(?ServerLog $log): self
{
$this->log = $log;
@ -93,7 +93,6 @@ public function connect(bool $sftp = false): void
*/
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false, ?callable $streamCallback = null): string
{
ds($command);
if (! $this->log && $log) {
$this->log = ServerLog::make($this->server, $log);
if ($siteId) {

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Enums\QueueStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -45,6 +46,17 @@ class Queue extends AbstractModel
'redirect_stderr' => 'boolean',
];
public static array $statusColors = [
QueueStatus::RUNNING => 'success',
QueueStatus::CREATING => 'warning',
QueueStatus::DELETING => 'warning',
QueueStatus::FAILED => 'danger',
QueueStatus::STARTING => 'warning',
QueueStatus::STOPPING => 'warning',
QueueStatus::RESTARTING => 'warning',
QueueStatus::STOPPED => 'gray',
];
public function getServerIdAttribute(int $value): int
{
if (! $value) {

View File

@ -114,7 +114,7 @@ public function getContent($lines = null): ?string
return '';
}
public static function log(Server $server, string $type, string $content, ?Site $site = null): void
public static function log(Server $server, string $type, string $content, ?Site $site = null): static
{
$log = new static([
'server_id' => $server->id,
@ -125,6 +125,8 @@ public static function log(Server $server, string $type, string $content, ?Site
]);
$log->save();
$log->write($content);
return $log;
}
public static function make(Server $server, string $type): ServerLog

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Enums\SslStatus;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -18,6 +19,8 @@
* @property Site $site
* @property string $ca_path
* @property ?array $domains
* @property int $log_id
* @property ?ServerLog $log
*/
class Ssl extends AbstractModel
{
@ -32,6 +35,7 @@ class Ssl extends AbstractModel
'expires_at',
'status',
'domains',
'log_id',
];
protected $casts = [
@ -41,6 +45,14 @@ class Ssl extends AbstractModel
'ca' => 'encrypted',
'expires_at' => 'datetime',
'domains' => 'array',
'log_id' => 'integer',
];
public static array $statusColors = [
SslStatus::CREATED => 'success',
SslStatus::CREATING => 'warning',
SslStatus::DELETING => 'warning',
SslStatus::FAILED => 'danger',
];
public function site(): BelongsTo
@ -126,4 +138,9 @@ public function getDomains(): array
return $this->domains;
}
public function log(): BelongsTo
{
return $this->belongsTo(ServerLog::class);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Policies;
use App\Models\Queue;
use App\Models\Server;
use App\Models\Site;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class QueuePolicy
{
use HandlesAuthorization;
public function viewAny(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->isReady();
}
public function view(User $user, Queue $queue, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$queue->site_id === $site->id;
}
public function create(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->isReady();
}
public function update(User $user, Queue $queue, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$queue->site_id === $site->id;
}
public function delete(User $user, Queue $queue, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$queue->site_id === $site->id;
}
}

View File

@ -2,7 +2,6 @@
namespace App\Policies;
use App\Enums\ServiceStatus;
use App\Models\Server;
use App\Models\Service;
use App\Models\User;
@ -36,52 +35,4 @@ public function delete(User $user, Service $service): bool
{
return ($user->isAdmin() || $service->server->project->users->contains($user)) && $service->server->isReady();
}
public function start(User $user, Service $service): bool
{
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
$service->server->isReady() &&
in_array($service->status, [
ServiceStatus::STOPPED,
ServiceStatus::FAILED,
]);
}
public function stop(User $user, Service $service): bool
{
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
$service->server->isReady() &&
in_array($service->status, [
ServiceStatus::READY,
ServiceStatus::FAILED,
]);
}
public function restart(User $user, Service $service): bool
{
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
$service->server->isReady() &&
in_array($service->status, [
ServiceStatus::READY,
ServiceStatus::FAILED,
ServiceStatus::STOPPED,
]);
}
public function enable(User $user, Service $service): bool
{
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
$service->server->isReady() &&
$service->status == ServiceStatus::DISABLED;
}
public function disable(User $user, Service $service): bool
{
return ($user->isAdmin() || $service->server->project->users->contains($user)) &&
$service->server->isReady() &&
in_array($service->status, [
ServiceStatus::READY,
ServiceStatus::STOPPED,
]);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Policies;
use App\Models\Server;
use App\Models\Site;
use App\Models\Ssl;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class SslPolicy
{
use HandlesAuthorization;
public function viewAny(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->isReady();
}
public function view(User $user, Ssl $ssl, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$ssl->site_id === $site->id;
}
public function create(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->isReady();
}
public function update(User $user, Ssl $ssl, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$ssl->site_id === $site->id;
}
public function delete(User $user, Ssl $ssl, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$ssl->site_id === $site->id;
}
}

View File

@ -2,6 +2,7 @@
namespace App\SSH\Services\Webserver;
use App\Exceptions\SSHError;
use App\Exceptions\SSLCreationException;
use App\Models\Site;
use App\Models\Ssl;
@ -113,7 +114,7 @@ public function changePHPVersion(Site $site, $version): void
}
/**
* @throws SSLCreationException
* @throws SSHError
*/
public function setupSSL(Ssl $ssl): void
{
@ -136,7 +137,7 @@ public function setupSSL(Ssl $ssl): void
'pk_path' => $ssl->getPkPath(),
]);
}
$result = $this->service->server->ssh()->exec(
$result = $this->service->server->ssh()->setLog($ssl->log)->exec(
$command,
'create-ssl',
$ssl->site_id

View File

@ -2,6 +2,7 @@
use App\Exceptions\SSHError;
use App\Helpers\HtmxResponse;
use Filament\Notifications\Actions\Action;
use Filament\Notifications\Notification;
function generate_public_key($privateKeyPath, $publicKeyPath): void
@ -67,6 +68,13 @@ function run_action(object $static, Closure $callback): void
->danger()
->title($e->getMessage())
->body($e->getLog()?->getContent(30))
->actions([
Action::make('View Logs')
->url(App\Web\Pages\Servers\Logs\Index::getUrl([
'server' => $e->getLog()?->server_id,
]))
->openUrlInNewTab(),
])
->send();
if (method_exists($static, 'halt')) {

View File

@ -56,7 +56,7 @@ protected function getHeaderActions(): array
Select::make('user')
->rules(fn (callable $get) => CreateCronJob::rules($get())['user'])
->options([
'vito' => 'vito',
'vito' => $this->server->ssh_user,
'root' => 'root',
]),
Select::make('frequency')

View File

@ -16,7 +16,6 @@
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Throwable;
class Index extends Page
{
@ -228,21 +227,12 @@ protected function getHeaderActions(): array
]),
])
->modalSubmitActionLabel('Create')
->action(function ($input) {
$this->authorize('create', Server::class);
$this->validate();
try {
$server = app(CreateServerAction::class)->create(auth()->user(), $input);
->action(function (array $data) {
run_action($this, function () use ($data) {
$server = app(CreateServerAction::class)->create(auth()->user(), $data);
$this->redirect(View::getUrl(['server' => $server]));
} catch (Throwable $e) {
Notification::make()
->title($e->getMessage())
->danger()
->send();
}
});
}),
];
}

View File

@ -26,6 +26,8 @@ class LogsList extends Widget
public ?string $label = '';
protected $listeners = ['$refresh'];
protected function getTableQuery(): Builder
{
return ServerLog::query()

View File

@ -11,7 +11,7 @@
use Filament\Notifications\Notification;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget;
@ -33,9 +33,10 @@ protected function getTableQuery(): Builder
protected function getTableColumns(): array
{
return [
ImageColumn::make('image_url')
IconColumn::make('id')
->label('Service')
->size(24),
->icon(fn (Service $record) => 'icon-'.$record->name)
->width(24),
TextColumn::make('name')
->sortable(),
TextColumn::make('version')
@ -59,21 +60,22 @@ 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->serviceAction('start', 'heroicon-o-play'),
$this->serviceAction('stop', 'heroicon-o-stop'),
$this->serviceAction('restart', 'heroicon-o-arrow-path'),
$this->serviceAction('disable', 'heroicon-o-x-mark'),
$this->serviceAction('enable', 'heroicon-o-check'),
$this->uninstallAction(),
]),
]);
}
private function serviceAction(string $type): Action
private function serviceAction(string $type, string $icon): Action
{
return Action::make($type)
->authorize(fn (Service $service) => auth()->user()?->can($type, $service))
->authorize(fn (Service $service) => auth()->user()?->can('update', $service))
->label(ucfirst($type).' Service')
->icon($icon)
->action(function (Service $service) use ($type) {
try {
app(Manage::class)->$type($service);
@ -95,6 +97,7 @@ private function uninstallAction(): Action
return Action::make('uninstall')
->authorize(fn (Service $service) => auth()->user()?->can('delete', $service))
->label('Uninstall Service')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->action(function (Service $service) {

View File

@ -2,7 +2,15 @@
namespace App\Web\Pages\Servers\Sites\Pages\Queues;
use App\Actions\Queue\CreateQueue;
use App\Web\Pages\Servers\Sites\Page;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Support\Enums\MaxWidth;
class Index extends Page
{
@ -17,6 +25,54 @@ public static function canAccess(): bool
public function getWidgets(): array
{
return [];
return [
[Widgets\QueuesList::class, ['site' => $this->site]],
];
}
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/sites/queues.html')
->openUrlInNewTab(),
CreateAction::make('create')
->icon('heroicon-o-plus')
->createAnother(false)
->modalWidth(MaxWidth::ExtraLarge)
->label('New Queue')
->form([
TextInput::make('command')
->rules(CreateQueue::rules($this->server)['command'])
->helperText('Example: php /home/vito/your-site/artisan queue:work'),
Select::make('user')
->rules(fn (callable $get) => CreateQueue::rules($this->server)['user'])
->options([
'vito' => $this->server->ssh_user,
'root' => 'root',
]),
TextInput::make('numprocs')
->default(1)
->rules(CreateQueue::rules($this->server)['numprocs'])
->helperText('Number of processes'),
Grid::make()
->schema([
Checkbox::make('auto_start')
->default(false),
Checkbox::make('auto_restart')
->default(false),
]),
])
->using(function (array $data) {
run_action($this, function () use ($data) {
app(CreateQueue::class)->create($this->site, $data);
$this->dispatch('$refresh');
});
}),
];
}
}

View File

@ -0,0 +1,160 @@
<?php
namespace App\Web\Pages\Servers\Sites\Pages\Queues\Widgets;
use App\Actions\Queue\DeleteQueue;
use App\Actions\Queue\EditQueue;
use App\Actions\Queue\GetQueueLogs;
use App\Actions\Queue\ManageQueue;
use App\Models\Queue;
use App\Models\Site;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as Widget;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\View\ComponentAttributeBag;
class QueuesList extends Widget
{
public Site $site;
protected $listeners = ['$refresh'];
protected function getTableQuery(): Builder
{
return Queue::query()->where('site_id', $this->site->id);
}
protected static ?string $heading = '';
protected function getTableColumns(): array
{
return [
TextColumn::make('command')
->limit(20)
->copyable()
->tooltip(fn (Queue $record) => $record->command)
->searchable()
->sortable(),
TextColumn::make('created_at')
->formatStateUsing(fn (Queue $record) => $record->created_at_by_timezone)
->sortable(),
TextColumn::make('status')
->label('Status')
->badge()
->color(fn (Queue $record) => Queue::$statusColors[$record->status])
->searchable()
->sortable(),
];
}
public function getTable(): Table
{
return $this->table
->actions([
ActionGroup::make([
$this->editAction(),
$this->operationAction('start', 'heroicon-o-play'),
$this->operationAction('stop', 'heroicon-o-stop'),
$this->operationAction('restart', 'heroicon-o-arrow-path'),
$this->logsAction(),
$this->deleteAction(),
]),
]);
}
private function operationAction(string $type, string $icon): Action
{
return Action::make($type)
->authorize(fn (Queue $record) => auth()->user()->can('update', [$record, $this->site, $this->site->server]))
->label(ucfirst($type).' queue')
->icon($icon)
->action(function (Queue $record) use ($type) {
run_action($this, function () use ($record, $type) {
app(ManageQueue::class)->$type($record);
$this->dispatch('$refresh');
});
});
}
private function logsAction(): Action
{
return Action::make('logs')
->icon('heroicon-o-eye')
->authorize(fn (Queue $record) => auth()->user()->can('view', [$record, $this->site, $this->site->server]))
->modalHeading('View Log')
->modalContent(function (Queue $record) {
return view('components.console-view', [
'slot' => app(GetQueueLogs::class)->getLogs($record),
'attributes' => new ComponentAttributeBag,
]);
})
->modalSubmitAction(false)
->modalCancelActionLabel('Close');
}
private function editAction(): Action
{
return EditAction::make('edit')
->icon('heroicon-o-pencil-square')
->authorize(fn (Queue $record) => auth()->user()->can('update', [$record, $this->site, $this->site->server]))
->modalWidth(MaxWidth::ExtraLarge)
->fillForm(fn (Queue $record) => [
'command' => $record->command,
'user' => $record->user,
'numprocs' => $record->numprocs,
'auto_start' => $record->auto_start,
'auto_restart' => $record->auto_restart,
])
->form([
TextInput::make('command')
->rules(EditQueue::rules($this->site->server)['command'])
->helperText('Example: php /home/vito/your-site/artisan queue:work'),
Select::make('user')
->rules(fn (callable $get) => EditQueue::rules($this->site->server)['user'])
->options([
'vito' => $this->site->server->ssh_user,
'root' => 'root',
]),
TextInput::make('numprocs')
->default(1)
->rules(EditQueue::rules($this->site->server)['numprocs'])
->helperText('Number of processes'),
Grid::make()
->schema([
Checkbox::make('auto_start')
->default(false),
Checkbox::make('auto_restart')
->default(false),
]),
])
->using(function (Queue $record, array $data) {
run_action($this, function () use ($record, $data) {
app(EditQueue::class)->edit($record, $data);
$this->dispatch('$refresh');
});
});
}
private function deleteAction(): Action
{
return DeleteAction::make('delete')
->icon('heroicon-o-trash')
->authorize(fn (Queue $record) => auth()->user()->can('delete', [$record, $this->site, $this->site->server]))
->using(function (Queue $record) {
run_action($this, function () use ($record) {
app(DeleteQueue::class)->delete($record);
$this->dispatch('$refresh');
});
});
}
}

View File

@ -2,7 +2,17 @@
namespace App\Web\Pages\Servers\Sites\Pages\SSL;
use App\Actions\SSL\CreateSSL;
use App\Models\Ssl;
use App\Web\Pages\Servers\Sites\Page;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Get;
use Filament\Support\Enums\MaxWidth;
class Index extends Page
{
@ -12,11 +22,60 @@ class Index extends Page
public static function canAccess(): bool
{
return true;
return auth()->user()?->can('viewAny', [Ssl::class, static::getSiteFromRoute(), static::getServerFromRoute()]) ?? false;
}
public function getWidgets(): array
{
return [];
return [
[Widgets\SslsList::class, ['site' => $this->site]],
];
}
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/sites/ssl.html')
->openUrlInNewTab(),
CreateAction::make('create')
->label('New Certificate')
->icon('heroicon-o-lock-closed')
->form([
Select::make('type')
->options(
collect(config('core.ssl_types'))->mapWithKeys(fn ($type) => [$type => $type])
)
->rules(fn (Get $get) => CreateSSL::rules($get())['type'])
->reactive(),
Textarea::make('certificate')
->rows(5)
->rules(fn (Get $get) => CreateSSL::rules($get())['certificate'])
->visible(fn (Get $get) => $get('type') === 'custom'),
Textarea::make('private')
->label('Private Key')
->rows(5)
->rules(fn (Get $get) => CreateSSL::rules($get())['private'])
->visible(fn (Get $get) => $get('type') === 'custom'),
DatePicker::make('expires_at')
->format('Y-m-d')
->rules(fn (Get $get) => CreateSSL::rules($get())['expires_at'])
->visible(fn (Get $get) => $get('type') === 'custom'),
Checkbox::make('aliases')
->label("Set SSL for site's aliases as well"),
])
->createAnother(false)
->modalWidth(MaxWidth::Large)
->using(function (array $data) {
run_action($this, function () use ($data) {
app(CreateSSL::class)->create($this->site, $data);
$this->dispatch('$refresh');
});
}),
];
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Web\Pages\Servers\Sites\Pages\SSL\Widgets;
use App\Actions\SSL\DeleteSSL;
use App\Models\Site;
use App\Models\Ssl;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as Widget;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\View\ComponentAttributeBag;
class SslsList extends Widget
{
public Site $site;
protected $listeners = ['$refresh'];
protected function getTableQuery(): Builder
{
return Ssl::query()->where('site_id', $this->site->id);
}
protected static ?string $heading = '';
protected function getTableColumns(): array
{
return [
TextColumn::make('type')
->searchable()
->sortable(),
TextColumn::make('created_at')
->formatStateUsing(fn (Ssl $record) => $record->created_at_by_timezone)
->sortable(),
TextColumn::make('expires_at')
->formatStateUsing(fn (Ssl $record) => $record->getDateTimeByTimezone($record->expires_at))
->sortable(),
TextColumn::make('status')
->label('Status')
->badge()
->color(fn (Ssl $record) => Ssl::$statusColors[$record->status])
->searchable()
->sortable(),
];
}
public function getTable(): Table
{
return $this->table
->actions([
Action::make('logs')
->hiddenLabel()
->tooltip('Logs')
->icon('heroicon-o-eye')
->authorize(fn (Ssl $record) => auth()->user()->can('view', [$record, $this->site, $this->site->server]))
->modalHeading('View Log')
->modalContent(function (Ssl $record) {
return view('components.console-view', [
'slot' => $record->log?->getContent(),
'attributes' => new ComponentAttributeBag,
]);
})
->modalSubmitAction(false)
->modalCancelActionLabel('Close'),
DeleteAction::make('delete')
->hiddenLabel()
->tooltip('Delete')
->icon('heroicon-o-trash')
->authorize(fn (Ssl $record) => auth()->user()->can('delete', [$record, $this->site, $this->site->server]))
->using(function (Ssl $record) {
run_action($this, function () use ($record) {
app(DeleteSSL::class)->delete($record);
$this->dispatch('$refresh');
});
}),
]);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Web\Pages\Servers\Sites;
use App\Actions\Site\DeleteSite;
use App\SSH\Services\Webserver\Webserver;
use App\Web\Fields\CodeEditorField;
use Filament\Actions\Action;
@ -40,16 +41,23 @@ private function deleteAction(): Action
{
return DeleteAction::make()
->icon('heroicon-o-trash')
->record($this->server)
->record($this->site)
->modalHeading('Delete Site')
->modalDescription('Once your site is deleted, all of its resources and data will be permanently deleted and can\'t be restored');
->modalDescription('Once your site is deleted, all of its resources and data will be permanently deleted and can\'t be restored')
->using(function () {
run_action($this, function () {
app(DeleteSite::class)->delete($this->site);
$this->redirect(Index::getUrl(['server' => $this->server]));
});
});
}
private function vhostAction(): Action
{
return Action::make('vhost')
->color('gray')
->icon('si-nginx')
->icon('icon-nginx')
->label('VHost')
->modalSubmitActionLabel('Save')
->form([

View File

@ -79,11 +79,19 @@ public function getWidgets(): array
public function getHeaderActions(): array
{
$actions = [];
$actions = [
Action::make('read-the-docs')
->label('Read the Docs')
->icon('heroicon-o-document-text')
->color('gray')
->url('https://vitodeploy.com/sites/application.html')
->openUrlInNewTab(),
];
$actionsGroup = [];
if (in_array(SiteFeature::DEPLOYMENT, $this->site->type()->supportedFeatures())) {
$actions[] = $this->deployAction();
$actionsGroup[] = $this->autoDeploymentAction();
$actionsGroup[] = $this->deploymentScriptAction();
}
@ -130,6 +138,27 @@ private function deployAction(): Action
});
}
private function autoDeploymentAction(): Action
{
return Action::make('auto-deployment')
->label(fn () => $this->site->isAutoDeployment() ? 'Disable Auto Deployment' : 'Enable Auto Deployment')
->modalHeading(fn () => $this->site->isAutoDeployment() ? 'Disable Auto Deployment' : 'Enable Auto Deployment')
->modalIconColor(fn () => $this->site->isAutoDeployment() ? 'red' : 'green')
->requiresConfirmation()
->action(function () {
run_action($this, function () {
$this->site->isAutoDeployment()
? $this->site->disableAutoDeployment()
: $this->site->enableAutoDeployment();
Notification::make()
->success()
->title('Auto deployment '.($this->site->isAutoDeployment() ? 'disabled' : 'enabled').'!')
->send();
});
});
}
private function deploymentScriptAction(): Action
{
return Action::make('deployment-script')

View File

@ -7,6 +7,7 @@
use App\Web\Pages\Servers\Sites\Settings;
use App\Web\Pages\Servers\Sites\View;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as Widget;
@ -26,6 +27,10 @@ protected function getTableQuery(): Builder
protected function getTableColumns(): array
{
return [
IconColumn::make('type')
->icon(fn (Site $record) => 'icon-'.$record->type)
->tooltip(fn (Site $record) => $record->type)
->width(24),
TextColumn::make('domain')
->searchable()
->sortable(),

View File

@ -6,6 +6,7 @@
use App\Web\Pages\Servers\Settings;
use App\Web\Pages\Servers\View;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as Widget;
@ -23,6 +24,10 @@ protected function getTableQuery(): Builder
protected function getTableColumns(): array
{
return [
IconColumn::make('provider')
->icon(fn (Server $record) => 'icon-'.$record->provider)
->tooltip(fn (Server $record) => $record->provider)
->width(24),
TextColumn::make('name')
->searchable()
->sortable(),

View File

@ -8,7 +8,7 @@
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as Widget;
@ -26,9 +26,9 @@ protected function getTableQuery(): Builder
protected function getTableColumns(): array
{
return [
ImageColumn::make('image_url')
->label('Provider')
->size(24),
IconColumn::make('provider')
->icon(fn (ServerProvider $record) => 'icon-'.$record->provider)
->width(24),
TextColumn::make('name')
->default(fn ($record) => $record->profile)
->label('Name')

View File

@ -8,7 +8,7 @@
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as Widget;
@ -28,9 +28,9 @@ protected function getTableQuery(): Builder
protected function getTableColumns(): array
{
return [
ImageColumn::make('image_url')
->label('Provider')
->size(24),
IconColumn::make('provider')
->icon(fn (SourceControl $record) => 'icon-'.$record->provider)
->width(24),
TextColumn::make('name')
->default(fn (SourceControl $record) => $record->profile)
->label('Name')

View File

@ -8,7 +8,7 @@
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as Widget;
@ -26,11 +26,12 @@ protected function getTableQuery(): Builder
protected function getTableColumns(): array
{
return [
ImageColumn::make('image_url')
->label('Provider')
->size(24),
IconColumn::make('provider')
->icon(fn (StorageProvider $record) => 'icon-'.$record->provider)
->tooltip(fn (StorageProvider $record) => $record->provider)
->width(24),
TextColumn::make('name')
->default(fn ($record) => $record->profile)
->default(fn (StorageProvider $record) => $record->profile)
->label('Name')
->searchable()
->sortable(),

View File

@ -5,57 +5,91 @@
use App\Actions\Tag\SyncTags;
use App\Models\Server;
use App\Models\Site;
use Filament\Forms\Components\Actions\Action as FormAction;
use Filament\Forms\Components\Select;
use Filament\Infolists\Components\Actions\Action;
use Filament\Infolists\Components\Actions\Action as InfolistAction;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\Action as TableAction;
class EditTags
{
/**
* @param Site|Server $taggable
*/
public static function infolist(mixed $taggable): Action
public static function infolist(mixed $taggable): InfolistAction
{
return Action::make('edit_tags')
return InfolistAction::make('edit_tags')
->icon('heroicon-o-pencil-square')
->tooltip('Edit Tags')
->hiddenLabel()
->modalSubmitActionLabel('Save')
->modalHeading('Edit Tags')
->modalWidth(MaxWidth::Medium)
->form([
Select::make('tags')
->default($taggable->tags()->pluck('tags.id')->toArray())
->options(function () {
return auth()->user()->currentProject->tags()->pluck('name', 'id')->toArray();
})
->nestedRecursiveRules(SyncTags::rules(auth()->user()->currentProject->id)['tags.*'])
->suffixAction(
\Filament\Forms\Components\Actions\Action::make('create_tag')
->icon('heroicon-o-plus')
->tooltip('Create a new tag')
->modalSubmitActionLabel('Create')
->modalHeading('Create Tag')
->modalWidth(MaxWidth::Medium)
->form(Create::form())
->action(function (array $data) {
Create::action($data);
}),
)
->multiple(),
])
->action(function (array $data) use ($taggable) {
app(SyncTags::class)->sync(auth()->user(), [
'taggable_id' => $taggable->id,
'taggable_type' => get_class($taggable),
'tags' => $data['tags'],
]);
->form(static::form($taggable))
->action(static::action($taggable));
}
Notification::make()
->success()
->title('Tags updated!')
->send();
});
/**
* @param Site|Server $taggable
*/
public static function table(mixed $taggable): TableAction
{
return TableAction::make('edit_tags')
->icon('heroicon-o-pencil-square')
->tooltip('Edit Tags')
->hiddenLabel()
->modalSubmitActionLabel('Save')
->modalHeading('Edit Tags')
->modalWidth(MaxWidth::Medium)
->form(static::form($taggable))
->action(static::action($taggable));
}
/**
* @param Site|Server $taggable
*/
private static function form(mixed $taggable): array
{
return [
Select::make('tags')
->default($taggable->tags()->pluck('tags.id')->toArray())
->options(function () {
return auth()->user()->currentProject->tags()->pluck('name', 'id')->toArray();
})
->nestedRecursiveRules(SyncTags::rules(auth()->user()->currentProject->id)['tags.*'])
->suffixAction(
FormAction::make('create_tag')
->icon('heroicon-o-plus')
->tooltip('Create a new tag')
->modalSubmitActionLabel('Create')
->modalHeading('Create Tag')
->modalWidth(MaxWidth::Medium)
->form(Create::form())
->action(function (array $data) {
Create::action($data);
}),
)
->multiple(),
];
}
/**
* @param Site|Server $taggable
*/
private static function action(mixed $taggable): \Closure
{
return function (array $data) use ($taggable) {
app(SyncTags::class)->sync(auth()->user(), [
'taggable_id' => $taggable->id,
'taggable_type' => get_class($taggable),
'tags' => $data['tags'],
]);
Notification::make()
->success()
->title('Tags updated!')
->send();
};
}
}