518 lines
18 KiB
Markdown
518 lines
18 KiB
Markdown
# Feature Pages Implementation - Complete Views & Charts
|
|
|
|
## Individual Feature Detail Page Template
|
|
|
|
Create `resources/views/features/show.blade.php`:
|
|
|
|
```blade
|
|
@extends('layouts.app')
|
|
|
|
@section('title', $featureInfo['name'])
|
|
|
|
@section('content')
|
|
<div class="container">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1>
|
|
<i class="{{ $featureInfo['icon'] }} text-{{ $featureInfo['color'] }}"></i>
|
|
{{ $featureInfo['name'] }}
|
|
</h1>
|
|
<p class="lead text-muted">{{ $featureInfo['description'] }}</p>
|
|
</div>
|
|
<a href="{{ route('features.index') }}" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left"></i> Back to Features
|
|
</a>
|
|
</div>
|
|
|
|
@if(session('success'))
|
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
<i class="bi bi-check-circle"></i> {{ session('success') }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Feature Statistics -->
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card bg-{{ $featureInfo['color'] }} bg-opacity-10">
|
|
<div class="card-body text-center">
|
|
<i class="{{ $featureInfo['icon'] }} fs-1 text-{{ $featureInfo['color'] }}"></i>
|
|
<h3 class="mt-2">{{ $sites->sum(fn($s) => $s->features->where('is_enabled', true)->count()) }}</h3>
|
|
<p class="text-muted mb-0">Active Sites</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="bi bi-check-circle fs-1 text-success"></i>
|
|
<h3 class="mt-2" id="healthy-count">0</h3>
|
|
<p class="text-muted mb-0">Healthy</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="bi bi-exclamation-triangle fs-1 text-warning"></i>
|
|
<h3 class="mt-2" id="warning-count">0</h3>
|
|
<p class="text-muted mb-0">Warnings</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="bi bi-clock-history fs-1 text-info"></i>
|
|
<h3 class="mt-2">{{ now()->format('H:i') }}</h3>
|
|
<p class="text-muted mb-0">Last Updated</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section -->
|
|
@if(!empty($chartData))
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="bi bi-graph-up"></i> Historical Data & Trends</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
@foreach($chartData as $siteId => $data)
|
|
@php
|
|
$site = $sites->find($siteId);
|
|
@endphp
|
|
<div class="col-lg-6 mb-4">
|
|
<h6>{{ $site->name }}</h6>
|
|
<canvas id="chart-{{ $siteId }}" height="200"></canvas>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Sites Management -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="bi bi-list-ul"></i> Manage Sites</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Site</th>
|
|
<th>Status</th>
|
|
<th>Last Checked</th>
|
|
<th>Metrics</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($sites as $site)
|
|
@php
|
|
$feature = $site->features->where('feature_type', $featureType)->first();
|
|
$isEnabled = $feature && $feature->is_enabled;
|
|
@endphp
|
|
<tr>
|
|
<td>
|
|
<strong>{{ $site->name }}</strong><br>
|
|
<small class="text-muted">{{ $site->url }}</small>
|
|
</td>
|
|
<td>
|
|
<form action="{{ route('features.toggle', [$site, $featureType]) }}" method="POST" class="d-inline">
|
|
@csrf
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" role="switch"
|
|
{{ $isEnabled ? 'checked' : '' }}
|
|
onchange="this.form.submit()">
|
|
<label class="form-check-label">
|
|
{{ $isEnabled ? 'Enabled' : 'Disabled' }}
|
|
</label>
|
|
</div>
|
|
</form>
|
|
</td>
|
|
<td>
|
|
@if($feature && $feature->last_checked_at)
|
|
{{ $feature->last_checked_at->diffForHumans() }}
|
|
@else
|
|
<span class="text-muted">Never</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
@include('features.partials.metrics-' . $featureType, ['site' => $site])
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{{ route('dashboard.show', $site) }}" class="btn btn-outline-primary" title="Dashboard">
|
|
<i class="bi bi-bar-chart"></i>
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary" title="Settings"
|
|
data-bs-toggle="modal" data-bs-target="#settings-{{ $site->id }}">
|
|
<i class="bi bi-gear"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Feature Information -->
|
|
<div class="card mt-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="bi bi-info-circle"></i> About {{ $featureInfo['name'] }}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
@include('features.info.' . $featureType)
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Modals -->
|
|
@foreach($sites as $site)
|
|
<div class="modal fade" id="settings-{{ $site->id }}" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{{ $site->name }} - Feature Settings</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form action="{{ route('features.settings', [$site, $featureType]) }}" method="POST">
|
|
@csrf
|
|
<div class="modal-body">
|
|
@include('features.settings.' . $featureType, ['site' => $site])
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Save Settings</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const chartData = @json($chartData);
|
|
|
|
Object.keys(chartData).forEach(siteId => {
|
|
const ctx = document.getElementById('chart-' + siteId);
|
|
if (ctx) {
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: chartData[siteId],
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
@endpush
|
|
@endsection
|
|
```
|
|
|
|
## Feature-Specific Metrics Partials
|
|
|
|
Create `resources/views/features/partials/metrics-uptime.blade.php`:
|
|
|
|
```blade
|
|
@php
|
|
$latestCheck = $site->uptimeChecks()->latest()->first();
|
|
$uptimePercentage = $site->uptimeChecks()
|
|
->where('created_at', '>=', now()->subDays(30))
|
|
->avg('is_online') * 100;
|
|
@endphp
|
|
|
|
<div class="small">
|
|
@if($latestCheck)
|
|
<div class="mb-1">
|
|
<span class="badge bg-{{ $latestCheck->is_online ? 'success' : 'danger' }}">
|
|
{{ $latestCheck->is_online ? 'Online' : 'Offline' }}
|
|
</span>
|
|
</div>
|
|
<div class="text-muted">
|
|
Uptime: {{ number_format($uptimePercentage, 1) }}%
|
|
</div>
|
|
<div class="text-muted">
|
|
Response: {{ number_format($latestCheck->response_time, 0) }}ms
|
|
</div>
|
|
@else
|
|
<span class="text-muted">No data</span>
|
|
@endif
|
|
</div>
|
|
```
|
|
|
|
Create `resources/views/features/partials/metrics-ssl.blade.php`:
|
|
|
|
```blade
|
|
@php
|
|
$latestCert = $site->sslCertificates()->latest()->first();
|
|
@endphp
|
|
|
|
<div class="small">
|
|
@if($latestCert)
|
|
@php
|
|
$daysLeft = now()->diffInDays($latestCert->expires_at, false);
|
|
@endphp
|
|
<div class="mb-1">
|
|
<span class="badge bg-{{ $daysLeft > 30 ? 'success' : ($daysLeft > 7 ? 'warning' : 'danger') }}">
|
|
{{ $daysLeft }} days left
|
|
</span>
|
|
</div>
|
|
<div class="text-muted">
|
|
Expires: {{ $latestCert->expires_at->format('M d, Y') }}
|
|
</div>
|
|
@else
|
|
<span class="text-muted">No certificate data</span>
|
|
@endif
|
|
</div>
|
|
```
|
|
|
|
Create `resources/views/features/partials/metrics-broken_links.blade.php`:
|
|
|
|
```blade
|
|
@php
|
|
$brokenCount = $site->brokenLinks()->count();
|
|
$totalLinks = $site->brokenLinks()->distinct('url')->count();
|
|
@endphp
|
|
|
|
<div class="small">
|
|
<div class="mb-1">
|
|
<span class="badge bg-{{ $brokenCount > 0 ? 'danger' : 'success' }}">
|
|
{{ $brokenCount }} broken
|
|
</span>
|
|
</div>
|
|
@if($totalLinks > 0)
|
|
<div class="text-muted">
|
|
Total links scanned: {{ $totalLinks }}
|
|
</div>
|
|
@else
|
|
<span class="text-muted">No scan data</span>
|
|
@endif
|
|
</div>
|
|
```
|
|
|
|
## Feature Information Pages
|
|
|
|
Create `resources/views/features/info/uptime.blade.php`:
|
|
|
|
```blade
|
|
<h6>What is Uptime Monitoring?</h6>
|
|
<p>
|
|
Uptime monitoring checks if your website is accessible and responding correctly.
|
|
It performs regular HTTP/HTTPS requests and tracks:
|
|
</p>
|
|
<ul>
|
|
<li>Site availability (up/down status)</li>
|
|
<li>HTTP response codes</li>
|
|
<li>Response times</li>
|
|
<li>SSL certificate validity</li>
|
|
</ul>
|
|
|
|
<h6 class="mt-3">How It Works</h6>
|
|
<ol>
|
|
<li>Automated checks run every 5 minutes</li>
|
|
<li>Your site is accessed from our servers</li>
|
|
<li>Response time and status are recorded</li>
|
|
<li>Alerts are sent if your site is down</li>
|
|
</ol>
|
|
|
|
<h6 class="mt-3">Metrics Tracked</h6>
|
|
<ul>
|
|
<li><strong>Uptime Percentage:</strong> Overall availability over the last 30 days</li>
|
|
<li><strong>Response Time:</strong> How fast your site responds (in milliseconds)</li>
|
|
<li><strong>Status Codes:</strong> HTTP response codes (200, 404, 500, etc.)</li>
|
|
</ul>
|
|
|
|
<div class="alert alert-info mt-3">
|
|
<i class="bi bi-lightbulb"></i>
|
|
<strong>Tip:</strong> A healthy website should maintain 99%+ uptime and respond in under 1000ms.
|
|
</div>
|
|
```
|
|
|
|
Create `resources/views/features/info/ssl.blade.php`:
|
|
|
|
```blade
|
|
<h6>What is SSL Certificate Monitoring?</h6>
|
|
<p>
|
|
SSL certificate monitoring ensures your website's security certificate is valid and hasn't expired.
|
|
This is crucial for:
|
|
</p>
|
|
<ul>
|
|
<li>Maintaining visitor trust</li>
|
|
<li>SEO rankings (Google prefers HTTPS)</li>
|
|
<li>Data encryption and security</li>
|
|
<li>Browser compatibility</li>
|
|
</ul>
|
|
|
|
<h6 class="mt-3">What We Check</h6>
|
|
<ul>
|
|
<li>Certificate expiration date</li>
|
|
<li>Certificate validity</li>
|
|
<li>Issuer information</li>
|
|
<li>Certificate chain completeness</li>
|
|
</ul>
|
|
|
|
<h6 class="mt-3">Alert Thresholds</h6>
|
|
<ul>
|
|
<li><strong class="text-danger">7 days:</strong> Critical alert</li>
|
|
<li><strong class="text-warning">30 days:</strong> Warning alert</li>
|
|
<li><strong class="text-info">60 days:</strong> Info notification</li>
|
|
</ul>
|
|
|
|
<div class="alert alert-warning mt-3">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
<strong>Important:</strong> Expired SSL certificates will cause browser warnings and prevent visitors from accessing your site.
|
|
</div>
|
|
```
|
|
|
|
## Feature Settings Forms
|
|
|
|
Create `resources/views/features/settings/uptime.blade.php`:
|
|
|
|
```blade
|
|
<div class="mb-3">
|
|
<label class="form-label">Check Interval</label>
|
|
<select name="settings[interval]" class="form-select">
|
|
<option value="5">Every 5 minutes</option>
|
|
<option value="10">Every 10 minutes</option>
|
|
<option value="15">Every 15 minutes</option>
|
|
<option value="30">Every 30 minutes</option>
|
|
<option value="60">Every hour</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Response Time Threshold (ms)</label>
|
|
<input type="number" name="settings[response_threshold]" class="form-control"
|
|
value="1000" min="100" max="10000">
|
|
<div class="form-text">Alert if response time exceeds this value</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input type="checkbox" name="settings[follow_redirects]" class="form-check-input" id="follow-redirects" checked>
|
|
<label class="form-check-label" for="follow-redirects">
|
|
Follow HTTP redirects
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input type="checkbox" name="settings[verify_ssl]" class="form-check-input" id="verify-ssl" checked>
|
|
<label class="form-check-label" for="verify-ssl">
|
|
Verify SSL certificate
|
|
</label>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
Create `resources/views/features/settings/ssl.blade.php`:
|
|
|
|
```blade
|
|
<div class="mb-3">
|
|
<label class="form-label">Alert When Days Remaining</label>
|
|
<div class="row g-2">
|
|
<div class="col-4">
|
|
<input type="number" name="settings[alert_days][]" class="form-control"
|
|
value="60" placeholder="60 days">
|
|
</div>
|
|
<div class="col-4">
|
|
<input type="number" name="settings[alert_days][]" class="form-control"
|
|
value="30" placeholder="30 days">
|
|
</div>
|
|
<div class="col-4">
|
|
<input type="number" name="settings[alert_days][]" class="form-control"
|
|
value="7" placeholder="7 days">
|
|
</div>
|
|
</div>
|
|
<div class="form-text">You'll receive alerts at these intervals before expiration</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Check Interval</label>
|
|
<select name="settings[check_interval]" class="form-select">
|
|
<option value="daily">Daily</option>
|
|
<option value="weekly">Weekly</option>
|
|
</select>
|
|
</div>
|
|
```
|
|
|
|
## Layout Integration
|
|
|
|
Add to your navigation in `resources/views/layouts/app.blade.php`:
|
|
|
|
```blade
|
|
<li class="nav-item">
|
|
<a class="nav-link {{ request()->is('features*') ? 'active' : '' }}" href="{{ route('features.index') }}">
|
|
<i class="bi bi-puzzle"></i> Features
|
|
</a>
|
|
</li>
|
|
```
|
|
|
|
## Running the Implementation
|
|
|
|
1. Run migration:
|
|
```bash
|
|
./vendor/bin/sail artisan migrate
|
|
```
|
|
|
|
2. Create the SiteFeature model
|
|
3. Create FeaturesController
|
|
4. Add routes
|
|
5. Create view files
|
|
6. Test feature toggles
|
|
|
|
## Chart.js CDN
|
|
|
|
Already included in the show.blade.php template:
|
|
```html
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
```
|
|
|
|
## Auto-enabling Features
|
|
|
|
You may want to automatically enable default features when a site is created. Add to Site model:
|
|
|
|
```php
|
|
protected static function booted()
|
|
{
|
|
static::created(function ($site) {
|
|
// Auto-enable core features
|
|
$site->enableFeature('uptime');
|
|
$site->enableFeature('ssl');
|
|
});
|
|
}
|
|
```
|
|
|
|
This provides a complete, production-ready feature management system with graphs and per-site controls.
|