315 lines
12 KiB
PHP
315 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Jabali\Pages;
|
|
|
|
use App\Jobs\RunGitDeployment;
|
|
use App\Models\Domain;
|
|
use App\Models\GitDeployment as GitDeploymentModel;
|
|
use App\Services\Agent\AgentClient;
|
|
use BackedEnum;
|
|
use Exception;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\Concerns\InteractsWithActions;
|
|
use Filament\Actions\Contracts\HasActions;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Forms\Concerns\InteractsWithForms;
|
|
use Filament\Forms\Contracts\HasForms;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Tables\Columns\IconColumn;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Concerns\InteractsWithTable;
|
|
use Filament\Tables\Contracts\HasTable;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Contracts\Support\Htmlable;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Str;
|
|
|
|
class GitDeployment extends Page implements HasActions, HasForms, HasTable
|
|
{
|
|
use InteractsWithActions;
|
|
use InteractsWithForms;
|
|
use InteractsWithTable;
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-code-bracket-square';
|
|
|
|
protected static ?int $navigationSort = 16;
|
|
|
|
protected static ?string $slug = 'git-deployment';
|
|
|
|
protected string $view = 'filament.jabali.pages.git-deployment';
|
|
|
|
protected ?AgentClient $agent = null;
|
|
|
|
public ?string $deployKey = null;
|
|
|
|
public function getTitle(): string|Htmlable
|
|
{
|
|
return __('Git Deployment');
|
|
}
|
|
|
|
public static function getNavigationLabel(): string
|
|
{
|
|
return __('Git Deployment');
|
|
}
|
|
|
|
protected function getAgent(): AgentClient
|
|
{
|
|
if ($this->agent === null) {
|
|
$this->agent = new AgentClient;
|
|
}
|
|
|
|
return $this->agent;
|
|
}
|
|
|
|
protected function getUsername(): string
|
|
{
|
|
return Auth::user()->username;
|
|
}
|
|
|
|
protected function getDomainOptions(): array
|
|
{
|
|
return Domain::query()
|
|
->where('user_id', Auth::id())
|
|
->orderBy('domain')
|
|
->pluck('domain', 'id')
|
|
->toArray();
|
|
}
|
|
|
|
protected function getWebhookUrl(GitDeploymentModel $deployment): string
|
|
{
|
|
return url("/api/webhooks/git/{$deployment->id}/{$deployment->secret_token}");
|
|
}
|
|
|
|
protected function getDeployKey(): string
|
|
{
|
|
if ($this->deployKey) {
|
|
return $this->deployKey;
|
|
}
|
|
|
|
try {
|
|
$result = $this->getAgent()->gitGenerateKey($this->getUsername());
|
|
$this->deployKey = $result['public_key'] ?? '';
|
|
} catch (Exception) {
|
|
$this->deployKey = '';
|
|
}
|
|
|
|
return $this->deployKey ?? '';
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->query(GitDeploymentModel::query()->where('user_id', Auth::id())->with('domain'))
|
|
->columns([
|
|
TextColumn::make('domain.domain')
|
|
->label(__('Domain'))
|
|
->searchable()
|
|
->sortable(),
|
|
TextColumn::make('repo_url')
|
|
->label(__('Repository'))
|
|
->limit(40)
|
|
->tooltip(fn (GitDeploymentModel $record) => $record->repo_url)
|
|
->sortable(),
|
|
TextColumn::make('branch')
|
|
->label(__('Branch'))
|
|
->badge()
|
|
->color('gray'),
|
|
IconColumn::make('auto_deploy')
|
|
->label(__('Auto Deploy'))
|
|
->boolean(),
|
|
TextColumn::make('last_status')
|
|
->label(__('Status'))
|
|
->badge()
|
|
->color(fn (?string $state): string => match ($state) {
|
|
'success' => 'success',
|
|
'failed' => 'danger',
|
|
'running' => 'warning',
|
|
'queued' => 'info',
|
|
default => 'gray',
|
|
})
|
|
->default('never'),
|
|
TextColumn::make('last_deployed_at')
|
|
->label(__('Last Deployed'))
|
|
->since()
|
|
->sortable(),
|
|
])
|
|
->recordActions([
|
|
Action::make('deploy')
|
|
->label(__('Deploy'))
|
|
->icon('heroicon-o-arrow-path')
|
|
->color('primary')
|
|
->action(function (GitDeploymentModel $record): void {
|
|
$record->update(['last_status' => 'queued']);
|
|
RunGitDeployment::dispatch($record->id);
|
|
Notification::make()->title(__('Deployment queued'))->success()->send();
|
|
}),
|
|
Action::make('webhook')
|
|
->label(__('Webhook'))
|
|
->icon('heroicon-o-link')
|
|
->color('gray')
|
|
->modalHeading(__('Webhook URL'))
|
|
->modalSubmitAction(false)
|
|
->modalCancelActionLabel(__('Close'))
|
|
->form([
|
|
Textarea::make('webhook_url')
|
|
->label(__('Webhook URL'))
|
|
->rows(2)
|
|
->disabled()
|
|
->dehydrated(false),
|
|
Textarea::make('deploy_key')
|
|
->label(__('Deploy Key'))
|
|
->rows(3)
|
|
->disabled()
|
|
->dehydrated(false),
|
|
])
|
|
->fillForm(fn (GitDeploymentModel $record): array => [
|
|
'webhook_url' => $this->getWebhookUrl($record),
|
|
'deploy_key' => $this->getDeployKey(),
|
|
]),
|
|
Action::make('edit')
|
|
->label(__('Edit'))
|
|
->icon('heroicon-o-pencil-square')
|
|
->color('gray')
|
|
->modalHeading(__('Edit Deployment'))
|
|
->form($this->getDeploymentForm())
|
|
->fillForm(fn (GitDeploymentModel $record): array => [
|
|
'domain_id' => $record->domain_id,
|
|
'repo_url' => $record->repo_url,
|
|
'branch' => $record->branch,
|
|
'deploy_path' => $record->deploy_path,
|
|
'auto_deploy' => $record->auto_deploy,
|
|
'deploy_script' => $record->deploy_script,
|
|
'framework_preset' => 'custom',
|
|
])
|
|
->action(function (GitDeploymentModel $record, array $data): void {
|
|
$record->update([
|
|
'domain_id' => $data['domain_id'],
|
|
'repo_url' => $data['repo_url'],
|
|
'branch' => $data['branch'],
|
|
'deploy_path' => $data['deploy_path'],
|
|
'auto_deploy' => $data['auto_deploy'] ?? false,
|
|
'deploy_script' => $data['deploy_script'] ?? null,
|
|
]);
|
|
|
|
Notification::make()->title(__('Deployment updated'))->success()->send();
|
|
}),
|
|
Action::make('delete')
|
|
->label(__('Delete'))
|
|
->icon('heroicon-o-trash')
|
|
->color('danger')
|
|
->requiresConfirmation()
|
|
->action(function (GitDeploymentModel $record): void {
|
|
$record->delete();
|
|
Notification::make()->title(__('Deployment deleted'))->success()->send();
|
|
}),
|
|
])
|
|
->emptyStateHeading(__('No deployments yet'))
|
|
->emptyStateDescription(__('Add a repository to deploy to your domain'))
|
|
->emptyStateIcon('heroicon-o-code-bracket-square');
|
|
}
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
Action::make('addDeployment')
|
|
->label(__('Add Deployment'))
|
|
->icon('heroicon-o-plus')
|
|
->color('primary')
|
|
->form($this->getDeploymentForm())
|
|
->action(function (array $data): void {
|
|
GitDeploymentModel::create([
|
|
'user_id' => Auth::id(),
|
|
'domain_id' => $data['domain_id'],
|
|
'repo_url' => $data['repo_url'],
|
|
'branch' => $data['branch'],
|
|
'deploy_path' => $data['deploy_path'],
|
|
'auto_deploy' => $data['auto_deploy'] ?? false,
|
|
'deploy_script' => $data['deploy_script'] ?? null,
|
|
'secret_token' => Str::random(40),
|
|
'last_status' => 'never',
|
|
]);
|
|
|
|
Notification::make()->title(__('Deployment created'))->success()->send();
|
|
$this->resetTable();
|
|
}),
|
|
Action::make('deployKey')
|
|
->label(__('Deploy Key'))
|
|
->icon('heroicon-o-key')
|
|
->color('gray')
|
|
->modalHeading(__('SSH Deploy Key'))
|
|
->modalSubmitAction(false)
|
|
->modalCancelActionLabel(__('Close'))
|
|
->form([
|
|
Textarea::make('public_key')
|
|
->label(__('Public Key'))
|
|
->rows(3)
|
|
->disabled()
|
|
->dehydrated(false),
|
|
])
|
|
->fillForm(fn (): array => ['public_key' => $this->getDeployKey()]),
|
|
];
|
|
}
|
|
|
|
protected function getDeploymentForm(): array
|
|
{
|
|
return [
|
|
Select::make('domain_id')
|
|
->label(__('Domain'))
|
|
->options($this->getDomainOptions())
|
|
->searchable()
|
|
->required()
|
|
->live()
|
|
->afterStateUpdated(function ($state, callable $set): void {
|
|
if (! $state) {
|
|
return;
|
|
}
|
|
$domain = Domain::where('id', $state)->where('user_id', Auth::id())->first();
|
|
if ($domain) {
|
|
$set('deploy_path', $domain->document_root);
|
|
}
|
|
}),
|
|
TextInput::make('repo_url')
|
|
->label(__('Repository URL'))
|
|
->placeholder('git@github.com:org/repo.git')
|
|
->required(),
|
|
TextInput::make('branch')
|
|
->label(__('Branch'))
|
|
->default('main')
|
|
->required(),
|
|
TextInput::make('deploy_path')
|
|
->label(__('Deploy Path'))
|
|
->helperText(__('Must be inside your home directory'))
|
|
->required(),
|
|
Select::make('framework_preset')
|
|
->label(__('Framework Preset'))
|
|
->options([
|
|
'custom' => __('Custom'),
|
|
'laravel' => __('Laravel'),
|
|
'symfony' => __('Symfony'),
|
|
])
|
|
->default('custom')
|
|
->live()
|
|
->afterStateUpdated(function ($state, callable $set): void {
|
|
if ($state === 'laravel') {
|
|
$set('deploy_script', "composer install --no-dev --optimize-autoloader\nphp artisan migrate --force\nphp artisan config:cache\nphp artisan route:cache\nphp artisan view:cache");
|
|
} elseif ($state === 'symfony') {
|
|
$set('deploy_script', "composer install --no-dev --optimize-autoloader\nphp bin/console cache:clear --no-warmup\nphp bin/console cache:warmup");
|
|
}
|
|
}),
|
|
Textarea::make('deploy_script')
|
|
->label(__('Deploy Script (optional)'))
|
|
->rows(6)
|
|
->helperText(__('Run after code is deployed. Leave empty to skip.')),
|
|
Toggle::make('auto_deploy')
|
|
->label(__('Enable auto-deploy from webhook'))
|
|
->default(false),
|
|
];
|
|
}
|
|
}
|