mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-05 07:52:34 +00:00
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:
75
app/Actions/Redirect/CreateRedirect.php
Normal file
75
app/Actions/Redirect/CreateRedirect.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Redirect;
|
||||
|
||||
use App\Enums\RedirectStatus;
|
||||
use App\Models\Redirect;
|
||||
use App\Models\Service;
|
||||
use App\Models\Site;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CreateRedirect
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function create(Site $site, array $input): Redirect
|
||||
{
|
||||
$redirect = new Redirect;
|
||||
|
||||
$redirect->site_id = $site->id;
|
||||
$redirect->from = $input['from'];
|
||||
$redirect->to = $input['to'];
|
||||
$redirect->mode = $input['mode'];
|
||||
$redirect->status = RedirectStatus::CREATING;
|
||||
$redirect->save();
|
||||
|
||||
dispatch(function () use ($site, $redirect): void {
|
||||
/** @var Service $service */
|
||||
$service = $site->server->webserver();
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $service->handler();
|
||||
$webserver->updateVHost($site);
|
||||
$redirect->status = RedirectStatus::READY;
|
||||
$redirect->save();
|
||||
})
|
||||
->catch(function () use ($redirect): void {
|
||||
$redirect->status = RedirectStatus::FAILED;
|
||||
$redirect->save();
|
||||
})
|
||||
->onConnection('ssh');
|
||||
|
||||
return $redirect->refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string>>
|
||||
*/
|
||||
public static function rules(Site $site): array
|
||||
{
|
||||
return [
|
||||
'from' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
'not_regex:/^http(s)?:\/\//',
|
||||
Rule::unique('redirects', 'from')->where('site_id', $site->id),
|
||||
],
|
||||
'to' => [
|
||||
'required',
|
||||
'url:http,https',
|
||||
],
|
||||
'mode' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::in([
|
||||
301,
|
||||
302,
|
||||
307,
|
||||
308,
|
||||
]),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
30
app/Actions/Redirect/DeleteRedirect.php
Normal file
30
app/Actions/Redirect/DeleteRedirect.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Redirect;
|
||||
|
||||
use App\Enums\RedirectStatus;
|
||||
use App\Models\Redirect;
|
||||
use App\Models\Service;
|
||||
use App\Models\Site;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
|
||||
class DeleteRedirect
|
||||
{
|
||||
public function delete(Site $site, Redirect $redirect): void
|
||||
{
|
||||
$redirect->status = RedirectStatus::DELETING;
|
||||
$redirect->save();
|
||||
|
||||
dispatch(function () use ($site, $redirect): void {
|
||||
/** @var Service $service */
|
||||
$service = $site->server->webserver();
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $service->handler();
|
||||
$webserver->updateVHost($site);
|
||||
$redirect->delete();
|
||||
})->catch(function () use ($redirect): void {
|
||||
$redirect->status = RedirectStatus::FAILED;
|
||||
$redirect->save();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
}
|
14
app/Enums/RedirectStatus.php
Normal file
14
app/Enums/RedirectStatus.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
final class RedirectStatus
|
||||
{
|
||||
const CREATING = 'creating';
|
||||
|
||||
const READY = 'ready';
|
||||
|
||||
const DELETING = 'deleting';
|
||||
|
||||
const FAILED = 'failed';
|
||||
}
|
87
app/Http/Controllers/API/RedirectController.php
Normal file
87
app/Http/Controllers/API/RedirectController.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Actions\Redirect\CreateRedirect;
|
||||
use App\Actions\Redirect\DeleteRedirect;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\RedirectResource;
|
||||
use App\Models\Project;
|
||||
use App\Models\Redirect;
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
use Illuminate\Http\Response as HttpResponse;
|
||||
use Knuckles\Scribe\Attributes\BodyParam;
|
||||
use Knuckles\Scribe\Attributes\Endpoint;
|
||||
use Knuckles\Scribe\Attributes\Group;
|
||||
use Knuckles\Scribe\Attributes\Response;
|
||||
use Knuckles\Scribe\Attributes\ResponseFromApiResource;
|
||||
use Spatie\RouteAttributes\Attributes\Delete;
|
||||
use Spatie\RouteAttributes\Attributes\Get;
|
||||
use Spatie\RouteAttributes\Attributes\Middleware;
|
||||
use Spatie\RouteAttributes\Attributes\Post;
|
||||
use Spatie\RouteAttributes\Attributes\Prefix;
|
||||
|
||||
#[Prefix('api/projects/{project}/servers/{server}/sites/{site}/redirects')]
|
||||
#[Middleware(['auth:sanctum', 'can-see-project'])]
|
||||
#[Group(name: 'redirects')]
|
||||
class RedirectController extends Controller
|
||||
{
|
||||
#[Get('/', name: 'api.projects.servers.sites.redirects.index', middleware: 'ability:read')]
|
||||
#[Endpoint(title: 'index', description: 'Get all redirects.')]
|
||||
#[ResponseFromApiResource(RedirectResource::class, Redirect::class, collection: true, paginate: 25)]
|
||||
public function index(Project $project, Server $server, Site $site): ResourceCollection
|
||||
{
|
||||
$this->authorize('view', [Redirect::class, $site, $server]);
|
||||
|
||||
$this->validateRoute($project, $server, $site);
|
||||
|
||||
return RedirectResource::collection($site->redirects()->simplePaginate(25));
|
||||
}
|
||||
|
||||
#[Post('/', name: 'api.projects.servers.sites.redirects.create', middleware: 'ability:write')]
|
||||
#[Endpoint(title: 'create', description: 'Create a new redirect.')]
|
||||
#[BodyParam(name: 'from', required: true)]
|
||||
#[BodyParam(name: 'to', required: true)]
|
||||
#[BodyParam(name: 'mode', required: true, enum: [301, 302, 307, 308])]
|
||||
#[Response(status: 200)]
|
||||
public function create(Request $request, Project $project, Server $server, Site $site): RedirectResource
|
||||
{
|
||||
$this->authorize('create', [Redirect::class, $site, $server]);
|
||||
|
||||
$this->validateRoute($project, $server, $site);
|
||||
|
||||
$this->validate($request, CreateRedirect::rules($site));
|
||||
|
||||
$redirect = app(CreateRedirect::class)->create($site, $request->all());
|
||||
|
||||
return new RedirectResource($redirect);
|
||||
}
|
||||
|
||||
#[Delete('/{redirect}', name: 'api.projects.servers.sites.redirects.delete', middleware: 'ability:write')]
|
||||
#[Endpoint(title: 'delete', description: 'Delete a redirect.')]
|
||||
#[Response(status: 204)]
|
||||
public function delete(Project $project, Server $server, Site $site, Redirect $redirect): HttpResponse
|
||||
{
|
||||
$this->authorize('delete', [Redirect::class, $site, $server]);
|
||||
|
||||
$this->validateRoute($project, $server, $site);
|
||||
|
||||
app(DeleteRedirect::class)->delete($site, $redirect);
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
private function validateRoute(Project $project, Server $server, Site $site): void
|
||||
{
|
||||
if ($project->id !== $server->project_id) {
|
||||
abort(404, 'Server not found in project');
|
||||
}
|
||||
|
||||
if ($site->server_id !== $server->id) {
|
||||
abort(404, 'Site not found in server');
|
||||
}
|
||||
}
|
||||
}
|
28
app/Http/Resources/RedirectResource.php
Normal file
28
app/Http/Resources/RedirectResource.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Redirect;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin Redirect */
|
||||
class RedirectResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'site_id' => $this->site_id,
|
||||
'mode' => $this->mode,
|
||||
'from' => $this->from,
|
||||
'to' => $this->to,
|
||||
'status' => $this->status,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
49
app/Models/Redirect.php
Normal file
49
app/Models/Redirect.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\RedirectStatus;
|
||||
use Database\Factories\RedirectFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $site_id
|
||||
* @property string $from
|
||||
* @property string $to
|
||||
* @property string $mode
|
||||
* @property string $status
|
||||
* @property Site $site
|
||||
*/
|
||||
class Redirect extends AbstractModel
|
||||
{
|
||||
/** @use HasFactory<RedirectFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'site_id',
|
||||
'from',
|
||||
'to',
|
||||
'mode',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $statusColors = [
|
||||
RedirectStatus::CREATING => 'warning',
|
||||
RedirectStatus::READY => 'success',
|
||||
RedirectStatus::DELETING => 'warning',
|
||||
RedirectStatus::FAILED => 'danger',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Site, covariant $this>
|
||||
*/
|
||||
public function site(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Site::class);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\RedirectStatus;
|
||||
use App\Enums\SiteStatus;
|
||||
use App\Enums\SslStatus;
|
||||
use App\Exceptions\FailedToDestroyGitHook;
|
||||
@ -52,6 +53,8 @@
|
||||
* @property ?SourceControl $sourceControl
|
||||
* @property Collection<int, LoadBalancerServer> $loadBalancerServers
|
||||
* @property Project $project
|
||||
* @property Collection<int, Redirect> $redirects
|
||||
* @property Collection<int, Redirect> $activeRedirects
|
||||
*/
|
||||
class Site extends AbstractModel
|
||||
{
|
||||
@ -419,4 +422,20 @@ public function getSshUsers(): array
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<Redirect, covariant $this>
|
||||
*/
|
||||
public function redirects(): HasMany
|
||||
{
|
||||
return $this->hasMany(Redirect::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<Redirect, covariant $this>
|
||||
*/
|
||||
public function activeRedirects(): HasMany
|
||||
{
|
||||
return $this->redirects()->whereIn('status', [RedirectStatus::CREATING, RedirectStatus::READY]);
|
||||
}
|
||||
}
|
||||
|
35
app/Policies/RedirectPolicy.php
Normal file
35
app/Policies/RedirectPolicy.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\Models\User;
|
||||
|
||||
class RedirectPolicy
|
||||
{
|
||||
public function view(User $user, Site $site, Server $server): bool
|
||||
{
|
||||
if ($user->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $site->server->project->users->contains($user);
|
||||
}
|
||||
|
||||
public function create(User $user, Site $site, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $site->server->project->users->contains($user))
|
||||
&& $site->server_id === $server->id
|
||||
&& $site->server->isReady()
|
||||
&& $site->server->webserver();
|
||||
}
|
||||
|
||||
public function delete(User $user, Site $site, Server $server): bool
|
||||
{
|
||||
return ($user->isAdmin() || $site->server->project->users->contains($user))
|
||||
&& $site->server_id === $server->id
|
||||
&& $site->server->isReady()
|
||||
&& $site->server->webserver();
|
||||
}
|
||||
}
|
@ -80,18 +80,14 @@ public function createVHost(Site $site): void
|
||||
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/nginx/sites-available/'.$site->domain,
|
||||
view('ssh.services.webserver.nginx.vhost', [
|
||||
'site' => $site,
|
||||
]),
|
||||
$this->generateVhost($site),
|
||||
'root'
|
||||
);
|
||||
|
||||
$this->service->server->ssh()->exec(
|
||||
view('ssh.services.webserver.nginx.create-vhost', [
|
||||
'domain' => $site->domain,
|
||||
'vhost' => view('ssh.services.webserver.nginx.vhost', [
|
||||
'site' => $site,
|
||||
]),
|
||||
'vhost' => $this->generateVhost($site),
|
||||
]),
|
||||
'create-vhost',
|
||||
$site->id
|
||||
@ -105,9 +101,7 @@ public function updateVHost(Site $site, ?string $vhost = null): void
|
||||
{
|
||||
$this->service->server->ssh()->write(
|
||||
'/etc/nginx/sites-available/'.$site->domain,
|
||||
$vhost ?? view('ssh.services.webserver.nginx.vhost', [
|
||||
'site' => $site,
|
||||
]),
|
||||
$vhost ?? $this->generateVhost($site),
|
||||
'root'
|
||||
);
|
||||
|
||||
@ -209,4 +203,13 @@ public function removeSSL(Ssl $ssl): void
|
||||
|
||||
$this->updateVHost($ssl->site);
|
||||
}
|
||||
|
||||
private function generateVhost(Site $site): string
|
||||
{
|
||||
$vhost = view('ssh.services.webserver.nginx.vhost', [
|
||||
'site' => $site,
|
||||
]);
|
||||
|
||||
return format_nginx_config($vhost);
|
||||
}
|
||||
}
|
||||
|
@ -218,3 +218,36 @@ function home_path(string $user): string
|
||||
|
||||
return '/home/'.$user;
|
||||
}
|
||||
|
||||
function format_nginx_config(string $config): string
|
||||
{
|
||||
$lines = explode("\n", trim($config));
|
||||
$indent = 0;
|
||||
$formattedLines = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$trimmed = trim($line);
|
||||
|
||||
// Preserve empty lines exactly as they are
|
||||
if ($trimmed === '') {
|
||||
$formattedLines[] = '';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If line is a closing brace, decrease indentation first
|
||||
if ($trimmed === '}') {
|
||||
$indent--;
|
||||
}
|
||||
|
||||
// Apply indentation
|
||||
$formattedLines[] = str_repeat(' ', max(0, $indent)).$trimmed;
|
||||
|
||||
// If line contains an opening brace, increase indentation
|
||||
if (str_ends_with($trimmed, '{')) {
|
||||
$indent++;
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $formattedLines)."\n";
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
55
app/Web/Pages/Servers/Sites/Pages/Redirects/Index.php
Normal file
55
app/Web/Pages/Servers/Sites/Pages/Redirects/Index.php
Normal 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);
|
||||
})),
|
||||
];
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user