From 038f89c160fd95d949c3a3d29a3384fb712cccce Mon Sep 17 00:00:00 2001 From: Leon Date: Wed, 18 Mar 2026 13:09:25 +0100 Subject: [PATCH] feat: spatie health filament --- app/Providers/AppServiceProvider.php | 18 + .../Filament/DashboardPanelProvider.php | 4 + composer.json | 4 +- composer.lock | 370 +++++++++++++++++- config/health.php | 164 ++++++++ ...2026_03_18_110246_create_health_tables.php | 36 ++ .../filament-spatie-health-styles.css | 1 + 7 files changed, 595 insertions(+), 2 deletions(-) create mode 100644 config/health.php create mode 100644 database/migrations/2026_03_18_110246_create_health_tables.php create mode 100644 public/css/filament-spatie-health/filament-spatie-health-styles.css diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 58ae8f6..b22d48e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,6 +8,15 @@ use Dedoc\Scramble\Support\Generator\SecurityScheme; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; +use Meilisearch\Meilisearch; +use Spatie\Health\Checks\Checks\MeilisearchCheck; +use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck; +use Spatie\Health\Facades\Health; +use Spatie\Health\Checks\Checks\DatabaseCheck; +use Spatie\Health\Checks\Checks\RedisCheck; +use Spatie\Health\Checks\Checks\HorizonCheck; +use Spatie\Health\Checks\Checks\OptimizedAppCheck; +use Spatie\Health\Checks\Checks\DebugModeCheck; class AppServiceProvider extends ServiceProvider { @@ -39,5 +48,14 @@ class AppServiceProvider extends ServiceProvider */ }); + Health::checks([ + DatabaseCheck::new(), + UsedDiskSpaceCheck::new(), + RedisCheck::new(), + HorizonCheck::new(), + OptimizedAppCheck::new(), + DebugModeCheck::new(), + MeilisearchCheck::new()->url('http://meilisearch:7700') + ]); } } diff --git a/app/Providers/Filament/DashboardPanelProvider.php b/app/Providers/Filament/DashboardPanelProvider.php index 83e5824..8490af6 100644 --- a/app/Providers/Filament/DashboardPanelProvider.php +++ b/app/Providers/Filament/DashboardPanelProvider.php @@ -12,6 +12,7 @@ use Filament\PanelProvider; use Filament\Support\Colors\Color; use Filament\Widgets\AccountWidget; use Filament\Widgets\FilamentInfoWidget; +use ShuvroRoy\FilamentSpatieLaravelHealth\FilamentSpatieLaravelHealthPlugin; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; @@ -54,6 +55,9 @@ class DashboardPanelProvider extends PanelProvider ]) ->authMiddleware([ Authenticate::class, + ]) + ->plugins([ + FilamentSpatieLaravelHealthPlugin::make(), ]); } } diff --git a/composer.json b/composer.json index 25471c3..45ce485 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,9 @@ "laravel/tinker": "^2.10.1", "meilisearch/meilisearch-php": "^1.16", "predis/predis": "^3.4", - "scalar/laravel": "^0.2.0" + "scalar/laravel": "^0.2.0", + "shuvroroy/filament-spatie-laravel-health": "^3.2", + "spatie/laravel-health": "^1.39" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 60896e9..eeded5b 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": "2a710ddb9f9c83cf6e70df03723922cc", + "content-hash": "8c9c72cb13393a33fd51a4f3ee47635b", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -5971,6 +5971,158 @@ ], "time": "2022-12-17T21:53:22+00:00" }, + { + "name": "shuvroroy/filament-spatie-laravel-health", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/shuvroroy/filament-spatie-laravel-health.git", + "reference": "77e80a542411313da9e78d89171a12b9a107667e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shuvroroy/filament-spatie-laravel-health/zipball/77e80a542411313da9e78d89171a12b9a107667e", + "reference": "77e80a542411313da9e78d89171a12b9a107667e", + "shasum": "" + }, + "require": { + "filament/filament": "^4.0|^5.0", + "php": "^8.2", + "spatie/laravel-health": "^1.23", + "spatie/laravel-package-tools": "^1.9" + }, + "require-dev": { + "larastan/larastan": "^3.0", + "laravel/pint": "^1.0", + "nunomaduro/collision": "^7.0||^8.0", + "orchestra/testbench": "^9.0||^10.0", + "pestphp/pest": "^2.0||^3.0", + "pestphp/pest-plugin-arch": "^2.0||^3.0", + "pestphp/pest-plugin-laravel": "^2.0||^3.0", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "ShuvroRoy\\FilamentSpatieLaravelHealth\\FilamentSpatieLaravelHealthServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "ShuvroRoy\\FilamentSpatieLaravelHealth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shuvro Roy", + "email": "shuvro.nsu.cse@gmail.com", + "role": "Developer" + } + ], + "description": "This plugin is built on top of Spatie's Laravel-health package", + "homepage": "https://github.com/shuvroroy/filament-spatie-laravel-health", + "keywords": [ + "filament", + "filament-spatie-laravel-health", + "laravel-health" + ], + "support": { + "issues": "https://github.com/shuvroroy/filament-spatie-laravel-health/issues", + "source": "https://github.com/shuvroroy/filament-spatie-laravel-health/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://www.buymeacoffee.com/shuvroroy", + "type": "buy_me_a_coffee" + } + ], + "time": "2026-01-17T12:24:55+00:00" + }, + { + "name": "spatie/enum", + "version": "3.13.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/enum.git", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/enum/zipball/f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0" + }, + "require-dev": { + "fakerphp/faker": "^1.9.1", + "larapack/dd": "^1.1", + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "^4.3" + }, + "suggest": { + "fakerphp/faker": "To use the enum faker provider", + "phpunit/phpunit": "To use the enum assertions" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Enum\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev@gummibeer.de", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "PHP Enums", + "homepage": "https://github.com/spatie/enum", + "keywords": [ + "enum", + "enumerable", + "spatie" + ], + "support": { + "docs": "https://docs.spatie.be/enum", + "issues": "https://github.com/spatie/enum/issues", + "source": "https://github.com/spatie/enum" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-04-22T08:51:55+00:00" + }, { "name": "spatie/invade", "version": "2.1.0", @@ -6030,6 +6182,98 @@ ], "time": "2024-05-17T09:06:10+00:00" }, + { + "name": "spatie/laravel-health", + "version": "1.39.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-health.git", + "reference": "b2dd80bd9d3e02639ba40b788aabca5977d3208f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-health/zipball/b2dd80bd9d3e02639ba40b788aabca5977d3208f", + "reference": "b2dd80bd9d3e02639ba40b788aabca5977d3208f", + "shasum": "" + }, + "require": { + "dragonmantank/cron-expression": "^3.3.1", + "guzzlehttp/guzzle": "^6.5|^7.4.5|^7.2", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/notifications": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "laravel/serializable-closure": "^1.3|^2.0", + "nunomaduro/termwind": "^1.0|^2.0", + "php": "^8.2", + "spatie/enum": "^3.13", + "spatie/laravel-package-tools": "^1.12.1", + "spatie/regex": "^3.1.1|^3.1", + "spatie/temporary-directory": "^2.2", + "symfony/process": "^5.4|^6.0|^7.0|^8.0" + }, + "require-dev": { + "laravel/horizon": "^5.9.10", + "nunomaduro/collision": "^5.10|^6.2.1|^6.1|^8.0", + "orchestra/testbench": "^9.0|^10.0|^11.0", + "pestphp/pest": "^2.34|^3.0|^4.0", + "pestphp/pest-plugin-laravel": "^2.3|^3.0|^4.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1.1|^2.0", + "phpunit/phpunit": "^9.5.21|^9.5.10|^10.5|^11.0|^12.0", + "spatie/laravel-ray": "^1.30", + "spatie/pest-plugin-snapshots": "^1.1|^2.3|^3.0", + "spatie/pest-plugin-test-time": "^1.1.1|^1.1|^2.0|^3.0", + "spatie/test-time": "^1.3|^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Health": "Spatie\\Health\\Facades\\Health" + }, + "providers": [ + "Spatie\\Health\\HealthServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Health\\": "src", + "Spatie\\Health\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Monitor the health of a Laravel application", + "homepage": "https://github.com/spatie/laravel-health", + "keywords": [ + "laravel", + "laravel-health", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-health/tree/1.39.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-03-17T20:58:31+00:00" + }, { "name": "spatie/laravel-package-tools", "version": "1.93.0", @@ -6091,6 +6335,69 @@ ], "time": "2026-02-21T12:49:54+00:00" }, + { + "name": "spatie/regex", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/regex.git", + "reference": "d543de2019a0068e7b80da0ba24f1c51c7469303" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/regex/zipball/d543de2019a0068e7b80da0ba24f1c51c7469303", + "reference": "d543de2019a0068e7b80da0ba24f1c51c7469303", + "shasum": "" + }, + "require": { + "php": "^8.0|^8.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Regex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sebastian De Deyne", + "email": "sebastian@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A sane interface for php's built in preg_* functions", + "homepage": "https://github.com/spatie/regex", + "keywords": [ + "expression", + "expressions", + "regex", + "regular", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/regex/issues", + "source": "https://github.com/spatie/regex/tree/3.1.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-11-30T21:13:59+00:00" + }, { "name": "spatie/shiki-php", "version": "2.3.3", @@ -6156,6 +6463,67 @@ ], "time": "2026-02-01T09:30:04+00:00" }, + { + "name": "spatie/temporary-directory", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "662e481d6ec07ef29fd05010433428851a42cd07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/662e481d6ec07ef29fd05010433428851a42cd07", + "reference": "662e481d6ec07ef29fd05010433428851a42cd07", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.3.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-01-12T07:42:22+00:00" + }, { "name": "symfony/clock", "version": "v7.4.0", diff --git a/config/health.php b/config/health.php new file mode 100644 index 0000000..96356fb --- /dev/null +++ b/config/health.php @@ -0,0 +1,164 @@ + [ + Spatie\Health\ResultStores\EloquentHealthResultStore::class => [ + 'connection' => env('HEALTH_DB_CONNECTION', env('DB_CONNECTION')), + 'model' => Spatie\Health\Models\HealthCheckResultHistoryItem::class, + 'keep_history_for_days' => 5, + ], + + /* + Spatie\Health\ResultStores\CacheHealthResultStore::class => [ + 'store' => 'file', + ], + + Spatie\Health\ResultStores\JsonFileHealthResultStore::class => [ + 'disk' => 's3', + 'path' => 'health.json', + ], + + Spatie\Health\ResultStores\InMemoryHealthResultStore::class, + */ + ], + + /* + * You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'. + * For Slack you need to install laravel/slack-notification-channel. + */ + 'notifications' => [ + /* + * Notifications will only get sent if this option is set to `true`. + */ + 'enabled' => true, + + 'notifications' => [ + Spatie\Health\Notifications\CheckFailedNotification::class => ['mail'], + ], + + /* + * Here you can specify the notifiable to which the notifications should be sent. The default + * notifiable will use the variables specified in this config file. + */ + 'notifiable' => Spatie\Health\Notifications\Notifiable::class, + + /* + * When checks start failing, you could potentially end up getting + * a notification every minute. + * + * With this setting, notifications are throttled. By default, you'll + * only get one notification per hour. + */ + 'throttle_notifications_for_minutes' => 60, + 'throttle_notifications_key' => 'health:latestNotificationSentAt:', + + /* + * When set to true, notifications will only be sent when at least one + * check has a 'failed' status. Warnings will be ignored. + */ + 'only_on_failure' => false, + + 'mail' => [ + 'to' => 'your@example.com', + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + ], + + 'slack' => [ + 'webhook_url' => env('HEALTH_SLACK_WEBHOOK_URL', ''), + + /* + * If this is set to null the default channel of the webhook will be used. + */ + 'channel' => null, + + 'username' => null, + + 'icon' => null, + ], + ], + + /* + * You can let Oh Dear monitor the results of all health checks. This way, you'll + * get notified of any problems even if your application goes totally down. Via + * Oh Dear, you can also have access to more advanced notification options. + */ + 'oh_dear_endpoint' => [ + 'enabled' => false, + + /* + * When this option is enabled, the checks will run before sending a response. + * Otherwise, we'll send the results from the last time the checks have run. + */ + 'always_send_fresh_results' => true, + + /* + * The secret that is displayed at the Application Health settings at Oh Dear. + */ + 'secret' => env('OH_DEAR_HEALTH_CHECK_SECRET'), + + /* + * The URL that should be configured in the Application health settings at Oh Dear. + */ + 'url' => '/oh-dear-health-check-results', + ], + + /* + * You can specify a heartbeat URL for the Horizon check. + * This URL will be pinged if the Horizon check is successful. + * This way you can get notified if Horizon goes down. + */ + 'horizon' => [ + 'heartbeat_url' => env('HORIZON_HEARTBEAT_URL'), + ], + + /* + * You can specify a heartbeat URL for the Schedule check. + * This URL will be pinged if the Schedule check is successful. + * This way you can get notified if the schedule fails to run. + */ + 'schedule' => [ + 'heartbeat_url' => env('SCHEDULE_HEARTBEAT_URL'), + ], + + /* + * You can set a theme for the local results page + * + * - light: light mode + * - dark: dark mode + */ + 'theme' => 'light', + + /* + * When enabled, completed `HealthQueueJob`s will be displayed + * in Horizon's silenced jobs screen. + */ + 'silence_health_queue_job' => true, + + /* + * The response code to use for HealthCheckJsonResultsController when a health + * check has failed + */ + 'json_results_failure_status' => 200, + + /* + * You can specify a secret token that needs to be sent in the X-Secret-Token for secured access. + */ + 'secret_token' => env('HEALTH_SECRET_TOKEN'), + +/** + * By default, conditionally skipped health checks are treated as failures. + * You can override this behavior by uncommenting the configuration below. + * + * @link https://spatie.be/docs/laravel-health/v1/basic-usage/conditionally-running-or-modifying-checks + */ + // 'treat_skipped_as_failure' => false +]; diff --git a/database/migrations/2026_03_18_110246_create_health_tables.php b/database/migrations/2026_03_18_110246_create_health_tables.php new file mode 100644 index 0000000..5d8ebff --- /dev/null +++ b/database/migrations/2026_03_18_110246_create_health_tables.php @@ -0,0 +1,36 @@ +getConnectionName(); + $tableName = EloquentHealthResultStore::getHistoryItemInstance()->getTable(); + + Schema::connection($connection)->create($tableName, function (Blueprint $table) { + $table->id(); + + $table->string('check_name'); + $table->string('check_label'); + $table->string('status'); + $table->text('notification_message')->nullable(); + $table->string('short_summary')->nullable(); + $table->json('meta'); + $table->timestamp('ended_at'); + $table->uuid('batch'); + + $table->timestamps(); + }); + + Schema::connection($connection)->table($tableName, function (Blueprint $table) { + $table->index('created_at'); + $table->index('batch'); + }); + } +}; diff --git a/public/css/filament-spatie-health/filament-spatie-health-styles.css b/public/css/filament-spatie-health/filament-spatie-health-styles.css new file mode 100644 index 0000000..81f0bfd --- /dev/null +++ b/public/css/filament-spatie-health/filament-spatie-health-styles.css @@ -0,0 +1 @@ +.filament-spatie-health .grid{display:grid}.filament-spatie-health .grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@media (min-width:640px){.filament-spatie-health .sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.filament-spatie-health .lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}.filament-spatie-health .gap-6{gap:1.5rem}.filament-spatie-health .mb-5{margin-bottom:1.25rem}.filament-spatie-health .text-danger-400{color:oklch(.704 .191 22.216)}.filament-spatie-health .text-center{text-align:center}