*/ use HasFactory; /** * The attributes that are mass assignable. * * @var list */ protected $fillable = [ 'user_id', 'team_id', 'project_id', 'name', 'url', 'check_type', 'assertion_type', 'assertion_value', 'is_active', 'status', 'check_interval', 'last_checked_at', 'next_run_at', 'last_error', 'consecutive_failures', 'failure_threshold', 'last_decided_round_id', 'cert_expires_at', 'cert_issuer', 'cert_alerted_threshold_days', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'is_active' => 'boolean', 'last_checked_at' => 'datetime', 'next_run_at' => 'datetime', 'cert_expires_at' => 'datetime', 'check_type' => CheckType::class, 'assertion_type' => AssertionType::class, ]; } /** * Whether a response-body assertion is configured on this monitor. */ public function hasAssertion(): bool { return $this->check_type === CheckType::Http && $this->assertion_type !== null && $this->assertion_type !== AssertionType::None && $this->assertion_value !== null && $this->assertion_value !== ''; } /** * Days until the TLS certificate expires (negative when already expired), * or null when no certificate has been observed. */ public function certDaysRemaining(): ?int { if ($this->cert_expires_at === null) { return null; } return (int) floor(now()->diffInDays($this->cert_expires_at, false)); } /** * The notification channels alerts for this monitor should go to: * the monitor's attached active channels, falling back to the owner's * default channel (legacy monitors with none attached). * * @return \Illuminate\Support\Collection */ public function alertChannels(): \Illuminate\Support\Collection { $channels = $this->notificationChannels() ->where('is_active', true) ->get() ->filter(fn (NotificationChannel $channel) => $channel->isConfigured()) ->values(); if ($channels->isNotEmpty()) { return $channels->toBase(); } $default = $this->user?->defaultNotificationChannel(); return $default && $default->isConfigured() ? collect([$default]) : collect(); } /** * Get the user that owns the monitor */ public function user(): BelongsTo { return $this->belongsTo(User::class); } /** * Get the team that owns the monitor (optional) */ public function team(): BelongsTo { return $this->belongsTo(Team::class); } /** * Get the project this monitor belongs to (optional) */ public function project(): BelongsTo { return $this->belongsTo(Project::class); } /** * Return the effective team for access control. * * When a monitor is inside a project, the project's team wins over * the monitor's own team_id. Standalone monitors fall back to team_id. */ public function effectiveTeam(): ?Team { if ($this->project_id) { return $this->project?->team; } return $this->team; } /** * Return the effective owner user_id for access control. * * When a monitor is inside a project, the project owner governs. */ public function effectiveUserId(): int { if ($this->project_id && $this->project) { return $this->project->user_id; } return $this->user_id; } /** * Check if the monitor is currently up */ public function isUp(): bool { return $this->status === 'up'; } /** * Check if the monitor is currently down */ public function isDown(): bool { return $this->status === 'down'; } /** * Check if the monitor is pending first check */ public function isPending(): bool { return $this->status === 'pending'; } /** * Get all check results for this monitor */ public function checkResults(): HasMany { return $this->hasMany(CheckResult::class); } /** * Get the latest check result for this monitor */ public function latestCheckResult(): HasOne { return $this->hasOne(CheckResult::class)->latestOfMany(); } /** * Get all incidents (downtimes) for this monitor */ public function incidents(): HasMany { return $this->hasMany(Incident::class); } /** * Get the currently ongoing incident, if any */ public function ongoingIncident(): HasOne { return $this->hasOne(Incident::class)->whereNull('ended_at')->latestOfMany('started_at'); } /** * Notification channels selected for this monitor */ public function notificationChannels(): BelongsToMany { return $this->belongsToMany(NotificationChannel::class); } }