# Feature Management System - Complete Implementation Guide ## Overview This system provides individual pages for each monitoring feature with: - Feature activation/deactivation per site - Feature-specific information and documentation - Historical data visualization with charts - Progress tracking over time - Feature-specific settings ## Database Schema ### Migration Created `database/migrations/2026_01_18_123956_create_site_features_table.php` **Fields:** - `site_id` - Foreign key to sites table - `feature_type` - Type of feature (uptime, ssl, dns, etc.) - `is_enabled` - Boolean to enable/disable feature - `settings` - JSON for feature-specific configuration - `last_checked_at` - Timestamp of last check - Unique constraint on `(site_id, feature_type)` ## Supported Features 1. **Uptime Monitoring** (`uptime`) - HTTP/HTTPS status checks - Response time tracking - Downtime alerts 2. **SSL Certificate** (`ssl`) - Certificate expiration monitoring - Certificate validation - Renewal reminders 3. **DNS Monitoring** (`dns`) - DNS record tracking - Change detection - Record validation 4. **Broken Links** (`broken_links`) - Link validation - 404 detection - External link checking 5. **Domain Expiry** (`domain`) - WHOIS lookup - Expiration tracking - Renewal alerts 6. **Sitemap** (`sitemap`) - XML validation - URL counting - Structure validation 7. **Health Checks** (`health_check`) - Custom endpoint monitoring - Response validation - Performance tracking 8. **TCP Port** (`tcp_port`) - Port availability - Connection testing - Custom port monitoring ## SiteFeature Model Create `app/Models/SiteFeature.php`: ```php 'boolean', 'settings' => 'array', 'last_checked_at' => 'datetime', ]; public function site(): BelongsTo { return $this->belongsTo(Site::class); } /** * Get feature configuration */ public static function getAvailableFeatures(): array { return [ 'uptime' => [ 'name' => 'Uptime Monitoring', 'description' => 'Monitor your site availability and response times', 'icon' => 'bi-arrow-up-circle', 'color' => 'primary', 'metrics' => ['uptime_percentage', 'avg_response_time', 'total_checks'], ], 'ssl' => [ 'name' => 'SSL Certificate', 'description' => 'Track SSL certificate expiration and validity', 'icon' => 'bi-shield-lock', 'color' => 'success', 'metrics' => ['days_until_expiry', 'is_valid', 'last_check'], ], 'dns' => [ 'name' => 'DNS Monitoring', 'description' => 'Monitor DNS records and detect changes', 'icon' => 'bi-server', 'color' => 'info', 'metrics' => ['total_records', 'last_change', 'record_types'], ], 'broken_links' => [ 'name' => 'Broken Links', 'description' => 'Scan for broken links and 404 errors', 'icon' => 'bi-link-45deg', 'color' => 'warning', 'metrics' => ['broken_count', 'total_links', 'last_scan'], ], 'domain' => [ 'name' => 'Domain Expiry', 'description' => 'Track domain registration expiration', 'icon' => 'bi-globe', 'color' => 'danger', 'metrics' => ['days_until_expiry', 'expiry_date', 'registrar'], ], 'sitemap' => [ 'name' => 'Sitemap Monitoring', 'description' => 'Validate sitemap structure and accessibility', 'icon' => 'bi-diagram-3', 'color' => 'secondary', 'metrics' => ['is_valid', 'url_count', 'last_validated'], ], 'health_check' => [ 'name' => 'Health Checks', 'description' => 'Monitor custom health endpoints', 'icon' => 'bi-heart-pulse', 'color' => 'danger', 'metrics' => ['status', 'response_time', 'success_rate'], ], 'tcp_port' => [ 'name' => 'TCP Port Monitoring', 'description' => 'Monitor TCP port availability', 'icon' => 'bi-hdd-network', 'color' => 'dark', 'metrics' => ['open_ports', 'closed_ports', 'total_ports'], ], ]; } /** * Get feature info */ public function getFeatureInfo(): array { $features = self::getAvailableFeatures(); return $features[$this->feature_type] ?? []; } /** * Toggle feature status */ public function toggle(): void { $this->update(['is_enabled' => !$this->is_enabled]); } } ``` ## Update Site Model Add to `app/Models/Site.php`: ```php use App\Models\SiteFeature; public function features(): HasMany { return $this->hasMany(SiteFeature::class); } /** * Check if feature is enabled */ public function hasFeature(string $featureType): bool { return $this->features() ->where('feature_type', $featureType) ->where('is_enabled', true) ->exists(); } /** * Get or create feature */ public function getOrCreateFeature(string $featureType): SiteFeature { return $this->features()->firstOrCreate( ['feature_type' => $featureType], ['is_enabled' => false] ); } /** * Enable feature */ public function enableFeature(string $featureType, array $settings = []): SiteFeature { $feature = $this->getOrCreateFeature($featureType); $feature->update([ 'is_enabled' => true, 'settings' => array_merge($feature->settings ?? [], $settings), ]); return $feature; } /** * Disable feature */ public function disableFeature(string $featureType): void { $this->features() ->where('feature_type', $featureType) ->update(['is_enabled' => false]); } ``` ## FeaturesController Create `app/Http/Controllers/FeaturesController.php`: ```php id()) ->with('features') ->get(); $availableFeatures = SiteFeature::getAvailableFeatures(); return view('features.index', compact('sites', 'availableFeatures')); } /** * Show specific feature details */ public function show(string $featureType) { $availableFeatures = SiteFeature::getAvailableFeatures(); if (!isset($availableFeatures[$featureType])) { abort(404); } $featureInfo = $availableFeatures[$featureType]; $sites = Site::where('user_id', auth()->id()) ->with(['features' => function ($query) use ($featureType) { $query->where('feature_type', $featureType); }]) ->get(); // Get historical data for charts $chartData = $this->getChartData($featureType, $sites); return view('features.show', compact('featureType', 'featureInfo', 'sites', 'chartData')); } /** * Toggle feature for a site */ public function toggle(Request $request, Site $site, string $featureType) { $this->authorize('update', $site); $feature = $site->getOrCreateFeature($featureType); $feature->toggle(); return back()->with('success', 'Feature ' . ($feature->is_enabled ? 'enabled' : 'disabled') . ' successfully.'); } /** * Update feature settings */ public function updateSettings(Request $request, Site $site, string $featureType) { $this->authorize('update', $site); $validated = $request->validate([ 'settings' => 'required|array', ]); $feature = $site->getOrCreateFeature($featureType); $feature->update(['settings' => $validated['settings']]); return back()->with('success', 'Feature settings updated successfully.'); } /** * Get chart data for a feature */ protected function getChartData(string $featureType, $sites) { $chartData = []; foreach ($sites as $site) { $data = match ($featureType) { 'uptime' => $this->getUptimeChartData($site), 'ssl' => $this->getSslChartData($site), 'dns' => $this->getDnsChartData($site), 'broken_links' => $this->getBrokenLinksChartData($site), 'domain' => $this->getDomainChartData($site), 'sitemap' => $this->getSitemapChartData($site), 'health_check' => $this->getHealthCheckChartData($site), 'tcp_port' => $this->getTcpPortChartData($site), default => [], }; if (!empty($data)) { $chartData[$site->id] = $data; } } return $chartData; } // Chart data methods for each feature type protected function getUptimeChartData(Site $site): array { $checks = $site->uptimeChecks() ->where('created_at', '>=', now()->subDays(30)) ->orderBy('created_at') ->get(); return [ 'labels' => $checks->pluck('created_at')->map(fn($date) => $date->format('M d'))->toArray(), 'datasets' => [ [ 'label' => 'Response Time (ms)', 'data' => $checks->pluck('response_time')->toArray(), 'borderColor' => 'rgb(75, 192, 192)', 'tension' => 0.1, ], [ 'label' => 'Status Code', 'data' => $checks->pluck('status_code')->toArray(), 'borderColor' => 'rgb(255, 99, 132)', 'tension' => 0.1, ], ], ]; } protected function getSslChartData(Site $site): array { $certs = $site->sslCertificates() ->where('created_at', '>=', now()->subDays(90)) ->orderBy('created_at') ->get(); return [ 'labels' => $certs->pluck('created_at')->map(fn($date) => $date->format('M d'))->toArray(), 'datasets' => [ [ 'label' => 'Days Until Expiry', 'data' => $certs->map(function ($cert) { return $cert->expires_at ? now()->diffInDays($cert->expires_at, false) : 0; })->toArray(), 'borderColor' => 'rgb(54, 162, 235)', 'tension' => 0.1, ], ], ]; } protected function getBrokenLinksChartData(Site $site): array { $links = $site->brokenLinks() ->selectRaw('DATE(created_at) as date, COUNT(*) as count') ->where('created_at', '>=', now()->subDays(30)) ->groupBy('date') ->orderBy('date') ->get(); return [ 'labels' => $links->pluck('date')->toArray(), 'datasets' => [ [ 'label' => 'Broken Links Found', 'data' => $links->pluck('count')->toArray(), 'backgroundColor' => 'rgba(255, 99, 132, 0.2)', 'borderColor' => 'rgb(255, 99, 132)', 'tension' => 0.1, ], ], ]; } // Add similar methods for other features... } ``` ## Routes Add to `routes/web.php`: ```php Route::middleware('auth')->group(function () { // Feature management Route::get('/features', [FeaturesController::class, 'index'])->name('features.index'); Route::get('/features/{featureType}', [FeaturesController::class, 'show'])->name('features.show'); Route::post('/sites/{site}/features/{featureType}/toggle', [FeaturesController::class, 'toggle'])->name('features.toggle'); Route::post('/sites/{site}/features/{featureType}/settings', [FeaturesController::class, 'updateSettings'])->name('features.settings'); }); ``` ## Views ### Features Overview Page Create `resources/views/features/index.blade.php`: ```blade @extends('layouts.app') @section('title', 'Features Overview') @section('content')
Manage monitoring features for your sites. Enable or disable features as needed.
{{ $feature['description'] }}
| Site | @foreach($availableFeatures as $type => $feature)@endforeach |
|---|---|
|
{{ $site->name }} {{ $site->url }} |
@foreach(array_keys($availableFeatures) as $type)
@php
$feature = $site->features->where('feature_type', $type)->first();
$isEnabled = $feature && $feature->is_enabled;
@endphp
@endforeach |