mirror of
https://github.com/pelican-dev/panel.git
synced 2025-09-08 08:18:50 +02:00
Merge branch 'main' into filament-v4
This commit is contained in:
commit
71602cb0cc
@ -32,6 +32,6 @@ enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return str($this->value)->headline();
|
||||
return trans('server/backup.backup_status.' . strtolower($this->value));
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return str($this->value)->title();
|
||||
return trans('server/console.status.' . strtolower($this->value));
|
||||
}
|
||||
|
||||
public function isOffline(): bool
|
||||
|
@ -2,9 +2,50 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ServerResourceType
|
||||
use App\Models\Server;
|
||||
|
||||
enum ServerResourceType: string
|
||||
{
|
||||
case Unit;
|
||||
case Percentage;
|
||||
case Time;
|
||||
case Uptime = 'uptime';
|
||||
case CPU = 'cpu_absolute';
|
||||
case Memory = 'memory_bytes';
|
||||
case Disk = 'disk_bytes';
|
||||
|
||||
case CPULimit = 'cpu';
|
||||
case MemoryLimit = 'memory';
|
||||
case DiskLimit = 'disk';
|
||||
|
||||
/**
|
||||
* @return int resource amount in bytes
|
||||
*/
|
||||
public function getResourceAmount(Server $server): int
|
||||
{
|
||||
if ($this->isLimit()) {
|
||||
$resourceAmount = $server->{$this->value} ?? 0;
|
||||
|
||||
if (!$this->isPercentage()) {
|
||||
// Our limits are entered as MiB/ MB so we need to convert them to bytes
|
||||
$resourceAmount *= config('panel.use_binary_prefix') ? 1024 * 1024 : 1000 * 1000;
|
||||
}
|
||||
|
||||
return $resourceAmount;
|
||||
}
|
||||
|
||||
return $server->retrieveResources()[$this->value] ?? 0;
|
||||
}
|
||||
|
||||
public function isLimit(): bool
|
||||
{
|
||||
return $this === ServerResourceType::CPULimit || $this === ServerResourceType::MemoryLimit || $this === ServerResourceType::DiskLimit;
|
||||
}
|
||||
|
||||
public function isTime(): bool
|
||||
{
|
||||
return $this === ServerResourceType::Uptime;
|
||||
}
|
||||
|
||||
public function isPercentage(): bool
|
||||
{
|
||||
return $this === ServerResourceType::CPU || $this === ServerResourceType::CPULimit;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case Normal = 'normal';
|
||||
case Installing = 'installing';
|
||||
case InstallFailed = 'install_failed';
|
||||
case ReinstallFailed = 'reinstall_failed';
|
||||
@ -18,7 +17,6 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
public function getIcon(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Normal => 'tabler-heart',
|
||||
self::Installing => 'tabler-heart-bolt',
|
||||
self::InstallFailed => 'tabler-heart-x',
|
||||
self::ReinstallFailed => 'tabler-heart-x',
|
||||
@ -31,14 +29,13 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
if ($hex) {
|
||||
return match ($this) {
|
||||
self::Normal, self::Installing, self::RestoringBackup => '#2563EB',
|
||||
self::Installing, self::RestoringBackup => '#2563EB',
|
||||
self::Suspended => '#D97706',
|
||||
self::InstallFailed, self::ReinstallFailed => '#EF4444',
|
||||
};
|
||||
}
|
||||
|
||||
return match ($this) {
|
||||
self::Normal => 'primary',
|
||||
self::Installing => 'primary',
|
||||
self::InstallFailed => 'danger',
|
||||
self::ReinstallFailed => 'danger',
|
||||
|
@ -211,7 +211,7 @@ class CreateEgg extends CreateRecord
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value'))->maxLength(255),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
|
@ -200,7 +200,7 @@ class EditEgg extends EditRecord
|
||||
'*' => trans('admin/egg.error_reserved'),
|
||||
])
|
||||
->required(),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value'))->maxLength(255),
|
||||
TextInput::make('default_value')->label(trans('admin/egg.default_value')),
|
||||
Fieldset::make(trans('admin/egg.user_permissions'))
|
||||
->schema([
|
||||
Checkbox::make('user_viewable')->label(trans('admin/egg.viewable')),
|
||||
|
@ -9,6 +9,7 @@ use App\Filament\Admin\Resources\WebhookResource\Pages\ViewWebhookConfiguration;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
@ -132,23 +133,12 @@ class WebhookResource extends Resource
|
||||
->live()
|
||||
->inline()
|
||||
->options(WebhookType::class)
|
||||
->default(WebhookType::Regular)
|
||||
->afterStateHydrated(function (WebhookType $state) {
|
||||
if ($state === WebhookType::Discord) {
|
||||
self::sendHelpBanner();
|
||||
}
|
||||
})
|
||||
->afterStateUpdated(function (WebhookType $state) {
|
||||
if ($state === WebhookType::Discord) {
|
||||
self::sendHelpBanner();
|
||||
}
|
||||
}),
|
||||
->default(WebhookType::Regular->value),
|
||||
TextInput::make('description')
|
||||
->label(trans('admin/webhook.description'))
|
||||
->required(),
|
||||
TextInput::make('endpoint')
|
||||
->label(trans('admin/webhook.endpoint'))
|
||||
->activeUrl()
|
||||
->required()
|
||||
->columnSpanFull()
|
||||
->afterStateUpdated(fn (string $state, Set $set) => $set('type', str($state)->contains('discord.com') ? WebhookType::Discord : WebhookType::Regular)),
|
||||
@ -156,8 +146,16 @@ class WebhookResource extends Resource
|
||||
->hidden(fn (Get $get) => $get('type') === WebhookType::Discord)
|
||||
->dehydratedWhenHidden()
|
||||
->schema(fn () => self::getRegularFields())
|
||||
->formBefore()
|
||||
->columnSpanFull(),
|
||||
->headerActions([
|
||||
Action::make('reset_headers')
|
||||
->label(trans('admin/webhook.reset_headers'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-trash')
|
||||
->action(fn (Get $get, Set $set) => $set('headers', [
|
||||
'X-Webhook-Event' => '{{event}}',
|
||||
])),
|
||||
])
|
||||
->formBefore(),
|
||||
Section::make(trans('admin/webhook.discord'))
|
||||
->hidden(fn (Get $get) => $get('type') === WebhookType::Regular)
|
||||
->dehydratedWhenHidden()
|
||||
@ -168,9 +166,6 @@ class WebhookResource extends Resource
|
||||
->formBefore()
|
||||
->columnSpanFull(),
|
||||
Section::make(trans('admin/webhook.events'))
|
||||
->collapsible()
|
||||
->collapsed(fn (Get $get) => count($get('events') ?? []))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
CheckboxList::make('events')
|
||||
->live()
|
||||
@ -191,7 +186,10 @@ class WebhookResource extends Resource
|
||||
{
|
||||
return [
|
||||
KeyValue::make('headers')
|
||||
->label(trans('admin/webhook.headers')),
|
||||
->label(trans('admin/webhook.headers'))
|
||||
->default(fn () => [
|
||||
'X-Webhook-Event' => '{{event}}',
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -63,4 +63,15 @@ class CreateWebhookConfiguration extends CreateRecord
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return EditWebhookConfiguration::getUrl(['record' => $this->getRecord()]);
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
parent::mount();
|
||||
WebhookResource::sendHelpBanner();
|
||||
}
|
||||
}
|
||||
|
@ -123,4 +123,10 @@ class EditWebhookConfiguration extends EditRecord
|
||||
{
|
||||
$this->dispatch('refresh-widget');
|
||||
}
|
||||
|
||||
public function mount(int|string $record): void
|
||||
{
|
||||
parent::mount($record);
|
||||
WebhookResource::sendHelpBanner();
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class ListServers extends ListRecords
|
||||
TextColumn::make('condition')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->tooltip(fn (Server $server) => $server->formatResource('uptime', type: ServerResourceType::Time))
|
||||
->tooltip(fn (Server $server) => $server->formatResource(ServerResourceType::Uptime))
|
||||
->icon(fn (Server $server) => $server->condition->getIcon())
|
||||
->color(fn (Server $server) => $server->condition->getColor()),
|
||||
TextColumn::make('name')
|
||||
@ -79,22 +79,22 @@ class ListServers extends ListRecords
|
||||
->copyable(request()->isSecure())
|
||||
->state(fn (Server $server) => $server->allocation->address ?? 'None'),
|
||||
TextColumn::make('cpuUsage')
|
||||
->label('Resources')
|
||||
->label(trans('server/dashboard.resources'))
|
||||
->icon('tabler-cpu')
|
||||
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('cpu', limit: true, type: ServerResourceType::Percentage, precision: 0))
|
||||
->state(fn (Server $server) => $server->formatResource('cpu_absolute', type: ServerResourceType::Percentage))
|
||||
->tooltip(fn (Server $server) => trans('server/dashboard.usage_limit', ['resource' => $server->formatResource(ServerResourceType::CPULimit)]))
|
||||
->state(fn (Server $server) => $server->formatResource(ServerResourceType::CPU))
|
||||
->color(fn (Server $server) => $this->getResourceColor($server, 'cpu')),
|
||||
TextColumn::make('memoryUsage')
|
||||
->label('')
|
||||
->icon('tabler-device-desktop-analytics')
|
||||
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('memory', limit: true))
|
||||
->state(fn (Server $server) => $server->formatResource('memory_bytes'))
|
||||
->tooltip(fn (Server $server) => trans('server/dashboard.usage_limit', ['resource' => $server->formatResource(ServerResourceType::MemoryLimit)]))
|
||||
->state(fn (Server $server) => $server->formatResource(ServerResourceType::Memory))
|
||||
->color(fn (Server $server) => $this->getResourceColor($server, 'memory')),
|
||||
TextColumn::make('diskUsage')
|
||||
->label('')
|
||||
->icon('tabler-device-sd-card')
|
||||
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('disk', limit: true))
|
||||
->state(fn (Server $server) => $server->formatResource('disk_bytes'))
|
||||
->tooltip(fn (Server $server) => trans('server/dashboard.usage_limit', ['resource' => $server->formatResource(ServerResourceType::DiskLimit)]))
|
||||
->state(fn (Server $server) => $server->formatResource(ServerResourceType::Disk))
|
||||
->color(fn (Server $server) => $this->getResourceColor($server, 'disk')),
|
||||
];
|
||||
}
|
||||
|
@ -15,6 +15,11 @@ class PreviewStartupAction extends Action
|
||||
return 'preview';
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('server/startup.preview');
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
@ -163,6 +163,7 @@ class Console extends Page
|
||||
|
||||
return [
|
||||
Action::make('start')
|
||||
->label(trans('server/console.power_actions.start'))
|
||||
->color('primary')
|
||||
->size(Size::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'start', uuid: $server->uuid))
|
||||
@ -170,6 +171,7 @@ class Console extends Page
|
||||
->disabled(fn () => $server->isInConflictState() || !$this->status->isStartable())
|
||||
->icon('tabler-player-play-filled'),
|
||||
Action::make('restart')
|
||||
->label(trans('server/console.power_actions.restart'))
|
||||
->color('gray')
|
||||
->size(Size::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'restart', uuid: $server->uuid))
|
||||
@ -177,6 +179,7 @@ class Console extends Page
|
||||
->disabled(fn () => $server->isInConflictState() || !$this->status->isRestartable())
|
||||
->icon('tabler-reload'),
|
||||
Action::make('stop')
|
||||
->label(trans('server/console.power_actions.stop'))
|
||||
->color('danger')
|
||||
->size(Size::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'stop', uuid: $server->uuid))
|
||||
@ -185,11 +188,11 @@ class Console extends Page
|
||||
->disabled(fn () => $server->isInConflictState() || !$this->status->isStoppable())
|
||||
->icon('tabler-player-stop-filled'),
|
||||
Action::make('kill')
|
||||
->label(trans('server/console.power_actions.kill'))
|
||||
->color('danger')
|
||||
->tooltip(trans('server/console.power_actions.kill_tooltip'))
|
||||
->size(ActionSize::ExtraLarge)
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Do you wish to kill this server?')
|
||||
->modalDescription('This can result in data corruption and/or data loss!')
|
||||
->modalSubmitActionLabel('Kill Server')
|
||||
->size(Size::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'kill', uuid: $server->uuid))
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_CONTROL_STOP, $server))
|
||||
@ -197,4 +200,14 @@ class Console extends Page
|
||||
->icon('tabler-alert-square'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/console.title');
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/console.title');
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,7 @@ class Settings extends ServerFormPage
|
||||
'lg' => 6,
|
||||
])
|
||||
->components([
|
||||
Section::make('Server Information')
|
||||
->columnSpanFull()
|
||||
Section::make(trans('server/setting.server_info.title'))
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
@ -46,12 +45,12 @@ class Settings extends ServerFormPage
|
||||
'lg' => 6,
|
||||
])
|
||||
->schema([
|
||||
Fieldset::make('Server')
|
||||
->label('Information')
|
||||
Fieldset::make()
|
||||
->label(trans('server/setting.server_info.information'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Server Name')
|
||||
->label(trans('server/setting.server_info.name'))
|
||||
->disabled(fn (Server $server) => !auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
|
||||
->required()
|
||||
->columnSpan([
|
||||
@ -63,7 +62,7 @@ class Settings extends ServerFormPage
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, Server $server) => $this->updateName($state, $server)),
|
||||
Textarea::make('description')
|
||||
->label('Server Description')
|
||||
->label(trans('server/setting.server_info.description'))
|
||||
->hidden(!config('panel.editable_server_descriptions'))
|
||||
->disabled(fn (Server $server) => !auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
|
||||
->columnSpan([
|
||||
@ -76,7 +75,7 @@ class Settings extends ServerFormPage
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(fn ($state, Server $server) => $this->updateDescription($state ?? '', $server)),
|
||||
TextInput::make('uuid')
|
||||
->label('Server UUID')
|
||||
->label(trans('server/setting.server_info.uuid'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
@ -85,12 +84,12 @@ class Settings extends ServerFormPage
|
||||
])
|
||||
->disabled(),
|
||||
TextInput::make('id')
|
||||
->label('Server ID')
|
||||
->label(trans('server/setting.server_info.id'))
|
||||
->disabled()
|
||||
->columnSpan(1),
|
||||
]),
|
||||
Fieldset::make('Limits')
|
||||
->label('Limits')
|
||||
Fieldset::make()
|
||||
->label(trans('server/setting.server_info.limits.title'))
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 1,
|
||||
@ -101,59 +100,57 @@ class Settings extends ServerFormPage
|
||||
->schema([
|
||||
TextInput::make('cpu')
|
||||
->label('')
|
||||
->prefix('CPU')
|
||||
->prefix(trans('server/setting.server_info.limits.cpu'))
|
||||
->prefixIcon('tabler-cpu')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'Unlimited' : Number::format($server->cpu, locale: auth()->user()->language) . '%'),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? trans('server/setting.server_info.limits.unlimited') : Number::format($server->cpu, locale: auth()->user()->language) . '%'),
|
||||
TextInput::make('memory')
|
||||
->label('')
|
||||
->prefix('Memory')
|
||||
->prefix(trans('server/setting.server_info.limits.memory'))
|
||||
->prefixIcon('tabler-device-desktop-analytics')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'Unlimited' : convert_bytes_to_readable($server->memory * 2 ** 20)),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? trans('server/setting.server_info.limits.unlimited') : convert_bytes_to_readable($server->memory * 2 ** 20)),
|
||||
TextInput::make('disk')
|
||||
->label('')
|
||||
->prefix('Disk Space')
|
||||
->prefix(trans('server/setting.server_info.limits.disk'))
|
||||
->prefixIcon('tabler-device-sd-card')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'Unlimited' : convert_bytes_to_readable($server->disk * 2 ** 20)),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? trans('server/setting.server_info.limits.unlimited') : convert_bytes_to_readable($server->disk * 2 ** 20)),
|
||||
TextInput::make('backup_limit')
|
||||
->label('')
|
||||
->prefix('Backups')
|
||||
->prefix(trans('server/setting.server_info.limits.backups'))
|
||||
->prefixIcon('tabler-file-zip')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Backups' : $server->backups->count() . ' of ' . $state),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Backups' : $server->backups->count() . ' ' .trans('server/setting.server_info.limits.of') . ' ' . $state),
|
||||
TextInput::make('database_limit')
|
||||
->label('')
|
||||
->prefix('Databases')
|
||||
->prefix(trans('server/setting.server_info.limits.databases'))
|
||||
->prefixIcon('tabler-database')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Databases' : $server->databases->count() . ' of ' . $state),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Databases' : $server->databases->count() . ' ' . trans('server/setting.server_info.limits.of') . ' ' .$state),
|
||||
TextInput::make('allocation_limit')
|
||||
->label('')
|
||||
->prefix('Allocations')
|
||||
->prefix(trans('server/setting.server_info.limits.allocations'))
|
||||
->prefixIcon('tabler-network')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Additional Allocations' : $server->allocations->count() . ' of ' . $state),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? trans('server/setting.server_info.limits.no_allocations') : $server->allocations->count() . ' ' .trans('server/setting.server_info.limits.of') . ' ' . $state),
|
||||
]),
|
||||
]),
|
||||
Section::make('Node Information')
|
||||
->columnSpanFull()
|
||||
Section::make(trans('server/setting.node_info.title'))
|
||||
->schema([
|
||||
TextInput::make('node.name')
|
||||
->label('Node Name')
|
||||
->label(trans('server/setting.node_info.name'))
|
||||
->formatStateUsing(fn (Server $server) => $server->node->name)
|
||||
->disabled(),
|
||||
Fieldset::make('SFTP Information')
|
||||
Fieldset::make(trans('server/setting.node_info.sftp.title'))
|
||||
->columnSpanFull()
|
||||
->hidden(fn (Server $server) => !auth()->user()->can(Permission::ACTION_FILE_SFTP, $server))
|
||||
->label('SFTP Information')
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
@ -162,13 +159,13 @@ class Settings extends ServerFormPage
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('connection')
|
||||
->label('Connection')
|
||||
->label(trans('server/setting.node_info.sftp.connection'))
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->suffixCopy()
|
||||
->hintAction(
|
||||
Action::make('connect_sftp')
|
||||
->label('Connect to SFTP')
|
||||
->label(trans('server/setting.node_info.sftp.action'))
|
||||
->color('success')
|
||||
->icon('tabler-plug')
|
||||
->url(function (Server $server) {
|
||||
@ -183,29 +180,30 @@ class Settings extends ServerFormPage
|
||||
return 'sftp://' . auth()->user()->username . '.' . $server->uuid_short . '@' . $fqdn . ':' . $server->node->daemon_sftp;
|
||||
}),
|
||||
TextInput::make('username')
|
||||
->label('Username')
|
||||
->label(trans('server/setting.node_info.sftp.username'))
|
||||
->columnSpan(1)
|
||||
->suffixCopy()
|
||||
->disabled()
|
||||
->formatStateUsing(fn (Server $server) => auth()->user()->username . '.' . $server->uuid_short),
|
||||
TextEntry::make('password')
|
||||
->label(trans('server/setting.node_info.sftp.password'))
|
||||
->columnSpan(1)
|
||||
->state('Your SFTP password is the same as the password you use to access this panel.'),
|
||||
->content(trans('server/setting.node_info.sftp.password_body')),
|
||||
]),
|
||||
]),
|
||||
Section::make('Reinstall Server')
|
||||
->columnSpanFull()
|
||||
Section::make(trans('server/setting.reinstall.title'))
|
||||
->hidden(fn (Server $server) => !auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server))
|
||||
->columnSpanFull()
|
||||
->collapsible()
|
||||
->footerActions([
|
||||
Action::make('reinstall')
|
||||
->label(trans('server/setting.reinstall.action'))
|
||||
->color('danger')
|
||||
->disabled(fn (Server $server) => !auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server))
|
||||
->label('Reinstall')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Are you sure you want to reinstall the server?')
|
||||
->modalDescription('Some files may be deleted or modified during this process, please back up your data before continuing.')
|
||||
->modalSubmitActionLabel('Yes, Reinstall')
|
||||
->modalHeading(trans('server/setting.reinstall.modal'))
|
||||
->modalDescription(trans('server/setting.reinstall.modal_description'))
|
||||
->modalSubmitActionLabel(trans('server/setting.reinstall.yes'))
|
||||
->action(function (Server $server, ReinstallServerService $reinstallService) {
|
||||
abort_unless(auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server), 403);
|
||||
|
||||
@ -215,9 +213,9 @@ class Settings extends ServerFormPage
|
||||
report($exception);
|
||||
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Server Reinstall failed')
|
||||
->title(trans('server/setting.reinstall.notification_fail'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
@ -227,8 +225,8 @@ class Settings extends ServerFormPage
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/setting.reinstall.notification_start'))
|
||||
->success()
|
||||
->title('Server Reinstall started')
|
||||
->send();
|
||||
|
||||
redirect(Console::getUrl());
|
||||
@ -237,9 +235,9 @@ class Settings extends ServerFormPage
|
||||
->footerActionsAlignment(Alignment::Right)
|
||||
->schema([
|
||||
TextEntry::make('stop_info')
|
||||
->label('Reinstalling your server will stop it, and then re-run the installation script that initially set it up.'),
|
||||
->label(trans('server/setting.reinstall.body')),
|
||||
TextEntry::make('files_info')
|
||||
->label('Some files may be deleted or modified during this process, please back up your data before continuing.'),
|
||||
->label(trans('server/setting.reinstall.body2')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@ -262,15 +260,15 @@ class Settings extends ServerFormPage
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Updated Server Name')
|
||||
->title(trans('server/setting.notification_name'))
|
||||
->body(fn () => $original . ' -> ' . $name)
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Failed')
|
||||
->title(trans('server/setting.failed'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
@ -293,16 +291,26 @@ class Settings extends ServerFormPage
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Updated Server Description')
|
||||
->title(trans('server/setting.notification_description'))
|
||||
->body(fn () => $original . ' -> ' . $description)
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Failed')
|
||||
->title(trans('server/setting.failed'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/setting.title');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/setting.title');
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class Startup extends ServerFormPage
|
||||
Hidden::make('previewing')
|
||||
->default(false),
|
||||
Textarea::make('startup')
|
||||
->label('Startup Command')
|
||||
->label(trans('server/startup.command'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
@ -52,10 +52,10 @@ class Startup extends ServerFormPage
|
||||
'lg' => 4,
|
||||
])
|
||||
->autosize()
|
||||
->hintAction(PreviewStartupAction::make('preview'))
|
||||
->hintAction(PreviewStartupAction::make())
|
||||
->readOnly(),
|
||||
TextInput::make('custom_image')
|
||||
->label('Docker Image')
|
||||
->label(trans('server/startup.docker_image'))
|
||||
->readOnly()
|
||||
->visible(fn (Server $server) => !in_array($server->image, $server->egg->docker_images))
|
||||
->formatStateUsing(fn (Server $server) => $server->image)
|
||||
@ -66,7 +66,7 @@ class Startup extends ServerFormPage
|
||||
'lg' => 2,
|
||||
]),
|
||||
Select::make('image')
|
||||
->label('Docker Image')
|
||||
->label(trans('server/startup.docker_image'))
|
||||
->live()
|
||||
->visible(fn (Server $server) => in_array($server->image, $server->egg->docker_images))
|
||||
->disabled(fn (Server $server) => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
|
||||
@ -81,8 +81,8 @@ class Startup extends ServerFormPage
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Docker image updated')
|
||||
->body('Restart the server to use the new image.')
|
||||
->title(trans('server/startup.notification_docker'))
|
||||
->body(trans('server/startup.notification_docker_body'))
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
@ -98,11 +98,11 @@ class Startup extends ServerFormPage
|
||||
'md' => 2,
|
||||
'lg' => 2,
|
||||
]),
|
||||
Section::make('Server Variables')
|
||||
Section::make(trans('server/startup.variables'))
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Repeater::make('server_variables')
|
||||
->label('')
|
||||
->hiddenLabel()
|
||||
->relationship('serverVariables', fn (Builder $query) => $query->where('egg_variables.user_viewable', true)->orderByPowerJoins('variable.sort'))
|
||||
->grid()
|
||||
->disabled(fn (Server $server) => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||
@ -209,9 +209,9 @@ class Startup extends ServerFormPage
|
||||
|
||||
if ($validator->fails()) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Validation Failed: ' . $serverVariable->variable->name)
|
||||
->title(trans('server/startup.validation_fail', ['variable' => $serverVariable->variable->name]))
|
||||
->body(implode(', ', $validator->errors()->all()))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return null;
|
||||
@ -234,18 +234,28 @@ class Startup extends ServerFormPage
|
||||
->log();
|
||||
}
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('Updated: ' . $serverVariable->variable->name)
|
||||
->title(trans('server/startup.update', ['variable' => $serverVariable->variable->name]))
|
||||
->body(fn () => $original . ' -> ' . $state)
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Failed: ' . $serverVariable->variable->name)
|
||||
->title(trans('server/startup.fail', ['variable' => $serverVariable->variable->name]))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return trans('server/startup.title');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/startup.title');
|
||||
}
|
||||
}
|
||||
|
@ -39,10 +39,6 @@ class ActivityResource extends Resource
|
||||
|
||||
protected static ?string $model = ActivityLog::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Activity';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Activity';
|
||||
|
||||
protected static ?int $navigationSort = 8;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-stack';
|
||||
@ -62,14 +58,16 @@ class ActivityResource extends Resource
|
||||
->defaultPaginationPageOption(25)
|
||||
->columns([
|
||||
TextColumn::make('event')
|
||||
->label(trans('server/activity.event'))
|
||||
->html()
|
||||
->description(fn ($state) => $state)
|
||||
->icon(fn (ActivityLog $activityLog) => $activityLog->getIcon())
|
||||
->formatStateUsing(fn (ActivityLog $activityLog) => $activityLog->getLabel()),
|
||||
TextColumn::make('user')
|
||||
->label(trans('server/activity.user'))
|
||||
->state(function (ActivityLog $activityLog) use ($server) {
|
||||
if (!$activityLog->actor instanceof User) {
|
||||
return $activityLog->actor_id === null ? 'System' : 'Deleted user';
|
||||
return $activityLog->actor_id === null ? trans('server/activity.system') : trans('server/activity.deleted_user');
|
||||
}
|
||||
|
||||
$user = $activityLog->actor->username;
|
||||
@ -85,6 +83,7 @@ class ActivityResource extends Resource
|
||||
->url(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update', $activityLog->actor) ? EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin') : '')
|
||||
->grow(false),
|
||||
DateTimeColumn::make('timestamp')
|
||||
->label(trans('server/activity.timestamp'))
|
||||
->since()
|
||||
->sortable()
|
||||
->grow(false),
|
||||
@ -95,11 +94,13 @@ class ActivityResource extends Resource
|
||||
//->visible(fn (ActivityLog $activityLog) => $activityLog->hasAdditionalMetadata())
|
||||
->schema([
|
||||
TextEntry::make('event')
|
||||
->label(trans('server/activity.event'))
|
||||
->state(fn (ActivityLog $activityLog) => new HtmlString($activityLog->getLabel())),
|
||||
TextInput::make('user')
|
||||
->label(trans('server/activity.user'))
|
||||
->formatStateUsing(function (ActivityLog $activityLog) use ($server) {
|
||||
if (!$activityLog->actor instanceof User) {
|
||||
return $activityLog->actor_id === null ? 'System' : 'Deleted user';
|
||||
return $activityLog->actor_id === null ? trans('server/activity.system') : trans('server/activity.deleted_user');
|
||||
}
|
||||
|
||||
$user = $activityLog->actor->username;
|
||||
@ -122,9 +123,10 @@ class ActivityResource extends Resource
|
||||
->visible(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update', $activityLog->actor))
|
||||
->url(fn (ActivityLog $activityLog) => EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin'))
|
||||
),
|
||||
DateTimePicker::make('timestamp'),
|
||||
DateTimePicker::make('timestamp')
|
||||
->label(trans('server/activity.timestamp')),
|
||||
KeyValue::make('properties')
|
||||
->label('Metadata')
|
||||
->label(trans('server/activity.metadata'))
|
||||
->formatStateUsing(fn ($state) => Arr::dot($state)),
|
||||
]),
|
||||
])
|
||||
@ -174,4 +176,9 @@ class ActivityResource extends Resource
|
||||
'index' => ListActivities::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/activity.title');
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,9 @@ class ListActivities extends ListRecords
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/activity.title');
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,6 @@ class AllocationResource extends Resource
|
||||
|
||||
protected static ?string $model = Allocation::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Network';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Network';
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-network';
|
||||
@ -50,16 +46,17 @@ class AllocationResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('ip')
|
||||
->label('Address')
|
||||
->label(trans('server/network.address'))
|
||||
->formatStateUsing(fn (Allocation $allocation) => $allocation->alias),
|
||||
TextColumn::make('alias')
|
||||
->hidden(),
|
||||
TextColumn::make('port'),
|
||||
TextColumn::make('port')
|
||||
->label(trans('server/network.port')),
|
||||
TextInputColumn::make('notes')
|
||||
->label(trans('server/network.notes'))
|
||||
->visibleFrom('sm')
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_ALLOCATION_UPDATE, $server))
|
||||
->label('Notes')
|
||||
->placeholder('No Notes'),
|
||||
->placeholder(trans('server/network.no_notes')),
|
||||
IconColumn::make('primary')
|
||||
->icon(fn ($state) => match ($state) {
|
||||
true => 'tabler-star-filled',
|
||||
@ -69,15 +66,15 @@ class AllocationResource extends Resource
|
||||
true => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->tooltip(fn (Allocation $allocation) => ($allocation->id === $server->allocation_id ? 'Already' : 'Make') . ' Primary')
|
||||
->tooltip(fn (Allocation $allocation) => $allocation->id === $server->allocation_id ? trans('server/network.primary') : trans('server/network.make_primary'))
|
||||
->action(fn (Allocation $allocation) => auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server) && $server->update(['allocation_id' => $allocation->id]))
|
||||
->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id)
|
||||
->label('Primary'),
|
||||
->label(trans('server/network.primary')),
|
||||
])
|
||||
->recordActions([
|
||||
DetachAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server))
|
||||
->label('Delete')
|
||||
->label(trans('server/network.delete'))
|
||||
->icon('tabler-trash')
|
||||
->action(function (Allocation $allocation) {
|
||||
Allocation::query()->where('id', $allocation->id)->update([
|
||||
@ -121,4 +118,9 @@ class AllocationResource extends Resource
|
||||
'index' => ListAllocations::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/network.title');
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class ListAllocations extends ListRecords
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon(fn () => $server->allocations()->count() >= $server->allocation_limit ? 'tabler-network-off' : 'tabler-network')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_CREATE, $server))
|
||||
->tooltip(fn () => $server->allocations()->count() >= $server->allocation_limit ? 'Allocation limit reached' : 'Add Allocation')
|
||||
->tooltip(fn () => $server->allocations()->count() >= $server->allocation_limit ? trans('server/network.limit') : trans('server/network.add'))
|
||||
->hidden(fn () => !config('panel.client_features.allocations.enabled'))
|
||||
->disabled(fn () => $server->allocations()->count() >= $server->allocation_limit)
|
||||
->color(fn () => $server->allocations()->count() >= $server->allocation_limit ? 'danger' : 'primary')
|
||||
@ -56,4 +56,14 @@ class ListAllocations extends ListRecords
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/network.title');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/network.title');
|
||||
}
|
||||
}
|
||||
|
@ -84,14 +84,15 @@ class BackupResource extends Resource
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->label(trans('server/backup.actions.create.name'))
|
||||
->columnSpanFull(),
|
||||
TextArea::make('ignored')
|
||||
->columnSpanFull()
|
||||
->label('Ignored Files & Directories'),
|
||||
->label(trans('server/backup.actions.create.ignored'))
|
||||
->columnSpanFull(),
|
||||
Toggle::make('is_locked')
|
||||
->label('Lock?')
|
||||
->helperText('Prevents this backup from being deleted until explicitly unlocked.'),
|
||||
->label(trans('server/backup.actions.create.locked'))
|
||||
->helperText(trans('server/backup.actions.create.lock_helper'))
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -107,32 +108,65 @@ class BackupResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('server/backup.actions.create.name'))
|
||||
->searchable(),
|
||||
BytesColumn::make('bytes')
|
||||
->label('Size'),
|
||||
->label(trans('server/backup.size')),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label('Created')
|
||||
->label(trans('server/backup.created_at'))
|
||||
->since()
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->label(trans('server/backup.status'))
|
||||
->badge(),
|
||||
IconColumn::make('is_locked')
|
||||
->label(trans('server/backup.is_locked'))
|
||||
->visibleFrom('md')
|
||||
->label('Lock Status')
|
||||
->trueIcon('tabler-lock')
|
||||
->falseIcon('tabler-lock-open'),
|
||||
])
|
||||
->recordActions([
|
||||
ActionGroup::make([
|
||||
Action::make('rename')
|
||||
->icon('tabler-pencil')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server))
|
||||
->label('Rename')
|
||||
->form([
|
||||
TextInput::make('name')
|
||||
->label('Backup Name')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default(fn (Backup $backup) => $backup->name),
|
||||
])
|
||||
->action(function (Backup $backup, $data) {
|
||||
$oldName = $backup->name;
|
||||
$newName = $data['name'];
|
||||
|
||||
$backup->update(['name' => $newName]);
|
||||
|
||||
if ($oldName !== $newName) {
|
||||
Activity::event('server:backup.rename')
|
||||
->subject($backup)
|
||||
->property(['old_name' => $oldName, 'new_name' => $newName])
|
||||
->log();
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Backup Renamed')
|
||||
->body('The backup has been successfully renamed.')
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
Action::make('lock')
|
||||
->iconSize(IconSize::Large)
|
||||
->icon(fn (Backup $backup) => !$backup->is_locked ? 'tabler-lock' : 'tabler-lock-open')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server))
|
||||
->label(fn (Backup $backup) => !$backup->is_locked ? 'Lock' : 'Unlock')
|
||||
->label(fn (Backup $backup) => !$backup->is_locked ? trans('server/backup.actions.lock.lock') : trans('server/backup.actions.lock.unlock'))
|
||||
->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->toggleLock($request, $server, $backup))
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
Action::make('download')
|
||||
->label(trans('server/backup.actions.download'))
|
||||
->iconSize(IconSize::Large)
|
||||
->color('primary')
|
||||
->icon('tabler-download')
|
||||
@ -140,6 +174,7 @@ class BackupResource extends Resource
|
||||
->url(fn (DownloadLinkService $downloadLinkService, Backup $backup, Request $request) => $downloadLinkService->handle($backup, $request->user()), true)
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
Action::make('restore')
|
||||
->label(trans('server/backup.actions.restore.title'))
|
||||
->iconSize(IconSize::Large)
|
||||
->color('success')
|
||||
->icon('tabler-folder-up')
|
||||
@ -147,24 +182,24 @@ class BackupResource extends Resource
|
||||
->schema([
|
||||
TextEntry::make('stop_info')
|
||||
->hiddenLabel()
|
||||
->helperText('Your server will be stopped. You will not be able to control the power state, access the file manager, or create additional backups until this process is completed.'),
|
||||
->helperText(trans('server/backup.actions.restore.helper')),
|
||||
Checkbox::make('truncate')
|
||||
->label('Delete all files before restoring backup?'),
|
||||
->label(trans('server/backup.actions.restore.delete_all')),
|
||||
])
|
||||
->action(function (Backup $backup, $data, DaemonBackupRepository $daemonRepository, DownloadLinkService $downloadLinkService) use ($server) {
|
||||
if (!is_null($server->status)) {
|
||||
return Notification::make()
|
||||
->title(trans('server/backup.actions.restore.notification_fail'))
|
||||
->body(trans('server/backup.actions.restore.notification_fail_body_1'))
|
||||
->danger()
|
||||
->title('Backup Restore Failed')
|
||||
->body('This server is not currently in a state that allows for a backup to be restored.')
|
||||
->send();
|
||||
}
|
||||
|
||||
if (!$backup->is_successful && is_null($backup->completed_at)) {
|
||||
return Notification::make()
|
||||
->title(trans('server/backup.actions.restore.notification_fail'))
|
||||
->body(trans('server/backup.actions.restore.notification_fail_body_2'))
|
||||
->danger()
|
||||
->title('Backup Restore Failed')
|
||||
->body('This backup cannot be restored at this time: not completed or failed.')
|
||||
->send();
|
||||
}
|
||||
|
||||
@ -187,22 +222,27 @@ class BackupResource extends Resource
|
||||
});
|
||||
|
||||
return Notification::make()
|
||||
->title('Restoring Backup')
|
||||
->title(trans('server/backup.actions.restore.notification_started'))
|
||||
->send();
|
||||
})
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
DeleteAction::make('delete')
|
||||
->iconSize(IconSize::Large)
|
||||
->disabled(fn (Backup $backup) => $backup->is_locked)
|
||||
->modalDescription(fn (Backup $backup) => 'Do you wish to delete ' . $backup->name . '?')
|
||||
->modalSubmitActionLabel('Delete Backup')
|
||||
->modalDescription(fn (Backup $backup) => trans('server/backup.actions.delete.description', ['backup' => $backup->name]))
|
||||
->modalSubmitActionLabel(trans('server/backup.actions.delete.title'))
|
||||
->action(function (Backup $backup, DeleteBackupService $deleteBackupService) {
|
||||
try {
|
||||
$deleteBackupService->handle($backup);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/backup.actions.delete.notification_success'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (ConnectionException) {
|
||||
Notification::make()
|
||||
->title('Could not delete backup')
|
||||
->body('Connection to node failed')
|
||||
->title(trans('server/backup.actions.delete.notification_fail'))
|
||||
->body(trans('server/backup.actions.delete.notification_fail_body'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@ -279,4 +319,9 @@ class BackupResource extends Resource
|
||||
'index' => ListBackups::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/backup.title');
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,55 @@ class ListBackups extends ListRecords
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return [
|
||||
CreateAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_CREATE, $server))
|
||||
->icon('tabler-file-zip')->iconButton()->iconSize(IconSize::Large)
|
||||
->disabled(fn () => $server->backups()->count() >= $server->backup_limit)
|
||||
->tooltip(fn () => $server->backups()->count() >= $server->backup_limit ? trans('server/backup.actions.create.limit') : trans('server/backup.actions.create.title'))
|
||||
->color(fn () => $server->backups()->count() >= $server->backup_limit ? 'danger' : 'primary')
|
||||
->createAnother(false)
|
||||
->action(function (InitiateBackupService $initiateBackupService, $data) use ($server) {
|
||||
$action = $initiateBackupService->setIgnoredFiles(explode(PHP_EOL, $data['ignored'] ?? ''));
|
||||
|
||||
if (auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {
|
||||
$action->setIsLocked((bool) $data['is_locked']);
|
||||
}
|
||||
|
||||
try {
|
||||
$backup = $action->handle($server, $data['name']);
|
||||
|
||||
Activity::event('server:backup.start')
|
||||
->subject($backup)
|
||||
->property(['name' => $backup->name, 'locked' => (bool) $data['is_locked']])
|
||||
->log();
|
||||
|
||||
return Notification::make()
|
||||
->title(trans('server/backup.actions.create.notification_success'))
|
||||
->body(trans('server/backup.actions.create.created', ['name' => $backup->name]))
|
||||
->success()
|
||||
->send();
|
||||
} catch (HttpException $e) {
|
||||
return Notification::make()
|
||||
->title(trans('server/backup.actions.create.notification_fail'))
|
||||
->body($e->getMessage() . ' Try again' . ($e->getHeaders()['Retry-After'] ? ' in ' . $e->getHeaders()['Retry-After'] . ' seconds.' : ''))
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/backup.title');
|
||||
}
|
||||
}
|
||||
|
@ -69,13 +69,17 @@ class DatabaseResource extends Resource
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('host')
|
||||
->label(trans('server/database.host'))
|
||||
->formatStateUsing(fn (Database $database) => $database->address())
|
||||
->suffixCopy(),
|
||||
TextInput::make('database')
|
||||
->suffixCopy(),
|
||||
->label(trans('server/database.database'))
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('username')
|
||||
->suffixCopy(),
|
||||
->label(trans('server/database.username'))
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('password')
|
||||
->label(trans('server/database.password'))
|
||||
->password()->revealable()
|
||||
->hidden(fn () => !auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
->hintAction(
|
||||
@ -85,11 +89,12 @@ class DatabaseResource extends Resource
|
||||
->suffixCopy()
|
||||
->formatStateUsing(fn (Database $database) => $database->password),
|
||||
TextInput::make('remote')
|
||||
->label('Connections From'),
|
||||
->label(trans('server/database.remote')),
|
||||
TextInput::make('max_connections')
|
||||
->label(trans('server/database.max_connections'))
|
||||
->formatStateUsing(fn (Database $database) => $database->max_connections === 0 ? $database->max_connections : 'Unlimited'),
|
||||
TextInput::make('jdbc')
|
||||
->label('JDBC Connection String')
|
||||
->label(trans('server/database.jdbc'))
|
||||
->password()->revealable()
|
||||
->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
->suffixCopy()
|
||||
@ -106,12 +111,17 @@ class DatabaseResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('host')
|
||||
->label(trans('server/database.host'))
|
||||
->state(fn (Database $database) => $database->address())
|
||||
->badge(),
|
||||
TextColumn::make('database'),
|
||||
TextColumn::make('username'),
|
||||
TextColumn::make('remote'),
|
||||
TextColumn::make('database')
|
||||
->label(trans('server/database.database')),
|
||||
TextColumn::make('username')
|
||||
->label(trans('server/database.username')),
|
||||
TextColumn::make('remote')
|
||||
->label(trans('server/database.remote')),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('server/database.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
@ -154,4 +164,9 @@ class DatabaseResource extends Resource
|
||||
'index' => ListDatabases::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/database.title');
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class ListDatabases extends ListRecords
|
||||
CreateAction::make('new')
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon(fn () => $server->databases()->count() >= $server->database_limit ? 'tabler-database-x' : 'tabler-database-plus')
|
||||
->tooltip(fn () => $server->databases()->count() >= $server->database_limit ? 'Database limit reached' : 'Create Database')
|
||||
->tooltip(fn () => $server->databases()->count() >= $server->database_limit ? trans('server/database.limit') : trans('server/database.create_database'))
|
||||
->disabled(fn () => $server->databases()->count() >= $server->database_limit)
|
||||
->color(fn () => $server->databases()->count() >= $server->database_limit ? 'danger' : 'primary')
|
||||
->createAnother(false)
|
||||
@ -44,20 +44,20 @@ class ListDatabases extends ListRecords
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('database_host_id')
|
||||
->label('Database Host')
|
||||
->label(trans('server/database.database_host'))
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->placeholder('Select Database Host')
|
||||
->placeholder(trans('server/database.database_host_select'))
|
||||
->options(fn () => $server->node->databaseHosts->mapWithKeys(fn (DatabaseHost $databaseHost) => [$databaseHost->id => $databaseHost->name])),
|
||||
TextInput::make('database')
|
||||
->label(trans('server/database.name'))
|
||||
->columnSpan(1)
|
||||
->label('Database Name')
|
||||
->prefix('s'. $server->id . '_')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('Leaving this blank will auto generate a random name'),
|
||||
->hintIconTooltip(trans('server/database.name_hint')),
|
||||
TextInput::make('remote')
|
||||
->label(trans('server/database.connections_from'))
|
||||
->columnSpan(1)
|
||||
->label('Connections From')
|
||||
->default('%'),
|
||||
]),
|
||||
])
|
||||
@ -76,4 +76,9 @@ class ListDatabases extends ListRecords
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/database.title');
|
||||
}
|
||||
}
|
||||
|
@ -60,4 +60,9 @@ class FileResource extends Resource
|
||||
'index' => ListFiles::route('/{path?}'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/file.title');
|
||||
}
|
||||
}
|
||||
|
@ -72,10 +72,10 @@ class EditFiles extends Page
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Editing: ' . $this->path)
|
||||
Section::make(trans('server/file.actions.edit.title', ['file' => $this->path]))
|
||||
->footerActions([
|
||||
Action::make('save_and_close')
|
||||
->label('Save & Close')
|
||||
->label(trans('server/file.actions.edit.save_close'))
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->icon('tabler-device-floppy')
|
||||
->keyBindings('mod+shift+s')
|
||||
@ -88,14 +88,14 @@ class EditFiles extends Page
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('File saved')
|
||||
->title(trans('server/file.actions.edit.notification'))
|
||||
->body(fn () => $this->path)
|
||||
->send();
|
||||
|
||||
$this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
|
||||
}),
|
||||
Action::make('save')
|
||||
->label('Save')
|
||||
->label(trans('server/file.actions.edit.save'))
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->icon('tabler-device-floppy')
|
||||
->keyBindings('mod+s')
|
||||
@ -108,20 +108,20 @@ class EditFiles extends Page
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title('File saved')
|
||||
->title(trans('server/file.actions.edit.notification'))
|
||||
->body(fn () => $this->path)
|
||||
->send();
|
||||
}),
|
||||
Action::make('cancel')
|
||||
->label('Cancel')
|
||||
->label(trans('server/file.actions.edit.cancel'))
|
||||
->color('danger')
|
||||
->icon('tabler-x')
|
||||
->url(fn () => ListFiles::getUrl(['path' => dirname($this->path)])),
|
||||
])
|
||||
->footerActionsAlignment(Alignment::End) //TODO MISSING PADDING
|
||||
->schema([
|
||||
Select::make('lang') //TODO BROKEN
|
||||
->label('Syntax Highlighting')
|
||||
Select::make('lang')
|
||||
->label(trans('server/file.actions.new_file.syntax'))
|
||||
->searchable()
|
||||
->live()
|
||||
->options(EditorLanguages::class)
|
||||
@ -133,7 +133,7 @@ class EditFiles extends Page
|
||||
try {
|
||||
return $this->getDaemonFileRepository()->getContent($this->path, config('panel.files.max_edit_size'));
|
||||
} catch (FileSizeTooLargeException) {
|
||||
AlertBanner::make()
|
||||
AlertBanner::make('file_too_large')
|
||||
->title('<code>' . basename($this->path) . '</code> is too large!')
|
||||
->body('Max is ' . convert_bytes_to_readable(config('panel.files.max_edit_size')))
|
||||
->danger()
|
||||
@ -142,7 +142,7 @@ class EditFiles extends Page
|
||||
|
||||
$this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
|
||||
} catch (FileNotFoundException) {
|
||||
AlertBanner::make()
|
||||
AlertBanner::make('file_not_found')
|
||||
->title('<code>' . basename($this->path) . '</code> not found!')
|
||||
->danger()
|
||||
->closable()
|
||||
@ -150,7 +150,7 @@ class EditFiles extends Page
|
||||
|
||||
$this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
|
||||
} catch (FileNotEditableException) {
|
||||
AlertBanner::make()
|
||||
AlertBanner::make('file_is_directory')
|
||||
->title('<code>' . basename($this->path) . '</code> is a directory')
|
||||
->danger()
|
||||
->closable()
|
||||
@ -183,15 +183,6 @@ class EditFiles extends Page
|
||||
->info()
|
||||
->closable()
|
||||
->send();
|
||||
|
||||
try {
|
||||
$this->getDaemonFileRepository()->getDirectory('/');
|
||||
} catch (ConnectionException) {
|
||||
AlertBanner::make('node_connection_error')
|
||||
->title('Could not connect to the node!')
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,14 +91,17 @@ class ListFiles extends ListRecords
|
||||
->defaultSort('name')
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('server/file.name'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->icon(fn (File $file) => $file->getIcon()),
|
||||
BytesColumn::make('size')
|
||||
->label(trans('server/file.size'))
|
||||
->visibleFrom('md')
|
||||
->state(fn (File $file) => $file->is_directory ? null : $file->size)
|
||||
->sortable(),
|
||||
DateTimeColumn::make('modified_at')
|
||||
->label(trans('server/file.modified_at'))
|
||||
->visibleFrom('md')
|
||||
->since()
|
||||
->sortable(),
|
||||
@ -117,7 +120,7 @@ class ListFiles extends ListRecords
|
||||
->recordActions([
|
||||
Action::make('view')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server))
|
||||
->label('Open')
|
||||
->label(trans('server/file.actions.open'))
|
||||
->icon('tabler-eye')->iconSize(IconSize::Large)
|
||||
->visible(fn (File $file) => $file->is_directory)
|
||||
->url(fn (File $file) => self::getUrl(['path' => join_paths($this->path, $file->name)])),
|
||||
@ -129,11 +132,11 @@ class ListFiles extends ListRecords
|
||||
ActionGroup::make([
|
||||
Action::make('rename')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->label('Rename')
|
||||
->label(trans('server/file.actions.rename.title'))
|
||||
->icon('tabler-forms')->iconSize(IconSize::Large)
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('File name')
|
||||
->label(trans('server/file.actions.rename.name'))
|
||||
->default(fn (File $file) => $file->name)
|
||||
->required(),
|
||||
])
|
||||
@ -150,14 +153,14 @@ class ListFiles extends ListRecords
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title('File Renamed')
|
||||
->title(trans('server/file.actions.rename.notification'))
|
||||
->body(fn () => $file->name . ' -> ' . $data['name'])
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
Action::make('copy')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->label('Copy')
|
||||
->label(trans('server/file.actions.copy.title'))
|
||||
->icon('tabler-copy')->iconSize(IconSize::Large)
|
||||
->visible(fn (File $file) => $file->is_file)
|
||||
->action(function (File $file) {
|
||||
@ -168,7 +171,7 @@ class ListFiles extends ListRecords
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title('File copied')
|
||||
->title(trans('server/file.actions.copy.notification'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
@ -176,18 +179,18 @@ class ListFiles extends ListRecords
|
||||
}),
|
||||
Action::make('download')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ_CONTENT, $server))
|
||||
->label('Download')
|
||||
->label(trans('server/file.actions.download'))
|
||||
->icon('tabler-download')->iconSize(IconSize::Large)
|
||||
->visible(fn (File $file) => $file->is_file)
|
||||
->url(fn (File $file) => DownloadFiles::getUrl(['path' => join_paths($this->path, $file->name)]), true),
|
||||
Action::make('move')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->label('Move')
|
||||
->label(trans('server/file.actions.move.title'))
|
||||
->icon('tabler-replace')->iconSize(IconSize::Large)
|
||||
->schema([
|
||||
TextInput::make('location')
|
||||
->label('New location')
|
||||
->hint('Enter the location of this file or folder, relative to the current directory.')
|
||||
->label(trans('server/file.actions.move.new_location'))
|
||||
->hint(trans('server/file.actions.move.new_location_hint'))
|
||||
->required()
|
||||
->live(),
|
||||
TextEntry::make('new_location')
|
||||
@ -210,22 +213,24 @@ class ListFiles extends ListRecords
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title('File Moved')
|
||||
->title(trans('server/file.actions.move.notification'))
|
||||
->body($oldLocation . ' -> ' . $newLocation)
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
Action::make('permissions')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->label('Permissions')
|
||||
->label(trans('server/file.actions.permissions.title'))
|
||||
->icon('tabler-license')->iconSize(IconSize::Large)
|
||||
->schema([
|
||||
CheckboxList::make('owner')
|
||||
->label(trans('server/file.actions.permissions.owner'))
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'write' => 'Write',
|
||||
'execute' => 'Execute',
|
||||
'read' => trans('server/file.actions.permissions.read'),
|
||||
'write' => trans('server/file.actions.permissions.write'),
|
||||
'execute' => trans('server/file.actions.permissions.execute'),
|
||||
])
|
||||
->formatStateUsing(function ($state, File $file) {
|
||||
$mode = (int) substr((string) $file->mode_bits, 0, 1);
|
||||
@ -233,11 +238,13 @@ class ListFiles extends ListRecords
|
||||
return $this->getPermissionsFromModeBit($mode);
|
||||
}),
|
||||
CheckboxList::make('group')
|
||||
->label(trans('server/file.actions.permissions.group'))
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'write' => 'Write',
|
||||
'execute' => 'Execute',
|
||||
'read' => trans('server/file.actions.permissions.read'),
|
||||
'write' => trans('server/file.actions.permissions.write'),
|
||||
'execute' => trans('server/file.actions.permissions.execute'),
|
||||
])
|
||||
->formatStateUsing(function ($state, File $file) {
|
||||
$mode = (int) substr((string) $file->mode_bits, 1, 1);
|
||||
@ -245,11 +252,13 @@ class ListFiles extends ListRecords
|
||||
return $this->getPermissionsFromModeBit($mode);
|
||||
}),
|
||||
CheckboxList::make('public')
|
||||
->label(trans('server/file.actions.permissions.public'))
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'write' => 'Write',
|
||||
'execute' => 'Execute',
|
||||
'read' => trans('server/file.actions.permissions.read'),
|
||||
'write' => trans('server/file.actions.permissions.write'),
|
||||
'execute' => trans('server/file.actions.permissions.execute'),
|
||||
])
|
||||
->formatStateUsing(function ($state, File $file) {
|
||||
$mode = (int) substr((string) $file->mode_bits, 2, 1);
|
||||
@ -267,17 +276,17 @@ class ListFiles extends ListRecords
|
||||
$this->getDaemonFileRepository()->chmodFiles($this->path, [['file' => $file->name, 'mode' => $mode]]);
|
||||
|
||||
Notification::make()
|
||||
->title('Permissions changed to ' . $mode)
|
||||
->title(trans('server/file.actions.permissions.notification', ['mode' => $mode]))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
Action::make('archive')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
|
||||
->label('Archive')
|
||||
->label(trans('server/file.actions.archive.title'))
|
||||
->icon('tabler-archive')->iconSize(IconSize::Large)
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Archive name')
|
||||
->label(trans('server/file.actions.archive.archive_name'))
|
||||
->placeholder(fn () => 'archive-' . str(Carbon::now()->toRfc3339String())->replace(':', '')->before('+0000') . 'Z')
|
||||
->suffix('.tar.gz'),
|
||||
])
|
||||
@ -291,7 +300,7 @@ class ListFiles extends ListRecords
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title('Archive created')
|
||||
->title(trans('server/file.actions.archive.notification'))
|
||||
->body($archive['name'])
|
||||
->success()
|
||||
->send();
|
||||
@ -300,7 +309,7 @@ class ListFiles extends ListRecords
|
||||
}),
|
||||
Action::make('unarchive')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
|
||||
->label('Unarchive')
|
||||
->label(trans('server/file.actions.unarchive.title'))
|
||||
->icon('tabler-archive')->iconSize(IconSize::Large)
|
||||
->visible(fn (File $file) => $file->isArchive())
|
||||
->action(function (File $file) {
|
||||
@ -312,7 +321,7 @@ class ListFiles extends ListRecords
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title('Unarchive completed')
|
||||
->title(trans('server/file.actions.unarchive.notification'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
@ -341,10 +350,10 @@ class ListFiles extends ListRecords
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->schema([
|
||||
TextInput::make('location')
|
||||
->label('Directory')
|
||||
->hint('Enter the new directory, relative to the current directory.')
|
||||
->required()
|
||||
->live(),
|
||||
->label(trans('server/file.actions.move.directory'))
|
||||
->hint(trans('server/file.actions.move.directory_hint'))
|
||||
->required()
|
||||
->live(),
|
||||
TextEntry::make('new_location')
|
||||
->state(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location') ?? ''))),
|
||||
])
|
||||
@ -359,21 +368,21 @@ class ListFiles extends ListRecords
|
||||
->property('files', $files)
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title(count($files) . ' Files were moved to ' . resolve_path(join_paths($this->path, $location)))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
BulkAction::make('archive')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Archive name')
|
||||
->placeholder(fn () => 'archive-' . str(Carbon::now()->toRfc3339String())->replace(':', '')->before('+0000') . 'Z')
|
||||
->suffix('.tar.gz'),
|
||||
])
|
||||
->action(function ($data, Collection $files) {
|
||||
$files = $files->map(fn ($file) => $file['name'])->toArray();
|
||||
Notification::make()
|
||||
->title(trans('server/file.actions.move.bulk_notification', ['count' => count($files), 'directory' => resolve_path(join_paths($this->path, $location))]))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
BulkAction::make('archive')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
|
||||
->form([
|
||||
TextInput::make('name')
|
||||
->label(trans('server/file.actions.archive.archive_name'))
|
||||
->placeholder(fn () => 'archive-' . str(Carbon::now()->toRfc3339String())->replace(':', '')->before('+0000') . 'Z')
|
||||
->suffix('.tar.gz'),
|
||||
])
|
||||
->action(function ($data, Collection $files) {
|
||||
$files = $files->map(fn ($file) => $file['name'])->toArray();
|
||||
|
||||
$archive = $this->getDaemonFileRepository()->compressFiles($this->path, $files, $data['name']);
|
||||
|
||||
@ -383,11 +392,11 @@ class ListFiles extends ListRecords
|
||||
->property('files', $files)
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title('Archive created')
|
||||
->body($archive['name'])
|
||||
->success()
|
||||
->send();
|
||||
Notification::make()
|
||||
->title(trans('server/file.actions.archive.notification'))
|
||||
->body($archive['name'])
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return redirect(ListFiles::getUrl(['path' => $this->path]));
|
||||
}),
|
||||
@ -402,23 +411,13 @@ class ListFiles extends ListRecords
|
||||
->property('files', $files)
|
||||
->log();
|
||||
|
||||
Notification::make()
|
||||
->title(count($files) . ' Files deleted.')
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
]),
|
||||
Action::make('new_file')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->tooltip('New File')
|
||||
->hiddenLabel()->icon('tabler-file-plus')->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->color('primary')
|
||||
->keyBindings('')
|
||||
->modalSubmitActionLabel('Create')
|
||||
->action(function ($data) {
|
||||
$path = join_paths($this->path, $data['name']);
|
||||
try {
|
||||
$this->getDaemonFileRepository()->putContent($path, $data['editor'] ?? '');
|
||||
Notification::make()
|
||||
->title(trans('server/file.actions.delete.bulk_notification', ['count' => count($files)]))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
Activity::event('server:file.write')
|
||||
->property('file', join_paths($path, $data['name']))
|
||||
@ -430,65 +429,89 @@ class ListFiles extends ListRecords
|
||||
->closable()
|
||||
->send();
|
||||
|
||||
$this->redirect(self::getUrl(['path' => dirname($path)]));
|
||||
}
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required(),
|
||||
CodeEditor::make('editor')
|
||||
->hiddenLabel(),
|
||||
]),
|
||||
Action::make('new_folder')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->hiddenLabel()->icon('tabler-folder-plus')->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->tooltip('New Folder')
|
||||
->color('primary')
|
||||
->action(function ($data) {
|
||||
try {
|
||||
$this->getDaemonFileRepository()->createDirectory($data['name'], $this->path);
|
||||
return [
|
||||
HeaderAction::make('new_file')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->tooltip(trans('server/file.actions.new_file.title'))
|
||||
->hiddenLabel()->icon('tabler-file-plus')->iconButton()->iconSize(IconSize::Large)
|
||||
->color('primary')
|
||||
->modalSubmitActionLabel(trans('server/file.actions.new_file.create'))
|
||||
->action(function ($data) {
|
||||
$path = join_paths($this->path, $data['name']);
|
||||
try {
|
||||
$this->getDaemonFileRepository()->putContent($path, $data['editor'] ?? '');
|
||||
|
||||
Activity::event('server:file.create-directory')
|
||||
->property(['directory' => $this->path, 'name' => $data['name']])
|
||||
->log();
|
||||
} catch (FileExistsException) {
|
||||
$path = join_paths($this->path, $data['name']);
|
||||
AlertBanner::make()
|
||||
->title('<code>' . $path . '</code> already exists!')
|
||||
->danger()
|
||||
->closable()
|
||||
->send();
|
||||
Activity::event('server:file.write')
|
||||
->property('file', join_paths($path, $data['name']))
|
||||
->log();
|
||||
} catch (FileExistsException) {
|
||||
AlertBanner::make('file_already_exists')
|
||||
->title('<code>' . $path . '</code> already exists!')
|
||||
->danger()
|
||||
->closable()
|
||||
->send();
|
||||
|
||||
$this->redirect(self::getUrl(['path' => dirname($path)]));
|
||||
}
|
||||
})
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Folder Name')
|
||||
->required(),
|
||||
]),
|
||||
Action::make('upload')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->hiddenLabel()->icon('tabler-upload')->iconButton()->iconSize(IconSize::ExtraLarge)
|
||||
->tooltip('Upload')
|
||||
->color('success')
|
||||
->action(function ($data) {
|
||||
if (count($data['files']) > 0 && !isset($data['url'])) {
|
||||
/** @var UploadedFile $file */
|
||||
foreach ($data['files'] as $file) {
|
||||
$this->getDaemonFileRepository()->putContent(join_paths($this->path, $file->getClientOriginalName()), $file->getContent());
|
||||
$this->redirect(self::getUrl(['path' => dirname($path)]));
|
||||
}
|
||||
})
|
||||
->form([
|
||||
TextInput::make('name')
|
||||
->label(trans('server/file.actions.new_file.file_name'))
|
||||
->required(),
|
||||
Select::make('lang')
|
||||
->label(trans('server/file.actions.new_file.syntax'))
|
||||
->searchable()
|
||||
->native(false)
|
||||
->live()
|
||||
->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') ?? 'plaintext'),
|
||||
]),
|
||||
HeaderAction::make('new_folder')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->hiddenLabel()->icon('tabler-folder-plus')->iconButton()->iconSize(IconSize::Large)
|
||||
->tooltip(trans('server/file.actions.new_folder.title'))
|
||||
->color('primary')
|
||||
->action(function ($data) {
|
||||
try {
|
||||
$this->getDaemonFileRepository()->createDirectory($data['name'], $this->path);
|
||||
|
||||
Activity::event('server:file.uploaded')
|
||||
->property('directory', $this->path)
|
||||
->property('file', $file->getClientOriginalName())
|
||||
->log();
|
||||
}
|
||||
} elseif ($data['url'] !== null) {
|
||||
$this->getDaemonFileRepository()->pull($data['url'], $this->path);
|
||||
Activity::event('server:file.create-directory')
|
||||
->property(['directory' => $this->path, 'name' => $data['name']])
|
||||
->log();
|
||||
} catch (FileExistsException) {
|
||||
$path = join_paths($this->path, $data['name']);
|
||||
AlertBanner::make('folder_already_exists')
|
||||
->title('<code>' . $path . '</code> already exists!')
|
||||
->danger()
|
||||
->closable()
|
||||
->send();
|
||||
|
||||
Activity::event('server:file.pull')
|
||||
->property('url', $data['url'])
|
||||
$this->redirect(self::getUrl(['path' => dirname($path)]));
|
||||
}
|
||||
})
|
||||
->form([
|
||||
TextInput::make('name')
|
||||
->label(trans('server/file.actions.new_folder.folder_name'))
|
||||
->required(),
|
||||
]),
|
||||
HeaderAction::make('upload')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->hiddenLabel()->icon('tabler-upload')->iconButton()->iconSize(IconSize::Large)
|
||||
->tooltip(trans('server/file.actions.upload.title'))
|
||||
->color('success')
|
||||
->action(function ($data) {
|
||||
if (count($data['files']) > 0 && !isset($data['url'])) {
|
||||
/** @var UploadedFile $file */
|
||||
foreach ($data['files'] as $file) {
|
||||
$this->getDaemonFileRepository()->putContent(join_paths($this->path, $file->getClientOriginalName()), $file->getContent());
|
||||
|
||||
Activity::event('server:file.uploaded')
|
||||
->property('directory', $this->path)
|
||||
->log();
|
||||
}
|
||||
@ -540,12 +563,53 @@ class ListFiles extends ListRecords
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Action|ActionGroup>
|
||||
*/
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
return redirect(ListFiles::getUrl(['path' => $this->path]));
|
||||
})
|
||||
->form([
|
||||
Tabs::make()
|
||||
->contained(false)
|
||||
->schema([
|
||||
Tab::make(trans('server/file.actions.upload.from_files'))
|
||||
->live()
|
||||
->schema([
|
||||
FileUpload::make('files')
|
||||
->storeFiles(false)
|
||||
->previewable(false)
|
||||
->preserveFilenames()
|
||||
->maxSize((int) round($server->node->upload_size * (config('panel.use_binary_prefix') ? 1.048576 * 1024 : 1000)))
|
||||
->multiple(),
|
||||
]),
|
||||
Tab::make(trans('server/file.actions.upload.url'))
|
||||
->live()
|
||||
->disabled(fn (Get $get) => count($get('files')) > 0)
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->label(trans('server/file.actions.upload.url'))
|
||||
->url(),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
HeaderAction::make('search')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_READ, $server))
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->tooltip(trans('server/file.actions.global_search.title'))
|
||||
->color('primary')
|
||||
->icon('tabler-world-search')
|
||||
->modalHeading(trans('server/file.actions.global_search.title'))
|
||||
->modalSubmitActionLabel(trans('server/file.actions.global_search.search'))
|
||||
->form([
|
||||
TextInput::make('searchTerm')
|
||||
->label(trans('server/file.actions.global_search.search_term'))
|
||||
->placeholder(trans('server/file.actions.global_search.search_term_placeholder'))
|
||||
->required()
|
||||
->regex('/^[^*]*\*?[^*]*$/')
|
||||
->minValue(3),
|
||||
])
|
||||
->action(fn ($data) => redirect(SearchFiles::getUrl([
|
||||
'searchTerm' => $data['searchTerm'],
|
||||
'path' => $this->path,
|
||||
]))),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -584,4 +648,9 @@ class ListFiles extends ListRecords
|
||||
->where('path', '.*'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/file.title');
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Url;
|
||||
|
||||
@ -23,8 +24,6 @@ class SearchFiles extends ListRecords
|
||||
|
||||
protected static string $resource = FileResource::class;
|
||||
|
||||
protected static ?string $title = 'Global Search';
|
||||
|
||||
#[Locked]
|
||||
public string $searchTerm;
|
||||
|
||||
@ -37,7 +36,7 @@ class SearchFiles extends ListRecords
|
||||
|
||||
return [
|
||||
$resource::getUrl() => $resource::getBreadcrumb(),
|
||||
self::getUrl(['searchTerm' => $this->searchTerm]) => 'Search "' . $this->searchTerm . '"',
|
||||
self::getUrl(['searchTerm' => $this->searchTerm]) => trans('server/file.actions.global_search.search') . ' "' . $this->searchTerm . '"',
|
||||
];
|
||||
}
|
||||
|
||||
@ -51,10 +50,18 @@ class SearchFiles extends ListRecords
|
||||
->query(fn () => File::get($server, $this->path, $this->searchTerm)->orderByDesc('is_directory')->orderBy('name'))
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('server/file.name'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->icon(fn (File $file) => $file->getIcon()),
|
||||
BytesColumn::make('size'),
|
||||
BytesColumn::make('size')
|
||||
->label(trans('server/file.size'))
|
||||
->visibleFrom('md')
|
||||
->state(fn (File $file) => $file->size)
|
||||
->sortable(),
|
||||
DateTimeColumn::make('modified_at')
|
||||
->label(trans('server/file.modified_at'))
|
||||
->visibleFrom('md')
|
||||
->since()
|
||||
->sortable(),
|
||||
])
|
||||
@ -66,4 +73,9 @@ class SearchFiles extends ListRecords
|
||||
return $file->canEdit() ? EditFiles::getUrl(['path' => join_paths($this->path, $file->name)]) : null;
|
||||
});
|
||||
}
|
||||
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return trans('server/file.actions.global_search.title');
|
||||
}
|
||||
}
|
||||
|
@ -95,29 +95,29 @@ class ScheduleResource extends Resource
|
||||
])
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Schedule Name')
|
||||
->placeholder('A human readable identifier for this schedule.')
|
||||
->label(trans('server/schedule.name'))
|
||||
->columnSpanFull()
|
||||
->autocomplete(false)
|
||||
->columnSpanFull()
|
||||
->required(),
|
||||
Toggle::make('only_when_online')
|
||||
->label('Only when Server is Online?')
|
||||
->hintIconTooltip('Only execute this schedule when the server is in a running state.')
|
||||
->label(trans('server/schedule.only_online'))
|
||||
->hintIconTooltip(trans('server/schedule.only_online_hint'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->inline(false)
|
||||
->required()
|
||||
->default(1),
|
||||
Toggle::make('is_active')
|
||||
->label('Enable Schedule?')
|
||||
->hintIconTooltip('This schedule will be executed automatically if enabled.')
|
||||
->label(trans('server/schedule.enabled'))
|
||||
->hintIconTooltip(trans('server/schedule.enabled_hint'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->inline(false)
|
||||
->hiddenOn('view')
|
||||
->required()
|
||||
->default(1),
|
||||
ToggleButtons::make('Status')
|
||||
->formatStateUsing(fn (?Schedule $schedule) => !$schedule ? 'new' : (!$schedule->is_active ? 'inactive' : ($schedule->is_processing ? 'processing' : 'active')))
|
||||
->options(fn (?Schedule $schedule) => !$schedule ? ['new' => 'New'] : (!$schedule->is_active ? ['inactive' => 'Inactive'] : ($schedule->is_processing ? ['processing' => 'Processing'] : ['active' => 'Active'])))
|
||||
->formatStateUsing(fn (Schedule $schedule) => !$schedule->is_active ? 'inactive' : ($schedule->is_processing ? 'processing' : 'active'))
|
||||
->options(fn (Schedule $schedule) => !$schedule->is_active ? ['inactive' => trans('server/schedule.inactive')] : ($schedule->is_processing ? ['processing' => trans('server/schedule.processing')] : ['active' => trans('server/schedule.active')]))
|
||||
->colors([
|
||||
'new' => 'primary',
|
||||
'inactive' => 'danger',
|
||||
@ -126,22 +126,27 @@ class ScheduleResource extends Resource
|
||||
])
|
||||
->visibleOn('view'),
|
||||
Section::make('Cron')
|
||||
->description(fn (Get $get) => new HtmlString('Please keep in mind that the cron inputs below always assume UTC.<br>Next run in your timezone (' . auth()->user()->timezone . '): <b>'. Utilities::getScheduleNextRunDate($get('cron_minute'), $get('cron_hour'), $get('cron_day_of_month'), $get('cron_month'), $get('cron_day_of_week'))->timezone(auth()->user()->timezone) . '</b>'))
|
||||
->label(trans('server/schedule.cron'))
|
||||
->description(fn (Get $get) => new HtmlString(trans('server/schedule.cron_body') . '<br>' . trans('server/schedule.cron_timezone', ['timezone' => auth()->user()->timezone, 'next_run' => Utilities::getScheduleNextRunDate($get('cron_minute'), $get('cron_hour'), $get('cron_day_of_month'), $get('cron_month'), $get('cron_day_of_week'))->timezone(auth()->user()->timezone)])))
|
||||
->schema([
|
||||
Actions::make([
|
||||
CronPresetAction::make('hourly')
|
||||
->label(trans('server/schedule.time.hourly'))
|
||||
->cron('0', '*', '*', '*', '*'),
|
||||
CronPresetAction::make('daily')
|
||||
->label(trans('server/schedule.time.daily'))
|
||||
->cron('0', '0', '*', '*', '*'),
|
||||
CronPresetAction::make('weekly_monday')
|
||||
->label('Weekly (Monday)')
|
||||
->label(trans('server/schedule.time.weekly_mon'))
|
||||
->cron('0', '0', '*', '*', '1'),
|
||||
CronPresetAction::make('weekly_sunday')
|
||||
->label('Weekly (Sunday)')
|
||||
->label(trans('server/schedule.time.weekly_sun'))
|
||||
->cron('0', '0', '*', '*', '0'),
|
||||
CronPresetAction::make('monthly')
|
||||
->label(trans('server/schedule.time.monthly'))
|
||||
->cron('0', '0', '1', '*', '*'),
|
||||
CronPresetAction::make('every_x_minutes')
|
||||
->label(trans('server/schedule.time.every_min'))
|
||||
->color(fn (Get $get) => str($get('cron_minute'))->startsWith('*/')
|
||||
&& $get('cron_hour') == '*'
|
||||
&& $get('cron_day_of_month') == '*'
|
||||
@ -153,8 +158,8 @@ class ScheduleResource extends Resource
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(60)
|
||||
->prefix('Every')
|
||||
->suffix('Minutes'),
|
||||
->prefix(trans('server/schedule.time.every'))
|
||||
->suffix(trans('server/schedule.time.minutes')),
|
||||
])
|
||||
->action(function (Set $set, $data) {
|
||||
$set('cron_minute', '*/' . $data['x']);
|
||||
@ -175,8 +180,8 @@ class ScheduleResource extends Resource
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(24)
|
||||
->prefix('Every')
|
||||
->suffix('Hours'),
|
||||
->prefix(trans('server/schedule.time.every'))
|
||||
->suffix(trans('server/schedule.time.hours')),
|
||||
])
|
||||
->action(function (Set $set, $data) {
|
||||
$set('cron_minute', '0');
|
||||
@ -197,8 +202,8 @@ class ScheduleResource extends Resource
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(24)
|
||||
->prefix('Every')
|
||||
->suffix('Days'),
|
||||
->prefix(trans('server/schedule.time.every'))
|
||||
->suffix(trans('server/schedule.time.days')),
|
||||
])
|
||||
->action(function (Set $set, $data) {
|
||||
$set('cron_minute', '0');
|
||||
@ -219,8 +224,8 @@ class ScheduleResource extends Resource
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(24)
|
||||
->prefix('Every')
|
||||
->suffix('Months'),
|
||||
->prefix(trans('server/schedule.time.every'))
|
||||
->suffix(trans('server/schedule.time.months')),
|
||||
])
|
||||
->action(function (Set $set, $data) {
|
||||
$set('cron_minute', '0');
|
||||
@ -238,15 +243,15 @@ class ScheduleResource extends Resource
|
||||
->schema([
|
||||
Select::make('x')
|
||||
->label('')
|
||||
->prefix('Every')
|
||||
->prefix(trans('server/schedule.time.every'))
|
||||
->options([
|
||||
'1' => 'Monday',
|
||||
'2' => 'Tuesday',
|
||||
'3' => 'Wednesday',
|
||||
'4' => 'Thursday',
|
||||
'5' => 'Friday',
|
||||
'6' => 'Saturday',
|
||||
'0' => 'Sunday',
|
||||
'1' => trans('server/schedule.time.monday'),
|
||||
'2' => trans('server/schedule.time.tuesday'),
|
||||
'3' => trans('server/schedule.time.wednesday'),
|
||||
'4' => trans('server/schedule.time.thursday'),
|
||||
'5' => trans('server/schedule.time.friday'),
|
||||
'6' => trans('server/schedule.time.saturday'),
|
||||
'0' => trans('server/schedule.time.sunday'),
|
||||
])
|
||||
->selectablePlaceholder(false)
|
||||
->native(false),
|
||||
@ -262,31 +267,47 @@ class ScheduleResource extends Resource
|
||||
->hiddenOn('view'),
|
||||
Group::make([
|
||||
TextInput::make('cron_minute')
|
||||
->label('Minute')
|
||||
->default('*/5')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_hour')
|
||||
->label('Hour')
|
||||
->default('*')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_day_of_month')
|
||||
->label('Day of Month')
|
||||
->default('*')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_month')
|
||||
->label('Month')
|
||||
->default('*')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_day_of_week')
|
||||
->label(trans('server/schedule.time.minute'))
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->default('*/5')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_hour')
|
||||
->label(trans('server/schedule.time.hour'))
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->default('*')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_day_of_month')
|
||||
->label(trans('server/schedule.time.day_of_month'))
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->default('*')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_month')
|
||||
->label(trans('server/schedule.time.month'))
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->default('*')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('cron_day_of_week')
|
||||
->label(trans('server/schedule.time.day_of_week'))
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->label('Day of Week')
|
||||
->default('*')
|
||||
->required()
|
||||
->live(),
|
||||
@ -308,22 +329,26 @@ class ScheduleResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('server/schedule.name'))
|
||||
->searchable(),
|
||||
TextColumn::make('cron')
|
||||
->label(trans('server/schedule.cron'))
|
||||
->state(fn (Schedule $schedule) => $schedule->cron_minute . ' ' . $schedule->cron_hour . ' ' . $schedule->cron_day_of_month . ' ' . $schedule->cron_month . ' ' . $schedule->cron_day_of_week),
|
||||
TextColumn::make('status')
|
||||
->state(fn (Schedule $schedule) => !$schedule->is_active ? 'Inactive' : ($schedule->is_processing ? 'Processing' : 'Active')),
|
||||
->label(trans('server/schedule.status'))
|
||||
->state(fn (Schedule $schedule) => !$schedule->is_active ? trans('server/schedule.inactive') : ($schedule->is_processing ? trans('server/schedule.processing') : trans('server/schedule.active'))),
|
||||
IconColumn::make('only_when_online')
|
||||
->label(trans('server/schedule.online_only'))
|
||||
->boolean()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('last_run_at')
|
||||
->label('Last run')
|
||||
->placeholder('Never')
|
||||
->label(trans('server/schedule.last_run'))
|
||||
->placeholder(trans('server/schedule.never'))
|
||||
->since()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('next_run_at')
|
||||
->label('Next run')
|
||||
->placeholder('Never')
|
||||
->label(trans('server/schedule.next_run'))
|
||||
->placeholder(trans('server/schedule.never'))
|
||||
->since()
|
||||
->sortable()
|
||||
->state(fn (Schedule $schedule) => $schedule->is_active ? $schedule->next_run_at : null),
|
||||
@ -378,11 +403,16 @@ class ScheduleResource extends Resource
|
||||
return Utilities::getScheduleNextRunDate($minute, $hour, $dayOfMonth, $month, $dayOfWeek);
|
||||
} catch (Exception) {
|
||||
Notification::make()
|
||||
->title('The cron data provided does not evaluate to a valid expression')
|
||||
->title(trans('server/schedule.notification_invalid_cron'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
throw new Halt();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/schedule.title');
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class EditSchedule extends EditRecord
|
||||
DeleteAction::make()
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-trash')
|
||||
->tooltip('Delete Schedule')
|
||||
->tooltip(trans('server/schedule.delete'))
|
||||
->after(function ($record) {
|
||||
Activity::event('server:schedule.delete')
|
||||
->property('name', $record->name)
|
||||
@ -60,15 +60,15 @@ class EditSchedule extends EditRecord
|
||||
ExportScheduleAction::make()
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-download')
|
||||
->tooltip('Export Schedule'),
|
||||
->tooltip(trans('server/schedule.export')),
|
||||
$this->getSaveFormAction()->formId('form')
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-device-floppy')
|
||||
->tooltip('Save Schedule'),
|
||||
->tooltip(trans('server/schedule.save')),
|
||||
$this->getCancelFormAction()->formId('form')
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-cancel')
|
||||
->tooltip('Cancel'),
|
||||
->tooltip(trans('server/schedule.cancel')),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,25 @@ class ListSchedules extends ListRecords
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
return [
|
||||
CreateAction::make()
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-calendar-plus')
|
||||
->tooltip(trans('server/schedule.new')),
|
||||
ImportScheduleAction::make()
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-download')
|
||||
->tooltip(trans('server/schedule.import')),
|
||||
];
|
||||
}
|
||||
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return trans('server/schedule.title');
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class ViewSchedule extends ViewRecord
|
||||
return [
|
||||
Action::make('runNow')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_SCHEDULE_UPDATE, Filament::getTenant()))
|
||||
->label(fn (Schedule $schedule) => $schedule->tasks->count() === 0 ? 'No tasks' : ($schedule->is_processing ? 'Processing' : 'Run now'))
|
||||
->label(fn (Schedule $schedule) => $schedule->tasks->count() === 0 ? trans('server/schedule.no_tasks') : ($schedule->is_processing ? trans('server/schedule.processing') : trans('server/schedule.run_now')))
|
||||
->color(fn (Schedule $schedule) => $schedule->tasks->count() === 0 || $schedule->is_processing ? 'warning' : 'primary')
|
||||
->disabled(fn (Schedule $schedule) => $schedule->tasks->count() === 0 || $schedule->is_processing)
|
||||
->action(function (ProcessScheduleService $service, Schedule $schedule) {
|
||||
@ -45,7 +45,7 @@ class ViewSchedule extends ViewRecord
|
||||
EditAction::make()
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-calendar-code')
|
||||
->tooltip('Edit Schedule'),
|
||||
->tooltip(trans('server/schedule.edit')),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -31,10 +31,10 @@ class TasksRelationManager extends RelationManager
|
||||
private function getActionOptions(bool $full = true): array
|
||||
{
|
||||
return [
|
||||
Task::ACTION_POWER => $full ? 'Send power action' : 'Power action',
|
||||
Task::ACTION_COMMAND => $full ? 'Send command' : 'Command',
|
||||
Task::ACTION_BACKUP => $full ? 'Create backup' : 'Files to ignore',
|
||||
Task::ACTION_DELETE_FILES => $full ? 'Delete files' : 'Files to delete',
|
||||
Task::ACTION_POWER => $full ? trans('server/schedule.tasks.actions.power.title') : trans('server/schedule.tasks.actions.power.action'),
|
||||
Task::ACTION_COMMAND => $full ? trans('server/schedule.tasks.actions.command.title') : trans('server/schedule.tasks.actions.command.command'),
|
||||
Task::ACTION_BACKUP => $full ? trans('server/schedule.tasks.actions.backup.title') : trans('server/schedule.tasks.actions.backup.files_to_ignore'),
|
||||
Task::ACTION_DELETE_FILES => $full ? trans('server/schedule.tasks.actions.delete.title') : trans('server/schedule.tasks.actions.delete.files_to_delete'),
|
||||
];
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ class TasksRelationManager extends RelationManager
|
||||
{
|
||||
return [
|
||||
Select::make('action')
|
||||
->label(trans('server/schedule.tasks.actions.title'))
|
||||
->required()
|
||||
->live()
|
||||
->disableOptionWhen(fn (string $value) => $value === Task::ACTION_BACKUP && $schedule->server->backup_limit === 0)
|
||||
@ -56,27 +57,29 @@ class TasksRelationManager extends RelationManager
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('payload', $state === Task::ACTION_POWER ? 'restart' : null)),
|
||||
Textarea::make('payload')
|
||||
->hidden(fn (Get $get) => $get('action') === Task::ACTION_POWER)
|
||||
->label(fn (Get $get) => $this->getActionOptions(false)[$get('action')] ?? 'Payload'),
|
||||
->label(fn (Get $get) => $this->getActionOptions(false)[$get('action')] ?? trans('server/schedule.tasks.payload')),
|
||||
Select::make('payload')
|
||||
->visible(fn (Get $get) => $get('action') === Task::ACTION_POWER)
|
||||
->label('Power Action')
|
||||
->label(trans('server/schedule.tasks.actions.power.action'))
|
||||
->required()
|
||||
->options([
|
||||
'start' => 'Start',
|
||||
'restart' => 'Restart',
|
||||
'stop' => 'Stop',
|
||||
'kill' => 'Kill',
|
||||
'start' => trans('server/schedule.tasks.actions.power.start'),
|
||||
'restart' => trans('server/schedule.tasks.actions.power.restart'),
|
||||
'stop' => trans('server/schedule.tasks.actions.power.stop'),
|
||||
'kill' => trans('server/schedule.tasks.actions.power.kill'),
|
||||
])
|
||||
->selectablePlaceholder(false)
|
||||
->default('restart'),
|
||||
TextInput::make('time_offset')
|
||||
->label(trans('server/schedule.tasks.time_offset'))
|
||||
->hidden(fn (Get $get) => config('queue.default') === 'sync' || $get('sequence_id') === 1)
|
||||
->default(0)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->maxValue(900)
|
||||
->suffix('Seconds'),
|
||||
Toggle::make('continue_on_failure'),
|
||||
->suffix(trans('server/schedule.tasks.seconds')),
|
||||
Toggle::make('continue_on_failure')
|
||||
->label(trans('server/schedule.tasks.continue_on_failure')),
|
||||
];
|
||||
}
|
||||
|
||||
@ -93,17 +96,21 @@ class TasksRelationManager extends RelationManager
|
||||
->defaultSort('sequence_id')
|
||||
->columns([
|
||||
TextColumn::make('action')
|
||||
->label(trans('server/schedule.tasks.actions.title'))
|
||||
->state(fn (Task $task) => $this->getActionOptions()[$task->action] ?? $task->action),
|
||||
TextColumn::make('payload')
|
||||
->label(trans('server/schedule.tasks.payload'))
|
||||
->state(fn (Task $task) => match ($task->payload) {
|
||||
'start', 'restart', 'stop', 'kill' => mb_ucfirst($task->payload),
|
||||
default => explode(PHP_EOL, $task->payload)
|
||||
})
|
||||
->badge(),
|
||||
TextColumn::make('time_offset')
|
||||
->label(trans('server/schedule.tasks.time_offset'))
|
||||
->hidden(fn () => config('queue.default') === 'sync')
|
||||
->suffix(' Seconds'),
|
||||
->suffix(' '. trans('server/schedule.tasks.seconds')),
|
||||
IconColumn::make('continue_on_failure')
|
||||
->label(trans('server/schedule.tasks.continue_on_failure'))
|
||||
->boolean(),
|
||||
])
|
||||
->recordActions([
|
||||
@ -139,7 +146,7 @@ class TasksRelationManager extends RelationManager
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->createAnother(false)
|
||||
->label(fn () => $schedule->tasks()->count() >= config('panel.client_features.schedules.per_schedule_task_limit', 10) ? 'Task Limit Reached' : 'Create Task')
|
||||
->label(fn () => $schedule->tasks()->count() >= config('panel.client_features.schedules.per_schedule_task_limit', 10) ? trans('server/schedule.tasks.limit') : trans('server/schedule.tasks.create'))
|
||||
->disabled(fn () => $schedule->tasks()->count() >= config('panel.client_features.schedules.per_schedule_task_limit', 10))
|
||||
->schema($this->getTaskForm($schedule))
|
||||
->action(function ($data) use ($schedule) {
|
||||
|
@ -97,14 +97,14 @@ class UserResource extends Resource
|
||||
|
||||
foreach ($data['permissions'] as $permission) {
|
||||
$options[$permission] = str($permission)->headline();
|
||||
$descriptions[$permission] = trans('server/users.permissions.' . $data['name'] . '_' . str($permission)->replace('-', '_'));
|
||||
$descriptions[$permission] = trans('server/user.permissions.' . $data['name'] . '_' . str($permission)->replace('-', '_'));
|
||||
$permissionsArray[$data['name']][] = $permission;
|
||||
}
|
||||
|
||||
$tabs[] = Tab::make(str($data['name'])->headline())
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.' . $data['name'] . '_desc'))
|
||||
->description(trans('server/user.permissions.' . $data['name'] . '_desc'))
|
||||
->icon($data['icon'])
|
||||
->schema([
|
||||
CheckboxList::make($data['name'])
|
||||
@ -127,30 +127,33 @@ class UserResource extends Resource
|
||||
->alignCenter()->circular()
|
||||
->defaultImageUrl(fn (User $user) => Filament::getUserAvatarUrl($user)),
|
||||
TextColumn::make('username')
|
||||
->label(trans('server/user.username'))
|
||||
->searchable(),
|
||||
TextColumn::make('email')
|
||||
->label(trans('server/user.email'))
|
||||
->searchable(),
|
||||
TextColumn::make('permissions')
|
||||
->label(trans('server/user.permissions.title'))
|
||||
->state(fn (User $user) => count($server->subusers->where('user_id', $user->id)->first()->permissions)),
|
||||
])
|
||||
->recordActions([
|
||||
DeleteAction::make()
|
||||
->label('Remove User')
|
||||
->label(trans('server/user.delete'))
|
||||
->hidden(fn (User $user) => auth()->user()->id === $user->id)
|
||||
->action(function (User $user, SubuserDeletionService $subuserDeletionService) use ($server) {
|
||||
$subuser = $server->subusers->where('user_id', $user->id)->first();
|
||||
$subuserDeletionService->handle($subuser, $server);
|
||||
|
||||
Notification::make()
|
||||
->title('User Deleted!')
|
||||
->title(trans('server/user.notification_delete'))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
EditAction::make()
|
||||
->label('Edit User')
|
||||
->label(trans('server/user.edit'))
|
||||
->hidden(fn (User $user) => auth()->user()->id === $user->id)
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_USER_UPDATE, $server))
|
||||
->modalHeading(fn (User $user) => 'Editing ' . $user->email)
|
||||
->modalHeading(fn (User $user) => trans('server/user.editing', ['user' => $user->email]))
|
||||
->action(function (array $data, SubuserUpdateService $subuserUpdateService, User $user) use ($server) {
|
||||
$subuser = $server->subusers->where('user_id', $user->id)->first();
|
||||
|
||||
@ -164,7 +167,7 @@ class UserResource extends Resource
|
||||
$subuserUpdateService->handle($subuser, $server, $permissions);
|
||||
|
||||
Notification::make()
|
||||
->title('User Updated!')
|
||||
->title(trans('server/user.notification_edit'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
@ -191,7 +194,7 @@ class UserResource extends Resource
|
||||
]),
|
||||
Actions::make([
|
||||
Action::make('assignAll')
|
||||
->label('Assign All')
|
||||
->label(trans('server/user.assign_all'))
|
||||
->action(function (Set $set) use ($permissionsArray) {
|
||||
$permissions = $permissionsArray;
|
||||
foreach ($permissions as $key => $value) {
|
||||
@ -331,4 +334,9 @@ class UserResource extends Resource
|
||||
'index' => ListUsers::route('/'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('server/user.title');
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
@ -19,11 +21,134 @@ class ListUsers extends ListRecords
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$tabs = [];
|
||||
$permissionsArray = [];
|
||||
|
||||
foreach (Permission::permissionData() as $data) {
|
||||
$options = [];
|
||||
$descriptions = [];
|
||||
|
||||
foreach ($data['permissions'] as $permission) {
|
||||
$options[$permission] = str($permission)->headline();
|
||||
$descriptions[$permission] = trans('server/user.permissions.' . $data['name'] . '_' . str($permission)->replace('-', '_'));
|
||||
$permissionsArray[$data['name']][] = $permission;
|
||||
}
|
||||
|
||||
$tabs[] = Tab::make(str($data['name'])->headline())
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/user.permissions.' . $data['name'] . '_desc'))
|
||||
->icon($data['icon'])
|
||||
->schema([
|
||||
CheckboxList::make($data['name'])
|
||||
->label('')
|
||||
->bulkToggleable()
|
||||
->columns(2)
|
||||
->options($options)
|
||||
->descriptions($descriptions),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
Actions\CreateAction::make('invite')
|
||||
->hiddenLabel()->iconButton()->iconSize(IconSize::Large)
|
||||
->icon('tabler-user-plus')
|
||||
->tooltip(trans('server/user.invite_user'))
|
||||
->createAnother(false)
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_USER_CREATE, $server))
|
||||
->form([
|
||||
Grid::make()
|
||||
->columnSpanFull()
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 5,
|
||||
'lg' => 6,
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('email')
|
||||
->label(trans('server/user.email'))
|
||||
->email()
|
||||
->inlineLabel()
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 4,
|
||||
'lg' => 5,
|
||||
])
|
||||
->required(),
|
||||
assignAll::make([
|
||||
Action::make('assignAll')
|
||||
->label(trans('server/user.assign_all'))
|
||||
->action(function (Set $set, Get $get) use ($permissionsArray) {
|
||||
$permissions = $permissionsArray;
|
||||
foreach ($permissions as $key => $value) {
|
||||
$allValues = array_unique($value);
|
||||
$set($key, $allValues);
|
||||
}
|
||||
}),
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
Tabs::make()
|
||||
->columnSpanFull()
|
||||
->schema($tabs),
|
||||
]),
|
||||
])
|
||||
->modalHeading(trans('server/user.invite_user'))
|
||||
->modalSubmitActionLabel(trans('server/user.action'))
|
||||
->action(function (array $data, SubuserCreationService $service) use ($server) {
|
||||
$email = strtolower($data['email']);
|
||||
|
||||
$permissions = collect($data)
|
||||
->forget('email')
|
||||
->flatMap(fn ($permissions, $key) => collect($permissions)->map(fn ($permission) => "$key.$permission"))
|
||||
->push(Permission::ACTION_WEBSOCKET_CONNECT)
|
||||
->unique()
|
||||
->all();
|
||||
|
||||
try {
|
||||
$subuser = $service->handle($server, $email, $permissions);
|
||||
|
||||
Activity::event('server:subuser.create')
|
||||
->subject($subuser->user)
|
||||
->property([
|
||||
'email' => $data['email'],
|
||||
'permissions' => $permissions,
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/user.notification_add'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('server/user.notification_failed'))
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
|
||||
return redirect(self::getUrl(tenant: $server));
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return trans('server/user.title');
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,6 @@ class ServerCpuChart extends ChartWidget
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return 'CPU';
|
||||
return trans('server/console.labels.cpu');
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,6 @@ class ServerMemoryChart extends ChartWidget
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return 'Memory';
|
||||
return trans('server/console.labels.memory');
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,6 @@ class ServerNetworkChart extends ChartWidget
|
||||
{
|
||||
$lastData = collect(cache()->get("servers.{$this->server->id}.network"))->last();
|
||||
|
||||
return 'Network - ↓' . convert_bytes_to_readable($lastData->rx_bytes ?? 0) . ' - ↑' . convert_bytes_to_readable($lastData->tx_bytes ?? 0);
|
||||
return trans('server/console.labels.network') . ' - ↓' . convert_bytes_to_readable($lastData->rx_bytes ?? 0) . ' - ↑' . convert_bytes_to_readable($lastData->tx_bytes ?? 0);
|
||||
}
|
||||
}
|
||||
|
@ -20,14 +20,14 @@ class ServerOverview extends StatsOverviewWidget
|
||||
protected function getStats(): array
|
||||
{
|
||||
return [
|
||||
SmallStatBlock::make('Name', $this->server->name)
|
||||
SmallStatBlock::make(trans('server/console.labels.name'), $this->server->name)
|
||||
->copyOnClick(fn () => request()->isSecure()),
|
||||
SmallStatBlock::make('Status', $this->status()),
|
||||
SmallStatBlock::make('Address', $this->server?->allocation->address ?? 'None')
|
||||
SmallStatBlock::make(trans('server/console.labels.status'), $this->status()),
|
||||
SmallStatBlock::make(trans('server/console.labels.address'), $this->server?->allocation->address ?? 'None')
|
||||
->copyOnClick(fn () => request()->isSecure()),
|
||||
SmallStatBlock::make('CPU', $this->cpuUsage()),
|
||||
SmallStatBlock::make('Memory', $this->memoryUsage()),
|
||||
SmallStatBlock::make('Disk', $this->diskUsage()),
|
||||
SmallStatBlock::make(trans('server/console.labels.cpu'), $this->cpuUsage()),
|
||||
SmallStatBlock::make(trans('server/console.labels.memory'), $this->memoryUsage()),
|
||||
SmallStatBlock::make(trans('server/console.labels.disk'), $this->diskUsage()),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ use App\Http\Controllers\Api\Client\ClientApiController;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use App\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
|
||||
use App\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest;
|
||||
use App\Http\Requests\Api\Client\Servers\Backups\RenameBackupRequest;
|
||||
use Dedoc\Scramble\Attributes\Group;
|
||||
|
||||
#[Group('Server - Backup')]
|
||||
@ -198,6 +199,35 @@ class BackupController extends ClientApiController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename backup
|
||||
*
|
||||
* Updates the name of a backup for a server instance.
|
||||
*
|
||||
* @return array<array-key, mixed>
|
||||
*
|
||||
* @throws \Throwable
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function rename(RenameBackupRequest $request, Server $server, Backup $backup): array
|
||||
{
|
||||
$oldName = $backup->name;
|
||||
$newName = $request->input('name');
|
||||
|
||||
$backup->update(['name' => $newName]);
|
||||
|
||||
if ($oldName !== $newName) {
|
||||
Activity::event('server:backup.rename')
|
||||
->subject($backup)
|
||||
->property(['old_name' => $oldName, 'new_name' => $newName])
|
||||
->log();
|
||||
}
|
||||
|
||||
return $this->fractal->item($backup)
|
||||
->transformWith($this->getTransformer(BackupTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore backup
|
||||
*
|
||||
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Client\Servers\Backups;
|
||||
|
||||
use App\Models\Permission;
|
||||
use App\Http\Requests\Api\Client\ClientApiRequest;
|
||||
|
||||
class RenameBackupRequest extends ClientApiRequest
|
||||
{
|
||||
public function permission(): string
|
||||
{
|
||||
return Permission::ACTION_BACKUP_DELETE;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
@ -28,14 +28,14 @@ class ProcessWebhook implements ShouldQueue
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$data = $this->data[0];
|
||||
$data = $this->data[0] ?? [];
|
||||
if (count($data) === 1) {
|
||||
$data = reset($data);
|
||||
}
|
||||
$data = is_array($data) ? $data : (json_decode($data, true) ?? []);
|
||||
$data['event'] = $this->webhookConfiguration->transformClassName($this->eventName);
|
||||
|
||||
if ($this->webhookConfiguration->type === WebhookType::Discord) {
|
||||
$data = array_merge(
|
||||
is_array($data) ? $data : json_decode($data, true),
|
||||
['event' => $this->webhookConfiguration->transformClassName($this->eventName)]
|
||||
);
|
||||
|
||||
$payload = json_encode($this->webhookConfiguration->payload);
|
||||
$tmp = $this->webhookConfiguration->replaceVars($data, $payload);
|
||||
$data = json_decode($tmp, true);
|
||||
@ -53,9 +53,10 @@ class ProcessWebhook implements ShouldQueue
|
||||
}
|
||||
|
||||
try {
|
||||
$customHeaders = $this->webhookConfiguration->headers;
|
||||
$headers = [];
|
||||
if ($this->webhookConfiguration->type === WebhookType::Regular && $customHeaders = $this->webhookConfiguration->headers) {
|
||||
$headers = array_merge(['X-Webhook-Event', $this->eventName], $customHeaders);
|
||||
foreach ($customHeaders as $key => $value) {
|
||||
$headers[$key] = $this->webhookConfiguration->replaceVars($data, $value);
|
||||
}
|
||||
|
||||
Http::withHeaders($headers)->post($this->webhookConfiguration->endpoint, $data)->throw();
|
||||
|
@ -2,31 +2,34 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Filament\Notifications\Concerns\HasBody;
|
||||
use Filament\Notifications\Concerns\HasIcon;
|
||||
use Filament\Notifications\Concerns\HasId;
|
||||
use Filament\Notifications\Concerns\HasStatus;
|
||||
use Filament\Notifications\Concerns\HasTitle;
|
||||
use Closure;
|
||||
use Filament\Support\Concerns\EvaluatesClosures;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Wireable;
|
||||
use Filament\Notifications\Concerns;
|
||||
use Filament\Support\Components\ViewComponent;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
final class AlertBanner implements Wireable
|
||||
final class AlertBanner extends ViewComponent implements Arrayable
|
||||
{
|
||||
use EvaluatesClosures;
|
||||
use HasBody;
|
||||
use HasIcon;
|
||||
use HasId;
|
||||
use HasStatus;
|
||||
use HasTitle;
|
||||
use Concerns\HasBody;
|
||||
use Concerns\HasIcon;
|
||||
use Concerns\HasId;
|
||||
use Concerns\HasStatus;
|
||||
use Concerns\HasTitle;
|
||||
|
||||
protected bool|Closure $closable = false;
|
||||
|
||||
public static function make(?string $id = null): AlertBanner
|
||||
protected string $view = 'livewire.alerts.alert-banner';
|
||||
|
||||
protected string $viewIdentifier = 'alert-banner';
|
||||
|
||||
public function __construct(string $id)
|
||||
{
|
||||
$static = new self();
|
||||
$static->id($id ?? Str::orderedUuid());
|
||||
$this->id($id);
|
||||
}
|
||||
|
||||
public static function make(string $id): AlertBanner
|
||||
{
|
||||
$static = new self($id);
|
||||
$static->configure();
|
||||
|
||||
return $static;
|
||||
}
|
||||
@ -34,7 +37,7 @@ final class AlertBanner implements Wireable
|
||||
/**
|
||||
* @return array{id: string, title: ?string, body: ?string, status: ?string, icon: ?string, closeable: bool}
|
||||
*/
|
||||
public function toLivewire(): array
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
@ -46,15 +49,18 @@ final class AlertBanner implements Wireable
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromLivewire(mixed $value): AlertBanner
|
||||
/**
|
||||
* @param array{id: string, title: ?string, body: ?string, status: ?string, icon: ?string, closeable: bool} $data
|
||||
*/
|
||||
public static function fromArray(array $data): AlertBanner
|
||||
{
|
||||
$static = AlertBanner::make($value['id']);
|
||||
$static = AlertBanner::make($data['id']);
|
||||
|
||||
$static->title($value['title']);
|
||||
$static->body($value['body']);
|
||||
$static->status($value['status']);
|
||||
$static->icon($value['icon']);
|
||||
$static->closable($value['closeable']);
|
||||
$static->title($data['title']);
|
||||
$static->body($data['body']);
|
||||
$static->status($data['status']);
|
||||
$static->icon($data['icon']);
|
||||
$static->closable($data['closeable']);
|
||||
|
||||
return $static;
|
||||
}
|
||||
@ -73,7 +79,7 @@ final class AlertBanner implements Wireable
|
||||
|
||||
public function send(): AlertBanner
|
||||
{
|
||||
session()->push('alert-banners', $this->toLivewire());
|
||||
session()->push('alert-banners', $this->toArray());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -2,18 +2,18 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Filament\Notifications\Collection;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class AlertBannerContainer extends Component
|
||||
{
|
||||
/** @var array<AlertBanner> */
|
||||
public array $alertBanners;
|
||||
public Collection $alertBanners;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->alertBanners = [];
|
||||
$this->alertBanners = new Collection();
|
||||
$this->pullFromSession();
|
||||
}
|
||||
|
||||
@ -21,15 +21,16 @@ class AlertBannerContainer extends Component
|
||||
public function pullFromSession(): void
|
||||
{
|
||||
foreach (session()->pull('alert-banners', []) as $alertBanner) {
|
||||
$alertBanner = AlertBanner::fromLivewire($alertBanner);
|
||||
$this->alertBanners[$alertBanner->getId()] = $alertBanner;
|
||||
$alertBanner = AlertBanner::fromArray($alertBanner);
|
||||
$this->alertBanners->put($alertBanner->getId(), $alertBanner);
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(string $id): void
|
||||
{
|
||||
$alertBanners = &$this->alertBanners;
|
||||
unset($alertBanners[$id]);
|
||||
if ($this->alertBanners->has($id)) {
|
||||
$this->alertBanners->forget($id);
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
|
@ -24,37 +24,40 @@ class ServerEntry extends Component
|
||||
style="background-color: #D97706;">
|
||||
</div>
|
||||
|
||||
<div class="flex-1 dark:bg-gray-850 dark:text-white rounded-lg overflow-hidden p-2">
|
||||
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-lg overflow-hidden p-3">
|
||||
<div class="flex items-center mb-5 gap-2">
|
||||
<x-filament::loading-indicator class="h-5 w-5" />
|
||||
<x-filament::loading-indicator class="h-6 w-6" />
|
||||
<h2 class="text-xl font-bold">
|
||||
{{ $server->name }}
|
||||
<span class="dark:text-gray-400">
|
||||
({{ trans('server/dashboard.loading') }})
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between text-center">
|
||||
<div class="flex justify-between text-center items-center gap-4">
|
||||
<div>
|
||||
<p class="text-sm dark:text-gray-400">CPU</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.cpu') }}</p>
|
||||
<p class="text-md font-semibold">{{ Number::format(0, precision: 2, locale: auth()->user()->language ?? 'en') . '%' }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('cpu', type: \App\Enums\ServerResourceType::Percentage, limit: true) }}</p>
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource(\App\Enums\ServerResourceType::CPULimit) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm dark:text-gray-400">Memory</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.memory') }}</p>
|
||||
<p class="text-md font-semibold">{{ convert_bytes_to_readable(0, decimals: 2) }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('memory', limit: true) }}</p>
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource(\App\Enums\ServerResourceType::MemoryLimit) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm dark:text-gray-400">Disk</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.disk') }}</p>
|
||||
<p class="text-md font-semibold">{{ convert_bytes_to_readable(0, decimals: 2) }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('disk', limit: true) }}</p>
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource(\App\Enums\ServerResourceType::DiskLimit) }}</p>
|
||||
</div>
|
||||
<div class="hidden sm:block">
|
||||
<p class="text-sm dark:text-gray-400">Network</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.network') }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-md font-semibold">{{ $server->allocation?->address ?? 'None' }} </p>
|
||||
<p class="text-md font-semibold">{{ $server->allocation?->address ?? trans('server/dashboard.none') }} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -198,7 +198,7 @@ class File extends Model
|
||||
$message = str('Node connection failed');
|
||||
}
|
||||
|
||||
AlertBanner::make()
|
||||
AlertBanner::make('files_node_error')
|
||||
->title('Could not load files!')
|
||||
->body($message->toString())
|
||||
->danger()
|
||||
|
@ -468,17 +468,15 @@ class Server extends Model implements Validatable
|
||||
});
|
||||
}
|
||||
|
||||
public function formatResource(string $resourceKey, bool $limit = false, ServerResourceType $type = ServerResourceType::Unit, int $precision = 2): string
|
||||
public function formatResource(ServerResourceType $resourceType): string
|
||||
{
|
||||
$resourceAmount = $this->{$resourceKey} ?? 0;
|
||||
if (!$limit) {
|
||||
$resourceAmount = $this->retrieveResources()[$resourceKey] ?? 0;
|
||||
}
|
||||
$resourceAmount = $resourceType->getResourceAmount($this);
|
||||
|
||||
if ($type === ServerResourceType::Time) {
|
||||
if ($this->isSuspended()) {
|
||||
return 'Suspended';
|
||||
if ($resourceType->isTime()) {
|
||||
if (!is_null($this->status)) {
|
||||
return $this->status->getLabel();
|
||||
}
|
||||
|
||||
if ($resourceAmount === 0) {
|
||||
return ContainerStatus::Offline->getLabel();
|
||||
}
|
||||
@ -486,20 +484,16 @@ class Server extends Model implements Validatable
|
||||
return now()->subMillis($resourceAmount)->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE, short: true, parts: 4);
|
||||
}
|
||||
|
||||
if ($resourceAmount === 0 & $limit) {
|
||||
if ($resourceAmount === 0 & $resourceType->isLimit()) {
|
||||
// Unlimited symbol
|
||||
return "\u{221E}";
|
||||
}
|
||||
|
||||
if ($type === ServerResourceType::Percentage) {
|
||||
return Number::format($resourceAmount, precision: $precision, locale: auth()->user()->language ?? 'en') . '%';
|
||||
if ($resourceType->isPercentage()) {
|
||||
return Number::format($resourceAmount, precision: 2, locale: auth()->user()->language ?? 'en') . '%';
|
||||
}
|
||||
|
||||
// Our current limits are set in MB
|
||||
if ($limit) {
|
||||
$resourceAmount *= 2 ** 20;
|
||||
}
|
||||
|
||||
return convert_bytes_to_readable($resourceAmount, decimals: $precision, base: 3);
|
||||
return convert_bytes_to_readable($resourceAmount, base: 3);
|
||||
}
|
||||
|
||||
public function condition(): Attribute
|
||||
|
@ -48,7 +48,7 @@ class AppPanelProvider extends PanelProvider
|
||||
->userMenuItems([
|
||||
'profile' => fn (Action $action) => $action->label(auth()->user()->username),
|
||||
Action::make('toAdmin')
|
||||
->label('Admin')
|
||||
->label(trans('profile.admin'))
|
||||
->url('/admin')
|
||||
->icon('tabler-arrow-forward')
|
||||
->sort(5)
|
||||
|
@ -59,14 +59,14 @@ class ServerPanelProvider extends PanelProvider
|
||||
->url(fn () => ListServers::getUrl(panel: 'app'))
|
||||
->sort(6),
|
||||
Action::make('toAdmin')
|
||||
->label('Admin')
|
||||
->label(trans('profile.admin'))
|
||||
->icon('tabler-arrow-forward')
|
||||
->url(fn () => Filament::getPanel('admin')->getUrl())
|
||||
->sort(5)
|
||||
->visible(fn (): bool => auth()->user()->canAccessPanel(Filament::getPanel('admin'))),
|
||||
])
|
||||
->navigationItems([
|
||||
NavigationItem::make('Open in Admin')
|
||||
NavigationItem::make(trans('server/console.open_in_admin'))
|
||||
->url(fn () => EditServer::getUrl(['record' => Filament::getTenant()], panel: 'admin'))
|
||||
->visible(fn () => auth()->user()->canAccessPanel(Filament::getPanel('admin')) && auth()->user()->can('view server', Filament::getTenant()))
|
||||
->icon('tabler-arrow-back')
|
||||
|
@ -58,6 +58,7 @@ return [
|
||||
'fail' => 'Marked the <b>:name</b> backup as failed',
|
||||
'lock' => 'Locked the <b>:name</b> backup',
|
||||
'unlock' => 'Unlocked the <b>:name</b> backup',
|
||||
'rename' => 'Renamed backup from "<b>:old_name</b>" to "<b>:new_name</b>"',
|
||||
],
|
||||
'database' => [
|
||||
'create' => 'Created new database <b>:name</b>',
|
||||
|
@ -19,6 +19,7 @@ return [
|
||||
'headers' => 'Headers',
|
||||
'events' => 'Events',
|
||||
'regular' => 'Regular',
|
||||
'reset_headers' => 'Reset Headers',
|
||||
'discord' => 'Discord',
|
||||
'discord_message' => [
|
||||
'profile' => 'Profile',
|
||||
|
@ -12,6 +12,7 @@ return [
|
||||
'customization' => 'Customization',
|
||||
],
|
||||
'username' => 'Username',
|
||||
'admin' => 'Admin',
|
||||
'exit_admin' => 'Exit Admin',
|
||||
'email' => 'Email',
|
||||
'password' => 'Password',
|
||||
|
11
lang/en/server/activity.php
Normal file
11
lang/en/server/activity.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Activity',
|
||||
'event' => 'Event',
|
||||
'user' => 'User',
|
||||
'deleted_user' => 'Deleted User',
|
||||
'system' => 'System',
|
||||
'timestamp' => 'Timestamp',
|
||||
'metadata' => 'Metadata',
|
||||
];
|
50
lang/en/server/backup.php
Normal file
50
lang/en/server/backup.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Backups',
|
||||
'empty' => 'No Backups',
|
||||
'size' => 'Size',
|
||||
'created_at' => 'Created at',
|
||||
'status' => 'Status',
|
||||
'is_locked' => 'Lock Status',
|
||||
'backup_status' => [
|
||||
'in_progress' => 'In Progress',
|
||||
'successful' => 'Successful',
|
||||
'failed' => 'Failed',
|
||||
],
|
||||
'actions' => [
|
||||
'create' => [
|
||||
'title' => 'Create Backup',
|
||||
'limit' => 'Backup Limit Reached',
|
||||
'created' => ':name created',
|
||||
'notification_success' => 'Backup Created Successfully',
|
||||
'notification_fail' => 'Backup Creation Failed',
|
||||
'name' => 'Name',
|
||||
'ignored' => 'Ignored Files & Directories',
|
||||
'locked' => 'Locked?',
|
||||
'lock_helper' => 'Prevents this backup from being deleted until explicitly unlocked.',
|
||||
],
|
||||
'lock' => [
|
||||
'lock' => 'Lock',
|
||||
'unlock' => 'Unlock',
|
||||
],
|
||||
'download' => 'Download',
|
||||
'restore' => [
|
||||
'title' => 'Restore',
|
||||
'helper' => 'Your server will be stopped. You will not be able to control the power state, access the file manager, or create additional backups until this process is completed.',
|
||||
'delete_all' => 'Delete all files before restoring backup?',
|
||||
'notification_started' => 'Restoring Backup',
|
||||
'notification_success' => 'Backup Restored Successfully',
|
||||
'notification_fail' => 'Backup Restore Failed',
|
||||
'notification_fail_body_1' => 'This server is not currently in a state that allows for a backup to be restored.',
|
||||
'notification_fail_body_2' => 'This backup cannot be restored at this time: not completed or failed.',
|
||||
],
|
||||
'delete' => [
|
||||
'title' => 'Delete Backup',
|
||||
'description' => 'Do you wish to delete :backup?',
|
||||
'notification_success' => 'Backup Deleted',
|
||||
'notification_fail' => 'Could not delete backup',
|
||||
'notification_fail_body' => 'Connection to node failed. Please try again.',
|
||||
],
|
||||
],
|
||||
];
|
39
lang/en/server/console.php
Normal file
39
lang/en/server/console.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Console',
|
||||
'command' => 'Type a command...',
|
||||
'command_blocked' => 'Server Offline...',
|
||||
'command_blocked_title' => 'Can\'t send command when the server is Offline',
|
||||
'open_in_admin' => 'Open in Admin',
|
||||
'power_actions' => [
|
||||
'start' => 'Start',
|
||||
'stop' => 'Stop',
|
||||
'restart' => 'Restart',
|
||||
'kill' => 'Kill',
|
||||
'kill_tooltip' => 'This can result in data corruption and/or data loss!',
|
||||
],
|
||||
'labels' => [
|
||||
'cpu' => 'CPU',
|
||||
'memory' => 'Memory',
|
||||
'network' => 'Network',
|
||||
'disk' => 'Disk',
|
||||
'name' => 'Name',
|
||||
'status' => 'Status',
|
||||
'address' => 'Address',
|
||||
'unavailable' => 'Unavailable',
|
||||
],
|
||||
'status' => [
|
||||
'created' => 'Created',
|
||||
'starting' => 'Starting',
|
||||
'running' => 'Running',
|
||||
'restarting' => 'Restarting',
|
||||
'exited' => 'Exited',
|
||||
'paused' => 'Paused',
|
||||
'dead' => 'Dead',
|
||||
'removing' => 'Removing',
|
||||
'stopping' => 'Stopping',
|
||||
'offline' => 'Offline',
|
||||
'missing' => 'Missing',
|
||||
],
|
||||
];
|
25
lang/en/server/dashboard.php
Normal file
25
lang/en/server/dashboard.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Servers',
|
||||
'list' => 'Server List',
|
||||
'my_servers' => 'My Servers',
|
||||
'other_servers' => 'Others\' Servers',
|
||||
'all_servers' => 'All Servers',
|
||||
'empty_own' => 'You don\'t own any servers!',
|
||||
'empty_other' => 'You don\'t have access to any servers!',
|
||||
|
||||
'status' => 'Status',
|
||||
'server' => 'Server',
|
||||
'resource' => 'Resource',
|
||||
'usage_limit' => 'Usage Limit: :resource',
|
||||
|
||||
'cpu' => 'CPU',
|
||||
'memory' => 'Memory',
|
||||
'disk' => 'Disk',
|
||||
'network' => 'Network',
|
||||
'none' => 'None',
|
||||
'loading' => 'Loading...',
|
||||
|
||||
'power_actions' => 'Power Actions',
|
||||
];
|
21
lang/en/server/database.php
Normal file
21
lang/en/server/database.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Databases',
|
||||
'create_database' => 'Create Database',
|
||||
'limit' => 'Database limit reached',
|
||||
'viewing' => 'Viewing: :database',
|
||||
'host' => 'Host',
|
||||
'database' => 'Database',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'remote' => 'Remote',
|
||||
'created_at' => 'Created at',
|
||||
'name' => 'Database Name',
|
||||
'name_hint' => 'Leaving this blank will auto generate a random name',
|
||||
'connections_from' => 'Connections From',
|
||||
'max_connections' => 'Max Connections',
|
||||
'database_host' => 'Database Host',
|
||||
'database_host_select' => 'Select Database Host',
|
||||
'jdbc' => 'JDBC Connection String',
|
||||
];
|
82
lang/en/server/file.php
Normal file
82
lang/en/server/file.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Files',
|
||||
'name' => 'Name',
|
||||
'size' => 'Size',
|
||||
'modified_at' => 'Modified at',
|
||||
'actions' => [
|
||||
'open' => 'Open',
|
||||
'download' => 'Download',
|
||||
'copy' => [
|
||||
'title' => 'Copy',
|
||||
'notification' => 'File Copied',
|
||||
],
|
||||
'upload' => [
|
||||
'title' => 'Upload',
|
||||
'from_files' => 'Upload Files',
|
||||
'from_url' => 'Upload from URL',
|
||||
'url' => 'URL',
|
||||
],
|
||||
'rename' => [
|
||||
'title' => 'Rename',
|
||||
'file_name' => 'File Name',
|
||||
'notification' => 'File Renamed',
|
||||
],
|
||||
'move' => [
|
||||
'title' => 'Move',
|
||||
'directory' => 'Directory',
|
||||
'directory_hint' => 'Enter the new directory, relative to the current directory.',
|
||||
'new_location' => 'New Location',
|
||||
'new_location_hint' => 'Enter the location of this file or folder, relative to the current directory.',
|
||||
'notification' => 'File Moved',
|
||||
'bulk_notification' => ':count Files were moved to :directory',
|
||||
],
|
||||
'permissions' => [
|
||||
'title' => 'Permissions',
|
||||
'read' => 'Read',
|
||||
'write' => 'Write',
|
||||
'execute' => 'Execute',
|
||||
'owner' => 'Owner',
|
||||
'group' => 'Group',
|
||||
'public' => 'Public',
|
||||
'notification' => 'Permissions changed to :mode',
|
||||
],
|
||||
'archive' => [
|
||||
'title' => 'Archive',
|
||||
'archive_name' => 'Archive Name',
|
||||
'notification' => 'Archive Created',
|
||||
],
|
||||
'unarchive' => [
|
||||
'title' => 'Unarchive',
|
||||
'notification' => 'Unarchive Completed',
|
||||
],
|
||||
'new_file' => [
|
||||
'title' => 'New file',
|
||||
'file_name' => 'New file name',
|
||||
'syntax' => 'Syntax Highlighting',
|
||||
'create' => 'Create',
|
||||
],
|
||||
'new_folder' => [
|
||||
'title' => 'New folder',
|
||||
'folder_name' => 'New folder name',
|
||||
],
|
||||
'global_search' => [
|
||||
'title' => 'Global Search',
|
||||
'search_term' => 'Search term',
|
||||
'search_term_placeholder' => 'Enter a search term, ex. *.txt',
|
||||
'search' => 'Search',
|
||||
],
|
||||
'delete' => [
|
||||
'notification' => 'File Deleted',
|
||||
'bulk_notification' => ':count files were deleted',
|
||||
],
|
||||
'edit' => [
|
||||
'title' => 'Editing: :file',
|
||||
'save_close' => 'Save & Close',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'notification' => 'File Saved',
|
||||
],
|
||||
],
|
||||
];
|
15
lang/en/server/network.php
Normal file
15
lang/en/server/network.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Network',
|
||||
'add' => 'Add Allocation',
|
||||
'limit' => 'Allocation limit reached',
|
||||
'address' => 'Address',
|
||||
'port' => 'Port',
|
||||
'notes' => 'Notes',
|
||||
'no_notes' => 'No Notes',
|
||||
'make_primary' => 'Make Primary',
|
||||
'primary' => 'Primary',
|
||||
'make' => 'Make',
|
||||
'delete' => 'Delete',
|
||||
];
|
107
lang/en/server/schedule.php
Normal file
107
lang/en/server/schedule.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Schedules',
|
||||
'new' => 'New Schedule',
|
||||
'edit' => 'Edit Schedule',
|
||||
'save' => 'Save Schedule',
|
||||
'delete' => 'Delete Schedule',
|
||||
'import' => 'Import Schedule',
|
||||
'export' => 'Export Schedule',
|
||||
'name' => 'Name',
|
||||
'cron' => 'Cron',
|
||||
'status' => 'Status',
|
||||
'inactive' => 'Inactive',
|
||||
'processing' => 'Processing',
|
||||
'active' => 'Active',
|
||||
'no_tasks' => 'No Tasks',
|
||||
'run_now' => 'Run Now',
|
||||
'online_only' => 'Only When Online',
|
||||
'last_run' => 'Last Run',
|
||||
'next_run' => 'Next Run',
|
||||
'never' => 'Never',
|
||||
'cancel' => 'Cancel',
|
||||
|
||||
'only_online' => 'Only when Server is Online?',
|
||||
'only_online_hint' => 'Only execute this schedule when the server is in a running state.',
|
||||
'enabled' => 'Enable Schedule?',
|
||||
'enabled_hint' => 'This schedule will be executed automatically if enabled.',
|
||||
|
||||
'cron_body' => 'Please keep in mind that the cron inputs below always assume UTC.',
|
||||
'cron_timezone' => 'Next run in your timezone (:timezone): <b> :next_run </b>',
|
||||
|
||||
'time' => [
|
||||
'minute' => 'Minute',
|
||||
'hour' => 'Hour',
|
||||
'day' => 'Day',
|
||||
'week' => 'Week',
|
||||
'month' => 'Month',
|
||||
'day_of_month' => 'Day of Month',
|
||||
'day_of_week' => 'Day of Week',
|
||||
|
||||
'hourly' => 'Hourly',
|
||||
'daily' => 'Daily',
|
||||
'weekly_mon' => 'Weekly (Monday)',
|
||||
'weekly_sun' => 'Weekly (Sunday)',
|
||||
'monthly' => 'Monthly',
|
||||
'every_min' => 'Every x minutes',
|
||||
'every_hour' => 'Every x hours',
|
||||
'every_day' => 'Every x days',
|
||||
'every_week' => 'Every x weeks',
|
||||
'every_month' => 'Every x months',
|
||||
'every_day_of_week' => 'Every x day of week',
|
||||
|
||||
'every' => 'Every',
|
||||
'minutes' => 'Minutes',
|
||||
'hours' => 'Hours',
|
||||
'days' => 'Days',
|
||||
'months' => 'Months',
|
||||
|
||||
'monday' => 'Monday',
|
||||
'tuesday' => 'Tuesday',
|
||||
'wednesday' => 'Wednesday',
|
||||
'thursday' => 'Thursday',
|
||||
'friday' => 'Friday',
|
||||
'saturday' => 'Saturday',
|
||||
'sunday' => 'Sunday',
|
||||
],
|
||||
|
||||
'tasks' => [
|
||||
'title' => 'Tasks',
|
||||
'create' => 'Create Task',
|
||||
'limit' => 'Task Limit Reached',
|
||||
'action' => 'Action',
|
||||
'payload' => 'Payload',
|
||||
'time_offset' => 'Time Offset',
|
||||
'seconds' => 'Seconds',
|
||||
'continue_on_failure' => 'Continue On Failure',
|
||||
|
||||
'actions' => [
|
||||
'title' => 'Action',
|
||||
'power' => [
|
||||
'title' => 'Send Power Action',
|
||||
'action' => 'Power action',
|
||||
'start' => 'Start',
|
||||
'stop' => 'Stop',
|
||||
'restart' => 'Restart',
|
||||
'kill' => 'Kill',
|
||||
],
|
||||
'command' => [
|
||||
'title' => 'Send Command',
|
||||
'command' => 'Command',
|
||||
],
|
||||
'backup' => [
|
||||
'title' => 'Create Backup',
|
||||
'files_to_ignore' => 'Files to Ignore',
|
||||
],
|
||||
'delete' => [
|
||||
'title' => 'Delete Files',
|
||||
'files_to_delete' => 'Files to Delete',
|
||||
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'notification_invalid_cron' => 'The cron data provided does not evaluate to a valid expression',
|
||||
|
||||
];
|
51
lang/en/server/setting.php
Normal file
51
lang/en/server/setting.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Settings',
|
||||
'server_info' => [
|
||||
'title' => 'Server Information',
|
||||
'information' => 'Information',
|
||||
'name' => 'Server Name',
|
||||
'notification_name' => 'Updated Server Name',
|
||||
'description' => 'Server Description',
|
||||
'notification_description' => 'Updated Server Description',
|
||||
'failed' => 'Failed',
|
||||
'uuid' => 'Server UUID',
|
||||
'id' => 'Server ID',
|
||||
'limits' => [
|
||||
'title' => 'Limits',
|
||||
'unlimited' => 'Unlimited',
|
||||
'of' => 'of',
|
||||
'cpu' => 'CPU',
|
||||
'memory' => 'Memory',
|
||||
'disk' => 'Disk Space',
|
||||
'backups' => 'Backups',
|
||||
'databases' => 'Databases',
|
||||
'allocations' => 'Allocations',
|
||||
'no_allocations' => 'No Additional Allocations',
|
||||
],
|
||||
],
|
||||
'node_info' => [
|
||||
'title' => 'Node Information',
|
||||
'name' => 'Node Name',
|
||||
'sftp' => [
|
||||
'title' => 'SFTP Information',
|
||||
'connection' => 'Connection',
|
||||
'action' => 'Connect to SFTP',
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'password_body' => 'Your SFTP password is the same as the password you use to access this panel.',
|
||||
],
|
||||
],
|
||||
'reinstall' => [
|
||||
'title' => 'Reinstall Server',
|
||||
'body' => 'Reinstalling your server will stop it, and then re-run the installation script that initially set it up.',
|
||||
'body2' => 'Some files may be deleted or modified during this process, please back up your data before continuing.',
|
||||
'action' => 'Reinstall',
|
||||
'modal' => 'Are you sure you want to reinstall the server?',
|
||||
'modal_description' => 'Some files may be deleted or modified during this process, please back up your data before continuing.',
|
||||
'yes' => 'Yes, Reinstall',
|
||||
'notification_start' => 'Reinstall Started',
|
||||
'notification_fail' => 'Reinstall Failed',
|
||||
],
|
||||
];
|
14
lang/en/server/startup.php
Normal file
14
lang/en/server/startup.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Startup',
|
||||
'command' => 'Startup Command',
|
||||
'preview' => 'Preview',
|
||||
'docker_image' => 'Docker Image',
|
||||
'notification_docker' => 'Docker Image Updated',
|
||||
'notification_docker_body' => 'Restart the server to use the new image.',
|
||||
'variables' => 'Server Variables',
|
||||
'update' => 'Updated: :variable',
|
||||
'fail' => 'Failed: :variable',
|
||||
'validation_fail' => 'Validation Failed: :variable',
|
||||
];
|
@ -1,7 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Users',
|
||||
'username' => 'Username',
|
||||
'email' => 'Email',
|
||||
'assign_all' => 'Assign all',
|
||||
'invite_user' => 'Invite User',
|
||||
'action' => 'Invite',
|
||||
'remove' => 'Remove User',
|
||||
'edit' => 'Edit User',
|
||||
'editing' => 'Editing :user',
|
||||
'delete' => 'Delete User',
|
||||
'notification_add' => 'User Invited!',
|
||||
'notification_edit' => 'User Updated!',
|
||||
'notification_delete' => 'User Deleted!',
|
||||
'notification_failed' => 'Failed to invite user!',
|
||||
'permissions' => [
|
||||
'title' => 'Permissions',
|
||||
'activity_desc' => 'Permissions that control a user\'s access to the server activity logs.',
|
||||
'startup_desc' => 'Permissions that control a user\'s ability to view this server\'s startup parameters.',
|
||||
'settings_desc' => 'Permissions that control a user\'s ability to modify this server\'s settings.',
|
@ -30,8 +30,8 @@
|
||||
class="w-full focus:outline-none focus:ring-0 border-none dark:bg-gray-900"
|
||||
type="text"
|
||||
:readonly="{{ $this->canSendCommand() ? 'false' : 'true' }}"
|
||||
title="{{ $this->canSendCommand() ? '' : 'Can\'t send command when the server is Offline' }}"
|
||||
placeholder="{{ $this->canSendCommand() ? 'Type a command...' : 'Server Offline...' }}"
|
||||
title="{{ $this->canSendCommand() ? '' : trans('server/console.command_blocked_title') }}"
|
||||
placeholder="{{ $this->canSendCommand() ? trans('server/console.command') : trans('server/console.command_blocked') }}"
|
||||
wire:model="input"
|
||||
wire:keydown.enter="enter"
|
||||
wire:keydown.up.prevent="up"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div id="alert-banner-container" class="flex flex-col gap-4">
|
||||
@foreach (array_values($alertBanners) as $alertBanner)
|
||||
@include('livewire.alerts.alert-banner', ['alertBanner' => $alertBanner])
|
||||
@foreach ($alertBanners as $alertBanner)
|
||||
{{ $alertBanner }}
|
||||
@endforeach
|
||||
</div>
|
||||
|
@ -1,14 +1,12 @@
|
||||
@props(['alertBanner'])
|
||||
|
||||
@php
|
||||
$icon = $alertBanner->getIcon();
|
||||
$title = $alertBanner->getTitle();
|
||||
$body = $alertBanner->getBody();
|
||||
$icon = $getIcon();
|
||||
$title = $getTitle();
|
||||
$body = $getBody();
|
||||
@endphp
|
||||
|
||||
<div class="{{$alertBanner->getColorClasses()}} flex p-4 mt-3 rounded-xl shadow-lg bg-white dark:bg-gray-900 ring-1 ring-gray-950/5 dark:ring-white/10">
|
||||
<div class="{{$getColorClasses()}} flex p-4 mt-3 rounded-xl shadow-lg bg-white dark:bg-gray-900 ring-1 ring-gray-950/5 dark:ring-white/10">
|
||||
@if (filled($icon))
|
||||
<x-filament::icon :icon="$icon" class="h-8 w-8 mr-2" color="{{$alertBanner->getStatus()}}" />
|
||||
<x-filament::icon :icon="$icon" class="h-8 w-8 mr-2" color="{{$getStatus()}}" />
|
||||
@endif
|
||||
|
||||
<div class="flex flex-col flex-grow">
|
||||
@ -21,7 +19,7 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($alertBanner->isCloseable())
|
||||
<x-filament::icon-button color="gray" icon="tabler-x" wire:click="remove('{{$alertBanner->getID()}}')" />
|
||||
@if ($isCloseable())
|
||||
<x-filament::icon-button color="gray" icon="tabler-x" wire:click="remove('{{$getID()}}')" />
|
||||
@endif
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<h2 class="text-xl font-bold">
|
||||
{{ $server->name }}
|
||||
<span class="dark:text-gray-400">
|
||||
({{ $server->formatResource('uptime', type: \App\Enums\ServerResourceType::Time) }})
|
||||
({{ $server->formatResource(\App\Enums\ServerResourceType::Uptime) }})
|
||||
</span>
|
||||
</h2>
|
||||
<div class="end-0" x-on:click.stop>
|
||||
@ -33,27 +33,27 @@
|
||||
|
||||
<div class="flex justify-between text-center items-center gap-4">
|
||||
<div>
|
||||
<p class="text-sm dark:text-gray-400">CPU</p>
|
||||
<p class="text-md font-semibold">{{ $server->formatResource('cpu_absolute', type: \App\Enums\ServerResourceType::Percentage) }}</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.cpu') }}</p>
|
||||
<p class="text-md font-semibold">{{ $server->formatResource(\App\Enums\ServerResourceType::CPU) }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('cpu', limit: true, type: \App\Enums\ServerResourceType::Percentage) }}</p>
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource(\App\Enums\ServerResourceType::CPULimit) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm dark:text-gray-400">Memory</p>
|
||||
<p class="text-md font-semibold">{{ $server->formatResource('memory_bytes') }}</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.memory') }}</p>
|
||||
<p class="text-md font-semibold">{{ $server->formatResource(\App\Enums\ServerResourceType::Memory) }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('memory', limit: true) }}</p>
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource(\App\Enums\ServerResourceType::MemoryLimit) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm dark:text-gray-400">Disk</p>
|
||||
<p class="text-md font-semibold">{{ $server->formatResource('disk_bytes') }}</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.disk') }}</p>
|
||||
<p class="text-md font-semibold">{{ $server->formatResource(\App\Enums\ServerResourceType::Disk) }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('disk', limit: true) }}</p>
|
||||
<p class="text-xs dark:text-gray-400">{{ $server->formatResource(\App\Enums\ServerResourceType::DiskLimit) }}</p>
|
||||
</div>
|
||||
<div class="hidden sm:block">
|
||||
<p class="text-sm dark:text-gray-400">Network</p>
|
||||
<p class="text-sm dark:text-gray-400">{{ trans('server/dashboard.network') }}</p>
|
||||
<hr class="p-0.5">
|
||||
<p class="text-md font-semibold">{{ $server->allocation?->address ?? 'None' }}</p>
|
||||
<p class="text-md font-semibold">{{ $server->allocation?->address ?? trans('server/dashboard.none') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -111,6 +111,7 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe
|
||||
Route::post('/', [Client\Servers\BackupController::class, 'store']);
|
||||
Route::get('/{backup:uuid}', [Client\Servers\BackupController::class, 'view']);
|
||||
Route::get('/{backup:uuid}/download', [Client\Servers\BackupController::class, 'download']);
|
||||
Route::put('/{backup:uuid}/rename', [Client\Servers\BackupController::class, 'rename']);
|
||||
Route::post('/{backup:uuid}/lock', [Client\Servers\BackupController::class, 'toggleLock']);
|
||||
Route::post('/{backup:uuid}/restore', [Client\Servers\BackupController::class, 'restore']);
|
||||
Route::delete('/{backup:uuid}', [Client\Servers\BackupController::class, 'delete']);
|
||||
|
Loading…
x
Reference in New Issue
Block a user