diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index c9cb05955..9ade7cda9 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -64,10 +64,9 @@ body: label: Error Logs description: | Run the following command to collect logs on your system. - - Wings: `sudo wings diagnostics` - Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl -X POST -F 'c=@-' paste.pelistuff.com` - placeholder: "https://pelipaste.com/a1h6z" + Wings: `sudo wings diagnostics --hastebin-url=https://logs.pelican.dev` + Panel: `tail -n 300 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl --data-binary @- https://logs.pelican.dev` + placeholder: "https://logs.pelican.dev/c17f750e" render: bash validations: required: false diff --git a/Dockerfile b/Dockerfile index 5db6281af..418aa44cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -85,7 +85,8 @@ RUN chown root:www-data ./ \ && ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \ # Allow www-data write permissions where necessary && chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \ - && chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord + && chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \ + && chown -R www-data: /usr/local/etc/php/ # Configure Supervisor COPY docker/supervisord.conf /etc/supervisord.conf diff --git a/Dockerfile.dev b/Dockerfile.dev index 982235a49..fc9ecb0fd 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -89,7 +89,8 @@ RUN chown root:www-data ./ \ && ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \ # Allow www-data write permissions where necessary && chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \ - && chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord + && chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \ + && chown -R www-data: /usr/local/etc/php/ # Configure Supervisor COPY docker/supervisord.conf /etc/supervisord.conf diff --git a/app/Enums/StartupVariableType.php b/app/Enums/StartupVariableType.php new file mode 100644 index 000000000..f1b90c1b6 --- /dev/null +++ b/app/Enums/StartupVariableType.php @@ -0,0 +1,10 @@ +cpuHistory), -60); - $cpu = Number::format($data['cpu'], maxPrecision: 2, locale: auth()->user()->language); - $max = Number::format($this->threads * 100, locale: auth()->user()->language); + $cpu = format_number($data['cpu'], maxPrecision: 2); + $max = format_number($this->threads * 100); return trans('admin/node.cpu_chart', ['cpu' => $cpu, 'max' => $max]); } diff --git a/app/Filament/Admin/Resources/Nodes/Widgets/NodeMemoryChart.php b/app/Filament/Admin/Resources/Nodes/Widgets/NodeMemoryChart.php index eb5e16c70..c51442f69 100644 --- a/app/Filament/Admin/Resources/Nodes/Widgets/NodeMemoryChart.php +++ b/app/Filament/Admin/Resources/Nodes/Widgets/NodeMemoryChart.php @@ -5,7 +5,6 @@ namespace App\Filament\Admin\Resources\Nodes\Widgets; use App\Models\Node; use Filament\Support\RawJs; use Filament\Widgets\ChartWidget; -use Illuminate\Support\Number; class NodeMemoryChart extends ChartWidget { @@ -85,12 +84,12 @@ class NodeMemoryChart extends ChartWidget $latestMemoryUsed = array_slice(end($this->memoryHistory), -60); $used = config('panel.use_binary_prefix') - ? Number::format($latestMemoryUsed['memory'], maxPrecision: 2, locale: auth()->user()->language) .' GiB' - : Number::format($latestMemoryUsed['memory'], maxPrecision: 2, locale: auth()->user()->language) . ' GB'; + ? format_number($latestMemoryUsed['memory'], maxPrecision: 2) .' GiB' + : format_number($latestMemoryUsed['memory'], maxPrecision: 2) . ' GB'; $total = config('panel.use_binary_prefix') - ? Number::format($this->totalMemory / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB' - : Number::format($this->totalMemory / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB'; + ? format_number($this->totalMemory / 1024 / 1024 / 1024, maxPrecision: 2) .' GiB' + : format_number($this->totalMemory / 1000 / 1000 / 1000, maxPrecision: 2) . ' GB'; return trans('admin/node.memory_chart', ['used' => $used, 'total' => $total]); } diff --git a/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php b/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php index 73fca3366..8479a07ae 100644 --- a/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php +++ b/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php @@ -13,10 +13,8 @@ use App\Services\Servers\ServerCreationService; use App\Services\Users\UserCreationService; use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderWidgets; -use Closure; use Exception; use Filament\Actions\Action; -use Filament\Schemas\Components\Component; use Filament\Schemas\Components\Fieldset; use Filament\Forms\Components\Hidden; use Filament\Forms\Components\KeyValue; @@ -39,7 +37,6 @@ use Filament\Support\Exceptions\Halt; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Blade; -use Illuminate\Support\Facades\Validator; use Illuminate\Support\HtmlString; use LogicException; use Filament\Schemas\Schema; @@ -432,7 +429,7 @@ class CreateServer extends CreateRecord Egg::query()->find($get('egg_id'))?->variables()?->count() ), Repeater::make('server_variables') - ->label('') + ->hiddenLabel() ->relationship('serverVariables', fn (Builder $query) => $query->orderByPowerJoins('variable.sort')) ->saveRelationshipsBeforeChildrenUsing(null) ->saveRelationshipsUsing(null) @@ -442,61 +439,15 @@ class CreateServer extends CreateRecord ->deletable(false) ->default([]) ->hidden(fn ($state) => empty($state)) - ->mutateRelationshipDataBeforeCreateUsing(function ($data) { - $data['variable_value'] = ($data['is_select'] ? $data['variable_value_select'] : $data['variable_value_input']) ?? ''; - - return $data; - }) - ->schema(function () { - $isSelect = Hidden::make('is_select') - ->dehydrated(false) - ->formatStateUsing(fn (Get $get) => (bool) collect($get('rules'))->reduce( - fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true - )); - - $text = TextInput::make('variable_value_input') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->required(fn (Get $get) => in_array('required', $get('rules'))) - ->rules( - fn (Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) { - $validator = Validator::make(['validatorkey' => $value], [ - 'validatorkey' => $get('rules'), - ]); - - if ($validator->fails()) { - $message = str($validator->errors()->first())->replace('validatorkey', $get('name'))->toString(); - - $fail($message); - } - }, - ); - - $select = Select::make('variable_value_select') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->options($this->getSelectOptionsFromRules(...)) - ->selectablePlaceholder(false); - - $components = [$text, $select]; - - foreach ($components as &$component) { - $component = $component - ->live(onBlur: true) - ->hintIcon('tabler-code') - ->label(fn (Get $get) => $get('name')) - ->hintIconTooltip(fn (Get $get) => implode('|', $get('rules'))) - ->prefix(fn (Get $get) => '{{' . $get('env_variable') . '}}') - ->helperText(fn (Get $get) => empty($get('description')) ? '—' : $get('description')) - ->afterStateUpdated(function (Set $set, Get $get, $state) { - $environment = $get($envPath = '../../environment'); - $environment[$get('env_variable')] = $state; - $set($envPath, $environment); - }); - } - - return [$isSelect, ...$components]; - }) + ->schema([ + StartupVariable::make('variable_value') + ->fromForm() + ->afterStateUpdated(function (Set $set, Get $get, $state) { + $environment = $get($envPath = '../../environment'); + $environment[$get('env_variable')] = $state; + $set($envPath, $environment); + }), + ]) ->columnSpan(2), ]), ]), @@ -864,40 +815,6 @@ class CreateServer extends CreateRecord } } - private function shouldHideComponent(Get $get, Component $component): bool - { - $containsRuleIn = collect($get('rules'))->reduce( - fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true - ); - - if ($component instanceof Select) { - return $containsRuleIn; - } - - if ($component instanceof TextInput) { - return !$containsRuleIn; - } - - throw new Exception('Component type not supported: ' . $component::class); - } - - /** - * @return array - */ - private function getSelectOptionsFromRules(Get $get): array - { - $inRule = collect($get('rules'))->reduce( - fn ($result, $value) => str($value)->startsWith('in:') ? $value : $result, '' - ); - - return str($inRule) - ->after('in:') - ->explode(',') - ->each(fn ($value) => str($value)->trim()) - ->mapWithKeys(fn ($value) => [$value => $value]) - ->all(); - } - /** * @param string[] $portEntries * @return array diff --git a/app/Filament/Admin/Resources/Servers/Pages/EditServer.php b/app/Filament/Admin/Resources/Servers/Pages/EditServer.php index 004b2a97f..485fa07b7 100644 --- a/app/Filament/Admin/Resources/Servers/Pages/EditServer.php +++ b/app/Filament/Admin/Resources/Servers/Pages/EditServer.php @@ -4,8 +4,6 @@ namespace App\Filament\Admin\Resources\Servers\Pages; use App\Enums\SuspendAction; use App\Filament\Admin\Resources\Servers\ServerResource; -use App\Filament\Admin\Resources\Servers\RelationManagers\AllocationsRelationManager; -use App\Filament\Admin\Resources\Servers\RelationManagers\DatabasesRelationManager; use App\Filament\Components\Actions\PreviewStartupAction; use App\Filament\Server\Pages\Console; use App\Models\Allocation; @@ -24,18 +22,8 @@ use App\Services\Servers\ToggleInstallService; use App\Services\Servers\TransferServerService; use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderWidgets; -use Closure; use Exception; -use Filament\Actions\Action; -use Filament\Actions\ActionGroup; -use Filament\Forms\Components\CodeEditor; -use Filament\Schemas\Components\Actions; -use Filament\Schemas\Components\Component; -use Filament\Schemas\Components\Fieldset; -use Filament\Forms\Components\Hidden; -use Filament\Forms\Components\KeyValue; -use Filament\Forms\Components\Repeater; -use Filament\Forms\Components\Select; +use Filament\Actions; use Filament\Schemas\Components\Grid; use Filament\Schemas\Components\Tabs; use Filament\Schemas\Components\Tabs\Tab; @@ -53,7 +41,6 @@ use Filament\Support\Enums\Alignment; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Validator; use Illuminate\Support\HtmlString; use LogicException; use Filament\Schemas\Schema; @@ -629,7 +616,7 @@ class EditServer extends EditRecord }), Repeater::make('server_variables') - ->label('') + ->hiddenLabel() ->relationship('serverVariables', function (Builder $query) { /** @var Server $server */ $server = $this->getRecord(); @@ -646,56 +633,16 @@ class EditServer extends EditRecord return $query->orderByPowerJoins('variable.sort'); }) ->grid() - ->mutateRelationshipDataBeforeSaveUsing(function (array $data) { - $data['variable_value'] = ($data['is_select'] ? $data['variable_value_select'] : $data['variable_value_input']) ?? ''; + ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array { + $data['variable_value'] ??= ''; return $data; }) ->reorderable(false)->addable(false)->deletable(false) - ->schema(function () { - $isSelect = Hidden::make('is_select') - ->dehydrated(false) - ->formatStateUsing(fn (ServerVariable $serverVariable) => (bool) array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:'), false)); - - $text = TextInput::make('variable_value_input') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->required(fn (ServerVariable $serverVariable) => $serverVariable->variable->getRequiredAttribute()) - ->rules([ - fn (ServerVariable $serverVariable): Closure => function (string $attribute, $value, Closure $fail) use ($serverVariable) { - $validator = Validator::make(['validatorkey' => $value], [ - 'validatorkey' => $serverVariable->variable->rules, - ]); - - if ($validator->fails()) { - $message = str($validator->errors()->first())->replace('validatorkey', $serverVariable->variable->name); - - $fail($message); - } - }, - ]); - - $select = Select::make('variable_value_select') - ->hidden($this->shouldHideComponent(...)) - ->dehydratedWhenHidden() - ->options($this->getSelectOptionsFromRules(...)) - ->selectablePlaceholder(false); - - $components = [$text, $select]; - - foreach ($components as &$component) { - $component = $component - ->live(onBlur: true) - ->hintIcon('tabler-code') - ->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name) - ->hintIconTooltip(fn (ServerVariable $serverVariable) => implode('|', $serverVariable->variable->rules)) - ->prefix(fn (ServerVariable $serverVariable) => '{{' . $serverVariable->variable->env_variable . '}}') - ->helperText(fn (ServerVariable $serverVariable) => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description) - ->formatStateUsing(fn (ServerVariable $serverVariable) => $serverVariable->variable_value); - } - - return [$isSelect, ...$components]; - }) + ->schema([ + StartupVariable::make('variable_value') + ->fromRecord(), + ]) ->columnSpan(6), ]), Tab::make(trans('admin/server.mounts')) @@ -1032,34 +979,4 @@ class EditServer extends EditRecord DatabasesRelationManager::class, ]; } - - private function shouldHideComponent(ServerVariable $serverVariable, Component $component): bool - { - $containsRuleIn = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:'), false); - - if ($component instanceof Select) { - return !$containsRuleIn; - } - - if ($component instanceof TextInput) { - return $containsRuleIn; - } - - throw new Exception('Component type not supported: ' . $component::class); - } - - /** - * @return array - */ - private function getSelectOptionsFromRules(ServerVariable $serverVariable): array - { - $inRule = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:')); - - return str($inRule) - ->after('in:') - ->explode(',') - ->each(fn ($value) => str($value)->trim()) - ->mapWithKeys(fn ($value) => [$value => $value]) - ->all(); - } } diff --git a/app/Filament/Components/Actions/ImportScheduleAction.php b/app/Filament/Components/Actions/ImportScheduleAction.php index 47139f87c..3850d4a72 100644 --- a/app/Filament/Components/Actions/ImportScheduleAction.php +++ b/app/Filament/Components/Actions/ImportScheduleAction.php @@ -39,26 +39,26 @@ class ImportScheduleAction extends Action Tabs::make('Tabs') ->contained(false) ->tabs([ - Tab::make(trans('admin/schedule.import.file')) + Tab::make(trans('server/schedule.import_action.file')) ->icon('tabler-file-upload') ->schema([ FileUpload::make('files') - ->label(trans('admin/schedule.model_label')) - ->hint(trans('admin/schedule.import.schedule_help')) + ->hiddenLabel() + ->hint(trans('server/schedule.import_action.schedule_help')) ->acceptedFileTypes(['application/json']) ->preserveFilenames() ->previewable(false) ->storeFiles(false) ->multiple(true), ]), - Tab::make(trans('admin/schedule.import.url')) + Tab::make(trans('server/schedule.import_action.url')) ->icon('tabler-world-upload') ->schema([ Repeater::make('urls') - ->label('') + ->hiddenLabel() ->itemLabel(fn (array $state) => str($state['url'])->afterLast('/schedule-')->before('.json')->headline()) - ->hint(trans('admin/schedule.import.url_help')) - ->addActionLabel(trans('admin/schedule.import.add_url')) + ->hint(trans('server/schedule.import_action.url_help')) + ->addActionLabel(trans('server/schedule.import_action.add_url')) ->grid(2) ->reorderable(false) ->addable(true) @@ -66,10 +66,10 @@ class ImportScheduleAction extends Action ->schema([ TextInput::make('url') ->live() - ->label(trans('admin/schedule.import.url')) + ->label(trans('server/schedule.import_action.url')) ->url() ->endsWith('.json') - ->validationAttribute(trans('admin/schedule.import.url')), + ->validationAttribute(trans('server/schedule.import_action.url')), ]), ]), ]), @@ -104,14 +104,14 @@ class ImportScheduleAction extends Action if ($failed->count() > 0) { Notification::make() - ->title(trans('admin/schedule.import.import_failed')) + ->title(trans('server/schedule.import_action.import_failed')) ->body($failed->join(', ')) ->danger() ->send(); } if ($success->count() > 0) { Notification::make() - ->title(trans('admin/schedule.import.import_success')) + ->title(trans('server/schedule.import_action.import_success')) ->body($success->join(', ')) ->success() ->send(); diff --git a/app/Filament/Components/Forms/Fields/StartupVariable.php b/app/Filament/Components/Forms/Fields/StartupVariable.php new file mode 100644 index 000000000..9df2dd906 --- /dev/null +++ b/app/Filament/Components/Forms/Fields/StartupVariable.php @@ -0,0 +1,181 @@ +label(fn (StartupVariable $component) => $component->getVariableName()); + + $this->prefix(fn (StartupVariable $component) => '{{' . $component->getVariableEnv() . '}}'); + + $this->hintIcon('tabler-code'); + + $this->hintIconTooltip(fn (StartupVariable $component) => implode('|', $component->getVariableRules())); + + $this->helperText(fn (StartupVariable $component) => !$component->getVariableDesc() ? '—' : $component->getVariableDesc()); + + $this->rules(fn (StartupVariable $component) => $component->getVariableRules()); + + $this->placeholder(fn (StartupVariable $component) => $component->getVariableDefault()); + + $this->live(onBlur: true); + } + + public function fromForm(): static + { + $this->variableName(fn (Get $get) => $get('name')); + $this->variableDesc(fn (Get $get) => $get('description')); + $this->variableEnv(fn (Get $get) => $get('env_variable')); + $this->variableDefault(fn (Get $get) => $get('default_value')); + $this->variableRules(fn (Get $get) => $get('rules')); + + return $this; + } + + public function fromRecord(): static + { + $this->variableName(fn (ServerVariable $record) => $record->variable->name); + $this->variableDesc(fn (ServerVariable $record) => $record->variable->description); + $this->variableEnv(fn (ServerVariable $record) => $record->variable->env_variable); + $this->variableDefault(fn (ServerVariable $record) => $record->variable->default_value); + $this->variableRules(fn (ServerVariable $record) => $record->variable->rules); + + return $this; + } + + public function variableName(string|Closure|null $name): static + { + $this->variableName = $name; + + return $this; + } + + public function variableDesc(string|Closure|null $desc): static + { + $this->variableDesc = $desc; + + return $this; + } + + public function variableEnv(string|Closure|null $envVariable): static + { + $this->variableEnv = $envVariable; + + return $this; + } + + public function variableDefault(string|Closure|null $default): static + { + $this->variableDefault = $default; + + return $this; + } + + /** @param string[]|Closure|null $rules */ + public function variableRules(array|Closure|null $rules): static + { + $this->variableRules = $rules; + + return $this; + } + + public function getVariableName(): ?string + { + return $this->evaluate($this->variableName); + } + + public function getVariableDesc(): ?string + { + return $this->evaluate($this->variableDesc); + } + + public function getVariableEnv(): ?string + { + return $this->evaluate($this->variableEnv); + } + + public function getVariableDefault(): ?string + { + return $this->evaluate($this->variableDefault); + } + + /** @return string[] */ + public function getVariableRules(): array + { + return (array) ($this->evaluate($this->variableRules) ?? []); + } + + public function isRequired(): bool + { + $rules = $this->getVariableRules(); + + return in_array('required', $rules); + } + + public function getType(): StartupVariableType + { + $rules = $this->getVariableRules(); + + if (Arr::first($rules, fn ($value) => str($value)->startsWith('in:'), false)) { + return StartupVariableType::Select; + } + + if (in_array('boolean', $rules)) { + return StartupVariableType::Toggle; + } + + return StartupVariableType::Text; + } + + /** @return string[] */ + public function getSelectOptions(): array + { + $rules = $this->getVariableRules(); + + $inRule = Arr::first($rules, fn ($value) => str($value)->startsWith('in:')); + if ($inRule) { + return str($inRule) + ->after('in:') + ->explode(',') + ->each(fn ($value) => Str::trim($value)) + ->all(); + } + + return []; + } +} diff --git a/app/Filament/Server/Pages/Settings.php b/app/Filament/Server/Pages/Settings.php index 0b785f046..28d20a860 100644 --- a/app/Filament/Server/Pages/Settings.php +++ b/app/Filament/Server/Pages/Settings.php @@ -16,7 +16,6 @@ use Filament\Forms\Components\TextInput; use Filament\Notifications\Notification; use Filament\Schemas\Schema; use Filament\Support\Enums\Alignment; -use Illuminate\Support\Number; class Settings extends ServerFormPage { @@ -110,7 +109,7 @@ class Settings extends ServerFormPage ->prefixIcon('tabler-cpu') ->columnSpan(1) ->disabled() - ->formatStateUsing(fn ($state, Server $server) => !$state ? trans('server/setting.server_info.limits.unlimited') : Number::format($server->cpu, locale: auth()->user()->language) . '%'), + ->formatStateUsing(fn ($state, Server $server) => !$state ? trans('server/setting.server_info.limits.unlimited') : format_number($server->cpu) . '%'), TextInput::make('memory') ->label('') ->prefix(trans('server/setting.server_info.limits.memory')) diff --git a/app/Filament/Server/Resources/Schedules/ScheduleResource.php b/app/Filament/Server/Resources/Schedules/ScheduleResource.php index 40dcf2591..ef26b60f5 100644 --- a/app/Filament/Server/Resources/Schedules/ScheduleResource.php +++ b/app/Filament/Server/Resources/Schedules/ScheduleResource.php @@ -127,7 +127,15 @@ class ScheduleResource extends Resource ->visibleOn('view'), Section::make('Cron') ->label(trans('server/schedule.cron')) - ->description(fn (Get $get) => new HtmlString(trans('server/schedule.cron_body') . '
' . 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)]))) + ->description(function (Get $get) { + try { + $nextRun = 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); + } catch (Exception) { + $nextRun = trans('server/schedule.invalid'); + } + + return new HtmlString(trans('server/schedule.cron_body') . '
' . trans('server/schedule.cron_timezone', ['timezone' => auth()->user()->timezone, 'next_run' => $nextRun])); + }) ->schema([ Actions::make([ CronPresetAction::make('hourly') diff --git a/app/Filament/Server/Widgets/ServerCpuChart.php b/app/Filament/Server/Widgets/ServerCpuChart.php index e79e0f757..909221821 100644 --- a/app/Filament/Server/Widgets/ServerCpuChart.php +++ b/app/Filament/Server/Widgets/ServerCpuChart.php @@ -7,7 +7,6 @@ use Carbon\Carbon; use Filament\Facades\Filament; use Filament\Support\RawJs; use Filament\Widgets\ChartWidget; -use Illuminate\Support\Number; class ServerCpuChart extends ChartWidget { @@ -31,7 +30,7 @@ class ServerCpuChart extends ChartWidget $cpu = collect(cache()->get("servers.{$this->server->id}.cpu_absolute")) ->slice(-$period) ->map(fn ($value, $key) => [ - 'cpu' => Number::format($value, maxPrecision: 2), + 'cpu' => round($value, 2), 'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'), ]) ->all(); diff --git a/app/Filament/Server/Widgets/ServerMemoryChart.php b/app/Filament/Server/Widgets/ServerMemoryChart.php index e1c7334bb..2a8b09dff 100644 --- a/app/Filament/Server/Widgets/ServerMemoryChart.php +++ b/app/Filament/Server/Widgets/ServerMemoryChart.php @@ -7,7 +7,6 @@ use Carbon\Carbon; use Filament\Facades\Filament; use Filament\Support\RawJs; use Filament\Widgets\ChartWidget; -use Illuminate\Support\Number; class ServerMemoryChart extends ChartWidget { @@ -31,7 +30,7 @@ class ServerMemoryChart extends ChartWidget $memUsed = collect(cache()->get("servers.{$this->server->id}.memory_bytes")) ->slice(-$period) ->map(fn ($value, $key) => [ - 'memory' => Number::format(config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, maxPrecision: 2), + 'memory' => round(config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, 2), 'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'), ]) ->all(); diff --git a/app/Filament/Server/Widgets/ServerOverview.php b/app/Filament/Server/Widgets/ServerOverview.php index ab9c4ae59..07698f58e 100644 --- a/app/Filament/Server/Widgets/ServerOverview.php +++ b/app/Filament/Server/Widgets/ServerOverview.php @@ -8,7 +8,6 @@ use App\Models\Server; use Carbon\CarbonInterface; use Filament\Notifications\Notification; use Filament\Widgets\StatsOverviewWidget; -use Illuminate\Support\Number; use Livewire\Attributes\On; class ServerOverview extends StatsOverviewWidget @@ -54,9 +53,9 @@ class ServerOverview extends StatsOverviewWidget } $data = collect(cache()->get("servers.{$this->server->id}.cpu_absolute"))->last(default: 0); - $cpu = Number::format($data, maxPrecision: 2, locale: auth()->user()->language) . ' %'; + $cpu = format_number($data, maxPrecision: 2) . ' %'; - return $cpu . ($this->server->cpu > 0 ? ' / ' . Number::format($this->server->cpu, locale: auth()->user()->language) . ' %' : ' / ∞'); + return $cpu . ($this->server->cpu > 0 ? ' / ' . format_number($this->server->cpu) . ' %' : ' / ∞'); } public function memoryUsage(): string @@ -68,7 +67,7 @@ class ServerOverview extends StatsOverviewWidget } $latestMemoryUsed = collect(cache()->get("servers.{$this->server->id}.memory_bytes"))->last(default: 0); - $totalMemory = $this->server->memory * 2 ** 20; + $totalMemory = $this->server->memory * (config('panel.use_binary_prefix') ? 1024 * 1024 : 1000 * 1000); $used = convert_bytes_to_readable($latestMemoryUsed); $total = convert_bytes_to_readable($totalMemory); diff --git a/app/Livewire/Installer/PanelInstaller.php b/app/Livewire/Installer/PanelInstaller.php index ee48cb67e..6a6cce1c7 100644 --- a/app/Livewire/Installer/PanelInstaller.php +++ b/app/Livewire/Installer/PanelInstaller.php @@ -2,7 +2,6 @@ namespace App\Livewire\Installer; -use App\Filament\Admin\Pages\Dashboard; use App\Livewire\Installer\Steps\CacheStep; use App\Livewire\Installer\Steps\DatabaseStep; use App\Livewire\Installer\Steps\EnvironmentStep; @@ -15,6 +14,7 @@ use App\Traits\CheckMigrationsTrait; use App\Traits\EnvironmentWriterTrait; use Exception; use Filament\Actions\Action; +use Filament\Facades\Filament; use Filament\Forms\Contracts\HasForms; use Filament\Schemas\Components\Wizard; use Filament\Forms\Concerns\InteractsWithForms; @@ -110,7 +110,7 @@ class PanelInstaller extends SimplePage implements HasForms $this->writeToEnv('env_session'); // Redirect to admin panel - $this->redirect(Dashboard::getUrl()); + $this->redirect(Filament::getPanel('admin')->getUrl()); } catch (Halt) { } } diff --git a/app/Livewire/ServerEntry.php b/app/Livewire/ServerEntry.php index 42d44e13d..bff93236d 100644 --- a/app/Livewire/ServerEntry.php +++ b/app/Livewire/ServerEntry.php @@ -38,7 +38,7 @@ class ServerEntry extends Component

{{ trans('server/dashboard.cpu') }}

-

{{ Number::format(0, precision: 2, locale: auth()->user()->language ?? 'en') . '%' }}

+

{{ format_number(0, precision: 2) . '%' }}


{{ $server->formatResource(\App\Enums\ServerResourceType::CPULimit) }}

diff --git a/app/Models/Role.php b/app/Models/Role.php index e7eed657d..46270eddb 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -46,7 +46,7 @@ class Role extends BaseRole 'health' => [ 'view', ], - 'activity' => [ + 'activityLog' => [ 'seeIps', ], ]; diff --git a/app/Models/Server.php b/app/Models/Server.php index 479dcfa37..d391b3b0e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -23,7 +23,6 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Http; -use Illuminate\Support\Number; use Psr\Http\Message\ResponseInterface; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -490,7 +489,7 @@ class Server extends Model implements Validatable } if ($resourceType->isPercentage()) { - return Number::format($resourceAmount, precision: 2, locale: auth()->user()->language ?? 'en') . '%'; + return format_number($resourceAmount, precision: 2) . '%'; } return convert_bytes_to_readable($resourceAmount, base: 3); diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 06585c319..1ed8a2451 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -3,6 +3,7 @@ namespace App\Providers\Filament; use Filament\Actions\Action; +use Filament\Facades\Filament; use Filament\Navigation\NavigationGroup; use Filament\Panel; @@ -20,7 +21,7 @@ class AdminPanelProvider extends PanelProvider 'profile' => fn (Action $action) => $action->label(auth()->user()->username), Action::make('exitAdmin') ->label(fn () => trans('profile.exit_admin')) - ->url('/') + ->url(fn () => Filament::getPanel('app')->getUrl()) ->icon('tabler-arrow-back') ->sort(24), ]) diff --git a/app/Providers/Filament/AppPanelProvider.php b/app/Providers/Filament/AppPanelProvider.php index fbf0c4456..47fbab894 100644 --- a/app/Providers/Filament/AppPanelProvider.php +++ b/app/Providers/Filament/AppPanelProvider.php @@ -19,7 +19,7 @@ class AppPanelProvider extends PanelProvider 'profile' => fn (Action $action) => $action->label(auth()->user()->username), Action::make('toAdmin') ->label(trans('profile.admin')) - ->url('/admin') + ->url(fn () => Filament::getPanel('admin')->getUrl()) ->icon('tabler-arrow-forward') ->sort(5) ->visible(fn () => auth()->user()->canAccessPanel(Filament::getPanel('admin'))), diff --git a/app/helpers.php b/app/helpers.php index 46b9e8648..9f958a6e6 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -45,7 +45,7 @@ if (!function_exists('convert_bytes_to_readable')) { $fromBase = log($bytes) / log($conversionUnit); $base ??= floor($fromBase); - return Number::format(pow($conversionUnit, $fromBase - $base), $decimals, locale: auth()->user()->language) . ' ' . $suffix[$base]; + return format_number(pow($conversionUnit, $fromBase - $base), precision: $decimals) . ' ' . $suffix[$base]; } } @@ -98,3 +98,15 @@ if (!function_exists('get_ip_from_hostname')) { return false; } } + +if (!function_exists('format_number')) { + function format_number(int|float $number, ?int $precision = null, ?int $maxPrecision = null): false|string + { + try { + return Number::format($number, $precision, $maxPrecision, auth()->user()->language ?? 'en'); + } catch (Throwable) { + // User language is invalid, so default to english + return Number::format($number, $precision, $maxPrecision, 'en'); + } + } +} diff --git a/database/migrations/2025_08_30_111308_fix_activity_log_permission_name.php b/database/migrations/2025_08_30_111308_fix_activity_log_permission_name.php new file mode 100644 index 000000000..e1e5c093b --- /dev/null +++ b/database/migrations/2025_08_30_111308_fix_activity_log_permission_name.php @@ -0,0 +1,27 @@ +where('name', 'seeIps activity') + ->update(['name' => 'seeIps activityLog']); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + DB::table('permissions') + ->where('name', 'seeIps activityLog') + ->update(['name' => 'seeIps activity']); + } +}; diff --git a/lang/en/admin/schedule.php b/lang/en/admin/schedule.php deleted file mode 100644 index 79e6b449b..000000000 --- a/lang/en/admin/schedule.php +++ /dev/null @@ -1,15 +0,0 @@ - 'Schedule', - 'model_label_plural' => 'Schedule', - 'import' => [ - 'file' => 'File', - 'url' => 'URL', - 'schedule_help' => 'This should be the raw .json file ( schedule-daily-restart.json )', - 'url_help' => 'URLs must point directly to the raw .json file', - 'add_url' => 'New URL', - 'import_failed' => 'Import Failed', - 'import_success' => 'Import Success', - ], -]; diff --git a/lang/en/server/schedule.php b/lang/en/server/schedule.php index 87507bc83..fbb49637e 100644 --- a/lang/en/server/schedule.php +++ b/lang/en/server/schedule.php @@ -30,6 +30,8 @@ return [ 'cron_body' => 'Please keep in mind that the cron inputs below always assume UTC.', 'cron_timezone' => 'Next run in your timezone (:timezone): :next_run ', + 'invalid' => 'Invalid', + 'time' => [ 'minute' => 'Minute', 'hour' => 'Hour', @@ -104,4 +106,13 @@ return [ 'notification_invalid_cron' => 'The cron data provided does not evaluate to a valid expression', + 'import_action' => [ + 'file' => 'File', + 'url' => 'URL', + 'schedule_help' => 'This should be the raw .json file ( schedule-daily-restart.json )', + 'url_help' => 'URLs must point directly to the raw .json file', + 'add_url' => 'New URL', + 'import_failed' => 'Import Failed', + 'import_success' => 'Import Success', + ], ]; diff --git a/resources/views/filament/components/startup-variable.blade.php b/resources/views/filament/components/startup-variable.blade.php new file mode 100644 index 000000000..11e33d6f2 --- /dev/null +++ b/resources/views/filament/components/startup-variable.blade.php @@ -0,0 +1,67 @@ +@php + $statePath = $getStatePath(); + $isRequired = $isRequired(); + $isDisabled = $isDisabled(); + $type = $getType(); +@endphp + + + + {{ $getLabel() }} + + + + @if ($type === \App\Enums\StartupVariableType::Select) + + @if (!$isRequired) + + @endif + + @foreach ($getSelectOptions() as $value) + + @endforeach + + @else + + @endif + +