Files
jabali-panel/app/Filament/Jabali/Pages/Domains.php
2026-01-24 19:36:46 +02:00

796 lines
35 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Jabali\Pages;
use App\Filament\Concerns\HasPageTour;
use App\Models\Domain;
use App\Models\DomainHotlinkSetting;
use App\Models\DomainRedirect;
use App\Services\Agent\AgentClient;
use BackedEnum;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Components\Radio;
use Filament\Forms\Components\Repeater;
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\Schemas\Components\Grid;
use Filament\Support\Enums\Width;
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;
class Domains extends Page implements HasForms, HasActions, HasTable
{
use InteractsWithForms;
use InteractsWithActions;
use InteractsWithTable;
use HasPageTour;
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-globe-alt';
public static function getNavigationLabel(): string
{
return __('Domains');
}
protected static ?int $navigationSort = 2;
protected string $view = 'filament.jabali.pages.domains';
protected ?AgentClient $agent = null;
public function getTitle(): string|Htmlable
{
return __('Domains');
}
public function getAgent(): AgentClient
{
if ($this->agent === null) {
$this->agent = new AgentClient();
}
return $this->agent;
}
public function getUsername(): string
{
return Auth::user()->username;
}
public function table(Table $table): Table
{
return $table
->query(Domain::query()->where('user_id', Auth::id()))
->columns([
TextColumn::make('domain')
->label(__('Domain'))
->icon('heroicon-o-globe-alt')
->iconColor('primary')
->description(fn (Domain $record) => $record->document_root)
->url(fn (Domain $record) => 'http://' . $record->domain, shouldOpenInNewTab: true)
->searchable()
->sortable(),
IconColumn::make('is_active')
->label(__('Status'))
->boolean()
->trueIcon('heroicon-o-check-circle')
->falseIcon('heroicon-o-x-circle')
->trueColor('success')
->falseColor('danger'),
IconColumn::make('ssl_enabled')
->label(__('SSL'))
->boolean()
->trueIcon('heroicon-m-lock-closed')
->falseIcon('heroicon-m-lock-open')
->trueColor('success')
->falseColor('warning'),
IconColumn::make('page_cache_enabled')
->label(__('Page Cache'))
->boolean()
->trueIcon('heroicon-o-bolt')
->falseIcon('heroicon-o-bolt-slash')
->trueColor('success')
->falseColor('gray'),
TextColumn::make('redirects_count')
->label(__('Redirects'))
->counts('redirects')
->badge()
->color('info'),
TextColumn::make('created_at')
->label(__('Created'))
->date('M d, Y')
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->recordActions([
ActionGroup::make([
Action::make('files')
->label(__('Files'))
->icon('heroicon-o-folder')
->color('info')
->action(fn (Domain $record) => $this->openFileManager($record)),
Action::make('redirects')
->label(__('Redirects'))
->icon('heroicon-o-arrow-right-circle')
->color('warning')
->modalHeading(fn (Domain $record) => __('Redirects for :domain', ['domain' => $record->domain]))
->modalDescription(__('Redirect this domain to another domain or set up page redirects'))
->modalWidth(Width::FourExtraLarge)
->modalSubmitActionLabel(__('Save Redirects'))
->form(fn (Domain $record) => $this->getRedirectsForm($record))
->fillForm(fn (Domain $record) => $this->getRedirectsFormData($record))
->action(fn (Domain $record, array $data) => $this->saveRedirects($record, $data)),
Action::make('hotlink')
->label(__('Hotlink Protection'))
->icon('heroicon-o-shield-check')
->color('success')
->modalHeading(fn (Domain $record) => __('Hotlink Protection for :domain', ['domain' => $record->domain]))
->modalDescription(__('Prevent other websites from directly linking to your files'))
->modalWidth(Width::TwoExtraLarge)
->form($this->getHotlinkForm())
->fillForm(fn (Domain $record) => $this->getHotlinkFormData($record))
->action(fn (Domain $record, array $data) => $this->saveHotlinkSettings($record, $data)),
Action::make('index')
->label(__('Index Manager'))
->icon('heroicon-o-document-text')
->color('gray')
->modalHeading(fn (Domain $record) => __('Index Manager for :domain', ['domain' => $record->domain]))
->modalDescription(__('Set the default directory index files'))
->modalWidth(Width::Medium)
->form($this->getIndexForm())
->fillForm(fn (Domain $record) => ['directory_index' => $record->directory_index])
->action(fn (Domain $record, array $data) => $this->saveIndexSettings($record, $data)),
])
->label(__('Settings'))
->icon('heroicon-o-cog-6-tooth')
->color('gray')
->button(),
Action::make('toggle')
->label(fn (Domain $record) => $record->is_active ? __('Disable') : __('Enable'))
->icon(fn (Domain $record) => $record->is_active ? 'heroicon-o-no-symbol' : 'heroicon-o-check')
->color(fn (Domain $record) => $record->is_active ? 'warning' : 'success')
->action(fn (Domain $record) => $this->toggleDomain($record)),
Action::make('delete')
->label(__('Delete'))
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->modalHeading(__('Delete Domain'))
->modalDescription(fn (Domain $record) => __('Are you sure you want to delete') . " '{$record->domain}'? " . __('This will also delete the following associated data:'))
->modalIcon('heroicon-o-trash')
->modalIconColor('danger')
->modalSubmitActionLabel(__('Delete Domain'))
->modalWidth(Width::Large)
->form(fn (Domain $record): array => [
Toggle::make('delete_files')
->label(__('Delete all domain files'))
->helperText(__('Permanently delete all files in the domain folder'))
->default(true),
Toggle::make('delete_dns')
->label(__('Delete DNS records') . ' (' . $record->dnsRecords()->count() . ')')
->helperText(__('Remove all DNS records for this domain'))
->default(true)
->visible(fn () => $record->dnsRecords()->exists()),
Toggle::make('delete_email')
->label(__('Delete email accounts') . ' (' . ($record->emailDomain?->mailboxes()->count() ?? 0) . ')')
->helperText(__('Remove all mailboxes and email configuration'))
->default(true)
->visible(fn () => $record->emailDomain()->exists()),
Toggle::make('delete_ssl')
->label(__('Delete SSL certificate'))
->helperText(__('Remove SSL certificate for this domain'))
->default(true)
->visible(fn () => $record->sslCertificate()->exists()),
Toggle::make('delete_wordpress')
->label(__('Delete WordPress sites'))
->helperText(__('Remove all WordPress installations on this domain'))
->default(true),
])
->action(fn (Domain $record, array $data) => $this->deleteDomain($record, $data)),
])
->emptyStateHeading(__('No domains yet'))
->emptyStateDescription(__('Click "Add Domain" to add your first domain'))
->emptyStateIcon('heroicon-o-globe-alt')
->striped()
->defaultSort('created_at', 'desc');
}
protected function getRedirectsForm(Domain $record): array
{
return [
// Domain-wide redirect
Toggle::make('domain_redirect_enabled')
->label(__('Redirect Entire Domain'))
->helperText(__('Redirect all traffic from this domain to another domain'))
->live()
->columnSpanFull(),
Grid::make()
->schema([
TextInput::make('domain_redirect_url')
->label(__('Redirect To'))
->placeholder('https://newdomain.com')
->helperText(__('All requests to this domain will be redirected to this URL'))
->url()
->required(fn ($get) => $get('domain_redirect_enabled'))
->columnSpan(['default' => 2, 'md' => 1]),
Select::make('domain_redirect_type')
->label(__('Redirect Type'))
->options([
'301' => __('Permanent (301) - SEO friendly'),
'302' => __('Temporary (302)'),
])
->default('301')
->required(fn ($get) => $get('domain_redirect_enabled'))
->columnSpan(['default' => 2, 'md' => 1]),
])
->columns(['default' => 2, 'md' => 2])
->visible(fn ($get) => $get('domain_redirect_enabled')),
// Page redirects
Repeater::make('redirects')
->label(__('Page Redirects'))
->helperText(__('Redirect specific paths to other URLs'))
->schema([
Grid::make()
->schema([
TextInput::make('source_path')
->label(__('Source Path'))
->placeholder('/old-page')
->helperText(__('Path to redirect from (e.g., /old-page)'))
->required()
->columnSpan(['default' => 2, 'md' => 1]),
TextInput::make('destination_url')
->label(__('Destination URL'))
->placeholder('https://example.com/new-page')
->helperText(__('Full URL to redirect to'))
->required()
->url()
->columnSpan(['default' => 2, 'md' => 1]),
])
->columns(['default' => 2, 'md' => 2]),
Grid::make()
->schema([
Select::make('redirect_type')
->label(__('Type'))
->options([
'301' => __('Permanent (301)'),
'302' => __('Temporary (302)'),
])
->default('301')
->required()
->columnSpan(['default' => 2, 'sm' => 1]),
Toggle::make('is_wildcard')
->label(__('Wildcard'))
->helperText(__('Match all paths starting with source'))
->columnSpan(['default' => 2, 'sm' => 1]),
Toggle::make('is_active')
->label(__('Active'))
->default(true)
->columnSpan(['default' => 2, 'sm' => 1]),
])
->columns(['default' => 2, 'sm' => 3]),
])
->itemLabel(fn (array $state): ?string => ($state['source_path'] ?? '') . ' → ' . ($state['redirect_type'] ?? '301'))
->collapsible()
->collapsed(fn () => $record->redirects()->count() > 3)
->addActionLabel(__('Add Page Redirect'))
->reorderable()
->defaultItems(0)
->visible(fn ($get) => !$get('domain_redirect_enabled')),
];
}
protected function getRedirectsFormData(Domain $record): array
{
// Check if there's a domain-wide redirect (source_path = '/*' or '*')
$domainRedirect = $record->redirects()
->whereIn('source_path', ['/*', '*', '/'])
->where('is_wildcard', true)
->first();
return [
'domain_redirect_enabled' => $domainRedirect !== null,
'domain_redirect_url' => $domainRedirect?->destination_url ?? '',
'domain_redirect_type' => $domainRedirect?->redirect_type ?? '301',
'redirects' => $record->redirects()
->whereNotIn('source_path', ['/*', '*', '/'])
->orWhere('is_wildcard', false)
->get()
->map(fn ($r) => [
'id' => $r->id,
'source_path' => $r->source_path,
'destination_url' => $r->destination_url,
'redirect_type' => $r->redirect_type,
'is_wildcard' => $r->is_wildcard,
'is_active' => $r->is_active,
])->toArray(),
];
}
protected function getHotlinkForm(): array
{
return [
Toggle::make('is_enabled')
->label(__('Enable Hotlink Protection'))
->helperText(__('Block other websites from directly linking to your images and files'))
->live(),
Grid::make()
->schema([
Textarea::make('allowed_domains')
->label(__('Allowed Domains'))
->helperText(__('One domain per line that can link to your files (your own domain is always allowed)'))
->placeholder("example.com\ntrusted-site.com")
->rows(4)
->columnSpan(['default' => 2, 'md' => 1]),
TextInput::make('protected_extensions')
->label(__('Protected File Extensions'))
->helperText(__('Comma-separated list of file extensions to protect'))
->placeholder('jpg,jpeg,png,gif,webp,svg,mp4,mp3,pdf')
->default(DomainHotlinkSetting::getDefaultExtensions())
->columnSpan(['default' => 2, 'md' => 1]),
])
->columns(['default' => 2, 'md' => 2])
->visible(fn ($get) => $get('is_enabled')),
Grid::make()
->schema([
Toggle::make('block_blank_referrer')
->label(__('Block Blank Referrer'))
->helperText(__('Block requests with no referrer header'))
->default(true)
->columnSpan(['default' => 2, 'md' => 1]),
TextInput::make('redirect_url')
->label(__('Redirect URL (Optional)'))
->helperText(__('Redirect blocked requests to this URL instead of showing an error'))
->placeholder('https://example.com/hotlink-blocked.png')
->url()
->columnSpan(['default' => 2, 'md' => 1]),
])
->columns(['default' => 2, 'md' => 2])
->visible(fn ($get) => $get('is_enabled')),
];
}
protected function getHotlinkFormData(Domain $record): array
{
$setting = $record->hotlinkSetting;
if (!$setting) {
return [
'is_enabled' => false,
'allowed_domains' => '',
'block_blank_referrer' => true,
'protected_extensions' => DomainHotlinkSetting::getDefaultExtensions(),
'redirect_url' => '',
];
}
return [
'is_enabled' => $setting->is_enabled,
'allowed_domains' => $setting->allowed_domains,
'block_blank_referrer' => $setting->block_blank_referrer,
'protected_extensions' => $setting->protected_extensions,
'redirect_url' => $setting->redirect_url ?? '',
];
}
protected function getIndexForm(): array
{
return [
Radio::make('directory_index')
->label(__('Directory Index Priority'))
->helperText(__('Choose which file should be served as the default index'))
->options([
'index.php index.html' => __('PHP first (index.php, then index.html)'),
'index.html index.php' => __('HTML first (index.html, then index.php)'),
'index.php' => __('PHP only (index.php)'),
'index.html' => __('HTML only (index.html)'),
'index.php index.html index.htm' => __('PHP, HTML, HTM (full support)'),
])
->default('index.php index.html')
->required(),
];
}
protected function getHeaderActions(): array
{
return [
$this->getTourAction(),
$this->createDomainAction(),
];
}
protected function createDomainAction(): Action
{
return Action::make('createDomain')
->label(__('Add Domain'))
->icon('heroicon-o-plus-circle')
->color('primary')
->modalHeading(__('Add Domain'))
->modalDescription(__('Add a new domain to your hosting account'))
->modalIcon('heroicon-o-globe-alt')
->modalIconColor('primary')
->modalSubmitActionLabel(__('Add Domain'))
->form([
TextInput::make('domain')
->label(__('Domain Name'))
->placeholder(__('example.com'))
->required()
->regex('/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/')
->helperText(__('Enter the domain name without http:// or www')),
])
->action(function (array $data): void {
try {
$result = $this->getAgent()->domainCreate($this->getUsername(), $data['domain']);
if ($result['success'] ?? false) {
Domain::create([
'user_id' => Auth::id(),
'domain' => $data['domain'],
'document_root' => '/home/' . $this->getUsername() . '/domains/' . $data['domain'] . '/public_html',
'is_active' => true,
'ssl_enabled' => false,
'directory_index' => 'index.php index.html',
'page_cache_enabled' => false,
]);
Notification::make()
->title(__('Domain created!'))
->body(__('Your domain is now active.'))
->success()
->send();
} else {
throw new Exception($result['error'] ?? 'Unknown error');
}
} catch (Exception $e) {
Notification::make()
->title(__('Error creating domain'))
->body($e->getMessage())
->danger()
->send();
}
});
}
public function saveRedirects(Domain $domain, array $data): void
{
try {
$existingIds = [];
// Handle domain-wide redirect
if ($data['domain_redirect_enabled'] ?? false) {
// Delete all existing redirects and create a single domain-wide redirect
$domain->redirects()->delete();
$redirect = $domain->redirects()->create([
'source_path' => '/*',
'destination_url' => $data['domain_redirect_url'],
'redirect_type' => $data['domain_redirect_type'] ?? '301',
'is_wildcard' => true,
'is_active' => true,
]);
$existingIds[] = $redirect->id;
} else {
// Delete any domain-wide redirects
$domain->redirects()
->whereIn('source_path', ['/*', '*', '/'])
->where('is_wildcard', true)
->delete();
// Handle page redirects
$redirectsData = $data['redirects'] ?? [];
foreach ($redirectsData as $redirectData) {
if (!empty($redirectData['id'])) {
$redirect = DomainRedirect::find($redirectData['id']);
if ($redirect && $redirect->domain_id === $domain->id) {
$redirect->update([
'source_path' => $redirectData['source_path'],
'destination_url' => $redirectData['destination_url'],
'redirect_type' => $redirectData['redirect_type'],
'is_wildcard' => $redirectData['is_wildcard'] ?? false,
'is_active' => $redirectData['is_active'] ?? true,
]);
$existingIds[] = $redirect->id;
}
} else {
$redirect = $domain->redirects()->create([
'source_path' => $redirectData['source_path'],
'destination_url' => $redirectData['destination_url'],
'redirect_type' => $redirectData['redirect_type'],
'is_wildcard' => $redirectData['is_wildcard'] ?? false,
'is_active' => $redirectData['is_active'] ?? true,
]);
$existingIds[] = $redirect->id;
}
}
// Delete removed page redirects (but not domain-wide ones which we already handled)
if (!empty($existingIds)) {
$domain->redirects()
->whereNotIn('id', $existingIds)
->whereNotIn('source_path', ['/*', '*', '/'])
->delete();
}
}
// Apply redirects via agent
$this->applyRedirects($domain);
Notification::make()
->title(__('Redirects saved!'))
->body(__('Your redirect rules have been updated.'))
->success()
->send();
} catch (Exception $e) {
Notification::make()
->title(__('Error saving redirects'))
->body($e->getMessage())
->danger()
->send();
}
}
protected function applyRedirects(Domain $domain): void
{
$redirects = $domain->redirects()->where('is_active', true)->get()->map(fn ($r) => [
'source' => $r->source_path,
'destination' => $r->destination_url,
'type' => $r->redirect_type,
'wildcard' => $r->is_wildcard,
])->toArray();
$this->getAgent()->send('domain.set_redirects', [
'username' => $this->getUsername(),
'domain' => $domain->domain,
'redirects' => $redirects,
]);
}
public function saveHotlinkSettings(Domain $domain, array $data): void
{
try {
$setting = $domain->hotlinkSetting;
if (!$setting) {
$setting = new DomainHotlinkSetting(['domain_id' => $domain->id]);
}
$setting->fill([
'is_enabled' => $data['is_enabled'] ?? false,
'allowed_domains' => $data['allowed_domains'] ?? '',
'block_blank_referrer' => $data['block_blank_referrer'] ?? true,
'protected_extensions' => $data['protected_extensions'] ?? DomainHotlinkSetting::getDefaultExtensions(),
'redirect_url' => $data['redirect_url'] ?? null,
]);
$setting->save();
// Apply hotlink protection via agent
$this->getAgent()->send('domain.set_hotlink_protection', [
'username' => $this->getUsername(),
'domain' => $domain->domain,
'enabled' => $setting->is_enabled,
'allowed_domains' => $setting->getAllowedDomainsArray(),
'block_blank_referrer' => $setting->block_blank_referrer,
'protected_extensions' => $setting->getProtectedExtensionsArray(),
'redirect_url' => $setting->redirect_url,
]);
Notification::make()
->title(__('Hotlink protection updated!'))
->body($setting->is_enabled ? __('Protection is now active.') : __('Protection has been disabled.'))
->success()
->send();
} catch (Exception $e) {
Notification::make()
->title(__('Error saving hotlink settings'))
->body($e->getMessage())
->danger()
->send();
}
}
public function saveIndexSettings(Domain $domain, array $data): void
{
try {
$domain->update(['directory_index' => $data['directory_index']]);
// Apply index settings via agent
$this->getAgent()->send('domain.set_directory_index', [
'username' => $this->getUsername(),
'domain' => $domain->domain,
'directory_index' => $data['directory_index'],
]);
Notification::make()
->title(__('Index settings updated!'))
->body(__('Directory index priority has been changed.'))
->success()
->send();
} catch (Exception $e) {
Notification::make()
->title(__('Error saving index settings'))
->body($e->getMessage())
->danger()
->send();
}
}
public function toggleDomain(Domain $domain): void
{
try {
$newStatus = !$domain->is_active;
$result = $this->getAgent()->domainToggle($this->getUsername(), $domain->domain, $newStatus);
if ($result['success'] ?? false) {
$domain->update(['is_active' => $newStatus]);
$status = $newStatus ? __('Enabled') : __('Disabled');
Notification::make()
->title(__('Domain') . " {$status}")
->success()
->send();
} else {
throw new Exception($result['error'] ?? 'Unknown error');
}
} catch (Exception $e) {
Notification::make()
->title(__('Error toggling domain'))
->body($e->getMessage())
->danger()
->send();
}
}
public function deleteDomain(Domain $domain, array $options): void
{
$deletedItems = [];
$errors = [];
try {
// Delete WordPress sites first (via Agent)
if ($options['delete_wordpress'] ?? false) {
try {
$wpResult = $this->getAgent()->send('wp.list', [
'username' => $this->getUsername(),
]);
foreach ($wpResult['sites'] ?? [] as $site) {
if (($site['domain'] ?? '') === $domain->domain) {
$this->getAgent()->send('wp.delete', [
'username' => $this->getUsername(),
'site_id' => $site['id'],
'delete_files' => true,
'delete_database' => true,
]);
$deletedItems[] = __('WordPress site');
}
}
} catch (Exception $e) {
$errors[] = __('WordPress: ') . $e->getMessage();
}
}
// Delete SSL certificate
if ($options['delete_ssl'] ?? false) {
if ($domain->sslCertificate) {
try {
$this->getAgent()->send('ssl.delete', [
'username' => $this->getUsername(),
'domain' => $domain->domain,
]);
$domain->sslCertificate->delete();
$deletedItems[] = __('SSL certificate');
} catch (Exception $e) {
$errors[] = __('SSL: ') . $e->getMessage();
}
}
}
// Delete email accounts
if ($options['delete_email'] ?? false) {
if ($domain->emailDomain) {
try {
foreach ($domain->emailDomain->mailboxes as $mailbox) {
$this->getAgent()->send('email.mailbox_delete', [
'username' => $this->getUsername(),
'email' => $mailbox->email,
'delete_files' => true,
'maildir_path' => $mailbox->maildir_path,
]);
}
$this->getAgent()->send('email.disable_domain', [
'username' => $this->getUsername(),
'domain' => $domain->domain,
]);
$mailboxCount = $domain->emailDomain->mailboxes()->count();
$domain->emailDomain->mailboxes()->delete();
$domain->emailDomain->delete();
$deletedItems[] = __(':count email account(s)', ['count' => $mailboxCount]);
} catch (Exception $e) {
$errors[] = __('Email: ') . $e->getMessage();
}
}
}
// Delete DNS records
if ($options['delete_dns'] ?? false) {
try {
$dnsCount = $domain->dnsRecords()->count();
if ($dnsCount > 0) {
$this->getAgent()->send('dns.delete_zone', [
'domain' => $domain->domain,
]);
$domain->dnsRecords()->delete();
$deletedItems[] = __(':count DNS record(s)', ['count' => $dnsCount]);
}
} catch (Exception $e) {
$errors[] = __('DNS: ') . $e->getMessage();
}
}
// Delete redirects and hotlink settings (cascade should handle this, but be explicit)
$domain->redirects()->delete();
$domain->hotlinkSetting?->delete();
// Delete domain files and configuration via Agent
$result = $this->getAgent()->domainDelete(
$this->getUsername(),
$domain->domain,
$options['delete_files'] ?? false
);
if ($result['success'] ?? false) {
$domain->delete();
$message = __('Domain deleted successfully.');
if (!empty($deletedItems)) {
$message .= ' ' . __('Also deleted: ') . implode(', ', $deletedItems);
}
Notification::make()
->title(__('Domain deleted'))
->body($message)
->success()
->send();
if (!empty($errors)) {
Notification::make()
->title(__('Some items had warnings'))
->body(implode("\n", $errors))
->warning()
->send();
}
} else {
throw new Exception($result['error'] ?? 'Unknown error');
}
} catch (Exception $e) {
Notification::make()
->title(__('Error deleting domain'))
->body($e->getMessage())
->danger()
->send();
}
}
public function openFileManager(Domain $domain): void
{
$path = str_replace('/home/' . $this->getUsername() . '/', '', $domain->document_root);
$this->redirect(route('filament.jabali.pages.files', ['path' => $path]));
}
}