From c464b321dd189f32f084e3a08e5e69fd9fdc50c1 Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:05:00 +0200 Subject: [PATCH 01/65] Update EditProfile.php (#454) --- app/Filament/Resources/UserResource/Pages/EditProfile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Resources/UserResource/Pages/EditProfile.php b/app/Filament/Resources/UserResource/Pages/EditProfile.php index 6dceac4e2..7cc2dfde9 100644 --- a/app/Filament/Resources/UserResource/Pages/EditProfile.php +++ b/app/Filament/Resources/UserResource/Pages/EditProfile.php @@ -215,7 +215,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile Action::make('Create') ->disabled(fn (Get $get) => $get('description') === null) ->successRedirectUrl(route('filament.admin.auth.profile', ['tab' => '-api-keys-tab'])) - ->action(function (Get $get, Action $action, $user) { + ->action(function (Get $get, Action $action, User $user) { $token = $user->createToken( $get('description'), $get('allowed_ips'), From 40721a2cb8b933eb617c78462fb4b0e86c76536c Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 2 Jul 2024 08:01:17 -0400 Subject: [PATCH 02/65] Fix #452 Prob not the best solution, but it works Closes: https://github.com/pelican-dev/panel/issues/452 --- .../Resources/ServerResource/Pages/CreateServer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index bc3a4191e..604ecf33a 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -233,7 +233,9 @@ class CreateServer extends CreateRecord $end = min((int) $end, 2 ** 16 - 1); $range = $start <= $end ? range($start, $end) : range($end, $start); foreach ($range as $i) { - $ports->push($i); + if($i > 1024 && $i <= 65535) { + $ports->push($i); + } } } @@ -249,8 +251,6 @@ class CreateServer extends CreateRecord $ports = $sortedPorts; } - $ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values(); - if ($update) { $set('allocation_ports', $ports->all()); } From 71649510855e36a6dc84a36ce0878eed9ad8e720 Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Tue, 2 Jul 2024 19:31:35 +0200 Subject: [PATCH 03/65] Update EditServer.php (#455) --- app/Filament/Resources/ServerResource/Pages/EditServer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Resources/ServerResource/Pages/EditServer.php index 3a18664e8..7c6ee83a9 100644 --- a/app/Filament/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Resources/ServerResource/Pages/EditServer.php @@ -747,8 +747,7 @@ class EditServer extends EditRecord Actions\DeleteAction::make('Delete') ->successRedirectUrl(route('filament.admin.resources.servers.index')) ->color('danger') - ->disabled(fn (Server $server) => $server->databases()->count() > 0) - ->label(fn (Server $server) => $server->databases()->count() > 0 ? 'Server has a Database' : 'Delete') + ->label('Delete') ->after(fn (Server $server) => resolve(ServerDeletionService::class)->handle($server)) ->requiresConfirmation(), Actions\Action::make('console') From 160ea1ed50231db4719f604d46904b0c4ddfcd14 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 3 Jul 2024 10:27:57 -0400 Subject: [PATCH 04/65] Enable Update URL Since importing an egg via url was added, we can enable this. --- app/Filament/Resources/EggResource/Pages/CreateEgg.php | 7 ++++--- app/Filament/Resources/EggResource/Pages/EditEgg.php | 9 ++++++--- .../Resources/ServerResource/Pages/CreateServer.php | 2 +- app/Models/Egg.php | 1 + 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/Filament/Resources/EggResource/Pages/CreateEgg.php b/app/Filament/Resources/EggResource/Pages/CreateEgg.php index e97bc2af2..16f94be53 100644 --- a/app/Filament/Resources/EggResource/Pages/CreateEgg.php +++ b/app/Filament/Resources/EggResource/Pages/CreateEgg.php @@ -74,9 +74,10 @@ class CreateEgg extends CreateRecord ->helperText('') ->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]), TextInput::make('update_url') - ->disabled() - ->helperText('Not implemented.') - ->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]), + ->hintIcon('tabler-question-mark') + ->hintToolTip('URLs must point directly to the raw .json file.') + ->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]) + ->url(), KeyValue::make('docker_images') ->live() ->columnSpanFull() diff --git a/app/Filament/Resources/EggResource/Pages/EditEgg.php b/app/Filament/Resources/EggResource/Pages/EditEgg.php index 3ced26164..c2282ea80 100644 --- a/app/Filament/Resources/EggResource/Pages/EditEgg.php +++ b/app/Filament/Resources/EggResource/Pages/EditEgg.php @@ -91,8 +91,10 @@ class EditEgg extends EditRecord ->helperText('') ->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]), TextInput::make('update_url') - ->disabled() - ->helperText('Not implemented.') + ->label('Update URL') + ->url() + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('URLs must point directly to the raw .json file.') ->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]), KeyValue::make('docker_images') ->live() @@ -247,8 +249,9 @@ class EditEgg extends EditRecord Tab::make('From URL') ->icon('tabler-world-upload') ->schema([ - TextInput::make('url') + TextInput::make('update_url') ->label('URL') + ->formatStateUsing(fn (Egg $egg): string => $egg->update_url) ->hint('Link to the egg file (eg. minecraft.json)') ->url(), ]), diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index 604ecf33a..572499aa0 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -233,7 +233,7 @@ class CreateServer extends CreateRecord $end = min((int) $end, 2 ** 16 - 1); $range = $start <= $end ? range($start, $end) : range($end, $start); foreach ($range as $i) { - if($i > 1024 && $i <= 65535) { + if ($i > 1024 && $i <= 65535) { $ports->push($i); } } diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 5586b4c1f..40e20d22a 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -95,6 +95,7 @@ class Egg extends Model 'config_stop', 'config_from', 'startup', + 'update_url', 'script_is_privileged', 'script_install', 'script_entry', From c115c6ddf5900c5a3d0b6641cd4c903383d50940 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 3 Jul 2024 10:36:34 -0400 Subject: [PATCH 05/65] Add Update URL to stock eggs --- .../eggs/minecraft/egg-bungeecord.json | 4 +- .../eggs/minecraft/egg-forge-minecraft.json | 12 +++--- .../Seeders/eggs/minecraft/egg-paper.json | 12 +++--- .../minecraft/egg-sponge--sponge-vanilla.json | 10 ++--- .../eggs/minecraft/egg-vanilla-minecraft.json | 8 ++-- database/Seeders/eggs/rust/egg-rust.json | 40 +++++++++---------- ...egg-counter--strike--global-offensive.json | 10 ++--- .../egg-custom-source-engine-game.json | 16 ++++---- .../eggs/source-engine/egg-garrys-mod.json | 20 +++++----- .../eggs/source-engine/egg-insurgency.json | 8 ++-- .../source-engine/egg-team-fortress2.json | 10 ++--- .../eggs/voice-servers/egg-mumble-server.json | 8 ++-- .../voice-servers/egg-teamspeak3-server.json | 18 ++++----- 13 files changed, 88 insertions(+), 88 deletions(-) diff --git a/database/Seeders/eggs/minecraft/egg-bungeecord.json b/database/Seeders/eggs/minecraft/egg-bungeecord.json index d6d15c3fc..f9b389560 100644 --- a/database/Seeders/eggs/minecraft/egg-bungeecord.json +++ b/database/Seeders/eggs/minecraft/egg-bungeecord.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-bungeecord.json" }, - "exported_at": "2024-06-04T22:51:49+00:00", + "exported_at": "2024-07-03T14:33:51+00:00", "name": "Bungeecord", "author": "panel@example.com", "uuid": "9e6b409e-4028-4947-aea8-50a2c404c271", diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index 6875e79ae..693e299db 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-forge-minecraft.json" }, - "exported_at": "2024-06-04T22:51:58+00:00", + "exported_at": "2024-07-03T14:33:51+00:00", "name": "Forge Minecraft", "author": "panel@example.com", "uuid": "ed072427-f209-4603-875c-f540c6dd5a65", @@ -45,7 +45,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -56,7 +56,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|max:9", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -67,7 +67,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|in:recommended,latest", - "sort": null, + "sort": 3, "field_type": "text" }, { @@ -78,7 +78,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|regex:\/^[0-9\\.\\-]+$\/", - "sort": null, + "sort": 4, "field_type": "text" } ] diff --git a/database/Seeders/eggs/minecraft/egg-paper.json b/database/Seeders/eggs/minecraft/egg-paper.json index 8eb3002ec..39463b0e9 100644 --- a/database/Seeders/eggs/minecraft/egg-paper.json +++ b/database/Seeders/eggs/minecraft/egg-paper.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-paper.json" }, - "exported_at": "2024-06-04T22:51:57+00:00", + "exported_at": "2024-07-03T14:33:52+00:00", "name": "Paper", "author": "parker@example.com", "uuid": "5da37ef6-58da-4169-90a6-e683e1721247", @@ -45,7 +45,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|string|max:20", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -56,7 +56,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -67,7 +67,7 @@ "user_viewable": false, "user_editable": false, "rules": "nullable|string", - "sort": null, + "sort": 3, "field_type": "text" }, { @@ -78,7 +78,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|max:20", - "sort": null, + "sort": 4, "field_type": "text" } ] diff --git a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json index a9d0ebeef..278f58a6c 100644 --- a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json +++ b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-sponge--sponge-vanilla.json" }, - "exported_at": "2024-06-04T22:50:55+00:00", + "exported_at": "2024-07-03T14:34:02+00:00", "name": "Sponge (SpongeVanilla)", "author": "panel@example.com", "uuid": "f0d2f88f-1ff3-42a0-b03f-ac44c5571e6d", @@ -45,7 +45,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -56,8 +56,8 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", - "sort": null, + "sort": 2, "field_type": "text" } ] -} +} \ No newline at end of file diff --git a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json index 63036eda0..94f257863 100644 --- a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-vanilla-minecraft.json" }, - "exported_at": "2024-06-04T22:51:16+00:00", + "exported_at": "2024-07-03T14:34:02+00:00", "name": "Vanilla Minecraft", "author": "panel@example.com", "uuid": "9ac39f3d-0c34-4d93-8174-c52ab9e6c57b", @@ -45,7 +45,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -56,7 +56,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|between:3,15", - "sort": null, + "sort": 2, "field_type": "text" } ] diff --git a/database/Seeders/eggs/rust/egg-rust.json b/database/Seeders/eggs/rust/egg-rust.json index 151bd7210..5b763bd7c 100644 --- a/database/Seeders/eggs/rust/egg-rust.json +++ b/database/Seeders/eggs/rust/egg-rust.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/rust\/egg-rust.json" }, - "exported_at": "2024-06-02T20:42:09+00:00", + "exported_at": "2024-07-03T14:34:09+00:00", "name": "Rust", "author": "panel@example.com", "uuid": "bace2dfb-209c-452a-9459-7d6f340b07ae", @@ -39,7 +39,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|max:60", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -50,7 +50,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|in:vanilla,oxide,carbon", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -61,7 +61,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|max:20", - "sort": null, + "sort": 3, "field_type": "text" }, { @@ -72,7 +72,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string", - "sort": null, + "sort": 4, "field_type": "text" }, { @@ -83,7 +83,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|url", - "sort": null, + "sort": 5, "field_type": "text" }, { @@ -94,7 +94,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|integer", - "sort": null, + "sort": 6, "field_type": "text" }, { @@ -105,7 +105,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|string", - "sort": null, + "sort": 7, "field_type": "text" }, { @@ -116,7 +116,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|integer", - "sort": null, + "sort": 8, "field_type": "text" }, { @@ -127,7 +127,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|url", - "sort": null, + "sort": 9, "field_type": "text" }, { @@ -138,7 +138,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|integer", - "sort": null, + "sort": 10, "field_type": "text" }, { @@ -149,7 +149,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|integer", - "sort": null, + "sort": 11, "field_type": "text" }, { @@ -160,7 +160,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^[\\w.-]*$\/|max:64", - "sort": null, + "sort": 12, "field_type": "text" }, { @@ -171,7 +171,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|integer", - "sort": null, + "sort": 13, "field_type": "text" }, { @@ -182,7 +182,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|string", - "sort": null, + "sort": 14, "field_type": "text" }, { @@ -193,7 +193,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|integer", - "sort": null, + "sort": 15, "field_type": "text" }, { @@ -204,7 +204,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|url", - "sort": null, + "sort": 16, "field_type": "text" }, { @@ -215,7 +215,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|url", - "sort": null, + "sort": 17, "field_type": "text" }, { @@ -226,7 +226,7 @@ "user_viewable": false, "user_editable": false, "rules": "required|string|in:258550", - "sort": null, + "sort": 18, "field_type": "text" } ] diff --git a/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json b/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json index a73d2ff98..6f450834c 100644 --- a/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json +++ b/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-counter--strike--global-offensive.json" }, - "exported_at": "2024-06-02T20:42:04+00:00", + "exported_at": "2024-07-03T14:34:03+00:00", "name": "Counter-Strike: Global Offensive", "author": "panel@example.com", "uuid": "437c367d-06be-498f-a604-fdad135504d7", @@ -40,7 +40,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|alpha_dash", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -51,7 +51,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|alpha_num|size:32", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -62,7 +62,7 @@ "user_viewable": false, "user_editable": false, "rules": "required|string|max:20", - "sort": null, + "sort": 3, "field_type": "text" } ] diff --git a/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json b/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json index f1517fffb..f2e8bc088 100644 --- a/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json +++ b/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-custom-source-engine-game.json" }, - "exported_at": "2024-06-02T20:42:04+00:00", + "exported_at": "2024-07-03T14:34:04+00:00", "name": "Custom Source Engine Game", "author": "panel@example.com", "uuid": "2a42d0c2-c0ba-4067-9a0a-9b95d77a3490", @@ -39,7 +39,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|numeric|digits_between:1,6", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -50,7 +50,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|alpha_dash|between:1,100", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -61,7 +61,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|alpha_dash", - "sort": null, + "sort": 3, "field_type": "text" }, { @@ -72,7 +72,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|string", - "sort": null, + "sort": 4, "field_type": "text" }, { @@ -83,7 +83,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|string", - "sort": null, + "sort": 5, "field_type": "text" }, { @@ -94,7 +94,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|string", - "sort": null, + "sort": 6, "field_type": "text" } ] diff --git a/database/Seeders/eggs/source-engine/egg-garrys-mod.json b/database/Seeders/eggs/source-engine/egg-garrys-mod.json index 782d24a7b..60a6c4298 100644 --- a/database/Seeders/eggs/source-engine/egg-garrys-mod.json +++ b/database/Seeders/eggs/source-engine/egg-garrys-mod.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-garrys-mod.json" }, - "exported_at": "2024-06-02T20:42:05+00:00", + "exported_at": "2024-07-03T14:34:04+00:00", "name": "Garrys Mod", "author": "panel@example.com", "uuid": "60ef81d4-30a2-4d98-ab64-f59c69e2f915", @@ -40,7 +40,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|alpha_dash", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -51,7 +51,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|string|alpha_num|size:32", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -62,7 +62,7 @@ "user_viewable": false, "user_editable": false, "rules": "required|string|max:20", - "sort": null, + "sort": 3, "field_type": "text" }, { @@ -73,7 +73,7 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|integer", - "sort": null, + "sort": 4, "field_type": "text" }, { @@ -84,7 +84,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string", - "sort": null, + "sort": 5, "field_type": "text" }, { @@ -95,7 +95,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|integer|max:128", - "sort": null, + "sort": 6, "field_type": "text" }, { @@ -106,7 +106,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|integer|max:100", - "sort": null, + "sort": 7, "field_type": "text" }, { @@ -117,7 +117,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|boolean", - "sort": null, + "sort": 8, "field_type": "text" } ] diff --git a/database/Seeders/eggs/source-engine/egg-insurgency.json b/database/Seeders/eggs/source-engine/egg-insurgency.json index f9bb02e5c..b246dabbe 100644 --- a/database/Seeders/eggs/source-engine/egg-insurgency.json +++ b/database/Seeders/eggs/source-engine/egg-insurgency.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-insurgency.json" }, - "exported_at": "2024-06-02T20:42:06+00:00", + "exported_at": "2024-07-03T14:34:05+00:00", "name": "Insurgency", "author": "panel@example.com", "uuid": "a5702286-655b-4069-bf1e-925c7300b61a", @@ -39,7 +39,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|regex:\/^(237410)$\/", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -50,7 +50,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^(\\w{1,20})$\/", - "sort": null, + "sort": 2, "field_type": "text" } ] diff --git a/database/Seeders/eggs/source-engine/egg-team-fortress2.json b/database/Seeders/eggs/source-engine/egg-team-fortress2.json index 5a3fcae96..24795824c 100644 --- a/database/Seeders/eggs/source-engine/egg-team-fortress2.json +++ b/database/Seeders/eggs/source-engine/egg-team-fortress2.json @@ -2,9 +2,9 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-team-fortress2.json" }, - "exported_at": "2024-06-02T20:42:07+00:00", + "exported_at": "2024-07-03T14:34:06+00:00", "name": "Team Fortress 2", "author": "panel@example.com", "uuid": "7f8eb681-b2c8-4bf8-b9f4-d79ff70b6e5d", @@ -40,7 +40,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|in:232250", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -51,7 +51,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|regex:\/^(\\w{1,20})$\/", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -62,7 +62,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|alpha_num|size:32", - "sort": null, + "sort": 3, "field_type": "text" } ] diff --git a/database/Seeders/eggs/voice-servers/egg-mumble-server.json b/database/Seeders/eggs/voice-servers/egg-mumble-server.json index fd7ecf679..6254b856a 100644 --- a/database/Seeders/eggs/voice-servers/egg-mumble-server.json +++ b/database/Seeders/eggs/voice-servers/egg-mumble-server.json @@ -2,14 +2,14 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/voice-servers\/egg-mumble-server.json" }, - "exported_at": "2024-06-04T22:53:03+00:00", + "exported_at": "2024-07-03T14:34:07+00:00", "name": "Mumble Server", "author": "panel@example.com", "uuid": "727ee758-7fb2-4979-972b-d3eba4e1e9f0", "description": "Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.", - "features": null, + "features": [], "docker_images": { "Mumble": "ghcr.io\/parkervcp\/yolks:voice_mumble" }, @@ -37,7 +37,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|numeric|digits_between:1,5", - "sort": null, + "sort": 1, "field_type": "text" } ] diff --git a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json index 5c6199c98..949d6a03d 100644 --- a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json +++ b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json @@ -2,14 +2,14 @@ "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL", "meta": { "version": "PTDL_v2", - "update_url": null + "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/voice-servers\/egg-teamspeak3-server.json" }, - "exported_at": "2024-06-02T20:42:08+00:00", + "exported_at": "2024-07-03T14:34:08+00:00", "name": "Teamspeak3 Server", "author": "panel@example.com", "uuid": "983b1fac-d322-4d5f-a636-436127326b37", "description": "VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.", - "features": null, + "features": [], "docker_images": { "Debian": "ghcr.io\/parkervcp\/yolks:debian" }, @@ -37,7 +37,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|max:6", - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -48,7 +48,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|integer|between:1025,65535", - "sort": null, + "sort": 2, "field_type": "text" }, { @@ -59,7 +59,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|integer|between:1025,65535", - "sort": null, + "sort": 3, "field_type": "text" }, { @@ -70,7 +70,7 @@ "user_viewable": true, "user_editable": true, "rules": "required|string|max:12", - "sort": null, + "sort": 4, "field_type": "text" }, { @@ -81,7 +81,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|integer|between:1025,65535", - "sort": null, + "sort": 5, "field_type": "text" }, { @@ -92,7 +92,7 @@ "user_viewable": true, "user_editable": false, "rules": "required|integer|between:1025,65535", - "sort": null, + "sort": 6, "field_type": "text" } ] From 8ba15538a9db364de6ce453fee1687186f43e6f6 Mon Sep 17 00:00:00 2001 From: notCharles Date: Wed, 3 Jul 2024 16:33:32 -0400 Subject: [PATCH 06/65] Fix ToolTip --- app/Filament/Resources/EggResource/Pages/CreateEgg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Resources/EggResource/Pages/CreateEgg.php b/app/Filament/Resources/EggResource/Pages/CreateEgg.php index 16f94be53..d73863f57 100644 --- a/app/Filament/Resources/EggResource/Pages/CreateEgg.php +++ b/app/Filament/Resources/EggResource/Pages/CreateEgg.php @@ -75,7 +75,7 @@ class CreateEgg extends CreateRecord ->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]), TextInput::make('update_url') ->hintIcon('tabler-question-mark') - ->hintToolTip('URLs must point directly to the raw .json file.') + ->hintIconTooltip('URLs must point directly to the raw .json file.') ->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]) ->url(), KeyValue::make('docker_images') From 07735464c7b190932be1a9c74006e96b3e3fd33e Mon Sep 17 00:00:00 2001 From: Boy132 Date: Fri, 5 Jul 2024 01:15:45 +0200 Subject: [PATCH 07/65] Add contributing guide (#460) --- contributing.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 contributing.md diff --git a/contributing.md b/contributing.md new file mode 100644 index 000000000..5c9ee66c8 --- /dev/null +++ b/contributing.md @@ -0,0 +1,54 @@ +# Contributing + +Welcome to the Pelican project! We are excited to have you contribute to our open-source project. This guide will help you get started with setting up your development environment, understanding our coding standards, and making your first or next contribution. + +## Getting started + +To start contributing to Pelican Panel, you need to have a basic understanding of the following: + +* [PHP](https://php.net) & [Laravel](https://laravel.com) +* [Livewire](https://laravel-livewire.com) & [Filament](https://filamentphp.com) +* [Git](https://git-scm.com) & [Github](https://github.com) + +## Dev Environment Setup + +1. Fork the Repository +2. Clone your Fork +3. Install Dependencies (PHP modules & composer, and run `composer install`) +4. Configure your Environment (via `php artisan p:environment:setup`) +5. Set up your Database (via `php artisan p:environment:database`) and run Migrations (via `php artisan migrate --seed --force`) +6. Create your first Admin User (via `php artisan p:user:make`) +7. Start your Webserver (e.g. Nginx or Apache) + +As IDE we recommend [Visual Studio](https://visualstudio.microsoft.com)/ [Visual Studio Code](https://code.visualstudio.com) (free) or [PhpStorm](https://www.jetbrains.com/phpstorm) (paid). + +To easily install PHP and the Webserver we recommend Laravel Herd. ([Windows](https://herd.laravel.com/windows) & [macOS](https://herd.laravel.com)) +The (paid) Pro version of Laravel Herd also offers easy MySQL and Redis hosting, but it is not needed. + +## Coding Standards + +We use PHPStan/ [Larastan](https://github.com/larastan/larastan) and PHP-CS-Fixer/ [Pint](https://laravel.com/docs/11.x/pint) to enforce certain code styles and standards. +You can run PHPStan via `\vendor\bin\phpstan analyse` and Pint via `\vendor\bin\pint`. + +## Making Contributions + +From your forked repository, make your own changes on your own branch. (do not make changes directly to `main`!) +When you are ready, you can submit a pull request to the Pelican repository. If you still work on your pull request or need help with something make sure to mark it as Draft. + +Also, please make sure that your pull requests are as targeted and simple as possible and don't do a hundred things at a time. If you want to add/ change/ fix 5 different things you should make 5 different pull requests. + +*Note: For now we only accept pull requests that handle existing issues!* + +## Code Review Process + +Your pull request will then be reviewed by the maintainers. +Once you have an approval from a maintainer, another will merge it once it’s confirmed. + +Depending on the pull request size this process can take multiple days. + +## Community and Support + +* Help: [Discord](https://discord.gg/pelican-panel) +* Bugs: [GitHub Issues](https://github.com/pelican-dev/panel/issues) +* Features: [GitHub Discussions](https://github.com/pelican-dev/panel/discussions) +* Security vulnerabilities: See our [security policy](./security.md). From 7557dc1c8d0fa79581a82c553cde15eda3940059 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Fri, 5 Jul 2024 16:17:35 +0200 Subject: [PATCH 08/65] Restart queue worker when changing email settings (#457) --- app/Console/Commands/Environment/EmailSettingsCommand.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Console/Commands/Environment/EmailSettingsCommand.php b/app/Console/Commands/Environment/EmailSettingsCommand.php index b67a5ed77..1059cf780 100644 --- a/app/Console/Commands/Environment/EmailSettingsCommand.php +++ b/app/Console/Commands/Environment/EmailSettingsCommand.php @@ -61,6 +61,8 @@ class EmailSettingsCommand extends Command $this->writeToEnvironment($this->variables); + $this->call('queue:restart'); + $this->line('Updating stored environment configuration file.'); $this->line(''); } From 212c93c2ba904682249def55a500feb6b233efbf Mon Sep 17 00:00:00 2001 From: notCharles Date: Fri, 5 Jul 2024 18:24:07 -0400 Subject: [PATCH 09/65] Fix #462 --- .../Resources/DatabaseHostResource/Pages/EditDatabaseHost.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Filament/Resources/DatabaseHostResource/Pages/EditDatabaseHost.php b/app/Filament/Resources/DatabaseHostResource/Pages/EditDatabaseHost.php index 733fcac61..8cb4929f0 100644 --- a/app/Filament/Resources/DatabaseHostResource/Pages/EditDatabaseHost.php +++ b/app/Filament/Resources/DatabaseHostResource/Pages/EditDatabaseHost.php @@ -64,8 +64,7 @@ class EditDatabaseHost extends EditRecord ->helperText('The password for the database user.') ->password() ->revealable() - ->maxLength(255) - ->required(), + ->maxLength(255), Select::make('node_id') ->searchable() ->preload() From 7dad2d0e42cc9d89ac4f0f2e6f1c561765d2abc2 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sun, 7 Jul 2024 19:33:25 -0400 Subject: [PATCH 10/65] Fix #464 --- .../components/dashboard/forms/UpdateEmailAddressForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx index 2258e67b9..7980f0d7a 100644 --- a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx @@ -70,7 +70,7 @@ export default () => { id={'confirm_password'} type={'password'} name={'password'} - label={t('confirm_password', { ns: 'strings' })} + label={t('current_password', { ns: 'strings' })} />
From 1c1c8c0cc617fd65953771624c9eec6adc90e364 Mon Sep 17 00:00:00 2001 From: Exotical Date: Wed, 10 Jul 2024 06:30:12 +0200 Subject: [PATCH 11/65] Fix client Activity tab issues; fixes #465 (#466) * Remove deploy.locations from validator * Change location data to optional for backwards compat * Better styling * Add back comma to follow coding style * Remove EventServiceProvider from providers file Fixes duplicated auth messages in the client Activity tab. * Add null check on $model->actor Prevents the client Activity tab page from breaking when an authentication attempt has failed. * Proper type checking on $model->actor Chose instanceof as it seems to be the best in terms of type safety. Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> * Revert removal of EventServiceProvider * Remove subscription of AuthenticationListener * Remove subscriptions for auth events * Remove unused import Dispatcher * Remove unused import AuthenticationListener --------- Co-authored-by: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> --- app/Listeners/Auth/AuthenticationListener.php | 7 ------- app/Providers/EventServiceProvider.php | 5 ----- app/Transformers/Api/Client/ActivityLogTransformer.php | 2 +- bootstrap/providers.php | 1 - 4 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/Listeners/Auth/AuthenticationListener.php b/app/Listeners/Auth/AuthenticationListener.php index 01a8e35c0..b06428bd2 100644 --- a/app/Listeners/Auth/AuthenticationListener.php +++ b/app/Listeners/Auth/AuthenticationListener.php @@ -5,7 +5,6 @@ namespace App\Listeners\Auth; use App\Facades\Activity; use Illuminate\Auth\Events\Failed; use App\Events\Auth\DirectLogin; -use Illuminate\Events\Dispatcher; class AuthenticationListener { @@ -28,10 +27,4 @@ class AuthenticationListener $activity->event($event instanceof Failed ? 'auth:fail' : 'auth:success')->log(); } - - public function subscribe(Dispatcher $events): void - { - $events->listen(Failed::class, self::class); - $events->listen(DirectLogin::class, self::class); - } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 88b8a3d01..e8b9a9a0d 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -10,7 +10,6 @@ use App\Observers\UserObserver; use App\Observers\ServerObserver; use App\Observers\SubuserObserver; use App\Observers\EggVariableObserver; -use App\Listeners\Auth\AuthenticationListener; use App\Events\Server\Installed as ServerInstalledEvent; use App\Notifications\ServerInstalled as ServerInstalledNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -24,10 +23,6 @@ class EventServiceProvider extends ServiceProvider ServerInstalledEvent::class => [ServerInstalledNotification::class], ]; - protected $subscribe = [ - AuthenticationListener::class, - ]; - /** * Register any events for your application. */ diff --git a/app/Transformers/Api/Client/ActivityLogTransformer.php b/app/Transformers/Api/Client/ActivityLogTransformer.php index af666beb6..488ad1c95 100644 --- a/app/Transformers/Api/Client/ActivityLogTransformer.php +++ b/app/Transformers/Api/Client/ActivityLogTransformer.php @@ -55,7 +55,7 @@ class ActivityLogTransformer extends BaseClientTransformer $properties = $model->properties ->mapWithKeys(function ($value, $key) use ($model) { - if ($key === 'ip' && !$model->actor->is($this->request->user())) { + if ($key === 'ip' && $model->actor instanceof User && !$model->actor->is($this->request->user())) { return [$key => '[hidden]']; } diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 8c37bb7b5..a20d8785c 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -8,6 +8,5 @@ return [ App\Providers\Filament\AdminPanelProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\ViewComposerServiceProvider::class, - SocialiteProviders\Manager\ServiceProvider::class, ]; From 447e889a4fa9b1665ec0bc32c7cc06e36270ca24 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 10 Jul 2024 08:36:24 +0200 Subject: [PATCH 12/65] Fix default timestamp for activity logs (#468) * fix default timestamp for activity logs * fix phpstan --- app/Models/ActivityLog.php | 4 +++ ...948_fix-activity-log-timestamp-default.php | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 database/migrations/2024_07_08_112948_fix-activity-log-timestamp-default.php diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index 06fd2d108..bb6149c14 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -140,6 +140,10 @@ class ActivityLog extends Model { parent::boot(); + static::creating(function (self $model) { + $model->timestamp = Carbon::now(); + }); + static::created(function (self $model) { Event::dispatch(new ActivityLogged($model)); }); diff --git a/database/migrations/2024_07_08_112948_fix-activity-log-timestamp-default.php b/database/migrations/2024_07_08_112948_fix-activity-log-timestamp-default.php new file mode 100644 index 000000000..892ca1826 --- /dev/null +++ b/database/migrations/2024_07_08_112948_fix-activity-log-timestamp-default.php @@ -0,0 +1,28 @@ +timestamp('timestamp')->default(null)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('activity_logs', function (Blueprint $table) { + $table->timestamp('timestamp')->useCurrent()->change(); + }); + } +}; From bb7c0e0e66845802af577ecc488c3ccbae72eb50 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 10 Jul 2024 09:25:15 +0200 Subject: [PATCH 13/65] Add "Delete files" task (#470) * started "delete files" task * add logic to DeleteFilesService * add frontend * make nicer * move description to right place --- .../Servers/Schedules/StoreTaskRequest.php | 2 +- app/Jobs/Schedule/RunTaskJob.php | 7 +++- app/Models/Task.php | 1 + app/Services/Files/DeleteFilesService.php | 41 +++++++++++++++++++ .../server/schedules/ScheduleTaskRow.tsx | 6 +++ .../server/schedules/TaskDetailsModal.tsx | 15 ++++++- 6 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 app/Services/Files/DeleteFilesService.php diff --git a/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php index a510c1b5f..190d3e54f 100644 --- a/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php @@ -19,7 +19,7 @@ class StoreTaskRequest extends ViewScheduleRequest public function rules(): array { return [ - 'action' => 'required|in:command,power,backup', + 'action' => 'required|in:command,power,backup,delete_files', 'payload' => 'required_unless:action,backup|string|nullable', 'time_offset' => 'required|numeric|min:0|max:900', 'sequence_id' => 'sometimes|required|numeric|min:1', diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 0d2b255a6..6a660e5c0 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -12,6 +12,7 @@ use Illuminate\Foundation\Bus\DispatchesJobs; use App\Services\Backups\InitiateBackupService; use App\Repositories\Daemon\DaemonPowerRepository; use App\Exceptions\Http\Connection\DaemonConnectionException; +use App\Services\Files\DeleteFilesService; class RunTaskJob extends Job implements ShouldQueue { @@ -34,7 +35,8 @@ class RunTaskJob extends Job implements ShouldQueue */ public function handle( InitiateBackupService $backupService, - DaemonPowerRepository $powerRepository + DaemonPowerRepository $powerRepository, + DeleteFilesService $deleteFilesService ): void { // Do not process a task that is not set to active, unless it's been manually triggered. if (!$this->task->schedule->is_active && !$this->manualRun) { @@ -67,6 +69,9 @@ class RunTaskJob extends Job implements ShouldQueue case Task::ACTION_BACKUP: $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); break; + case Task::ACTION_DELETE_FILES: + $deleteFilesService->handle($server, explode(PHP_EOL, $this->task->payload)); + break; default: throw new \InvalidArgumentException('Invalid task action provided: ' . $this->task->action); } diff --git a/app/Models/Task.php b/app/Models/Task.php index 6545ab5f5..254d38ece 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -33,6 +33,7 @@ class Task extends Model public const ACTION_POWER = 'power'; public const ACTION_COMMAND = 'command'; public const ACTION_BACKUP = 'backup'; + public const ACTION_DELETE_FILES = 'delete_files'; /** * The table associated with the model. diff --git a/app/Services/Files/DeleteFilesService.php b/app/Services/Files/DeleteFilesService.php new file mode 100644 index 000000000..2b1be5fda --- /dev/null +++ b/app/Services/Files/DeleteFilesService.php @@ -0,0 +1,41 @@ +daemonFileRepository->setServer($server)->getDirectory($path))->each(function ($item) use ($path, $pattern, $filesToDelete) { + if (Str::is($pattern, $item['name'])) { + $filesToDelete->push($path . '/' . $item['name']); + } + }); + } + + if ($filesToDelete->isNotEmpty()) { + $this->daemonFileRepository->setServer($server)->deleteFiles('/', $filesToDelete->toArray()); + } + } +} diff --git a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx index 36db1fff9..ee638e3f9 100644 --- a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx @@ -9,6 +9,7 @@ import { faPencilAlt, faToggleOn, faTrashAlt, + faTrash, } from '@fortawesome/free-solid-svg-icons'; import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask'; import { httpErrorToHuman } from '@/api/http'; @@ -35,6 +36,8 @@ const getActionDetails = (action: string): [string, any] => { return ['Send Power Action', faToggleOn]; case 'backup': return ['Create Backup', faFileArchive]; + case 'delete_files': + return ['Delete Files', faTrash]; default: return ['Unknown Action', faCode]; } @@ -94,6 +97,9 @@ export default ({ schedule, task }: Props) => { {task.action === 'backup' && (

Ignoring files & folders:

)} + {task.action === 'delete_files' && ( +

Files to delete:

+ )}
diff --git a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx index 0af8fb441..4903df931 100644 --- a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx +++ b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx @@ -34,7 +34,7 @@ interface Values { } const schema = object().shape({ - action: string().required().oneOf(['command', 'power', 'backup']), + action: string().required().oneOf(['command', 'power', 'backup', 'delete_files']), payload: string().when('action', { is: (v) => v !== 'backup', then: string().required('A task payload must be provided.'), @@ -131,6 +131,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => { +
@@ -166,7 +167,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
- ) : ( + ) : values.action === 'backup' ? (
{
+ ) : ( +
+ + + + +
)}
From 1fdff43ae7fbbfdefa46ba48b4700be4f791b6c7 Mon Sep 17 00:00:00 2001 From: Charles Date: Sun, 14 Jul 2024 16:48:14 -0400 Subject: [PATCH 14/65] Add Node CPU/Memory Graphs (#459) * Update Node Stats Soon TM * Update * Make these smaller * Change graphs * Remove this. Didn't work anyways. * Update Graphs * Use User TZ and config var * Fix math * Change to per thread. --- app/Console/Kernel.php | 3 + .../Resources/NodeResource/Pages/EditNode.php | 47 +++++++--- .../NodeResource/Widgets/NodeCpuChart.php | 81 +++++++++++++++++ .../NodeResource/Widgets/NodeMemoryChart.php | 87 +++++++++++-------- .../NodeResource/Widgets/NodeStorageChart.php | 4 +- app/Jobs/NodeStatistics.php | 46 ++++++++++ .../components/node-cpu-chart.blade.php | 3 + .../components/node-memory-chart.blade.php | 3 + .../components/node-storage-chart.blade.php | 3 + 9 files changed, 229 insertions(+), 48 deletions(-) create mode 100644 app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php create mode 100644 app/Jobs/NodeStatistics.php create mode 100644 resources/views/filament/components/node-cpu-chart.blade.php create mode 100644 resources/views/filament/components/node-memory-chart.blade.php create mode 100644 resources/views/filament/components/node-storage-chart.blade.php diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 1dd998646..5b8505970 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,6 +2,7 @@ namespace App\Console; +use App\Jobs\NodeStatistics; use App\Models\ActivityLog; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Database\Console\PruneCommand; @@ -32,6 +33,8 @@ class Kernel extends ConsoleKernel $schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping(); $schedule->command(CleanServiceBackupFilesCommand::class)->daily(); + $schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping(); + if (config('backups.prune_age')) { // Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted. $schedule->command(PruneOrphanedBackupsCommand::class)->everyThirtyMinutes(); diff --git a/app/Filament/Resources/NodeResource/Pages/EditNode.php b/app/Filament/Resources/NodeResource/Pages/EditNode.php index 94ea3a8a4..c6fd0e451 100644 --- a/app/Filament/Resources/NodeResource/Pages/EditNode.php +++ b/app/Filament/Resources/NodeResource/Pages/EditNode.php @@ -3,12 +3,11 @@ namespace App\Filament\Resources\NodeResource\Pages; use App\Filament\Resources\NodeResource; -use App\Filament\Resources\NodeResource\Widgets\NodeMemoryChart; -use App\Filament\Resources\NodeResource\Widgets\NodeStorageChart; use App\Models\Node; use App\Services\Nodes\NodeUpdateService; use Filament\Actions; use Filament\Forms; +use Filament\Forms\Components\Fieldset; use Filament\Forms\Components\Grid; use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Tabs; @@ -17,6 +16,7 @@ use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\ToggleButtons; +use Filament\Forms\Components\View; use Filament\Forms\Get; use Filament\Forms\Set; use Filament\Notifications\Notification; @@ -41,6 +41,32 @@ class EditNode extends EditRecord ->persistTabInQueryString() ->columnSpanFull() ->tabs([ + Tab::make('') + ->label('Overview') + ->icon('tabler-chart-area-line-filled') + ->columns(6) + ->schema([ + Fieldset::make() + ->label('Node Information') + ->columns(4) + ->schema([ + Placeholder::make('') + ->label('Wings Version') + ->content(fn (Node $node) => $node->systemInformation()['version']), + Placeholder::make('') + ->label('CPU Threads') + ->content(fn (Node $node) => $node->systemInformation()['cpu_count']), + Placeholder::make('') + ->label('Architecture') + ->content(fn (Node $node) => $node->systemInformation()['architecture']), + Placeholder::make('') + ->label('Kernel') + ->content(fn (Node $node) => $node->systemInformation()['kernel_version']), + ]), + View::make('filament.components.node-cpu-chart')->columnSpan(3), + View::make('filament.components.node-memory-chart')->columnSpan(3), + // TODO: Make purdy View::make('filament.components.node-storage-chart')->columnSpan(3), + ]), Tab::make('Basic Settings') ->icon('tabler-server') ->schema([ @@ -437,16 +463,17 @@ class EditNode extends EditRecord ]; } - protected function getFooterWidgets(): array - { - return [ - NodeStorageChart::class, - NodeMemoryChart::class, - ]; - } - protected function afterSave(): void { $this->fillForm(); } + + protected function getColumnSpan() + { + return null; + } + protected function getColumnStart() + { + return null; + } } diff --git a/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php b/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php new file mode 100644 index 000000000..ecc6bc592 --- /dev/null +++ b/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php @@ -0,0 +1,81 @@ +record; + $threads = $node->systemInformation()['cpu_count']; + + $cpu = collect(cache()->get("nodes.$node->id.cpu_percent")) + ->slice(-10) + ->map(fn ($value, $key) => [ + 'cpu' => number_format($value * $threads, 2), + 'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'), + ]) + ->all(); + + return [ + 'datasets' => [ + [ + 'data' => array_column($cpu, 'cpu'), + 'backgroundColor' => [ + 'rgba(96, 165, 250, 0.3)', + ], + 'tension' => '0.3', + 'fill' => true, + ], + ], + 'labels' => array_column($cpu, 'timestamp'), + ]; + } + + protected function getType(): string + { + return 'line'; + } + + protected function getOptions(): RawJs + { + return RawJs::make(<<<'JS' + { + scales: { + y: { + min: 0, + }, + }, + plugins: { + legend: { + display: false, + } + } + } + JS); + } + + public function getHeading(): string + { + /** @var Node $node */ + $node = $this->record; + $threads = $node->systemInformation()['cpu_count']; + + $cpu = number_format(collect(cache()->get("nodes.$node->id.cpu_percent"))->last() * $threads, 2); + $max = number_format($threads * 100) . '%'; + + return 'CPU - ' . $cpu . '% Of ' . $max; + } +} diff --git a/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php b/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php index 8ed87046a..3d8762447 100644 --- a/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php +++ b/app/Filament/Resources/NodeResource/Widgets/NodeMemoryChart.php @@ -3,66 +3,83 @@ namespace App\Filament\Resources\NodeResource\Widgets; use App\Models\Node; +use Carbon\Carbon; +use Filament\Support\RawJs; use Filament\Widgets\ChartWidget; use Illuminate\Database\Eloquent\Model; class NodeMemoryChart extends ChartWidget { - protected static ?string $heading = 'Memory'; - - protected static ?string $pollingInterval = '60s'; + protected static ?string $pollingInterval = '5s'; + protected static ?string $maxHeight = '300px'; public ?Model $record = null; - protected static ?array $options = [ - 'scales' => [ - 'x' => [ - 'grid' => [ - 'display' => false, - ], - 'ticks' => [ - 'display' => false, - ], - ], - 'y' => [ - 'grid' => [ - 'display' => false, - ], - 'ticks' => [ - 'display' => false, - ], - ], - ], - ]; - protected function getData(): array { /** @var Node $node */ $node = $this->record; - $total = ($node->statistics()['memory_total'] ?? 0) / 1024 / 1024 / 1024; - $used = ($node->statistics()['memory_used'] ?? 0) / 1024 / 1024 / 1024; - $unused = $total - $used; + $memUsed = collect(cache()->get("nodes.$node->id.memory_used"))->slice(-10) + ->map(fn ($value, $key) => [ + 'memory' => config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, + 'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'), + ]) + ->all(); return [ 'datasets' => [ [ - 'label' => 'Data Cool', - 'data' => [$used, $unused], + 'data' => array_column($memUsed, 'memory'), 'backgroundColor' => [ - 'rgb(255, 99, 132)', - 'rgb(54, 162, 235)', - 'rgb(255, 205, 86)', + 'rgba(96, 165, 250, 0.3)', ], + 'tension' => '0.3', + 'fill' => true, ], - // 'backgroundColor' => [], ], - 'labels' => ['Used', 'Unused'], + 'labels' => array_column($memUsed, 'timestamp'), ]; } protected function getType(): string { - return 'pie'; + return 'line'; + } + + protected function getOptions(): RawJs + { + return RawJs::make(<<<'JS' + { + scales: { + y: { + min: 0, + }, + }, + plugins: { + legend: { + display: false, + } + } + } + JS); + } + + public function getHeading(): string + { + /** @var Node $node */ + $node = $this->record; + $latestMemoryUsed = collect(cache()->get("nodes.$node->id.memory_used"))->last(); + $totalMemory = collect(cache()->get("nodes.$node->id.memory_total"))->last(); + + $used = config('panel.use_binary_prefix') + ? number_format($latestMemoryUsed / 1024 / 1024 / 1024, 2) .' GiB' + : number_format($latestMemoryUsed / 1000 / 1000 / 1000, 2) . ' GB'; + + $total = config('panel.use_binary_prefix') + ? number_format($totalMemory / 1024 / 1024 / 1024, 2) .' GiB' + : number_format($totalMemory / 1000 / 1000 / 1000, 2) . ' GB'; + + return 'Memory - ' . $used . ' Of ' . $total; } } diff --git a/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php b/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php index bcfbfcf4f..b841d84ef 100644 --- a/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php +++ b/app/Filament/Resources/NodeResource/Widgets/NodeStorageChart.php @@ -9,8 +9,8 @@ use Illuminate\Database\Eloquent\Model; class NodeStorageChart extends ChartWidget { protected static ?string $heading = 'Storage'; - protected static ?string $pollingInterval = '60s'; + protected static ?string $maxHeight = '300px'; public ?Model $record = null; @@ -47,7 +47,6 @@ class NodeStorageChart extends ChartWidget return [ 'datasets' => [ [ - 'label' => 'Data Cool', 'data' => [$used, $unused], 'backgroundColor' => [ 'rgb(255, 99, 132)', @@ -55,7 +54,6 @@ class NodeStorageChart extends ChartWidget 'rgb(255, 205, 86)', ], ], - // 'backgroundColor' => [], ], 'labels' => ['Used', 'Unused'], ]; diff --git a/app/Jobs/NodeStatistics.php b/app/Jobs/NodeStatistics.php new file mode 100644 index 000000000..19fae9b9d --- /dev/null +++ b/app/Jobs/NodeStatistics.php @@ -0,0 +1,46 @@ +statistics(); + $timestamp = now()->getTimestamp(); + + foreach ($stats as $key => $value) { + $cacheKey = "nodes.{$node->id}.$key"; + $data = cache()->get($cacheKey, []); + + // Add current timestamp and value to the data array + $data[$timestamp] = $value; + + // Update the cache with the new data, expires in 1 minute + cache()->put($cacheKey, $data, now()->addMinute()); + } + } + } + +} diff --git a/resources/views/filament/components/node-cpu-chart.blade.php b/resources/views/filament/components/node-cpu-chart.blade.php new file mode 100644 index 000000000..d2627c42d --- /dev/null +++ b/resources/views/filament/components/node-cpu-chart.blade.php @@ -0,0 +1,3 @@ + + @livewire(\App\Filament\Resources\NodeResource\Widgets\NodeCpuChart::class, ['record'=> $getRecord()]) + diff --git a/resources/views/filament/components/node-memory-chart.blade.php b/resources/views/filament/components/node-memory-chart.blade.php new file mode 100644 index 000000000..cb934d007 --- /dev/null +++ b/resources/views/filament/components/node-memory-chart.blade.php @@ -0,0 +1,3 @@ + + @livewire(\App\Filament\Resources\NodeResource\Widgets\NodeMemoryChart::class, ['record'=> $getRecord()]) + diff --git a/resources/views/filament/components/node-storage-chart.blade.php b/resources/views/filament/components/node-storage-chart.blade.php new file mode 100644 index 000000000..ea7b5358c --- /dev/null +++ b/resources/views/filament/components/node-storage-chart.blade.php @@ -0,0 +1,3 @@ + + @livewire(\App\Filament\Resources\NodeResource\Widgets\NodeStorageChart::class, ['record'=> $getRecord()]) + From 833ae30e593ef32032103ac47b0b4998a2339386 Mon Sep 17 00:00:00 2001 From: Charles Date: Mon, 15 Jul 2024 19:09:52 -0400 Subject: [PATCH 15/65] Add timeouts (#483) * Add timeouts Add Timeouts to github call. * use config value --- app/Services/Helpers/SoftwareVersionService.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index c66d85904..cb7021a74 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -89,13 +89,23 @@ class SoftwareVersionService $versionData = []; try { - $response = $this->client->request('GET', 'https://api.github.com/repos/pelican-dev/panel/releases/latest'); + $response = $this->client->request('GET', 'https://api.github.com/repos/pelican-dev/panel/releases/latest', + [ + 'timeout' => config('panel.guzzle.timeout'), + 'connect_timeout' => config('panel.guzzle.connect_timeout'), + ] + ); if ($response->getStatusCode() === 200) { $panelData = json_decode($response->getBody(), true); $versionData['panel'] = trim($panelData['tag_name'], 'v'); } - $response = $this->client->request('GET', 'https://api.github.com/repos/pelican-dev/wings/releases/latest'); + $response = $this->client->request('GET', 'https://api.github.com/repos/pelican-dev/wings/releases/latest', + [ + 'timeout' => config('panel.guzzle.timeout'), + 'connect_timeout' => config('panel.guzzle.connect_timeout'), + ] + ); if ($response->getStatusCode() === 200) { $wingsData = json_decode($response->getBody(), true); $versionData['daemon'] = trim($wingsData['tag_name'], 'v'); From 8a3d67ada06500025537e142b500b38b64b6ab8c Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 17 Jul 2024 13:00:54 +0200 Subject: [PATCH 16/65] Fix update egg from url (#492) --- app/Filament/Resources/EggResource/Pages/EditEgg.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Filament/Resources/EggResource/Pages/EditEgg.php b/app/Filament/Resources/EggResource/Pages/EditEgg.php index c2282ea80..3fdf0854e 100644 --- a/app/Filament/Resources/EggResource/Pages/EditEgg.php +++ b/app/Filament/Resources/EggResource/Pages/EditEgg.php @@ -249,9 +249,9 @@ class EditEgg extends EditRecord Tab::make('From URL') ->icon('tabler-world-upload') ->schema([ - TextInput::make('update_url') + TextInput::make('url') ->label('URL') - ->formatStateUsing(fn (Egg $egg): string => $egg->update_url) + ->default(fn (Egg $egg): string => $egg->update_url) ->hint('Link to the egg file (eg. minecraft.json)') ->url(), ]), From a04937d6984bff8a0636c4da70f9ef81f2898d7d Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 17 Jul 2024 13:01:13 +0200 Subject: [PATCH 17/65] Fix `PORT_FLOOR` check and `CIDR_MAX_BITS` in AssignmentService (#491) * fix max cidr * fix port floor --- app/Services/Allocations/AssignmentService.php | 6 +++--- lang/en/exceptions.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index 3788d6bf8..3fd0dc27c 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -14,7 +14,7 @@ use App\Exceptions\Service\Allocation\TooManyPortsInRangeException; class AssignmentService { - public const CIDR_MAX_BITS = 27; + public const CIDR_MAX_BITS = 25; public const CIDR_MIN_BITS = 32; public const PORT_FLOOR = 1024; public const PORT_CEIL = 65535; @@ -74,7 +74,7 @@ class AssignmentService throw new TooManyPortsInRangeException(); } - if ((int) $matches[1] <= self::PORT_FLOOR || (int) $matches[2] > self::PORT_CEIL) { + if ((int) $matches[1] < self::PORT_FLOOR || (int) $matches[2] > self::PORT_CEIL) { throw new PortOutOfRangeException(); } @@ -88,7 +88,7 @@ class AssignmentService ]; } } else { - if ((int) $port <= self::PORT_FLOOR || (int) $port > self::PORT_CEIL) { + if ((int) $port < self::PORT_FLOOR || (int) $port > self::PORT_CEIL) { throw new PortOutOfRangeException(); } diff --git a/lang/en/exceptions.php b/lang/en/exceptions.php index 3c9adf4c9..9ec18f9b2 100644 --- a/lang/en/exceptions.php +++ b/lang/en/exceptions.php @@ -11,7 +11,7 @@ return [ 'too_many_ports' => 'Adding more than 1000 ports in a single range at once is not supported.', 'invalid_mapping' => 'The mapping provided for :port was invalid and could not be processed.', 'cidr_out_of_range' => 'CIDR notation only allows masks between /25 and /32.', - 'port_out_of_range' => 'Ports in an allocation must be greater than 1024 and less than or equal to 65535.', + 'port_out_of_range' => 'Ports in an allocation must be greater than or equal to 1024 and less than or equal to 65535.', ], 'egg' => [ 'delete_has_servers' => 'An Egg with active servers attached to it cannot be deleted from the Panel.', From 10806d6d6bcad8a875bdfc7fec7ecce2c6bced6c Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 17 Jul 2024 14:43:04 +0200 Subject: [PATCH 18/65] Fix SQLite foreign keys (#478) * start migration to fix sqlite foreign keys * add remaining foreign keys * add ".sqlite.backup" files to gitignore --- database/.gitignore | 1 + ...095213_fix_missing_sqlite_foreign_keys.php | 284 ++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 database/migrations/2024_07_12_095213_fix_missing_sqlite_foreign_keys.php diff --git a/database/.gitignore b/database/.gitignore index 9b1dffd90..0c40e2f77 100644 --- a/database/.gitignore +++ b/database/.gitignore @@ -1 +1,2 @@ *.sqlite +*.sqlite.backup diff --git a/database/migrations/2024_07_12_095213_fix_missing_sqlite_foreign_keys.php b/database/migrations/2024_07_12_095213_fix_missing_sqlite_foreign_keys.php new file mode 100644 index 000000000..47c78a09a --- /dev/null +++ b/database/migrations/2024_07_12_095213_fix_missing_sqlite_foreign_keys.php @@ -0,0 +1,284 @@ +getDriverName() !== 'sqlite') { + return; + } + + // Disable foreign checks + // legacy_alter_table needs to be 'ON' so existing foreign key table references aren't renamed when renaming the table, see https://www.sqlite.org/lang_altertable.html + DB::statement('PRAGMA foreign_keys = OFF'); + DB::statement('PRAGMA legacy_alter_table = ON'); + + DB::transaction(function () { + // api_keys_user_id_foreign + DB::statement('ALTER TABLE api_keys RENAME TO _api_keys_old'); + DB::statement('CREATE TABLE api_keys + ("id" integer primary key autoincrement not null, + "token" text not null, + "allowed_ips" text not null, + "created_at" datetime, + "updated_at" datetime, + "user_id" integer not null, + "memo" text, + "r_servers" integer not null default \'0\', + "r_nodes" integer not null default \'0\', + "r_allocations" integer not null default \'0\', + "r_users" integer not null default \'0\', + "r_eggs" integer not null default \'0\', + "r_database_hosts" integer not null default \'0\', + "r_server_databases" integer not null default \'0\', + "identifier" varchar, + "key_type" integer not null default \'0\', + "last_used_at" datetime, + "expires_at" datetime, + "r_mounts" integer not null default \'0\', + foreign key("user_id") references "users"("id") on delete cascade)'); + DB::statement('INSERT INTO api_keys SELECT * FROM _api_keys_old'); + DB::statement('DROP TABLE _api_keys_old'); + DB::statement('CREATE UNIQUE INDEX "api_keys_identifier_unique" on "api_keys" ("identifier")'); + + // database_hosts_node_id_foreign + DB::statement('ALTER TABLE database_hosts RENAME TO _database_hosts_old'); + DB::statement('CREATE TABLE database_hosts + ("id" integer primary key autoincrement not null, + "name" varchar not null, + "host" varchar not null, + "port" integer not null, + "username" varchar not null, + "password" text not null, + "max_databases" integer, + "node_id" integer, + "created_at" datetime, + "updated_at" datetime, + foreign key("node_id") references "nodes"("id") on delete set null)'); + DB::statement('INSERT INTO database_hosts SELECT * FROM _database_hosts_old'); + DB::statement('DROP TABLE _database_hosts_old'); + + // mount_node_node_id_foreign + // mount_node_mount_id_foreign + DB::statement('ALTER TABLE mount_node RENAME TO _mount_node_old'); + DB::statement('CREATE TABLE mount_node + ("node_id" integer not null, + "mount_id" integer not null, + foreign key("node_id") references "nodes"("id") on delete cascade on update cascade, + foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)'); + DB::statement('INSERT INTO mount_node SELECT * FROM _mount_node_old'); + DB::statement('DROP TABLE _mount_node_old'); + DB::statement('CREATE UNIQUE INDEX "mount_node_node_id_mount_id_unique" on "mount_node" ("node_id", "mount_id")'); + + // servers_node_id_foreign + // servers_owner_id_foreign + // servers_egg_id_foreign + // servers_allocation_id_foreign + DB::statement('ALTER TABLE servers RENAME TO _servers_old'); + DB::statement('CREATE TABLE servers + ("id" integer primary key autoincrement not null, + "uuid" varchar not null, + "uuid_short" varchar not null, + "node_id" integer not null, + "name" varchar not null, + "owner_id" integer not null, + "memory" integer not null, + "swap" integer not null, + "disk" integer not null, + "io" integer not null, + "cpu" integer not null, + "egg_id" integer not null, + "startup" text not null, + "created_at" datetime, + "updated_at" datetime, + "allocation_id" integer not null, + "image" varchar not null, + "description" text not null, + "skip_scripts" tinyint(1) not null default \'0\', + "external_id" varchar, + "database_limit" integer default \'0\', + "allocation_limit" integer, + "threads" varchar, + "backup_limit" integer not null default \'0\', + "status" varchar, + "installed_at" datetime, + "oom_killer" integer not null default \'0\', + "docker_labels" text, + foreign key("node_id") references "nodes"("id"), + foreign key("owner_id") references "users"("id"), + foreign key("egg_id") references "eggs"("id"), + foreign key("allocation_id") references "allocations"("id"))'); + DB::statement('INSERT INTO servers SELECT * FROM _servers_old'); + DB::statement('DROP TABLE _servers_old'); + DB::statement('CREATE UNIQUE INDEX "servers_allocation_id_unique" on "servers" ("allocation_id")'); + DB::statement('CREATE UNIQUE INDEX "servers_external_id_unique" on "servers" ("external_id")'); + DB::statement('CREATE UNIQUE INDEX "servers_uuid_unique" on "servers" ("uuid")'); + DB::statement('CREATE UNIQUE INDEX "servers_uuidshort_unique" on "servers" ("uuid_short")'); + + // databases_server_id_foreign + // databases_database_host_id_foreign + DB::statement('ALTER TABLE databases RENAME TO _databases_old'); + DB::statement('CREATE TABLE databases + ("id" integer primary key autoincrement not null, + "server_id" integer not null, + "database_host_id" integer not null, + "database" varchar not null, + "username" varchar not null, + "remote" varchar not null default \'%\', + "password" text not null, + "created_at" datetime, + "updated_at" datetime, + "max_connections" integer default \'0\', + foreign key("server_id") references "servers"("id"), + foreign key("database_host_id") references "database_hosts"("id"))'); + DB::statement('INSERT INTO databases SELECT * FROM _databases_old'); + DB::statement('DROP TABLE _databases_old'); + DB::statement('CREATE UNIQUE INDEX "databases_database_host_id_server_id_database_unique" on "databases" ("database_host_id", "server_id", "database")'); + DB::statement('CREATE UNIQUE INDEX "databases_database_host_id_username_unique" on "databases" ("database_host_id", "username")'); + + // allocations_node_id_foreign + // allocations_server_id_foreign + DB::statement('ALTER TABLE allocations RENAME TO _allocations_old'); + DB::statement('CREATE TABLE allocations + ("id" integer primary key autoincrement not null, + "node_id" integer not null, + "ip" varchar not null, + "port" integer not null, + "server_id" integer, + "created_at" datetime, + "updated_at" datetime, + "ip_alias" text, + "notes" varchar, + foreign key("node_id") references "nodes"("id") on delete cascade, + foreign key("server_id") references "servers"("id") on delete cascade on update set null)'); + DB::statement('INSERT INTO allocations SELECT * FROM _allocations_old'); + DB::statement('DROP TABLE _allocations_old'); + DB::statement('CREATE UNIQUE INDEX "allocations_node_id_ip_port_unique" on "allocations" ("node_id", "ip", "port")'); + + // eggs_config_from_foreign + // eggs_copy_script_from_foreign + DB::statement('ALTER TABLE eggs RENAME TO _eggs_old'); + DB::statement('CREATE TABLE eggs + ("id" integer primary key autoincrement not null, + "name" varchar not null, + "description" text, + "created_at" datetime, + "updated_at" datetime, + "startup" text, + "config_from" integer, + "config_stop" varchar, + "config_logs" text, + "config_startup" text, + "config_files" text, + "script_install" text, + "script_is_privileged" tinyint(1) not null default \'1\', + "script_entry" varchar not null default \'ash\', + "script_container" varchar not null default \'alpine:3.4\', + "copy_script_from" integer, + "uuid" varchar not null, + "author" varchar not null, + "features" text, + "docker_images" text, + "update_url" text, + "file_denylist" text, + "force_outgoing_ip" tinyint(1) not null default \'0\', + "tags" text not null, + foreign key("config_from") references "eggs"("id") on delete set null, + foreign key("copy_script_from") references "eggs"("id") on delete set null)'); + DB::statement('INSERT INTO eggs SELECT * FROM _eggs_old'); + DB::statement('DROP TABLE _eggs_old'); + DB::statement('CREATE UNIQUE INDEX "service_options_uuid_unique" on "eggs" ("uuid")'); + + // egg_mount_mount_id_foreign + // egg_mount_egg_id_foreign + DB::statement('ALTER TABLE egg_mount RENAME TO _egg_mount_old'); + DB::statement('CREATE TABLE egg_mount + ("egg_id" integer not null, + "mount_id" integer not null, + foreign key("egg_id") references "eggs"("id") on delete cascade on update cascade, + foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)'); + DB::statement('INSERT INTO egg_mount SELECT * FROM _egg_mount_old'); + DB::statement('DROP TABLE _egg_mount_old'); + DB::statement('CREATE UNIQUE INDEX "egg_mount_egg_id_mount_id_unique" on "egg_mount" ("egg_id", "mount_id")'); + + // service_variables_egg_id_foreign + DB::statement('ALTER TABLE egg_variables RENAME TO _egg_variables_old'); + DB::statement('CREATE TABLE egg_variables + ("id" integer primary key autoincrement not null, + "egg_id" integer not null, + "name" varchar not null, + "description" text not null, + "env_variable" varchar not null, + "default_value" text not null, + "user_viewable" integer not null, + "user_editable" integer not null, + "rules" text not null, + "created_at" datetime, + "updated_at" datetime, + "sort" integer, + foreign key("egg_id") references "eggs"("id") on delete cascade)'); + DB::statement('INSERT INTO egg_variables SELECT * FROM _egg_variables_old'); + DB::statement('DROP TABLE _egg_variables_old'); + + // mount_server_server_id_foreign + // mount_server_mount_id_foreign + DB::statement('ALTER TABLE mount_server RENAME TO _mount_server_old'); + DB::statement('CREATE TABLE mount_server + ("server_id" integer not null, + "mount_id" integer not null, + foreign key("server_id") references "servers"("id") on delete cascade on update cascade, + foreign key("mount_id") references "mounts"("id") on delete cascade on update cascade)'); + DB::statement('INSERT INTO mount_server SELECT * FROM _mount_server_old'); + DB::statement('DROP TABLE _mount_server_old'); + DB::statement('CREATE UNIQUE INDEX "mount_server_server_id_mount_id_unique" on "mount_server" ("server_id", "mount_id")'); + + // server_variables_variable_id_foreign + DB::statement('ALTER TABLE server_variables RENAME TO _server_variables_old'); + DB::statement('CREATE TABLE server_variables + ("id" integer primary key autoincrement not null, + "server_id" integer not null, + "variable_id" integer not null, + "variable_value" text not null, + "created_at" datetime, + "updated_at" datetime, + foreign key("server_id") references "servers"("id") on delete cascade, + foreign key("variable_id") references "egg_variables"("id") on delete cascade)'); + DB::statement('INSERT INTO server_variables SELECT * FROM _server_variables_old'); + DB::statement('DROP TABLE _server_variables_old'); + + // subusers_user_id_foreign + // subusers_server_id_foreign + DB::statement('ALTER TABLE subusers RENAME TO _subusers_old'); + DB::statement('CREATE TABLE subusers + ("id" integer primary key autoincrement not null, + "user_id" integer not null, + "server_id" integer not null, + "created_at" datetime, + "updated_at" datetime, + "permissions" text, + foreign key("user_id") references "users"("id") on delete cascade, + foreign key("server_id") references "servers"("id") on delete cascade)'); + DB::statement('INSERT INTO subusers SELECT * FROM _subusers_old'); + DB::statement('DROP TABLE _subusers_old'); + }); + + DB::statement('PRAGMA foreign_keys = ON'); + DB::statement('PRAGMA legacy_alter_table = OFF'); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Reverse not needed + } +}; From 56b4938dc2b3b38dc731d929e37c869f0c21ab4b Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Wed, 17 Jul 2024 22:22:12 +0200 Subject: [PATCH 19/65] Fix #489 (#490) * Fix #489 * Update app/Filament/Resources/NodeResource/Pages/EditNode.php Co-authored-by: Boy132 * Update app/Filament/Resources/NodeResource/Pages/EditNode.php Co-authored-by: Boy132 * Update app/Filament/Resources/NodeResource/Pages/EditNode.php Co-authored-by: Boy132 --------- Co-authored-by: Boy132 --- app/Filament/Resources/NodeResource/Pages/EditNode.php | 8 ++++---- .../Resources/NodeResource/Widgets/NodeCpuChart.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Filament/Resources/NodeResource/Pages/EditNode.php b/app/Filament/Resources/NodeResource/Pages/EditNode.php index c6fd0e451..d72435fd2 100644 --- a/app/Filament/Resources/NodeResource/Pages/EditNode.php +++ b/app/Filament/Resources/NodeResource/Pages/EditNode.php @@ -52,16 +52,16 @@ class EditNode extends EditRecord ->schema([ Placeholder::make('') ->label('Wings Version') - ->content(fn (Node $node) => $node->systemInformation()['version']), + ->content(fn (Node $node) => $node->systemInformation()['version'] ?? 'Unknown'), Placeholder::make('') ->label('CPU Threads') - ->content(fn (Node $node) => $node->systemInformation()['cpu_count']), + ->content(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0), Placeholder::make('') ->label('Architecture') - ->content(fn (Node $node) => $node->systemInformation()['architecture']), + ->content(fn (Node $node) => $node->systemInformation()['architecture'] ?? 'Unknown'), Placeholder::make('') ->label('Kernel') - ->content(fn (Node $node) => $node->systemInformation()['kernel_version']), + ->content(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? 'Unknown'), ]), View::make('filament.components.node-cpu-chart')->columnSpan(3), View::make('filament.components.node-memory-chart')->columnSpan(3), diff --git a/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php b/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php index ecc6bc592..45d18fc90 100644 --- a/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php +++ b/app/Filament/Resources/NodeResource/Widgets/NodeCpuChart.php @@ -19,7 +19,7 @@ class NodeCpuChart extends ChartWidget { /** @var Node $node */ $node = $this->record; - $threads = $node->systemInformation()['cpu_count']; + $threads = $node->systemInformation()['cpu_count'] ?? 0; $cpu = collect(cache()->get("nodes.$node->id.cpu_percent")) ->slice(-10) @@ -71,7 +71,7 @@ class NodeCpuChart extends ChartWidget { /** @var Node $node */ $node = $this->record; - $threads = $node->systemInformation()['cpu_count']; + $threads = $node->systemInformation()['cpu_count'] ?? 0; $cpu = number_format(collect(cache()->get("nodes.$node->id.cpu_percent"))->last() * $threads, 2); $max = number_format($threads * 100) . '%'; From 56484a2282b5cf68c441b3154e3e411b20ef334b Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sat, 20 Jul 2024 17:18:45 +0200 Subject: [PATCH 20/65] Increase guzzle timeout when running tests (#485) * increase guzzle timeout when running tests * catch correct exception --- .github/workflows/ci.yaml | 6 ++++++ app/Services/Helpers/SoftwareVersionService.php | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 43a644a99..92fea58bd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,6 +41,8 @@ jobs: DB_HOST: 127.0.0.1 DB_DATABASE: testing DB_USERNAME: root + GUZZLE_TIMEOUT: 60 + GUZZLE_CONNECT_TIMEOUT: 60 steps: - name: Code Checkout uses: actions/checkout@v4 @@ -113,6 +115,8 @@ jobs: DB_HOST: 127.0.0.1 DB_DATABASE: testing DB_USERNAME: root + GUZZLE_TIMEOUT: 60 + GUZZLE_CONNECT_TIMEOUT: 60 steps: - name: Code Checkout uses: actions/checkout@v4 @@ -173,6 +177,8 @@ jobs: QUEUE_CONNECTION: sync DB_CONNECTION: sqlite DB_DATABASE: testing.sqlite + GUZZLE_TIMEOUT: 60 + GUZZLE_CONNECT_TIMEOUT: 60 steps: - name: Code Checkout uses: actions/checkout@v4 diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index cb7021a74..7d50203bf 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -4,7 +4,7 @@ namespace App\Services\Helpers; use GuzzleHttp\Client; use Carbon\CarbonImmutable; -use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\GuzzleException; use Illuminate\Support\Arr; use Illuminate\Contracts\Cache\Repository as CacheRepository; @@ -110,7 +110,7 @@ class SoftwareVersionService $wingsData = json_decode($response->getBody(), true); $versionData['daemon'] = trim($wingsData['tag_name'], 'v'); } - } catch (ClientException $e) { + } catch (GuzzleException $e) { } $versionData['discord'] = 'https://pelican.dev/discord'; From dfba8e3993aa93bff98c294fd169ddd74296b362 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sat, 20 Jul 2024 17:23:03 +0200 Subject: [PATCH 21/65] Command to cleanup docker images (#495) * add command to cleanup docker images * automatically cleanup images daily * fix request * fix empty check * run pint --- .../Maintenance/PruneImagesCommand.php | 60 +++++++++++++++++++ app/Console/Kernel.php | 3 + 2 files changed, 63 insertions(+) create mode 100644 app/Console/Commands/Maintenance/PruneImagesCommand.php diff --git a/app/Console/Commands/Maintenance/PruneImagesCommand.php b/app/Console/Commands/Maintenance/PruneImagesCommand.php new file mode 100644 index 000000000..23f613fa3 --- /dev/null +++ b/app/Console/Commands/Maintenance/PruneImagesCommand.php @@ -0,0 +1,60 @@ +argument('node'); + + if (empty($node)) { + $nodes = Node::all(); + /** @var Node $node */ + foreach ($nodes as $node) { + $this->cleanupImages($node); + } + } else { + $this->cleanupImages((int) $node); + } + } + + private function cleanupImages(int|Node $node): void + { + if (!$node instanceof Node) { + $node = Node::query()->findOrFail($node); + } + + try { + $response = Http::daemon($node) + ->connectTimeout(5) + ->timeout(30) + ->delete('/api/system/docker/image/prune') + ->json() ?? []; + + if (empty($response) || $response['ImagesDeleted'] === null) { + $this->warn("Node {$node->id}: No images to clean up."); + + return; + } + + $count = count($response['ImagesDeleted']); + + $useBinaryPrefix = config('panel.use_binary_prefix'); + $space = round($useBinaryPrefix ? $response['SpaceReclaimed'] / 1024 / 1024 : $response['SpaceReclaimed'] / 1000 / 1000, 2) . ($useBinaryPrefix ? ' MiB' : ' MB'); + + $this->info("Node {$node->id}: Cleaned up {$count} dangling docker images. ({$space})"); + } catch (Exception $exception) { + $this->error($exception->getMessage()); + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 5b8505970..4a9bbee17 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use App\Console\Commands\Schedule\ProcessRunnableCommand; use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand; use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand; +use App\Console\Commands\Maintenance\PruneImagesCommand; class Kernel extends ConsoleKernel { @@ -31,7 +32,9 @@ class Kernel extends ConsoleKernel // Execute scheduled commands for servers every minute, as if there was a normal cron running. $schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping(); + $schedule->command(CleanServiceBackupFilesCommand::class)->daily(); + $schedule->command(PruneImagesCommand::class)->daily(); $schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping(); From acf43f28267cab6a269272a9a938f49840616dbf Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Sat, 20 Jul 2024 17:38:34 +0200 Subject: [PATCH 22/65] Ability to create allocations on EditServer page (#494) * Ability to create allocation on edit page + Ability to assign allocation to server on creation * Disable dehydrate for readonly * set these to false --------- Co-authored-by: notCharles --- .../ServerResource/Pages/EditServer.php | 6 +- .../AllocationsRelationManager.php | 88 ++++++++++++++++++- .../UserResource/Pages/EditProfile.php | 2 + .../Allocations/AssignmentService.php | 7 +- 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Resources/ServerResource/Pages/EditServer.php index 7c6ee83a9..c229f658c 100644 --- a/app/Filament/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Resources/ServerResource/Pages/EditServer.php @@ -124,7 +124,8 @@ class EditServer extends EditRecord 'md' => 2, 'lg' => 3, ]) - ->readOnly(), + ->readOnly() + ->dehydrated(false), Forms\Components\TextInput::make('uuid_short') ->label('Short UUID') ->hintAction(CopyAction::make()) @@ -134,7 +135,8 @@ class EditServer extends EditRecord 'md' => 2, 'lg' => 3, ]) - ->readOnly(), + ->readOnly() + ->dehydrated(false), Forms\Components\TextInput::make('external_id') ->label('External ID') ->columnSpan([ diff --git a/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php b/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php index d2615a481..cbac9bf92 100644 --- a/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php +++ b/app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php @@ -4,11 +4,15 @@ namespace App\Filament\Resources\ServerResource\RelationManagers; use App\Models\Allocation; use App\Models\Server; -use Filament\Forms; +use App\Services\Allocations\AssignmentService; +use Filament\Forms\Components\TagsInput; +use Filament\Forms\Components\TextInput; +use Filament\Forms\Set; use Filament\Forms\Form; use Filament\Resources\RelationManagers\RelationManager; use Filament\Tables; use Filament\Tables\Table; +use Illuminate\Support\HtmlString; /** * @method Server getOwnerRecord() @@ -21,7 +25,7 @@ class AllocationsRelationManager extends RelationManager { return $form ->schema([ - Forms\Components\TextInput::make('ip') + TextInput::make('ip') ->required() ->maxLength(255), ]); @@ -62,9 +66,87 @@ class AllocationsRelationManager extends RelationManager ->label(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id ? '' : 'Make Primary'), ]) ->headerActions([ - //TODO Tables\Actions\CreateAction::make()->label('Create Allocation'), + Tables\Actions\CreateAction::make()->label('Create Allocation') + ->createAnother(false) + ->form(fn () => [ + TextInput::make('allocation_ip') + ->datalist($this->getOwnerRecord()->node->ipAddresses()) + ->label('IP Address') + ->inlineLabel() + ->ipv4() + ->helperText("Usually your machine's public IP unless you are port forwarding.") + ->required(), + TextInput::make('allocation_alias') + ->label('Alias') + ->inlineLabel() + ->default(null) + ->helperText('Optional display name to help you remember what these are.') + ->required(false), + TagsInput::make('allocation_ports') + ->placeholder('Examples: 27015, 27017-27019') + ->helperText(new HtmlString(' + These are the ports that users can connect to this Server through. +
+ You would have to port forward these on your home network. + ')) + ->label('Ports') + ->inlineLabel() + ->live() + ->afterStateUpdated(function ($state, Set $set) { + $ports = collect(); + $update = false; + foreach ($state as $portEntry) { + if (!str_contains($portEntry, '-')) { + if (is_numeric($portEntry)) { + $ports->push((int) $portEntry); + + continue; + } + + // Do not add non numerical ports + $update = true; + + continue; + } + + $update = true; + [$start, $end] = explode('-', $portEntry); + if (!is_numeric($start) || !is_numeric($end)) { + continue; + } + + $start = max((int) $start, 0); + $end = min((int) $end, 2 ** 16 - 1); + foreach (range($start, $end) as $i) { + $ports->push($i); + } + } + + $uniquePorts = $ports->unique()->values(); + if ($ports->count() > $uniquePorts->count()) { + $update = true; + $ports = $uniquePorts; + } + + $sortedPorts = $ports->sort()->values(); + if ($sortedPorts->all() !== $ports->all()) { + $update = true; + $ports = $sortedPorts; + } + + $ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values(); + + if ($update) { + $set('allocation_ports', $ports->all()); + } + }) + ->splitKeys(['Tab', ' ', ',']) + ->required(), + ]) + ->action(fn (array $data) => resolve(AssignmentService::class)->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord())), Tables\Actions\AssociateAction::make() ->multiple() + ->associateAnother(false) ->preloadRecordSelect() ->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)) ->label('Add Allocation'), diff --git a/app/Filament/Resources/UserResource/Pages/EditProfile.php b/app/Filament/Resources/UserResource/Pages/EditProfile.php index 7cc2dfde9..d8eb7274f 100644 --- a/app/Filament/Resources/UserResource/Pages/EditProfile.php +++ b/app/Filament/Resources/UserResource/Pages/EditProfile.php @@ -53,6 +53,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile ->label(trans('strings.username')) ->disabled() ->readOnly() + ->dehydrated(false) ->maxLength(255) ->unique(ignoreRecord: true) ->autofocus(), @@ -119,6 +120,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile ->hidden(fn () => !cache()->get("users.{$this->getUser()->id}.2fa.tokens")) ->rows(10) ->readOnly() + ->dehydrated(false) ->formatStateUsing(fn () => cache()->get("users.{$this->getUser()->id}.2fa.tokens")) ->helperText('These will not be shown again!') ->label('Backup Tokens:'), diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index 3fd0dc27c..33ed4aa74 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -5,6 +5,7 @@ namespace App\Services\Allocations; use App\Models\Allocation; use IPTools\Network; use App\Models\Node; +use App\Models\Server; use Illuminate\Database\ConnectionInterface; use App\Exceptions\DisplayException; use App\Exceptions\Service\Allocation\CidrOutOfRangeException; @@ -37,7 +38,7 @@ class AssignmentService * @throws \App\Exceptions\Service\Allocation\PortOutOfRangeException * @throws \App\Exceptions\Service\Allocation\TooManyPortsInRangeException */ - public function handle(Node $node, array $data): array + public function handle(Node $node, array $data, Server $server = null): array { $explode = explode('/', $data['allocation_ip']); if (count($explode) !== 1) { @@ -84,7 +85,7 @@ class AssignmentService 'ip' => $ip->__toString(), 'port' => (int) $unit, 'ip_alias' => array_get($data, 'allocation_alias'), - 'server_id' => null, + 'server_id' => $server->id ?? null, ]; } } else { @@ -97,7 +98,7 @@ class AssignmentService 'ip' => $ip->__toString(), 'port' => (int) $port, 'ip_alias' => array_get($data, 'allocation_alias'), - 'server_id' => null, + 'server_id' => $server->id ?? null, ]; } From 8662806dfd0f4d889ed5d7e5c87fdb8e227610d1 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sat, 20 Jul 2024 18:51:38 -0400 Subject: [PATCH 23/65] Fix 500 if update url is blank --- app/Filament/Resources/EggResource/Pages/EditEgg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Resources/EggResource/Pages/EditEgg.php b/app/Filament/Resources/EggResource/Pages/EditEgg.php index 3fdf0854e..b4af8bc0a 100644 --- a/app/Filament/Resources/EggResource/Pages/EditEgg.php +++ b/app/Filament/Resources/EggResource/Pages/EditEgg.php @@ -251,7 +251,7 @@ class EditEgg extends EditRecord ->schema([ TextInput::make('url') ->label('URL') - ->default(fn (Egg $egg): string => $egg->update_url) + ->default(fn (Egg $egg): ?string => $egg->update_url) ->hint('Link to the egg file (eg. minecraft.json)') ->url(), ]), From fcef8d69aee83367f4a61c538b437593dadfd012 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sat, 20 Jul 2024 19:15:01 -0400 Subject: [PATCH 24/65] Remove breadcrumbs --- app/Providers/Filament/AdminPanelProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 60339c744..923a3a77d 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -37,6 +37,7 @@ class AdminPanelProvider extends PanelProvider ->path('admin') ->topNavigation(config('panel.filament.top-navigation', true)) ->login() + ->breadcrumbs(false) ->homeUrl('/') ->favicon(config('app.favicon', '/pelican.ico')) ->brandName(config('app.name', 'Pelican')) From 2c2e52b18a4244ef571310994bfb45ab42ff966b Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 23 Jul 2024 11:32:32 +0200 Subject: [PATCH 25/65] fix phpstan (#503) --- app/Models/Egg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 40e20d22a..e8c672064 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -17,7 +17,7 @@ use Illuminate\Support\Str; * @property array|null $features * @property string $docker_image -- deprecated, use $docker_images * @property array $docker_images - * @property string $update_url + * @property string|null $update_url * @property bool $force_outgoing_ip * @property array|null $file_denylist * @property string|null $config_files From 465a03bf0e5b0049662e13d5bd24b6ea2745196a Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Wed, 24 Jul 2024 20:10:45 -0400 Subject: [PATCH 26/65] Update readme.md --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 74b0c7998..210802d90 100644 --- a/readme.md +++ b/readme.md @@ -5,9 +5,6 @@ ![Total Downloads](https://img.shields.io/github/downloads/pelican-dev/panel/total?style=flat&label=Total%20Downloads&labelColor=rgba(0%2C%2070%2C%20114%2C%201)&color=rgba(255%2C%20255%2C%20255%2C%201)) ![Latest Release](https://img.shields.io/github/v/release/pelican-dev/panel?style=flat&label=Latest%20Release&labelColor=rgba(0%2C%2070%2C%20114%2C%201)&color=rgba(255%2C%20255%2C%20255%2C%201)) - -Subscribe on Polar - Pelican Panel is an open-source, web-based application designed for easy management of game servers. It offers a user-friendly interface for deploying, configuring, and managing servers, with features like real-time resource monitoring, Docker container isolation, and extensive customization options. Ideal for both individual gamers and hosting companies, it simplifies server administration without requiring deep technical knowledge. @@ -21,7 +18,7 @@ Fly High, Game On: Pelican's pledge for unrivaled game servers. * [Discord](https://discord.gg/pelican-panel) * [Wings](https://github.com/pelican-dev/wings) -### Supported Games and Servers +## Supported Games and Servers Pelican supports a wide variety of games by utilizing Docker containers to isolate each instance. This gives you the power to run game servers without bloating machines with a host of additional dependencies. @@ -44,4 +41,7 @@ Some of our popular eggs include: | [Storage](https://github.com/pelican-eggs/storage) | S3 | SFTP Share | | | | [Monitoring](https://github.com/pelican-eggs/monitoring) | Prometheus | Loki | | | +## Repository Activity +![Stats](https://repobeats.axiom.co/api/embed/4d8cc7012b325141e6fae9c34a22b3669ad5753b.svg "Repobeats analytics image") + *Copyright Pelican® 2024* From e1bdf95971f8c07109d419f2cc7fda78566f01ae Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:58:20 +0200 Subject: [PATCH 27/65] Update SetupTOTPDialog.tsx (#476) --- .../scripts/components/dashboard/forms/SetupTOTPDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx index b1635a32d..f68127bd2 100644 --- a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx @@ -127,7 +127,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => { }; export default asDialog({ - title: i18n.t('dashboard/account:two_factor.setup.title') ?? 'Enable Two-Step Verification', + title: 'Enable Two-Step Verification', description: "Help protect your account from unauthorized access. You'll be prompted for a verification code each time you sign in.", })(ConfigureTwoFactorForm); From bddd6af8af93c2eb482475dd7ae57e39a0a4ccc4 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 29 Jul 2024 12:13:08 +0200 Subject: [PATCH 28/65] Fix user deletion in no interactive mode (#506) --- app/Console/Commands/User/DeleteUserCommand.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index a6810feee..8c85510ed 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -15,7 +15,7 @@ class DeleteUserCommand extends Command public function handle(): int { $search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users')); - Assert::notEmpty($search, 'Search term should be an email address, got: %s.'); + Assert::notEmpty($search, 'Search term should not be empty.'); $results = User::query() ->where('id', 'LIKE', "$search%") @@ -42,6 +42,8 @@ class DeleteUserCommand extends Command if (!$deleteUser = $this->ask(trans('command/messages.user.select_search_user'))) { return $this->handle(); } + + $deleteUser = User::query()->findOrFail($deleteUser); } else { if (count($results) > 1) { $this->error(trans('command/messages.user.multiple_found')); @@ -53,8 +55,7 @@ class DeleteUserCommand extends Command } if ($this->confirm(trans('command/messages.user.confirm_delete')) || !$this->input->isInteractive()) { - $user = User::query()->findOrFail($deleteUser); - $user->delete(); + $deleteUser->delete(); $this->info(trans('command/messages.user.deleted')); } From d89af243a8b288bd0717b72f061a582836f277c4 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 29 Jul 2024 12:13:29 +0200 Subject: [PATCH 29/65] Fix user search on "create server" (#508) --- app/Filament/Resources/ServerResource/Pages/CreateServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index 572499aa0..bfe5fcbdd 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -80,7 +80,7 @@ class CreateServer extends CreateRecord 'lg' => 3, ]) ->relationship('user', 'username') - ->searchable(['user', 'username', 'email']) + ->searchable(['username', 'email']) ->getOptionLabelFromRecordUsing(fn (User $user) => "$user->email | $user->username " . ($user->root_admin ? '(admin)' : '')) ->createOptionForm([ Forms\Components\TextInput::make('username') From a58e15947815bf2f9bca78296ee5be62c99f83c4 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 29 Jul 2024 12:14:24 +0200 Subject: [PATCH 30/65] Settings page (#486) * remove old settings stuff * add basic settings page * add some settings * add "test mail" button * fix mail fields not updating * fix phpstan * fix default for "top navigation" * force toggle buttons to be bool * force toggle to be bool * add class to view to allow customization * add mailgun settings * add notification settings * add timeout settings * organize tabs into sub-functions * add more settings * add backup settings * add sections to mail settings * add setting for trusted_proxies * fix unsaved data alert not showing * fix clear action * Fix clear action v2 TagsInput expects an array, not a string, fails on saving when using `''` * Add App favicon * Remove defaults, collapse misc sections * Move Save btn, Add API rate limit * small cleanup --------- Co-authored-by: notCharles --- .env.example | 1 - .github/workflows/ci.yaml | 3 - .../Environment/AppSettingsCommand.php | 9 +- app/Filament/Clusters/Settings.php | 10 - app/Filament/Pages/Settings.php | 570 ++++++++++++++++++ .../Admin/Settings/AdvancedController.php | 56 -- .../Admin/Settings/IndexController.php | 56 -- .../Admin/Settings/MailController.php | 82 --- app/Models/Setting.php | 58 -- app/Providers/AppServiceProvider.php | 5 - app/Providers/SettingsServiceProvider.php | 112 ---- config/app.php | 1 + config/panel.php | 12 - lang/en/commands.php | 1 - .../views/admin/settings/advanced.blade.php | 127 ---- .../views/admin/settings/index.blade.php | 75 --- resources/views/admin/settings/mail.blade.php | 202 ------- .../views/filament/pages/settings.blade.php | 15 + resources/views/layouts/admin.blade.php | 5 - .../partials/admin/settings/nav.blade.php | 16 - .../partials/admin/settings/notice.blade.php | 11 - routes/admin.php | 20 - 22 files changed, 587 insertions(+), 860 deletions(-) delete mode 100644 app/Filament/Clusters/Settings.php create mode 100644 app/Filament/Pages/Settings.php delete mode 100644 app/Http/Controllers/Admin/Settings/AdvancedController.php delete mode 100644 app/Http/Controllers/Admin/Settings/IndexController.php delete mode 100644 app/Http/Controllers/Admin/Settings/MailController.php delete mode 100644 app/Providers/SettingsServiceProvider.php delete mode 100644 resources/views/admin/settings/advanced.blade.php delete mode 100644 resources/views/admin/settings/index.blade.php delete mode 100644 resources/views/admin/settings/mail.blade.php create mode 100644 resources/views/filament/pages/settings.blade.php delete mode 100644 resources/views/partials/admin/settings/nav.blade.php delete mode 100644 resources/views/partials/admin/settings/notice.blade.php diff --git a/.env.example b/.env.example index ae71e78e6..95607b2e3 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,6 @@ APP_KEY= APP_TIMEZONE=UTC APP_URL=http://panel.test APP_LOCALE=en -APP_ENVIRONMENT_ONLY=true LOG_CHANNEL=daily LOG_STACK=single diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 92fea58bd..5e2ee8847 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,6 @@ jobs: APP_KEY: ThisIsARandomStringForTests12345 APP_TIMEZONE: UTC APP_URL: http://localhost/ - APP_ENVIRONMENT_ONLY: "true" CACHE_DRIVER: array MAIL_MAILER: array SESSION_DRIVER: array @@ -106,7 +105,6 @@ jobs: APP_KEY: ThisIsARandomStringForTests12345 APP_TIMEZONE: UTC APP_URL: http://localhost/ - APP_ENVIRONMENT_ONLY: "true" CACHE_DRIVER: array MAIL_MAILER: array SESSION_DRIVER: array @@ -170,7 +168,6 @@ jobs: APP_KEY: ThisIsARandomStringForTests12345 APP_TIMEZONE: UTC APP_URL: http://localhost/ - APP_ENVIRONMENT_ONLY: "true" CACHE_DRIVER: array MAIL_MAILER: array SESSION_DRIVER: array diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index 4f2e93119..a480e9b6f 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -38,8 +38,7 @@ class AppSettingsCommand extends Command {--queue= : The queue driver backend to use.} {--redis-host= : Redis host to use for connections.} {--redis-pass= : Password used to connect to redis.} - {--redis-port= : Port to connect to redis over.} - {--settings-ui= : Enable or disable the settings UI.}'; + {--redis-port= : Port to connect to redis over.}'; protected array $variables = []; @@ -87,12 +86,6 @@ class AppSettingsCommand extends Command array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null ); - if (!is_null($this->option('settings-ui'))) { - $this->variables['APP_ENVIRONMENT_ONLY'] = $this->option('settings-ui') == 'true' ? 'false' : 'true'; - } else { - $this->variables['APP_ENVIRONMENT_ONLY'] = $this->confirm(__('commands.appsettings.comment.settings_ui'), true) ? 'false' : 'true'; - } - // Make sure session cookies are set as "secure" when using HTTPS if (str_starts_with($this->variables['APP_URL'], 'https://')) { $this->variables['SESSION_SECURE_COOKIE'] = 'true'; diff --git a/app/Filament/Clusters/Settings.php b/app/Filament/Clusters/Settings.php deleted file mode 100644 index 0ac8254c8..000000000 --- a/app/Filament/Clusters/Settings.php +++ /dev/null @@ -1,10 +0,0 @@ -form->fill(); + } + + protected function getFormSchema(): array + { + return [ + Tabs::make('Tabs') + ->columns() + ->persistTabInQueryString() + ->tabs([ + Tab::make('general') + ->label('General') + ->icon('tabler-home') + ->schema($this->generalSettings()), + Tab::make('recaptcha') + ->label('reCAPTCHA') + ->icon('tabler-shield') + ->schema($this->recaptchaSettings()), + Tab::make('mail') + ->label('Mail') + ->icon('tabler-mail') + ->schema($this->mailSettings()), + Tab::make('backup') + ->label('Backup') + ->icon('tabler-box') + ->schema($this->backupSettings()), + Tab::make('misc') + ->label('Misc') + ->icon('tabler-tool') + ->schema($this->miscSettings()), + ]), + ]; + } + + private function generalSettings(): array + { + return [ + TextInput::make('APP_NAME') + ->label('App Name') + ->required() + ->default(env('APP_NAME', 'Pelican')), + TextInput::make('APP_FAVICON') + ->label('App Favicon') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('Favicons should be placed in the public folder, located in the root panel directory.') + ->required() + ->default(env('APP_FAVICON', './pelican.ico')), + Toggle::make('APP_DEBUG') + ->label('Enable Debug Mode?') + ->inline(false) + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state)) + ->default(env('RECAPTCHA_ENABLED', config('recaptcha.enabled'))), + ToggleButtons::make('FILAMENT_TOP_NAVIGATION') + ->label('Navigation') + ->grouped() + ->options([ + false => 'Sidebar', + true => 'Topbar', + ]) + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state)) + ->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))), + ToggleButtons::make('PANEL_USE_BINARY_PREFIX') + ->label('Unit prefix') + ->grouped() + ->options([ + false => 'Decimal Prefix (MB/ GB)', + true => 'Binary Prefix (MiB/ GiB)', + ]) + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_USE_BINARY_PREFIX', (bool) $state)) + ->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))), + ToggleButtons::make('APP_2FA_REQUIRED') + ->label('2FA Requirement') + ->grouped() + ->options([ + 0 => 'Not required', + 1 => 'Required for only Admins', + 2 => 'Required for all Users', + ]) + ->formatStateUsing(fn ($state): int => (int) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('APP_2FA_REQUIRED', (int) $state)) + ->default(env('APP_2FA_REQUIRED', config('panel.auth.2fa_required'))), + TagsInput::make('TRUSTED_PROXIES') + ->label('Trusted Proxies') + ->separator() + ->splitKeys(['Tab', ' ']) + ->placeholder('New IP or IP Range') + ->default(env('TRUSTED_PROXIES', config('trustedproxy.proxies'))) + ->hintActions([ + FormAction::make('clear') + ->label('Clear') + ->color('danger') + ->icon('tabler-trash') + ->requiresConfirmation() + ->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])), + FormAction::make('cloudflare') + ->label('Set to Cloudflare IPs') + ->icon('tabler-brand-cloudflare') + ->action(fn (Set $set) => $set('TRUSTED_PROXIES', [ + '173.245.48.0/20', + '103.21.244.0/22', + '103.22.200.0/22', + '103.31.4.0/22', + '141.101.64.0/18', + '108.162.192.0/18', + '190.93.240.0/20', + '188.114.96.0/20', + '197.234.240.0/22', + '198.41.128.0/17', + '162.158.0.0/15', + '104.16.0.0/13', + '104.24.0.0/14', + '172.64.0.0/13', + '131.0.72.0/22', + ])), + ]), + ]; + } + + private function recaptchaSettings(): array + { + return [ + Toggle::make('RECAPTCHA_ENABLED') + ->label('Enable reCAPTCHA?') + ->inline(false) + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->live() + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('RECAPTCHA_ENABLED', (bool) $state)) + ->default(env('RECAPTCHA_ENABLED', config('recaptcha.enabled'))), + TextInput::make('RECAPTCHA_DOMAIN') + ->label('Domain') + ->required() + ->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED')) + ->default(env('RECAPTCHA_DOMAIN', config('recaptcha.domain'))), + TextInput::make('RECAPTCHA_WEBSITE_KEY') + ->label('Website Key') + ->required() + ->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED')) + ->default(env('RECAPTCHA_WEBSITE_KEY', config('recaptcha.website_key'))), + TextInput::make('RECAPTCHA_SECRET_KEY') + ->label('Secret Key') + ->required() + ->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED')) + ->default(env('RECAPTCHA_SECRET_KEY', config('recaptcha.secret_key'))), + ]; + } + + private function mailSettings(): array + { + return [ + ToggleButtons::make('MAIL_MAILER') + ->label('Mail Driver') + ->columnSpanFull() + ->grouped() + ->options([ + 'log' => 'Print mails to Log', + 'smtp' => 'SMTP Server', + 'sendmail' => 'sendmail Binary', + 'mailgun' => 'Mailgun', + 'mandrill' => 'Mandrill', + 'postmark' => 'Postmark', + ]) + ->live() + ->default(env('MAIL_MAILER', config('mail.default'))) + ->hintAction( + FormAction::make('test') + ->label('Send Test Mail') + ->icon('tabler-send') + ->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log') + ->action(function () { + try { + MailNotification::route('mail', auth()->user()->email) + ->notify(new MailTested(auth()->user())); + + Notification::make() + ->title('Test Mail sent') + ->success() + ->send(); + } catch (Exception $exception) { + Notification::make() + ->title('Test Mail failed') + ->body($exception->getMessage()) + ->danger() + ->send(); + } + }) + ), + Section::make('"From" Settings') + ->description('Set the Address and Name used as "From" in mails.') + ->columns() + ->schema([ + TextInput::make('MAIL_FROM_ADDRESS') + ->label('From Address') + ->required() + ->email() + ->default(env('MAIL_FROM_ADDRESS', config('mail.from.address'))), + TextInput::make('MAIL_FROM_NAME') + ->label('From Name') + ->required() + ->default(env('MAIL_FROM_NAME', config('mail.from.name'))), + ]), + Section::make('SMTP Configuration') + ->columns() + ->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp') + ->schema([ + TextInput::make('MAIL_HOST') + ->label('SMTP Host') + ->required() + ->default(env('MAIL_HOST', config('mail.mailers.smtp.host'))), + TextInput::make('MAIL_PORT') + ->label('SMTP Port') + ->required() + ->numeric() + ->minValue(1) + ->maxValue(65535) + ->default(env('MAIL_PORT', config('mail.mailers.smtp.port'))), + TextInput::make('MAIL_USERNAME') + ->label('SMTP Username') + ->required() + ->default(env('MAIL_USERNAME', config('mail.mailers.smtp.username'))), + TextInput::make('MAIL_PASSWORD') + ->label('SMTP Password') + ->password() + ->revealable() + ->default(env('MAIL_PASSWORD')), + ToggleButtons::make('MAIL_ENCRYPTION') + ->label('SMTP encryption') + ->required() + ->grouped() + ->options(['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None']) + ->default(env('MAIL_ENCRYPTION', config('mail.mailers.smtp.encryption', 'tls'))), + ]), + Section::make('Mailgun Configuration') + ->columns() + ->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun') + ->schema([ + TextInput::make('MAILGUN_DOMAIN') + ->label('Mailgun Domain') + ->required() + ->default(env('MAILGUN_DOMAIN', config('services.mailgun.domain'))), + TextInput::make('MAILGUN_SECRET') + ->label('Mailgun Secret') + ->required() + ->default(env('MAIL_USERNAME', config('services.mailgun.secret'))), + TextInput::make('MAILGUN_ENDPOINT') + ->label('Mailgun Endpoint') + ->required() + ->default(env('MAILGUN_ENDPOINT', config('services.mailgun.endpoint'))), + ]), + ]; + } + + private function backupSettings(): array + { + return [ + ToggleButtons::make('APP_BACKUP_DRIVER') + ->label('Backup Driver') + ->columnSpanFull() + ->grouped() + ->options([ + Backup::ADAPTER_DAEMON => 'Wings', + Backup::ADAPTER_AWS_S3 => 'S3', + ]) + ->live() + ->default(env('APP_BACKUP_DRIVER', config('backups.default'))), + Section::make('Throttles') + ->description('Configure how many backups can be created in a period. Set period to 0 to disable this throttle.') + ->columns() + ->schema([ + TextInput::make('BACKUP_THROTTLE_LIMIT') + ->label('Limit') + ->required() + ->numeric() + ->minValue(1) + ->default(config('backups.throttles.limit')), + TextInput::make('BACKUP_THROTTLE_PERIOD') + ->label('Period') + ->required() + ->numeric() + ->minValue(0) + ->suffix('Seconds') + ->default(config('backups.throttles.period')), + ]), + Section::make('S3 Configuration') + ->columns() + ->visible(fn (Get $get) => $get('APP_BACKUP_DRIVER') === Backup::ADAPTER_AWS_S3) + ->schema([ + TextInput::make('AWS_DEFAULT_REGION') + ->label('Default Region') + ->required() + ->default(config('backups.disks.s3.region')), + TextInput::make('AWS_ACCESS_KEY_ID') + ->label('Access Key ID') + ->required() + ->default(config('backups.disks.s3.key')), + TextInput::make('AWS_SECRET_ACCESS_KEY') + ->label('Secret Access Key') + ->required() + ->default(config('backups.disks.s3.secret')), + TextInput::make('AWS_BACKUPS_BUCKET') + ->label('Bucket') + ->required() + ->default(config('backups.disks.s3.bucket')), + TextInput::make('AWS_ENDPOINT') + ->label('Endpoint') + ->required() + ->default(config('backups.disks.s3.endpoint')), + Toggle::make('AWS_USE_PATH_STYLE_ENDPOINT') + ->label('Use path style endpoint?') + ->inline(false) + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->live() + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('AWS_USE_PATH_STYLE_ENDPOINT', (bool) $state)) + ->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))), + ]), + ]; + } + + private function miscSettings(): array + { + return [ + Section::make('Automatic Allocation Creation') + ->description('Toggle if Users can create allocations via the client area.') + ->columns() + ->collapsible() + ->collapsed() + ->schema([ + Toggle::make('PANEL_CLIENT_ALLOCATIONS_ENABLED') + ->label('Allow Users to create allocations?') + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->live() + ->columnSpanFull() + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_CLIENT_ALLOCATIONS_ENABLED', (bool) $state)) + ->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))), + TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START') + ->label('Starting Port') + ->required() + ->numeric() + ->minValue(1024) + ->maxValue(65535) + ->visible(fn (Get $get) => $get('PANEL_CLIENT_ALLOCATIONS_ENABLED')) + ->default(env('PANEL_CLIENT_ALLOCATIONS_RANGE_START')), + TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_END') + ->label('Ending Port') + ->required() + ->numeric() + ->minValue(1024) + ->maxValue(65535) + ->visible(fn (Get $get) => $get('PANEL_CLIENT_ALLOCATIONS_ENABLED')) + ->default(env('PANEL_CLIENT_ALLOCATIONS_RANGE_END')), + ]), + Section::make('Mail Notifications') + ->description('Toggle which mail notifications should be sent to Users.') + ->columns() + ->collapsible() + ->collapsed() + ->schema([ + Toggle::make('PANEL_SEND_INSTALL_NOTIFICATION') + ->label('Server Installed') + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->live() + ->columnSpanFull() + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_INSTALL_NOTIFICATION', (bool) $state)) + ->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))), + Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION') + ->label('Server Reinstalled') + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->live() + ->columnSpanFull() + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REINSTALL_NOTIFICATION', (bool) $state)) + ->default(env('PANEL_SEND_REINSTALL_NOTIFICATION', config('panel.email.send_reinstall_notification'))), + ]), + Section::make('Connections') + ->description('Timeouts used when making requests.') + ->columns() + ->collapsible() + ->collapsed() + ->schema([ + TextInput::make('GUZZLE_TIMEOUT') + ->label('Request Timeout') + ->required() + ->numeric() + ->minValue(15) + ->maxValue(60) + ->suffix('Seconds') + ->default(env('GUZZLE_TIMEOUT', config('panel.guzzle.timeout'))), + TextInput::make('GUZZLE_CONNECT_TIMEOUT') + ->label('Connect Timeout') + ->required() + ->numeric() + ->minValue(5) + ->maxValue(60) + ->suffix('Seconds') + ->default(env('GUZZLE_CONNECT_TIMEOUT', config('panel.guzzle.connect_timeout'))), + ]), + Section::make('Activity Logs') + ->description('Configure how often old activity logs should be pruned and whether admin activities should be logged.') + ->columns() + ->collapsible() + ->collapsed() + ->schema([ + TextInput::make('APP_ACTIVITY_PRUNE_DAYS') + ->label('Prune age') + ->required() + ->numeric() + ->minValue(1) + ->maxValue(365) + ->suffix('Days') + ->default(env('APP_ACTIVITY_PRUNE_DAYS', config('activity.prune_days'))), + Toggle::make('APP_ACTIVITY_HIDE_ADMIN') + ->label('Hide admin activities?') + ->inline(false) + ->onIcon('tabler-check') + ->offIcon('tabler-x') + ->onColor('success') + ->offColor('danger') + ->live() + ->formatStateUsing(fn ($state): bool => (bool) $state) + ->afterStateUpdated(fn ($state, Set $set) => $set('APP_ACTIVITY_HIDE_ADMIN', (bool) $state)) + ->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))), + ]), + Section::make('API') + ->description('Defines the rate limit for the number of requests per minute that can be executed.') + ->columns() + ->collapsible() + ->collapsed() + ->schema([ + TextInput::make('APP_API_CLIENT_RATELIMIT') + ->label('Client API Rate Limit') + ->required() + ->numeric() + ->minValue(1) + ->suffix('Requests Per Minute') + ->default(env('APP_API_CLIENT_RATELIMIT', config('http.rate_limit.client'))), + TextInput::make('APP_API_APPLICATION_RATELIMIT') + ->label('Application API Rate Limit') + ->required() + ->numeric() + ->minValue(1) + ->suffix('Requests Per Minute') + ->default(env('APP_API_APPLICATION_RATELIMIT', config('http.rate_limit.application'))), + ]), + ]; + } + + protected function getFormStatePath(): ?string + { + return 'data'; + } + + protected function hasUnsavedDataChangesAlert(): bool + { + return true; + } + + public function save(): void + { + try { + $data = $this->form->getState(); + + $this->writeToEnvironment($data); + + Artisan::call('config:clear'); + Artisan::call('queue:restart'); + + $this->rememberData(); + + $this->redirect($this->getUrl()); + + Notification::make() + ->title('Settings saved') + ->success() + ->send(); + } catch (Exception $exception) { + Notification::make() + ->title('Save failed') + ->body($exception->getMessage()) + ->danger() + ->send(); + } + } + + protected function getHeaderActions(): array + { + return [ + Action::make('save') + ->action('save') + ->keyBindings(['mod+s']), + ]; + + } + protected function getFormActions(): array + { + return []; + } +} diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php deleted file mode 100644 index def9124a8..000000000 --- a/app/Http/Controllers/Admin/Settings/AdvancedController.php +++ /dev/null @@ -1,56 +0,0 @@ - $showRecaptchaWarning, - ]); - } - - /** - * @throws \App\Exceptions\Model\DataValidationException - */ - public function update(AdvancedSettingsFormRequest $request): RedirectResponse - { - foreach ($request->normalize() as $key => $value) { - Setting::set('settings::' . $key, $value); - } - - $this->kernel->call('queue:restart'); - $this->alert->success('Advanced settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - - return redirect()->route('admin.settings.advanced'); - } -} diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php deleted file mode 100644 index 47c567458..000000000 --- a/app/Http/Controllers/Admin/Settings/IndexController.php +++ /dev/null @@ -1,56 +0,0 @@ - $this->versionService, - 'languages' => $this->getAvailableLanguages(), - ]); - } - - /** - * Handle settings update. - * - * @throws \App\Exceptions\Model\DataValidationException - */ - public function update(BaseSettingsFormRequest $request): RedirectResponse - { - foreach ($request->normalize() as $key => $value) { - Setting::set('settings::' . $key, $value); - } - - $this->kernel->call('queue:restart'); - $this->alert->success('Panel settings have been updated successfully and the queue worker was restarted to apply these changes.')->flash(); - - return redirect()->route('admin.settings'); - } -} diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php deleted file mode 100644 index 33aa5c31f..000000000 --- a/app/Http/Controllers/Admin/Settings/MailController.php +++ /dev/null @@ -1,82 +0,0 @@ - config('mail.default') !== 'smtp', - ]); - } - - /** - * Handle request to update SMTP mail settings. - * - * @throws DisplayException - * @throws \App\Exceptions\Model\DataValidationException - */ - public function update(MailSettingsFormRequest $request): Response - { - if (config('mail.default') !== 'smtp') { - throw new DisplayException('This feature is only available if SMTP is the selected email driver for the Panel.'); - } - - $values = $request->normalize(); - if (array_get($values, 'mail:mailers:smtp:password') === '!e') { - $values['mail:mailers:smtp:password'] = ''; - } - - foreach ($values as $key => $value) { - if (in_array($key, SettingsServiceProvider::getEncryptedKeys()) && !empty($value)) { - $value = encrypt($value); - } - - Setting::set('settings::' . $key, $value); - } - - $this->kernel->call('queue:restart'); - - return response('', 204); - } - - /** - * Submit a request to send a test mail message. - */ - public function test(Request $request): Response - { - try { - Notification::route('mail', $request->user()->email) - ->notify(new MailTested($request->user())); - } catch (\Exception $exception) { - return response($exception->getMessage(), 500); - } - - return response('', 204); - } -} diff --git a/app/Models/Setting.php b/app/Models/Setting.php index d25bd1b5d..9efad2b08 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -24,62 +24,4 @@ class Setting extends Model 'key' => 'required|string|between:1,255', 'value' => 'string', ]; - - private static array $cache = []; - - private static array $databaseMiss = []; - - /** - * Store a new persistent setting in the database. - */ - public static function set(string $key, string $value = null): void - { - // Clear item from the cache. - self::clearCache($key); - - self::query()->updateOrCreate(['key' => $key], ['value' => $value ?? '']); - - self::$cache[$key] = $value; - } - - /** - * Retrieve a persistent setting from the database. - */ - public static function get(string $key, mixed $default = null): mixed - { - // If item has already been requested return it from the cache. If - // we already know it is missing, immediately return the default value. - if (array_key_exists($key, self::$cache)) { - return self::$cache[$key]; - } elseif (array_key_exists($key, self::$databaseMiss)) { - return value($default); - } - - $instance = self::query()->where('key', $key)->first(); - if (is_null($instance)) { - self::$databaseMiss[$key] = true; - - return value($default); - } - - return self::$cache[$key] = $instance->value; - } - - /** - * Remove a key from the database cache. - */ - public static function forget(string $key) - { - self::clearCache($key); - - return self::query()->where('key', $key)->delete(); - } - - /** - * Remove a key from the cache. - */ - private static function clearCache(string $key): void - { - unset(self::$cache[$key], self::$databaseMiss[$key]); - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 7d5ba0c44..384e4501d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -87,11 +87,6 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // Only load the settings service provider if the environment is configured to allow it. - if (!config('panel.load_environment_only', false) && $this->app->environment() !== 'testing') { - $this->app->register(SettingsServiceProvider::class); - } - $this->app->singleton('extensions.themes', function () { return new Theme(); }); diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php deleted file mode 100644 index 5c7412455..000000000 --- a/app/Providers/SettingsServiceProvider.php +++ /dev/null @@ -1,112 +0,0 @@ -keys = array_merge($this->keys, $this->emailKeys); - } - - try { - $values = Setting::all()->mapWithKeys(function ($setting) { - return [$setting->key => $setting->value]; - })->toArray(); - } catch (QueryException $exception) { - $log->notice('A query exception was encountered while trying to load settings from the database: ' . $exception->getMessage()); - - return; - } - - foreach ($this->keys as $key) { - $value = array_get($values, 'settings::' . $key, config(str_replace(':', '.', $key))); - if (in_array($key, self::$encrypted)) { - try { - $value = decrypt($value); - } catch (Exception) { - // ignore - } - } - - switch (strtolower($value)) { - case 'true': - case '(true)': - $value = true; - break; - case 'false': - case '(false)': - $value = false; - break; - case 'empty': - case '(empty)': - $value = ''; - break; - case 'null': - case '(null)': - $value = null; - } - - config()->set(str_replace(':', '.', $key), $value); - } - } - - public static function getEncryptedKeys(): array - { - return self::$encrypted; - } -} diff --git a/config/app.php b/config/app.php index e8d4deb30..f9a4acc85 100644 --- a/config/app.php +++ b/config/app.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Facade; return [ 'name' => env('APP_NAME', 'Pelican'), + 'favicon' => env('APP_FAVICON', './pelican.ico'), 'version' => 'canary', diff --git a/config/panel.php b/config/panel.php index 7b049f145..32b11bc3a 100644 --- a/config/panel.php +++ b/config/panel.php @@ -1,18 +1,6 @@ (bool) env('APP_ENVIRONMENT_ONLY', false), - /* |-------------------------------------------------------------------------- | Authentication diff --git a/lang/en/commands.php b/lang/en/commands.php index a42ca228b..3b9ab9f45 100644 --- a/lang/en/commands.php +++ b/lang/en/commands.php @@ -6,7 +6,6 @@ return [ 'author' => 'Provide the email address that eggs exported by this Panel should be from. This should be a valid email address.', 'url' => 'The application URL MUST begin with https:// or http:// depending on if you are using SSL or not. If you do not include the scheme your emails and other content will link to the wrong location.', 'timezone' => "The timezone should match one of PHP\'s supported timezones. If you are unsure, please reference https://php.net/manual/en/timezones.php.", - 'settings_ui' => 'Enable UI based settings editor?', ], 'redis' => [ 'note' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.', diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php deleted file mode 100644 index dc5543ca9..000000000 --- a/resources/views/admin/settings/advanced.blade.php +++ /dev/null @@ -1,127 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'advanced']) - -@section('title') - Advanced Settings -@endsection - -@section('content-header') -

Advanced SettingsConfigure advanced settings for Panel.

- -@endsection - -@section('content') - @yield('settings::nav') -
-
-
-
-
-

reCAPTCHA

-
-
-
-
- -
- -

If enabled, login forms and password reset forms will do a silent captcha check and display a visible captcha if needed.

-
-
-
- -
- -
-
-
- -
- -

Used for communication between your site and Google. Be sure to keep it a secret.

-
-
-
- @if($showRecaptchaWarning) -
-
-
- You are currently using reCAPTCHA keys that were shipped with this Panel. For improved security it is recommended to generate new invisible reCAPTCHA keys that tied specifically to your website. -
-
-
- @endif -
-
-
-
-

HTTP Connections

-
-
-
-
- -
- -

The amount of time in seconds to wait for a connection to be opened before throwing an error.

-
-
-
- -
- -

The amount of time in seconds to wait for a request to be completed before throwing an error.

-
-
-
-
-
-
-
-

Automatic Allocation Creation

-
-
-
-
- -
- -

If enabled users will have the option to automatically create new allocations for their server via the frontend.

-
-
-
- -
- -

The starting port in the range that can be automatically allocated.

-
-
-
- -
- -

The ending port in the range that can be automatically allocated.

-
-
-
-
-
-
- -
-
-
-
-@endsection diff --git a/resources/views/admin/settings/index.blade.php b/resources/views/admin/settings/index.blade.php deleted file mode 100644 index 19356e8b8..000000000 --- a/resources/views/admin/settings/index.blade.php +++ /dev/null @@ -1,75 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'basic']) - -@section('title') - Settings -@endsection - -@section('content-header') -

Panel SettingsConfigure Panel to your liking.

- -@endsection - -@section('content') - @yield('settings::nav') -
-
-
-
-

Panel Settings

-
-
-
-
-
- -
- -

This is the name that is used throughout the panel and in emails sent to clients.

-
-
-
- -
-
- @php - $level = old('panel:auth:2fa_required', config('panel.auth.2fa_required')); - @endphp - - - -
-

If enabled, any account falling into the selected grouping will be required to have 2-Factor authentication enabled to use the Panel.

-
-
-
- -
- -

The default language to use when rendering UI components.

-
-
-
-
- -
-
-
-
-@endsection diff --git a/resources/views/admin/settings/mail.blade.php b/resources/views/admin/settings/mail.blade.php deleted file mode 100644 index 0488d1c7a..000000000 --- a/resources/views/admin/settings/mail.blade.php +++ /dev/null @@ -1,202 +0,0 @@ -@extends('layouts.admin') -@include('partials/admin.settings.nav', ['activeTab' => 'mail']) - -@section('title') - Mail Settings -@endsection - -@section('content-header') -

Mail SettingsConfigure how email sending should be handled.

- -@endsection - -@section('content') - @yield('settings::nav') -
-
-
-
-

Email Settings

-
- @if($disabled) -
-
-
-
- This interface is limited to instances using SMTP as the mail driver. Please either use php artisan p:environment:mail command to update your email settings, or set MAIL_DRIVER=smtp in your environment file. -
-
-
-
- @else -
-
-
-
- -
- -

Enter the SMTP server address that mail should be sent through.

-
-
-
- -
- -

Enter the SMTP server port that mail should be sent through.

-
-
-
- -
- @php - $encryption = old('mail:mailers:smtp:encryption', config('mail.mailers.smtp.encryption')); - @endphp - -

Select the type of encryption to use when sending mail.

-
-
-
- -
- -

The username to use when connecting to the SMTP server.

-
-
-
- -
- -

The password to use in conjunction with the SMTP username. Leave blank to continue using the existing password. To set the password to an empty value enter !e into the field.

-
-
-
-
-
-
- -
- -

Enter an email address that all outgoing emails will originate from.

-
-
-
- -
- -

The name that emails should appear to come from.

-
-
-
-
- -
- @endif -
-
-
-@endsection - -@section('footer-scripts') - @parent - - -@endsection diff --git a/resources/views/filament/pages/settings.blade.php b/resources/views/filament/pages/settings.blade.php new file mode 100644 index 000000000..9f9b3f439 --- /dev/null +++ b/resources/views/filament/pages/settings.blade.php @@ -0,0 +1,15 @@ + + + {{ $this->form }} + + + + diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index d48fad64b..24b21db19 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -96,11 +96,6 @@
  • OTHER
  • -
  • - - Settings - -
  • Application API diff --git a/resources/views/partials/admin/settings/nav.blade.php b/resources/views/partials/admin/settings/nav.blade.php deleted file mode 100644 index 9f1ace7f3..000000000 --- a/resources/views/partials/admin/settings/nav.blade.php +++ /dev/null @@ -1,16 +0,0 @@ -@include('partials/admin.settings.notice') - -@section('settings::nav') - @yield('settings::notice') - -@endsection diff --git a/resources/views/partials/admin/settings/notice.blade.php b/resources/views/partials/admin/settings/notice.blade.php deleted file mode 100644 index 980c5ef60..000000000 --- a/resources/views/partials/admin/settings/notice.blade.php +++ /dev/null @@ -1,11 +0,0 @@ -@section('settings::notice') - @if(config('panel.load_environment_only', false)) -
    -
    -
    - Your Panel is currently configured to read settings from the environment only. You will need to set APP_ENVIRONMENT_ONLY=false in your environment file in order to load settings dynamically. -
    -
    -
    - @endif -@endsection diff --git a/routes/admin.php b/routes/admin.php index 3786b876a..60d047e01 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -40,26 +40,6 @@ Route::prefix('databases')->group(function () { Route::delete('/view/{host:id}', [Admin\DatabaseController::class, 'delete']); }); -/* -|-------------------------------------------------------------------------- -| Settings Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /admin/settings -| -*/ -Route::prefix('settings')->group(function () { - Route::get('/', [Admin\Settings\IndexController::class, 'index'])->name('admin.settings'); - Route::get('/mail', [Admin\Settings\MailController::class, 'index'])->name('admin.settings.mail'); - Route::get('/advanced', [Admin\Settings\AdvancedController::class, 'index'])->name('admin.settings.advanced'); - - Route::post('/mail/test', [Admin\Settings\MailController::class, 'test'])->name('admin.settings.mail.test'); - - Route::patch('/', [Admin\Settings\IndexController::class, 'update']); - Route::patch('/mail', [Admin\Settings\MailController::class, 'update']); - Route::patch('/advanced', [Admin\Settings\AdvancedController::class, 'update']); -}); - /* |-------------------------------------------------------------------------- | User Controller Routes From 3f40256f8bc2e35265624f6eb69dda4805ceb86c Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 30 Jul 2024 16:07:20 +0200 Subject: [PATCH 31/65] Settings page followup (#514) * remove group for toggle buttons * fix default for APP_DEBUG * correctly handle bool values * fix pint * small cleanup for example .env --- .env.example | 6 +----- app/Filament/Pages/Settings.php | 17 ++++++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 95607b2e3..6a128b13d 100644 --- a/.env.example +++ b/.env.example @@ -26,11 +26,7 @@ MAIL_FROM_ADDRESS=no-reply@example.com MAIL_FROM_NAME="Pelican Admin" # Set this to your domain to prevent it defaulting to 'localhost', causing mail servers such as Gmail to reject your mail # MAIL_EHLO_DOMAIN=panel.example.com + SESSION_ENCRYPT=false SESSION_PATH=/ SESSION_DOMAIN=null - -# Set this to true, and set start & end ports to auto create allocations. -PANEL_CLIENT_ALLOCATIONS_ENABLED=false -PANEL_CLIENT_ALLOCATIONS_RANGE_START= -PANEL_CLIENT_ALLOCATIONS_RANGE_END= diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php index 06513b47a..5ec862795 100644 --- a/app/Filament/Pages/Settings.php +++ b/app/Filament/Pages/Settings.php @@ -102,10 +102,10 @@ class Settings extends Page implements HasForms ->offColor('danger') ->formatStateUsing(fn ($state): bool => (bool) $state) ->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state)) - ->default(env('RECAPTCHA_ENABLED', config('recaptcha.enabled'))), + ->default(env('APP_DEBUG', config('app.debug'))), ToggleButtons::make('FILAMENT_TOP_NAVIGATION') ->label('Navigation') - ->grouped() + ->inline() ->options([ false => 'Sidebar', true => 'Topbar', @@ -115,7 +115,7 @@ class Settings extends Page implements HasForms ->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))), ToggleButtons::make('PANEL_USE_BINARY_PREFIX') ->label('Unit prefix') - ->grouped() + ->inline() ->options([ false => 'Decimal Prefix (MB/ GB)', true => 'Binary Prefix (MiB/ GiB)', @@ -125,7 +125,7 @@ class Settings extends Page implements HasForms ->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))), ToggleButtons::make('APP_2FA_REQUIRED') ->label('2FA Requirement') - ->grouped() + ->inline() ->options([ 0 => 'Not required', 1 => 'Required for only Admins', @@ -209,7 +209,7 @@ class Settings extends Page implements HasForms ToggleButtons::make('MAIL_MAILER') ->label('Mail Driver') ->columnSpanFull() - ->grouped() + ->inline() ->options([ 'log' => 'Print mails to Log', 'smtp' => 'SMTP Server', @@ -284,7 +284,7 @@ class Settings extends Page implements HasForms ToggleButtons::make('MAIL_ENCRYPTION') ->label('SMTP encryption') ->required() - ->grouped() + ->inline() ->options(['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None']) ->default(env('MAIL_ENCRYPTION', config('mail.mailers.smtp.encryption', 'tls'))), ]), @@ -314,7 +314,7 @@ class Settings extends Page implements HasForms ToggleButtons::make('APP_BACKUP_DRIVER') ->label('Backup Driver') ->columnSpanFull() - ->grouped() + ->inline() ->options([ Backup::ADAPTER_DAEMON => 'Wings', Backup::ADAPTER_AWS_S3 => 'S3', @@ -532,6 +532,9 @@ class Settings extends Page implements HasForms try { $data = $this->form->getState(); + // Convert bools to a string, so they are correctly written to the .env file + $data = array_map(fn ($value) => is_bool($value) ? ($value ? 'true' : 'false') : $value, $data); + $this->writeToEnvironment($data); Artisan::call('config:clear'); From 686c4375bc823a3b4ec8a85d40d4e4366ae37ce3 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 30 Jul 2024 10:43:24 -0400 Subject: [PATCH 32/65] Layout fix for mobile --- .../ServerResource/Pages/EditServer.php | 56 ++++++------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Resources/ServerResource/Pages/EditServer.php index c229f658c..48665601d 100644 --- a/app/Filament/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Resources/ServerResource/Pages/EditServer.php @@ -7,6 +7,8 @@ use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabasePasswordService; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\Repeater; +use Filament\Forms\Get; +use Filament\Forms\Set; use LogicException; use App\Filament\Resources\ServerResource; use App\Http\Controllers\Admin\ServersController; @@ -36,22 +38,16 @@ class EditServer extends EditRecord public function form(Form $form): Form { return $form - ->columns([ - 'default' => 1, - 'sm' => 2, - 'md' => 2, - 'lg' => 4, - ]) ->schema([ Tabs::make('Tabs') ->persistTabInQueryString() - ->columnSpan(6) ->columns([ 'default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 6, ]) + ->columnSpanFull() ->tabs([ Tabs\Tab::make('Information') ->icon('tabler-info-circle') @@ -161,12 +157,6 @@ class EditServer extends EditRecord ->icon('tabler-brand-docker') ->schema([ Forms\Components\Fieldset::make('Resource Limits') - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]) ->columns([ 'default' => 1, 'sm' => 2, @@ -342,12 +332,6 @@ class EditServer extends EditRecord Forms\Components\Fieldset::make('Feature Limits') ->inlineLabel() - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]) ->columns([ 'default' => 1, 'sm' => 2, @@ -372,12 +356,6 @@ class EditServer extends EditRecord ->numeric(), ]), Forms\Components\Fieldset::make('Docker Settings') - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]) ->columns([ 'default' => 1, 'sm' => 2, @@ -440,10 +418,10 @@ class EditServer extends EditRecord ->disabledOn('edit') ->prefixIcon('tabler-egg') ->columnSpan([ - 'default' => 1, + 'default' => 6, 'sm' => 3, 'md' => 3, - 'lg' => 5, + 'lg' => 4, ]) ->relationship('egg', 'name') ->searchable() @@ -452,6 +430,12 @@ class EditServer extends EditRecord Forms\Components\ToggleButtons::make('skip_scripts') ->label('Run Egg Install Script?')->inline() + ->columnSpan([ + 'default' => 6, + 'sm' => 1, + 'md' => 1, + 'lg' => 2, + ]) ->options([ false => 'Yes', true => 'Skip', @@ -469,12 +453,9 @@ class EditServer extends EditRecord Forms\Components\Textarea::make('startup') ->label('Startup Command') ->required() - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]) + ->hint('sdfa') + ->hintIcon('tabler-code') + ->columnSpan(6) ->rows(function ($state) { return str($state)->explode("\n")->reduce( fn (int $carry, $line) => $carry + floor(strlen($line) / 125), @@ -486,17 +467,12 @@ class EditServer extends EditRecord ->hintAction(CopyAction::make()) ->label('Default Startup Command') ->disabled() - ->formatStateUsing(function ($state, Forms\Get $get, Forms\Set $set) { + ->formatStateUsing(function ($state, Get $get, Set $set) { $egg = Egg::query()->find($get('egg_id')); return $egg->startup; }) - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]), + ->columnSpan(6), Forms\Components\Repeater::make('server_variables') ->relationship('serverVariables') From c4864feaa5ff65515e705b65b07d94a812c2a8bf Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 30 Jul 2024 10:45:12 -0400 Subject: [PATCH 33/65] Whoops --- app/Filament/Resources/ServerResource/Pages/EditServer.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Resources/ServerResource/Pages/EditServer.php index 48665601d..c8726fde5 100644 --- a/app/Filament/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Resources/ServerResource/Pages/EditServer.php @@ -453,8 +453,6 @@ class EditServer extends EditRecord Forms\Components\Textarea::make('startup') ->label('Startup Command') ->required() - ->hint('sdfa') - ->hintIcon('tabler-code') ->columnSpan(6) ->rows(function ($state) { return str($state)->explode("\n")->reduce( From d22f97568452bdbf2b6f99b3d17575599a642050 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 30 Jul 2024 12:58:16 -0400 Subject: [PATCH 34/65] More Mobile UI Closes https://github.com/pelican-dev/panel/issues/512 --- .../ServerResource/Pages/CreateServer.php | 82 +++++++++---------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index bfe5fcbdd..62e62b192 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -40,8 +40,8 @@ class CreateServer extends CreateRecord ->icon('tabler-info-circle') ->completedIcon('tabler-check') ->columns([ - 'default' => 2, - 'sm' => 2, + 'default' => 1, + 'sm' => 1, 'md' => 4, 'lg' => 6, ]) @@ -61,7 +61,7 @@ class CreateServer extends CreateRecord })) ->columnSpan([ 'default' => 2, - 'sm' => 4, + 'sm' => 3, 'md' => 2, 'lg' => 3, ]) @@ -75,8 +75,8 @@ class CreateServer extends CreateRecord ->label('Owner') ->columnSpan([ 'default' => 2, - 'sm' => 4, - 'md' => 2, + 'sm' => 3, + 'md' => 3, 'lg' => 3, ]) ->relationship('user', 'username') @@ -125,10 +125,10 @@ class CreateServer extends CreateRecord ->prefixIcon('tabler-server-2') ->default(fn () => ($this->node = Node::query()->latest()->first())?->id) ->columnSpan([ - 'default' => 1, - 'sm' => 2, - 'md' => 2, - 'lg' => 2, + 'default' => 2, + 'sm' => 3, + 'md' => 6, + 'lg' => 6, ]) ->live() ->relationship('node', 'name') @@ -146,10 +146,10 @@ class CreateServer extends CreateRecord ->prefixIcon('tabler-network') ->label('Primary Allocation') ->columnSpan([ - 'default' => 1, - 'sm' => 2, - 'md' => 1, - 'lg' => 2, + 'default' => 2, + 'sm' => 3, + 'md' => 2, + 'lg' => 3, ]) ->disabled(fn (Forms\Get $get) => $get('node_id') === null) ->searchable(['ip', 'port', 'ip_alias']) @@ -268,10 +268,10 @@ class CreateServer extends CreateRecord Forms\Components\Repeater::make('allocation_additional') ->label('Additional Allocations') ->columnSpan([ - 'default' => 1, - 'sm' => 2, - 'md' => 1, - 'lg' => 2, + 'default' => 2, + 'sm' => 3, + 'md' => 3, + 'lg' => 3, ]) ->addActionLabel('Add Allocation') ->disabled(fn (Forms\Get $get) => $get('allocation_id') === null) @@ -303,12 +303,13 @@ class CreateServer extends CreateRecord ), ), - Forms\Components\TextInput::make('description') + Forms\Components\TextArea::make('description') ->placeholder('Description') + ->rows(3) ->columnSpan([ - 'default' => 1, - 'sm' => 2, - 'md' => 2, + 'default' => 2, + 'sm' => 6, + 'md' => 6, 'lg' => 6, ]) ->label('Notes'), @@ -491,12 +492,7 @@ class CreateServer extends CreateRecord ->completedIcon('tabler-check') ->schema([ Forms\Components\Fieldset::make('Resource Limits') - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]) + ->columnSpan(6) ->columns([ 'default' => 1, 'sm' => 2, @@ -676,12 +672,7 @@ class CreateServer extends CreateRecord Forms\Components\Fieldset::make('Feature Limits') ->inlineLabel() - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]) + ->columnSpan(6) ->columns([ 'default' => 1, 'sm' => 2, @@ -712,18 +703,13 @@ class CreateServer extends CreateRecord ->default(0), ]), Forms\Components\Fieldset::make('Docker Settings') - ->columnSpan([ - 'default' => 2, - 'sm' => 4, - 'md' => 4, - 'lg' => 6, - ]) ->columns([ 'default' => 1, 'sm' => 2, 'md' => 3, - 'lg' => 3, + 'lg' => 4, ]) + ->columnSpan(6) ->schema([ Forms\Components\Select::make('select_image') ->label('Image Name') @@ -742,7 +728,12 @@ class CreateServer extends CreateRecord return array_flip($images) + ['ghcr.io/custom-image' => 'Custom Image']; }) ->selectablePlaceholder(false) - ->columnSpan(1), + ->columnSpan([ + 'default' => 1, + 'sm' => 2, + 'md' => 3, + 'lg' => 2, + ]), Forms\Components\TextInput::make('image') ->label('Image') @@ -758,13 +749,18 @@ class CreateServer extends CreateRecord } }) ->placeholder('Enter a custom Image') - ->columnSpan(2), + ->columnSpan([ + 'default' => 1, + 'sm' => 2, + 'md' => 3, + 'lg' => 2, + ]), Forms\Components\KeyValue::make('docker_labels') ->label('Container Labels') ->keyLabel('Title') ->valueLabel('Description') - ->columnSpan(3), + ->columnSpanFull(), Forms\Components\CheckboxList::make('mounts') ->live() From 525a106e817cf557384038174b341f5f537b327d Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 30 Jul 2024 14:12:29 -0400 Subject: [PATCH 35/65] Change TextArea -> Textarea... Makes no sense as we have TextInput, TagsInput and KeyValue... But TextArea is an issue... --- app/Filament/Resources/ServerResource/Pages/CreateServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index 62e62b192..343c8fa0c 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -303,7 +303,7 @@ class CreateServer extends CreateRecord ), ), - Forms\Components\TextArea::make('description') + Forms\Components\Textarea::make('description') ->placeholder('Description') ->rows(3) ->columnSpan([ From 18cf6e933849b9e6bf6579ed1c6ec475e86594d0 Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:10:58 +0200 Subject: [PATCH 36/65] Update SetupTOTPDialog.tsx (#518) --- resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx index f68127bd2..ec8326aa8 100644 --- a/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTOTPDialog.tsx @@ -4,7 +4,6 @@ import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoF import { useFlashKey } from '@/plugins/useFlash'; import tw from 'twin.macro'; import { useTranslation } from 'react-i18next'; -import i18n from '@/i18n'; import QRCode from 'qrcode.react'; import { Button } from '@/components/elements/button/index'; import Spinner from '@/components/elements/Spinner'; From 496eaaaf832a78e05166b1b421accadb63af3400 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sat, 3 Aug 2024 21:13:17 +0200 Subject: [PATCH 37/65] Web Installer (#504) * simplify setup command * add installer page * add route for installer * adjust gitignore * set colors globally * add "unsaved data changes" alert * add helper method to check if panel is installed * make nicer * redis username isn't required * bring back db settings command * store current date in "installed" file * only redirect if install was successfull * remove fpm requirement * change "installed" marker to env variable * improve requirements step * add commands to change cache, queue or session drivers respectively * removed `grouped` for better mobile view --- .env.example | 1 + .../Environment/AppSettingsCommand.php | 135 ++-------------- .../Environment/CacheSettingsCommand.php | 68 +++++++++ .../Environment/QueueSettingsCommand.php | 66 ++++++++ .../Environment/SessionSettingsCommand.php | 69 +++++++++ .../Pages/Installer/PanelInstaller.php | 144 ++++++++++++++++++ .../Pages/Installer/Steps/AdminUserStep.php | 31 ++++ .../Pages/Installer/Steps/DatabaseStep.php | 95 ++++++++++++ .../Pages/Installer/Steps/EnvironmentStep.php | 94 ++++++++++++ .../Pages/Installer/Steps/RedisStep.php | 42 +++++ .../Installer/Steps/RequirementsStep.php | 87 +++++++++++ app/Providers/AppServiceProvider.php | 11 ++ app/Providers/Filament/AdminPanelProvider.php | 10 -- .../Commands/RequestRedisSettingsTrait.php | 37 +++++ app/helpers.php | 8 + .../views/filament/pages/installer.blade.php | 7 + routes/base.php | 4 + 17 files changed, 778 insertions(+), 131 deletions(-) create mode 100644 app/Console/Commands/Environment/CacheSettingsCommand.php create mode 100644 app/Console/Commands/Environment/QueueSettingsCommand.php create mode 100644 app/Console/Commands/Environment/SessionSettingsCommand.php create mode 100644 app/Filament/Pages/Installer/PanelInstaller.php create mode 100644 app/Filament/Pages/Installer/Steps/AdminUserStep.php create mode 100644 app/Filament/Pages/Installer/Steps/DatabaseStep.php create mode 100644 app/Filament/Pages/Installer/Steps/EnvironmentStep.php create mode 100644 app/Filament/Pages/Installer/Steps/RedisStep.php create mode 100644 app/Filament/Pages/Installer/Steps/RequirementsStep.php create mode 100644 app/Traits/Commands/RequestRedisSettingsTrait.php create mode 100644 resources/views/filament/pages/installer.blade.php diff --git a/.env.example b/.env.example index 6a128b13d..84ff1d432 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ APP_KEY= APP_TIMEZONE=UTC APP_URL=http://panel.test APP_LOCALE=en +APP_INSTALLED=false LOG_CHANNEL=daily LOG_STACK=single diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index a480e9b6f..b3ee96e08 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -3,7 +3,6 @@ namespace App\Console\Commands\Environment; use Illuminate\Console\Command; -use Illuminate\Contracts\Console\Kernel; use App\Traits\Commands\EnvironmentWriterTrait; use Illuminate\Support\Facades\Artisan; @@ -11,147 +10,41 @@ class AppSettingsCommand extends Command { use EnvironmentWriterTrait; - public const CACHE_DRIVERS = [ - 'file' => 'Filesystem (recommended)', - 'redis' => 'Redis', - ]; - - public const SESSION_DRIVERS = [ - 'file' => 'Filesystem (recommended)', - 'redis' => 'Redis', - 'database' => 'Database', - 'cookie' => 'Cookie', - ]; - - public const QUEUE_DRIVERS = [ - 'database' => 'Database (recommended)', - 'redis' => 'Redis', - 'sync' => 'Synchronous', - ]; - protected $description = 'Configure basic environment settings for the Panel.'; protected $signature = 'p:environment:setup - {--url= : The URL that this Panel is running on.} - {--cache= : The cache driver backend to use.} - {--session= : The session driver backend to use.} - {--queue= : The queue driver backend to use.} - {--redis-host= : Redis host to use for connections.} - {--redis-pass= : Password used to connect to redis.} - {--redis-port= : Port to connect to redis over.}'; + {--url= : The URL that this Panel is running on.}'; protected array $variables = []; - /** - * AppSettingsCommand constructor. - */ - public function __construct(private Kernel $console) + public function handle(): void { - parent::__construct(); - } + $path = base_path('.env'); + if (!file_exists($path)) { + $this->comment('Copying example .env file'); + copy($path . '.example', $path); + } + + if (!config('app.key')) { + $this->comment('Generating app key'); + Artisan::call('key:generate'); + } - /** - * Handle command execution. - * - * @throws \App\Exceptions\PanelException - */ - public function handle(): int - { $this->variables['APP_TIMEZONE'] = 'UTC'; - $this->output->comment(__('commands.appsettings.comment.url')); $this->variables['APP_URL'] = $this->option('url') ?? $this->ask( 'Application URL', config('app.url', 'https://example.com') ); - $selected = config('cache.default', 'file'); - $this->variables['CACHE_STORE'] = $this->option('cache') ?? $this->choice( - 'Cache Driver', - self::CACHE_DRIVERS, - array_key_exists($selected, self::CACHE_DRIVERS) ? $selected : null - ); - - $selected = config('session.driver', 'file'); - $this->variables['SESSION_DRIVER'] = $this->option('session') ?? $this->choice( - 'Session Driver', - self::SESSION_DRIVERS, - array_key_exists($selected, self::SESSION_DRIVERS) ? $selected : null - ); - - $selected = config('queue.default', 'database'); - $this->variables['QUEUE_CONNECTION'] = $this->option('queue') ?? $this->choice( - 'Queue Driver', - self::QUEUE_DRIVERS, - array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null - ); - // Make sure session cookies are set as "secure" when using HTTPS if (str_starts_with($this->variables['APP_URL'], 'https://')) { $this->variables['SESSION_SECURE_COOKIE'] = 'true'; } - $redisUsed = count(collect($this->variables)->filter(function ($item) { - return $item === 'redis'; - })) !== 0; - - if ($redisUsed) { - $this->requestRedisSettings(); - } - - $path = base_path('.env'); - if (!file_exists($path)) { - copy($path . '.example', $path); - } - + $this->comment('Writing variables to .env file'); $this->writeToEnvironment($this->variables); - if (!config('app.key')) { - Artisan::call('key:generate'); - } - - if ($this->variables['QUEUE_CONNECTION'] !== 'sync') { - $this->call('p:environment:queue-service', [ - '--use-redis' => $redisUsed, - ]); - } - - $this->info($this->console->output()); - - return 0; - } - - /** - * Request redis connection details and verify them. - */ - private function requestRedisSettings(): void - { - $this->output->note(__('commands.appsettings.redis.note')); - $this->variables['REDIS_HOST'] = $this->option('redis-host') ?? $this->ask( - 'Redis Host', - config('database.redis.default.host') - ); - - $askForRedisPassword = true; - if (!empty(config('database.redis.default.password'))) { - $this->variables['REDIS_PASSWORD'] = config('database.redis.default.password'); - $askForRedisPassword = $this->confirm('It seems a password is already defined for Redis, would you like to change it?'); - } - - if ($askForRedisPassword) { - $this->output->comment(__('commands.appsettings.redis.comment')); - $this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden( - 'Redis Password' - ); - } - - if (empty($this->variables['REDIS_PASSWORD'])) { - $this->variables['REDIS_PASSWORD'] = 'null'; - } - - $this->variables['REDIS_PORT'] = $this->option('redis-port') ?? $this->ask( - 'Redis Port', - config('database.redis.default.port') - ); + $this->info("Setup complete. Vist {$this->variables['APP_URL']}/installer to complete the installation"); } } diff --git a/app/Console/Commands/Environment/CacheSettingsCommand.php b/app/Console/Commands/Environment/CacheSettingsCommand.php new file mode 100644 index 000000000..4870e1bc9 --- /dev/null +++ b/app/Console/Commands/Environment/CacheSettingsCommand.php @@ -0,0 +1,68 @@ + 'Filesystem (default)', + 'database' => 'Database', + 'redis' => 'Redis', + ]; + + protected $description = 'Configure cache settings for the Panel.'; + + protected $signature = 'p:environment:cache + {--driver= : The cache driver backend to use.} + {--redis-host= : Redis host to use for connections.} + {--redis-pass= : Password used to connect to redis.} + {--redis-port= : Port to connect to redis over.}'; + + protected array $variables = []; + + /** + * CacheSettingsCommand constructor. + */ + public function __construct(private Kernel $console) + { + parent::__construct(); + } + + /** + * Handle command execution. + */ + public function handle(): int + { + $selected = config('cache.default', 'file'); + $this->variables['CACHE_STORE'] = $this->option('driver') ?? $this->choice( + 'Cache Driver', + self::CACHE_DRIVERS, + array_key_exists($selected, self::CACHE_DRIVERS) ? $selected : null + ); + + if ($this->variables['CACHE_STORE'] === 'redis') { + $this->requestRedisSettings(); + + if (config('queue.default') !== 'sync') { + $this->call('p:environment:queue-service', [ + '--use-redis' => true, + '--overwrite' => true, + ]); + } + } + + $this->writeToEnvironment($this->variables); + + $this->info($this->console->output()); + + return 0; + } +} diff --git a/app/Console/Commands/Environment/QueueSettingsCommand.php b/app/Console/Commands/Environment/QueueSettingsCommand.php new file mode 100644 index 000000000..3d48a3123 --- /dev/null +++ b/app/Console/Commands/Environment/QueueSettingsCommand.php @@ -0,0 +1,66 @@ + 'Database (default)', + 'redis' => 'Redis', + 'sync' => 'Synchronous', + ]; + + protected $description = 'Configure queue settings for the Panel.'; + + protected $signature = 'p:environment:queue + {--driver= : The queue driver backend to use.} + {--redis-host= : Redis host to use for connections.} + {--redis-pass= : Password used to connect to redis.} + {--redis-port= : Port to connect to redis over.}'; + + protected array $variables = []; + + /** + * QueueSettingsCommand constructor. + */ + public function __construct(private Kernel $console) + { + parent::__construct(); + } + + /** + * Handle command execution. + */ + public function handle(): int + { + $selected = config('queue.default', 'database'); + $this->variables['QUEUE_CONNECTION'] = $this->option('driver') ?? $this->choice( + 'Queue Driver', + self::QUEUE_DRIVERS, + array_key_exists($selected, self::QUEUE_DRIVERS) ? $selected : null + ); + + if ($this->variables['QUEUE_CONNECTION'] === 'redis') { + $this->requestRedisSettings(); + + $this->call('p:environment:queue-service', [ + '--use-redis' => true, + '--overwrite' => true, + ]); + } + + $this->writeToEnvironment($this->variables); + + $this->info($this->console->output()); + + return 0; + } +} diff --git a/app/Console/Commands/Environment/SessionSettingsCommand.php b/app/Console/Commands/Environment/SessionSettingsCommand.php new file mode 100644 index 000000000..9a4081c51 --- /dev/null +++ b/app/Console/Commands/Environment/SessionSettingsCommand.php @@ -0,0 +1,69 @@ + 'Filesystem (default)', + 'redis' => 'Redis', + 'database' => 'Database', + 'cookie' => 'Cookie', + ]; + + protected $description = 'Configure session settings for the Panel.'; + + protected $signature = 'p:environment:session + {--driver= : The session driver backend to use.} + {--redis-host= : Redis host to use for connections.} + {--redis-pass= : Password used to connect to redis.} + {--redis-port= : Port to connect to redis over.}'; + + protected array $variables = []; + + /** + * SessionSettingsCommand constructor. + */ + public function __construct(private Kernel $console) + { + parent::__construct(); + } + + /** + * Handle command execution. + */ + public function handle(): int + { + $selected = config('session.driver', 'file'); + $this->variables['SESSION_DRIVER'] = $this->option('driver') ?? $this->choice( + 'Session Driver', + self::SESSION_DRIVERS, + array_key_exists($selected, self::SESSION_DRIVERS) ? $selected : null + ); + + if ($this->variables['SESSION_DRIVER'] === 'redis') { + $this->requestRedisSettings(); + + if (config('queue.default') !== 'sync') { + $this->call('p:environment:queue-service', [ + '--use-redis' => true, + '--overwrite' => true, + ]); + } + } + + $this->writeToEnvironment($this->variables); + + $this->info($this->console->output()); + + return 0; + } +} diff --git a/app/Filament/Pages/Installer/PanelInstaller.php b/app/Filament/Pages/Installer/PanelInstaller.php new file mode 100644 index 000000000..222793a64 --- /dev/null +++ b/app/Filament/Pages/Installer/PanelInstaller.php @@ -0,0 +1,144 @@ +form->fill(); + } + + public function dehydrate(): void + { + Artisan::call('config:clear'); + Artisan::call('cache:clear'); + } + + protected function getFormSchema(): array + { + return [ + Wizard::make([ + RequirementsStep::make(), + EnvironmentStep::make(), + DatabaseStep::make(), + RedisStep::make() + ->hidden(fn (Get $get) => $get('env.SESSION_DRIVER') != 'redis' && $get('env.QUEUE_CONNECTION') != 'redis' && $get('env.CACHE_STORE') != 'redis'), + AdminUserStep::make(), + ]) + ->persistStepInQueryString() + ->submitAction(new HtmlString(Blade::render(<<<'BLADE' + + Finish + + BLADE))), + ]; + } + + protected function getFormStatePath(): ?string + { + return 'data'; + } + + protected function hasUnsavedDataChangesAlert(): bool + { + return true; + } + + public function submit() + { + try { + $inputs = $this->form->getState(); + + // Write variables to .env file + $variables = array_get($inputs, 'env'); + $this->writeToEnvironment($variables); + + $redisUsed = count(collect($variables)->filter(function ($item) { + return $item === 'redis'; + })) !== 0; + + // Create queue worker service (if needed) + if ($variables['QUEUE_CONNECTION'] !== 'sync') { + Artisan::call('p:environment:queue-service', [ + '--use-redis' => $redisUsed, + '--overwrite' => true, + ]); + } + + // Run migrations + Artisan::call('migrate', [ + '--force' => true, + '--seed' => true, + ]); + + // Create first admin user + $userData = array_get($inputs, 'user'); + $userData['root_admin'] = true; + app(UserCreationService::class)->handle($userData); + + // Install setup complete + $this->writeToEnvironment(['APP_INSTALLED' => true]); + + $this->rememberData(); + + Notification::make() + ->title('Successfully Installed') + ->success() + ->send(); + + redirect()->intended(Filament::getUrl()); + } catch (Exception $exception) { + Notification::make() + ->title('Installation Failed') + ->body($exception->getMessage()) + ->danger() + ->send(); + } + } +} diff --git a/app/Filament/Pages/Installer/Steps/AdminUserStep.php b/app/Filament/Pages/Installer/Steps/AdminUserStep.php new file mode 100644 index 000000000..68ebb6510 --- /dev/null +++ b/app/Filament/Pages/Installer/Steps/AdminUserStep.php @@ -0,0 +1,31 @@ +label('Admin User') + ->schema([ + TextInput::make('user.email') + ->label('Admin E-Mail') + ->required() + ->email() + ->default('admin@example.com'), + TextInput::make('user.username') + ->label('Admin Username') + ->required() + ->default('admin'), + TextInput::make('user.password') + ->label('Admin Password') + ->required() + ->password() + ->revealable(), + ]); + } +} diff --git a/app/Filament/Pages/Installer/Steps/DatabaseStep.php b/app/Filament/Pages/Installer/Steps/DatabaseStep.php new file mode 100644 index 000000000..94a7952e2 --- /dev/null +++ b/app/Filament/Pages/Installer/Steps/DatabaseStep.php @@ -0,0 +1,95 @@ +label('Database') + ->columns() + ->schema([ + TextInput::make('env.DB_DATABASE') + ->label(fn (Get $get) => $get('env.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name') + ->columnSpanFull() + ->hintIcon('tabler-question-mark') + ->hintIconTooltip(fn (Get $get) => $get('env.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.') + ->required() + ->default(fn (Get $get) => env('DB_DATABASE', $get('env.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')), + TextInput::make('env.DB_HOST') + ->label('Database Host') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The host of your database. Make sure it is reachable.') + ->required() + ->default(env('DB_HOST', '127.0.0.1')) + ->hidden(fn (Get $get) => $get('env.DB_CONNECTION') === 'sqlite'), + TextInput::make('env.DB_PORT') + ->label('Database Port') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The port of your database.') + ->required() + ->numeric() + ->minValue(1) + ->maxValue(65535) + ->default(env('DB_PORT', 3306)) + ->hidden(fn (Get $get) => $get('env.DB_CONNECTION') === 'sqlite'), + TextInput::make('env.DB_USERNAME') + ->label('Database Username') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The name of your database user.') + ->required() + ->default(env('DB_USERNAME', 'pelican')) + ->hidden(fn (Get $get) => $get('env.DB_CONNECTION') === 'sqlite'), + TextInput::make('env.DB_PASSWORD') + ->label('Database Password') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The password of your database user. Can be empty.') + ->password() + ->revealable() + ->default(env('DB_PASSWORD')) + ->hidden(fn (Get $get) => $get('env.DB_CONNECTION') === 'sqlite'), + ]) + ->afterValidation(function (Get $get) { + $driver = $get('env.DB_CONNECTION'); + if ($driver !== 'sqlite') { + /** @var DatabaseManager $database */ + $database = app(DatabaseManager::class); + + try { + config()->set('database.connections._panel_install_test', [ + 'driver' => $driver, + 'host' => $get('env.DB_HOST'), + 'port' => $get('env.DB_PORT'), + 'database' => $get('env.DB_DATABASE'), + 'username' => $get('env.DB_USERNAME'), + 'password' => $get('env.DB_PASSWORD'), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'strict' => true, + ]); + + $database->connection('_panel_install_test')->getPdo(); + } catch (PDOException $exception) { + Notification::make() + ->title('Database connection failed') + ->body($exception->getMessage()) + ->danger() + ->send(); + + $database->disconnect('_panel_install_test'); + + throw new Halt('Database connection failed'); + } + } + }); + } +} diff --git a/app/Filament/Pages/Installer/Steps/EnvironmentStep.php b/app/Filament/Pages/Installer/Steps/EnvironmentStep.php new file mode 100644 index 000000000..d9cc5eafa --- /dev/null +++ b/app/Filament/Pages/Installer/Steps/EnvironmentStep.php @@ -0,0 +1,94 @@ + 'Filesystem', + 'redis' => 'Redis', + ]; + + public const SESSION_DRIVERS = [ + 'file' => 'Filesystem', + 'redis' => 'Redis', + 'database' => 'Database', + 'cookie' => 'Cookie', + ]; + + public const QUEUE_DRIVERS = [ + 'database' => 'Database', + 'redis' => 'Redis', + 'sync' => 'Synchronous', + ]; + + public const DATABASE_DRIVERS = [ + 'sqlite' => 'SQLite', + 'mariadb' => 'MariaDB', + 'mysql' => 'MySQL', + ]; + + public static function make(): Step + { + return Step::make('environment') + ->label('Environment') + ->columns() + ->schema([ + TextInput::make('env.APP_NAME') + ->label('App Name') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('This will be the Name of your Panel.') + ->required() + ->default(config('app.name')), + TextInput::make('env.APP_URL') + ->label('App URL') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('This will be the URL you access your Panel from.') + ->required() + ->default(config('app.url')) + ->live() + ->afterStateUpdated(fn ($state, Set $set) => $set('env.SESSION_SECURE_COOKIE', str_starts_with($state, 'https://'))), + Toggle::make('env.SESSION_SECURE_COOKIE') + ->hidden() + ->default(env('SESSION_SECURE_COOKIE')), + ToggleButtons::make('env.CACHE_STORE') + ->label('Cache Driver') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The driver used for caching. We recommend "Filesystem".') + ->required() + ->inline() + ->options(self::CACHE_DRIVERS) + ->default(config('cache.default', 'file')), + ToggleButtons::make('env.SESSION_DRIVER') + ->label('Session Driver') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The driver used for storing sessions. We recommend "Filesystem" or "Database".') + ->required() + ->inline() + ->options(self::SESSION_DRIVERS) + ->default(config('session.driver', 'file')), + ToggleButtons::make('env.QUEUE_CONNECTION') + ->label('Queue Driver') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The driver used for handling queues. We recommend "Database".') + ->required() + ->inline() + ->options(self::QUEUE_DRIVERS) + ->default(config('queue.default', 'database')), + ToggleButtons::make('env.DB_CONNECTION') + ->label('Database Driver') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".') + ->required() + ->inline() + ->options(self::DATABASE_DRIVERS) + ->default(config('database.default', 'sqlite')), + ]); + } +} diff --git a/app/Filament/Pages/Installer/Steps/RedisStep.php b/app/Filament/Pages/Installer/Steps/RedisStep.php new file mode 100644 index 000000000..04a4b1b72 --- /dev/null +++ b/app/Filament/Pages/Installer/Steps/RedisStep.php @@ -0,0 +1,42 @@ +label('Redis') + ->columns() + ->schema([ + TextInput::make('env.REDIS_HOST') + ->label('Redis Host') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The host of your redis server. Make sure it is reachable.') + ->required() + ->default(config('database.redis.default.host')), + TextInput::make('env.REDIS_PORT') + ->label('Redis Port') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The port of your redis server.') + ->required() + ->default(config('database.redis.default.port')), + TextInput::make('env.REDIS_USERNAME') + ->label('Redis Username') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The name of your redis user. Can be empty') + ->default(config('database.redis.default.username')), + TextInput::make('env.REDIS_PASSWORD') + ->label('Redis Password') + ->hintIcon('tabler-question-mark') + ->hintIconTooltip('The password for your redis user. Can be empty.') + ->password() + ->revealable() + ->default(config('database.redis.default.password')), + ]); + } +} diff --git a/app/Filament/Pages/Installer/Steps/RequirementsStep.php b/app/Filament/Pages/Installer/Steps/RequirementsStep.php new file mode 100644 index 000000000..483e4ca1b --- /dev/null +++ b/app/Filament/Pages/Installer/Steps/RequirementsStep.php @@ -0,0 +1,87 @@ += 0; + + $fields = [ + Section::make('PHP Version') + ->description('8.2 or newer') + ->icon($correctPhpVersion ? 'tabler-check' : 'tabler-x') + ->iconColor($correctPhpVersion ? 'success' : 'danger') + ->schema([ + Placeholder::make('') + ->content('Your PHP Version ' . ($correctPhpVersion ? 'is' : 'needs to be') .' 8.2 or newer.'), + ]), + ]; + + $phpExtensions = [ + 'BCMath' => extension_loaded('bcmath'), + 'cURL' => extension_loaded('curl'), + 'GD' => extension_loaded('gd'), + 'intl' => extension_loaded('intl'), + 'mbstring' => extension_loaded('mbstring'), + 'MySQL' => extension_loaded('pdo_mysql'), + 'SQLite3' => extension_loaded('pdo_sqlite'), + 'XML' => extension_loaded('xml'), + 'Zip' => extension_loaded('zip'), + ]; + $allExtensionsInstalled = !in_array(false, $phpExtensions); + + $fields[] = Section::make('PHP Extensions') + ->description(implode(', ', array_keys($phpExtensions))) + ->icon($allExtensionsInstalled ? 'tabler-check' : 'tabler-x') + ->iconColor($allExtensionsInstalled ? 'success' : 'danger') + ->schema([ + Placeholder::make('') + ->content('All needed PHP Extensions are installed.') + ->visible($allExtensionsInstalled), + Placeholder::make('') + ->content('The following PHP Extensions are missing: ' . implode(', ', array_keys($phpExtensions, false))) + ->visible(!$allExtensionsInstalled), + ]); + + $folderPermissions = [ + 'Storage' => substr(sprintf('%o', fileperms(base_path('storage/'))), -4) >= 755, + 'Cache' => substr(sprintf('%o', fileperms(base_path('bootstrap/cache/'))), -4) >= 755, + ]; + $correctFolderPermissions = !in_array(false, $folderPermissions); + + $fields[] = Section::make('Folder Permissions') + ->description(implode(', ', array_keys($folderPermissions))) + ->icon($correctFolderPermissions ? 'tabler-check' : 'tabler-x') + ->iconColor($correctFolderPermissions ? 'success' : 'danger') + ->schema([ + Placeholder::make('') + ->content('All Folders have the correct permissions.') + ->visible($correctFolderPermissions), + Placeholder::make('') + ->content('The following Folders have wrong permissions: ' . implode(', ', array_keys($folderPermissions, false))) + ->visible(!$correctFolderPermissions), + ]); + + return Step::make('requirements') + ->label('Server Requirements') + ->schema($fields) + ->afterValidation(function () use ($correctPhpVersion, $allExtensionsInstalled, $correctFolderPermissions) { + if (!$correctPhpVersion || !$allExtensionsInstalled || !$correctFolderPermissions) { + Notification::make() + ->title('Some requirements are missing!') + ->danger() + ->send(); + + throw new Halt(); + } + }); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 384e4501d..19a43bef4 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -10,6 +10,8 @@ use App\Services\Helpers\SoftwareVersionService; use Dedoc\Scramble\Scramble; use Dedoc\Scramble\Support\Generator\OpenApi; use Dedoc\Scramble\Support\Generator\SecurityScheme; +use Filament\Support\Colors\Color; +use Filament\Support\Facades\FilamentColor; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Broadcast; @@ -80,6 +82,15 @@ class AppServiceProvider extends ServiceProvider Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { $event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class); }); + + FilamentColor::register([ + 'danger' => Color::Red, + 'gray' => Color::Zinc, + 'info' => Color::Sky, + 'primary' => Color::Blue, + 'success' => Color::Green, + 'warning' => Color::Amber, + ]); } /** diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 923a3a77d..8c8f6355e 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -9,7 +9,6 @@ use Filament\Http\Middleware\DisableBladeIconComponents; use Filament\Http\Middleware\DispatchServingFilamentEvent; use Filament\Panel; use Filament\PanelProvider; -use Filament\Support\Colors\Color; use Filament\Support\Facades\FilamentAsset; use Filament\Widgets; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; @@ -44,15 +43,6 @@ class AdminPanelProvider extends PanelProvider ->brandLogo(config('app.logo')) ->brandLogoHeight('2rem') ->profile(EditProfile::class, false) - ->colors([ - 'danger' => Color::Red, - 'gray' => Color::Zinc, - 'info' => Color::Sky, - 'primary' => Color::Blue, - 'success' => Color::Green, - 'warning' => Color::Amber, - 'blurple' => Color::hex('#5865F2'), - ]) ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') ->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters') diff --git a/app/Traits/Commands/RequestRedisSettingsTrait.php b/app/Traits/Commands/RequestRedisSettingsTrait.php new file mode 100644 index 000000000..527915da6 --- /dev/null +++ b/app/Traits/Commands/RequestRedisSettingsTrait.php @@ -0,0 +1,37 @@ +output->note(__('commands.appsettings.redis.note')); + $this->variables['REDIS_HOST'] = $this->option('redis-host') ?? $this->ask( + 'Redis Host', + config('database.redis.default.host') + ); + + $askForRedisPassword = true; + if (!empty(config('database.redis.default.password'))) { + $this->variables['REDIS_PASSWORD'] = config('database.redis.default.password'); + $askForRedisPassword = $this->confirm('It seems a password is already defined for Redis, would you like to change it?'); + } + + if ($askForRedisPassword) { + $this->output->comment(__('commands.appsettings.redis.comment')); + $this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden( + 'Redis Password' + ); + } + + if (empty($this->variables['REDIS_PASSWORD'])) { + $this->variables['REDIS_PASSWORD'] = 'null'; + } + + $this->variables['REDIS_PORT'] = $this->option('redis-port') ?? $this->ask( + 'Redis Port', + config('database.redis.default.port') + ); + } +} diff --git a/app/helpers.php b/app/helpers.php index c2aa5cd74..4f3e87021 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -40,3 +40,11 @@ if (!function_exists('object_get_strict')) { return $object; } } + +if (!function_exists('is_installed')) { + function is_installed(): bool + { + // This defaults to true so existing panels count as "installed" + return env('APP_INSTALLED', true); + } +} diff --git a/resources/views/filament/pages/installer.blade.php b/resources/views/filament/pages/installer.blade.php new file mode 100644 index 000000000..1977991c2 --- /dev/null +++ b/resources/views/filament/pages/installer.blade.php @@ -0,0 +1,7 @@ + + + {{ $this->form }} + + + + \ No newline at end of file diff --git a/routes/base.php b/routes/base.php index 6fbc41ae8..69e1c2f9a 100644 --- a/routes/base.php +++ b/routes/base.php @@ -1,5 +1,6 @@ withoutMiddleware(['auth', RequireTwoFactorAuthentication::class]) ->where('namespace', '.*'); +Route::get('installer', PanelInstaller::class)->name('installer') + ->withoutMiddleware(['auth', RequireTwoFactorAuthentication::class]); + Route::get('/{react}', [Base\IndexController::class, 'index']) ->where('react', '^(?!(\/)?(api|auth|admin|daemon|legacy)).+'); From 953ee940aa5955dfdad452dbdc46f48ca5e5bb55 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sun, 4 Aug 2024 18:53:54 +0200 Subject: [PATCH 38/65] Installer followup (#519) * remove queue worker service creation from installer * auto check redis --- .../Environment/QueueWorkerServiceCommand.php | 4 ++-- app/Filament/Pages/Installer/PanelInstaller.php | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/Console/Commands/Environment/QueueWorkerServiceCommand.php b/app/Console/Commands/Environment/QueueWorkerServiceCommand.php index 607f2ae9b..dbf1aa735 100644 --- a/app/Console/Commands/Environment/QueueWorkerServiceCommand.php +++ b/app/Console/Commands/Environment/QueueWorkerServiceCommand.php @@ -14,7 +14,6 @@ class QueueWorkerServiceCommand extends Command {--service-name= : Name of the queue worker service.} {--user= : The user that PHP runs under.} {--group= : The group that PHP runs under.} - {--use-redis : Whether redis is used.} {--overwrite : Force overwrite if the service file already exists.}'; public function handle(): void @@ -32,7 +31,8 @@ class QueueWorkerServiceCommand extends Command $user = $this->option('user') ?? $this->ask('Webserver User', 'www-data'); $group = $this->option('group') ?? $this->ask('Webserver Group', 'www-data'); - $afterRedis = $this->option('use-redis') ? ' + $redisUsed = config('queue.default') === 'redis' || config('session.driver') === 'redis' || config('cache.default') === 'redis'; + $afterRedis = $redisUsed ? ' After=redis-server.service' : ''; $basePath = base_path(); diff --git a/app/Filament/Pages/Installer/PanelInstaller.php b/app/Filament/Pages/Installer/PanelInstaller.php index 222793a64..00b182af0 100644 --- a/app/Filament/Pages/Installer/PanelInstaller.php +++ b/app/Filament/Pages/Installer/PanelInstaller.php @@ -99,18 +99,6 @@ class PanelInstaller extends SimplePage implements HasForms $variables = array_get($inputs, 'env'); $this->writeToEnvironment($variables); - $redisUsed = count(collect($variables)->filter(function ($item) { - return $item === 'redis'; - })) !== 0; - - // Create queue worker service (if needed) - if ($variables['QUEUE_CONNECTION'] !== 'sync') { - Artisan::call('p:environment:queue-service', [ - '--use-redis' => $redisUsed, - '--overwrite' => true, - ]); - } - // Run migrations Artisan::call('migrate', [ '--force' => true, From 2e094605e9a300273fb97032c86b70703c06bb2d Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sun, 4 Aug 2024 22:21:23 +0200 Subject: [PATCH 39/65] Round memory, swap and disk limits for wings (#523) --- .../Servers/ServerConfigurationStructureService.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index d9d0f7fe6..43b8c9736 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -51,12 +51,12 @@ class ServerConfigurationStructureService 'invocation' => $server->startup, 'skip_egg_scripts' => $server->skip_scripts, 'build' => [ - 'memory_limit' => config('panel.use_binary_prefix') ? $server->memory : $server->memory / 1.048576, - 'swap' => config('panel.use_binary_prefix') ? $server->swap : $server->swap / 1.048576, + 'memory_limit' => (int) round(config('panel.use_binary_prefix') ? $server->memory : $server->memory / 1.048576), + 'swap' => (int) round(config('panel.use_binary_prefix') ? $server->swap : $server->swap / 1.048576), 'io_weight' => $server->io, 'cpu_limit' => $server->cpu, 'threads' => $server->threads, - 'disk_space' => config('panel.use_binary_prefix') ? $server->disk : $server->disk / 1.048576, + 'disk_space' => (int) round(config('panel.use_binary_prefix') ? $server->disk : $server->disk / 1.048576), 'oom_killer' => $server->oom_killer, ], 'container' => [ From e8e19589693e88a8a541257920121c9b1a452c9a Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:31:52 +0200 Subject: [PATCH 40/65] Make default favicon path absolute to avoid 404 on admin (#529) --- app/Filament/Pages/Settings.php | 2 +- config/app.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php index 5ec862795..ea1e099c8 100644 --- a/app/Filament/Pages/Settings.php +++ b/app/Filament/Pages/Settings.php @@ -92,7 +92,7 @@ class Settings extends Page implements HasForms ->hintIcon('tabler-question-mark') ->hintIconTooltip('Favicons should be placed in the public folder, located in the root panel directory.') ->required() - ->default(env('APP_FAVICON', './pelican.ico')), + ->default(env('APP_FAVICON', '/pelican.ico')), Toggle::make('APP_DEBUG') ->label('Enable Debug Mode?') ->inline(false) diff --git a/config/app.php b/config/app.php index f9a4acc85..1a89ba65f 100644 --- a/config/app.php +++ b/config/app.php @@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Facade; return [ 'name' => env('APP_NAME', 'Pelican'), - 'favicon' => env('APP_FAVICON', './pelican.ico'), + 'favicon' => env('APP_FAVICON', '/pelican.ico'), 'version' => 'canary', From d6e0421aafe61ab8f245a605015ef3ea94a47e8c Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:59:16 +0200 Subject: [PATCH 41/65] Update StoreNodeRequest.php (#531) --- app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index c7166e9df..913204b83 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -20,6 +20,7 @@ class StoreNodeRequest extends ApplicationApiRequest return collect($rules ?? Node::getRules())->only([ 'public', 'name', + 'description', 'fqdn', 'scheme', 'behind_proxy', From 7f8fb3f650b600332c729cebf4ed77675d21de50 Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:59:28 +0200 Subject: [PATCH 42/65] Patch Env CLI (#528) * Remove unused option * Add redis user * Adapt lang * Change default redis username * Cleanup * Update app/Traits/Commands/RequestRedisSettingsTrait.php Co-authored-by: Boy132 --------- Co-authored-by: Boy132 --- .../Environment/CacheSettingsCommand.php | 2 +- .../Environment/QueueSettingsCommand.php | 2 +- .../Environment/SessionSettingsCommand.php | 2 +- .../Commands/RequestRedisSettingsTrait.php | 17 ++++++++++++++++- lang/en/commands.php | 3 ++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/Console/Commands/Environment/CacheSettingsCommand.php b/app/Console/Commands/Environment/CacheSettingsCommand.php index 4870e1bc9..97705734d 100644 --- a/app/Console/Commands/Environment/CacheSettingsCommand.php +++ b/app/Console/Commands/Environment/CacheSettingsCommand.php @@ -23,6 +23,7 @@ class CacheSettingsCommand extends Command protected $signature = 'p:environment:cache {--driver= : The cache driver backend to use.} {--redis-host= : Redis host to use for connections.} + {--redis-user= : User used to connect to redis.} {--redis-pass= : Password used to connect to redis.} {--redis-port= : Port to connect to redis over.}'; @@ -53,7 +54,6 @@ class CacheSettingsCommand extends Command if (config('queue.default') !== 'sync') { $this->call('p:environment:queue-service', [ - '--use-redis' => true, '--overwrite' => true, ]); } diff --git a/app/Console/Commands/Environment/QueueSettingsCommand.php b/app/Console/Commands/Environment/QueueSettingsCommand.php index 3d48a3123..6cca843ae 100644 --- a/app/Console/Commands/Environment/QueueSettingsCommand.php +++ b/app/Console/Commands/Environment/QueueSettingsCommand.php @@ -23,6 +23,7 @@ class QueueSettingsCommand extends Command protected $signature = 'p:environment:queue {--driver= : The queue driver backend to use.} {--redis-host= : Redis host to use for connections.} + {--redis-user= : User used to connect to redis.} {--redis-pass= : Password used to connect to redis.} {--redis-port= : Port to connect to redis over.}'; @@ -52,7 +53,6 @@ class QueueSettingsCommand extends Command $this->requestRedisSettings(); $this->call('p:environment:queue-service', [ - '--use-redis' => true, '--overwrite' => true, ]); } diff --git a/app/Console/Commands/Environment/SessionSettingsCommand.php b/app/Console/Commands/Environment/SessionSettingsCommand.php index 9a4081c51..a314d8edf 100644 --- a/app/Console/Commands/Environment/SessionSettingsCommand.php +++ b/app/Console/Commands/Environment/SessionSettingsCommand.php @@ -24,6 +24,7 @@ class SessionSettingsCommand extends Command protected $signature = 'p:environment:session {--driver= : The session driver backend to use.} {--redis-host= : Redis host to use for connections.} + {--redis-user= : User used to connect to redis.} {--redis-pass= : Password used to connect to redis.} {--redis-port= : Port to connect to redis over.}'; @@ -54,7 +55,6 @@ class SessionSettingsCommand extends Command if (config('queue.default') !== 'sync') { $this->call('p:environment:queue-service', [ - '--use-redis' => true, '--overwrite' => true, ]); } diff --git a/app/Traits/Commands/RequestRedisSettingsTrait.php b/app/Traits/Commands/RequestRedisSettingsTrait.php index 527915da6..07ef229c2 100644 --- a/app/Traits/Commands/RequestRedisSettingsTrait.php +++ b/app/Traits/Commands/RequestRedisSettingsTrait.php @@ -12,12 +12,24 @@ trait RequestRedisSettingsTrait config('database.redis.default.host') ); + $askForRedisUser = true; $askForRedisPassword = true; + + if (!empty(config('database.redis.default.user'))) { + $this->variables['REDIS_USERNAME'] = config('database.redis.default.user'); + $askForRedisUser = $this->confirm(__('commands.appsettings.redis.confirm', ['field' => 'user'])); + } if (!empty(config('database.redis.default.password'))) { $this->variables['REDIS_PASSWORD'] = config('database.redis.default.password'); - $askForRedisPassword = $this->confirm('It seems a password is already defined for Redis, would you like to change it?'); + $askForRedisPassword = $this->confirm(__('commands.appsettings.redis.confirm', ['field' => 'password'])); } + if ($askForRedisUser) { + $this->output->comment(__('commands.appsettings.redis.comment')); + $this->variables['REDIS_USERNAME'] = $this->option('redis-user') ?? $this->output->askHidden( + 'Redis User' + ); + } if ($askForRedisPassword) { $this->output->comment(__('commands.appsettings.redis.comment')); $this->variables['REDIS_PASSWORD'] = $this->option('redis-pass') ?? $this->output->askHidden( @@ -25,6 +37,9 @@ trait RequestRedisSettingsTrait ); } + if (empty($this->variables['REDIS_USERNAME'])) { + $this->variables['REDIS_USERNAME'] = 'null'; + } if (empty($this->variables['REDIS_PASSWORD'])) { $this->variables['REDIS_PASSWORD'] = 'null'; } diff --git a/lang/en/commands.php b/lang/en/commands.php index 3b9ab9f45..b6fa6c9ca 100644 --- a/lang/en/commands.php +++ b/lang/en/commands.php @@ -9,7 +9,8 @@ return [ ], 'redis' => [ 'note' => 'You\'ve selected the Redis driver for one or more options, please provide valid connection information below. In most cases you can use the defaults provided unless you have modified your setup.', - 'comment' => 'By default a Redis server instance has no password as it is running locally and inaccessible to the outside world. If this is the case, simply hit enter without entering a value.', + 'comment' => 'By default a Redis server instance has for username default and no password as it is running locally and inaccessible to the outside world. If this is the case, simply hit enter without entering a value.', + 'confirm' => 'It seems a :field is already defined for Redis, would you like to change it?', ], ], 'database_settings' => [ From 1fba700096f3e44f84f12bdb9265f5c36020c62f Mon Sep 17 00:00:00 2001 From: Boy132 Date: Fri, 9 Aug 2024 08:23:03 +0200 Subject: [PATCH 43/65] Improve error handling for Installer (#532) * make sure migrations ran * add loading indicator to finish button * make error notification persistent * fix migration checker * cleanup traits --- .../Environment/AppSettingsCommand.php | 2 +- .../Environment/CacheSettingsCommand.php | 4 +-- .../Environment/DatabaseSettingsCommand.php | 2 +- .../Environment/EmailSettingsCommand.php | 2 +- .../Environment/QueueSettingsCommand.php | 4 +-- .../Environment/SessionSettingsCommand.php | 4 +-- .../Commands/Overrides/SeedCommand.php | 2 +- app/Console/Commands/Overrides/UpCommand.php | 2 +- .../Pages/Installer/PanelInstaller.php | 15 ++++++++-- app/Filament/Pages/Settings.php | 2 +- app/Traits/CheckMigrationsTrait.php | 29 +++++++++++++++++++ .../Commands}/RequiresDatabaseMigrations.php | 28 ++++-------------- .../{Commands => }/EnvironmentWriterTrait.php | 7 +++-- .../Helpers/EnvironmentWriterTraitTest.php | 2 +- 14 files changed, 65 insertions(+), 40 deletions(-) create mode 100644 app/Traits/CheckMigrationsTrait.php rename app/{Console => Traits/Commands}/RequiresDatabaseMigrations.php (63%) rename app/Traits/{Commands => }/EnvironmentWriterTrait.php (88%) diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index b3ee96e08..e74278ad3 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -2,8 +2,8 @@ namespace App\Console\Commands\Environment; +use App\Traits\EnvironmentWriterTrait; use Illuminate\Console\Command; -use App\Traits\Commands\EnvironmentWriterTrait; use Illuminate\Support\Facades\Artisan; class AppSettingsCommand extends Command diff --git a/app/Console/Commands/Environment/CacheSettingsCommand.php b/app/Console/Commands/Environment/CacheSettingsCommand.php index 97705734d..7837bea1d 100644 --- a/app/Console/Commands/Environment/CacheSettingsCommand.php +++ b/app/Console/Commands/Environment/CacheSettingsCommand.php @@ -2,10 +2,10 @@ namespace App\Console\Commands\Environment; +use App\Traits\Commands\RequestRedisSettingsTrait; +use App\Traits\EnvironmentWriterTrait; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; -use App\Traits\Commands\EnvironmentWriterTrait; -use App\Traits\Commands\RequestRedisSettingsTrait; class CacheSettingsCommand extends Command { diff --git a/app/Console/Commands/Environment/DatabaseSettingsCommand.php b/app/Console/Commands/Environment/DatabaseSettingsCommand.php index f05f9bbc4..f1218e40c 100644 --- a/app/Console/Commands/Environment/DatabaseSettingsCommand.php +++ b/app/Console/Commands/Environment/DatabaseSettingsCommand.php @@ -2,10 +2,10 @@ namespace App\Console\Commands\Environment; +use App\Traits\EnvironmentWriterTrait; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; use Illuminate\Database\DatabaseManager; -use App\Traits\Commands\EnvironmentWriterTrait; class DatabaseSettingsCommand extends Command { diff --git a/app/Console/Commands/Environment/EmailSettingsCommand.php b/app/Console/Commands/Environment/EmailSettingsCommand.php index 1059cf780..87c8186c1 100644 --- a/app/Console/Commands/Environment/EmailSettingsCommand.php +++ b/app/Console/Commands/Environment/EmailSettingsCommand.php @@ -2,8 +2,8 @@ namespace App\Console\Commands\Environment; +use App\Traits\EnvironmentWriterTrait; use Illuminate\Console\Command; -use App\Traits\Commands\EnvironmentWriterTrait; class EmailSettingsCommand extends Command { diff --git a/app/Console/Commands/Environment/QueueSettingsCommand.php b/app/Console/Commands/Environment/QueueSettingsCommand.php index 6cca843ae..f8b667402 100644 --- a/app/Console/Commands/Environment/QueueSettingsCommand.php +++ b/app/Console/Commands/Environment/QueueSettingsCommand.php @@ -2,10 +2,10 @@ namespace App\Console\Commands\Environment; +use App\Traits\Commands\RequestRedisSettingsTrait; +use App\Traits\EnvironmentWriterTrait; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; -use App\Traits\Commands\EnvironmentWriterTrait; -use App\Traits\Commands\RequestRedisSettingsTrait; class QueueSettingsCommand extends Command { diff --git a/app/Console/Commands/Environment/SessionSettingsCommand.php b/app/Console/Commands/Environment/SessionSettingsCommand.php index a314d8edf..be99686f4 100644 --- a/app/Console/Commands/Environment/SessionSettingsCommand.php +++ b/app/Console/Commands/Environment/SessionSettingsCommand.php @@ -2,10 +2,10 @@ namespace App\Console\Commands\Environment; +use App\Traits\Commands\RequestRedisSettingsTrait; +use App\Traits\EnvironmentWriterTrait; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; -use App\Traits\Commands\EnvironmentWriterTrait; -use App\Traits\Commands\RequestRedisSettingsTrait; class SessionSettingsCommand extends Command { diff --git a/app/Console/Commands/Overrides/SeedCommand.php b/app/Console/Commands/Overrides/SeedCommand.php index 23c476860..c4ca828b2 100644 --- a/app/Console/Commands/Overrides/SeedCommand.php +++ b/app/Console/Commands/Overrides/SeedCommand.php @@ -2,7 +2,7 @@ namespace App\Console\Commands\Overrides; -use App\Console\RequiresDatabaseMigrations; +use App\Traits\Commands\RequiresDatabaseMigrations; use Illuminate\Database\Console\Seeds\SeedCommand as BaseSeedCommand; class SeedCommand extends BaseSeedCommand diff --git a/app/Console/Commands/Overrides/UpCommand.php b/app/Console/Commands/Overrides/UpCommand.php index 3b71ea854..e17808e24 100644 --- a/app/Console/Commands/Overrides/UpCommand.php +++ b/app/Console/Commands/Overrides/UpCommand.php @@ -2,7 +2,7 @@ namespace App\Console\Commands\Overrides; -use App\Console\RequiresDatabaseMigrations; +use App\Traits\Commands\RequiresDatabaseMigrations; use Illuminate\Foundation\Console\UpCommand as BaseUpCommand; class UpCommand extends BaseUpCommand diff --git a/app/Filament/Pages/Installer/PanelInstaller.php b/app/Filament/Pages/Installer/PanelInstaller.php index 00b182af0..2e0dcaf57 100644 --- a/app/Filament/Pages/Installer/PanelInstaller.php +++ b/app/Filament/Pages/Installer/PanelInstaller.php @@ -8,7 +8,8 @@ use App\Filament\Pages\Installer\Steps\EnvironmentStep; use App\Filament\Pages\Installer\Steps\RedisStep; use App\Filament\Pages\Installer\Steps\RequirementsStep; use App\Services\Users\UserCreationService; -use App\Traits\Commands\EnvironmentWriterTrait; +use App\Traits\CheckMigrationsTrait; +use App\Traits\EnvironmentWriterTrait; use Exception; use Filament\Facades\Filament; use Filament\Forms\Components\Wizard; @@ -29,6 +30,7 @@ use Illuminate\Support\HtmlString; */ class PanelInstaller extends SimplePage implements HasForms { + use CheckMigrationsTrait; use EnvironmentWriterTrait; use HasUnsavedDataChangesAlert; use InteractsWithForms; @@ -73,8 +75,10 @@ class PanelInstaller extends SimplePage implements HasForms Finish + BLADE))), ]; @@ -105,13 +109,17 @@ class PanelInstaller extends SimplePage implements HasForms '--seed' => true, ]); + if (!$this->hasCompletedMigrations()) { + throw new Exception('Migrations didn\'t run successfully. Double check your database configuration.'); + } + // Create first admin user $userData = array_get($inputs, 'user'); $userData['root_admin'] = true; app(UserCreationService::class)->handle($userData); // Install setup complete - $this->writeToEnvironment(['APP_INSTALLED' => true]); + $this->writeToEnvironment(['APP_INSTALLED' => 'true']); $this->rememberData(); @@ -122,10 +130,13 @@ class PanelInstaller extends SimplePage implements HasForms redirect()->intended(Filament::getUrl()); } catch (Exception $exception) { + report($exception); + Notification::make() ->title('Installation Failed') ->body($exception->getMessage()) ->danger() + ->persistent() ->send(); } } diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php index ea1e099c8..4cfe7ce10 100644 --- a/app/Filament/Pages/Settings.php +++ b/app/Filament/Pages/Settings.php @@ -4,7 +4,7 @@ namespace App\Filament\Pages; use App\Models\Backup; use App\Notifications\MailTested; -use App\Traits\Commands\EnvironmentWriterTrait; +use App\Traits\EnvironmentWriterTrait; use Exception; use Filament\Actions\Action; use Filament\Forms\Components\Actions\Action as FormAction; diff --git a/app/Traits/CheckMigrationsTrait.php b/app/Traits/CheckMigrationsTrait.php new file mode 100644 index 000000000..ee2de8f3a --- /dev/null +++ b/app/Traits/CheckMigrationsTrait.php @@ -0,0 +1,29 @@ +make('migrator'); + + $files = $migrator->getMigrationFiles(database_path('migrations')); + + if (!$migrator->repositoryExists()) { + return false; + } + + if (array_diff(array_keys($files), $migrator->getRepository()->getRan())) { + return false; + } + + return true; + } +} diff --git a/app/Console/RequiresDatabaseMigrations.php b/app/Traits/Commands/RequiresDatabaseMigrations.php similarity index 63% rename from app/Console/RequiresDatabaseMigrations.php rename to app/Traits/Commands/RequiresDatabaseMigrations.php index c6b71f1bd..67551a26a 100644 --- a/app/Console/RequiresDatabaseMigrations.php +++ b/app/Traits/Commands/RequiresDatabaseMigrations.php @@ -1,32 +1,16 @@ getLaravel()->make('migrator'); - - $files = $migrator->getMigrationFiles(database_path('migrations')); - - if (!$migrator->repositoryExists()) { - return false; - } - - if (array_diff(array_keys($files), $migrator->getRepository()->getRan())) { - return false; - } - - return true; - } + use CheckMigrationsTrait; /** * Throw a massive error into the console to hopefully catch the users attention and get diff --git a/app/Traits/Commands/EnvironmentWriterTrait.php b/app/Traits/EnvironmentWriterTrait.php similarity index 88% rename from app/Traits/Commands/EnvironmentWriterTrait.php rename to app/Traits/EnvironmentWriterTrait.php index eb40f8c8b..cd7fbdfe0 100644 --- a/app/Traits/Commands/EnvironmentWriterTrait.php +++ b/app/Traits/EnvironmentWriterTrait.php @@ -1,8 +1,8 @@ Date: Sat, 10 Aug 2024 18:20:21 -0400 Subject: [PATCH 44/65] Fix Single Egg Import --- app/Filament/Resources/EggResource/Pages/EditEgg.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/Filament/Resources/EggResource/Pages/EditEgg.php b/app/Filament/Resources/EggResource/Pages/EditEgg.php index b4af8bc0a..acd76e479 100644 --- a/app/Filament/Resources/EggResource/Pages/EditEgg.php +++ b/app/Filament/Resources/EggResource/Pages/EditEgg.php @@ -270,16 +270,14 @@ class EditEgg extends EditRecord Notification::make() ->title('Import Failed') ->body($exception->getMessage()) - ->danger() + ->danger() // Will Robinson ->send(); report($exception); return; } - } - - if (!empty($data['url'])) { + } elseif (!empty($data['url'])) { try { $eggImportService->fromUrl($data['url'], $egg); } catch (Exception $exception) { From bad5409d9c5354c42c5eaec4378f5a6d0f779635 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sat, 10 Aug 2024 19:39:41 -0400 Subject: [PATCH 45/65] Fix saving SMTP without encryption --- app/Filament/Pages/Settings.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php index 4cfe7ce10..aec3819c4 100644 --- a/app/Filament/Pages/Settings.php +++ b/app/Filament/Pages/Settings.php @@ -262,28 +262,27 @@ class Settings extends Page implements HasForms ->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp') ->schema([ TextInput::make('MAIL_HOST') - ->label('SMTP Host') + ->label('Host') ->required() ->default(env('MAIL_HOST', config('mail.mailers.smtp.host'))), TextInput::make('MAIL_PORT') - ->label('SMTP Port') + ->label('Port') ->required() ->numeric() ->minValue(1) ->maxValue(65535) ->default(env('MAIL_PORT', config('mail.mailers.smtp.port'))), TextInput::make('MAIL_USERNAME') - ->label('SMTP Username') + ->label('Username') ->required() ->default(env('MAIL_USERNAME', config('mail.mailers.smtp.username'))), TextInput::make('MAIL_PASSWORD') - ->label('SMTP Password') + ->label('Password') ->password() ->revealable() ->default(env('MAIL_PASSWORD')), ToggleButtons::make('MAIL_ENCRYPTION') - ->label('SMTP encryption') - ->required() + ->label('Encryption') ->inline() ->options(['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None']) ->default(env('MAIL_ENCRYPTION', config('mail.mailers.smtp.encryption', 'tls'))), @@ -293,15 +292,15 @@ class Settings extends Page implements HasForms ->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun') ->schema([ TextInput::make('MAILGUN_DOMAIN') - ->label('Mailgun Domain') + ->label('Domain') ->required() ->default(env('MAILGUN_DOMAIN', config('services.mailgun.domain'))), TextInput::make('MAILGUN_SECRET') - ->label('Mailgun Secret') + ->label('Secret') ->required() ->default(env('MAIL_USERNAME', config('services.mailgun.secret'))), TextInput::make('MAILGUN_ENDPOINT') - ->label('Mailgun Endpoint') + ->label('Endpoint') ->required() ->default(env('MAILGUN_ENDPOINT', config('services.mailgun.endpoint'))), ]), From 155f2d6476984185852fb69ed73958c6475437e9 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 13 Aug 2024 19:43:16 +0200 Subject: [PATCH 46/65] Add migration to fix allocations server_id foreign key (#542) * add migration to fix allocations server_id foreign key * fix the fix... --- ...1337_fix_allocation_server_foreign_key.php | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 database/migrations/2024_08_13_171337_fix_allocation_server_foreign_key.php diff --git a/database/migrations/2024_08_13_171337_fix_allocation_server_foreign_key.php b/database/migrations/2024_08_13_171337_fix_allocation_server_foreign_key.php new file mode 100644 index 000000000..9abb9317e --- /dev/null +++ b/database/migrations/2024_08_13_171337_fix_allocation_server_foreign_key.php @@ -0,0 +1,54 @@ +getDriverName() !== 'sqlite') { + return; + } + + // Disable foreign checks + // legacy_alter_table needs to be 'ON' so existing foreign key table references aren't renamed when renaming the table, see https://www.sqlite.org/lang_altertable.html + DB::statement('PRAGMA foreign_keys = OFF'); + DB::statement('PRAGMA legacy_alter_table = ON'); + + DB::transaction(function () { + DB::statement('ALTER TABLE allocations RENAME TO _allocations_old'); + DB::statement('CREATE TABLE allocations + ("id" integer primary key autoincrement not null, + "node_id" integer not null, + "ip" varchar not null, + "port" integer not null, + "server_id" integer, + "created_at" datetime, + "updated_at" datetime, + "ip_alias" text, + "notes" varchar, + foreign key("node_id") references "nodes"("id") on delete cascade, + foreign key("server_id") references "servers"("id") on delete set null)'); + DB::statement('INSERT INTO allocations SELECT * FROM _allocations_old'); + DB::statement('DROP TABLE _allocations_old'); + DB::statement('CREATE UNIQUE INDEX "allocations_node_id_ip_port_unique" on "allocations" ("node_id", "ip", "port")'); + }); + + DB::statement('PRAGMA foreign_keys = ON'); + DB::statement('PRAGMA legacy_alter_table = OFF'); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Reverse not needed + } +}; From 1864fff04f8b2847f8ff043bb38819f481b55028 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Fri, 16 Aug 2024 22:44:12 +0200 Subject: [PATCH 47/65] Update default image for new eggs (#540) --- app/Filament/Resources/EggResource/Pages/CreateEgg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Resources/EggResource/Pages/CreateEgg.php b/app/Filament/Resources/EggResource/Pages/CreateEgg.php index d73863f57..856963fab 100644 --- a/app/Filament/Resources/EggResource/Pages/CreateEgg.php +++ b/app/Filament/Resources/EggResource/Pages/CreateEgg.php @@ -187,7 +187,7 @@ class CreateEgg extends CreateRecord TextInput::make('script_container') ->required() ->maxLength(255) - ->default('alpine:3.4'), + ->default('ghcr.io/pelican-eggs/installers:debian'), Select::make('script_entry') ->selectablePlaceholder(false) From 68e24896ae0a4380342bcf148bc6fced53b1983a Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+RMartinOscar@users.noreply.github.com> Date: Fri, 16 Aug 2024 22:50:09 +0200 Subject: [PATCH 48/65] Patch for node 18 (#539) --- package.json | 3 ++- yarn.lock | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ebb3266be..713b5cbaf 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react-router-dom": "^5.1.2", "react-transition-group": "^4.4.1", "reaptcha": "^1.7.2", + "rimraf": "^4", "sockette": "^2.0.6", "styled-components": "^5.2.1", "styled-components-breakpoint": "^3.0.0-preview.20", @@ -130,7 +131,7 @@ "yarn-deduplicate": "^1.1.1" }, "scripts": { - "clean": "cd public/assets && find . \\( -name \"*.js\" -o -name \"*.map\" \\) -type f -delete", + "clean": "cd public/assets && rimraf -g *.js *.map", "test": "jest", "lint": "eslint ./resources/scripts/**/*.{ts,tsx} --ext .ts,.tsx", "watch": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", diff --git a/yarn.lock b/yarn.lock index ff431b75e..fca7a7415 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2736,6 +2736,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -4758,6 +4765,16 @@ glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^9.2.0: + version "9.3.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" + integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -6164,6 +6181,11 @@ loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4 dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -6376,6 +6398,13 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^8.0.2: + version "8.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" + integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -6409,11 +6438,21 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" +minipass@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + minipass@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -6923,6 +6962,14 @@ path-parse@^1.0.6, path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.6.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -7991,6 +8038,13 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^4: + version "4.4.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" + integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og== + dependencies: + glob "^9.2.0" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" From bf23389dba1cd5fcfc561025e5523dc194b31987 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sun, 18 Aug 2024 16:11:40 +0200 Subject: [PATCH 49/65] Fix default value for mailgun secret (#552) --- app/Filament/Pages/Settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php index aec3819c4..ad9ea96ba 100644 --- a/app/Filament/Pages/Settings.php +++ b/app/Filament/Pages/Settings.php @@ -298,7 +298,7 @@ class Settings extends Page implements HasForms TextInput::make('MAILGUN_SECRET') ->label('Secret') ->required() - ->default(env('MAIL_USERNAME', config('services.mailgun.secret'))), + ->default(env('MAILGUN_SECRET', config('services.mailgun.secret'))), TextInput::make('MAILGUN_ENDPOINT') ->label('Endpoint') ->required() From ffadf9ac163c57ef5b0bca72016a109015398381 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sun, 18 Aug 2024 16:43:40 +0200 Subject: [PATCH 50/65] Clear cache before running migrations (#553) --- app/Filament/Pages/Installer/PanelInstaller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Filament/Pages/Installer/PanelInstaller.php b/app/Filament/Pages/Installer/PanelInstaller.php index 2e0dcaf57..11cbd98c8 100644 --- a/app/Filament/Pages/Installer/PanelInstaller.php +++ b/app/Filament/Pages/Installer/PanelInstaller.php @@ -103,6 +103,10 @@ class PanelInstaller extends SimplePage implements HasForms $variables = array_get($inputs, 'env'); $this->writeToEnvironment($variables); + // Clear cache + Artisan::call('config:clear'); + Artisan::call('cache:clear'); + // Run migrations Artisan::call('migrate', [ '--force' => true, From d0c89b0729e8b52a6135b2076b291f74d9c3c54d Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sun, 18 Aug 2024 17:23:02 +0200 Subject: [PATCH 51/65] ix installer cache (#554) --- app/Filament/Pages/Installer/PanelInstaller.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Filament/Pages/Installer/PanelInstaller.php b/app/Filament/Pages/Installer/PanelInstaller.php index 11cbd98c8..b6aea2b09 100644 --- a/app/Filament/Pages/Installer/PanelInstaller.php +++ b/app/Filament/Pages/Installer/PanelInstaller.php @@ -103,14 +103,11 @@ class PanelInstaller extends SimplePage implements HasForms $variables = array_get($inputs, 'env'); $this->writeToEnvironment($variables); - // Clear cache - Artisan::call('config:clear'); - Artisan::call('cache:clear'); - // Run migrations Artisan::call('migrate', [ '--force' => true, '--seed' => true, + '--database' => $variables['DB_CONNECTION'], ]); if (!$this->hasCompletedMigrations()) { From d1ca21de9f16008594d0b122187d6b8b6cb58426 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sun, 18 Aug 2024 11:41:36 -0400 Subject: [PATCH 52/65] Test Runners --- .github/workflows/ci.yaml | 6 +++--- .github/workflows/cla.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e2ee8847..14591007a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,7 +11,7 @@ on: jobs: mysql: name: MySQL - runs-on: ubuntu-latest + runs-on: self-hosted strategy: fail-fast: false matrix: @@ -84,7 +84,7 @@ jobs: mariadb: name: MariaDB - runs-on: ubuntu-latest + runs-on: self-hosted strategy: fail-fast: false matrix: @@ -157,7 +157,7 @@ jobs: sqlite: name: SQLite - runs-on: ubuntu-latest + runs-on: self-hosted strategy: fail-fast: false matrix: diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 90621b05c..28d96a698 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -13,7 +13,7 @@ permissions: jobs: CLAAssistant: - runs-on: ubuntu-latest + runs-on: self-hosted steps: - name: "CLA Assistant" if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' From 0ff429215dad385e1373dfe3afc26d79dbf697d5 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sun, 18 Aug 2024 11:54:12 -0400 Subject: [PATCH 53/65] Revert "Test Runners" This reverts commit d1ca21de9f16008594d0b122187d6b8b6cb58426. --- .github/workflows/ci.yaml | 6 +++--- .github/workflows/cla.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 14591007a..5e2ee8847 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,7 +11,7 @@ on: jobs: mysql: name: MySQL - runs-on: self-hosted + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -84,7 +84,7 @@ jobs: mariadb: name: MariaDB - runs-on: self-hosted + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -157,7 +157,7 @@ jobs: sqlite: name: SQLite - runs-on: self-hosted + runs-on: ubuntu-latest strategy: fail-fast: false matrix: diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 28d96a698..90621b05c 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -13,7 +13,7 @@ permissions: jobs: CLAAssistant: - runs-on: self-hosted + runs-on: ubuntu-latest steps: - name: "CLA Assistant" if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' From c2b1a98d292753153de3c07fb65e4973a67e5ab4 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Mon, 19 Aug 2024 08:33:53 +0200 Subject: [PATCH 54/65] Convert variable "rules" to array (#507) * convert variable "rules" to array * allow importing eggs with string rules * fix tests * update stock eggs to rules array --- .../Resources/EggResource/Pages/CreateEgg.php | 29 +++++- .../Resources/EggResource/Pages/EditEgg.php | 29 +++++- .../ServerResource/Pages/CreateServer.php | 16 ++-- .../ServerResource/Pages/EditServer.php | 20 ++-- app/Models/EggVariable.php | 9 +- .../Eggs/Sharing/EggImporterService.php | 2 + .../Variables/VariableCreationService.php | 4 +- .../Eggs/Variables/VariableUpdateService.php | 11 +-- .../Api/Client/EggVariableTransformer.php | 2 +- .../eggs/minecraft/egg-bungeecord.json | 13 ++- .../eggs/minecraft/egg-forge-minecraft.json | 24 ++++- .../Seeders/eggs/minecraft/egg-paper.json | 24 ++++- .../minecraft/egg-sponge--sponge-vanilla.json | 12 ++- .../eggs/minecraft/egg-vanilla-minecraft.json | 13 ++- database/Seeders/eggs/rust/egg-rust.json | 96 +++++++++++++++---- ...egg-counter--strike--global-offensive.json | 21 +++- .../egg-custom-source-engine-game.json | 35 +++++-- .../eggs/source-engine/egg-garrys-mod.json | 48 ++++++++-- .../eggs/source-engine/egg-insurgency.json | 12 ++- .../source-engine/egg-team-fortress2.json | 19 +++- .../eggs/voice-servers/egg-mumble-server.json | 8 +- .../voice-servers/egg-teamspeak3-server.json | 38 ++++++-- ...24_07_25_072050_convert_rules_to_array.php | 37 +++++++ .../Startup/UpdateStartupVariableTest.php | 2 +- .../Servers/VariableValidatorServiceTest.php | 2 +- 25 files changed, 408 insertions(+), 118 deletions(-) create mode 100644 database/migrations/2024_07_25_072050_convert_rules_to_array.php diff --git a/app/Filament/Resources/EggResource/Pages/CreateEgg.php b/app/Filament/Resources/EggResource/Pages/CreateEgg.php index 856963fab..5dfa3b05f 100644 --- a/app/Filament/Resources/EggResource/Pages/CreateEgg.php +++ b/app/Filament/Resources/EggResource/Pages/CreateEgg.php @@ -134,7 +134,7 @@ class CreateEgg extends CreateRecord ->mutateRelationshipDataBeforeCreateUsing(function (array $data): array { $data['default_value'] ??= ''; $data['description'] ??= ''; - $data['rules'] ??= ''; + $data['rules'] ??= []; $data['user_viewable'] ??= ''; $data['user_editable'] ??= ''; @@ -143,7 +143,7 @@ class CreateEgg extends CreateRecord ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array { $data['default_value'] ??= ''; $data['description'] ??= ''; - $data['rules'] ??= ''; + $data['rules'] ??= []; $data['user_viewable'] ??= ''; $data['user_editable'] ??= ''; @@ -173,7 +173,30 @@ class CreateEgg extends CreateRecord Checkbox::make('user_viewable')->label('Viewable'), Checkbox::make('user_editable')->label('Editable'), ]), - Textarea::make('rules')->columnSpanFull(), + TagsInput::make('rules') + ->columnSpanFull() + ->placeholder('Add Rule') + ->reorderable() + ->suggestions([ + 'required', + 'nullable', + 'string', + 'integer', + 'numeric', + 'boolean', + 'alpha', + 'alpha_dash', + 'alpha_num', + 'url', + 'email', + 'regex:', + 'min:', + 'max:', + 'between:', + 'between:1024,65535', + 'in:', + 'in:true,false', + ]), ]), ]), Tab::make('Install Script') diff --git a/app/Filament/Resources/EggResource/Pages/EditEgg.php b/app/Filament/Resources/EggResource/Pages/EditEgg.php index acd76e479..ee7bf5bd6 100644 --- a/app/Filament/Resources/EggResource/Pages/EditEgg.php +++ b/app/Filament/Resources/EggResource/Pages/EditEgg.php @@ -144,7 +144,7 @@ class EditEgg extends EditRecord ->mutateRelationshipDataBeforeCreateUsing(function (array $data): array { $data['default_value'] ??= ''; $data['description'] ??= ''; - $data['rules'] ??= ''; + $data['rules'] ??= []; $data['user_viewable'] ??= ''; $data['user_editable'] ??= ''; @@ -153,7 +153,7 @@ class EditEgg extends EditRecord ->mutateRelationshipDataBeforeSaveUsing(function (array $data): array { $data['default_value'] ??= ''; $data['description'] ??= ''; - $data['rules'] ??= ''; + $data['rules'] ??= []; $data['user_viewable'] ??= ''; $data['user_editable'] ??= ''; @@ -183,7 +183,30 @@ class EditEgg extends EditRecord Checkbox::make('user_viewable')->label('Viewable'), Checkbox::make('user_editable')->label('Editable'), ]), - TextInput::make('rules')->columnSpanFull(), + TagsInput::make('rules') + ->columnSpanFull() + ->placeholder('Add Rule') + ->reorderable() + ->suggestions([ + 'required', + 'nullable', + 'string', + 'integer', + 'numeric', + 'boolean', + 'alpha', + 'alpha_dash', + 'alpha_num', + 'url', + 'email', + 'regex:', + 'min:', + 'max:', + 'between:', + 'between:1024,65535', + 'in:', + 'in:true,false', + ]), ]), ]), Tab::make('Install Script') diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index 343c8fa0c..9f2de64b9 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -6,6 +6,7 @@ use App\Filament\Resources\ServerResource; use App\Models\Allocation; use App\Models\Egg; use App\Models\Node; +use App\Models\ServerVariable; use App\Models\User; use App\Services\Allocations\AssignmentService; use App\Services\Servers\RandomWordService; @@ -443,8 +444,7 @@ class CreateServer extends CreateRecord $text = Forms\Components\TextInput::make('variable_value') ->hidden($this->shouldHideComponent(...)) - ->maxLength(255) - ->required(fn (Forms\Get $get) => in_array('required', explode('|', $get('rules')))) + ->required(fn (ServerVariable $serverVariable) => $serverVariable->variable->getRequiredAttribute()) ->rules( fn (Forms\Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) { $validator = Validator::make(['validatorkey' => $value], [ @@ -806,11 +806,9 @@ class CreateServer extends CreateRecord return $service->handle($data); } - private function shouldHideComponent(Forms\Get $get, Forms\Components\Component $component): bool + private function shouldHideComponent(ServerVariable $serverVariable, Forms\Components\Component $component): bool { - $containsRuleIn = str($get('rules'))->explode('|')->reduce( - fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true - ); + $containsRuleIn = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:'), false); if ($component instanceof Forms\Components\Select) { return $containsRuleIn; @@ -823,11 +821,9 @@ class CreateServer extends CreateRecord throw new \Exception('Component type not supported: ' . $component::class); } - private function getSelectOptionsFromRules(Forms\Get $get): array + private function getSelectOptionsFromRules(ServerVariable $serverVariable): array { - $inRule = str($get('rules'))->explode('|')->reduce( - fn ($result, $value) => str($value)->startsWith('in:') ? $value : $result, '' - ); + $inRule = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:')); return str($inRule) ->after('in:') diff --git a/app/Filament/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Resources/ServerResource/Pages/EditServer.php index c8726fde5..74cfe7018 100644 --- a/app/Filament/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Resources/ServerResource/Pages/EditServer.php @@ -489,7 +489,7 @@ class EditServer extends EditRecord $text = Forms\Components\TextInput::make('variable_value') ->hidden($this->shouldHideComponent(...)) - ->required(fn (ServerVariable $serverVariable) => in_array('required', explode('|', $serverVariable->variable->rules))) + ->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], [ @@ -516,7 +516,7 @@ class EditServer extends EditRecord ->live(onBlur: true) ->hintIcon('tabler-code') ->label(fn (ServerVariable $serverVariable) => $serverVariable->variable->name) - ->hintIconTooltip(fn (ServerVariable $serverVariable) => $serverVariable->variable->rules) + ->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); } @@ -757,28 +757,24 @@ class EditServer extends EditRecord ]; } - private function shouldHideComponent(Forms\Get $get, Forms\Components\Component $component): bool + private function shouldHideComponent(ServerVariable $serverVariable, Forms\Components\Component $component): bool { - $containsRuleIn = str($get('rules'))->explode('|')->reduce( - fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true - ); + $containsRuleIn = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:'), false); if ($component instanceof Forms\Components\Select) { - return $containsRuleIn; + return !$containsRuleIn; } if ($component instanceof Forms\Components\TextInput) { - return !$containsRuleIn; + return $containsRuleIn; } throw new \Exception('Component type not supported: ' . $component::class); } - private function getSelectOptionsFromRules(Forms\Get $get): array + private function getSelectOptionsFromRules(ServerVariable $serverVariable): array { - $inRule = str($get('rules'))->explode('|')->reduce( - fn ($result, $value) => str($value)->startsWith('in:') ? $value : $result, '' - ); + $inRule = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:')); return str($inRule) ->after('in:') diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index c589d9534..2d3a4fb2c 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * @property string $default_value * @property bool $user_viewable * @property bool $user_editable - * @property string $rules + * @property array $rules * @property \Carbon\CarbonImmutable $created_at * @property \Carbon\CarbonImmutable $updated_at * @property bool $required @@ -58,12 +58,14 @@ class EggVariable extends Model 'default_value' => 'string', 'user_viewable' => 'boolean', 'user_editable' => 'boolean', - 'rules' => 'string', + 'rules' => 'array', + 'rules.*' => 'string', ]; protected $attributes = [ 'user_editable' => 0, 'user_viewable' => 0, + 'rules' => '[]', ]; protected function casts(): array @@ -72,6 +74,7 @@ class EggVariable extends Model 'egg_id' => 'integer', 'user_viewable' => 'bool', 'user_editable' => 'bool', + 'rules' => 'array', 'created_at' => 'immutable_datetime', 'updated_at' => 'immutable_datetime', ]; @@ -79,7 +82,7 @@ class EggVariable extends Model public function getRequiredAttribute(): bool { - return in_array('required', explode('|', $this->rules)); + return in_array('required', $this->rules); } public function egg(): HasOne diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 1e85923d7..dc54525a6 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -54,6 +54,8 @@ class EggImporterService // Update existing variables or create new ones. foreach ($parsed['variables'] ?? [] as $variable) { EggVariable::unguarded(function () use ($egg, $variable) { + $variable['rules'] = is_array($variable['rules']) ? $variable['rules'] : explode('|', $variable['rules']); + $egg->variables()->updateOrCreate([ 'env_variable' => $variable['env_variable'], ], Collection::make($variable)->except(['egg_id', 'env_variable'])->toArray()); diff --git a/app/Services/Eggs/Variables/VariableCreationService.php b/app/Services/Eggs/Variables/VariableCreationService.php index d2213f5be..c6c726986 100644 --- a/app/Services/Eggs/Variables/VariableCreationService.php +++ b/app/Services/Eggs/Variables/VariableCreationService.php @@ -40,7 +40,7 @@ class VariableCreationService throw new ReservedVariableNameException(sprintf('Cannot use the protected name %s for this environment variable.', array_get($data, 'env_variable'))); } - if (!empty($data['rules'] ?? '')) { + if (!empty($data['rules'] ?? [])) { $this->validateRules($data['rules']); } @@ -55,7 +55,7 @@ class VariableCreationService 'default_value' => $data['default_value'] ?? '', 'user_viewable' => in_array('user_viewable', $options), 'user_editable' => in_array('user_editable', $options), - 'rules' => $data['rules'] ?? '', + 'rules' => $data['rules'] ?? [], ]); return $eggVariable; diff --git a/app/Services/Eggs/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php index 515cbc271..e56ae8ea7 100644 --- a/app/Services/Eggs/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -2,7 +2,6 @@ namespace App\Services\Eggs\Variables; -use Illuminate\Support\Str; use App\Models\EggVariable; use App\Exceptions\DisplayException; use App\Traits\Services\ValidatesValidationRules; @@ -54,12 +53,8 @@ class VariableUpdateService } } - if (!empty($data['rules'] ?? '')) { - $this->validateRules( - (is_string($data['rules']) && Str::contains($data['rules'], ';;')) - ? explode(';;', $data['rules']) - : $data['rules'] - ); + if (!empty($data['rules'] ?? [])) { + $this->validateRules($data['rules']); } $options = array_get($data, 'options') ?? []; @@ -71,7 +66,7 @@ class VariableUpdateService 'default_value' => $data['default_value'] ?? '', 'user_viewable' => in_array('user_viewable', $options), 'user_editable' => in_array('user_editable', $options), - 'rules' => $data['rules'] ?? '', + 'rules' => $data['rules'] ?? [], ]); } } diff --git a/app/Transformers/Api/Client/EggVariableTransformer.php b/app/Transformers/Api/Client/EggVariableTransformer.php index bdc2fad15..141f43b61 100644 --- a/app/Transformers/Api/Client/EggVariableTransformer.php +++ b/app/Transformers/Api/Client/EggVariableTransformer.php @@ -27,7 +27,7 @@ class EggVariableTransformer extends BaseClientTransformer 'default_value' => $variable->default_value, 'server_value' => $variable->server_value, 'is_editable' => $variable->user_editable, - 'rules' => $variable->rules, + 'rules' => implode('|', $variable->rules), ]; } } diff --git a/database/Seeders/eggs/minecraft/egg-bungeecord.json b/database/Seeders/eggs/minecraft/egg-bungeecord.json index f9b389560..49bc8be50 100644 --- a/database/Seeders/eggs/minecraft/egg-bungeecord.json +++ b/database/Seeders/eggs/minecraft/egg-bungeecord.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-bungeecord.json" }, - "exported_at": "2024-07-03T14:33:51+00:00", + "exported_at": "2024-07-25T12:03:12+00:00", "name": "Bungeecord", "author": "panel@example.com", "uuid": "9e6b409e-4028-4947-aea8-50a2c404c271", @@ -44,7 +44,11 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|alpha_num|between:1,6", + "rules": [ + "required", + "alpha_num", + "between:1,6" + ], "sort": null, "field_type": "text" }, @@ -55,7 +59,10 @@ "default_value": "bungeecord.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "rules": [ + "required", + "regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + ], "sort": null, "field_type": "text" } diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index 693e299db..13afe9748 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-forge-minecraft.json" }, - "exported_at": "2024-07-03T14:33:51+00:00", + "exported_at": "2024-07-25T12:03:19+00:00", "name": "Forge Minecraft", "author": "panel@example.com", "uuid": "ed072427-f209-4603-875c-f540c6dd5a65", @@ -44,7 +44,10 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "rules": [ + "required", + "regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + ], "sort": 1, "field_type": "text" }, @@ -55,7 +58,11 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:9", + "rules": [ + "required", + "string", + "max:9" + ], "sort": 2, "field_type": "text" }, @@ -66,7 +73,11 @@ "default_value": "recommended", "user_viewable": true, "user_editable": true, - "rules": "required|string|in:recommended,latest", + "rules": [ + "required", + "string", + "in:recommended,latest" + ], "sort": 3, "field_type": "text" }, @@ -77,7 +88,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|regex:\/^[0-9\\.\\-]+$\/", + "rules": [ + "nullable", + "regex:\/^[0-9\\.\\-]+$\/" + ], "sort": 4, "field_type": "text" } diff --git a/database/Seeders/eggs/minecraft/egg-paper.json b/database/Seeders/eggs/minecraft/egg-paper.json index 39463b0e9..d8a73b9f4 100644 --- a/database/Seeders/eggs/minecraft/egg-paper.json +++ b/database/Seeders/eggs/minecraft/egg-paper.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-paper.json" }, - "exported_at": "2024-07-03T14:33:52+00:00", + "exported_at": "2024-07-25T12:03:43+00:00", "name": "Paper", "author": "parker@example.com", "uuid": "5da37ef6-58da-4169-90a6-e683e1721247", @@ -44,7 +44,11 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|max:20", + "rules": [ + "nullable", + "string", + "max:20" + ], "sort": 1, "field_type": "text" }, @@ -55,7 +59,10 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "rules": [ + "required", + "regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + ], "sort": 2, "field_type": "text" }, @@ -66,7 +73,10 @@ "default_value": "", "user_viewable": false, "user_editable": false, - "rules": "nullable|string", + "rules": [ + "nullable", + "string" + ], "sort": 3, "field_type": "text" }, @@ -77,7 +87,11 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20", + "rules": [ + "required", + "string", + "max:20" + ], "sort": 4, "field_type": "text" } diff --git a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json index 278f58a6c..1a72aefe7 100644 --- a/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json +++ b/database/Seeders/eggs/minecraft/egg-sponge--sponge-vanilla.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-sponge--sponge-vanilla.json" }, - "exported_at": "2024-07-03T14:34:02+00:00", + "exported_at": "2024-07-25T12:03:55+00:00", "name": "Sponge (SpongeVanilla)", "author": "panel@example.com", "uuid": "f0d2f88f-1ff3-42a0-b03f-ac44c5571e6d", @@ -44,7 +44,10 @@ "default_value": "1.12.2-7.3.0", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([a-zA-Z0-9.\\-_]+)$\/", + "rules": [ + "required", + "regex:\/^([a-zA-Z0-9.\\-_]+)$\/" + ], "sort": 1, "field_type": "text" }, @@ -55,7 +58,10 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "rules": [ + "required", + "regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + ], "sort": 2, "field_type": "text" } diff --git a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json index 94f257863..71d71938e 100644 --- a/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-vanilla-minecraft.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/minecraft\/egg-vanilla-minecraft.json" }, - "exported_at": "2024-07-03T14:34:02+00:00", + "exported_at": "2024-07-25T12:04:05+00:00", "name": "Vanilla Minecraft", "author": "panel@example.com", "uuid": "9ac39f3d-0c34-4d93-8174-c52ab9e6c57b", @@ -44,7 +44,10 @@ "default_value": "server.jar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^([\\w\\d._-]+)(\\.jar)$\/", + "rules": [ + "required", + "regex:\/^([\\w\\d._-]+)(\\.jar)$\/" + ], "sort": 1, "field_type": "text" }, @@ -55,7 +58,11 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|between:3,15", + "rules": [ + "required", + "string", + "between:3,15" + ], "sort": 2, "field_type": "text" } diff --git a/database/Seeders/eggs/rust/egg-rust.json b/database/Seeders/eggs/rust/egg-rust.json index 5b763bd7c..e74368604 100644 --- a/database/Seeders/eggs/rust/egg-rust.json +++ b/database/Seeders/eggs/rust/egg-rust.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/rust\/egg-rust.json" }, - "exported_at": "2024-07-03T14:34:09+00:00", + "exported_at": "2024-07-25T12:06:17+00:00", "name": "Rust", "author": "panel@example.com", "uuid": "bace2dfb-209c-452a-9459-7d6f340b07ae", @@ -38,7 +38,11 @@ "default_value": "A Rust Server", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:60", + "rules": [ + "required", + "string", + "max:60" + ], "sort": 1, "field_type": "text" }, @@ -49,7 +53,10 @@ "default_value": "vanilla", "user_viewable": true, "user_editable": true, - "rules": "required|in:vanilla,oxide,carbon", + "rules": [ + "required", + "in:vanilla,oxide,carbon" + ], "sort": 2, "field_type": "text" }, @@ -60,7 +67,11 @@ "default_value": "Procedural Map", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20", + "rules": [ + "required", + "string", + "max:20" + ], "sort": 3, "field_type": "text" }, @@ -71,7 +82,10 @@ "default_value": "Powered by Panel", "user_viewable": true, "user_editable": true, - "rules": "required|string", + "rules": [ + "required", + "string" + ], "sort": 4, "field_type": "text" }, @@ -82,7 +96,10 @@ "default_value": "http:\/\/example.com", "user_viewable": true, "user_editable": true, - "rules": "nullable|url", + "rules": [ + "nullable", + "url" + ], "sort": 5, "field_type": "text" }, @@ -93,7 +110,10 @@ "default_value": "3000", "user_viewable": true, "user_editable": true, - "rules": "required|integer", + "rules": [ + "required", + "integer" + ], "sort": 6, "field_type": "text" }, @@ -104,7 +124,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string", + "rules": [ + "nullable", + "string" + ], "sort": 7, "field_type": "text" }, @@ -115,7 +138,10 @@ "default_value": "40", "user_viewable": true, "user_editable": true, - "rules": "required|integer", + "rules": [ + "required", + "integer" + ], "sort": 8, "field_type": "text" }, @@ -126,7 +152,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|url", + "rules": [ + "nullable", + "url" + ], "sort": 9, "field_type": "text" }, @@ -137,7 +166,10 @@ "default_value": "27017", "user_viewable": true, "user_editable": false, - "rules": "required|integer", + "rules": [ + "required", + "integer" + ], "sort": 10, "field_type": "text" }, @@ -148,7 +180,10 @@ "default_value": "28016", "user_viewable": true, "user_editable": false, - "rules": "required|integer", + "rules": [ + "required", + "integer" + ], "sort": 11, "field_type": "text" }, @@ -159,7 +194,11 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^[\\w.-]*$\/|max:64", + "rules": [ + "required", + "regex:\/^[\\w.-]*$\/", + "max:64" + ], "sort": 12, "field_type": "text" }, @@ -170,7 +209,10 @@ "default_value": "60", "user_viewable": true, "user_editable": true, - "rules": "required|integer", + "rules": [ + "required", + "integer" + ], "sort": 13, "field_type": "text" }, @@ -181,7 +223,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string", + "rules": [ + "nullable", + "string" + ], "sort": 14, "field_type": "text" }, @@ -192,7 +237,10 @@ "default_value": "28082", "user_viewable": true, "user_editable": false, - "rules": "required|integer", + "rules": [ + "required", + "integer" + ], "sort": 15, "field_type": "text" }, @@ -203,7 +251,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|url", + "rules": [ + "nullable", + "url" + ], "sort": 16, "field_type": "text" }, @@ -214,7 +265,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|url", + "rules": [ + "nullable", + "url" + ], "sort": 17, "field_type": "text" }, @@ -225,7 +279,11 @@ "default_value": "258550", "user_viewable": false, "user_editable": false, - "rules": "required|string|in:258550", + "rules": [ + "required", + "string", + "in:258550" + ], "sort": 18, "field_type": "text" } diff --git a/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json b/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json index 6f450834c..1c092bd21 100644 --- a/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json +++ b/database/Seeders/eggs/source-engine/egg-counter--strike--global-offensive.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-counter--strike--global-offensive.json" }, - "exported_at": "2024-07-03T14:34:03+00:00", + "exported_at": "2024-07-25T12:04:25+00:00", "name": "Counter-Strike: Global Offensive", "author": "panel@example.com", "uuid": "437c367d-06be-498f-a604-fdad135504d7", @@ -39,7 +39,11 @@ "default_value": "de_dust2", "user_viewable": true, "user_editable": true, - "rules": "required|string|alpha_dash", + "rules": [ + "required", + "string", + "alpha_dash" + ], "sort": 1, "field_type": "text" }, @@ -50,7 +54,12 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "required|string|alpha_num|size:32", + "rules": [ + "required", + "string", + "alpha_num", + "size:32" + ], "sort": 2, "field_type": "text" }, @@ -61,7 +70,11 @@ "default_value": "740", "user_viewable": false, "user_editable": false, - "rules": "required|string|max:20", + "rules": [ + "required", + "string", + "max:20" + ], "sort": 3, "field_type": "text" } diff --git a/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json b/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json index f2e8bc088..1126e629f 100644 --- a/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json +++ b/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-custom-source-engine-game.json" }, - "exported_at": "2024-07-03T14:34:04+00:00", + "exported_at": "2024-07-25T12:03:27+00:00", "name": "Custom Source Engine Game", "author": "panel@example.com", "uuid": "2a42d0c2-c0ba-4067-9a0a-9b95d77a3490", @@ -38,7 +38,11 @@ "default_value": "", "user_viewable": true, "user_editable": false, - "rules": "required|numeric|digits_between:1,6", + "rules": [ + "required", + "numeric", + "digits_between:1,6" + ], "sort": 1, "field_type": "text" }, @@ -49,7 +53,11 @@ "default_value": "", "user_viewable": true, "user_editable": false, - "rules": "required|alpha_dash|between:1,100", + "rules": [ + "required", + "alpha_dash", + "between:1,100" + ], "sort": 2, "field_type": "text" }, @@ -60,7 +68,11 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "required|string|alpha_dash", + "rules": [ + "required", + "string", + "alpha_dash" + ], "sort": 3, "field_type": "text" }, @@ -71,7 +83,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string", + "rules": [ + "nullable", + "string" + ], "sort": 4, "field_type": "text" }, @@ -82,7 +97,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string", + "rules": [ + "nullable", + "string" + ], "sort": 5, "field_type": "text" }, @@ -93,7 +111,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string", + "rules": [ + "nullable", + "string" + ], "sort": 6, "field_type": "text" } diff --git a/database/Seeders/eggs/source-engine/egg-garrys-mod.json b/database/Seeders/eggs/source-engine/egg-garrys-mod.json index 60a6c4298..21a289cd5 100644 --- a/database/Seeders/eggs/source-engine/egg-garrys-mod.json +++ b/database/Seeders/eggs/source-engine/egg-garrys-mod.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-garrys-mod.json" }, - "exported_at": "2024-07-03T14:34:04+00:00", + "exported_at": "2024-07-25T12:05:02+00:00", "name": "Garrys Mod", "author": "panel@example.com", "uuid": "60ef81d4-30a2-4d98-ab64-f59c69e2f915", @@ -39,7 +39,11 @@ "default_value": "gm_flatgrass", "user_viewable": true, "user_editable": true, - "rules": "required|string|alpha_dash", + "rules": [ + "required", + "string", + "alpha_dash" + ], "sort": 1, "field_type": "text" }, @@ -50,7 +54,12 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|string|alpha_num|size:32", + "rules": [ + "nullable", + "string", + "alpha_num", + "size:32" + ], "sort": 2, "field_type": "text" }, @@ -61,7 +70,11 @@ "default_value": "4020", "user_viewable": false, "user_editable": false, - "rules": "required|string|max:20", + "rules": [ + "required", + "string", + "max:20" + ], "sort": 3, "field_type": "text" }, @@ -72,7 +85,10 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "nullable|integer", + "rules": [ + "nullable", + "integer" + ], "sort": 4, "field_type": "text" }, @@ -83,7 +99,10 @@ "default_value": "sandbox", "user_viewable": true, "user_editable": true, - "rules": "required|string", + "rules": [ + "required", + "string" + ], "sort": 5, "field_type": "text" }, @@ -94,7 +113,11 @@ "default_value": "32", "user_viewable": true, "user_editable": true, - "rules": "required|integer|max:128", + "rules": [ + "required", + "integer", + "max:128" + ], "sort": 6, "field_type": "text" }, @@ -105,7 +128,11 @@ "default_value": "22", "user_viewable": true, "user_editable": true, - "rules": "required|integer|max:100", + "rules": [ + "required", + "integer", + "max:100" + ], "sort": 7, "field_type": "text" }, @@ -116,7 +143,10 @@ "default_value": "0", "user_viewable": true, "user_editable": true, - "rules": "required|boolean", + "rules": [ + "required", + "boolean" + ], "sort": 8, "field_type": "text" } diff --git a/database/Seeders/eggs/source-engine/egg-insurgency.json b/database/Seeders/eggs/source-engine/egg-insurgency.json index b246dabbe..1c7e1525f 100644 --- a/database/Seeders/eggs/source-engine/egg-insurgency.json +++ b/database/Seeders/eggs/source-engine/egg-insurgency.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-insurgency.json" }, - "exported_at": "2024-07-03T14:34:05+00:00", + "exported_at": "2024-07-25T12:05:30+00:00", "name": "Insurgency", "author": "panel@example.com", "uuid": "a5702286-655b-4069-bf1e-925c7300b61a", @@ -38,7 +38,10 @@ "default_value": "237410", "user_viewable": true, "user_editable": false, - "rules": "required|regex:\/^(237410)$\/", + "rules": [ + "required", + "regex:\/^(237410)$\/" + ], "sort": 1, "field_type": "text" }, @@ -49,7 +52,10 @@ "default_value": "sinjar", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^(\\w{1,20})$\/", + "rules": [ + "required", + "regex:\/^(\\w{1,20})$\/" + ], "sort": 2, "field_type": "text" } diff --git a/database/Seeders/eggs/source-engine/egg-team-fortress2.json b/database/Seeders/eggs/source-engine/egg-team-fortress2.json index 24795824c..667d04555 100644 --- a/database/Seeders/eggs/source-engine/egg-team-fortress2.json +++ b/database/Seeders/eggs/source-engine/egg-team-fortress2.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/source-engine\/egg-team-fortress2.json" }, - "exported_at": "2024-07-03T14:34:06+00:00", + "exported_at": "2024-07-25T12:05:42+00:00", "name": "Team Fortress 2", "author": "panel@example.com", "uuid": "7f8eb681-b2c8-4bf8-b9f4-d79ff70b6e5d", @@ -39,7 +39,10 @@ "default_value": "232250", "user_viewable": true, "user_editable": false, - "rules": "required|in:232250", + "rules": [ + "required", + "in:232250" + ], "sort": 1, "field_type": "text" }, @@ -50,7 +53,10 @@ "default_value": "cp_dustbowl", "user_viewable": true, "user_editable": true, - "rules": "required|regex:\/^(\\w{1,20})$\/", + "rules": [ + "required", + "regex:\/^(\\w{1,20})$\/" + ], "sort": 2, "field_type": "text" }, @@ -61,7 +67,12 @@ "default_value": "", "user_viewable": true, "user_editable": true, - "rules": "required|string|alpha_num|size:32", + "rules": [ + "required", + "string", + "alpha_num", + "size:32" + ], "sort": 3, "field_type": "text" } diff --git a/database/Seeders/eggs/voice-servers/egg-mumble-server.json b/database/Seeders/eggs/voice-servers/egg-mumble-server.json index 6254b856a..a4c02919f 100644 --- a/database/Seeders/eggs/voice-servers/egg-mumble-server.json +++ b/database/Seeders/eggs/voice-servers/egg-mumble-server.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/voice-servers\/egg-mumble-server.json" }, - "exported_at": "2024-07-03T14:34:07+00:00", + "exported_at": "2024-07-25T12:05:52+00:00", "name": "Mumble Server", "author": "panel@example.com", "uuid": "727ee758-7fb2-4979-972b-d3eba4e1e9f0", @@ -36,7 +36,11 @@ "default_value": "100", "user_viewable": true, "user_editable": false, - "rules": "required|numeric|digits_between:1,5", + "rules": [ + "required", + "numeric", + "digits_between:1,5" + ], "sort": 1, "field_type": "text" } diff --git a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json index 949d6a03d..0543a16c0 100644 --- a/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json +++ b/database/Seeders/eggs/voice-servers/egg-teamspeak3-server.json @@ -4,7 +4,7 @@ "version": "PTDL_v2", "update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/voice-servers\/egg-teamspeak3-server.json" }, - "exported_at": "2024-07-03T14:34:08+00:00", + "exported_at": "2024-07-25T12:06:05+00:00", "name": "Teamspeak3 Server", "author": "panel@example.com", "uuid": "983b1fac-d322-4d5f-a636-436127326b37", @@ -36,7 +36,11 @@ "default_value": "latest", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:6", + "rules": [ + "required", + "string", + "max:6" + ], "sort": 1, "field_type": "text" }, @@ -47,7 +51,11 @@ "default_value": "30033", "user_viewable": true, "user_editable": false, - "rules": "required|integer|between:1025,65535", + "rules": [ + "required", + "integer", + "between:1025,65535" + ], "sort": 2, "field_type": "text" }, @@ -58,7 +66,11 @@ "default_value": "10011", "user_viewable": true, "user_editable": false, - "rules": "required|integer|between:1025,65535", + "rules": [ + "required", + "integer", + "between:1025,65535" + ], "sort": 3, "field_type": "text" }, @@ -69,7 +81,11 @@ "default_value": "raw,http,ssh", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:12", + "rules": [ + "required", + "string", + "max:12" + ], "sort": 4, "field_type": "text" }, @@ -80,7 +96,11 @@ "default_value": "10022", "user_viewable": true, "user_editable": false, - "rules": "required|integer|between:1025,65535", + "rules": [ + "required", + "integer", + "between:1025,65535" + ], "sort": 5, "field_type": "text" }, @@ -91,7 +111,11 @@ "default_value": "10080", "user_viewable": true, "user_editable": false, - "rules": "required|integer|between:1025,65535", + "rules": [ + "required", + "integer", + "between:1025,65535" + ], "sort": 6, "field_type": "text" } diff --git a/database/migrations/2024_07_25_072050_convert_rules_to_array.php b/database/migrations/2024_07_25_072050_convert_rules_to_array.php new file mode 100644 index 000000000..8e6473a95 --- /dev/null +++ b/database/migrations/2024_07_25_072050_convert_rules_to_array.php @@ -0,0 +1,37 @@ +json('rules')->change(); + }); + + DB::table('egg_variables')->select(['id', 'rules'])->cursor()->each(function ($eggVariable) { + DB::table('egg_variables')->where('id', $eggVariable->id)->update(['rules' => explode('|', $eggVariable->rules)]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('api_keys', function (Blueprint $table) { + $table->text('rules')->change(); + }); + + DB::table('egg_variables')->select(['id', 'rules'])->cursor()->each(function ($eggVariable) { + DB::table('egg_variables')->where('id', $eggVariable->id)->update(['rules' => implode('|', json_decode($eggVariable->rules))]); + }); + } +}; diff --git a/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php b/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php index 02d972fe4..e9c8af97c 100644 --- a/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php +++ b/tests/Integration/Api/Client/Server/Startup/UpdateStartupVariableTest.php @@ -120,7 +120,7 @@ class UpdateStartupVariableTest extends ClientApiIntegrationTestCase [$user, $server] = $this->generateTestAccount(); $egg = $this->cloneEggAndVariables($server->egg); - $egg->variables()->first()->update(['rules' => 'nullable|string']); + $egg->variables()->first()->update(['rules' => ['nullable', 'string']]); $server->fill(['egg_id' => $egg->id])->save(); $server->refresh(); diff --git a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php index 113e29762..6f49b97fb 100644 --- a/tests/Integration/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Integration/Services/Servers/VariableValidatorServiceTest.php @@ -120,7 +120,7 @@ class VariableValidatorServiceTest extends IntegrationTestCase $egg = $this->cloneEggAndVariables($this->egg); $egg->variables()->where('env_variable', '!=', 'BUNGEE_VERSION')->delete(); - $egg->variables()->update(['rules' => 'nullable|string']); + $egg->variables()->update(['rules' => ['nullable', 'string']]); $response = $this->getService()->handle($egg->id, []); $this->assertCount(1, $response); From 20b06b7b39d4369972d5d3880a3e6fcd204b6466 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 20 Aug 2024 22:45:41 +0200 Subject: [PATCH 55/65] Fix variables on CreateServer page (#558) --- .../ServerResource/Pages/CreateServer.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index 9f2de64b9..9c2c32da9 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -6,7 +6,6 @@ use App\Filament\Resources\ServerResource; use App\Models\Allocation; use App\Models\Egg; use App\Models\Node; -use App\Models\ServerVariable; use App\Models\User; use App\Services\Allocations\AssignmentService; use App\Services\Servers\RandomWordService; @@ -444,7 +443,7 @@ class CreateServer extends CreateRecord $text = Forms\Components\TextInput::make('variable_value') ->hidden($this->shouldHideComponent(...)) - ->required(fn (ServerVariable $serverVariable) => $serverVariable->variable->getRequiredAttribute()) + ->required(fn (Forms\Get $get) => in_array('required', $get('rules'))) ->rules( fn (Forms\Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get) { $validator = Validator::make(['validatorkey' => $value], [ @@ -471,7 +470,7 @@ class CreateServer extends CreateRecord ->live(onBlur: true) ->hintIcon('tabler-code') ->label(fn (Forms\Get $get) => $get('name')) - ->hintIconTooltip(fn (Forms\Get $get) => $get('rules')) + ->hintIconTooltip(fn (Forms\Get $get) => implode('|', $get('rules'))) ->prefix(fn (Forms\Get $get) => '{{' . $get('env_variable') . '}}') ->helperText(fn (Forms\Get $get) => empty($get('description')) ? '—' : $get('description')) ->afterStateUpdated(function (Forms\Set $set, Forms\Get $get, $state) { @@ -806,9 +805,11 @@ class CreateServer extends CreateRecord return $service->handle($data); } - private function shouldHideComponent(ServerVariable $serverVariable, Forms\Components\Component $component): bool + private function shouldHideComponent(Forms\Get $get, Forms\Components\Component $component): bool { - $containsRuleIn = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:'), false); + $containsRuleIn = collect($get('rules'))->reduce( + fn ($result, $value) => $result === true && !str($value)->startsWith('in:'), true + ); if ($component instanceof Forms\Components\Select) { return $containsRuleIn; @@ -821,9 +822,11 @@ class CreateServer extends CreateRecord throw new \Exception('Component type not supported: ' . $component::class); } - private function getSelectOptionsFromRules(ServerVariable $serverVariable): array + private function getSelectOptionsFromRules(Forms\Get $get): array { - $inRule = array_first($serverVariable->variable->rules, fn ($value) => str($value)->startsWith('in:')); + $inRule = collect($get('rules'))->reduce( + fn ($result, $value) => str($value)->startsWith('in:') ? $value : $result, '' + ); return str($inRule) ->after('in:') From 05477c711fe7041ad71273179dc1a2a64bafbfb3 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Thu, 22 Aug 2024 22:19:38 +0200 Subject: [PATCH 56/65] Create missing server variables on EditServer page (#560) * create missing server variables on editserver page * remove count check --- .../ServerResource/Pages/EditServer.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/Filament/Resources/ServerResource/Pages/EditServer.php b/app/Filament/Resources/ServerResource/Pages/EditServer.php index 74cfe7018..31e682ab5 100644 --- a/app/Filament/Resources/ServerResource/Pages/EditServer.php +++ b/app/Filament/Resources/ServerResource/Pages/EditServer.php @@ -29,6 +29,7 @@ use Filament\Notifications\Notification; use Filament\Resources\Pages\EditRecord; use Illuminate\Support\Facades\Validator; use Closure; +use Illuminate\Database\Eloquent\Builder; use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; class EditServer extends EditRecord @@ -473,7 +474,21 @@ class EditServer extends EditRecord ->columnSpan(6), Forms\Components\Repeater::make('server_variables') - ->relationship('serverVariables') + ->relationship('serverVariables', function (Builder $query) { + /** @var Server $server */ + $server = $this->getRecord(); + + foreach ($server->variables as $variable) { + ServerVariable::query()->firstOrCreate([ + 'server_id' => $server->id, + 'variable_id' => $variable->id, + ], [ + 'variable_value' => $variable->server_value ?? '', + ]); + } + + return $query; + }) ->grid() ->mutateRelationshipDataBeforeSaveUsing(function (array &$data): array { foreach ($data as $key => $value) { From 818781ca66367e2bdb851ea4d7aa51468ffaaf8b Mon Sep 17 00:00:00 2001 From: Boy132 Date: Thu, 22 Aug 2024 22:19:56 +0200 Subject: [PATCH 57/65] Fix isViable for Nodes with "unlimited" resources (#559) --- app/Models/Node.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Models/Node.php b/app/Models/Node.php index 473d3a4a2..a322aa999 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -244,21 +244,21 @@ class Node extends Model */ public function isViable(int $memory, int $disk, int $cpu): bool { - if ($this->memory_overallocate >= 0) { + if ($this->memory > 0 && $this->memory_overallocate >= 0) { $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100)); if ($this->servers_sum_memory + $memory > $memoryLimit) { return false; } } - if ($this->disk_overallocate >= 0) { + if ($this->disk > 0 && $this->disk_overallocate >= 0) { $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100)); if ($this->servers_sum_disk + $disk > $diskLimit) { return false; } } - if ($this->cpu_overallocate >= 0) { + if ($this->cpu > 0 && $this->cpu_overallocate >= 0) { $cpuLimit = $this->cpu * (1 + ($this->cpu_overallocate / 100)); if ($this->servers_sum_cpu + $cpu > $cpuLimit) { return false; From 40810877e0f021b683baa29a0255634b3b2a2a46 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Thu, 22 Aug 2024 22:20:11 +0200 Subject: [PATCH 58/65] Add redis connection check to installer (#556) --- .../Pages/Installer/PanelInstaller.php | 3 +++ .../Pages/Installer/Steps/DatabaseStep.php | 9 +++---- .../Pages/Installer/Steps/RedisStep.php | 27 ++++++++++++++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/app/Filament/Pages/Installer/PanelInstaller.php b/app/Filament/Pages/Installer/PanelInstaller.php index b6aea2b09..160439086 100644 --- a/app/Filament/Pages/Installer/PanelInstaller.php +++ b/app/Filament/Pages/Installer/PanelInstaller.php @@ -103,6 +103,9 @@ class PanelInstaller extends SimplePage implements HasForms $variables = array_get($inputs, 'env'); $this->writeToEnvironment($variables); + // Clear config cache + Artisan::call('config:clear'); + // Run migrations Artisan::call('migrate', [ '--force' => true, diff --git a/app/Filament/Pages/Installer/Steps/DatabaseStep.php b/app/Filament/Pages/Installer/Steps/DatabaseStep.php index 94a7952e2..db9b36cea 100644 --- a/app/Filament/Pages/Installer/Steps/DatabaseStep.php +++ b/app/Filament/Pages/Installer/Steps/DatabaseStep.php @@ -7,7 +7,7 @@ use Filament\Forms\Components\Wizard\Step; use Filament\Forms\Get; use Filament\Notifications\Notification; use Filament\Support\Exceptions\Halt; -use Illuminate\Database\DatabaseManager; +use Illuminate\Support\Facades\DB; use PDOException; class DatabaseStep @@ -61,9 +61,6 @@ class DatabaseStep ->afterValidation(function (Get $get) { $driver = $get('env.DB_CONNECTION'); if ($driver !== 'sqlite') { - /** @var DatabaseManager $database */ - $database = app(DatabaseManager::class); - try { config()->set('database.connections._panel_install_test', [ 'driver' => $driver, @@ -77,7 +74,7 @@ class DatabaseStep 'strict' => true, ]); - $database->connection('_panel_install_test')->getPdo(); + DB::connection('_panel_install_test')->getPdo(); } catch (PDOException $exception) { Notification::make() ->title('Database connection failed') @@ -85,7 +82,7 @@ class DatabaseStep ->danger() ->send(); - $database->disconnect('_panel_install_test'); + DB::disconnect('_panel_install_test'); throw new Halt('Database connection failed'); } diff --git a/app/Filament/Pages/Installer/Steps/RedisStep.php b/app/Filament/Pages/Installer/Steps/RedisStep.php index 04a4b1b72..2ab176f40 100644 --- a/app/Filament/Pages/Installer/Steps/RedisStep.php +++ b/app/Filament/Pages/Installer/Steps/RedisStep.php @@ -2,8 +2,13 @@ namespace App\Filament\Pages\Installer\Steps; +use Exception; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Wizard\Step; +use Filament\Forms\Get; +use Filament\Notifications\Notification; +use Filament\Support\Exceptions\Halt; +use Illuminate\Support\Facades\Redis; class RedisStep { @@ -37,6 +42,26 @@ class RedisStep ->password() ->revealable() ->default(config('database.redis.default.password')), - ]); + ]) + ->afterValidation(function (Get $get) { + try { + config()->set('database.redis._panel_install_test', [ + 'host' => $get('env.REDIS_HOST'), + 'username' => $get('env.REDIS_USERNAME'), + 'password' => $get('env.REDIS_PASSWORD'), + 'port' => $get('env.REDIS_PORT'), + ]); + + Redis::connection('_panel_install_test')->command('ping'); + } catch (Exception $exception) { + Notification::make() + ->title('Redis connection failed') + ->body($exception->getMessage()) + ->danger() + ->send(); + + throw new Halt('Redis connection failed'); + } + }); } } From 58307c15a39a09d9b260bfa57606ac4a4f0a8843 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sat, 24 Aug 2024 19:16:33 -0400 Subject: [PATCH 59/65] App Name AlphaNum Closes https://github.com/pelican-dev/panel/issues/562 --- app/Filament/Pages/Settings.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php index ad9ea96ba..68574e0e9 100644 --- a/app/Filament/Pages/Settings.php +++ b/app/Filament/Pages/Settings.php @@ -86,6 +86,7 @@ class Settings extends Page implements HasForms TextInput::make('APP_NAME') ->label('App Name') ->required() + ->alphaNum() ->default(env('APP_NAME', 'Pelican')), TextInput::make('APP_FAVICON') ->label('App Favicon') From e152efc5f9b7947510b327829591feda500f722d Mon Sep 17 00:00:00 2001 From: notCharles Date: Sat, 24 Aug 2024 21:05:43 -0400 Subject: [PATCH 60/65] Add toggle for starting server after install --- .../ServerResource/Pages/CreateServer.php | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/app/Filament/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Resources/ServerResource/Pages/CreateServer.php index 9c2c32da9..6e4705345 100644 --- a/app/Filament/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Resources/ServerResource/Pages/CreateServer.php @@ -321,9 +321,9 @@ class CreateServer extends CreateRecord ->completedIcon('tabler-check') ->columns([ 'default' => 1, - 'sm' => 2, - 'md' => 2, - 'lg' => 4, + 'sm' => 4, + 'md' => 4, + 'lg' => 6, ]) ->schema([ Forms\Components\Select::make('egg_id') @@ -333,7 +333,7 @@ class CreateServer extends CreateRecord 'default' => 1, 'sm' => 2, 'md' => 2, - 'lg' => 3, + 'lg' => 4, ]) ->searchable() ->preload() @@ -390,29 +390,51 @@ class CreateServer extends CreateRecord ->inline() ->required(), + Forms\Components\ToggleButtons::make('start_on_completion') + ->label('Start Server After Install?') + ->default(true) + ->required() + ->columnSpan([ + 'default' => 1, + 'sm' => 1, + 'md' => 1, + 'lg' => 1, + ]) + ->options([ + true => 'Yes', + false => 'No', + ]) + ->colors([ + true => 'primary', + false => 'danger', + ]) + ->icons([ + true => 'tabler-code', + false => 'tabler-code-off', + ]) + ->inline(), + Forms\Components\Textarea::make('startup') ->hintIcon('tabler-code') ->label('Startup Command') ->hidden(fn (Forms\Get $get) => $get('egg_id') === null) ->required() ->live() - ->columnSpan([ - 'default' => 1, - 'sm' => 2, - 'md' => 2, - 'lg' => 4, - ]) ->rows(function ($state) { return str($state)->explode("\n")->reduce( fn (int $carry, $line) => $carry + floor(strlen($line) / 125), 1 ); - }), + }) + ->columnSpan([ + 'default' => 1, + 'sm' => 4, + 'md' => 4, + 'lg' => 6, + ]), Forms\Components\Hidden::make('environment')->default([]), - Forms\Components\Hidden::make('start_on_completion')->default(true), - Forms\Components\Section::make('Variables') ->icon('tabler-eggs') ->iconColor('primary') From d7b5966e1b780215e1ec3ec83ca3ee8ab43fbca8 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sun, 1 Sep 2024 17:42:15 +0200 Subject: [PATCH 61/65] Remove `required` from smtp username (#565) --- app/Filament/Pages/Settings.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Filament/Pages/Settings.php b/app/Filament/Pages/Settings.php index 68574e0e9..08f95c84a 100644 --- a/app/Filament/Pages/Settings.php +++ b/app/Filament/Pages/Settings.php @@ -275,7 +275,6 @@ class Settings extends Page implements HasForms ->default(env('MAIL_PORT', config('mail.mailers.smtp.port'))), TextInput::make('MAIL_USERNAME') ->label('Username') - ->required() ->default(env('MAIL_USERNAME', config('mail.mailers.smtp.username'))), TextInput::make('MAIL_PASSWORD') ->label('Password') From 49e93c13794bcd07151635623f16370a6b836dd8 Mon Sep 17 00:00:00 2001 From: notCharles Date: Fri, 6 Sep 2024 15:50:42 -0400 Subject: [PATCH 62/65] Fix Migration Fix rule migration reversal. --- .../migrations/2024_07_25_072050_convert_rules_to_array.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2024_07_25_072050_convert_rules_to_array.php b/database/migrations/2024_07_25_072050_convert_rules_to_array.php index 8e6473a95..54eb7b136 100644 --- a/database/migrations/2024_07_25_072050_convert_rules_to_array.php +++ b/database/migrations/2024_07_25_072050_convert_rules_to_array.php @@ -26,7 +26,7 @@ return new class extends Migration */ public function down(): void { - Schema::table('api_keys', function (Blueprint $table) { + Schema::table('egg_variables', function (Blueprint $table) { $table->text('rules')->change(); }); From 8c64a4ad55e970805e74dfce5748b5c814a10f31 Mon Sep 17 00:00:00 2001 From: notCharles Date: Sat, 7 Sep 2024 09:47:39 -0400 Subject: [PATCH 63/65] Make MySQL Happy MySQL complains when we try to change the rules column to json before we change the data... If we change the data, then change the column its happy. :) --- .../2024_07_25_072050_convert_rules_to_array.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/database/migrations/2024_07_25_072050_convert_rules_to_array.php b/database/migrations/2024_07_25_072050_convert_rules_to_array.php index 54eb7b136..6646a6661 100644 --- a/database/migrations/2024_07_25_072050_convert_rules_to_array.php +++ b/database/migrations/2024_07_25_072050_convert_rules_to_array.php @@ -12,13 +12,13 @@ return new class extends Migration */ public function up(): void { - Schema::table('egg_variables', function (Blueprint $table) { - $table->json('rules')->change(); - }); - DB::table('egg_variables')->select(['id', 'rules'])->cursor()->each(function ($eggVariable) { DB::table('egg_variables')->where('id', $eggVariable->id)->update(['rules' => explode('|', $eggVariable->rules)]); }); + + Schema::table('egg_variables', function (Blueprint $table) { + $table->json('rules')->change(); + }); } /** From 8497e8b009df8b71d183fa57619cc599a9b2d6b9 Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 7 Sep 2024 12:45:25 -0400 Subject: [PATCH 64/65] Update egg-bungeecord.json (#571) --- database/Seeders/eggs/minecraft/egg-bungeecord.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/Seeders/eggs/minecraft/egg-bungeecord.json b/database/Seeders/eggs/minecraft/egg-bungeecord.json index 49bc8be50..e71b8d1ec 100644 --- a/database/Seeders/eggs/minecraft/egg-bungeecord.json +++ b/database/Seeders/eggs/minecraft/egg-bungeecord.json @@ -49,7 +49,7 @@ "alpha_num", "between:1,6" ], - "sort": null, + "sort": 1, "field_type": "text" }, { @@ -63,8 +63,8 @@ "required", "regex:\/^([\\w\\d._-]+)(\\.jar)$\/" ], - "sort": null, + "sort": 2, "field_type": "text" } ] -} \ No newline at end of file +} From 68a0cbbf102ea4116cf5e0b3553da42ae9afb995 Mon Sep 17 00:00:00 2001 From: ash Date: Mon, 16 Sep 2024 10:16:25 -0400 Subject: [PATCH 65/65] Update placeholders & panel error command (#576) --- .github/ISSUE_TEMPLATE/bug-report.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index fd1b1e6f7..c9cb05955 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -33,7 +33,6 @@ body: attributes: label: Panel Version description: Version number of your Panel (latest is not a version) - placeholder: 1.4.0 validations: required: true @@ -42,7 +41,6 @@ body: attributes: label: Wings Version description: Version number of your Wings (latest is not a version) - placeholder: 1.4.2 validations: required: true @@ -68,7 +66,7 @@ body: 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 | nc pelipaste.com 99` + 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" render: bash validations: