'array', 'response_data' => 'array', 'last_checked_at' => 'datetime', 'is_healthy' => 'boolean', 'is_active' => 'boolean', ]; /** * Get the site that owns the health check. */ public function site(): BelongsTo { return $this->belongsTo(Site::class); } /** * Scope to filter active health checks. */ public function scopeActive($query) { return $query->where('is_active', true); } /** * Scope to filter healthy checks. */ public function scopeHealthy($query) { return $query->where('is_healthy', true); } /** * Scope to filter unhealthy checks. */ public function scopeUnhealthy($query) { return $query->where('is_healthy', false); } /** * Get the full URL for the health check endpoint. */ public function getFullUrlAttribute(): string { $siteUrl = rtrim($this->site->url, '/'); $endpoint = ltrim($this->endpoint, '/'); return $siteUrl . '/' . $endpoint; } /** * Get status badge color. */ public function getStatusColorAttribute(): string { if ($this->is_healthy === null) { return 'secondary'; } return $this->is_healthy ? 'success' : 'danger'; } /** * Get status text. */ public function getStatusTextAttribute(): string { if ($this->is_healthy === null) { return 'Unknown'; } return $this->is_healthy ? 'Healthy' : 'Unhealthy'; } /** * Get display name for the endpoint. */ public function getDisplayNameAttribute(): string { return $this->endpoint ?: 'Health Check'; } /** * Validate if response contains all expected keys. */ public function validateResponseKeys(array $responseData): bool { if (empty($this->expected_json_keys)) { return true; } foreach ($this->expected_json_keys as $key) { if (!$this->hasNestedKey($responseData, $key)) { return false; } } return true; } /** * Check if nested key exists in array using dot notation. */ protected function hasNestedKey(array $array, string $key): bool { if (array_key_exists($key, $array)) { return true; } foreach (explode('.', $key) as $segment) { if (!is_array($array) || !array_key_exists($segment, $array)) { return false; } $array = $array[$segment]; } return true; } }