From 23d13d9e83a2a51a6b2e065c02f261ec627c0052 Mon Sep 17 00:00:00 2001 From: MartinOscar <40749467+rmartinoscar@users.noreply.github.com> Date: Tue, 20 May 2025 17:58:16 +0200 Subject: [PATCH 1/4] Fix `Mount` translation (#1382) --- app/Filament/Admin/Resources/MountResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Admin/Resources/MountResource.php b/app/Filament/Admin/Resources/MountResource.php index ea1a8ebf3..24b1a4c5c 100644 --- a/app/Filament/Admin/Resources/MountResource.php +++ b/app/Filament/Admin/Resources/MountResource.php @@ -76,7 +76,7 @@ class MountResource extends Resource ->badge() ->icon(fn ($state) => $state ? 'tabler-writing-off' : 'tabler-writing') ->color(fn ($state) => $state ? 'success' : 'warning') - ->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writeable')), + ->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')), ]) ->actions([ ViewAction::make() From 51037c5c20c248f4247a51b6dee3631753636bdf Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Tue, 20 May 2025 15:32:43 -0500 Subject: [PATCH 2/4] Laravel 12.15.0 Shift (#1390) Co-authored-by: Shift --- composer.json | 2 +- composer.lock | 198 +++++++++++++++++++++++++------------------------- 2 files changed, 100 insertions(+), 100 deletions(-) diff --git a/composer.json b/composer.json index 1174df9e0..979749f75 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "doctrine/dbal": "~3.6.0", "filament/filament": "^3.3", "guzzlehttp/guzzle": "^7.9", - "laravel/framework": "^12.13", + "laravel/framework": "^12.15", "laravel/helpers": "^1.7", "laravel/sanctum": "^4.1", "laravel/socialite": "^5.20", diff --git a/composer.lock b/composer.lock index 0cbafa4bb..66133b8b4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a24a7b46deafee826d9c132b6554e97b", + "content-hash": "0779ca7c1d1cbeeb09f80ec120490078", "packages": [ { "name": "abdelhamiderrahmouni/filament-monaco-editor", @@ -1020,16 +1020,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.343.6", + "version": "3.343.14", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3746aca8cbed5f46beba850e0a480ef58e71b197" + "reference": "8bb5b542b28c4538b44de4335396e77bf9fbedf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3746aca8cbed5f46beba850e0a480ef58e71b197", - "reference": "3746aca8cbed5f46beba850e0a480ef58e71b197", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8bb5b542b28c4538b44de4335396e77bf9fbedf6", + "reference": "8bb5b542b28c4538b44de4335396e77bf9fbedf6", "shasum": "" }, "require": { @@ -1111,9 +1111,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.343.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.343.14" }, - "time": "2025-05-07T18:10:08+00:00" + "time": "2025-05-19T18:02:45+00:00" }, { "name": "aymanalhattami/filament-context-menu", @@ -2630,16 +2630,16 @@ }, { "name": "filament/actions", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/actions.git", - "reference": "08caa8dec43ebf4192dcd999cca786656a3dbc90" + "reference": "66b509aa72fa882ce91218eb743684a9350bc3fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/08caa8dec43ebf4192dcd999cca786656a3dbc90", - "reference": "08caa8dec43ebf4192dcd999cca786656a3dbc90", + "url": "https://api.github.com/repos/filamentphp/actions/zipball/66b509aa72fa882ce91218eb743684a9350bc3fb", + "reference": "66b509aa72fa882ce91218eb743684a9350bc3fb", "shasum": "" }, "require": { @@ -2679,20 +2679,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-04-30T09:16:43+00:00" + "time": "2025-05-19T07:25:24+00:00" }, { "name": "filament/filament", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/panels.git", - "reference": "2c4783bdd973967cc2dbc2dc518c70b04839ace3" + "reference": "bc83f6ca48340cc4127994687be121462fed9b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/2c4783bdd973967cc2dbc2dc518c70b04839ace3", - "reference": "2c4783bdd973967cc2dbc2dc518c70b04839ace3", + "url": "https://api.github.com/repos/filamentphp/panels/zipball/bc83f6ca48340cc4127994687be121462fed9b7a", + "reference": "bc83f6ca48340cc4127994687be121462fed9b7a", "shasum": "" }, "require": { @@ -2744,20 +2744,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-04-30T09:16:38+00:00" + "time": "2025-05-19T07:26:37+00:00" }, { "name": "filament/forms", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/forms.git", - "reference": "22e62dc2b4c68018e9846aadf7e8c5310d0e38cf" + "reference": "0e46d1d14e6f30a57dd85103d2b07aff52a75a5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/22e62dc2b4c68018e9846aadf7e8c5310d0e38cf", - "reference": "22e62dc2b4c68018e9846aadf7e8c5310d0e38cf", + "url": "https://api.github.com/repos/filamentphp/forms/zipball/0e46d1d14e6f30a57dd85103d2b07aff52a75a5a", + "reference": "0e46d1d14e6f30a57dd85103d2b07aff52a75a5a", "shasum": "" }, "require": { @@ -2800,11 +2800,11 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-04-30T09:16:39+00:00" + "time": "2025-05-19T07:26:45+00:00" }, { "name": "filament/infolists", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/infolists.git", @@ -2855,7 +2855,7 @@ }, { "name": "filament/notifications", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/notifications.git", @@ -2907,7 +2907,7 @@ }, { "name": "filament/support", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/support.git", @@ -2966,16 +2966,16 @@ }, { "name": "filament/tables", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/tables.git", - "reference": "bb5fad7306c39fdbb08d97982073114ac465bf92" + "reference": "64806e3c13caeabb23a8668a7aaf1efc8395df96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/bb5fad7306c39fdbb08d97982073114ac465bf92", - "reference": "bb5fad7306c39fdbb08d97982073114ac465bf92", + "url": "https://api.github.com/repos/filamentphp/tables/zipball/64806e3c13caeabb23a8668a7aaf1efc8395df96", + "reference": "64806e3c13caeabb23a8668a7aaf1efc8395df96", "shasum": "" }, "require": { @@ -3014,11 +3014,11 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-04-30T09:16:33+00:00" + "time": "2025-05-19T07:26:42+00:00" }, { "name": "filament/widgets", - "version": "v3.3.14", + "version": "v3.3.15", "source": { "type": "git", "url": "https://github.com/filamentphp/widgets.git", @@ -3727,16 +3727,16 @@ }, { "name": "kirschbaum-development/eloquent-power-joins", - "version": "4.2.3", + "version": "4.2.4", "source": { "type": "git", "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git", - "reference": "d04e06b12e5e7864c303b8a8c6045bfcd4e2c641" + "reference": "4a8012cef7abed8ac3633a180c69138a228b6c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/d04e06b12e5e7864c303b8a8c6045bfcd4e2c641", - "reference": "d04e06b12e5e7864c303b8a8c6045bfcd4e2c641", + "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/4a8012cef7abed8ac3633a180c69138a228b6c4c", + "reference": "4a8012cef7abed8ac3633a180c69138a228b6c4c", "shasum": "" }, "require": { @@ -3784,22 +3784,22 @@ ], "support": { "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues", - "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.3" + "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.4" }, - "time": "2025-04-01T14:41:56+00:00" + "time": "2025-05-19T14:14:41+00:00" }, { "name": "laravel/framework", - "version": "v12.13.0", + "version": "v12.15.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "52b588bcd8efc6d01bc1493d2d67848f8065f269" + "reference": "2ef7fb183f18e547af4eb9f5a55b2ac1011f0b77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/52b588bcd8efc6d01bc1493d2d67848f8065f269", - "reference": "52b588bcd8efc6d01bc1493d2d67848f8065f269", + "url": "https://api.github.com/repos/laravel/framework/zipball/2ef7fb183f18e547af4eb9f5a55b2ac1011f0b77", + "reference": "2ef7fb183f18e547af4eb9f5a55b2ac1011f0b77", "shasum": "" }, "require": { @@ -4001,7 +4001,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-07T17:29:01+00:00" + "time": "2025-05-20T15:10:44+00:00" }, { "name": "laravel/helpers", @@ -6160,31 +6160,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", - "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", + "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.8" + "symfony/console": "^7.2.6" }, "require-dev": { - "illuminate/console": "^11.33.2", - "laravel/pint": "^1.18.2", + "illuminate/console": "^11.44.7", + "laravel/pint": "^1.22.0", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.11", - "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.8", + "pestphp/pest": "^2.36.0 || ^3.8.2", + "phpstan/phpstan": "^1.12.25", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.2.6", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -6227,7 +6227,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" }, "funding": [ { @@ -6243,7 +6243,7 @@ "type": "github" } ], - "time": "2024-11-21T10:39:51+00:00" + "time": "2025-05-08T08:14:37+00:00" }, { "name": "openspout/openspout", @@ -8002,16 +8002,16 @@ }, { "name": "secondnetwork/blade-tabler-icons", - "version": "v3.31.0", + "version": "v3.33.0", "source": { "type": "git", "url": "https://github.com/secondnetwork/blade-tabler-icons.git", - "reference": "bf069bbdb2a63aa8eaeb10e6fd993464e9340a31" + "reference": "523b3ede493ce9f8cbe3a5bdce21769976a80b28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/secondnetwork/blade-tabler-icons/zipball/bf069bbdb2a63aa8eaeb10e6fd993464e9340a31", - "reference": "bf069bbdb2a63aa8eaeb10e6fd993464e9340a31", + "url": "https://api.github.com/repos/secondnetwork/blade-tabler-icons/zipball/523b3ede493ce9f8cbe3a5bdce21769976a80b28", + "reference": "523b3ede493ce9f8cbe3a5bdce21769976a80b28", "shasum": "" }, "require": { @@ -8054,9 +8054,9 @@ ], "support": { "issues": "https://github.com/secondnetwork/blade-tabler-icons/issues", - "source": "https://github.com/secondnetwork/blade-tabler-icons/tree/v3.31.0" + "source": "https://github.com/secondnetwork/blade-tabler-icons/tree/v3.33.0" }, - "time": "2025-03-06T09:24:43+00:00" + "time": "2025-05-17T06:28:48+00:00" }, { "name": "socialiteproviders/authentik", @@ -8705,16 +8705,16 @@ }, { "name": "spatie/laravel-health", - "version": "1.34.1", + "version": "1.34.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-health.git", - "reference": "283d7d5dffeeff6452c9a182f466c327fec6db3b" + "reference": "e7d9d6e0807a9de46f544c76d54badc19af36ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-health/zipball/283d7d5dffeeff6452c9a182f466c327fec6db3b", - "reference": "283d7d5dffeeff6452c9a182f466c327fec6db3b", + "url": "https://api.github.com/repos/spatie/laravel-health/zipball/e7d9d6e0807a9de46f544c76d54badc19af36ddc", + "reference": "e7d9d6e0807a9de46f544c76d54badc19af36ddc", "shasum": "" }, "require": { @@ -8786,7 +8786,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/laravel-health/tree/1.34.1" + "source": "https://github.com/spatie/laravel-health/tree/1.34.2" }, "funding": [ { @@ -8794,7 +8794,7 @@ "type": "github" } ], - "time": "2025-04-17T06:34:01+00:00" + "time": "2025-05-20T08:31:02+00:00" }, { "name": "spatie/laravel-package-tools", @@ -8859,16 +8859,16 @@ }, { "name": "spatie/laravel-permission", - "version": "6.17.0", + "version": "6.18.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-permission.git", - "reference": "02ada8f638b643713fa2fb543384738e27346ddb" + "reference": "3c05f04d12275dfbe462c8b4aae3290e586c2dde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/02ada8f638b643713fa2fb543384738e27346ddb", - "reference": "02ada8f638b643713fa2fb543384738e27346ddb", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/3c05f04d12275dfbe462c8b4aae3290e586c2dde", + "reference": "3c05f04d12275dfbe462c8b4aae3290e586c2dde", "shasum": "" }, "require": { @@ -8930,7 +8930,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-permission/issues", - "source": "https://github.com/spatie/laravel-permission/tree/6.17.0" + "source": "https://github.com/spatie/laravel-permission/tree/6.18.0" }, "funding": [ { @@ -8938,7 +8938,7 @@ "type": "github" } ], - "time": "2025-04-08T15:06:14+00:00" + "time": "2025-05-14T03:32:23+00:00" }, { "name": "spatie/laravel-query-builder", @@ -13132,16 +13132,16 @@ }, { "name": "laravel/pint", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36" + "reference": "941d1927c5ca420c22710e98420287169c7bcaf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", - "reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36", + "url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7", + "reference": "941d1927c5ca420c22710e98420287169c7bcaf7", "shasum": "" }, "require": { @@ -13153,11 +13153,11 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.75.0", - "illuminate/view": "^11.44.2", - "larastan/larastan": "^3.3.1", + "illuminate/view": "^11.44.7", + "larastan/larastan": "^3.4.0", "laravel-zero/framework": "^11.36.1", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3", + "nunomaduro/termwind": "^2.3.1", "pestphp/pest": "^2.36.0" }, "bin": [ @@ -13194,20 +13194,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-04-08T22:11:45+00:00" + "time": "2025-05-08T08:38:12+00:00" }, { "name": "laravel/sail", - "version": "v1.42.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "2edaaf77f3c07a4099965bb3d7dfee16e801c0f6" + "reference": "71a509b14b2621ce58574274a74290f933c687f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/2edaaf77f3c07a4099965bb3d7dfee16e801c0f6", - "reference": "2edaaf77f3c07a4099965bb3d7dfee16e801c0f6", + "url": "https://api.github.com/repos/laravel/sail/zipball/71a509b14b2621ce58574274a74290f933c687f7", + "reference": "71a509b14b2621ce58574274a74290f933c687f7", "shasum": "" }, "require": { @@ -13257,7 +13257,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-04-29T14:26:46+00:00" + "time": "2025-05-13T13:34:34+00:00" }, { "name": "mockery/mockery", @@ -14016,16 +14016,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.14", + "version": "2.1.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8f2e03099cac24ff3b379864d171c5acbfc6b9a2" + "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8f2e03099cac24ff3b379864d171c5acbfc6b9a2", - "reference": "8f2e03099cac24ff3b379864d171c5acbfc6b9a2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", + "reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9", "shasum": "" }, "require": { @@ -14070,7 +14070,7 @@ "type": "github" } ], - "time": "2025-05-02T15:32:28+00:00" + "time": "2025-05-16T09:40:10+00:00" }, { "name": "phpunit/php-code-coverage", @@ -15424,16 +15424,16 @@ }, { "name": "spatie/backtrace", - "version": "1.7.3", + "version": "1.7.4", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "80ae5fabe2a1e3bc7df5c7ca5d91b094dc314b20" + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/80ae5fabe2a1e3bc7df5c7ca5d91b094dc314b20", - "reference": "80ae5fabe2a1e3bc7df5c7ca5d91b094dc314b20", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/cd37a49fce7137359ac30ecc44ef3e16404cccbe", + "reference": "cd37a49fce7137359ac30ecc44ef3e16404cccbe", "shasum": "" }, "require": { @@ -15471,7 +15471,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.7.3" + "source": "https://github.com/spatie/backtrace/tree/1.7.4" }, "funding": [ { @@ -15483,7 +15483,7 @@ "type": "other" } ], - "time": "2025-05-07T07:20:00+00:00" + "time": "2025-05-08T15:41:09+00:00" }, { "name": "spatie/error-solutions", @@ -15966,7 +15966,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -15977,7 +15977,7 @@ "ext-pdo": "*", "ext-zip": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.2" }, From 97fb66f5d6ea0355d72ee489d03735657e7c47e5 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Wed, 21 May 2025 08:46:27 +0200 Subject: [PATCH 3/4] Use `app` panel for password link in AccountCreated notification (#1389) --- app/Notifications/AccountCreated.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Notifications/AccountCreated.php b/app/Notifications/AccountCreated.php index 4f3ba975d..00459842d 100644 --- a/app/Notifications/AccountCreated.php +++ b/app/Notifications/AccountCreated.php @@ -30,7 +30,7 @@ class AccountCreated extends Notification implements ShouldQueue ->line('Email: ' . $notifiable->email); if (!is_null($this->token)) { - return $message->action('Setup Your Account', Filament::getResetPasswordUrl($this->token, $notifiable)); + return $message->action('Setup Your Account', Filament::getPanel('app')->getResetPasswordUrl($this->token, $notifiable)); } return $message; From c22e7456b532e102f2a1ba7e8a4d57ea1d955ba0 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Thu, 22 May 2025 08:41:17 +0200 Subject: [PATCH 4/4] Move tables & forms to resources in client area (#1388) --- .../Server/Resources/ActivityResource.php | 107 ++++++++++++- .../ActivityResource/Pages/ListActivities.php | 101 ------------- .../Server/Resources/AllocationResource.php | 61 ++++++++ .../Pages/ListAllocations.php | 65 +------- .../Server/Resources/BackupResource.php | 139 ++++++++++++++++- .../BackupResource/Pages/ListBackups.php | 141 +----------------- .../Server/Resources/DatabaseResource.php | 72 ++++++++- .../DatabaseResource/Pages/ListDatabases.php | 68 --------- .../Server/Resources/ScheduleResource.php | 46 ++++++ .../ScheduleResource/Pages/ListSchedules.php | 52 +------ .../ScheduleResource/Pages/ViewSchedule.php | 7 +- 11 files changed, 426 insertions(+), 433 deletions(-) diff --git a/app/Filament/Server/Resources/ActivityResource.php b/app/Filament/Server/Resources/ActivityResource.php index 584e27cde..97b6fb8dc 100644 --- a/app/Filament/Server/Resources/ActivityResource.php +++ b/app/Filament/Server/Resources/ActivityResource.php @@ -2,6 +2,8 @@ namespace App\Filament\Server\Resources; +use App\Filament\Admin\Resources\UserResource\Pages\EditUser; +use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Server\Resources\ActivityResource\Pages; use App\Models\ActivityLog; use App\Models\Permission; @@ -9,9 +11,20 @@ use App\Models\Role; use App\Models\Server; use App\Models\User; use Filament\Facades\Filament; +use Filament\Forms\Components\Actions\Action; +use Filament\Forms\Components\DateTimePicker; +use Filament\Forms\Components\KeyValue; +use Filament\Forms\Components\Placeholder; +use Filament\Forms\Components\TextInput; use Filament\Resources\Resource; +use Filament\Tables\Actions\ViewAction; +use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Filters\SelectFilter; +use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; +use Illuminate\Support\Arr; +use Illuminate\Support\HtmlString; class ActivityResource extends Resource { @@ -25,6 +38,95 @@ class ActivityResource extends Resource protected static ?string $navigationIcon = 'tabler-stack'; + public static function table(Table $table): Table + { + /** @var Server $server */ + $server = Filament::getTenant(); + + return $table + ->paginated([25, 50]) + ->defaultPaginationPageOption(25) + ->columns([ + TextColumn::make('event') + ->html() + ->description(fn ($state) => $state) + ->icon(fn (ActivityLog $activityLog) => $activityLog->getIcon()) + ->formatStateUsing(fn (ActivityLog $activityLog) => $activityLog->getLabel()), + TextColumn::make('user') + ->state(function (ActivityLog $activityLog) use ($server) { + if (!$activityLog->actor instanceof User) { + return $activityLog->actor_id === null ? 'System' : 'Deleted user'; + } + + $user = $activityLog->actor->username; + + // Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin + if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) { + $user .= " ({$activityLog->actor->email})"; + } + + return $user; + }) + ->tooltip(fn (ActivityLog $activityLog) => auth()->user()->can('seeIps activityLog') ? $activityLog->ip : '') + ->url(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update user') ? EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin') : '') + ->grow(false), + DateTimeColumn::make('timestamp') + ->since() + ->sortable() + ->grow(false), + ]) + ->defaultSort('timestamp', 'desc') + ->actions([ + ViewAction::make() + //->visible(fn (ActivityLog $activityLog) => $activityLog->hasAdditionalMetadata()) + ->form([ + Placeholder::make('event') + ->content(fn (ActivityLog $activityLog) => new HtmlString($activityLog->getLabel())), + TextInput::make('user') + ->formatStateUsing(function (ActivityLog $activityLog) use ($server) { + if (!$activityLog->actor instanceof User) { + return $activityLog->actor_id === null ? 'System' : 'Deleted user'; + } + + $user = $activityLog->actor->username; + + // Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin + if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) { + $user .= " ({$activityLog->actor->email})"; + } + + if (auth()->user()->can('seeIps activityLog')) { + $user .= " - $activityLog->ip"; + } + + return $user; + }) + ->hintAction( + Action::make('edit') + ->label(trans('filament-actions::edit.single.label')) + ->icon('tabler-edit') + ->visible(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update user')) + ->url(fn (ActivityLog $activityLog) => EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin')) + ), + DateTimePicker::make('timestamp'), + KeyValue::make('properties') + ->label('Metadata') + ->formatStateUsing(fn ($state) => Arr::dot($state)), + ]), + ]) + ->filters([ + SelectFilter::make('event') + ->options(fn (Table $table) => $table->getQuery()->pluck('event', 'event')->unique()->sort()) + ->searchable() + ->preload(), + ]); + } + + public static function canViewAny(): bool + { + return auth()->user()->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant()); + } + public static function getEloquentQuery(): Builder { /** @var Server $server */ @@ -51,11 +153,6 @@ class ActivityResource extends Resource }); } - public static function canViewAny(): bool - { - return auth()->user()->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant()); - } - public static function getPages(): array { return [ diff --git a/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php b/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php index 9c953d81d..fca7f6207 100644 --- a/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php +++ b/app/Filament/Server/Resources/ActivityResource/Pages/ListActivities.php @@ -2,114 +2,13 @@ namespace App\Filament\Server\Resources\ActivityResource\Pages; -use App\Filament\Admin\Resources\UserResource\Pages\EditUser; use App\Filament\Server\Resources\ActivityResource; -use App\Models\ActivityLog; -use App\Filament\Components\Tables\Columns\DateTimeColumn; -use App\Models\Server; -use App\Models\User; -use Filament\Facades\Filament; -use Filament\Forms\Components\Actions\Action; -use Filament\Forms\Components\DateTimePicker; -use Filament\Forms\Components\KeyValue; -use Filament\Forms\Components\Placeholder; -use Filament\Forms\Components\TextInput; use Filament\Resources\Pages\ListRecords; -use Filament\Tables\Actions\ViewAction; -use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Filters\SelectFilter; -use Filament\Tables\Table; -use Illuminate\Support\Arr; -use Illuminate\Support\HtmlString; class ListActivities extends ListRecords { protected static string $resource = ActivityResource::class; - public function table(Table $table): Table - { - /** @var Server $server */ - $server = Filament::getTenant(); - - return $table - ->paginated([25, 50]) - ->defaultPaginationPageOption(25) - ->columns([ - TextColumn::make('event') - ->html() - ->description(fn ($state) => $state) - ->icon(fn (ActivityLog $activityLog) => $activityLog->getIcon()) - ->formatStateUsing(fn (ActivityLog $activityLog) => $activityLog->getLabel()), - TextColumn::make('user') - ->state(function (ActivityLog $activityLog) use ($server) { - if (!$activityLog->actor instanceof User) { - return $activityLog->actor_id === null ? 'System' : 'Deleted user'; - } - - $user = $activityLog->actor->username; - - // Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin - if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) { - $user .= " ({$activityLog->actor->email})"; - } - - return $user; - }) - ->tooltip(fn (ActivityLog $activityLog) => auth()->user()->can('seeIps activityLog') ? $activityLog->ip : '') - ->url(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update user') ? EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin') : '') - ->grow(false), - DateTimeColumn::make('timestamp') - ->since() - ->sortable() - ->grow(false), - ]) - ->defaultSort('timestamp', 'desc') - ->actions([ - ViewAction::make() - //->visible(fn (ActivityLog $activityLog) => $activityLog->hasAdditionalMetadata()) - ->form([ - Placeholder::make('event') - ->content(fn (ActivityLog $activityLog) => new HtmlString($activityLog->getLabel())), - TextInput::make('user') - ->formatStateUsing(function (ActivityLog $activityLog) use ($server) { - if (!$activityLog->actor instanceof User) { - return $activityLog->actor_id === null ? 'System' : 'Deleted user'; - } - - $user = $activityLog->actor->username; - - // Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin - if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) { - $user .= " ({$activityLog->actor->email})"; - } - - if (auth()->user()->can('seeIps activityLog')) { - $user .= " - $activityLog->ip"; - } - - return $user; - }) - ->hintAction( - Action::make('edit') - ->label(trans('filament-actions::edit.single.label')) - ->icon('tabler-edit') - ->visible(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update user')) - ->url(fn (ActivityLog $activityLog) => EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin')) - ), - DateTimePicker::make('timestamp'), - KeyValue::make('properties') - ->label('Metadata') - ->formatStateUsing(fn ($state) => Arr::dot($state)), - ]), - ]) - ->filters([ - SelectFilter::make('event') - ->options(fn (Table $table) => $table->getQuery()->pluck('event', 'event')->unique()->sort()) - ->searchable() - ->preload(), - ]); - } - public function getBreadcrumbs(): array { return []; diff --git a/app/Filament/Server/Resources/AllocationResource.php b/app/Filament/Server/Resources/AllocationResource.php index 614f52b47..9719cc975 100644 --- a/app/Filament/Server/Resources/AllocationResource.php +++ b/app/Filament/Server/Resources/AllocationResource.php @@ -2,12 +2,18 @@ namespace App\Filament\Server\Resources; +use App\Facades\Activity; use App\Filament\Server\Resources\AllocationResource\Pages; use App\Models\Allocation; use App\Models\Permission; use App\Models\Server; use Filament\Facades\Filament; use Filament\Resources\Resource; +use Filament\Tables\Actions\DetachAction; +use Filament\Tables\Columns\IconColumn; +use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Columns\TextInputColumn; +use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; class AllocationResource extends Resource @@ -22,6 +28,61 @@ class AllocationResource extends Resource protected static ?string $navigationIcon = 'tabler-network'; + public static function table(Table $table): Table + { + /** @var Server $server */ + $server = Filament::getTenant(); + + return $table + ->columns([ + TextColumn::make('ip') + ->label('Address') + ->formatStateUsing(fn (Allocation $allocation) => $allocation->alias), + TextColumn::make('alias') + ->hidden(), + TextColumn::make('port'), + TextInputColumn::make('notes') + ->visibleFrom('sm') + ->disabled(fn () => !auth()->user()->can(Permission::ACTION_ALLOCATION_UPDATE, $server)) + ->label('Notes') + ->placeholder('No Notes'), + IconColumn::make('primary') + ->icon(fn ($state) => match ($state) { + true => 'tabler-star-filled', + default => 'tabler-star', + }) + ->color(fn ($state) => match ($state) { + true => 'warning', + default => 'gray', + }) + ->action(function (Allocation $allocation) use ($server) { + if (auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server)) { + return $server->update(['allocation_id' => $allocation->id]); + } + }) + ->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) + ->label('Primary'), + ]) + ->actions([ + DetachAction::make() + ->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server)) + ->label('Delete') + ->icon('tabler-trash') + ->hidden(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) + ->action(function (Allocation $allocation) { + Allocation::query()->where('id', $allocation->id)->update([ + 'notes' => null, + 'server_id' => null, + ]); + + Activity::event('server:allocation.delete') + ->subject($allocation) + ->property('allocation', $allocation->toString()) + ->log(); + }), + ]); + } + // TODO: find better way handle server conflict state public static function canAccess(): bool { diff --git a/app/Filament/Server/Resources/AllocationResource/Pages/ListAllocations.php b/app/Filament/Server/Resources/AllocationResource/Pages/ListAllocations.php index 24ed7e407..ee9526cc3 100644 --- a/app/Filament/Server/Resources/AllocationResource/Pages/ListAllocations.php +++ b/app/Filament/Server/Resources/AllocationResource/Pages/ListAllocations.php @@ -4,85 +4,24 @@ namespace App\Filament\Server\Resources\AllocationResource\Pages; use App\Facades\Activity; use App\Filament\Server\Resources\AllocationResource; -use App\Models\Allocation; use App\Models\Permission; use App\Models\Server; use App\Services\Allocations\FindAssignableAllocationService; -use Filament\Actions; +use Filament\Actions\Action; use Filament\Facades\Filament; use Filament\Resources\Pages\ListRecords; -use Filament\Tables\Actions\DetachAction; -use Filament\Tables\Columns\IconColumn; -use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Columns\TextInputColumn; -use Filament\Tables\Table; class ListAllocations extends ListRecords { protected static string $resource = AllocationResource::class; - public function table(Table $table): Table - { - /** @var Server $server */ - $server = Filament::getTenant(); - - return $table - ->columns([ - TextColumn::make('ip') - ->label('Address') - ->formatStateUsing(fn (Allocation $allocation) => $allocation->alias), - TextColumn::make('alias') - ->hidden(), - TextColumn::make('port'), - TextInputColumn::make('notes') - ->visibleFrom('sm') - ->disabled(fn () => !auth()->user()->can(Permission::ACTION_ALLOCATION_UPDATE, $server)) - ->label('Notes') - ->placeholder('No Notes'), - IconColumn::make('primary') - ->icon(fn ($state) => match ($state) { - true => 'tabler-star-filled', - default => 'tabler-star', - }) - ->color(fn ($state) => match ($state) { - true => 'warning', - default => 'gray', - }) - ->action(function (Allocation $allocation) use ($server) { - if (auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server)) { - return $server->update(['allocation_id' => $allocation->id]); - } - }) - ->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) - ->label('Primary'), - ]) - ->actions([ - DetachAction::make() - ->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server)) - ->label('Delete') - ->icon('tabler-trash') - ->hidden(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) - ->action(function (Allocation $allocation) { - Allocation::query()->where('id', $allocation->id)->update([ - 'notes' => null, - 'server_id' => null, - ]); - - Activity::event('server:allocation.delete') - ->subject($allocation) - ->property('allocation', $allocation->toString()) - ->log(); - }), - ]); - } - protected function getHeaderActions(): array { /** @var Server $server */ $server = Filament::getTenant(); return [ - Actions\Action::make('addAllocation') + Action::make('addAllocation') ->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_CREATE, $server)) ->label(fn () => $server->allocations()->count() >= $server->allocation_limit ? 'Allocation limit reached' : 'Add Allocation') ->hidden(fn () => !config('panel.client_features.allocations.enabled')) diff --git a/app/Filament/Server/Resources/BackupResource.php b/app/Filament/Server/Resources/BackupResource.php index 13b84f50e..d163b751b 100644 --- a/app/Filament/Server/Resources/BackupResource.php +++ b/app/Filament/Server/Resources/BackupResource.php @@ -2,13 +2,35 @@ namespace App\Filament\Server\Resources; +use App\Enums\BackupStatus; +use App\Enums\ServerState; +use App\Facades\Activity; use App\Filament\Server\Resources\BackupResource\Pages; +use App\Http\Controllers\Api\Client\Servers\BackupController; use App\Models\Backup; use App\Models\Permission; use App\Models\Server; +use App\Repositories\Daemon\DaemonBackupRepository; +use App\Services\Backups\DownloadLinkService; +use App\Filament\Components\Tables\Columns\BytesColumn; +use App\Filament\Components\Tables\Columns\DateTimeColumn; use Filament\Facades\Filament; +use Filament\Forms\Components\Checkbox; +use Filament\Forms\Components\Placeholder; +use Filament\Forms\Components\Textarea; +use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Toggle; +use Filament\Forms\Form; +use Filament\Notifications\Notification; use Filament\Resources\Resource; +use Filament\Tables\Actions\Action; +use Filament\Tables\Actions\ActionGroup; +use Filament\Tables\Actions\DeleteAction; +use Filament\Tables\Columns\IconColumn; +use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\Request; class BackupResource extends Resource { @@ -44,8 +66,121 @@ class BackupResource extends Resource return null; } - return $count >= $limit ? 'danger' - : ($count >= $limit * self::WARNING_THRESHOLD ? 'warning' : 'success'); + return $count >= $limit ? 'danger' : ($count >= $limit * self::WARNING_THRESHOLD ? 'warning' : 'success'); + } + + public static function form(Form $form): Form + { + return $form + ->schema([ + TextInput::make('name') + ->label('Name') + ->columnSpanFull(), + TextArea::make('ignored') + ->columnSpanFull() + ->label('Ignored Files & Directories'), + Toggle::make('is_locked') + ->label('Lock?') + ->helperText('Prevents this backup from being deleted until explicitly unlocked.'), + ]); + } + + public static function table(Table $table): Table + { + /** @var Server $server */ + $server = Filament::getTenant(); + + return $table + ->columns([ + TextColumn::make('name') + ->searchable(), + BytesColumn::make('bytes') + ->label('Size'), + DateTimeColumn::make('created_at') + ->label('Created') + ->since() + ->sortable(), + TextColumn::make('status') + ->label('Status') + ->badge(), + IconColumn::make('is_locked') + ->visibleFrom('md') + ->label('Lock Status') + ->trueIcon('tabler-lock') + ->falseIcon('tabler-lock-open'), + ]) + ->actions([ + ActionGroup::make([ + Action::make('lock') + ->icon(fn (Backup $backup) => !$backup->is_locked ? 'tabler-lock' : 'tabler-lock-open') + ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) + ->label(fn (Backup $backup) => !$backup->is_locked ? 'Lock' : 'Unlock') + ->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->toggleLock($request, $server, $backup)) + ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), + Action::make('download') + ->color('primary') + ->icon('tabler-download') + ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server)) + ->url(fn (DownloadLinkService $downloadLinkService, Backup $backup, Request $request) => $downloadLinkService->handle($backup, $request->user()), true) + ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), + Action::make('restore') + ->color('success') + ->icon('tabler-folder-up') + ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_RESTORE, $server)) + ->form([ + Placeholder::make('') + ->helperText('Your server will be stopped. You will not be able to control the power state, access the file manager, or create additional backups until this process is completed.'), + Checkbox::make('truncate') + ->label('Delete all files before restoring backup?'), + ]) + ->action(function (Backup $backup, $data, DaemonBackupRepository $daemonRepository, DownloadLinkService $downloadLinkService) use ($server) { + if (!is_null($server->status)) { + return Notification::make() + ->danger() + ->title('Backup Restore Failed') + ->body('This server is not currently in a state that allows for a backup to be restored.') + ->send(); + } + + if (!$backup->is_successful && is_null($backup->completed_at)) { //TODO Change to Notifications + return Notification::make() + ->danger() + ->title('Backup Restore Failed') + ->body('This backup cannot be restored at this time: not completed or failed.') + ->send(); + } + + $log = Activity::event('server:backup.restore') + ->subject($backup) + ->property(['name' => $backup->name, 'truncate' => $data['truncate']]); + + $log->transaction(function () use ($downloadLinkService, $daemonRepository, $backup, $server, $data) { + // If the backup is for an S3 file we need to generate a unique Download link for + // it that will allow daemon to actually access the file. + if ($backup->disk === Backup::ADAPTER_AWS_S3) { + $url = $downloadLinkService->handle($backup, auth()->user()); + } + + // Update the status right away for the server so that we know not to allow certain + // actions against it via the Panel API. + $server->update(['status' => ServerState::RestoringBackup]); + + $daemonRepository->setServer($server)->restore($backup, $url ?? null, $data['truncate']); + }); + + return Notification::make() + ->title('Restoring Backup') + ->send(); + }) + ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), + DeleteAction::make('delete') + ->disabled(fn (Backup $backup) => $backup->is_locked) + ->modalDescription(fn (Backup $backup) => 'Do you wish to delete, ' . $backup->name . '?') + ->modalSubmitActionLabel('Delete Backup') + ->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->delete($request, $server, $backup)) + ->visible(fn (Backup $backup) => $backup->status !== BackupStatus::InProgress), + ]), + ]); } // TODO: find better way handle server conflict state diff --git a/app/Filament/Server/Resources/BackupResource/Pages/ListBackups.php b/app/Filament/Server/Resources/BackupResource/Pages/ListBackups.php index b6a9d3a6c..abc5c70cc 100644 --- a/app/Filament/Server/Resources/BackupResource/Pages/ListBackups.php +++ b/app/Filament/Server/Resources/BackupResource/Pages/ListBackups.php @@ -2,165 +2,28 @@ namespace App\Filament\Server\Resources\BackupResource\Pages; -use App\Enums\BackupStatus; -use App\Enums\ServerState; use App\Facades\Activity; use App\Filament\Server\Resources\BackupResource; -use App\Http\Controllers\Api\Client\Servers\BackupController; -use App\Models\Backup; use App\Models\Permission; use App\Models\Server; -use App\Repositories\Daemon\DaemonBackupRepository; -use App\Services\Backups\DownloadLinkService; use App\Services\Backups\InitiateBackupService; -use App\Filament\Components\Tables\Columns\BytesColumn; -use App\Filament\Components\Tables\Columns\DateTimeColumn; -use Filament\Actions; +use Filament\Actions\CreateAction; use Filament\Facades\Filament; -use Filament\Forms\Components\Checkbox; -use Filament\Forms\Components\Placeholder; -use Filament\Forms\Components\Textarea; -use Filament\Forms\Components\TextInput; -use Filament\Forms\Components\Toggle; -use Filament\Forms\Form; use Filament\Notifications\Notification; use Filament\Resources\Pages\ListRecords; -use Filament\Tables\Actions\Action; -use Filament\Tables\Actions\ActionGroup; -use Filament\Tables\Actions\DeleteAction; -use Filament\Tables\Columns\IconColumn; -use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Table; -use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\HttpException; class ListBackups extends ListRecords { protected static string $resource = BackupResource::class; - protected static bool $canCreateAnother = false; - - public function form(Form $form): Form - { - return $form - ->schema([ - TextInput::make('name') - ->label('Name') - ->columnSpanFull(), - TextArea::make('ignored') - ->columnSpanFull() - ->label('Ignored Files & Directories'), - Toggle::make('is_locked') - ->label('Lock?') - ->helperText('Prevents this backup from being deleted until explicitly unlocked.'), - ]); - } - - public function table(Table $table): Table - { - /** @var Server $server */ - $server = Filament::getTenant(); - - return $table - ->columns([ - TextColumn::make('name') - ->searchable(), - BytesColumn::make('bytes') - ->label('Size'), - DateTimeColumn::make('created_at') - ->label('Created') - ->since() - ->sortable(), - TextColumn::make('status') - ->label('Status') - ->badge(), - IconColumn::make('is_locked') - ->visibleFrom('md') - ->label('Lock Status') - ->trueIcon('tabler-lock') - ->falseIcon('tabler-lock-open'), - ]) - ->actions([ - ActionGroup::make([ - Action::make('lock') - ->icon(fn (Backup $backup) => !$backup->is_locked ? 'tabler-lock' : 'tabler-lock-open') - ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) - ->label(fn (Backup $backup) => !$backup->is_locked ? 'Lock' : 'Unlock') - ->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->toggleLock($request, $server, $backup)) - ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), - Action::make('download') - ->color('primary') - ->icon('tabler-download') - ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server)) - ->url(fn (DownloadLinkService $downloadLinkService, Backup $backup, Request $request) => $downloadLinkService->handle($backup, $request->user()), true) - ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), - Action::make('restore') - ->color('success') - ->icon('tabler-folder-up') - ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_RESTORE, $server)) - ->form([ - Placeholder::make('') - ->helperText('Your server will be stopped. You will not be able to control the power state, access the file manager, or create additional backups until this process is completed.'), - Checkbox::make('truncate') - ->label('Delete all files before restoring backup?'), - ]) - ->action(function (Backup $backup, $data, DaemonBackupRepository $daemonRepository, DownloadLinkService $downloadLinkService) use ($server) { - if (!is_null($server->status)) { - return Notification::make() - ->danger() - ->title('Backup Restore Failed') - ->body('This server is not currently in a state that allows for a backup to be restored.') - ->send(); - } - - if (!$backup->is_successful && is_null($backup->completed_at)) { //TODO Change to Notifications - return Notification::make() - ->danger() - ->title('Backup Restore Failed') - ->body('This backup cannot be restored at this time: not completed or failed.') - ->send(); - } - - $log = Activity::event('server:backup.restore') - ->subject($backup) - ->property(['name' => $backup->name, 'truncate' => $data['truncate']]); - - $log->transaction(function () use ($downloadLinkService, $daemonRepository, $backup, $server, $data) { - // If the backup is for an S3 file we need to generate a unique Download link for - // it that will allow daemon to actually access the file. - if ($backup->disk === Backup::ADAPTER_AWS_S3) { - $url = $downloadLinkService->handle($backup, auth()->user()); - } - - // Update the status right away for the server so that we know not to allow certain - // actions against it via the Panel API. - $server->update(['status' => ServerState::RestoringBackup]); - - $daemonRepository->setServer($server)->restore($backup, $url ?? null, $data['truncate']); - }); - - return Notification::make() - ->title('Restoring Backup') - ->send(); - }) - ->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful), - DeleteAction::make('delete') - ->disabled(fn (Backup $backup) => $backup->is_locked) - ->modalDescription(fn (Backup $backup) => 'Do you wish to delete, ' . $backup->name . '?') - ->modalSubmitActionLabel('Delete Backup') - ->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->delete($request, $server, $backup)) - ->visible(fn (Backup $backup) => $backup->status !== BackupStatus::InProgress), - ]), - ]); - } - protected function getHeaderActions(): array { /** @var Server $server */ $server = Filament::getTenant(); return [ - Actions\CreateAction::make() + CreateAction::make() ->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_CREATE, $server)) ->label(fn () => $server->backups()->count() >= $server->backup_limit ? 'Backup limit reached' : 'Create Backup') ->disabled(fn () => $server->backups()->count() >= $server->backup_limit) diff --git a/app/Filament/Server/Resources/DatabaseResource.php b/app/Filament/Server/Resources/DatabaseResource.php index 39f339244..4a89a2598 100644 --- a/app/Filament/Server/Resources/DatabaseResource.php +++ b/app/Filament/Server/Resources/DatabaseResource.php @@ -2,13 +2,23 @@ namespace App\Filament\Server\Resources; +use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction; +use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Server\Resources\DatabaseResource\Pages; use App\Models\Database; use App\Models\Permission; use App\Models\Server; +use App\Services\Databases\DatabaseManagementService; use Filament\Facades\Filament; +use Filament\Forms\Components\TextInput; +use Filament\Forms\Form; use Filament\Resources\Resource; +use Filament\Tables\Actions\DeleteAction; +use Filament\Tables\Actions\ViewAction; +use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; +use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; class DatabaseResource extends Resource { @@ -42,9 +52,65 @@ class DatabaseResource extends Resource return null; } - return $count >= $limit - ? 'danger' - : ($count >= $limit * self::WARNING_THRESHOLD ? 'warning' : 'success'); + return $count >= $limit ? 'danger' : ($count >= $limit * self::WARNING_THRESHOLD ? 'warning' : 'success'); + } + + public static function form(Form $form): Form + { + /** @var Server $server */ + $server = Filament::getTenant(); + + return $form + ->schema([ + TextInput::make('host') + ->formatStateUsing(fn (Database $database) => $database->address()) + ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), + TextInput::make('database') + ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), + TextInput::make('username') + ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), + TextInput::make('password') + ->password()->revealable() + ->hidden(fn () => !auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server)) + ->hintAction( + RotateDatabasePasswordAction::make() + ->authorize(fn () => auth()->user()->can(Permission::ACTION_DATABASE_UPDATE, $server)) + ) + ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->formatStateUsing(fn (Database $database) => $database->password), + TextInput::make('remote') + ->label('Connections From'), + TextInput::make('max_connections') + ->formatStateUsing(fn (Database $database) => $database->max_connections === 0 ? $database->max_connections : 'Unlimited'), + TextInput::make('jdbc') + ->label('JDBC Connection String') + ->password()->revealable() + ->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server)) + ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) + ->columnSpanFull() + ->formatStateUsing(fn (Database $database) => $database->jdbc), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('host') + ->state(fn (Database $database) => $database->address()) + ->badge(), + TextColumn::make('database'), + TextColumn::make('username'), + TextColumn::make('remote'), + DateTimeColumn::make('created_at') + ->sortable(), + ]) + ->actions([ + ViewAction::make() + ->modalHeading(fn (Database $database) => 'Viewing ' . $database->database), + DeleteAction::make() + ->using(fn (Database $database, DatabaseManagementService $service) => $service->delete($database)), + ]); } // TODO: find better way handle server conflict state diff --git a/app/Filament/Server/Resources/DatabaseResource/Pages/ListDatabases.php b/app/Filament/Server/Resources/DatabaseResource/Pages/ListDatabases.php index 3c7eda0aa..fbb1bd42b 100644 --- a/app/Filament/Server/Resources/DatabaseResource/Pages/ListDatabases.php +++ b/app/Filament/Server/Resources/DatabaseResource/Pages/ListDatabases.php @@ -2,12 +2,8 @@ namespace App\Filament\Server\Resources\DatabaseResource\Pages; -use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction; -use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Server\Resources\DatabaseResource; -use App\Models\Database; use App\Models\DatabaseHost; -use App\Models\Permission; use App\Models\Server; use App\Services\Databases\DatabaseManagementService; use Filament\Actions\CreateAction; @@ -15,76 +11,12 @@ use Filament\Facades\Filament; use Filament\Forms\Components\Grid; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; -use Filament\Forms\Form; use Filament\Resources\Pages\ListRecords; -use Filament\Tables\Actions\DeleteAction; -use Filament\Tables\Actions\ViewAction; -use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Table; -use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; class ListDatabases extends ListRecords { protected static string $resource = DatabaseResource::class; - public function form(Form $form): Form - { - /** @var Server $server */ - $server = Filament::getTenant(); - - return $form - ->schema([ - TextInput::make('host') - ->formatStateUsing(fn (Database $database) => $database->address()) - ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), - TextInput::make('database') - ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), - TextInput::make('username') - ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null), - TextInput::make('password') - ->password()->revealable() - ->hidden(fn () => !auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server)) - ->hintAction( - RotateDatabasePasswordAction::make() - ->authorize(fn () => auth()->user()->can(Permission::ACTION_DATABASE_UPDATE, $server)) - ) - ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) - ->formatStateUsing(fn (Database $database) => $database->password), - TextInput::make('remote') - ->label('Connections From'), - TextInput::make('max_connections') - ->formatStateUsing(fn (Database $database) => $database->max_connections === 0 ? $database->max_connections : 'Unlimited'), - TextInput::make('jdbc') - ->label('JDBC Connection String') - ->password()->revealable() - ->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server)) - ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) - ->columnSpanFull() - ->formatStateUsing(fn (Database $database) => $database->jdbc), - ]); - } - - public function table(Table $table): Table - { - return $table - ->columns([ - TextColumn::make('host') - ->state(fn (Database $database) => $database->address()) - ->badge(), - TextColumn::make('database'), - TextColumn::make('username'), - TextColumn::make('remote'), - DateTimeColumn::make('created_at') - ->sortable(), - ]) - ->actions([ - ViewAction::make() - ->modalHeading(fn (Database $database) => 'Viewing ' . $database->database), - DeleteAction::make() - ->using(fn (Database $database, DatabaseManagementService $service) => $service->delete($database)), - ]); - } - protected function getHeaderActions(): array { /** @var Server $server */ diff --git a/app/Filament/Server/Resources/ScheduleResource.php b/app/Filament/Server/Resources/ScheduleResource.php index 0e07402d7..53f7160ac 100644 --- a/app/Filament/Server/Resources/ScheduleResource.php +++ b/app/Filament/Server/Resources/ScheduleResource.php @@ -2,6 +2,8 @@ namespace App\Filament\Server\Resources; +use App\Facades\Activity; +use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Server\Resources\ScheduleResource\Pages; use App\Filament\Server\Resources\ScheduleResource\RelationManagers\TasksRelationManager; use App\Helpers\Utilities; @@ -23,6 +25,12 @@ use Filament\Forms\Set; use Filament\Notifications\Notification; use Filament\Resources\Resource; use Filament\Support\Exceptions\Halt; +use Filament\Tables\Actions\DeleteAction; +use Filament\Tables\Actions\EditAction; +use Filament\Tables\Actions\ViewAction; +use Filament\Tables\Columns\IconColumn; +use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; class ScheduleResource extends Resource @@ -303,6 +311,44 @@ class ScheduleResource extends Resource ]); } + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('name') + ->searchable(), + TextColumn::make('cron') + ->state(fn (Schedule $schedule) => $schedule->cron_minute . ' ' . $schedule->cron_hour . ' ' . $schedule->cron_day_of_month . ' ' . $schedule->cron_month . ' ' . $schedule->cron_day_of_week), + TextColumn::make('status') + ->state(fn (Schedule $schedule) => !$schedule->is_active ? 'Inactive' : ($schedule->is_processing ? 'Processing' : 'Active')), + IconColumn::make('only_when_online') + ->boolean() + ->sortable(), + DateTimeColumn::make('last_run_at') + ->label('Last run') + ->placeholder('Never') + ->since() + ->sortable(), + DateTimeColumn::make('next_run_at') + ->label('Next run') + ->placeholder('Never') + ->since() + ->sortable() + ->state(fn (Schedule $schedule) => $schedule->is_active ? $schedule->next_run_at : null), + ]) + ->actions([ + ViewAction::make(), + EditAction::make(), + DeleteAction::make() + ->after(function (Schedule $schedule) { + Activity::event('server:schedule.delete') + ->subject($schedule) + ->property('name', $schedule->name) + ->log(); + }), + ]); + } + public static function getRelations(): array { return [ diff --git a/app/Filament/Server/Resources/ScheduleResource/Pages/ListSchedules.php b/app/Filament/Server/Resources/ScheduleResource/Pages/ListSchedules.php index 0f79e8a40..398f5cd93 100644 --- a/app/Filament/Server/Resources/ScheduleResource/Pages/ListSchedules.php +++ b/app/Filament/Server/Resources/ScheduleResource/Pages/ListSchedules.php @@ -2,65 +2,19 @@ namespace App\Filament\Server\Resources\ScheduleResource\Pages; -use App\Facades\Activity; use App\Filament\Server\Resources\ScheduleResource; -use App\Models\Schedule; -use App\Filament\Components\Tables\Columns\DateTimeColumn; -use Filament\Actions; +use Filament\Actions\CreateAction; use Filament\Resources\Pages\ListRecords; -use Filament\Tables\Actions\DeleteAction; -use Filament\Tables\Actions\EditAction; -use Filament\Tables\Actions\ViewAction; -use Filament\Tables\Columns\IconColumn; -use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Table; class ListSchedules extends ListRecords { protected static string $resource = ScheduleResource::class; - public function table(Table $table): Table - { - return $table - ->columns([ - TextColumn::make('name') - ->searchable(), - TextColumn::make('cron') - ->state(fn (Schedule $schedule) => $schedule->cron_minute . ' ' . $schedule->cron_hour . ' ' . $schedule->cron_day_of_month . ' ' . $schedule->cron_month . ' ' . $schedule->cron_day_of_week), - TextColumn::make('status') - ->state(fn (Schedule $schedule) => !$schedule->is_active ? 'Inactive' : ($schedule->is_processing ? 'Processing' : 'Active')), - IconColumn::make('only_when_online') - ->boolean() - ->sortable(), - DateTimeColumn::make('last_run_at') - ->label('Last run') - ->placeholder('Never') - ->since() - ->sortable(), - DateTimeColumn::make('next_run_at') - ->label('Next run') - ->placeholder('Never') - ->since() - ->sortable() - ->state(fn (Schedule $schedule) => $schedule->is_active ? $schedule->next_run_at : null), - ]) - ->actions([ - ViewAction::make(), - EditAction::make(), - DeleteAction::make() - ->after(function (Schedule $schedule) { - Activity::event('server:schedule.delete') - ->subject($schedule) - ->property('name', $schedule->name) - ->log(); - }), - ]); - } - protected function getHeaderActions(): array { return [ - Actions\CreateAction::make()->label('New Schedule'), + CreateAction::make() + ->label('New Schedule'), ]; } diff --git a/app/Filament/Server/Resources/ScheduleResource/Pages/ViewSchedule.php b/app/Filament/Server/Resources/ScheduleResource/Pages/ViewSchedule.php index a9dd654ea..2ce565da0 100644 --- a/app/Filament/Server/Resources/ScheduleResource/Pages/ViewSchedule.php +++ b/app/Filament/Server/Resources/ScheduleResource/Pages/ViewSchedule.php @@ -7,7 +7,8 @@ use App\Filament\Server\Resources\ScheduleResource; use App\Models\Permission; use App\Models\Schedule; use App\Services\Schedules\ProcessScheduleService; -use Filament\Actions; +use Filament\Actions\Action; +use Filament\Actions\EditAction; use Filament\Facades\Filament; use Filament\Resources\Pages\ViewRecord; @@ -18,7 +19,7 @@ class ViewSchedule extends ViewRecord protected function getHeaderActions(): array { return [ - Actions\Action::make('runNow') + Action::make('runNow') ->authorize(fn () => auth()->user()->can(Permission::ACTION_SCHEDULE_UPDATE, Filament::getTenant())) ->label(fn (Schedule $schedule) => $schedule->tasks->count() === 0 ? 'No tasks' : ($schedule->is_processing ? 'Processing' : 'Run now')) ->color(fn (Schedule $schedule) => $schedule->tasks->count() === 0 || $schedule->is_processing ? 'warning' : 'primary') @@ -33,7 +34,7 @@ class ViewSchedule extends ViewRecord $this->fillForm(); }), - Actions\EditAction::make(), + EditAction::make(), ]; }