diff --git a/app/Enums/EditorLanguages.php b/app/Enums/EditorLanguages.php
index 91dcaebe7..7480a412c 100644
--- a/app/Enums/EditorLanguages.php
+++ b/app/Enums/EditorLanguages.php
@@ -36,6 +36,7 @@ enum EditorLanguages: string implements HasLabel
case java = 'java';
case javascript = 'javascript';
case julia = 'julia';
+ case json = 'json';
case kotlin = 'kotlin';
case less = 'less';
case lexon = 'lexon';
@@ -89,7 +90,49 @@ enum EditorLanguages: string implements HasLabel
case wgsl = 'wgsl';
case xml = 'xml';
case yaml = 'yaml';
- case json = 'json';
+
+ public static function fromWithAlias(string $match): self
+ {
+ return match ($match) {
+ 'h' => self::c,
+
+ 'cc', 'hpp' => self::cpp,
+
+ 'cs' => self::csharp,
+
+ 'class' => self::java,
+
+ 'htm' => self::html,
+
+ 'js', 'mjs', 'cjs' => self::javascript,
+
+ 'kt', 'kts' => self::kotlin,
+
+ 'md' => self::markdown,
+
+ 'm' => self::objectivec,
+
+ 'pl', 'pm' => self::perl,
+
+ 'php3', 'php4', 'php5', 'phtml' => self::php,
+
+ 'py', 'pyc', 'pyo', 'pyi' => self::python,
+
+ 'rdata', 'rds' => self::r,
+
+ 'rb', 'erb' => self::ruby,
+
+ 'sc' => self::scala,
+
+ 'sh', 'zsh' => self::shell,
+
+ 'ts', 'tsx' => self::typescript,
+
+ 'yml' => self::yaml,
+
+ default => self::tryFrom($match) ?? self::plaintext,
+ };
+ }
public function getLabel(): string
{
diff --git a/app/Filament/Server/Resources/FileResource.php b/app/Filament/Server/Resources/FileResource.php
index fd956e9c2..d94da981d 100644
--- a/app/Filament/Server/Resources/FileResource.php
+++ b/app/Filament/Server/Resources/FileResource.php
@@ -56,6 +56,7 @@ class FileResource extends Resource
return [
'edit' => Pages\EditFiles::route('/edit/{path}'),
'search' => Pages\SearchFiles::route('/search/{searchTerm}'), // TODO: find better way?
+ 'download' => Pages\DownloadFiles::route('/download/{path}'),
'index' => Pages\ListFiles::route('/{path?}'),
];
}
diff --git a/app/Filament/Server/Resources/FileResource/Pages/DownloadFiles.php b/app/Filament/Server/Resources/FileResource/Pages/DownloadFiles.php
new file mode 100644
index 000000000..2f147f68a
--- /dev/null
+++ b/app/Filament/Server/Resources/FileResource/Pages/DownloadFiles.php
@@ -0,0 +1,64 @@
+authorizeAccess();
+
+ /** @var Server $server */
+ $server = Filament::getTenant();
+
+ $token = $service
+ ->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
+ ->setUser(auth()->user())
+ ->setClaims([
+ 'file_path' => rawurldecode($path),
+ 'server_uuid' => $server->uuid,
+ ])
+ ->handle($server->node, auth()->user()->id . $server->uuid);
+
+ Activity::event('server:file.download')
+ ->property('file', $path)
+ ->log();
+
+ redirect()->away($server->node->getConnectionAddress() . '/download/file?token=' . $token->toString());
+ }
+
+ protected function authorizeAccess(): void
+ {
+ abort_unless(auth()->user()->can(Permission::ACTION_FILE_READ_CONTENT, Filament::getTenant()), 403);
+ }
+
+ 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', '.*'),
+ );
+ }
+}
diff --git a/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php b/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php
index ef693d735..8fb29f775 100644
--- a/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php
+++ b/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php
@@ -6,7 +6,7 @@ use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Enums\EditorLanguages;
use App\Facades\Activity;
use App\Filament\Server\Resources\FileResource;
-use App\Models\File;
+use App\Livewire\AlertBanner;
use App\Models\Permission;
use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository;
@@ -18,7 +18,6 @@ use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
-use Filament\Pages\Concerns\HasUnsavedDataChangesAlert;
use Filament\Pages\Concerns\InteractsWithFormActions;
use Filament\Panel;
use Filament\Resources\Pages\Page;
@@ -34,7 +33,6 @@ use Livewire\Attributes\Locked;
*/
class EditFiles extends Page
{
- use HasUnsavedDataChangesAlert;
use InteractsWithFormActions;
use InteractsWithForms;
@@ -54,27 +52,34 @@ class EditFiles extends Page
/** @var Server $server */
$server = Filament::getTenant();
- File::get($server, dirname($this->path))->orderByDesc('is_directory')->orderBy('name');
-
return $form
->schema([
- Select::make('lang')
- ->live()
- ->label('')
- ->placeholder('File Language')
- ->options(EditorLanguages::class)
- ->hidden() //TODO Fix Dis
- ->default(function () {
- $ext = pathinfo($this->path, PATHINFO_EXTENSION);
-
- if ($ext === 'yml') {
- return 'yaml';
- }
-
- return $ext;
- }),
Section::make('Editing: ' . $this->path)
->footerActions([
+ Action::make('save_and_close')
+ ->label('Save & Close')
+ ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
+ ->icon('tabler-device-floppy')
+ ->keyBindings('mod+shift+s')
+ ->action(function (DaemonFileRepository $fileRepository) use ($server) {
+ $data = $this->form->getState();
+
+ $fileRepository
+ ->setServer($server)
+ ->putContent($this->path, $data['editor'] ?? '');
+
+ Activity::event('server:file.write')
+ ->property('file', $this->path)
+ ->log();
+
+ Notification::make()
+ ->success()
+ ->title('File saved')
+ ->body(fn () => $this->path)
+ ->send();
+
+ $this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
+ }),
Action::make('save')
->label('Save')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
@@ -93,12 +98,9 @@ class EditFiles extends Page
Notification::make()
->success()
- ->duration(5000) // 5 seconds
- ->title('Saved File')
+ ->title('File saved')
->body(fn () => $this->path)
->send();
-
- $this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
}),
Action::make('cancel')
->label('Cancel')
@@ -108,10 +110,17 @@ class EditFiles extends Page
])
->footerActionsAlignment(Alignment::End)
->schema([
+ Select::make('lang')
+ ->label('Syntax Highlighting')
+ ->live()
+ ->options(EditorLanguages::class)
+ ->selectablePlaceholder(false)
+ ->afterStateUpdated(fn ($state) => $this->dispatch('setLanguage', lang: $state))
+ ->default(fn () => EditorLanguages::fromWithAlias(pathinfo($this->path, PATHINFO_EXTENSION))),
MonacoEditor::make('editor')
->label('')
->placeholderText('')
- ->formatStateUsing(function (DaemonFileRepository $fileRepository) use ($server) {
+ ->default(function (DaemonFileRepository $fileRepository) use ($server) {
try {
return $fileRepository
->setServer($server)
@@ -120,7 +129,7 @@ class EditFiles extends Page
abort(404, $this->path . ' not found.');
}
})
- ->language(fn (Get $get) => $get('lang') ?? 'plaintext')
+ ->language(fn (Get $get) => $get('lang'))
->view('filament.plugins.monaco-editor'),
]),
]);
@@ -133,6 +142,15 @@ class EditFiles extends Page
$this->path = $path;
$this->form->fill();
+
+ if (str($path)->endsWith('.pelicanignore')) {
+ AlertBanner::make()
+ ->title('You\'re editing a .pelicanignore
file!')
+ ->body('Any files or directories listed in here will be excluded from backups. Wildcards are supported by using an asterisk (*
).
You can negate a prior rule by prepending an exclamation point (!
).')
+ ->info()
+ ->closable()
+ ->send();
+ }
}
protected function authorizeAccess(): void
diff --git a/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php b/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php
index 6db4fc4e0..910633799 100644
--- a/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php
+++ b/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php
@@ -10,10 +10,8 @@ use App\Models\File;
use App\Models\Permission;
use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository;
-use App\Services\Nodes\NodeJWTService;
use App\Filament\Components\Tables\Columns\BytesColumn;
use App\Filament\Components\Tables\Columns\DateTimeColumn;
-use Carbon\CarbonImmutable;
use Filament\Actions\Action as HeaderAction;
use Filament\Facades\Filament;
use Filament\Forms\Components\CheckboxList;
@@ -21,6 +19,7 @@ use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs;
+use Filament\Forms\Components\Tabs\Tab;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
@@ -175,22 +174,7 @@ class ListFiles extends ListRecords
->label('Download')
->icon('tabler-download')
->visible(fn (File $file) => $file->is_file)
- ->action(function (File $file, NodeJWTService $service) use ($server) {
- $token = $service
- ->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
- ->setUser(auth()->user())
- ->setClaims([
- 'file_path' => rawurldecode(join_paths($this->path, $file->name)),
- 'server_uuid' => $server->uuid,
- ])
- ->handle($server->node, auth()->user()->id . $server->uuid);
-
- Activity::event('server:file.download')
- ->property('file', join_paths($this->path, $file->name))
- ->log();
-
- return redirect()->away(sprintf('%s/download/file?token=%s', $server->node->getConnectionAddress(), $token->toString())); // TODO: download works, but breaks modals
- }),
+ ->url(fn () => DownloadFiles::getUrl(['path' => $this->path]), true),
Action::make('move')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
->label('Move')
@@ -448,15 +432,16 @@ class ListFiles extends ListRecords
->label('File Name')
->required(),
Select::make('lang')
+ ->label('Syntax Highlighting')
->live()
- ->hidden() //TODO: Make file language selection work
- ->label('Language')
- ->placeholder('File Language')
- ->options(EditorLanguages::class),
+ ->options(EditorLanguages::class)
+ ->selectablePlaceholder(false)
+ ->afterStateUpdated(fn ($state) => $this->dispatch('setLanguage', lang: $state))
+ ->default(EditorLanguages::plaintext->value),
MonacoEditor::make('editor')
->label('')
->view('filament.plugins.monaco-editor')
- ->language(fn (Get $get) => $get('lang')),
+ ->language(fn (Get $get) => $get('lang') ?? 'plaintext'),
]),
HeaderAction::make('new_folder')
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
@@ -504,13 +489,12 @@ class ListFiles extends ListRecords
}
return redirect(ListFiles::getUrl(['path' => $this->path]));
-
})
->form([
Tabs::make()
->contained(false)
->schema([
- Tabs\Tab::make('Upload Files')
+ Tab::make('Upload Files')
->live()
->schema([
FileUpload::make('files')
@@ -520,7 +504,7 @@ class ListFiles extends ListRecords
->preserveFilenames()
->multiple(),
]),
- Tabs\Tab::make('Upload From URL')
+ Tab::make('Upload From URL')
->live()
->disabled(fn (Get $get) => count($get('files')) > 0)
->schema([
diff --git a/config/filament-monaco-editor.php b/config/filament-monaco-editor.php
index 7f87c0969..2ced43128 100644
--- a/config/filament-monaco-editor.php
+++ b/config/filament-monaco-editor.php
@@ -6,7 +6,7 @@ return [
'show-full-screen-toggle' => true,
'show-placeholder' => true,
'placeholder-text' => 'Your code here...',
- 'show-loader' => true,
+ 'show-loader' => false,
'font-size' => '16px',
'line-numbers-min-chars' => true,
'automatic-layout' => true,
diff --git a/resources/views/filament/plugins/monaco-editor.blade.php b/resources/views/filament/plugins/monaco-editor.blade.php
index 3980c1bbb..78bfb23db 100644
--- a/resources/views/filament/plugins/monaco-editor.blade.php
+++ b/resources/views/filament/plugins/monaco-editor.blade.php
@@ -1,3 +1,11 @@
+@script
+
+@endscript
+