796 lines
35 KiB
PHP
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]));
|
|
}
|
|
}
|