mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-11-04 02:36:53 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			295 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
namespace App\Filament\Server\Resources\Files\Pages;
 | 
						|
 | 
						|
use App\Exceptions\Http\Server\FileSizeTooLargeException;
 | 
						|
use App\Exceptions\Repository\FileNotEditableException;
 | 
						|
use App\Facades\Activity;
 | 
						|
use App\Filament\Server\Resources\Files\FileResource;
 | 
						|
use App\Livewire\AlertBanner;
 | 
						|
use App\Models\Permission;
 | 
						|
use App\Models\Server;
 | 
						|
use App\Repositories\Daemon\DaemonFileRepository;
 | 
						|
use App\Traits\Filament\CanCustomizeHeaderActions;
 | 
						|
use App\Traits\Filament\CanCustomizeHeaderWidgets;
 | 
						|
use Filament\Actions\Action;
 | 
						|
use Filament\Facades\Filament;
 | 
						|
use Filament\Forms\Components\CodeEditor;
 | 
						|
use Filament\Forms\Components\CodeEditor\Enums\Language;
 | 
						|
use Filament\Forms\Components\Select;
 | 
						|
use Filament\Forms\Concerns\InteractsWithForms;
 | 
						|
use Filament\Notifications\Notification;
 | 
						|
use Filament\Pages\Concerns\InteractsWithFormActions;
 | 
						|
use Filament\Panel;
 | 
						|
use Filament\Resources\Pages\Page;
 | 
						|
use Filament\Resources\Pages\PageRegistration;
 | 
						|
use Filament\Schemas\Components\Section;
 | 
						|
use Filament\Schemas\Components\Utilities\Get;
 | 
						|
use Filament\Schemas\Schema;
 | 
						|
use Filament\Support\Enums\Alignment;
 | 
						|
use Filament\Support\Facades\FilamentView;
 | 
						|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
 | 
						|
use Illuminate\Database\Eloquent\Model;
 | 
						|
use Illuminate\Http\Client\ConnectionException;
 | 
						|
use Illuminate\Routing\Route;
 | 
						|
use Illuminate\Support\Facades\Route as RouteFacade;
 | 
						|
use Illuminate\Support\Js;
 | 
						|
use Livewire\Attributes\Locked;
 | 
						|
use Throwable;
 | 
						|
 | 
						|
/**
 | 
						|
 * @property Schema $form
 | 
						|
 */
 | 
						|
class EditFiles extends Page
 | 
						|
{
 | 
						|
    use CanCustomizeHeaderActions;
 | 
						|
    use CanCustomizeHeaderWidgets;
 | 
						|
    use InteractsWithFormActions;
 | 
						|
    use InteractsWithForms;
 | 
						|
 | 
						|
    protected static string $resource = FileResource::class;
 | 
						|
 | 
						|
    protected string $view = 'filament.server.pages.edit-file';
 | 
						|
 | 
						|
    protected static ?string $title = '';
 | 
						|
 | 
						|
    #[Locked]
 | 
						|
    public string $path;
 | 
						|
 | 
						|
    public ?string $previousUrl = null;
 | 
						|
 | 
						|
    private DaemonFileRepository $fileRepository;
 | 
						|
 | 
						|
    /** @var array<string, mixed>|null */
 | 
						|
    public ?array $data = [];
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws Throwable
 | 
						|
     */
 | 
						|
    public function form(Schema $schema): Schema
 | 
						|
    {
 | 
						|
        /** @var Server $server */
 | 
						|
        $server = Filament::getTenant();
 | 
						|
 | 
						|
        Activity::event('server:file.read')
 | 
						|
            ->property('file', $this->path)
 | 
						|
            ->log();
 | 
						|
 | 
						|
        return $schema
 | 
						|
            ->components([
 | 
						|
                Section::make(trans('server/file.actions.edit.title', ['file' => $this->path]))
 | 
						|
                    ->footerActions([
 | 
						|
                        Action::make('save_and_close')
 | 
						|
                            ->label(trans('server/file.actions.edit.save_close'))
 | 
						|
                            ->authorize(fn () => user()?->can(Permission::ACTION_FILE_UPDATE, $server))
 | 
						|
                            ->icon('tabler-device-floppy')
 | 
						|
                            ->keyBindings('mod+shift+s')
 | 
						|
                            ->action(function () {
 | 
						|
                                $this->getDaemonFileRepository()->putContent($this->path, $this->data['editor'] ?? '');
 | 
						|
 | 
						|
                                Activity::event('server:file.write')
 | 
						|
                                    ->property('file', $this->path)
 | 
						|
                                    ->log();
 | 
						|
 | 
						|
                                Notification::make()
 | 
						|
                                    ->success()
 | 
						|
                                    ->title(trans('server/file.actions.edit.notification'))
 | 
						|
                                    ->body(fn () => $this->path)
 | 
						|
                                    ->send();
 | 
						|
 | 
						|
                                $this->redirectToList();
 | 
						|
                            }),
 | 
						|
                        Action::make('save')
 | 
						|
                            ->label(trans('server/file.actions.edit.save'))
 | 
						|
                            ->authorize(fn () => user()?->can(Permission::ACTION_FILE_UPDATE, $server))
 | 
						|
                            ->icon('tabler-device-floppy')
 | 
						|
                            ->keyBindings('mod+s')
 | 
						|
                            ->action(function () {
 | 
						|
                                $this->getDaemonFileRepository()->putContent($this->path, $this->data['editor'] ?? '');
 | 
						|
 | 
						|
                                Activity::event('server:file.write')
 | 
						|
                                    ->property('file', $this->path)
 | 
						|
                                    ->log();
 | 
						|
 | 
						|
                                Notification::make()
 | 
						|
                                    ->success()
 | 
						|
                                    ->title(trans('server/file.actions.edit.notification'))
 | 
						|
                                    ->body(fn () => $this->path)
 | 
						|
                                    ->send();
 | 
						|
                            }),
 | 
						|
                        Action::make('cancel')
 | 
						|
                            ->label(trans('server/file.actions.edit.cancel'))
 | 
						|
                            ->color('danger')
 | 
						|
                            ->icon('tabler-x')
 | 
						|
                            ->alpineClickHandler(function () {
 | 
						|
                                $url = $this->previousUrl ?? ListFiles::getUrl(['path' => dirname($this->path)]);
 | 
						|
 | 
						|
                                return FilamentView::hasSpaMode($url)
 | 
						|
                                    ? 'document.referrer ? window.history.back() : Livewire.navigate(' . Js::from($url) . ')'
 | 
						|
                                    : 'document.referrer ? window.history.back() : (window.location.href = ' . Js::from($url) . ')';
 | 
						|
                            }),
 | 
						|
                    ])
 | 
						|
                    ->footerActionsAlignment(Alignment::End)
 | 
						|
                    ->schema([
 | 
						|
                        Select::make('lang')
 | 
						|
                            ->label(trans('server/file.actions.new_file.syntax'))
 | 
						|
                            ->searchable()
 | 
						|
                            ->live()
 | 
						|
                            ->options(Language::class)
 | 
						|
                            ->selectablePlaceholder(false)
 | 
						|
                            ->default(fn () => match (pathinfo($this->path, PATHINFO_EXTENSION)) {
 | 
						|
                                'cc', 'hpp' => Language::Cpp,
 | 
						|
 | 
						|
                                'css', 'scss' => Language::Css,
 | 
						|
 | 
						|
                                'go' => Language::Go,
 | 
						|
 | 
						|
                                'html' => Language::Html,
 | 
						|
 | 
						|
                                'class', 'kt', 'kts' => Language::Java,
 | 
						|
 | 
						|
                                'js', 'mjs', 'cjs', 'ts', 'tsx' => Language::JavaScript,
 | 
						|
 | 
						|
                                'json', 'json5' => Language::Json,
 | 
						|
 | 
						|
                                'md' => Language::Markdown,
 | 
						|
 | 
						|
                                'php3', 'php4', 'php5', 'phtml', 'php' => Language::Php,
 | 
						|
 | 
						|
                                'py', 'pyc', 'pyo', 'pyi' => Language::Python,
 | 
						|
 | 
						|
                                'xml' => Language::Xml,
 | 
						|
 | 
						|
                                'yml', 'yaml' => Language::Yaml,
 | 
						|
 | 
						|
                                default => null,
 | 
						|
                            }),
 | 
						|
                        CodeEditor::make('editor')
 | 
						|
                            ->hiddenLabel()
 | 
						|
                            ->language(fn (Get $get) => $get('lang'))
 | 
						|
                            ->default(function () {
 | 
						|
                                try {
 | 
						|
                                    return $this->getDaemonFileRepository()->getContent($this->path, config('panel.files.max_edit_size'));
 | 
						|
                                } catch (FileSizeTooLargeException) {
 | 
						|
                                    AlertBanner::make('file_too_large')
 | 
						|
                                        ->title(trans('server/file.alerts.file_too_large.title', ['name' => basename($this->path)]))
 | 
						|
                                        ->body(trans('server/file.alerts.file_too_large.body', ['max' => convert_bytes_to_readable(config('panel.files.max_edit_size'))]))
 | 
						|
                                        ->danger()
 | 
						|
                                        ->closable()
 | 
						|
                                        ->send();
 | 
						|
                                } catch (FileNotFoundException) {
 | 
						|
                                    AlertBanner::make('file_not_found')
 | 
						|
                                        ->title(trans('server/file.alerts.file_not_found.title', ['name' => basename($this->path)]))
 | 
						|
                                        ->danger()
 | 
						|
                                        ->closable()
 | 
						|
                                        ->send();
 | 
						|
                                } catch (FileNotEditableException) {
 | 
						|
                                    AlertBanner::make('file_is_directory')
 | 
						|
                                        ->title(trans('server/file.alerts.file_not_found.title', ['name' => basename($this->path)]))
 | 
						|
                                        ->danger()
 | 
						|
                                        ->closable()
 | 
						|
                                        ->send();
 | 
						|
                                } catch (ConnectionException) {
 | 
						|
                                    // Alert banner for this one will be handled by ListFiles
 | 
						|
                                }
 | 
						|
                                $this->redirectToList();
 | 
						|
                            }),
 | 
						|
                    ])
 | 
						|
                    ->columnSpanFull(),
 | 
						|
            ]);
 | 
						|
    }
 | 
						|
 | 
						|
    private function redirectToList(): void
 | 
						|
    {
 | 
						|
        $url = ListFiles::getUrl(['path' => dirname($this->path)]);
 | 
						|
        $this->redirect($url, FilamentView::hasSpaMode($url));
 | 
						|
    }
 | 
						|
 | 
						|
    public function mount(string $path): void
 | 
						|
    {
 | 
						|
        $this->authorizeAccess();
 | 
						|
 | 
						|
        $this->path = $path;
 | 
						|
 | 
						|
        $this->form->fill();
 | 
						|
 | 
						|
        $this->previousUrl = url()->previous();
 | 
						|
 | 
						|
        if (str($path)->endsWith('.pelicanignore')) {
 | 
						|
            AlertBanner::make('.pelicanignore_info')
 | 
						|
                ->title(trans('server/file.alerts.pelicanignore.title'))
 | 
						|
                ->body(trans('server/file.alerts.pelicanignore.body'))
 | 
						|
                ->info()
 | 
						|
                ->closable()
 | 
						|
                ->send();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    protected function authorizeAccess(): void
 | 
						|
    {
 | 
						|
        abort_unless(user()?->can(Permission::ACTION_FILE_READ_CONTENT, Filament::getTenant()), 403);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @return array<string, Schema>
 | 
						|
     */
 | 
						|
    protected function getForms(): array
 | 
						|
    {
 | 
						|
        return [
 | 
						|
            'form' => $this->form(static::getResource()::form(
 | 
						|
                $this->makeSchema()
 | 
						|
                    ->statePath($this->getFormStatePath())
 | 
						|
                    ->columns($this->hasInlineLabels() ? 1 : 2)
 | 
						|
                    ->inlineLabel($this->hasInlineLabels()),
 | 
						|
            )),
 | 
						|
        ];
 | 
						|
    }
 | 
						|
 | 
						|
    public function getFormStatePath(): ?string
 | 
						|
    {
 | 
						|
        return 'data';
 | 
						|
    }
 | 
						|
 | 
						|
    public function getBreadcrumbs(): array
 | 
						|
    {
 | 
						|
        $resource = static::getResource();
 | 
						|
 | 
						|
        $breadcrumbs = [
 | 
						|
            $resource::getUrl() => $resource::getBreadcrumb(),
 | 
						|
        ];
 | 
						|
 | 
						|
        $previousParts = '';
 | 
						|
        foreach (explode('/', $this->path) as $part) {
 | 
						|
            $previousParts = $previousParts . '/' . $part;
 | 
						|
            $breadcrumbs[self::getUrl(['path' => ltrim($previousParts, '/')])] = $part;
 | 
						|
        }
 | 
						|
 | 
						|
        return $breadcrumbs;
 | 
						|
    }
 | 
						|
 | 
						|
    private function getDaemonFileRepository(): DaemonFileRepository
 | 
						|
    {
 | 
						|
        /** @var Server $server */
 | 
						|
        $server = Filament::getTenant();
 | 
						|
        $this->fileRepository ??= (new DaemonFileRepository())->setServer($server);
 | 
						|
 | 
						|
        return $this->fileRepository;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null, bool $shouldGuessMissingParameters = false): string
 | 
						|
    {
 | 
						|
        return parent::getUrl($parameters, $isAbsolute, $panel, $tenant) . '/';
 | 
						|
    }
 | 
						|
 | 
						|
    public static function route(string $path): PageRegistration
 | 
						|
    {
 | 
						|
        return new PageRegistration(
 | 
						|
            page: static::class,
 | 
						|
            route: fn (Panel $panel): Route => RouteFacade::get($path, static::class)
 | 
						|
                ->middleware(static::getRouteMiddleware($panel))
 | 
						|
                ->withoutMiddleware(static::getWithoutRouteMiddleware($panel))
 | 
						|
                ->where('path', '.*'),
 | 
						|
        );
 | 
						|
    }
 | 
						|
}
 |