Add site redirects (#552)

* feat(redirects): add redirects to sites

* chore(style): fixed coding style issues

* style: fix php-stan docblocks

* style: pint cleanup

* tests: fixed redirect test suite

* feat: vhosts include additional configs

* fix: use exact location matching

* - add enums
- use queues
- use vhost rather than separate conf files
- vhost formatter
- cleanup

* generate docs

---------

Co-authored-by: Saeed Vaziry <mr.saeedvaziry@gmail.com>
This commit is contained in:
Jamie Wood
2025-03-31 16:30:57 +01:00
committed by GitHub
parent 7882d2022c
commit f483f7fdca
53 changed files with 6944 additions and 4495 deletions

View File

@ -2,6 +2,7 @@
namespace App\Web\Pages\Servers\Sites;
use App\Models\Redirect;
use App\Models\ServerLog;
use App\Models\Site;
use App\Models\Ssl;
@ -75,6 +76,16 @@ public function getSecondSubNavigation(): array
]));
}
if ($user->can('view', [Redirect::class, $this->site, $this->server])) {
$items[] = NavigationItem::make(Pages\Redirects\Index::getNavigationLabel())
->icon('heroicon-o-arrows-right-left')
->isActiveWhen(fn () => request()->routeIs(Pages\Redirects\Index::getRouteName()))
->url(Pages\Redirects\Index::getUrl(parameters: [
'server' => $this->server,
'site' => $this->site,
]));
}
return [
NavigationGroup::make()
->items($items),

View File

@ -0,0 +1,43 @@
<?php
namespace App\Web\Pages\Servers\Sites\Pages\Redirects\Actions;
use App\Actions\Redirect\CreateRedirect;
use App\Models\Site;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Livewire\Component;
class Create
{
/**
* @return array<int, mixed>
*/
public static function form(Site $site): array
{
return [
TextInput::make('from')
->rules(CreateRedirect::rules($site)['from']),
TextInput::make('to')
->rules(CreateRedirect::rules($site)['to']),
Select::make('mode')
->rules(CreateRedirect::rules($site)['mode'])
->options([
'301' => '301 - Moved Permanently',
'302' => '302 - Found',
'307' => '307 - Temporary Redirect',
'308' => '308 - Permanent Redirect',
]),
];
}
/**
* @param array<string, mixed> $data
*/
public static function action(Component $component, array $data, Site $site): void
{
app(CreateRedirect::class)->create($site, $data);
$component->dispatch('$refresh');
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Web\Pages\Servers\Sites\Pages\Redirects;
use App\Models\Redirect;
use App\Web\Pages\Servers\Sites\Page;
use App\Web\Pages\Servers\Sites\Pages\Redirects\Actions\Create;
use App\Web\Pages\Servers\Sites\Pages\Redirects\Widgets\RedirectsList;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Support\Enums\MaxWidth;
class Index extends Page
{
protected static ?string $slug = 'servers/{server}/sites/{site}/redirects';
protected static ?string $title = 'Redirects';
public function mount(): void
{
$this->authorize('view', [Redirect::class, $this->site, $this->server]);
}
public function getWidgets(): array
{
return [
[
RedirectsList::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/docs/sites/redirects')
->openUrlInNewTab(),
CreateAction::make('create')
->icon('heroicon-o-plus')
->createAnother(false)
->modalWidth(MaxWidth::ExtraLarge)
->label('New Redirect')
->form(Create::form($this->site))
->using(fn (array $data) => run_action($this, function () use ($data): void {
Create::action($this, $data, $this->site);
})),
];
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Web\Pages\Servers\Sites\Pages\Redirects\Widgets;
use App\Actions\Redirect\DeleteRedirect;
use App\Models\Redirect;
use App\Models\Site;
use App\Models\User;
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;
class RedirectsList extends Widget
{
public Site $site;
/**
* @var array<string>
*/
protected $listeners = ['$refresh'];
/**
* @return Builder<Redirect>
*/
protected function getTableQuery(): Builder
{
return Redirect::query()->where('site_id', $this->site->id);
}
protected function getTableColumns(): array
{
auth()->user();
return [
TextColumn::make('from')
->limit(40)
->tooltip(fn (Redirect $redirect) => $redirect->from)
->searchable()
->copyable(),
TextColumn::make('to')
->limit(40)
->tooltip(fn (Redirect $redirect) => $redirect->to)
->searchable()
->copyable(),
TextColumn::make('mode')
->searchable()
->sortable(),
TextColumn::make('status')
->label('Status')
->badge()
->color(fn (Redirect $redirect) => Redirect::$statusColors[$redirect->status])
->searchable()
->sortable(),
TextColumn::make('created_at')
->formatStateUsing(fn (Redirect $record) => $record->created_at)
->sortable(),
];
}
public function table(Table $table): Table
{
/** @var User $user */
$user = auth()->user();
return $table
->heading(null)
->query($this->getTableQuery())
->columns($this->getTableColumns())
->actions([
DeleteAction::make('delete')
->hiddenLabel()
->tooltip('Delete')
->icon('heroicon-o-trash')
->authorize(fn (Redirect $record) => $user->can('delete', [$this->site, $this->site->server]))
->using(function (Redirect $record): void {
run_action($this, function () use ($record): void {
app(DeleteRedirect::class)->delete($this->site, $record);
$this->dispatch('$refresh');
});
}),
]);
}
}