some progress

This commit is contained in:
Doar Mihai
2026-01-24 12:16:10 +02:00
parent 3665a1d5a3
commit 612cc097f8
51 changed files with 8089 additions and 470 deletions
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands;
use App\Jobs\CheckDomainExpiry;
use App\Models\Domain;
use Illuminate\Console\Command;
class MonitorDomains extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'monitor:domains';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check expiry dates for all active domains';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Starting domain expiry monitoring...');
$domains = Domain::where('is_active', true)->get();
if ($domains->isEmpty()) {
$this->warn('No active domains found to monitor.');
return Command::SUCCESS;
}
$this->info("Found {$domains->count()} active domain(s) to check.");
$progressBar = $this->output->createProgressBar($domains->count());
$progressBar->start();
foreach ($domains as $domain) {
CheckDomainExpiry::dispatch($domain);
$progressBar->advance();
}
$progressBar->finish();
$this->newLine();
$this->info('Domain expiry check jobs have been dispatched.');
return Command::SUCCESS;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands;
use App\Jobs\CheckSitemap;
use App\Models\Sitemap;
use Illuminate\Console\Command;
class MonitorSitemaps extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'monitor:sitemaps';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check all active sitemaps for validity and accessibility';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Starting sitemap monitoring...');
$sitemaps = Sitemap::where('is_active', true)->get();
if ($sitemaps->isEmpty()) {
$this->warn('No active sitemaps found to monitor.');
return Command::SUCCESS;
}
$this->info("Found {$sitemaps->count()} active sitemap(s) to check.");
$progressBar = $this->output->createProgressBar($sitemaps->count());
$progressBar->start();
foreach ($sitemaps as $sitemap) {
CheckSitemap::dispatch($sitemap);
$progressBar->advance();
}
$progressBar->finish();
$this->newLine();
$this->info('Sitemap check jobs have been dispatched.');
return Command::SUCCESS;
}
}
@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers;
use App\Models\Site;
use App\Models\BrokenLink;
use Illuminate\Http\Request;
class BrokenLinkController extends Controller
{
/**
* Display broken links overview for all sites
*/
public function index()
{
$sites = Site::where('user_id', auth()->id())
->withCount('brokenLinks')
->get();
// Get broken link statistics
$stats = [
'total_sites' => $sites->count(),
'sites_with_broken_links' => $sites->where('broken_links_count', '>', 0)->count(),
'total_broken_links' => $sites->sum('broken_links_count'),
'sites_clean' => $sites->where('broken_links_count', 0)->count(),
];
// Get recent broken links across all sites
$recentBrokenLinks = BrokenLink::whereHas('site', function ($query) {
$query->where('user_id', auth()->id());
})
->with('site')
->latest()
->limit(50)
->get();
return view('broken-links.index', compact('sites', 'stats', 'recentBrokenLinks'));
}
/**
* Display broken links for a specific site
*/
public function show(Site $site)
{
$this->authorize('view', $site);
$brokenLinks = $site->brokenLinks()
->orderBy('created_at', 'desc')
->paginate(50);
$stats = [
'total_broken_links' => $brokenLinks->total(),
'unique_urls' => $site->brokenLinks()->distinct('url')->count('url'),
];
return view('broken-links.show', compact('site', 'brokenLinks', 'stats'));
}
}
@@ -11,6 +11,27 @@ use Illuminate\View\View;
class DnsRecordController extends Controller
{
/**
* Display overview of all DNS records across all sites
*/
public function overview(): View
{
$sites = Site::where('user_id', Auth::id())
->withCount('dnsRecords')
->with(['dnsRecords' => function ($query) {
$query->where('is_active', true)->limit(5);
}])
->get();
$stats = [
'total_sites' => $sites->count(),
'total_dns_records' => $sites->sum('dns_records_count'),
'sites_with_records' => $sites->where('dns_records_count', '>', 0)->count(),
];
return view('dns-records.overview', compact('sites', 'stats'));
}
/**
* Display a listing of the DNS records for a site.
*/
+124
View File
@@ -0,0 +1,124 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\CheckDomainExpiry;
use App\Models\Domain;
use App\Models\Site;
use Illuminate\Http\Request;
class DomainController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$domains = Domain::with('site')
->where('is_active', true)
->orderBy('expires_at', 'asc')
->paginate(15);
return view('domains.index', compact('domains'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$sites = Site::where('user_id', auth()->id())
->orderBy('name')
->get();
return view('domains.create', compact('sites'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'site_id' => 'required|exists:sites,id',
'domain' => 'required|string|max:255',
'is_active' => 'sometimes|nullable|in:on,off',
]);
$validated['is_active'] = $request->has('is_active');
$domain = Domain::create($validated);
// Dispatch job to check domain immediately
CheckDomainExpiry::dispatch($domain);
return redirect()
->route('domains.index')
->with('success', 'Domain added successfully. Checking expiry date...');
}
/**
* Display the specified resource.
*/
public function show(Domain $domain)
{
$domain->load('site');
return view('domains.show', compact('domain'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Domain $domain)
{
$sites = Site::where('user_id', auth()->id())
->orderBy('name')
->get();
return view('domains.edit', compact('domain', 'sites'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Domain $domain)
{
$validated = $request->validate([
'site_id' => 'required|exists:sites,id',
'domain' => 'required|string|max:255',
'is_active' => 'boolean',
]);
$validated['is_active'] = $request->has('is_active');
$domain->update($validated);
return redirect()
->route('domains.index')
->with('success', 'Domain updated successfully.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Domain $domain)
{
$domain->delete();
return redirect()
->route('domains.index')
->with('success', 'Domain deleted successfully.');
}
/**
* Manually trigger domain expiry check
*/
public function check(Domain $domain)
{
CheckDomainExpiry::dispatch($domain);
return back()
->with('success', 'Domain expiry check has been queued.');
}
}
@@ -11,6 +11,27 @@ use Illuminate\View\View;
class HealthCheckController extends Controller
{
/**
* Display overview of all health checks across all sites
*/
public function overview(): View
{
$sites = Site::where('user_id', Auth::id())
->withCount('healthChecks')
->with(['healthChecks' => function ($query) {
$query->where('is_active', true)->limit(5);
}])
->get();
$stats = [
'total_sites' => $sites->count(),
'total_health_checks' => $sites->sum('health_checks_count'),
'sites_with_checks' => $sites->where('health_checks_count', '>', 0)->count(),
];
return view('health-checks.overview', compact('sites', 'stats'));
}
/**
* Display a listing of the health checks for a site.
*/
+125
View File
@@ -0,0 +1,125 @@
<?php
namespace App\Http\Controllers;
use App\Jobs\CheckSitemap;
use App\Models\Sitemap;
use App\Models\Site;
use Illuminate\Http\Request;
class SitemapController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$sitemaps = Sitemap::with('site')
->where('is_active', true)
->orderBy('is_valid', 'asc')
->orderBy('last_checked_at', 'desc')
->paginate(15);
return view('sitemaps.index', compact('sitemaps'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$sites = Site::where('user_id', auth()->id())
->orderBy('name')
->get();
return view('sitemaps.create', compact('sites'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'site_id' => 'required|exists:sites,id',
'url' => 'required|url|max:500',
'is_active' => 'sometimes|nullable|in:on,off',
]);
$validated['is_active'] = $request->has('is_active');
$sitemap = Sitemap::create($validated);
// Dispatch job to check sitemap immediately
CheckSitemap::dispatch($sitemap);
return redirect()
->route('sitemaps.index')
->with('success', 'Sitemap added successfully. Validating now...');
}
/**
* Display the specified resource.
*/
public function show(Sitemap $sitemap)
{
$sitemap->load('site');
return view('sitemaps.show', compact('sitemap'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Sitemap $sitemap)
{
$sites = Site::where('user_id', auth()->id())
->orderBy('name')
->get();
return view('sitemaps.edit', compact('sitemap', 'sites'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Sitemap $sitemap)
{
$validated = $request->validate([
'site_id' => 'required|exists:sites,id',
'url' => 'required|url|max:500',
'is_active' => 'boolean',
]);
$validated['is_active'] = $request->has('is_active');
$sitemap->update($validated);
return redirect()
->route('sitemaps.index')
->with('success', 'Sitemap updated successfully.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Sitemap $sitemap)
{
$sitemap->delete();
return redirect()
->route('sitemaps.index')
->with('success', 'Sitemap deleted successfully.');
}
/**
* Manually trigger sitemap validation
*/
public function check(Sitemap $sitemap)
{
CheckSitemap::dispatch($sitemap);
return back()
->with('success', 'Sitemap validation has been queued.');
}
}
@@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers;
use App\Models\Site;
use App\Models\SslCertificate;
use Illuminate\Http\Request;
class SslCertificateController extends Controller
{
/**
* Display SSL certificate overview for all sites
*/
public function index()
{
$sites = Site::where('user_id', auth()->id())
->with(['sslCertificates' => function ($query) {
$query->latest()->limit(1);
}])
->get();
// Get SSL statistics
$stats = [
'total_sites' => $sites->count(),
'valid_certs' => 0,
'expiring_soon' => 0,
'expired' => 0,
];
foreach ($sites as $site) {
$latestCert = $site->sslCertificates->first();
if ($latestCert) {
if ($latestCert->expires_at) {
$daysUntilExpiry = now()->diffInDays($latestCert->expires_at, false);
if ($daysUntilExpiry < 0) {
$stats['expired']++;
} elseif ($daysUntilExpiry <= 30) {
$stats['expiring_soon']++;
} else {
$stats['valid_certs']++;
}
}
}
}
// Get recent SSL checks across all sites
$recentCertificates = SslCertificate::whereHas('site', function ($query) {
$query->where('user_id', auth()->id());
})
->with('site')
->latest()
->limit(50)
->get();
return view('ssl.index', compact('sites', 'stats', 'recentCertificates'));
}
/**
* Display SSL certificate details for a specific site
*/
public function show(Site $site)
{
$this->authorize('view', $site);
$sslCertificates = $site->sslCertificates()
->orderBy('created_at', 'desc')
->paginate(50);
$latestCert = $site->sslCertificates()->latest()->first();
return view('ssl.show', compact('site', 'sslCertificates', 'latestCert'));
}
}
@@ -9,6 +9,27 @@ use Illuminate\Support\Facades\Auth;
class TcpPortController extends Controller
{
/**
* Display overview of all TCP ports across all sites
*/
public function overview()
{
$sites = Site::where('user_id', Auth::id())
->withCount('tcpPorts')
->with(['tcpPorts' => function ($query) {
$query->where('is_active', true)->limit(5);
}])
->get();
$stats = [
'total_sites' => $sites->count(),
'total_ports_monitored' => $sites->sum('tcp_ports_count'),
'sites_with_ports' => $sites->where('tcp_ports_count', '>', 0)->count(),
];
return view('tcp-ports.overview', compact('sites', 'stats'));
}
/**
* Display a listing of the resource.
*/
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace App\Http\Controllers;
use App\Models\Site;
use App\Models\UptimeCheck;
use Illuminate\Http\Request;
class UptimeController extends Controller
{
/**
* Display uptime monitoring overview for all sites
*/
public function index()
{
$sites = Site::where('user_id', auth()->id())
->with(['uptimeChecks' => function ($query) {
$query->latest()->limit(1);
}])
->get();
// Get uptime statistics
$stats = [
'total_sites' => $sites->count(),
'sites_up' => 0,
'sites_down' => 0,
'avg_response_time' => 0,
];
$totalResponseTime = 0;
$checksCount = 0;
foreach ($sites as $site) {
$latestCheck = $site->uptimeChecks->first();
if ($latestCheck) {
if ($latestCheck->is_online) {
$stats['sites_up']++;
} else {
$stats['sites_down']++;
}
$totalResponseTime += $latestCheck->response_time ?? 0;
$checksCount++;
}
}
if ($checksCount > 0) {
$stats['avg_response_time'] = round($totalResponseTime / $checksCount, 2);
}
// Get recent checks across all sites
$recentChecks = UptimeCheck::whereHas('site', function ($query) {
$query->where('user_id', auth()->id());
})
->with('site')
->latest()
->limit(50)
->get();
return view('uptime.index', compact('sites', 'stats', 'recentChecks'));
}
/**
* Display uptime details for a specific site
*/
public function show(Site $site)
{
$this->authorize('view', $site);
$uptimeChecks = $site->uptimeChecks()
->orderBy('created_at', 'desc')
->paginate(100);
// Calculate uptime percentage for last 30 days
$checksLast30Days = $site->uptimeChecks()
->where('created_at', '>=', now()->subDays(30))
->get();
$uptimePercentage = 0;
if ($checksLast30Days->count() > 0) {
$upCount = $checksLast30Days->where('is_online', true)->count();
$uptimePercentage = round(($upCount / $checksLast30Days->count()) * 100, 2);
}
return view('uptime.show', compact('site', 'uptimeChecks', 'uptimePercentage'));
}
}
+142
View File
@@ -0,0 +1,142 @@
<?php
namespace App\Jobs;
use App\Models\Domain;
use App\Models\Notification;
use Carbon\Carbon;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class CheckDomainExpiry implements ShouldQueue
{
use Queueable;
/**
* Create a new job instance.
*/
public function __construct(
public Domain $domain
) {
}
/**
* Execute the job.
*/
public function handle(): void
{
try {
// Use WHOIS API to check domain expiry
// We'll use the WHOIS XML API (https://www.whoisxmlapi.com/)
// You can also use other providers like whoisfreaks.com or domainr.com
$apiKey = config('services.whois.api_key');
if (!$apiKey) {
Log::warning('WHOIS API key not configured');
return;
}
$response = Http::timeout(30)
->get('https://www.whoisxmlapi.com/whoisserver/WhoisService', [
'apiKey' => $apiKey,
'domainName' => $this->domain->domain,
'outputFormat' => 'JSON',
]);
if ($response->successful()) {
$data = $response->json();
// Parse expiry date from WHOIS response
$expiryDate = $this->parseExpiryDate($data);
if ($expiryDate) {
$this->domain->update([
'expires_at' => $expiryDate,
'last_checked_at' => Carbon::now(),
]);
// Check if domain is expiring soon (within 30 days)
if ($this->domain->isExpiringSoon()) {
$this->sendExpiryAlert();
}
}
} else {
Log::error('WHOIS API request failed', [
'domain' => $this->domain->domain,
'status' => $response->status(),
]);
}
$this->domain->update([
'last_checked_at' => Carbon::now(),
]);
} catch (\Exception $e) {
Log::error('Error checking domain expiry', [
'domain' => $this->domain->domain,
'error' => $e->getMessage(),
]);
throw $e; // Re-throw to allow job retry
}
}
/**
* Parse expiry date from WHOIS response
*/
protected function parseExpiryDate(array $data): ?Carbon
{
// Try multiple possible fields where expiry date might be
$possibleFields = [
'WhoisRecord.registryData.expiresDate',
'WhoisRecord.expiresDate',
'expiresDate',
];
foreach ($possibleFields as $field) {
$value = data_get($data, $field);
if ($value) {
try {
return Carbon::parse($value);
} catch (\Exception $e) {
Log::warning('Failed to parse date', [
'field' => $field,
'value' => $value,
]);
}
}
}
return null;
}
/**
* Send alert notification for expiring domain
*/
protected function sendExpiryAlert(): void
{
$daysUntilExpiry = $this->domain->daysUntilExpiration();
$message = $this->domain->isExpired()
? "Domain '{$this->domain->domain}' has expired!"
: "Domain '{$this->domain->domain}' expires in {$daysUntilExpiry} days (on {$this->domain->expires_at->format('Y-m-d')})";
Notification::create([
'user_id' => $this->domain->site->user_id,
'type' => 'domain_expiry',
'message' => $message,
'data' => json_encode([
'domain_id' => $this->domain->id,
'site_id' => $this->domain->site_id,
'domain' => $this->domain->domain,
'expires_at' => $this->domain->expires_at,
'days_until_expiry' => $daysUntilExpiry,
]),
'read_at' => null,
]);
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
namespace App\Jobs;
use App\Models\Notification;
use App\Models\Sitemap;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class CheckSitemap implements ShouldQueue
{
use Queueable;
/**
* Create a new job instance.
*/
public function __construct(
public Sitemap $sitemap
) {
}
/**
* Execute the job.
*/
public function handle(): void
{
try {
// Fetch the sitemap URL
$response = Http::timeout(30)
->withHeaders([
'User-Agent' => 'Mozilla/5.0 (compatible; SitemapMonitor/1.0)',
])
->get($this->sitemap->url);
if (!$response->successful()) {
$this->sitemap->markAsInvalid(
"HTTP {$response->status()}: Failed to fetch sitemap"
);
$this->sendAlert("Sitemap is inaccessible (HTTP {$response->status()})");
return;
}
// Get the response body
$xmlContent = $response->body();
// Validate XML structure using simplexml_load_string
libxml_use_internal_errors(true);
$xml = simplexml_load_string($xmlContent);
if ($xml === false) {
$errors = libxml_get_errors();
$errorMessages = array_map(fn($error) => trim($error->message), $errors);
libxml_clear_errors();
$this->sitemap->markAsInvalid(
'Invalid XML: ' . implode('; ', $errorMessages)
);
$this->sendAlert('Sitemap contains invalid XML');
return;
}
// Check if it's a valid sitemap (has urlset or sitemapindex)
$namespaces = $xml->getNamespaces(true);
$isUrlset = isset($xml->url) || isset($namespaces['']);
$isSitemapIndex = isset($xml->sitemap);
if (!$isUrlset && !$isSitemapIndex) {
$this->sitemap->markAsInvalid(
'Invalid sitemap format: Missing <urlset> or <sitemapindex> root element'
);
$this->sendAlert('Sitemap has invalid format');
return;
}
// Count URLs in the sitemap
$urlCount = $this->countUrls($xml);
// Mark as valid
$this->sitemap->markAsValid($urlCount);
Log::info('Sitemap validated successfully', [
'sitemap_id' => $this->sitemap->id,
'url' => $this->sitemap->url,
'url_count' => $urlCount,
]);
} catch (\Exception $e) {
Log::error('Error checking sitemap', [
'sitemap_id' => $this->sitemap->id,
'url' => $this->sitemap->url,
'error' => $e->getMessage(),
]);
$this->sitemap->markAsInvalid(
'Exception: ' . $e->getMessage()
);
$this->sendAlert('Sitemap check failed: ' . $e->getMessage());
throw $e; // Re-throw to allow job retry
}
}
/**
* Count URLs in the sitemap
*/
protected function countUrls(\SimpleXMLElement $xml): int
{
$count = 0;
// Register namespaces
$xml->registerXPathNamespace('sm', 'http://www.sitemaps.org/schemas/sitemap/0.9');
// Check for regular sitemap
$urls = $xml->xpath('//sm:url') ?: $xml->xpath('//url');
if ($urls !== false) {
$count = count($urls);
}
// If it's a sitemap index, count the sitemaps instead
if ($count === 0) {
$sitemaps = $xml->xpath('//sm:sitemap') ?: $xml->xpath('//sitemap');
if ($sitemaps !== false) {
$count = count($sitemaps);
}
}
return $count;
}
/**
* Send alert notification for invalid sitemap
*/
protected function sendAlert(string $message): void
{
Notification::create([
'user_id' => $this->sitemap->site->user_id,
'type' => 'sitemap_invalid',
'message' => "{$message}: {$this->sitemap->url}",
'data' => json_encode([
'sitemap_id' => $this->sitemap->id,
'site_id' => $this->sitemap->site_id,
'url' => $this->sitemap->url,
'error' => $this->sitemap->last_error,
]),
'read_at' => null,
]);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Carbon\Carbon;
class Domain extends Model
{
protected $fillable = [
'site_id',
'domain',
'expires_at',
'last_checked_at',
'is_active',
];
protected $casts = [
'expires_at' => 'datetime',
'last_checked_at' => 'datetime',
'is_active' => 'boolean',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
/**
* Check if domain is expiring soon (within 30 days)
*/
public function isExpiringSoon(): bool
{
if (!$this->expires_at) {
return false;
}
return $this->expires_at->lte(Carbon::now()->addDays(30));
}
/**
* Check if domain has expired
*/
public function isExpired(): bool
{
if (!$this->expires_at) {
return false;
}
return $this->expires_at->lte(Carbon::now());
}
/**
* Get days until expiration
*/
public function daysUntilExpiration(): ?int
{
if (!$this->expires_at) {
return null;
}
return Carbon::now()->diffInDays($this->expires_at, false);
}
}
+10
View File
@@ -53,4 +53,14 @@ class Site extends Model
{
return $this->hasMany(DnsRecord::class);
}
public function domains(): HasMany
{
return $this->hasMany(Domain::class);
}
public function sitemaps(): HasMany
{
return $this->hasMany(Sitemap::class);
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Sitemap extends Model
{
protected $fillable = [
'site_id',
'url',
'last_checked_at',
'is_valid',
'last_error',
'is_active',
'url_count',
];
protected $casts = [
'last_checked_at' => 'datetime',
'is_valid' => 'boolean',
'is_active' => 'boolean',
'url_count' => 'integer',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
/**
* Check if sitemap has errors
*/
public function hasErrors(): bool
{
return !$this->is_valid || !empty($this->last_error);
}
/**
* Get the status badge color
*/
public function getStatusBadgeClass(): string
{
if (!$this->last_checked_at) {
return 'bg-secondary';
}
return $this->is_valid ? 'bg-success' : 'bg-danger';
}
/**
* Get the status text
*/
public function getStatusText(): string
{
if (!$this->last_checked_at) {
return 'Not Checked';
}
return $this->is_valid ? 'Valid' : 'Invalid';
}
/**
* Mark sitemap as valid
*/
public function markAsValid(?int $urlCount = null): void
{
$this->update([
'is_valid' => true,
'last_error' => null,
'last_checked_at' => now(),
'url_count' => $urlCount,
]);
}
/**
* Mark sitemap as invalid
*/
public function markAsInvalid(string $error): void
{
$this->update([
'is_valid' => false,
'last_error' => $error,
'last_checked_at' => now(),
]);
}
}
+44
View File
@@ -24,6 +24,16 @@ class User extends Authenticatable
'email',
'password',
'notification_preferences',
'notification_channels',
'slack_webhook_url',
'telegram_chat_id',
'discord_webhook_url',
'teams_webhook_url',
'google_chat_webhook_url',
'webhook_url',
'sms_phone_number',
'opsgenie_api_key',
'pagerduty_integration_key',
];
/**
@@ -47,9 +57,43 @@ class User extends Authenticatable
'email_verified_at' => 'datetime',
'password' => 'hashed',
'notification_preferences' => 'array',
'notification_channels' => 'array',
];
}
/**
* Get enabled notification channels for a specific alert type
*/
public function getNotificationChannelsFor(string $alertType): array
{
$channels = $this->notification_channels ?? [];
return $channels[$alertType] ?? ['database'];
}
/**
* Route notification for Slack
*/
public function routeNotificationForSlack(): ?string
{
return $this->slack_webhook_url;
}
/**
* Route notification for Telegram
*/
public function routeNotificationForTelegram(): ?string
{
return $this->telegram_chat_id;
}
/**
* Route notification for Discord
*/
public function routeNotificationForDiscord(): ?string
{
return $this->discord_webhook_url;
}
public function sites(): HasMany
{
return $this->hasMany(Site::class);