lots of progress
This commit is contained in:
Vendored
+30
@@ -1,4 +1,34 @@
|
||||
import 'bootstrap';
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Body
|
||||
$body-bg: #f8fafc;
|
||||
|
||||
// Typography
|
||||
$font-family-sans-serif: 'Nunito', sans-serif;
|
||||
$font-size-base: 0.9rem;
|
||||
$line-height-base: 1.6;
|
||||
@@ -0,0 +1,8 @@
|
||||
// Fonts
|
||||
@import url('https://fonts.bunny.net/css?family=Nunito');
|
||||
|
||||
// Variables
|
||||
@import 'variables';
|
||||
|
||||
// Bootstrap
|
||||
@import 'bootstrap/scss/bootstrap';
|
||||
@@ -0,0 +1,115 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Alerts')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1><i class="bi bi-bell"></i> Alerts</h1>
|
||||
<p class="text-muted mb-0">Notification history for all your sites</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Tabs -->
|
||||
<ul class="nav nav-tabs mb-4" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="all-tab" data-bs-toggle="tab" data-bs-target="#all" type="button" role="tab">
|
||||
All ({{ $notifications->total() }})
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="unread-tab" data-bs-toggle="tab" data-bs-target="#unread" type="button" role="tab">
|
||||
Unread ({{ $unreadCount }})
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="read-tab" data-bs-toggle="tab" data-bs-target="#read" type="button" role="tab">
|
||||
Read ({{ $readCount }})
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Notifications List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
@if($notifications->isEmpty())
|
||||
<div class="alert alert-info mb-0">
|
||||
<i class="bi bi-info-circle"></i> No alerts yet. You'll receive notifications when issues are detected with your sites.
|
||||
</div>
|
||||
@else
|
||||
<div class="list-group list-group-flush">
|
||||
@foreach($notifications as $notification)
|
||||
<div class="list-group-item {{ !$notification->is_read ? 'bg-light' : '' }} border-start border-{{ $notification->type === 'uptime' ? 'danger' : ($notification->type === 'ssl' ? 'warning' : 'info') }} border-3">
|
||||
<div class="d-flex w-100 justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
@if($notification->type === 'uptime')
|
||||
<i class="bi bi-exclamation-triangle-fill text-danger me-2"></i>
|
||||
<span class="badge bg-danger me-2">Uptime Alert</span>
|
||||
@elseif($notification->type === 'ssl')
|
||||
<i class="bi bi-shield-exclamation text-warning me-2"></i>
|
||||
<span class="badge bg-warning me-2">SSL Alert</span>
|
||||
@else
|
||||
<i class="bi bi-link-45deg text-info me-2"></i>
|
||||
<span class="badge bg-info me-2">Broken Links</span>
|
||||
@endif
|
||||
@if(!$notification->is_read)
|
||||
<span class="badge bg-primary">New</span>
|
||||
@endif
|
||||
</div>
|
||||
<p class="mb-1">{{ $notification->message }}</p>
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-clock"></i> {{ $notification->created_at->diffForHumans() }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
@if(!$notification->is_read)
|
||||
<form action="{{ route('alerts.markAsRead', $notification) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary" title="Mark as read">
|
||||
<i class="bi bi-check"></i>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
<form action="{{ route('alerts.destroy', $notification) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete"
|
||||
onclick="return confirm('Are you sure you want to delete this alert?')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
{{ $notifications->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@if($notifications->isNotEmpty())
|
||||
<div class="card-footer">
|
||||
<div class="d-flex gap-2">
|
||||
<form action="{{ route('alerts.markAllAsRead') }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-check-all"></i> Mark All as Read
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route('alerts.deleteRead') }}" method="POST" onsubmit="return confirm('Are you sure you want to delete all read alerts?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i> Delete All Read
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,73 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Login') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('login') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
|
||||
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
|
||||
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6 offset-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
|
||||
|
||||
<label class="form-check-label" for="remember">
|
||||
{{ __('Remember Me') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-0">
|
||||
<div class="col-md-8 offset-md-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('Login') }}
|
||||
</button>
|
||||
|
||||
@if (Route::has('password.request'))
|
||||
<a class="btn btn-link" href="{{ route('password.request') }}">
|
||||
{{ __('Forgot Your Password?') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,49 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Confirm Password') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
{{ __('Please confirm your password before continuing.') }}
|
||||
|
||||
<form method="POST" action="{{ route('password.confirm') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
|
||||
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-0">
|
||||
<div class="col-md-8 offset-md-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('Confirm Password') }}
|
||||
</button>
|
||||
|
||||
@if (Route::has('password.request'))
|
||||
<a class="btn btn-link" href="{{ route('password.request') }}">
|
||||
{{ __('Forgot Your Password?') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,47 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Reset Password') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
@if (session('status'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('password.email') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
|
||||
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-0">
|
||||
<div class="col-md-6 offset-md-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('Send Password Reset Link') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,65 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Reset Password') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('password.update') }}">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="token" value="{{ $token }}">
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus>
|
||||
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
|
||||
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-0">
|
||||
<div class="col-md-6 offset-md-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('Reset Password') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,77 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Register') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('register') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="name" class="col-md-4 col-form-label text-md-end">{{ __('Name') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
|
||||
|
||||
@error('name')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
|
||||
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
|
||||
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-0">
|
||||
<div class="col-md-6 offset-md-4">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('Register') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,28 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Verify Your Email Address') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
@if (session('resent'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('A fresh verification link has been sent to your email address.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ __('Before proceeding, please check your email for a verification link.') }}
|
||||
{{ __('If you did not receive the email') }},
|
||||
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>.
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,20 @@
|
||||
@props(['type' => 'info', 'dismissible' => false])
|
||||
|
||||
@php
|
||||
$classes = [
|
||||
'success' => 'alert-success',
|
||||
'danger' => 'alert-danger',
|
||||
'warning' => 'alert-warning',
|
||||
'info' => 'alert-info',
|
||||
'primary' => 'alert-primary',
|
||||
'secondary' => 'alert-secondary',
|
||||
];
|
||||
$alertClass = $classes[$type] ?? $classes['info'];
|
||||
@endphp
|
||||
|
||||
<div {{ $attributes->merge(['class' => 'alert ' . $alertClass . ($dismissible ? ' alert-dismissible fade show' : '')]) }} role="alert">
|
||||
{{ $slot }}
|
||||
@if($dismissible)
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
@props(['color' => 'primary', 'pill' => false])
|
||||
|
||||
<span {{ $attributes->merge(['class' => 'badge bg-' . $color . ($pill ? ' rounded-pill' : '')]) }}>
|
||||
{{ $slot }}
|
||||
</span>
|
||||
@@ -0,0 +1,19 @@
|
||||
@props(['header' => null, 'footer' => null])
|
||||
|
||||
<div {{ $attributes->merge(['class' => 'card']) }}>
|
||||
@if($header)
|
||||
<div class="card-header">
|
||||
{{ $header }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="card-body">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
@if($footer)
|
||||
<div class="card-footer">
|
||||
{{ $footer }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
@props(['id', 'title', 'size' => 'md'])
|
||||
|
||||
<div class="modal fade" id="{{ $id }}" tabindex="-1" aria-labelledby="{{ $id }}Label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-{{ $size }}">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="{{ $id }}Label">{{ $title }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@isset($footer)
|
||||
<div class="modal-footer">
|
||||
{{ $footer }}
|
||||
</div>
|
||||
@endisset
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
@props(['title', 'value', 'icon', 'color' => 'primary'])
|
||||
|
||||
<div {{ $attributes->merge(['class' => 'card']) }}>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-1">{{ $title }}</h6>
|
||||
<h2 class="mb-0 text-{{ $color }}">{{ $value }}</h2>
|
||||
</div>
|
||||
<div class="text-{{ $color }}" style="font-size: 2rem;">
|
||||
<i class="bi bi-{{ $icon }}"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,508 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Dashboard')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.stat-card {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--card-gradient-start), var(--card-gradient-end));
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-card.primary {
|
||||
--card-gradient-start: #667eea;
|
||||
--card-gradient-end: #764ba2;
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05), rgba(118, 75, 162, 0.05));
|
||||
}
|
||||
|
||||
.stat-card.success {
|
||||
--card-gradient-start: #10b981;
|
||||
--card-gradient-end: #059669;
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.05), rgba(5, 150, 105, 0.05));
|
||||
}
|
||||
|
||||
.stat-card.danger {
|
||||
--card-gradient-start: #ef4444;
|
||||
--card-gradient-end: #dc2626;
|
||||
background: linear-gradient(135deg, rgba(239, 68, 68, 0.05), rgba(220, 38, 38, 0.05));
|
||||
}
|
||||
|
||||
.stat-card.info {
|
||||
--card-gradient-start: #3b82f6;
|
||||
--card-gradient-end: #2563eb;
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05), rgba(37, 99, 235, 0.05));
|
||||
}
|
||||
|
||||
.dark .stat-card.primary {
|
||||
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
|
||||
}
|
||||
|
||||
.dark .stat-card.success {
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1), rgba(5, 150, 105, 0.1));
|
||||
}
|
||||
|
||||
.dark .stat-card.danger {
|
||||
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1), rgba(220, 38, 38, 0.1));
|
||||
}
|
||||
|
||||
.dark .stat-card.info {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(37, 99, 235, 0.1));
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-icon.primary {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-icon.success {
|
||||
background: linear-gradient(135deg, #10b981, #059669);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-icon.danger {
|
||||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-icon.info {
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
opacity: 0.7;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.chart-card .card-header {
|
||||
background: transparent;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1.5rem;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.chart-card .card-body {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.table-modern {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.table-modern thead th {
|
||||
background: transparent;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 1rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.table-modern tbody tr {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.table-modern tbody tr:hover {
|
||||
background: var(--bg-secondary);
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.table-modern tbody td {
|
||||
padding: 1.25rem 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.site-url {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.badge-modern {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-action:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 4rem 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 4rem;
|
||||
opacity: 0.3;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Dashboard</h1>
|
||||
<p class="page-subtitle">Welcome back! Here's an overview of all your monitored sites.</p>
|
||||
</div>
|
||||
|
||||
<!-- Summary Stats -->
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="stat-card primary">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div class="stat-label">Total Sites</div>
|
||||
<div class="stat-value">{{ $totalSites }}</div>
|
||||
</div>
|
||||
<div class="stat-icon primary">
|
||||
<i class="bi bi-globe"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="stat-card success">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div class="stat-label">Sites Up</div>
|
||||
<div class="stat-value text-success">{{ $sitesUp }}</div>
|
||||
</div>
|
||||
<div class="stat-icon success">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="stat-card danger">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div class="stat-label">Sites Down</div>
|
||||
<div class="stat-value text-danger">{{ $sitesDown }}</div>
|
||||
</div>
|
||||
<div class="stat-icon danger">
|
||||
<i class="bi bi-exclamation-circle-fill"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="stat-card info">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div class="stat-label">Avg Uptime</div>
|
||||
<div class="stat-value text-info">{{ number_format($avgUptime, 1) }}%</div>
|
||||
</div>
|
||||
<div class="stat-icon info">
|
||||
<i class="bi bi-graph-up-arrow"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 7-Day Uptime Chart -->
|
||||
<div class="card chart-card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="mb-0 d-flex align-items-center">
|
||||
<i class="bi bi-graph-up me-2"></i>
|
||||
7-Day Uptime Overview
|
||||
</h5>
|
||||
</div>
|
||||
<div class="text-muted small">
|
||||
<i class="bi bi-clock"></i> Last 7 days
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if($sites->isNotEmpty())
|
||||
<canvas id="weeklyUptimeChart" height="80"></canvas>
|
||||
@else
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
</div>
|
||||
<h5>No Data Available</h5>
|
||||
<p class="text-muted">Add your first site to start monitoring and viewing analytics.</p>
|
||||
<a href="{{ route('sites.create') }}" class="btn btn-primary mt-3">
|
||||
<i class="bi bi-plus-circle"></i> Add Your First Site
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sites Table -->
|
||||
<div class="card chart-card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="mb-0 d-flex align-items-center">
|
||||
<i class="bi bi-globe me-2"></i>
|
||||
Your Sites
|
||||
</h5>
|
||||
</div>
|
||||
<a href="{{ route('sites.create') }}" class="btn btn-primary btn-sm btn-action">
|
||||
<i class="bi bi-plus-circle"></i> Add Site
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if($sites->isNotEmpty())
|
||||
<div class="table-responsive">
|
||||
<table class="table table-modern mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Site</th>
|
||||
<th>Status</th>
|
||||
<th>Uptime</th>
|
||||
<th>Response</th>
|
||||
<th>SSL</th>
|
||||
<th>Links</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($sites as $site)
|
||||
@php
|
||||
$latestCheck = $site->uptimeChecks->first();
|
||||
$latestSsl = $site->sslCertificates->first();
|
||||
$daysUntilExpiry = $latestSsl ? now()->diffInDays($latestSsl->expires_at, false) : null;
|
||||
@endphp
|
||||
<tr>
|
||||
<td>
|
||||
<div class="site-name">{{ $site->name }}</div>
|
||||
<div class="site-url">
|
||||
<i class="bi bi-link-45deg"></i> {{ Str::limit($site->url, 40) }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@if(!$latestCheck)
|
||||
<span class="badge badge-modern bg-secondary">
|
||||
<i class="bi bi-question-circle"></i> Not Checked
|
||||
</span>
|
||||
@elseif($latestCheck->status_code >= 200 && $latestCheck->status_code < 400)
|
||||
<span class="badge badge-modern bg-success">
|
||||
<i class="bi bi-check-circle-fill"></i> Online
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-modern bg-danger">
|
||||
<i class="bi bi-x-circle-fill"></i> Down
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@php
|
||||
$uptime = $site->uptime_percentage ?? 0;
|
||||
@endphp
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="progress" style="width: 60px; height: 6px;">
|
||||
<div class="progress-bar bg-{{ $uptime >= 99 ? 'success' : ($uptime >= 95 ? 'warning' : 'danger') }}"
|
||||
role="progressbar"
|
||||
style="width: {{ $uptime }}%"></div>
|
||||
</div>
|
||||
<span class="small fw-semibold">{{ number_format($uptime, 1) }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@if($site->avg_response_time)
|
||||
<span class="fw-semibold">{{ round($site->avg_response_time) }}</span>
|
||||
<span class="text-muted small">ms</span>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($latestSsl)
|
||||
@if($daysUntilExpiry < 0)
|
||||
<span class="badge badge-modern bg-danger">
|
||||
<i class="bi bi-shield-x"></i> Expired
|
||||
</span>
|
||||
@elseif($daysUntilExpiry < 30)
|
||||
<span class="badge badge-modern bg-warning text-dark">
|
||||
<i class="bi bi-clock-history"></i> {{ $daysUntilExpiry }}d
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-modern bg-success">
|
||||
<i class="bi bi-shield-check"></i> Valid
|
||||
</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($site->broken_links_count > 0)
|
||||
<span class="badge badge-modern bg-danger">
|
||||
<i class="bi bi-link-45deg"></i> {{ $site->broken_links_count }}
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-modern bg-success">
|
||||
<i class="bi bi-check-circle"></i> 0
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a href="{{ route('dashboard.show', $site) }}" class="btn btn-sm btn-outline-primary btn-action">
|
||||
<i class="bi bi-arrow-right-circle"></i> Details
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@else
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">
|
||||
<i class="bi bi-globe"></i>
|
||||
</div>
|
||||
<h5>No Sites Yet</h5>
|
||||
<p class="text-muted">Start monitoring your websites by adding your first site.</p>
|
||||
<a href="{{ route('sites.create') }}" class="btn btn-primary mt-3">
|
||||
<i class="bi bi-plus-circle"></i> Add Your First Site
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
@if($sites->isNotEmpty() && isset($chartData))
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const ctx = document.getElementById('weeklyUptimeChart').getContext('2d');
|
||||
|
||||
const chartData = @json($chartData);
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: chartData.labels,
|
||||
datasets: chartData.datasets.map((dataset, index) => ({
|
||||
label: dataset.label,
|
||||
data: dataset.data,
|
||||
borderColor: dataset.color,
|
||||
backgroundColor: dataset.color + '33',
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
tension: 0.1
|
||||
}))
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top'
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Uptime %'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Date'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
@@ -0,0 +1,230 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', $site->name . ' Dashboard')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1><i class="bi bi-bar-chart"></i> {{ $site->name }}</h1>
|
||||
<p class="text-muted mb-0">
|
||||
<i class="bi bi-link-45deg"></i> <a href="{{ $site->url }}" target="_blank">{{ $site->url }}</a>
|
||||
</p>
|
||||
</div>
|
||||
<a href="{{ route('sites.index') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Sites
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-primary">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Uptime</h6>
|
||||
<h2 class="mb-0">{{ $uptimePercentage }}%</h2>
|
||||
<small>Last 30 days</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-success">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Avg Response Time</h6>
|
||||
<h2 class="mb-0">{{ $avgResponseTime ? round($avgResponseTime) : 'N/A' }}</h2>
|
||||
<small>{{ $avgResponseTime ? 'milliseconds' : 'No data' }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white {{ $brokenLinks->total() > 0 ? 'bg-danger' : 'bg-success' }}">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Broken Links</h6>
|
||||
<h2 class="mb-0">{{ $brokenLinks->total() }}</h2>
|
||||
<small>{{ $brokenLinks->total() == 1 ? 'link' : 'links' }} detected</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Uptime Chart -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-graph-up"></i> Response Time History (Last 30 Days)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if($uptimeChecks->isNotEmpty())
|
||||
<canvas id="uptimeChart" height="80"></canvas>
|
||||
@else
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> No uptime data available yet. The site will be checked automatically.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSL Certificate -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-shield-lock"></i> SSL Certificate</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if($latestSslCert)
|
||||
@php
|
||||
$daysUntilExpiry = now()->diffInDays($latestSslCert->expires_at, false);
|
||||
$isExpired = $daysUntilExpiry < 0;
|
||||
$isExpiringSoon = $daysUntilExpiry < 30 && !$isExpired;
|
||||
@endphp
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<strong>Issuer:</strong><br>
|
||||
{{ $latestSslCert->issuer }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Expires:</strong><br>
|
||||
{{ $latestSslCert->expires_at->format('M d, Y') }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Days Remaining:</strong><br>
|
||||
<span class="badge bg-{{ $isExpired ? 'danger' : ($isExpiringSoon ? 'warning' : 'success') }}">
|
||||
{{ $isExpired ? 'EXPIRED' : $daysUntilExpiry . ' days' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Last Checked:</strong><br>
|
||||
{{ $latestSslCert->checked_at->diffForHumans() }}
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-info mb-0">
|
||||
<i class="bi bi-info-circle"></i> No SSL certificate data available yet.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Broken Links -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-link-45deg"></i> Broken Links</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if($brokenLinks->isNotEmpty())
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<th>Status Code</th>
|
||||
<th>Last Checked</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($brokenLinks as $link)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ $link->url }}" target="_blank" class="text-decoration-none">
|
||||
{{ $link->url }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-danger">{{ $link->status_code ?? 'Failed' }}</span>
|
||||
</td>
|
||||
<td>{{ $link->last_checked_at->diffForHumans() }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
{{ $brokenLinks->links() }}
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-success mb-0">
|
||||
<i class="bi bi-check-circle"></i> No broken links detected! Your site is looking good.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
@if($uptimeChecks->isNotEmpty())
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const ctx = document.getElementById('uptimeChart').getContext('2d');
|
||||
|
||||
const labels = @json($chartLabels);
|
||||
const responseData = @json($chartData);
|
||||
const statusCodes = @json($chartStatusCodes);
|
||||
|
||||
// Create background colors based on status codes
|
||||
const backgroundColors = statusCodes.map(code => {
|
||||
if (!code) return 'rgba(220, 53, 69, 0.5)'; // Red for null/failed
|
||||
if (code >= 200 && code < 400) return 'rgba(25, 135, 84, 0.5)'; // Green for success
|
||||
return 'rgba(220, 53, 69, 0.5)'; // Red for errors
|
||||
});
|
||||
|
||||
const borderColors = statusCodes.map(code => {
|
||||
if (!code) return 'rgb(220, 53, 69)';
|
||||
if (code >= 200 && code < 400) return 'rgb(25, 135, 84)';
|
||||
return 'rgb(220, 53, 69)';
|
||||
});
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Response Time (ms)',
|
||||
data: responseData,
|
||||
borderColor: 'rgb(13, 110, 253)',
|
||||
backgroundColor: backgroundColors,
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
pointBackgroundColor: backgroundColors,
|
||||
pointBorderColor: borderColors,
|
||||
pointRadius: 4,
|
||||
pointHoverRadius: 6
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top'
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
afterLabel: function(context) {
|
||||
const statusCode = statusCodes[context.dataIndex];
|
||||
return statusCode ? 'HTTP ' + statusCode : 'Connection Failed';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Response Time (ms)'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Check Time'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
@@ -0,0 +1,23 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('Dashboard') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
@if (session('status'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ __('You are logged in!') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" x-data="{ darkMode: localStorage.getItem('darkMode') === 'true' }" :class="{ 'dark': darkMode }">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>@yield('title', 'Dashboard') - {{ config('app.name') }}</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8f9fa;
|
||||
--bg-sidebar: #1f2937;
|
||||
--text-primary: #111827;
|
||||
--text-secondary: #6b7280;
|
||||
--border-color: #e5e7eb;
|
||||
--indigo-600: #4f46e5;
|
||||
--indigo-700: #4338ca;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--bg-primary: #111827;
|
||||
--bg-secondary: #1f2937;
|
||||
--text-primary: #f9fafb;
|
||||
--text-secondary: #9ca3af;
|
||||
--border-color: #374151;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 260px;
|
||||
background-color: var(--bg-sidebar);
|
||||
padding: 1.5rem 0;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
padding: 0 1.5rem 1.5rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sidebar-nav-item {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.sidebar-nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1.5rem;
|
||||
color: #9ca3af;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.sidebar-nav-link:hover,
|
||||
.sidebar-nav-link.active {
|
||||
background-color: rgba(79, 70, 229, 0.1);
|
||||
color: var(--indigo-600);
|
||||
}
|
||||
|
||||
.sidebar-nav-link i {
|
||||
margin-right: 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 260px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
background-color: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--indigo-600);
|
||||
border-color: var(--indigo-600);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--indigo-700);
|
||||
border-color: var(--indigo-700);
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: #10b981;
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@stack('styles')
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidebar-logo">
|
||||
<h4 class="text-white mb-0">
|
||||
<i class="bi bi-shield-check"></i>
|
||||
Site Monitor
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<ul class="sidebar-nav">
|
||||
<li class="sidebar-nav-item">
|
||||
<a href="{{ route('dashboard') }}" class="sidebar-nav-link {{ request()->routeIs('dashboard') ? 'active' : '' }}">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidebar-nav-item">
|
||||
<a href="{{ route('sites.index') }}" class="sidebar-nav-link {{ request()->routeIs('sites.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-globe"></i>
|
||||
Sites
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidebar-nav-item">
|
||||
<a href="{{ route('alerts.index') }}" class="sidebar-nav-link {{ request()->routeIs('alerts.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-bell"></i>
|
||||
Alerts
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidebar-nav-item">
|
||||
<a href="{{ route('settings') }}" class="sidebar-nav-link {{ request()->routeIs('settings') ? 'active' : '' }}">
|
||||
<i class="bi bi-gear"></i>
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- Topbar -->
|
||||
<div class="topbar">
|
||||
<button class="btn btn-outline-secondary d-md-none" onclick="document.getElementById('sidebar').classList.toggle('show')">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
|
||||
<div></div>
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<button class="btn btn-outline-secondary" @click="darkMode = !darkMode; localStorage.setItem('darkMode', darkMode)">
|
||||
<i class="bi" :class="darkMode ? 'bi-sun' : 'bi-moon'"></i>
|
||||
</button>
|
||||
|
||||
@auth
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
{{ auth()->user()->name }}
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="dropdown-item">
|
||||
<i class="bi bi-box-arrow-right"></i> Logout
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-4">
|
||||
@if (session('success'))
|
||||
<x-alert type="success" dismissible>
|
||||
{{ session('success') }}
|
||||
</x-alert>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<x-alert type="danger" dismissible>
|
||||
{{ session('error') }}
|
||||
</x-alert>
|
||||
@endif
|
||||
|
||||
@if ($errors->any())
|
||||
<x-alert type="danger" dismissible>
|
||||
<strong>Validation Error:</strong>
|
||||
<ul class="mb-0">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</x-alert>
|
||||
@endif
|
||||
|
||||
@yield('content')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
|
||||
<script>
|
||||
function confirmDelete(message) {
|
||||
return confirm(message || 'Are you sure you want to delete this?');
|
||||
}
|
||||
</script>
|
||||
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,144 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Settings')
|
||||
|
||||
@section('content')
|
||||
<div class="mb-4">
|
||||
<h1><i class="bi bi-gear"></i> Settings</h1>
|
||||
<p class="text-muted">Manage your account and notification preferences</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Notification Preferences -->
|
||||
<div class="col-lg-8 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-bell"></i> Notification Preferences</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('settings.notifications') }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<p class="text-muted mb-4">Choose how you want to be notified about issues with your sites.</p>
|
||||
|
||||
<!-- Uptime Alerts -->
|
||||
<div class="mb-4">
|
||||
<h6 class="mb-3">Uptime Alerts</h6>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="uptime_email" name="uptime[]" value="mail"
|
||||
{{ in_array('mail', $preferences['uptime'] ?? []) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="uptime_email">
|
||||
<i class="bi bi-envelope"></i> Email Notifications
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="uptime_slack" name="uptime[]" value="slack"
|
||||
{{ in_array('slack', $preferences['uptime'] ?? []) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="uptime_slack">
|
||||
<i class="bi bi-slack"></i> Slack Notifications
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSL Expiry Alerts -->
|
||||
<div class="mb-4">
|
||||
<h6 class="mb-3">SSL Certificate Alerts</h6>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="ssl_email" name="ssl[]" value="mail"
|
||||
{{ in_array('mail', $preferences['ssl'] ?? []) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="ssl_email">
|
||||
<i class="bi bi-envelope"></i> Email Notifications
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="ssl_slack" name="ssl[]" value="slack"
|
||||
{{ in_array('slack', $preferences['ssl'] ?? []) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="ssl_slack">
|
||||
<i class="bi bi-slack"></i> Slack Notifications
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Broken Links Alerts -->
|
||||
<div class="mb-4">
|
||||
<h6 class="mb-3">Broken Links Alerts</h6>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" class="form-check-input" id="broken_links_email" name="broken_links[]" value="mail"
|
||||
{{ in_array('mail', $preferences['broken_links'] ?? []) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="broken_links_email">
|
||||
<i class="bi bi-envelope"></i> Email Notifications
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="broken_links_slack" name="broken_links[]" value="slack"
|
||||
{{ in_array('slack', $preferences['broken_links'] ?? []) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="broken_links_slack">
|
||||
<i class="bi bi-slack"></i> Slack Notifications
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> Save Preferences
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile Settings -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-person"></i> Profile</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Name</label>
|
||||
<p class="mb-0">{{ auth()->user()->name }}</p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Email</label>
|
||||
<p class="mb-0">{{ auth()->user()->email }}</p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">Member Since</label>
|
||||
<p class="mb-0">{{ auth()->user()->created_at->format('M d, Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slack Integration -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-slack"></i> Slack Integration</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('settings.slack') }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="slack_webhook" class="form-label">Slack Webhook URL</label>
|
||||
<input type="url" class="form-control @error('slack_webhook') is-invalid @enderror"
|
||||
id="slack_webhook" name="slack_webhook"
|
||||
value="{{ old('slack_webhook', $slackWebhook ?? '') }}"
|
||||
placeholder="https://hooks.slack.com/...">
|
||||
@error('slack_webhook')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="form-text">
|
||||
<a href="https://api.slack.com/messaging/webhooks" target="_blank">Get your webhook URL</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> Save Webhook
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,61 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Add New Site')
|
||||
|
||||
@section('content')
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0"><i class="bi bi-plus-circle"></i> Add New Site</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('sites.store') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Site Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('name') is-invalid @enderror"
|
||||
id="name" name="name" value="{{ old('name') }}" required>
|
||||
@error('name')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="form-text">A friendly name to identify your site</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">Site URL <span class="text-danger">*</span></label>
|
||||
<input type="url" class="form-control @error('url') is-invalid @enderror"
|
||||
id="url" name="url" value="{{ old('url') }}" required
|
||||
placeholder="https://example.com">
|
||||
@error('url')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="form-text">The full URL of your site (must start with http:// or https://)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="monitoring_enabled"
|
||||
name="monitoring_enabled" {{ old('monitoring_enabled', true) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="monitoring_enabled">
|
||||
Enable Monitoring
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">Start monitoring this site immediately</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> Add Site
|
||||
</button>
|
||||
<a href="{{ route('sites.index') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-x-circle"></i> Cancel
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,61 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Edit Site')
|
||||
|
||||
@section('content')
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0"><i class="bi bi-pencil"></i> Edit Site</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('sites.update', $site) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Site Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control @error('name') is-invalid @enderror"
|
||||
id="name" name="name" value="{{ old('name', $site->name) }}" required>
|
||||
@error('name')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="form-text">A friendly name to identify your site</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">Site URL <span class="text-danger">*</span></label>
|
||||
<input type="url" class="form-control @error('url') is-invalid @enderror"
|
||||
id="url" name="url" value="{{ old('url', $site->url) }}" required>
|
||||
@error('url')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
<div class="form-text">The full URL of your site (must start with http:// or https://)</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="monitoring_enabled"
|
||||
name="monitoring_enabled" {{ old('monitoring_enabled', $site->monitoring_enabled) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="monitoring_enabled">
|
||||
Enable Monitoring
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">Enable or disable monitoring for this site</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-circle"></i> Update Site
|
||||
</button>
|
||||
<a href="{{ route('sites.index') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-x-circle"></i> Cancel
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,93 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'My Sites')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><i class="bi bi-list-ul"></i> My Sites</h1>
|
||||
<a href="{{ route('sites.create') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Add New Site
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if($sites->isEmpty())
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> You haven't added any sites yet. Click "Add New Site" to get started!
|
||||
</div>
|
||||
@else
|
||||
<div class="row">
|
||||
@foreach($sites as $site)
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-0">{{ $site->name }}</h5>
|
||||
@if($site->latestUptimeCheck)
|
||||
@if($site->latestUptimeCheck->status_code >= 200 && $site->latestUptimeCheck->status_code < 400)
|
||||
<span class="badge bg-success">Online</span>
|
||||
@else
|
||||
<span class="badge bg-danger">Down</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="badge bg-secondary">Not Checked</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<p class="card-text text-muted small mb-3">
|
||||
<i class="bi bi-link-45deg"></i> {{ $site->url }}
|
||||
</p>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-6">
|
||||
<div class="border rounded p-2 text-center">
|
||||
<div class="small text-muted">Uptime Checks</div>
|
||||
<strong>{{ $site->uptime_checks_count }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="border rounded p-2 text-center">
|
||||
<div class="small text-muted">Broken Links</div>
|
||||
<strong class="{{ $site->broken_links_count > 0 ? 'text-danger' : '' }}">
|
||||
{{ $site->broken_links_count }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($site->latestSslCertificate)
|
||||
@php
|
||||
$daysUntilExpiry = now()->diffInDays($site->latestSslCertificate->expires_at, false);
|
||||
@endphp
|
||||
<div class="alert alert-{{ $daysUntilExpiry < 30 ? 'warning' : 'success' }} alert-sm py-1 px-2 mb-3">
|
||||
<small>
|
||||
<i class="bi bi-shield-lock"></i> SSL expires in {{ $daysUntilExpiry }} days
|
||||
</small>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ route('dashboard.show', $site) }}" class="btn btn-sm btn-outline-primary flex-fill">
|
||||
<i class="bi bi-bar-chart"></i> Dashboard
|
||||
</a>
|
||||
<a href="{{ route('sites.edit', $site) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form action="{{ route('sites.destroy', $site) }}" method="POST" class="d-inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this site?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer small text-muted">
|
||||
<i class="bi bi-clock"></i> Added {{ $site->created_at->diffForHumans() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
@@ -0,0 +1,494 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{{ config('app.name') }} - Website Monitoring Made Simple</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 100px 0 80px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.hero-section h1 {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hero-section p.lead {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.btn-hero {
|
||||
padding: 1rem 2.5rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-hero-primary {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn-hero-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.btn-hero-secondary {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border: 2px solid white;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.btn-hero-secondary:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
padding: 2.5rem;
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.8rem;
|
||||
color: white;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: #6b7280;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
background: #f9fafb;
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: #667eea;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #6b7280;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 80px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-section h2 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.navbar-landing {
|
||||
padding: 1.5rem 0;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.navbar-landing .navbar-brand {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar-landing .nav-link {
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.navbar-landing .btn-login {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border: 1px solid white;
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.navbar-landing .btn-login:hover {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.pricing-badge {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero-section h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-section p.lead {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.btn-hero-secondary {
|
||||
margin-left: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-landing">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<i class="bi bi-shield-check"></i> {{ config('app.name', 'Site Monitor') }}
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
|
||||
<ul class="navbar-nav align-items-center">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#features">Features</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#pricing">Pricing</a>
|
||||
</li>
|
||||
@guest
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-login" href="{{ route('login') }}">Login</a>
|
||||
</li>
|
||||
@else
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-login" href="{{ route('dashboard') }}">Dashboard</a>
|
||||
</li>
|
||||
@endguest
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section">
|
||||
<div class="container position-relative">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-7">
|
||||
<h1>Monitor Your Websites With Confidence</h1>
|
||||
<p class="lead">Get instant alerts when your site goes down, SSL certificates expire, or broken links appear.</p>
|
||||
<div class="mt-4">
|
||||
@guest
|
||||
<a href="{{ route('register') }}" class="btn-hero btn-hero-primary">Get Started Free</a>
|
||||
<a href="#features" class="btn-hero btn-hero-secondary">Learn More</a>
|
||||
@else
|
||||
<a href="{{ route('dashboard') }}" class="btn-hero btn-hero-primary">Go to Dashboard</a>
|
||||
<a href="{{ route('sites.create') }}" class="btn-hero btn-hero-secondary">Add Your First Site</a>
|
||||
@endguest
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5 d-none d-lg-block text-center">
|
||||
<i class="bi bi-graph-up-arrow" style="font-size: 15rem; opacity: 0.2;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Stats Section -->
|
||||
<section class="stats-section">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 mb-md-0">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">99.9%</span>
|
||||
<div class="stat-label">Uptime Guaranteed</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4 mb-md-0">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">5 min</span>
|
||||
<div class="stat-label">Check Intervals</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">24/7</span>
|
||||
<div class="stat-label">Monitoring</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section id="features" class="py-5" style="padding: 80px 0;">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-4 fw-bold mb-3">Everything You Need to Monitor Your Sites</h2>
|
||||
<p class="lead text-muted">Comprehensive monitoring tools to keep your websites running smoothly</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="bi bi-activity"></i>
|
||||
</div>
|
||||
<h3>Uptime Monitoring</h3>
|
||||
<p>Check your website's availability every 5 minutes. Get instant alerts via email or Slack when your site goes down.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="bi bi-shield-lock"></i>
|
||||
</div>
|
||||
<h3>SSL Certificate Checks</h3>
|
||||
<p>Never let your SSL certificates expire. Receive notifications 30 days before expiry to renew on time.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
</div>
|
||||
<h3>Broken Link Detection</h3>
|
||||
<p>Automatically scan your website for broken links. Keep your SEO strong and user experience smooth.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
</div>
|
||||
<h3>Performance Tracking</h3>
|
||||
<p>Track response times and view historical performance data with beautiful charts and analytics.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="bi bi-bell"></i>
|
||||
</div>
|
||||
<h3>Smart Notifications</h3>
|
||||
<p>Customize notification preferences for each check type. Get alerts via email or Slack integration.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
</div>
|
||||
<h3>Beautiful Dashboard</h3>
|
||||
<p>View all your sites at a glance with an intuitive dashboard. Dark mode included for late-night monitoring.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pricing Section -->
|
||||
<section id="pricing" class="py-5 bg-light" style="padding: 80px 0;">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-4 fw-bold mb-3">Simple, Transparent Pricing</h2>
|
||||
<p class="lead text-muted">Start monitoring for free, upgrade as you grow</p>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-5 mb-4">
|
||||
<div class="feature-card text-center">
|
||||
<div class="mb-3">
|
||||
<span class="pricing-badge">FREE FOREVER</span>
|
||||
</div>
|
||||
<h3>Starter</h3>
|
||||
<div class="my-4">
|
||||
<span class="display-3 fw-bold">$0</span>
|
||||
<span class="text-muted">/month</span>
|
||||
</div>
|
||||
<ul class="list-unstyled text-start mb-4">
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Up to 5 sites</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> 5-minute checks</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Email notifications</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> SSL monitoring</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Broken link detection</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> 30 days history</li>
|
||||
</ul>
|
||||
<a href="{{ route('register') }}" class="btn btn-lg btn-outline-primary w-100">Get Started</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5 mb-4">
|
||||
<div class="feature-card text-center" style="border: 2px solid #667eea;">
|
||||
<div class="mb-3">
|
||||
<span class="pricing-badge" style="background: #667eea;">COMING SOON</span>
|
||||
</div>
|
||||
<h3>Professional</h3>
|
||||
<div class="my-4">
|
||||
<span class="display-3 fw-bold">$19</span>
|
||||
<span class="text-muted">/month</span>
|
||||
</div>
|
||||
<ul class="list-unstyled text-start mb-4">
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Unlimited sites</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> 1-minute checks</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Email + Slack notifications</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Advanced SSL monitoring</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Deep link crawling</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Unlimited history</li>
|
||||
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Priority support</li>
|
||||
</ul>
|
||||
<button class="btn btn-lg w-100" style="background: #667eea; color: white;" disabled>Coming Soon</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="cta-section">
|
||||
<div class="container">
|
||||
<h2>Ready to Monitor Your Websites?</h2>
|
||||
<p class="lead mb-4">Join thousands of developers who trust us to keep their sites online.</p>
|
||||
@guest
|
||||
<a href="{{ route('register') }}" class="btn-hero btn-hero-primary">Start Monitoring for Free</a>
|
||||
@else
|
||||
<a href="{{ route('sites.create') }}" class="btn-hero btn-hero-primary">Add Your First Site</a>
|
||||
@endguest
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4 mb-md-0">
|
||||
<h5 class="mb-3"><i class="bi bi-shield-check"></i> {{ config('app.name', 'Site Monitor') }}</h5>
|
||||
<p class="text-muted">Professional website monitoring made simple. Keep your sites online 24/7.</p>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4 mb-md-0">
|
||||
<h6 class="mb-3">Product</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2"><a href="#features" class="text-muted text-decoration-none">Features</a></li>
|
||||
<li class="mb-2"><a href="#pricing" class="text-muted text-decoration-none">Pricing</a></li>
|
||||
@guest
|
||||
<li class="mb-2"><a href="{{ route('login') }}" class="text-muted text-decoration-none">Login</a></li>
|
||||
<li class="mb-2"><a href="{{ route('register') }}" class="text-muted text-decoration-none">Sign Up</a></li>
|
||||
@endguest
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h6 class="mb-3">Built With</h6>
|
||||
<p class="text-muted">Laravel " MySQL " Bootstrap " Alpine.js " Chart.js</p>
|
||||
<p class="text-muted small mt-3">© {{ date('Y') }} {{ config('app.name') }}. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
// Smooth scrolling for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user