From 0d8df427b77f227da5d402fd586b0ab640a73f25 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Tue, 21 Jan 2025 20:59:58 +0000 Subject: [PATCH] Work on component monitoring --- ..._checked_at_column_to_components_table.php | 32 ++++++++ resources/lang/en/component.php | 2 + src/CachetCoreServiceProvider.php | 1 + src/Commands/CheckComponentsCommand.php | 79 +++++++++++++++++++ src/Filament/Resources/ComponentResource.php | 7 ++ src/Models/Component.php | 14 ++++ 6 files changed, 135 insertions(+) create mode 100644 database/migrations/2025_01_21_171353_add_checked_at_column_to_components_table.php create mode 100644 src/Commands/CheckComponentsCommand.php diff --git a/database/migrations/2025_01_21_171353_add_checked_at_column_to_components_table.php b/database/migrations/2025_01_21_171353_add_checked_at_column_to_components_table.php new file mode 100644 index 00000000..16828dde --- /dev/null +++ b/database/migrations/2025_01_21_171353_add_checked_at_column_to_components_table.php @@ -0,0 +1,32 @@ +boolean('checked')->default(false)->after('enabled'); + $table->timestamp('checked_at')->nullable()->after('checked'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('components', function (Blueprint $table) { + $table->dropColumn([ + 'checked', + 'checked_at', + ]); + }); + } +}; diff --git a/resources/lang/en/component.php b/resources/lang/en/component.php index 38058faf..274c88b9 100644 --- a/resources/lang/en/component.php +++ b/resources/lang/en/component.php @@ -10,6 +10,7 @@ 'group' => 'Group', 'enabled' => 'Enabled', 'created_at' => 'Created at', + 'checked_at' => 'Checked at', 'updated_at' => 'Updated at', 'deleted_at' => 'Deleted at', ], @@ -27,6 +28,7 @@ 'component_group_label' => 'Component Group', 'link_label' => 'Link', 'link_helper' => 'An optional link to the component.', + 'checked_label' => 'Whether to periodically check the component.', ], 'status' => [ 'operational' => 'Operational', diff --git a/src/CachetCoreServiceProvider.php b/src/CachetCoreServiceProvider.php index d8dd5da0..ada354c9 100644 --- a/src/CachetCoreServiceProvider.php +++ b/src/CachetCoreServiceProvider.php @@ -169,6 +169,7 @@ private function registerCommands(): void { if ($this->app->runningInConsole()) { $this->commands([ + Commands\CheckComponentsCommand::class, Commands\MakeUserCommand::class, Commands\SendBeaconCommand::class, Commands\VersionCommand::class, diff --git a/src/Commands/CheckComponentsCommand.php b/src/Commands/CheckComponentsCommand.php new file mode 100644 index 00000000..6ac007c6 --- /dev/null +++ b/src/Commands/CheckComponentsCommand.php @@ -0,0 +1,79 @@ +enabled() + ->checked() + ->whereNotNull('link') + ->get() + ->each(function (Component $component) { + $attempts = 1; + + try { + $response = Http::withUserAgent(Cachet::USER_AGENT) + ->retry(3, function (int $attempt) use (&$attempts): int { + $attempts = $attempt; + + return $attempt * 100; + }) + ->timeout(3) + ->get($component->link); + } catch (RequestException|ConnectionException $e) { + $status = match (true) { + $e->getCode() >= 400 => ComponentStatusEnum::partial_outage, + $e->getCode() >= 500 => ComponentStatusEnum::major_outage, + default => ComponentStatusEnum::partial_outage, + }; + + $component->update([ + 'status' => $status, + 'checked_at' => now(), + ]); + + return; + } + + $status = match (true) { + $response->successful() && $attempts === 1 => ComponentStatusEnum::operational, + $response->successful() && $attempts > 1 => ComponentStatusEnum::performance_issues, + $response->status() >= 400 => ComponentStatusEnum::partial_outage, + default => ComponentStatusEnum::operational, + }; + + $component->update([ + 'status' => $status, + 'checked_at' => now(), + ]); + }); + } +} diff --git a/src/Filament/Resources/ComponentResource.php b/src/Filament/Resources/ComponentResource.php index ae3def4d..5fc92e19 100644 --- a/src/Filament/Resources/ComponentResource.php +++ b/src/Filament/Resources/ComponentResource.php @@ -46,6 +46,8 @@ public static function form(Form $form): Form ->label(__('cachet::component.form.link_label')) ->url() ->label(__('cachet::component.form.link_helper')), + Forms\Components\Toggle::make('checked') + ->label(__('cachet::component.form.checked_label')), ]), Forms\Components\Section::make()->columns(2)->schema([ @@ -86,6 +88,11 @@ public static function table(Table $table): Table ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('checked_at') + ->label(__('cachet::component.list.headers.checked_at')) + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->label(__('cachet::component.list.headers.updated_at')) ->dateTime() diff --git a/src/Models/Component.php b/src/Models/Component.php index 657ea2c7..ad58db5b 100644 --- a/src/Models/Component.php +++ b/src/Models/Component.php @@ -26,6 +26,8 @@ * @property ComponentStatusEnum $latest_status * @property ?int $order * @property ?int $component_group_id + * @property ?bool $checked + * @property ?Carbon $checked_at * @property ?Carbon $created_at * @property ?Carbon $updated_at * @property ?Carbon $deleted_at @@ -33,6 +35,7 @@ * @property array $meta * @property ?ComponentGroup $componentGroup * + * @method static Builder|static checked() * @method static Builder|static disabled() * @method static Builder|static enabled() * @method static Builder|static outage() @@ -48,6 +51,7 @@ class Component extends Model /** @var array */ protected $casts = [ + 'checked' => 'bool', 'status' => ComponentStatusEnum::class, 'order' => 'int', 'enabled' => 'bool', @@ -64,6 +68,8 @@ class Component extends Model 'component_group_id', 'enabled', 'meta', + 'checked', + 'checked_at', ]; protected $dispatchesEvents = [ @@ -101,6 +107,14 @@ public function subscribers(): BelongsToMany return $this->belongsToMany(Subscriber::class, 'subscriptions'); } + /** + * Scope to checked components only. + */ + public function scopeChecked(Builder $query): void + { + $query->where('checked', true); + } + /** * Scope to disabled components only. */