agent === null) { $this->agent = new AgentClient; } return $this->agent; } public function getUsername(): string { return Auth::user()->username; } public function mount(): void { $this->loadWordPressDomains(); } public function loadWordPressDomains(): void { try { // Get WordPress sites from the agent $result = $this->getAgent()->wpList($this->getUsername()); $sites = $result['sites'] ?? []; // Build options array: domain_id => domain name (with path if subdirectory install) $this->wordPressDomains = []; foreach ($sites as $site) { $domain = Domain::where('user_id', Auth::id()) ->where('domain', $site['domain']) ->first(); if ($domain) { $label = $site['domain']; if (! empty($site['path'])) { $label .= '/'.$site['path']; } $this->wordPressDomains[$domain->id] = $label; } } } catch (Exception $e) { $this->wordPressDomains = []; } } public function table(Table $table): Table { return $table ->query(CronJob::query()->where('user_id', Auth::id())->orderBy('created_at', 'desc')) ->columns([ TextColumn::make('name') ->label(__('Job Name')) ->icon('heroicon-o-clock') ->iconColor('primary') ->description(fn (CronJob $record) => $record->command) ->searchable() ->sortable(), TextColumn::make('schedule') ->label(__('Schedule')) ->fontFamily('mono') ->description(fn (CronJob $record) => $record->schedule_human), TextColumn::make('type') ->label(__('Type')) ->badge() ->color(fn (string $state) => $state === 'wordpress' ? 'info' : 'gray') ->formatStateUsing(fn (string $state) => $state === 'wordpress' ? __('WordPress') : __('Custom')), IconColumn::make('is_active') ->label(__('Status')) ->boolean() ->trueIcon('heroicon-o-check-circle') ->falseIcon('heroicon-o-pause-circle') ->trueColor('success') ->falseColor('gray'), TextColumn::make('last_run_at') ->label(__('Last Run')) ->since() ->description(fn (CronJob $record) => $record->last_run_status ? ($record->last_run_status === 'success' ? __('Success') : __('Failed')) : null) ->placeholder(__('Never')) ->sortable(), ]) ->recordActions([ Action::make('run') ->label(__('Run Now')) ->icon('heroicon-o-play') ->color('info') ->action(fn (CronJob $record) => $this->runCronJob($record->id)), Action::make('viewOutput') ->label(__('View Output')) ->icon('heroicon-o-document-text') ->color('gray') ->visible(fn (CronJob $record) => $record->last_run_at !== null) ->modalHeading(__('Last Run Output')) ->modalDescription(fn (CronJob $record) => __('Last run: :time - Status: :status', [ 'time' => $record->last_run_at?->diffForHumans(), 'status' => $record->last_run_status === 'success' ? __('Success') : __('Failed'), ])) ->modalIcon(fn (CronJob $record) => $record->last_run_status === 'success' ? 'heroicon-o-check-circle' : 'heroicon-o-x-circle') ->modalIconColor(fn (CronJob $record) => $record->last_run_status === 'success' ? 'success' : 'danger') ->modalContent(fn (CronJob $record) => view('filament.jabali.components.cron-output', ['output' => $record->last_run_output])) ->modalSubmitAction(false) ->modalCancelActionLabel(__('Close')), Action::make('toggle') ->label(fn (CronJob $record) => $record->is_active ? __('Disable') : __('Enable')) ->icon(fn (CronJob $record) => $record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play-circle') ->color('warning') ->action(fn (CronJob $record) => $this->toggleCronJob($record->id)), Action::make('edit') ->label(__('Edit')) ->icon('heroicon-o-pencil') ->color('gray') ->visible(fn (CronJob $record) => $record->type === 'custom') ->modalHeading(__('Edit Cron Job')) ->modalDescription(fn (CronJob $record) => $record->name) ->modalIcon('heroicon-o-pencil') ->modalIconColor('gray') ->modalSubmitActionLabel(__('Save Changes')) ->fillForm(fn (CronJob $record) => [ 'name' => $record->name, 'schedule' => $record->schedule, 'command' => $record->command, ]) ->form([ TextInput::make('name') ->label(__('Job Name')) ->required() ->maxLength(255) ->helperText(__('A friendly name to identify this cron job')), Select::make('schedule') ->label(__('Schedule')) ->options(CronJob::scheduleOptions()) ->required() ->searchable() ->helperText(__('How often the command should run')), Textarea::make('command') ->label(__('Command')) ->required() ->rows(3) ->helperText(__('The command will run as your user account')), ]) ->action(fn (CronJob $record, array $data) => $this->updateCronJob($record, $data)), Action::make('delete') ->label(__('Delete')) ->icon('heroicon-o-trash') ->color('danger') ->requiresConfirmation() ->modalHeading(__('Delete Cron Job')) ->modalDescription(fn (CronJob $record) => __('Are you sure you want to delete')." '{$record->name}'?") ->modalIcon('heroicon-o-trash') ->modalIconColor('danger') ->modalSubmitActionLabel(__('Delete Cron Job')) ->action(fn (CronJob $record) => $this->deleteCronJob($record->id)), ]) ->emptyStateHeading(__('No cron jobs')) ->emptyStateDescription(__('Get started by creating a new cron job or setting up WordPress cron.')) ->emptyStateIcon('heroicon-o-clock') ->striped(); } public function createCronJobAction(): Action { return Action::make('createCronJob') ->label(__('Add Cron Job')) ->icon('heroicon-o-plus') ->modalHeading(__('Create Cron Job')) ->modalDescription(__('Schedule a command to run automatically at specified intervals')) ->modalIcon('heroicon-o-clock') ->modalIconColor('primary') ->modalSubmitActionLabel(__('Create Cron Job')) ->modalWidth('lg') ->form([ TextInput::make('name') ->label(__('Job Name')) ->required() ->maxLength(255) ->placeholder(__('My scheduled task')) ->helperText(__('A friendly name to identify this cron job')), Select::make('schedule') ->label(__('Schedule')) ->options(CronJob::scheduleOptions()) ->required() ->searchable() ->helperText(__('How often the command should run')), Textarea::make('command') ->label(__('Command')) ->required() ->rows(3) ->placeholder(__('php /home/user/script.php')) ->helperText(__('The command will run as your user account')), Placeholder::make('warning') ->content(new HtmlString('
'.__('Warning:').' '.__('Cron jobs run automatically at scheduled times. Misconfigured jobs can:').'

'.__('Only create cron jobs if you understand what the command does.').'

')), ]) ->action(function (array $data): void { try { // Create in database - Laravel scheduler will handle execution CronJob::create([ 'user_id' => Auth::id(), 'name' => $data['name'], 'schedule' => $data['schedule'], 'command' => $data['command'], 'type' => 'custom', 'is_active' => true, ]); Notification::make() ->title(__('Cron job created')) ->body(__('The job will run according to its schedule.')) ->success() ->send(); } catch (Exception $e) { Notification::make() ->title(__('Error creating cron job')) ->body($e->getMessage()) ->danger() ->send(); } }); } public function setupWordPressCronAction(): Action { return Action::make('setupWordPressCron') ->label(__('Setup WordPress Cron')) ->icon('heroicon-o-bolt') ->color('info') ->modalHeading(__('Setup WordPress Cron')) ->modalDescription(__('Replace WordPress\'s built-in cron with a real server cron job for better reliability and performance')) ->modalIcon('heroicon-o-bolt') ->modalIconColor('info') ->modalSubmitActionLabel(__('Setup WordPress Cron')) ->modalWidth('lg') ->form([ Select::make('domain_id') ->label(__('WordPress Site')) ->options($this->wordPressDomains) ->required() ->searchable() ->placeholder(__('Select a WordPress site')) ->helperText(__('Select the WordPress site to enable server-side cron for')), Select::make('schedule') ->label(__('Run Frequency')) ->options([ '*/5 * * * *' => __('Every 5 minutes (Recommended)'), '*/10 * * * *' => __('Every 10 minutes'), '*/15 * * * *' => __('Every 15 minutes'), '*/30 * * * *' => __('Every 30 minutes'), '0 * * * *' => __('Every hour'), ]) ->default('*/5 * * * *') ->required() ->helperText(__('How often WordPress scheduled tasks should run')), Placeholder::make('info') ->content(new HtmlString('
'.__('What this does:').'
')), ]) ->action(function (array $data): void { try { $domain = Domain::findOrFail($data['domain_id']); $username = $this->getUsername(); // Add DISABLE_WP_CRON to wp-config.php $result = $this->getAgent()->cronWordPressSetup( $username, $domain->domain, $data['schedule'], false // enable - adds DISABLE_WP_CRON to wp-config ); if (! $result['success']) { throw new Exception($result['error'] ?? __('Failed to setup WordPress cron')); } // Save to database - Laravel scheduler will handle execution $command = "cd /home/{$username}/domains/{$domain->domain}/public_html && /usr/bin/php wp-cron.php"; CronJob::updateOrCreate( [ 'user_id' => Auth::id(), 'type' => 'wordpress', 'metadata->domain_id' => $domain->id, ], [ 'name' => "WordPress Cron - {$domain->domain}", 'schedule' => $data['schedule'], 'command' => $command, 'is_active' => true, 'metadata' => ['domain_id' => $domain->id, 'domain' => $domain->domain], ] ); Notification::make() ->title(__('WordPress cron enabled')) ->body(__('Server cron is now handling scheduled tasks for :domain', ['domain' => $domain->domain])) ->success() ->send(); } catch (Exception $e) { Notification::make() ->title(__('Error setting up WordPress cron')) ->body($e->getMessage()) ->danger() ->send(); } }); } public function deleteCronJob(int $id): void { try { $cronJob = CronJob::where('user_id', Auth::id())->findOrFail($id); // If it's a WordPress cron, remove the wp-config DISABLE_WP_CRON constant if ($cronJob->type === 'wordpress' && isset($cronJob->metadata['domain'])) { $this->getAgent()->cronWordPressSetup( $this->getUsername(), $cronJob->metadata['domain'], $cronJob->schedule, true // disable - removes DISABLE_WP_CRON from wp-config ); } // Delete from database - Laravel scheduler will stop running it $cronJob->delete(); Notification::make() ->title(__('Cron job deleted')) ->success() ->send(); } catch (Exception $e) { Notification::make() ->title(__('Error deleting cron job')) ->body($e->getMessage()) ->danger() ->send(); } } public function updateCronJob(CronJob $cronJob, array $data): void { try { // Update database - Laravel scheduler uses these values $cronJob->update([ 'name' => $data['name'], 'schedule' => $data['schedule'], 'command' => $data['command'], ]); Notification::make() ->title(__('Cron job updated')) ->success() ->send(); } catch (Exception $e) { Notification::make() ->title(__('Error updating cron job')) ->body($e->getMessage()) ->danger() ->send(); } } public function toggleCronJob(int $id): void { try { $cronJob = CronJob::where('user_id', Auth::id())->findOrFail($id); $newState = ! $cronJob->is_active; // Update database - Laravel scheduler checks is_active $cronJob->update(['is_active' => $newState]); Notification::make() ->title($newState ? __('Cron job enabled') : __('Cron job disabled')) ->success() ->send(); } catch (Exception $e) { Notification::make() ->title(__('Error toggling cron job')) ->body($e->getMessage()) ->danger() ->send(); } } public function runCronJob(int $id): void { try { $cronJob = CronJob::where('user_id', Auth::id())->findOrFail($id); $result = $this->getAgent()->cronRun( $this->getUsername(), $cronJob->command ); // Update last run info $cronJob->update([ 'last_run_at' => now(), 'last_run_status' => $result['success'] ? 'success' : 'failed', 'last_run_output' => $result['output'] ?? null, ]); if ($result['success']) { Notification::make() ->title(__('Cron job executed')) ->body(__('Command completed successfully')) ->success() ->send(); } else { Notification::make() ->title(__('Cron job failed')) ->body($result['output'] ?? __('Command failed')) ->warning() ->send(); } } catch (Exception $e) { Notification::make() ->title(__('Error running cron job')) ->body($e->getMessage()) ->danger() ->send(); } } protected function getHeaderActions(): array { return [ $this->getTourAction(), $this->createCronJobAction(), $this->setupWordPressCronAction(), ]; } }