diff --git a/app/Enums/SubuserPermission.php b/app/Enums/SubuserPermission.php
index f0d7a343d..b99ce63b2 100644
--- a/app/Enums/SubuserPermission.php
+++ b/app/Enums/SubuserPermission.php
@@ -50,6 +50,9 @@ enum SubuserPermission: string
case ActivityRead = 'activity.read';
+ case MountRead = 'mount.read';
+ case MountUpdate = 'mount.update';
+
case StartupRead = 'startup.read';
case StartupUpdate = 'startup.update';
case StartupDockerImage = 'startup.docker-image';
@@ -84,6 +87,7 @@ enum SubuserPermission: string
'schedule' => TablerIcon::Clock,
'settings' => TablerIcon::Settings,
'activity' => TablerIcon::Stack,
+ 'mount' => TablerIcon::LayersLinked,
default => null,
};
}
diff --git a/app/Filament/Admin/Resources/Mounts/MountResource.php b/app/Filament/Admin/Resources/Mounts/MountResource.php
index 462ae595c..75e183614 100644
--- a/app/Filament/Admin/Resources/Mounts/MountResource.php
+++ b/app/Filament/Admin/Resources/Mounts/MountResource.php
@@ -95,6 +95,12 @@ class MountResource extends Resource
->icon(fn ($state) => $state ? TablerIcon::WritingOff : TablerIcon::Writing)
->color(fn ($state) => $state ? 'success' : 'warning')
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')),
+ TextColumn::make('user_mountable')
+ ->label(trans('admin/mount.table.user_mountable'))
+ ->badge()
+ ->icon(fn ($state) => $state ? TablerIcon::User : TablerIcon::UserOff)
+ ->color(fn ($state) => $state ? 'success' : 'warning')
+ ->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.user_mountable') : trans('admin/mount.toggles.not_user_mountable')),
])
->recordActions([
ViewAction::make()
@@ -124,7 +130,8 @@ class MountResource extends Resource
->label(trans('admin/mount.name'))
->required()
->helperText(trans('admin/mount.name_help'))
- ->maxLength(64),
+ ->maxLength(64)
+ ->columnSpanFull(),
ToggleButtons::make('read_only')
->label(trans('admin/mount.read_only'))
->helperText(trans('admin/mount.read_only_help'))
@@ -143,6 +150,24 @@ class MountResource extends Resource
])
->inline()
->default(false),
+ ToggleButtons::make('user_mountable')
+ ->label(trans('admin/mount.user_mountable'))
+ ->helperText(trans('admin/mount.user_mountable_help'))
+ ->stateCast(new BooleanStateCast(false, true))
+ ->options([
+ false => trans('admin/mount.toggles.not_user_mountable'),
+ true => trans('admin/mount.toggles.user_mountable'),
+ ])
+ ->icons([
+ false => TablerIcon::UserOff,
+ true => TablerIcon::User,
+ ])
+ ->colors([
+ false => 'warning',
+ true => 'success',
+ ])
+ ->inline()
+ ->default(true),
TextInput::make('source')
->label(trans('admin/mount.source'))
->required()
diff --git a/app/Filament/Admin/Resources/Mounts/Pages/CreateMount.php b/app/Filament/Admin/Resources/Mounts/Pages/CreateMount.php
index e35eb5ec1..c6dd95c9c 100644
--- a/app/Filament/Admin/Resources/Mounts/Pages/CreateMount.php
+++ b/app/Filament/Admin/Resources/Mounts/Pages/CreateMount.php
@@ -42,7 +42,6 @@ class CreateMount extends CreateRecord
protected function handleRecordCreation(array $data): Model
{
$data['uuid'] ??= Str::uuid()->toString();
- $data['user_mountable'] = 1;
return parent::handleRecordCreation($data);
}
diff --git a/app/Filament/Server/Pages/Mounts.php b/app/Filament/Server/Pages/Mounts.php
new file mode 100644
index 000000000..c4267e686
--- /dev/null
+++ b/app/Filament/Server/Pages/Mounts.php
@@ -0,0 +1,114 @@
+can(SubuserPermission::MountRead, Filament::getTenant());
+ }
+
+ protected function authorizeAccess(): void
+ {
+ abort_unless(user()?->can(SubuserPermission::MountRead, Filament::getTenant()), 403);
+ }
+
+ protected function fillForm(): void
+ {
+ $this->form->fill([
+ 'mounts' => $this->getRecord()->mounts->pluck('id')->toArray(),
+ ]);
+ }
+
+ public function form(Schema $schema): Schema
+ {
+ $server = $this->getRecord();
+
+ $allowedMounts = Mount::query()
+ ->where('user_mountable', true)
+ ->where(function ($query) use ($server) {
+ $query->whereDoesntHave('nodes')
+ ->orWhereHas('nodes', fn ($q) => $q->where('nodes.id', $server->node_id));
+ })
+ ->where(function ($query) use ($server) {
+ $query->whereDoesntHave('eggs')
+ ->orWhereHas('eggs', fn ($q) => $q->where('eggs.id', $server->egg_id));
+ })
+ ->get();
+
+ return parent::form($schema)
+ ->components([
+ Section::make([
+ CheckboxList::make('mounts')
+ ->label(trans('server/mount.description'))
+ ->relationship('mounts')
+ ->options(fn () => $allowedMounts->mapWithKeys(fn (Mount $mount) => [$mount->id => $mount->name]))
+ ->descriptions(fn () => $allowedMounts->mapWithKeys(fn (Mount $mount) => [$mount->id => new HtmlString(str("$mount->source -> $mount->target")->stripTags() . ($mount->description ? '
' . str($mount->description)->stripTags() : ''))]))
+ ->helperText(fn () => $allowedMounts->isEmpty() ? trans('server/mount.no_mounts') : null)
+ ->disabled(fn (Server $server) => !user()?->can(SubuserPermission::MountUpdate, $server))
+ ->bulkToggleable()
+ ->live()
+ ->afterStateUpdated(function ($state) {
+ $this->save();
+ })
+ ->columnSpanFull(),
+ ]),
+ ]);
+ }
+
+ public function save(): void
+ {
+ abort_unless(user()?->can(SubuserPermission::MountUpdate, $this->getRecord()), 403);
+
+ try {
+ $this->form->getState();
+ $this->form->saveRelationships();
+
+ Activity::event('server:mount.update')
+ ->log();
+
+ Notification::make()
+ ->title(trans('server/mount.notification_updated'))
+ ->body(trans('server/mount.notification_updated_body'))
+ ->success()
+ ->send();
+ } catch (Exception $exception) {
+ report($exception);
+
+ Notification::make()
+ ->title(trans('server/mount.notification_failed'))
+ ->body($exception->getMessage())
+ ->danger()
+ ->send();
+ }
+ }
+
+ public function getTitle(): string
+ {
+ return trans('server/mount.title');
+ }
+
+ public static function getNavigationLabel(): string
+ {
+ return trans('server/mount.title');
+ }
+}
diff --git a/app/Filament/Server/Pages/Settings.php b/app/Filament/Server/Pages/Settings.php
index 5cab330e5..5e8394a5b 100644
--- a/app/Filament/Server/Pages/Settings.php
+++ b/app/Filament/Server/Pages/Settings.php
@@ -27,7 +27,7 @@ class Settings extends ServerFormPage
{
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Settings;
- protected static ?int $navigationSort = 10;
+ protected static ?int $navigationSort = 11;
/**
* @throws Exception
diff --git a/app/Filament/Server/Pages/Startup.php b/app/Filament/Server/Pages/Startup.php
index 3bd714f51..8de8f688c 100644
--- a/app/Filament/Server/Pages/Startup.php
+++ b/app/Filament/Server/Pages/Startup.php
@@ -28,7 +28,7 @@ class Startup extends ServerFormPage
{
protected static string|BackedEnum|null $navigationIcon = TablerIcon::PlayerPlay;
- protected static ?int $navigationSort = 9;
+ protected static ?int $navigationSort = 10;
/**
* @throws Exception
diff --git a/lang/en/activity.php b/lang/en/activity.php
index 464854f00..4667ff5f8 100644
--- a/lang/en/activity.php
+++ b/lang/en/activity.php
@@ -120,6 +120,9 @@ return [
'update' => 'Updated the subuser permissions for :email',
'delete' => 'Removed :email as a subuser',
],
+ 'mount' => [
+ 'update' => 'Updated the mounts for the server',
+ ],
'crashed' => 'Server crashed',
],
];
diff --git a/lang/en/admin/mount.php b/lang/en/admin/mount.php
index 1f0c6f949..c0e0f826f 100644
--- a/lang/en/admin/mount.php
+++ b/lang/en/admin/mount.php
@@ -17,14 +17,19 @@ return [
'no_mounts' => 'No Mounts',
'eggs' => 'Eggs',
'nodes' => 'Nodes',
+ 'user_mountable' => 'User Mountable?',
+ 'user_mountable_help' => 'Should users be able to toggle this mount on or off for their servers?',
'toggles' => [
'writable' => 'Writable',
'read_only' => 'Read Only',
+ 'user_mountable' => 'User Mountable',
+ 'not_user_mountable' => 'Admin Only',
],
'table' => [
'name' => 'Name',
'all_eggs' => 'All Eggs',
'all_nodes' => 'All Nodes',
'read_only' => 'Read Only',
+ 'user_mountable' => 'User Mountable',
],
];
diff --git a/lang/en/server/mount.php b/lang/en/server/mount.php
new file mode 100644
index 000000000..42a191561
--- /dev/null
+++ b/lang/en/server/mount.php
@@ -0,0 +1,10 @@
+ 'Mounts',
+ 'description' => 'Manage the mounts attached to your server:',
+ 'no_mounts' => 'There are no user-mountable mounts available for this server.',
+ 'notification_updated' => 'Mounts updated successfully',
+ 'notification_updated_body' => 'Restart your server to apply the new mounts',
+ 'notification_failed' => 'Failed to update mounts',
+];
diff --git a/lang/en/server/user.php b/lang/en/server/user.php
index a1b2d3e3d..631bb17fd 100644
--- a/lang/en/server/user.php
+++ b/lang/en/server/user.php
@@ -100,5 +100,8 @@ return [
'backup_delete' => 'Allows a user to remove backups from the system.',
'backup_download' => 'Allows a user to download a backup for the server. Danger: this allows a user to access all files for the server in the backup.',
'backup_restore' => 'Allows a user to restore a backup for the server. Danger: this allows the user to delete all of the server files in the process.',
+ 'mount_desc' => 'Permissions that control a user\'s ability to manage mounts for this server.',
+ 'mount_read' => 'Allows a user to view the mounts page and see available mounts.',
+ 'mount_update' => 'Allows a user to toggle mounts on or off for the server.',
],
];