mirror of
https://github.com/pelican-dev/panel.git
synced 2026-07-17 20:53:56 +03:00
Compare commits
3 Commits
boy132/ran
...
server-use
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da64dbb6b5 | ||
|
|
0ef479274d | ||
|
|
44077a1b40 |
31
app/Enums/ServerUserSettingKey.php
Normal file
31
app/Enums/ServerUserSettingKey.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ServerUserSettingKey: string
|
||||
{
|
||||
case ManualBackupNotifications = 'manual_backup_notifications';
|
||||
case ScheduledBackupNotifications = 'scheduled_backup_notifications';
|
||||
|
||||
/**
|
||||
* The default value for users without an explicit setting.
|
||||
*/
|
||||
public function getDefaultValue(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::ManualBackupNotifications, self::ScheduledBackupNotifications => false,
|
||||
};
|
||||
}
|
||||
|
||||
/** @return array<string, bool> */
|
||||
public static function getDefaultSettings(): array
|
||||
{
|
||||
$default = [];
|
||||
|
||||
foreach (self::cases() as $key) {
|
||||
$default[$key->value] = $key->getDefaultValue();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ final class CreateBackupSchema extends TaskSchema
|
||||
|
||||
public function runTask(Task $task): void
|
||||
{
|
||||
$this->initiateService->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($task->server, null, true);
|
||||
$this->initiateService->setIsScheduled(true)->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($task->server, null, true);
|
||||
}
|
||||
|
||||
public function canCreate(Schedule $schedule): bool
|
||||
|
||||
@@ -689,16 +689,6 @@ class Settings extends Page implements HasSchemas
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REINSTALL_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_REINSTALL_NOTIFICATION', config('panel.email.send_reinstall_notification'))),
|
||||
Toggle::make('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.backup_completed'))
|
||||
->onIcon(TablerIcon::Check)
|
||||
->offIcon(TablerIcon::X)
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', config('panel.email.send_backup_completed_notification'))),
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.connections.title'))
|
||||
->description(trans('admin/setting.misc.connections.helper'))
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHosts\RelationManagers;
|
||||
|
||||
use App\Filament\Admin\Resources\Servers\Pages\EditServer;
|
||||
use App\Filament\Components\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Models\Database;
|
||||
@@ -51,7 +50,7 @@ class DatabasesRelationManager extends RelationManager
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('database')
|
||||
->heading(null)
|
||||
->heading('')
|
||||
->columns([
|
||||
TextColumn::make('database'),
|
||||
TextColumn::make('username')
|
||||
@@ -60,7 +59,7 @@ class DatabasesRelationManager extends RelationManager
|
||||
->label(trans('admin/databasehost.table.remote'))
|
||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||
TextColumn::make('server.name')
|
||||
->url(fn (Database $database) => user()?->can('update', $database->server) ? EditServer::getUrl(['record' => $database->server]) : null),
|
||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||
TextColumn::make('max_connections')
|
||||
->label(trans('admin/databasehost.table.max_connections'))
|
||||
->formatStateUsing(fn ($record) => $record->max_connections ?: trans('server/database.unlimited')),
|
||||
|
||||
@@ -43,8 +43,12 @@ class ListEggs extends ListRecords
|
||||
$defaultEggIcon = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path($defaultEggIcon)));
|
||||
|
||||
return $table
|
||||
->searchable(true)
|
||||
->defaultPaginationPageOption(25)
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('Id')
|
||||
->hidden(),
|
||||
ImageColumn::make('icon')
|
||||
->label('')
|
||||
->alignCenter()
|
||||
@@ -52,7 +56,7 @@ class ListEggs extends ListRecords
|
||||
->getStateUsing(fn (Egg $record) => $record->icon ?: $defaultEggIcon),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/egg.name'))
|
||||
->description(fn ($record) => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
->wrap()
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
@@ -2,13 +2,9 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Eggs\RelationManagers;
|
||||
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
|
||||
use App\Filament\Admin\Resources\Servers\Pages\EditServer;
|
||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||
use App\Filament\Components\Actions\ViewConsoleAction;
|
||||
use App\Models\Server;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -22,35 +18,27 @@ class ServersRelationManager extends RelationManager
|
||||
->recordTitleAttribute('servers')
|
||||
->emptyStateDescription(trans('admin/egg.no_servers'))
|
||||
->emptyStateHeading(trans('admin/egg.no_servers_help'))
|
||||
->searchable(false)
|
||||
->heading(trans('admin/egg.servers'))
|
||||
->columns([
|
||||
TextColumn::make('user.username')
|
||||
->label(trans('admin/server.owner'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->user) ? EditUser::getUrl(['record' => $server->user]) : null)
|
||||
->sortable()
|
||||
->searchable(),
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
|
||||
->sortable(),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/server.name'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server) ? EditServer::getUrl(['record' => $server]) : null)
|
||||
->sortable()
|
||||
->searchable(),
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.servers.edit', ['record' => $server]))
|
||||
->sortable(),
|
||||
TextColumn::make('node.name')
|
||||
->label(trans('admin/server.node'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->node) ? EditNode::getUrl(['record' => $server->node]) : null)
|
||||
->sortable()
|
||||
->searchable(),
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node])),
|
||||
TextColumn::make('image')
|
||||
->label(trans('admin/server.docker_image'))
|
||||
->badge(),
|
||||
TextColumn::make('allocation.address')
|
||||
->label(trans('admin/server.docker_image')),
|
||||
SelectColumn::make('allocation.id')
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->disabled()
|
||||
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||
->placeholder(trans('admin/server.none'))
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
ViewConsoleAction::make(),
|
||||
EditAction::make()
|
||||
->url(fn (Server $server) => EditServer::getUrl(['record' => $server], panel: 'admin')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ class ListNodes extends ListRecords
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->searchable(false)
|
||||
->checkIfRecordIsSelectableUsing(fn (Node $node) => $node->servers_count <= 0)
|
||||
->columns([
|
||||
TextColumn::make('uuid')
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
|
||||
use App\Filament\Admin\Resources\Servers\Pages\EditServer;
|
||||
use App\Filament\Components\Actions\UpdateNodeAllocations;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Node;
|
||||
@@ -12,8 +11,6 @@ use App\Services\Allocations\AssignmentService;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
@@ -33,7 +30,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'allocations';
|
||||
|
||||
protected static string|BackedEnum|null $icon = TablerIcon::Network;
|
||||
protected static string|BackedEnum|null $icon = TablerIcon::PlugConnected;
|
||||
|
||||
public function setTitle(): string
|
||||
{
|
||||
@@ -49,14 +46,28 @@ class AllocationsRelationManager extends RelationManager
|
||||
->recordTitleAttribute('address')
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->server_id === null)
|
||||
->paginationPageOptions([10, 20, 50, 100, 200, 500])
|
||||
->heading(null)
|
||||
->searchable()
|
||||
->heading('')
|
||||
->selectCurrentPageOnly() //Prevent people from trying to nuke 30,000 ports at once.... -,-
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->sortable()
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
TextColumn::make('port')
|
||||
->searchable()
|
||||
->label(trans('admin/node.ports')),
|
||||
TextColumn::make('server.name')
|
||||
->label(trans('admin/node.table.servers'))
|
||||
->icon(TablerIcon::BrandDocker)
|
||||
->visibleFrom('md')
|
||||
->searchable()
|
||||
->url(fn (Allocation $allocation): string => $allocation->server ? route('filament.admin.resources.servers.edit', ['record' => $allocation->server]) : ''),
|
||||
TextInputColumn::make('ip_alias')
|
||||
->searchable()
|
||||
->label(trans('admin/node.table.alias')),
|
||||
TextInputColumn::make('notes')
|
||||
->label(trans('admin/node.table.allocation_notes'))
|
||||
->placeholder(trans('admin/node.table.no_notes')),
|
||||
SelectColumn::make('ip')
|
||||
->options(function (Allocation $allocation) {
|
||||
$ips = Allocation::where('port', $allocation->port)->pluck('ip');
|
||||
@@ -70,35 +81,15 @@ class AllocationsRelationManager extends RelationManager
|
||||
})
|
||||
->selectablePlaceholder(false)
|
||||
->searchable()
|
||||
->sortable()
|
||||
->label(trans('admin/node.table.ip')),
|
||||
TextColumn::make('port')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->label(trans('admin/node.port')),
|
||||
TextInputColumn::make('ip_alias')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->label(trans('admin/node.table.alias'))
|
||||
->placeholder(trans('admin/node.table.no_alias')),
|
||||
TextInputColumn::make('notes')
|
||||
->label(trans('admin/node.table.allocation_notes'))
|
||||
->placeholder(trans('admin/node.table.no_notes')),
|
||||
TextColumn::make('server.name')
|
||||
->label(trans('admin/node.table.servers'))
|
||||
->placeholder(trans('admin/node.table.no_server'))
|
||||
->visibleFrom('md')
|
||||
->searchable()
|
||||
->url(fn (Allocation $allocation) => $allocation->server && user()?->can('update', $allocation->server) ? EditServer::getUrl(['record' => $allocation->server]) : null),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/node.no_allocations'))
|
||||
->recordActions([
|
||||
DeleteAction::make()
|
||||
->visible(fn (Allocation $allocation) => $allocation->server_id === null),
|
||||
])
|
||||
->toolbarActions([
|
||||
CreateAction::make()
|
||||
->createAnother(false)
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => user()?->can('update', $this->getOwnerRecord())),
|
||||
Action::make('create new allocation')
|
||||
->label(trans('admin/node.create_allocation'))
|
||||
->tooltip(trans('admin/node.create_allocation'))
|
||||
->icon(TablerIcon::WorldPlus)
|
||||
->schema(fn () => [
|
||||
Select::make('allocation_ip')
|
||||
@@ -153,7 +144,6 @@ class AllocationsRelationManager extends RelationManager
|
||||
UpdateNodeAllocations::make()
|
||||
->nodeRecord($this->getOwnerRecord())
|
||||
->authorize(fn () => user()?->can('update', $this->getOwnerRecord())),
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,8 @@ namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
|
||||
|
||||
use App\Enums\ServerResourceType;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||
use App\Filament\Admin\Resources\Servers\Pages\EditServer;
|
||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||
use App\Filament\Components\Actions\ViewConsoleAction;
|
||||
use App\Models\Server;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
@@ -30,27 +25,29 @@ class ServersRelationManager extends RelationManager
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->heading(null)
|
||||
->searchable(false)
|
||||
->heading('')
|
||||
->columns([
|
||||
TextColumn::make('user.username')
|
||||
->label(trans('admin/node.table.owner'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->user) ? EditUser::getUrl(['record' => $server->user]) : null)
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
|
||||
->searchable(),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/node.table.name'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server) ? EditServer::getUrl(['record' => $server]) : null)
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.servers.edit', ['record' => $server]))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('egg.name')
|
||||
->label(trans('admin/node.table.egg'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->egg) ? EditEgg::getUrl(['record' => $server->egg]) : null)
|
||||
->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->user]))
|
||||
->sortable(),
|
||||
SelectColumn::make('allocation.id')
|
||||
->label(trans('admin/node.primary_allocation'))
|
||||
->disabled(fn (Server $server) => $server->allocations->count() <= 1)
|
||||
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||
->selectablePlaceholder(fn (Server $server) => $server->allocations->count() <= 1)
|
||||
->placeholder(trans('admin/server.none')),
|
||||
->placeholder(trans('admin/server.none'))
|
||||
->sortable(),
|
||||
TextColumn::make('cpu')
|
||||
->label(trans('admin/node.cpu'))
|
||||
->state(fn (Server $server) => $server->formatResource(ServerResourceType::CPULimit)),
|
||||
@@ -64,22 +61,13 @@ class ServersRelationManager extends RelationManager
|
||||
->counts('databases')
|
||||
->label(trans('admin/node.databases'))
|
||||
->numeric()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
->sortable(),
|
||||
TextColumn::make('backups_count')
|
||||
->counts('backups')
|
||||
->label(trans('admin/node.backups'))
|
||||
->numeric()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
->sortable(),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/server.no_servers'))
|
||||
->recordActions([
|
||||
ViewConsoleAction::make(),
|
||||
EditAction::make()
|
||||
->url(fn (Server $server) => EditServer::getUrl(['record' => $server], panel: 'admin')),
|
||||
]);
|
||||
->emptyStateHeading(trans('admin/server.no_servers'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
namespace App\Filament\Admin\Resources\Servers\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
|
||||
use App\Filament\Admin\Resources\Servers\ServerResource;
|
||||
use App\Filament\Admin\Resources\Users\Pages\EditUser;
|
||||
use App\Filament\Components\Actions\ViewConsoleAction;
|
||||
use App\Filament\Server\Pages\Console;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
@@ -30,11 +28,12 @@ class ListServers extends ListRecords
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->searchable(false)
|
||||
->defaultGroup('node.name')
|
||||
->groups([
|
||||
Group::make('node.name')->getDescriptionFromRecordUsing(fn (Server $server) => str($server->node->description)->limit(150)),
|
||||
Group::make('user.username')->getDescriptionFromRecordUsing(fn (Server $server) => $server->user->email),
|
||||
Group::make('egg.name')->getDescriptionFromRecordUsing(fn (Server $server) => str($server->egg->description)->limit(150)),
|
||||
Group::make('node.name')->getDescriptionFromRecordUsing(fn (Server $server): string => str($server->node->description)->limit(150)),
|
||||
Group::make('user.username')->getDescriptionFromRecordUsing(fn (Server $server): string => $server->user->email),
|
||||
Group::make('egg.name')->getDescriptionFromRecordUsing(fn (Server $server): string => str($server->egg->description)->limit(150)),
|
||||
])
|
||||
->columns([
|
||||
TextColumn::make('condition')
|
||||
@@ -55,19 +54,19 @@ class ListServers extends ListRecords
|
||||
->sortable(),
|
||||
TextColumn::make('node.name')
|
||||
->label(trans('admin/server.node'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->node) ? EditNode::getUrl(['record' => $server->node]) : null)
|
||||
->url(fn (Server $server) => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
|
||||
->hidden(fn (Table $table) => $table->getGrouping()?->getId() === 'node.name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('egg.name')
|
||||
->label(trans('admin/server.egg'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->egg) ? EditEgg::getUrl(['record' => $server->egg]) : null)
|
||||
->url(fn (Server $server) => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
|
||||
->hidden(fn (Table $table) => $table->getGrouping()?->getId() === 'egg.name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('user.username')
|
||||
->label(trans('admin/user.username'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->user) ? EditUser::getUrl(['record' => $server->user]) : null)
|
||||
->url(fn (Server $server) => route('filament.admin.resources.users.edit', ['record' => $server->user]))
|
||||
->hidden(fn (Table $table) => $table->getGrouping()?->getId() === 'user.username')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
@@ -83,23 +82,19 @@ class ListServers extends ListRecords
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->hidden(fn () => user()?->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
|
||||
->state(fn (Server $server) => $server->allocation->address ?? trans('admin/server.none')),
|
||||
TextColumn::make('databases_count')
|
||||
->counts('databases')
|
||||
->label(trans('admin/server.databases'))
|
||||
->numeric()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
TextColumn::make('image')->hidden(),
|
||||
TextColumn::make('backups_count')
|
||||
->counts('backups')
|
||||
->label(trans('admin/server.backups'))
|
||||
->numeric()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
ViewConsoleAction::make(),
|
||||
Action::make('view')
|
||||
->tooltip(trans('admin/server.view'))
|
||||
->icon(TablerIcon::Terminal)
|
||||
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
|
||||
->authorize(fn (Server $server) => user()?->canAccessTenant($server)),
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Server;
|
||||
use App\Services\Allocations\AssignmentService;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\AssociateAction;
|
||||
use Filament\Actions\CreateAction;
|
||||
@@ -20,6 +19,7 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
@@ -32,30 +32,21 @@ class AllocationsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'allocations';
|
||||
|
||||
protected static string|BackedEnum|null $icon = TablerIcon::Network;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->heading(null)
|
||||
->heading('')
|
||||
->selectCurrentPageOnly()
|
||||
->recordTitleAttribute('address')
|
||||
->recordTitle(fn (Allocation $allocation) => $allocation->address)
|
||||
->inverseRelationship('server')
|
||||
->columns([
|
||||
TextColumn::make('ip')
|
||||
->label(trans('admin/server.ip_address'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
->label(trans('admin/server.ip_address')),
|
||||
TextColumn::make('port')
|
||||
->label(trans('admin/server.port'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
->label(trans('admin/server.port')),
|
||||
TextInputColumn::make('ip_alias')
|
||||
->label(trans('admin/server.alias'))
|
||||
->placeholder(trans('admin/server.no_alias'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
->label(trans('admin/server.alias')),
|
||||
TextInputColumn::make('notes')
|
||||
->label(trans('admin/server.notes'))
|
||||
->placeholder(trans('admin/server.no_notes')),
|
||||
@@ -81,26 +72,18 @@ class AllocationsRelationManager extends RelationManager
|
||||
->emptyStateHeading(trans('admin/server.no_allocations'))
|
||||
->recordActions([
|
||||
Action::make('make-primary')
|
||||
->authorize(fn (Allocation $allocation) => user()?->can('update', $allocation))
|
||||
->tooltip(trans('admin/server.make_primary'))
|
||||
->icon(TablerIcon::Star)
|
||||
->label(trans('admin/server.make_primary'))
|
||||
->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords())
|
||||
->hidden(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id),
|
||||
Action::make('lock')
|
||||
->authorize(fn (Allocation $allocation) => user()?->can('update', $allocation))
|
||||
->tooltip(trans('admin/server.lock'))
|
||||
->icon(TablerIcon::Lock)
|
||||
->label(trans('admin/server.lock'))
|
||||
->action(fn (Allocation $allocation) => $allocation->update(['is_locked' => true]) && $this->deselectAllTableRecords())
|
||||
->hidden(fn (Allocation $allocation) => $allocation->is_locked),
|
||||
Action::make('unlock')
|
||||
->authorize(fn (Allocation $allocation) => user()?->can('update', $allocation))
|
||||
->tooltip(trans('admin/server.unlock'))
|
||||
->icon(TablerIcon::LockOpen)
|
||||
->label(trans('admin/server.unlock'))
|
||||
->action(fn (Allocation $allocation) => $allocation->update(['is_locked' => false]) && $this->deselectAllTableRecords())
|
||||
->visible(fn (Allocation $allocation) => $allocation->is_locked),
|
||||
DissociateAction::make()
|
||||
->authorize(fn (Allocation $allocation) => user()?->can('update', $allocation))
|
||||
->tooltip(trans('admin/server.remove_allocation'))
|
||||
->after(function (Allocation $allocation) {
|
||||
$allocation->update([
|
||||
'notes' => null,
|
||||
@@ -127,7 +110,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
CreateAction::make()
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/server.create_allocation'))
|
||||
->icon(TablerIcon::WorldPlus)
|
||||
->icon(TablerIcon::Network)
|
||||
->createAnother(false)
|
||||
->schema(fn () => [
|
||||
Select::make('allocation_ip')
|
||||
@@ -180,6 +163,8 @@ class AllocationsRelationManager extends RelationManager
|
||||
])
|
||||
->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord())),
|
||||
AssociateAction::make()
|
||||
->icon(TablerIcon::FilePlus)
|
||||
->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->multiple()
|
||||
->associateAnother(false)
|
||||
->preloadRecordSelect()
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Servers\Pages\EditServer;
|
||||
use App\Filament\Components\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Models\Database;
|
||||
@@ -11,7 +10,6 @@ use App\Models\DatabaseHost;
|
||||
use App\Models\Server;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Services\Servers\RandomWordService;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
@@ -32,8 +30,6 @@ class DatabasesRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'databases';
|
||||
|
||||
protected static string|BackedEnum|null $icon = TablerIcon::Database;
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
@@ -66,7 +62,7 @@ class DatabasesRelationManager extends RelationManager
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->heading(null)
|
||||
->heading('')
|
||||
->recordTitleAttribute('database')
|
||||
->columns([
|
||||
TextColumn::make('database'),
|
||||
@@ -76,7 +72,7 @@ class DatabasesRelationManager extends RelationManager
|
||||
->label(trans('admin/databasehost.table.remote'))
|
||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
|
||||
TextColumn::make('server.name')
|
||||
->url(fn (Database $database) => user()?->can('update', $database->server) ? EditServer::getUrl(['record' => $database->server]) : null),
|
||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||
TextColumn::make('max_connections')
|
||||
->label(trans('admin/databasehost.table.max_connections'))
|
||||
->formatStateUsing(fn ($record) => $record->max_connections ?: trans('admin/databasehost.unlimited')),
|
||||
|
||||
@@ -4,15 +4,10 @@ namespace App\Filament\Admin\Resources\Users\RelationManagers;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use App\Enums\SuspendAction;
|
||||
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
|
||||
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
|
||||
use App\Filament\Admin\Resources\Servers\Pages\EditServer;
|
||||
use App\Filament\Components\Actions\ViewConsoleAction;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Services\Servers\SuspensionService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
@@ -28,6 +23,7 @@ class ServersRelationManager extends RelationManager
|
||||
$user = $this->getOwnerRecord();
|
||||
|
||||
return $table
|
||||
->searchable(false)
|
||||
->heading(trans('admin/user.servers'))
|
||||
->headerActions([
|
||||
Action::make('toggle_suspend')
|
||||
@@ -54,20 +50,22 @@ class ServersRelationManager extends RelationManager
|
||||
}),
|
||||
])
|
||||
->columns([
|
||||
TextColumn::make('uuid')
|
||||
->hidden()
|
||||
->label('UUID')
|
||||
->searchable(),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/server.name'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server) ? EditServer::getUrl(['record' => $server]) : null)
|
||||
->url(fn (Server $server) => route('filament.admin.resources.servers.edit', ['record' => $server]))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('node.name')
|
||||
->label(trans('admin/server.node'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->node) ? EditNode::getUrl(['record' => $server->node]) : null)
|
||||
->searchable()
|
||||
->url(fn (Server $server) => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
|
||||
->sortable(),
|
||||
TextColumn::make('egg.name')
|
||||
->label(trans('admin/server.egg'))
|
||||
->url(fn (Server $server) => user()?->can('update', $server->egg) ? EditEgg::getUrl(['record' => $server->egg]) : null)
|
||||
->searchable()
|
||||
->url(fn (Server $server) => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
|
||||
->sortable(),
|
||||
SelectColumn::make('allocation_id')
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
@@ -85,22 +83,13 @@ class ServersRelationManager extends RelationManager
|
||||
->counts('databases')
|
||||
->label(trans('admin/server.databases'))
|
||||
->numeric()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
->sortable(),
|
||||
TextColumn::make('backups_count')
|
||||
->counts('backups')
|
||||
->label(trans('admin/server.backups'))
|
||||
->numeric()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
->toggledHiddenByDefault(),
|
||||
->sortable(),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/server.no_servers'))
|
||||
->recordActions([
|
||||
ViewConsoleAction::make(),
|
||||
EditAction::make()
|
||||
->url(fn (Server $server) => EditServer::getUrl(['record' => $server], panel: 'admin')),
|
||||
]);
|
||||
->emptyStateHeading(trans('admin/server.no_servers'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,12 +118,10 @@ class UserResource extends Resource
|
||||
->defaultImageUrl(fn (User $user) => Filament::getUserAvatarUrl($user)),
|
||||
TextColumn::make('username')
|
||||
->label(trans('admin/user.username'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
->searchable(),
|
||||
TextColumn::make('email')
|
||||
->label(trans('admin/user.email'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
->searchable(),
|
||||
IconColumn::make('mfa_email_enabled')
|
||||
->label(trans('profile.tabs.2fa'))
|
||||
->visibleFrom('lg')
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Actions;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Server\Pages\Console;
|
||||
use App\Models\Server;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
|
||||
class ViewConsoleAction extends Action
|
||||
{
|
||||
protected ?Server $server = null;
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'view_console';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->tooltip(trans('admin/server.view_console'));
|
||||
|
||||
$this->icon(TablerIcon::Terminal);
|
||||
|
||||
$this->url(fn (?Server $server) => Console::getUrl(panel: 'server', tenant: $this->getServer() ?? $server));
|
||||
|
||||
$this->authorize(fn (?Server $server) => user()?->canAccessTenant($this->getServer() ?? $server));
|
||||
}
|
||||
|
||||
public function server(?Server $server): static
|
||||
{
|
||||
$this->server = $server;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getServer(): ?Server
|
||||
{
|
||||
/** @var ?Server $tenant */
|
||||
$tenant = Filament::getTenant();
|
||||
|
||||
return $this->server ?? $tenant;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Server\Pages;
|
||||
|
||||
use App\Enums\ServerUserSettingKey;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Facades\Activity;
|
||||
@@ -14,6 +15,7 @@ use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
@@ -254,9 +256,44 @@ class Settings extends ServerFormPage
|
||||
TextEntry::make('files_info')
|
||||
->label(trans('server/setting.reinstall.body2')),
|
||||
]),
|
||||
Section::make(trans('server/setting.notifications.title'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Toggle::make(ServerUserSettingKey::ManualBackupNotifications->value)
|
||||
->label(trans('server/setting.notifications.backup_manual.label'))
|
||||
->helperText(trans('server/setting.notifications.backup_manual.helper'))
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, Server $server) => $this->updateNotificationSetting(ServerUserSettingKey::ManualBackupNotifications, (bool) $state, $server)),
|
||||
Toggle::make(ServerUserSettingKey::ScheduledBackupNotifications->value)
|
||||
->label(trans('server/setting.notifications.backup_scheduled.label'))
|
||||
->helperText(trans('server/setting.notifications.backup_scheduled.helper'))
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, Server $server) => $this->updateNotificationSetting(ServerUserSettingKey::ScheduledBackupNotifications, (bool) $state, $server)),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function fillForm(): void
|
||||
{
|
||||
$data = $this->getRecord()->attributesToArray();
|
||||
|
||||
foreach (ServerUserSettingKey::cases() as $key) {
|
||||
$data[$key->value] = (bool) user()?->getServerSetting($this->getRecord(), $key);
|
||||
}
|
||||
|
||||
$this->form->fill($data);
|
||||
}
|
||||
|
||||
public function updateNotificationSetting(ServerUserSettingKey $key, bool $state, Server $server): void
|
||||
{
|
||||
user()?->updateServerSetting($server, $key, $state);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/setting.notifications.saved'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
public function updateName(string $name, Server $server): void
|
||||
{
|
||||
abort_unless(user()?->can(SubuserPermission::SettingsRename, $server), 403);
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\Backups\Pages;
|
||||
|
||||
use App\Enums\ServerUserSettingKey;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Server\Resources\Backups\BackupResource;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListBackups extends ListRecords
|
||||
@@ -14,6 +21,39 @@ class ListBackups extends ListRecords
|
||||
|
||||
protected static string $resource = BackupResource::class;
|
||||
|
||||
/** @return Action[] */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return [
|
||||
Action::make('notification_settings')
|
||||
->label(trans('server/backup.notifications.action'))
|
||||
->icon(TablerIcon::Bell)
|
||||
->schema([
|
||||
Toggle::make(ServerUserSettingKey::ManualBackupNotifications->value)
|
||||
->label(trans('server/backup.notifications.toggle_manual'))
|
||||
->helperText(trans('server/backup.notifications.helper_manual'))
|
||||
->default(fn () => (bool) user()?->getServerSetting($server, ServerUserSettingKey::ManualBackupNotifications)),
|
||||
Toggle::make(ServerUserSettingKey::ScheduledBackupNotifications->value)
|
||||
->label(trans('server/backup.notifications.toggle_scheduled'))
|
||||
->helperText(trans('server/backup.notifications.helper_scheduled'))
|
||||
->default(fn () => (bool) user()?->getServerSetting($server, ServerUserSettingKey::ScheduledBackupNotifications)),
|
||||
])
|
||||
->action(function (array $data) use ($server) {
|
||||
foreach (ServerUserSettingKey::cases() as $key) {
|
||||
user()?->updateServerSetting($server, $key, (bool) $data[$key->value]);
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/backup.notifications.saved'))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
return [];
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Listeners\Server;
|
||||
|
||||
use App\Enums\ServerUserSettingKey;
|
||||
use App\Events\Server\BackupCompleted;
|
||||
use App\Filament\Server\Resources\Backups\Pages\ListBackups;
|
||||
use App\Models\ServerUserSettings;
|
||||
use App\Models\User;
|
||||
use App\Notifications\BackupCompleted as BackupCompletedNotification;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
@@ -12,26 +15,47 @@ class BackupCompletedListener
|
||||
{
|
||||
public function handle(BackupCompleted $event): void
|
||||
{
|
||||
$event->backup->loadMissing('server');
|
||||
$event->backup->server->loadMissing('user');
|
||||
$event->backup->loadMissing(['server.user', 'server.subusers.user']);
|
||||
|
||||
$locale = $event->backup->server->user->language ?? 'en';
|
||||
$server = $event->backup->server;
|
||||
|
||||
Notification::make()
|
||||
->status($event->backup->is_successful ? 'success' : 'danger')
|
||||
->title(trans('notifications.backup_' . ($event->backup->is_successful ? 'completed' : 'failed'), locale: $locale))
|
||||
->body(trans('notifications.backup_body', ['name' => $event->backup->name, 'server' => $event->backup->server->name], $locale))
|
||||
->actions([
|
||||
Action::make('exclude_view')
|
||||
->button()
|
||||
->label(trans('notifications.view_backups', locale: $locale))
|
||||
->markAsRead()
|
||||
->url(fn () => ListBackups::getUrl(panel: 'server', tenant: $event->backup->server)),
|
||||
])
|
||||
->sendToDatabase($event->backup->server->user);
|
||||
$candidates = collect([$server->user])
|
||||
->concat($server->subusers->map->user)
|
||||
->filter()
|
||||
->unique('id');
|
||||
|
||||
if (config()->get('panel.email.send_backup_completed_notification', true)) {
|
||||
$event->backup->server->user->notify(new BackupCompletedNotification($event->backup));
|
||||
$settings = ServerUserSettings::query()
|
||||
->where('server_id', $server->id)
|
||||
->whereIn('user_id', $candidates->pluck('id'))
|
||||
->get()
|
||||
->keyBy('user_id');
|
||||
|
||||
// Users only receive notifications if they opted in for this kind of backup.
|
||||
$key = $event->backup->is_scheduled ? ServerUserSettingKey::ScheduledBackupNotifications : ServerUserSettingKey::ManualBackupNotifications;
|
||||
|
||||
$recipients = $candidates->filter(function (User $user) use ($settings, $key) {
|
||||
$userSettings = $settings->get($user->id)->settings ?? [];
|
||||
|
||||
return (bool) ($userSettings[$key->value] ?? $key->getDefaultValue());
|
||||
});
|
||||
|
||||
foreach ($recipients as $user) {
|
||||
$locale = $user->language ?? 'en';
|
||||
|
||||
Notification::make()
|
||||
->status($event->backup->is_successful ? 'success' : 'danger')
|
||||
->title(trans('notifications.backup_' . ($event->backup->is_successful ? 'completed' : 'failed'), locale: $locale))
|
||||
->body(trans('notifications.backup_body', ['name' => $event->backup->name, 'server' => $server->name], $locale))
|
||||
->actions([
|
||||
Action::make('exclude_view')
|
||||
->button()
|
||||
->label(trans('notifications.view_backups', locale: $locale))
|
||||
->markAsRead()
|
||||
->url(fn () => ListBackups::getUrl(panel: 'server', tenant: $server)),
|
||||
])
|
||||
->sendToDatabase($user);
|
||||
|
||||
$user->notify(new BackupCompletedNotification($event->backup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use Illuminate\Database\Query\Builder;
|
||||
* @property bool $is_successful
|
||||
* @property string|null $upload_id
|
||||
* @property bool $is_locked
|
||||
* @property bool $is_scheduled
|
||||
* @property-read Server $server
|
||||
* @property-read BackupStatus $status
|
||||
*
|
||||
@@ -69,6 +70,7 @@ class Backup extends Model implements Validatable
|
||||
protected $attributes = [
|
||||
'is_successful' => false,
|
||||
'is_locked' => false,
|
||||
'is_scheduled' => false,
|
||||
'checksum' => null,
|
||||
'bytes' => 0,
|
||||
'upload_id' => null,
|
||||
@@ -82,6 +84,7 @@ class Backup extends Model implements Validatable
|
||||
'uuid' => ['required', 'uuid'],
|
||||
'is_successful' => ['boolean'],
|
||||
'is_locked' => ['boolean'],
|
||||
'is_scheduled' => ['boolean'],
|
||||
'name' => ['required', 'string'],
|
||||
'ignored_files' => ['array'],
|
||||
'backup_host_id' => ['required', 'integer', 'exists:backup_hosts,id'],
|
||||
@@ -96,6 +99,7 @@ class Backup extends Model implements Validatable
|
||||
'id' => 'int',
|
||||
'is_successful' => 'bool',
|
||||
'is_locked' => 'bool',
|
||||
'is_scheduled' => 'bool',
|
||||
'ignored_files' => 'array',
|
||||
'bytes' => 'int',
|
||||
'completed_at' => 'immutable_datetime',
|
||||
|
||||
@@ -90,6 +90,8 @@ use Psr\Http\Message\ResponseInterface;
|
||||
* @property-read int|null $server_variables_count
|
||||
* @property-read Collection<int, Subuser> $subusers
|
||||
* @property-read int|null $subusers_count
|
||||
* @property-read Collection<int, ServerUserSettings> $userSettings
|
||||
* @property-read int|null $user_settings_count
|
||||
* @property-read ServerTransfer|null $transfer
|
||||
* @property-read User $user
|
||||
* @property-read Collection<int, EggVariable> $variables
|
||||
@@ -271,6 +273,16 @@ class Server extends Model implements HasAvatar, Validatable
|
||||
return $this->hasMany(Subuser::class, 'server_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the per-user settings associated with a server.
|
||||
*
|
||||
* @return HasMany<ServerUserSettings, $this>
|
||||
*/
|
||||
public function userSettings(): HasMany
|
||||
{
|
||||
return $this->hasMany(ServerUserSettings::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default allocation for a server.
|
||||
*/
|
||||
|
||||
84
app/Models/ServerUserSettings.php
Normal file
84
app/Models/ServerUserSettings.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Contracts\Validatable;
|
||||
use App\Traits\HasValidation;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $server_id
|
||||
* @property array<string, string|int|bool>|null $settings
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property-read Server $server
|
||||
* @property-read User $user
|
||||
*
|
||||
* @method static \Database\Factories\ServerUserSettingsFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings whereServerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings whereSettings($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|ServerUserSettings whereUserId($value)
|
||||
*/
|
||||
class ServerUserSettings extends Model implements Validatable
|
||||
{
|
||||
use HasFactory;
|
||||
use HasValidation;
|
||||
|
||||
/**
|
||||
* The resource name for this model when it is transformed into an
|
||||
* API representation using fractal.
|
||||
*/
|
||||
public const RESOURCE_NAME = 'server_user_settings';
|
||||
|
||||
protected $table = 'server_user_settings';
|
||||
|
||||
/**
|
||||
* Fields that are not mass assignable.
|
||||
*/
|
||||
protected $guarded = ['id', 'created_at', 'updated_at'];
|
||||
|
||||
/** @var array<array-key, string[]> */
|
||||
public static array $validationRules = [
|
||||
'user_id' => ['required', 'numeric', 'exists:users,id'],
|
||||
'server_id' => ['required', 'numeric', 'exists:servers,id'],
|
||||
'settings' => ['nullable', 'array'],
|
||||
'settings.manual_backup_notifications' => ['boolean'],
|
||||
'settings.scheduled_backup_notifications' => ['boolean'],
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => 'int',
|
||||
'server_id' => 'int',
|
||||
'settings' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server these settings are associated with.
|
||||
*/
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user these settings are associated with.
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use App\Contracts\Validatable;
|
||||
use App\Enums\CustomizationKey;
|
||||
use App\Enums\ServerUserSettingKey;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Events\User\Deleting;
|
||||
use App\Exceptions\DisplayException;
|
||||
@@ -86,6 +87,8 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
* @property-read int|null $sub_servers_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, Subuser> $subusers
|
||||
* @property-read int|null $subusers_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, ServerUserSettings> $serverSettings
|
||||
* @property-read int|null $server_settings_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, ApiKey> $tokens
|
||||
* @property-read int|null $tokens_count
|
||||
*
|
||||
@@ -351,6 +354,30 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
return $this->belongsToMany(Server::class, 'subusers');
|
||||
}
|
||||
|
||||
/** @return HasMany<ServerUserSettings, $this> */
|
||||
public function serverSettings(): HasMany
|
||||
{
|
||||
return $this->hasMany(ServerUserSettings::class);
|
||||
}
|
||||
|
||||
public function getServerSetting(Server $server, ServerUserSettingKey $key): string|int|bool
|
||||
{
|
||||
$settings = $this->serverSettings()->where('server_id', $server->id)->first()->settings ?? [];
|
||||
|
||||
return $settings[$key->value] ?? $key->getDefaultValue();
|
||||
}
|
||||
|
||||
public function updateServerSetting(Server $server, ServerUserSettingKey $key, string|int|bool $value): void
|
||||
{
|
||||
$row = $this->serverSettings()->firstOrNew(['server_id' => $server->id]);
|
||||
|
||||
$settings = $row->settings ?? [];
|
||||
$settings[$key->value] = $value;
|
||||
|
||||
$row->settings = array_intersect_key($settings, ServerUserSettingKey::getDefaultSettings());
|
||||
$row->save();
|
||||
}
|
||||
|
||||
/** @return ($key is null ? array<string, string|int|bool> : string|int|bool) */
|
||||
public function getCustomization(?CustomizationKey $key = null): array|string|int|bool|null
|
||||
{
|
||||
|
||||
@@ -6,11 +6,8 @@ use App\Enums\CustomizationKey;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Livewire\Passkeys;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\AssociateAction;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\DissociateAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\View\ActionsIconAlias;
|
||||
use Filament\Actions\ViewAction;
|
||||
@@ -30,7 +27,6 @@ use Filament\Support\Facades\FilamentColor;
|
||||
use Filament\Support\Facades\FilamentIcon;
|
||||
use Filament\Support\Facades\FilamentView;
|
||||
use Filament\Support\View\SupportIconAlias;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\View\TablesIconAlias;
|
||||
use Filament\View\PanelsIconAlias;
|
||||
use Filament\View\PanelsRenderHook;
|
||||
@@ -108,8 +104,6 @@ class FilamentServiceProvider extends ServiceProvider
|
||||
|
||||
Select::configureUsing(fn (Select $select) => $select->native(false));
|
||||
|
||||
SelectColumn::configureUsing(fn (SelectColumn $select) => $select->native(false));
|
||||
|
||||
KeyValue::configureUsing(fn (KeyValue $keyValue) => $keyValue
|
||||
->addAction(function (Action $action) {
|
||||
$action->tooltip(fn () => $action->getLabel());
|
||||
@@ -167,18 +161,6 @@ class FilamentServiceProvider extends ServiceProvider
|
||||
}
|
||||
});
|
||||
|
||||
DeleteBulkAction::configureUsing(function (DeleteBulkAction $action) {
|
||||
$action->icon(TablerIcon::Trash);
|
||||
$action->tooltip(fn () => $action->getLabel());
|
||||
$action->hiddenLabel();
|
||||
$action->iconSize(IconSize::Large);
|
||||
|
||||
if (user()?->getCustomization(CustomizationKey::ButtonStyle)) {
|
||||
$action->iconButton();
|
||||
$action->iconSize(IconSize::ExtraLarge);
|
||||
}
|
||||
});
|
||||
|
||||
CreateAction::configureUsing(function (CreateAction $action) {
|
||||
$action->icon(TablerIcon::Plus);
|
||||
$action->tooltip(fn () => $action->getLabel());
|
||||
@@ -215,30 +197,6 @@ class FilamentServiceProvider extends ServiceProvider
|
||||
}
|
||||
});
|
||||
|
||||
AssociateAction::configureUsing(function (AssociateAction $action) {
|
||||
$action->icon(TablerIcon::Link);
|
||||
$action->tooltip(fn () => $action->getLabel());
|
||||
$action->hiddenLabel();
|
||||
$action->iconSize(IconSize::Large);
|
||||
|
||||
if (user()?->getCustomization(CustomizationKey::ButtonStyle)) {
|
||||
$action->iconButton();
|
||||
$action->iconSize(IconSize::ExtraLarge);
|
||||
}
|
||||
});
|
||||
|
||||
DissociateAction::configureUsing(function (DissociateAction $action) {
|
||||
$action->icon(TablerIcon::Unlink);
|
||||
$action->tooltip(fn () => $action->getLabel());
|
||||
$action->hiddenLabel();
|
||||
$action->iconSize(IconSize::Large);
|
||||
|
||||
if (user()?->getCustomization(CustomizationKey::ButtonStyle)) {
|
||||
$action->iconButton();
|
||||
$action->iconSize(IconSize::ExtraLarge);
|
||||
}
|
||||
});
|
||||
|
||||
Action::configureUsing(function (Action $action) {
|
||||
$action->iconSize(IconSize::Large);
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ class InitiateBackupService
|
||||
|
||||
private bool $isLocked = false;
|
||||
|
||||
private bool $isScheduled = false;
|
||||
|
||||
/**
|
||||
* InitiateBackupService constructor.
|
||||
*/
|
||||
@@ -41,6 +43,16 @@ class InitiateBackupService
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the backup was initiated by a schedule rather than a user.
|
||||
*/
|
||||
public function setIsScheduled(bool $isScheduled): self
|
||||
{
|
||||
$this->isScheduled = $isScheduled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the files to be ignored by this backup.
|
||||
*
|
||||
@@ -125,6 +137,7 @@ class InitiateBackupService
|
||||
'ignored_files' => array_values($this->ignoredFiles ?? []),
|
||||
'backup_host_id' => $backupHost->id,
|
||||
'is_locked' => $this->isLocked,
|
||||
'is_scheduled' => $this->isScheduled,
|
||||
]);
|
||||
|
||||
$schema->createBackup($backup);
|
||||
|
||||
@@ -20,6 +20,7 @@ class BackupTransformer extends BaseClientTransformer
|
||||
'uuid' => $backup->uuid,
|
||||
'is_successful' => $backup->is_successful,
|
||||
'is_locked' => $backup->is_locked,
|
||||
'is_scheduled' => $backup->is_scheduled,
|
||||
'name' => $backup->name,
|
||||
'ignored_files' => $backup->ignored_files,
|
||||
'checksum' => $backup->checksum,
|
||||
|
||||
@@ -55,8 +55,6 @@ return [
|
||||
'send_install_notification' => env('PANEL_SEND_INSTALL_NOTIFICATION', true),
|
||||
// Should an email be sent to a server owner whenever their server is reinstalled?
|
||||
'send_reinstall_notification' => env('PANEL_SEND_REINSTALL_NOTIFICATION', true),
|
||||
// Should an email be sent to a server owner whenever a backup is completed?
|
||||
'send_backup_completed_notification' => env('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', true),
|
||||
],
|
||||
|
||||
'filament' => [
|
||||
|
||||
56
database/Factories/ServerUserSettingsFactory.php
Normal file
56
database/Factories/ServerUserSettingsFactory.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\ServerUserSettingKey;
|
||||
use App\Models\ServerUserSettings;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ServerUserSettingsFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = ServerUserSettings::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'settings' => [
|
||||
ServerUserSettingKey::ManualBackupNotifications->value => true,
|
||||
ServerUserSettingKey::ScheduledBackupNotifications->value => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate the user has opted in to all backup notifications.
|
||||
*/
|
||||
public function optedIn(): static
|
||||
{
|
||||
return $this->state([
|
||||
'settings' => [
|
||||
ServerUserSettingKey::ManualBackupNotifications->value => true,
|
||||
ServerUserSettingKey::ScheduledBackupNotifications->value => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate the user has opted out of all backup notifications.
|
||||
*/
|
||||
public function optedOut(): static
|
||||
{
|
||||
return $this->state([
|
||||
'settings' => [
|
||||
ServerUserSettingKey::ManualBackupNotifications->value => false,
|
||||
ServerUserSettingKey::ScheduledBackupNotifications->value => false,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('server_user_settings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('server_id');
|
||||
$table->json('settings')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||
$table->foreign('server_id')->references('id')->on('servers')->cascadeOnDelete();
|
||||
$table->unique(['user_id', 'server_id']);
|
||||
});
|
||||
|
||||
// Backup notifications now default off for everyone. Owners on existing
|
||||
// installs previously always received them, so seed an opt-in row for each
|
||||
// server's owner unless the panel-wide email switch was already disabled.
|
||||
if (!filter_var(env('PANEL_SEND_BACKUP_COMPLETED_NOTIFICATION', true), FILTER_VALIDATE_BOOL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = now();
|
||||
|
||||
DB::table('servers')->select(['id', 'owner_id'])->orderBy('id')->chunkById(250, function ($servers) use ($now) {
|
||||
DB::table('server_user_settings')->insert($servers->map(fn ($server) => [
|
||||
'user_id' => $server->owner_id,
|
||||
'server_id' => $server->id,
|
||||
'settings' => json_encode(['manual_backup_notifications' => true, 'scheduled_backup_notifications' => true]),
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
])->all());
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('server_user_settings');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('backups', function (Blueprint $table) {
|
||||
$table->boolean('is_scheduled')->default(false)->after('is_locked');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('backups', function (Blueprint $table) {
|
||||
$table->dropColumn('is_scheduled');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -25,8 +25,6 @@ return [
|
||||
'owner' => 'Owner',
|
||||
'allocation_notes' => 'Notes',
|
||||
'no_notes' => 'No notes',
|
||||
'no_server' => 'No server',
|
||||
'no_alias' => 'No alias',
|
||||
],
|
||||
'node_info' => 'Node Information',
|
||||
'wings_version' => 'Wings Version',
|
||||
|
||||
@@ -15,7 +15,6 @@ return [
|
||||
'ports' => 'Ports',
|
||||
'alias' => 'Alias',
|
||||
'alias_helper' => 'Optional display name to help you remember what these are.',
|
||||
'no_alias' => 'No Alias',
|
||||
'locked' => 'Locked?',
|
||||
'locked_helper' => 'Users won\'t be able to delete locked allocations',
|
||||
'lock' => 'Lock',
|
||||
@@ -111,8 +110,7 @@ return [
|
||||
'keep_old_variables' => 'Keep old variables if possible?',
|
||||
'create_allocation' => 'Create Allocation',
|
||||
'add_allocation' => 'Add Allocation',
|
||||
'remove_allocation' => 'Remove Allocation',
|
||||
'view_console' => 'View Console',
|
||||
'view' => 'View',
|
||||
'no_log' => 'No Log Available',
|
||||
'select_backups' => 'Select Backups',
|
||||
'warning_backups' => 'Be aware, not transferred backups will be deleted.',
|
||||
|
||||
@@ -121,7 +121,6 @@ return [
|
||||
'removed_from_server' => 'Removed from Server',
|
||||
'server_installed' => 'Server Installed',
|
||||
'server_reinstalled' => 'Server Reinstalled',
|
||||
'backup_completed' => 'Backup Completed',
|
||||
],
|
||||
'connections' => [
|
||||
'title' => 'Connections',
|
||||
|
||||
@@ -12,6 +12,14 @@ return [
|
||||
'successful' => 'Successful',
|
||||
'failed' => 'Failed',
|
||||
],
|
||||
'notifications' => [
|
||||
'action' => 'Notification Settings',
|
||||
'toggle_manual' => 'Manual Backup Notifications',
|
||||
'helper_manual' => 'Receive a notification and email when a manually created backup for this server completes or fails.',
|
||||
'toggle_scheduled' => 'Scheduled Backup Notifications',
|
||||
'helper_scheduled' => 'Receive a notification and email when a scheduled backup for this server completes or fails.',
|
||||
'saved' => 'Notification Settings Updated',
|
||||
],
|
||||
'actions' => [
|
||||
'create' => [
|
||||
'title' => 'Create Backup',
|
||||
|
||||
@@ -41,6 +41,18 @@ return [
|
||||
'password_body' => 'Your SFTP password is the same as the password you use to access this panel.',
|
||||
],
|
||||
],
|
||||
'notifications' => [
|
||||
'title' => 'Notifications',
|
||||
'saved' => 'Notification Settings Updated',
|
||||
'backup_manual' => [
|
||||
'label' => 'Manual Backup Notifications',
|
||||
'helper' => 'Receive a notification and email when a manually created backup for this server completes or fails.',
|
||||
],
|
||||
'backup_scheduled' => [
|
||||
'label' => 'Scheduled Backup Notifications',
|
||||
'helper' => 'Receive a notification and email when a scheduled backup for this server completes or fails.',
|
||||
],
|
||||
],
|
||||
'reinstall' => [
|
||||
'title' => 'Reinstall Server',
|
||||
'body' => 'Reinstalling your server will stop it, and then re-run the installation script that initially set it up.',
|
||||
|
||||
143
tests/Feature/BackupNotificationSettingsTest.php
Normal file
143
tests/Feature/BackupNotificationSettingsTest.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ServerUserSettingKey;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Events\Server\BackupCompleted;
|
||||
use App\Listeners\Server\BackupCompletedListener;
|
||||
use App\Models\Backup;
|
||||
use App\Models\BackupHost;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerUserSettings;
|
||||
use App\Models\User;
|
||||
use App\Notifications\BackupCompleted as BackupCompletedNotification;
|
||||
use Filament\Notifications\DatabaseNotification;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
function runBackupCompletedListener(Server $server, bool $scheduled = false): void
|
||||
{
|
||||
/** @var Backup $backup */
|
||||
$backup = Backup::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'backup_host_id' => BackupHost::factory()->create()->id,
|
||||
'is_scheduled' => $scheduled,
|
||||
]);
|
||||
|
||||
(new BackupCompletedListener())->handle(new BackupCompleted($backup));
|
||||
}
|
||||
|
||||
it('does not notify anyone by default', function () {
|
||||
Notification::fake();
|
||||
|
||||
[$subuser, $server] = generateTestAccount([SubuserPermission::WebsocketConnect]);
|
||||
|
||||
runBackupCompletedListener($server);
|
||||
runBackupCompletedListener($server, scheduled: true);
|
||||
|
||||
Notification::assertNotSentTo($server->user, DatabaseNotification::class);
|
||||
Notification::assertNotSentTo($server->user, BackupCompletedNotification::class);
|
||||
Notification::assertNotSentTo($subuser, DatabaseNotification::class);
|
||||
Notification::assertNotSentTo($subuser, BackupCompletedNotification::class);
|
||||
});
|
||||
|
||||
it('notifies owners who opted in to manual backups only for manual backups', function () {
|
||||
Notification::fake();
|
||||
|
||||
[$owner, $server] = generateTestAccount();
|
||||
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ManualBackupNotifications, true);
|
||||
|
||||
runBackupCompletedListener($server, scheduled: true);
|
||||
|
||||
Notification::assertNotSentTo($owner, DatabaseNotification::class);
|
||||
Notification::assertNotSentTo($owner, BackupCompletedNotification::class);
|
||||
|
||||
runBackupCompletedListener($server);
|
||||
|
||||
Notification::assertSentTo($owner, DatabaseNotification::class);
|
||||
Notification::assertSentTo($owner, BackupCompletedNotification::class);
|
||||
});
|
||||
|
||||
it('notifies owners who opted in to scheduled backups only for scheduled backups', function () {
|
||||
Notification::fake();
|
||||
|
||||
[$owner, $server] = generateTestAccount();
|
||||
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ScheduledBackupNotifications, true);
|
||||
|
||||
runBackupCompletedListener($server);
|
||||
|
||||
Notification::assertNotSentTo($owner, DatabaseNotification::class);
|
||||
Notification::assertNotSentTo($owner, BackupCompletedNotification::class);
|
||||
|
||||
runBackupCompletedListener($server, scheduled: true);
|
||||
|
||||
Notification::assertSentTo($owner, DatabaseNotification::class);
|
||||
Notification::assertSentTo($owner, BackupCompletedNotification::class);
|
||||
});
|
||||
|
||||
it('notifies subusers who opted in', function () {
|
||||
Notification::fake();
|
||||
|
||||
[$subuser, $server] = generateTestAccount([SubuserPermission::WebsocketConnect]);
|
||||
|
||||
$subuser->updateServerSetting($server, ServerUserSettingKey::ManualBackupNotifications, true);
|
||||
|
||||
runBackupCompletedListener($server);
|
||||
|
||||
Notification::assertSentTo($subuser, DatabaseNotification::class);
|
||||
Notification::assertSentTo($subuser, BackupCompletedNotification::class);
|
||||
});
|
||||
|
||||
it('does not notify users who opted back out', function () {
|
||||
Notification::fake();
|
||||
|
||||
[$owner, $server] = generateTestAccount();
|
||||
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ManualBackupNotifications, true);
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ManualBackupNotifications, false);
|
||||
|
||||
runBackupCompletedListener($server);
|
||||
|
||||
Notification::assertNotSentTo($owner, DatabaseNotification::class);
|
||||
Notification::assertNotSentTo($owner, BackupCompletedNotification::class);
|
||||
});
|
||||
|
||||
it('defaults backup notifications off for everyone', function () {
|
||||
[$owner, $server] = generateTestAccount();
|
||||
|
||||
/** @var User $subuser */
|
||||
$subuser = User::factory()->create();
|
||||
|
||||
foreach (ServerUserSettingKey::cases() as $key) {
|
||||
expect($owner->getServerSetting($server, $key))->toBeFalse()
|
||||
->and($subuser->getServerSetting($server, $key))->toBeFalse();
|
||||
}
|
||||
});
|
||||
|
||||
it('updates a single settings row per user and server', function () {
|
||||
[$owner, $server] = generateTestAccount();
|
||||
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ManualBackupNotifications, false);
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ManualBackupNotifications, true);
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ScheduledBackupNotifications, true);
|
||||
|
||||
expect(ServerUserSettings::query()->where('user_id', $owner->id)->where('server_id', $server->id)->count())->toBe(1)
|
||||
->and($owner->getServerSetting($server, ServerUserSettingKey::ManualBackupNotifications))->toBeTrue()
|
||||
->and($owner->getServerSetting($server, ServerUserSettingKey::ScheduledBackupNotifications))->toBeTrue();
|
||||
});
|
||||
|
||||
it('drops unknown keys when updating settings', function () {
|
||||
[$owner, $server] = generateTestAccount();
|
||||
|
||||
ServerUserSettings::factory()->create([
|
||||
'user_id' => $owner->id,
|
||||
'server_id' => $server->id,
|
||||
'settings' => ['unknown_key' => 'value'],
|
||||
]);
|
||||
|
||||
$owner->updateServerSetting($server, ServerUserSettingKey::ManualBackupNotifications, true);
|
||||
|
||||
$settings = ServerUserSettings::query()->where('user_id', $owner->id)->where('server_id', $server->id)->value('settings');
|
||||
|
||||
expect($settings)->toBe([ServerUserSettingKey::ManualBackupNotifications->value => true]);
|
||||
});
|
||||
Reference in New Issue
Block a user