You've already forked Atomcms-edit
955 lines
41 KiB
PHP
Executable File
955 lines
41 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Pages\Monitoring;
|
|
|
|
use App\Console\Commands\DDoSDetectionCommand;
|
|
use App\Enums\AlertSeverity;
|
|
use App\Enums\AlertType;
|
|
use App\Models\Miscellaneous\AlertLog;
|
|
use App\Models\Miscellaneous\WebsiteSetting;
|
|
use App\Services\AlertService;
|
|
use App\Services\RconService;
|
|
use App\Services\SettingsService;
|
|
use Carbon\Carbon;
|
|
use Filament\Actions\Action;
|
|
use Filament\Forms\Components\Placeholder;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Components\Toggle;
|
|
use Filament\Forms\Concerns\InteractsWithForms;
|
|
use Filament\Forms\Contracts\HasForms;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Schemas\Components\Section;
|
|
use Filament\Schemas\Schema;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Process;
|
|
use Illuminate\Support\HtmlString;
|
|
|
|
final class AlertSettings extends Page implements HasForms
|
|
{
|
|
use InteractsWithForms;
|
|
|
|
#[\Override]
|
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-command-line';
|
|
|
|
#[\Override]
|
|
protected static string|\UnitEnum|null $navigationGroup = 'Commandocentrum';
|
|
|
|
#[\Override]
|
|
protected static ?string $navigationLabel = 'Commandocentrum';
|
|
|
|
#[\Override]
|
|
protected static ?string $title = 'Commandocentrum';
|
|
|
|
#[\Override]
|
|
protected static ?string $slug = 'commandocentrum';
|
|
|
|
#[\Override]
|
|
protected string $view = 'filament.pages.monitoring.alert-settings';
|
|
|
|
public array $data = [];
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->fillForm();
|
|
}
|
|
|
|
protected function fillForm(): void
|
|
{
|
|
$this->data = [
|
|
'alert_email_enabled' => $this->getSettingBool('alert_email_enabled'),
|
|
'alert_email_address' => $this->getSetting('alert_email_address', ''),
|
|
'alert_discord_enabled' => $this->getSettingBool('alert_discord_enabled'),
|
|
'alert_discord_webhook_url' => $this->getSetting('alert_discord_webhook_url', ''),
|
|
'alert_emulator_enabled' => $this->getSettingBool('alert_emulator_enabled', true),
|
|
'alert_ddos_enabled' => $this->getSettingBool('alert_ddos_enabled', true),
|
|
'alert_ddos_threshold' => (int) $this->getSetting('alert_ddos_threshold', '100'),
|
|
'alert_ddos_auto_block' => $this->getSettingBool('alert_ddos_auto_block'),
|
|
'alert_errors_enabled' => $this->getSettingBool('alert_errors_enabled', true),
|
|
'alert_error_threshold' => (int) $this->getSetting('alert_error_threshold', '10'),
|
|
'alert_min_severity' => $this->getSetting('alert_min_severity', AlertSeverity::ERROR->value),
|
|
'emulator_github_url' => $this->getSetting('emulator_github_url', ''),
|
|
'emulator_source_repo' => $this->getSetting('emulator_source_repo', ''),
|
|
'emulator_jar_direct_url' => $this->getSetting('emulator_jar_direct_url', ''),
|
|
'emulator_jar_path' => $this->getSetting('emulator_jar_path', '/root/emulator'),
|
|
'emulator_source_path' => $this->getSetting('emulator_source_path', '/var/www/emulator-source'),
|
|
'emulator_service_name' => $this->getSetting('emulator_service_name', 'arcturus'),
|
|
'emulator_github_branch' => $this->getSetting('emulator_github_branch', 'main'),
|
|
'emulator_database_host' => $this->getSetting('emulator_database_host', '127.0.0.1'),
|
|
'emulator_database_port' => $this->getSetting('emulator_database_port', '3306'),
|
|
'emulator_database_name' => $this->getSetting('emulator_database_name', ''),
|
|
'emulator_database_username' => $this->getSetting('emulator_database_username', ''),
|
|
'emulator_database_password' => $this->getSetting('emulator_database_password', ''),
|
|
'emulator_version' => $this->getSetting('emulator_version', 'Onbekend'),
|
|
'hotel_alert_message' => '',
|
|
];
|
|
}
|
|
|
|
public function form(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->components([
|
|
Section::make('E-mail Meldingen')
|
|
->description('Ontvang alerts via e-mail')
|
|
->icon('heroicon-o-envelope')
|
|
->columns(2)
|
|
->afterHeader([
|
|
Action::make('test_email')
|
|
->label('Test E-mail')
|
|
->action('testEmail')
|
|
->color('info'),
|
|
Action::make('save_email')
|
|
->label('Opslaan')
|
|
->action('saveEmail')
|
|
->color('primary'),
|
|
])
|
|
->schema([
|
|
Toggle::make('alert_email_enabled')
|
|
->label('E-mail Meldingen Inschakelen'),
|
|
TextInput::make('alert_email_address')
|
|
->label('E-mail Adres')
|
|
->email()
|
|
->placeholder('admin@example.com')
|
|
->columnSpanFull(),
|
|
]),
|
|
Section::make('Discord Webhook')
|
|
->description('Ontvang alerts via Discord')
|
|
->icon('heroicon-o-globe-alt')
|
|
->columns(2)
|
|
->afterHeader([
|
|
Action::make('test_discord')
|
|
->label('Test Discord')
|
|
->action('testDiscord')
|
|
->color('info'),
|
|
Action::make('save_discord')
|
|
->label('Opslaan')
|
|
->action('saveDiscord')
|
|
->color('primary'),
|
|
])
|
|
->schema([
|
|
Toggle::make('alert_discord_enabled')
|
|
->label('Discord Meldingen Inschakelen'),
|
|
TextInput::make('alert_discord_webhook_url')
|
|
->label('Webhook URL')
|
|
->url()
|
|
->columnSpanFull()
|
|
->placeholder('https://discord.com/api/webhooks/...'),
|
|
]),
|
|
Section::make('📊 Status Dashboard')
|
|
->description('Live hotel statistieken')
|
|
->icon('heroicon-o-chart-bar')
|
|
->columns(3)
|
|
->afterHeader([
|
|
Action::make('refresh_dashboard')
|
|
->label('Vernieuwen')
|
|
->icon('heroicon-o-arrow-path')
|
|
->action('refreshDashboard')
|
|
->color('info'),
|
|
])
|
|
->schema([
|
|
Placeholder::make('online_users')
|
|
->label('')
|
|
->content(function () {
|
|
$label = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">';
|
|
$label .= '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>';
|
|
$label .= '<span style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;">Online Gebruikers</span>';
|
|
$label .= '<span style="height:1px;flex:1;background:linear-gradient(90deg,rgba(100,116,139,0.3),transparent);"></span>';
|
|
$label .= '</div>';
|
|
|
|
return new HtmlString($label . $this->getOnlineUsersHtml());
|
|
}),
|
|
Placeholder::make('uptime')
|
|
->label('')
|
|
->content(function () {
|
|
$label = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">';
|
|
$label .= '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>';
|
|
$label .= '<span style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;">Uptime</span>';
|
|
$label .= '<span style="height:1px;flex:1;background:linear-gradient(90deg,rgba(100,116,139,0.3),transparent);"></span>';
|
|
$label .= '</div>';
|
|
|
|
return new HtmlString($label . $this->getUptimeHtml());
|
|
}),
|
|
Placeholder::make('server_load')
|
|
->label('')
|
|
->content(function () {
|
|
$label = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">';
|
|
$label .= '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/></svg>';
|
|
$label .= '<span style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;">Server Load</span>';
|
|
$label .= '<span style="height:1px;flex:1;background:linear-gradient(90deg,rgba(100,116,139,0.3),transparent);"></span>';
|
|
$label .= '</div>';
|
|
|
|
return new HtmlString($label . $this->getServerLoadHtml());
|
|
}),
|
|
]),
|
|
Section::make('📢 Hotel Alert')
|
|
->description('Stuur een bericht naar alle online gebruikers')
|
|
->icon('heroicon-o-megaphone')
|
|
->columns(2)
|
|
->afterHeader([
|
|
Action::make('send_hotel_alert')
|
|
->label('Verstuur Alert')
|
|
->icon('heroicon-o-paper-airplane')
|
|
->action('sendHotelAlert')
|
|
->color('danger')
|
|
->requiresConfirmation()
|
|
->modalHeading('Hotel Alert Versturen')
|
|
->modalDescription('Dit bericht wordt naar ALLE online gebruikers gestuurd. Doorgaan?'),
|
|
])
|
|
->schema([
|
|
TextInput::make('hotel_alert_message')
|
|
->label('Bericht')
|
|
->placeholder('Typ hier je alert bericht...')
|
|
->columnSpanFull(),
|
|
]),
|
|
Section::make('📊 Activity Heatmap')
|
|
->description('Activiteit per uur (laatste 30 dagen)')
|
|
->icon('heroicon-o-chart-bar')
|
|
->columnSpanFull()
|
|
->afterHeader([
|
|
Action::make('refresh_heatmap')
|
|
->label('Vernieuwen')
|
|
->icon('heroicon-o-arrow-path')
|
|
->action('refreshDashboard')
|
|
->color('info'),
|
|
])
|
|
->schema([
|
|
Placeholder::make('activity_heatmap')
|
|
->label('')
|
|
->content(fn () => $this->getActivityHeatmapHtml()),
|
|
]),
|
|
Section::make('Emulator Monitoring')
|
|
->description('Monitor de emulator status en ontvang alerts bij problemen')
|
|
->icon('heroicon-o-server')
|
|
->columns(2)
|
|
->afterHeader([
|
|
Action::make('check_emulator')
|
|
->label('Check Nu')
|
|
->action('checkEmulator')
|
|
->color('warning'),
|
|
Action::make('save_emulator')
|
|
->label('Opslaan')
|
|
->action('saveEmulator')
|
|
->color('primary'),
|
|
Action::make('start_emulator')
|
|
->label('Start')
|
|
->icon('heroicon-o-play')
|
|
->color('success')
|
|
->requiresConfirmation()
|
|
->action('startEmulator'),
|
|
Action::make('stop_emulator')
|
|
->label('Stop')
|
|
->icon('heroicon-o-stop')
|
|
->color('danger')
|
|
->requiresConfirmation()
|
|
->action('stopEmulator'),
|
|
Action::make('restart_emulator')
|
|
->label('Restart')
|
|
->icon('heroicon-o-arrow-path')
|
|
->color('warning')
|
|
->requiresConfirmation()
|
|
->action('restartEmulator'),
|
|
])
|
|
->schema([
|
|
Toggle::make('alert_emulator_enabled')
|
|
->label('Emulator Monitoring Inschakelen')
|
|
->helperText('Stuurt een alert wanneer de emulator offline gaat'),
|
|
Placeholder::make('emulator_status')
|
|
->label('')
|
|
->content(function () {
|
|
$label = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">';
|
|
$label .= '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>';
|
|
$label .= '<span style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;">Huidige Status</span>';
|
|
$label .= '<span style="height:1px;flex:1;background:linear-gradient(90deg,rgba(100,116,139,0.3),transparent);"></span>';
|
|
$label .= '</div>';
|
|
|
|
return new HtmlString($label . $this->getEmulatorStatusHtml());
|
|
}),
|
|
]),
|
|
Section::make('📜 Emulator Logs')
|
|
->description('Bekijk live logs van de emulator')
|
|
->icon('heroicon-o-document-text')
|
|
->columnSpanFull()
|
|
->schema([
|
|
Placeholder::make('log_viewer')
|
|
->label('')
|
|
->content(fn () => view('filament.components.emulator-log-viewer')),
|
|
]),
|
|
Section::make('DDoS Detectie')
|
|
->description('Monitor verkeer en detecteer mogelijke DDoS aanvallen')
|
|
->icon('heroicon-o-shield-exclamation')
|
|
->columns(2)
|
|
->afterHeader([
|
|
Action::make('run_ddos_check')
|
|
->label('Run Check')
|
|
->action('runDdosCheck')
|
|
->color('warning'),
|
|
Action::make('save_ddos')
|
|
->label('Opslaan')
|
|
->action('saveDdos')
|
|
->color('primary'),
|
|
])
|
|
->schema([
|
|
Toggle::make('alert_ddos_enabled')
|
|
->label('DDoS Monitoring Inschakelen'),
|
|
TextInput::make('alert_ddos_threshold')
|
|
->label('Drempel (requests per IP)')
|
|
->numeric()
|
|
->minValue(10)
|
|
->default(100),
|
|
Toggle::make('alert_ddos_auto_block')
|
|
->label('Automatisch IPs Blokkeren')
|
|
->helperText('Blokkeert verdachte IPs automatisch via iptables'),
|
|
Placeholder::make('blocked_ips')
|
|
->label('')
|
|
->content(function () {
|
|
$label = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">';
|
|
$label .= '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><line x1="12" y1="2" x2="12" y2="12"/></svg>';
|
|
$label .= '<span style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;">Geblokkeerde IPs</span>';
|
|
$label .= '<span style="height:1px;flex:1;background:linear-gradient(90deg,rgba(100,116,139,0.3),transparent);"></span>';
|
|
$label .= '</div>';
|
|
|
|
return new HtmlString($label . $this->getBlockedIpsHtml());
|
|
}),
|
|
]),
|
|
Section::make('Error Monitoring')
|
|
->description('Monitor kritieke errors en exceptions')
|
|
->icon('heroicon-o-exclamation-circle')
|
|
->columns(2)
|
|
->afterHeader([
|
|
Action::make('save_errors')
|
|
->label('Opslaan')
|
|
->action('saveErrors')
|
|
->color('primary'),
|
|
])
|
|
->schema([
|
|
Toggle::make('alert_errors_enabled')
|
|
->label('Error Monitoring Inschakelen'),
|
|
TextInput::make('alert_error_threshold')
|
|
->label('Error Drempel (per 5 min)')
|
|
->numeric()
|
|
->minValue(1)
|
|
->default(10),
|
|
Select::make('alert_min_severity')
|
|
->label('Minimale Ernst')
|
|
->options([
|
|
AlertSeverity::INFO->value => 'Info',
|
|
AlertSeverity::WARNING->value => 'Warning',
|
|
AlertSeverity::ERROR->value => 'Error',
|
|
AlertSeverity::CRITICAL->value => 'Critical',
|
|
])
|
|
->default(AlertSeverity::ERROR->value),
|
|
]),
|
|
|
|
|
|
|
|
|
|
|
|
Section::make('Statistieken')
|
|
->description('Overzicht van recente alerts')
|
|
->icon('heroicon-o-chart-bar')
|
|
->schema([
|
|
Placeholder::make('stats')
|
|
->label('')
|
|
->content(fn () => $this->getStatsHtml()),
|
|
]),
|
|
|
|
])
|
|
->statePath('data');
|
|
}
|
|
|
|
private function getSetting(string $key, string $default = ''): string
|
|
{
|
|
return setting($key, $default);
|
|
}
|
|
|
|
private function getSettingBool(string $key, bool $default = false): bool
|
|
{
|
|
return (bool) setting($key, $default);
|
|
}
|
|
|
|
public function getEmulatorStatusHtml(): HtmlString
|
|
{
|
|
$rconService = new RconService;
|
|
$isConnected = $rconService->isConnected();
|
|
|
|
if ($isConnected) {
|
|
return new HtmlString('<span class="text-green-600 font-semibold">● ONLINE - Emulator is verbonden</span>');
|
|
}
|
|
|
|
return new HtmlString('<span class="text-red-600 font-semibold">● OFFLINE - Emulator is niet bereikbaar</span>');
|
|
}
|
|
|
|
public function getBlockedIpsHtml(): HtmlString
|
|
{
|
|
$blockedIps = DDoSDetectionCommand::getBlockedIps();
|
|
|
|
if ($blockedIps === []) {
|
|
return new HtmlString('<span class="text-gray-500">Geen geblokkeerde IPs</span>');
|
|
}
|
|
|
|
$list = implode(', ', $blockedIps);
|
|
|
|
return new HtmlString("<span class=\"text-red-600\">{$list}</span>");
|
|
}
|
|
|
|
public function getStatsHtml(): HtmlString
|
|
{
|
|
$total = AlertLog::count();
|
|
$unread = AlertLog::unread()->count();
|
|
$critical = AlertLog::critical()->count();
|
|
$resolved = AlertLog::where('is_read', true)->count();
|
|
$today = AlertLog::where('created_at', '>=', now()->startOfDay())->count();
|
|
$week = AlertLog::where('created_at', '>=', now()->startOfWeek())->count();
|
|
|
|
$html = '<style>';
|
|
$html .= '.stats-card { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); border-radius: 12px; padding: 16px; color: white; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif; }';
|
|
$html .= '.stats-card .grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }';
|
|
$html .= '.stats-card .grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }';
|
|
$html .= '.stats-card .stat-box { text-align: center; padding: 14px 8px; background: rgba(255,255,255,0.05); border-radius: 10px; }';
|
|
$html .= '.stats-card .stat-icon { font-size: 20px; margin-bottom: 6px; }';
|
|
$html .= '.stats-card .stat-number { font-size: 28px; font-weight: 700; line-height: 1; }';
|
|
$html .= '.stats-card .stat-label { font-size: 9px; text-transform: uppercase; letter-spacing: 1px; color: #64748b; margin-top: 6px; }';
|
|
$html .= '.stats-card .divider { height: 1px; background: rgba(255,255,255,0.08); margin: 12px 0; }';
|
|
$html .= '.stats-card .mini-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }';
|
|
$html .= '.stats-card .mini-box { text-align: center; padding: 10px 6px; background: rgba(255,255,255,0.04); border-radius: 8px; }';
|
|
$html .= '.stats-card .mini-number { font-size: 18px; font-weight: 700; }';
|
|
$html .= '.stats-card .mini-label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; color: #64748b; margin-top: 2px; }';
|
|
$html .= '.stats-card .section-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; color: #64748b; margin-bottom: 10px; }';
|
|
$html .= '</style>';
|
|
|
|
$html .= '<div class="stats-card">';
|
|
|
|
// Main stats grid
|
|
$html .= '<div class="grid-3">';
|
|
$html .= '<div class="stat-box"><div class="stat-number" style="color:#60a5fa;">' . $total . '</div><div class="stat-label">Totaal Alerts</div></div>';
|
|
$html .= '<div class="stat-box"><div class="stat-number" style="color:' . ($unread > 0 ? '#fbbf24' : '#4ade80') . ';">' . $unread . '</div><div class="stat-label">Ongelezen</div></div>';
|
|
$html .= '<div class="stat-box"><div class="stat-number" style="color:' . ($critical > 0 ? '#f87171' : '#4ade80') . ';">' . $critical . '</div><div class="stat-label">Kritiek</div></div>';
|
|
$html .= '</div>';
|
|
|
|
// Divider
|
|
$html .= '<div class="divider"></div>';
|
|
|
|
// Secondary stats
|
|
$html .= '<div class="section-title">Laatste Periode</div>';
|
|
$html .= '<div class="mini-grid">';
|
|
$html .= '<div class="mini-box"><div class="mini-number" style="color:#60a5fa;">' . $today . '</div><div class="mini-label">Vandaag</div></div>';
|
|
$html .= '<div class="mini-box"><div class="mini-number" style="color:#818cf8;">' . $week . '</div><div class="mini-label">Deze Week</div></div>';
|
|
$html .= '<div class="mini-box"><div class="mini-number" style="color:#4ade80;">' . $resolved . '</div><div class="mini-label">Opgelost</div></div>';
|
|
$html .= '</div>';
|
|
|
|
$html .= '</div>';
|
|
|
|
return new HtmlString($html);
|
|
}
|
|
|
|
public function saveEmail(): void
|
|
{
|
|
$this->saveSettings(['alert_email_enabled', 'alert_email_address']);
|
|
}
|
|
|
|
public function saveDiscord(): void
|
|
{
|
|
$this->saveSettings(['alert_discord_enabled', 'alert_discord_webhook_url']);
|
|
}
|
|
|
|
public function saveEmulator(): void
|
|
{
|
|
$this->saveSettings(['alert_emulator_enabled']);
|
|
}
|
|
|
|
public function saveDdos(): void
|
|
{
|
|
$this->saveSettings(['alert_ddos_enabled', 'alert_ddos_threshold', 'alert_ddos_auto_block']);
|
|
}
|
|
|
|
public function saveErrors(): void
|
|
{
|
|
$this->saveSettings(['alert_errors_enabled', 'alert_error_threshold', 'alert_min_severity']);
|
|
}
|
|
|
|
private function saveSettings(array $keys): void
|
|
{
|
|
foreach ($keys as $key) {
|
|
$value = $this->data[$key] ?? null;
|
|
WebsiteSetting::updateOrCreate(
|
|
['key' => $key],
|
|
['value' => is_bool($value) ? ($value ? '1' : '0') : (string) $value],
|
|
);
|
|
}
|
|
|
|
Notification::make()
|
|
->success()
|
|
->title(__('Saved'))
|
|
->body(__('Alert settings have been saved successfully.'))
|
|
->send();
|
|
}
|
|
|
|
public function clearAllLogs(): void
|
|
{
|
|
AlertLog::truncate();
|
|
Cache::flush();
|
|
|
|
Notification::make()
|
|
->success()
|
|
->title('🗑️ Alle Logs Geleegd!')
|
|
->body('Alle logs zijn gewist.')
|
|
->send();
|
|
|
|
$this->fillForm();
|
|
}
|
|
|
|
public function getOnlineUsersHtml(): HtmlString
|
|
{
|
|
try {
|
|
$count = DB::connection('mysql')->table('users')->where('online', '1')->count();
|
|
$users = DB::connection('mysql')->table('users')
|
|
->where('online', '1')
|
|
->orderByDesc('last_login')
|
|
->limit(5)
|
|
->get(['username', 'rank']);
|
|
|
|
$html = '<div class="text-2xl font-bold text-green-400">' . $count . '</div>';
|
|
if ($users->count() > 0) {
|
|
$html .= '<div class="text-xs text-gray-400 mt-1">';
|
|
foreach ($users as $u) {
|
|
$html .= $u->username . ', ';
|
|
}
|
|
$html = rtrim($html, ', ') . '</div>';
|
|
}
|
|
|
|
return new HtmlString($html);
|
|
} catch (\Exception) {
|
|
return new HtmlString('<span class="text-red-400">Could not load</span>');
|
|
}
|
|
}
|
|
|
|
public function getUptimeHtml(): HtmlString
|
|
{
|
|
try {
|
|
$serviceName = setting('emulator_service_name', 'emulator');
|
|
$result = Process::timeout(5)->run("systemctl show {$serviceName} --property=ActiveEnterTimestamp --no-pager 2>/dev/null | cut -d= -f2");
|
|
if ($result->successful() && ! in_array(trim($result->output()), ['', '0'], true)) {
|
|
$startTime = trim($result->output());
|
|
$startTimestamp = strtotime($startTime);
|
|
$now = time();
|
|
$diff = $now - $startTimestamp;
|
|
$hours = floor($diff / 3600);
|
|
$minutes = floor(($diff % 3600) / 60);
|
|
$seconds = $diff % 60;
|
|
if ($hours > 0) {
|
|
$uptime = "{$hours}u {$minutes}m {$seconds}s";
|
|
} elseif ($minutes > 0) {
|
|
$uptime = "{$minutes}m {$seconds}s";
|
|
} else {
|
|
$uptime = "{$seconds}s";
|
|
}
|
|
|
|
return new HtmlString('<div class="text-sm text-green-400">' . $uptime . '</div>');
|
|
}
|
|
|
|
return new HtmlString('<span class="text-yellow-400">Not active</span>');
|
|
} catch (\Exception) {
|
|
return new HtmlString('<span class="text-red-400">Could not load</span>');
|
|
}
|
|
}
|
|
|
|
public function getServerLoadHtml(): HtmlString
|
|
{
|
|
try {
|
|
$load = sys_getloadavg();
|
|
$cpuCount = (int) Process::run('nproc 2>/dev/null')->output() ?: 1;
|
|
$memoryUsage = Process::run("free -m | awk '/Mem:/ {printf \"%d%% (%dMB / %dMB)\", $3/$2*100, $3, $2}'")->output();
|
|
$diskUsage = Process::run("df -h / | awk 'NR==2 {print $5 \" used\"}'")->output();
|
|
|
|
$html = '<div class="text-sm space-y-1">';
|
|
$html .= '<div><span class="text-gray-400">CPU Load:</span> <span class="text-green-400">' . $load[0] . '</span> (' . $cpuCount . ' cores)</div>';
|
|
if ($memoryUsage) {
|
|
$html .= '<div><span class="text-gray-400">Memory:</span> <span class="text-blue-400">' . trim($memoryUsage) . '</span></div>';
|
|
}
|
|
if ($diskUsage) {
|
|
$html .= '<div><span class="text-gray-400">Disk:</span> <span class="text-purple-400">' . trim($diskUsage) . '</span></div>';
|
|
}
|
|
$html .= '</div>';
|
|
|
|
return new HtmlString($html);
|
|
} catch (\Exception) {
|
|
return new HtmlString('<span class="text-red-400">Could not load</span>');
|
|
}
|
|
}
|
|
|
|
public function refreshDashboard(): void
|
|
{
|
|
Notification::make()
|
|
->success()
|
|
->title('Dashboard')
|
|
->body('Statistieken vernieuwd')
|
|
->send();
|
|
}
|
|
|
|
public function sendHotelAlert(): void
|
|
{
|
|
if (empty($this->data['hotel_alert_message'])) {
|
|
Notification::make()
|
|
->warning()
|
|
->title('Bericht Vereist')
|
|
->body('Vul eerst een bericht in.')
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$rcon = new RconService;
|
|
$result = $rcon->sendCommand('hotelalert', ['message' => $this->data['hotel_alert_message']]);
|
|
|
|
Notification::make()
|
|
->success()
|
|
->title('Hotel Alert')
|
|
->body('Bericht verzonden naar alle online gebruikers!')
|
|
->send();
|
|
|
|
$this->data['hotel_alert_message'] = '';
|
|
} catch (\Exception $e) {
|
|
Notification::make()
|
|
->danger()
|
|
->title('Fout')
|
|
->body('Kon hotel alert niet versturen: ' . $e->getMessage())
|
|
->send();
|
|
}
|
|
}
|
|
|
|
public function getActivityHeatmapHtml(): HtmlString
|
|
{
|
|
try {
|
|
$result = DB::connection('mysql')->select('
|
|
SELECT
|
|
HOUR(FROM_UNIXTIME(timestamp)) as hour,
|
|
COUNT(*) as count
|
|
FROM room_enter_log
|
|
WHERE timestamp > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))
|
|
GROUP BY hour
|
|
ORDER BY hour
|
|
');
|
|
|
|
$data = [];
|
|
$maxCount = 0;
|
|
foreach ($result as $r) {
|
|
$data[(int) $r->hour] = (int) $r->count;
|
|
if ((int) $r->count > $maxCount) {
|
|
$maxCount = (int) $r->count;
|
|
}
|
|
}
|
|
|
|
$html = '<div class="space-y-2">';
|
|
$html .= '<div class="grid grid-cols-24 gap-1 items-end" style="height: 150px; display: grid; grid-template-columns: repeat(24, 1fr); align-items: flex-end;">';
|
|
|
|
for ($hour = 0; $hour < 24; $hour++) {
|
|
$count = $data[$hour] ?? 0;
|
|
$height = $maxCount > 0 ? max(5, ($count / $maxCount) * 120) : 5;
|
|
$color = $this->getHeatmapColor($count, $maxCount);
|
|
$html .= '<div class="flex flex-col items-center justify-end" style="height: 100%;">';
|
|
$html .= '<div style="width: 100%; height: ' . $height . 'px; background-color: ' . $color . '; border-radius: 2px;" title="' . $count . ' entries om ' . $hour . ':00"></div>';
|
|
$html .= '<span class="text-xs text-gray-400 mt-1">' . str_pad((string) $hour, 2, '0', STR_PAD_LEFT) . '</span>';
|
|
$html .= '</div>';
|
|
}
|
|
|
|
$html .= '</div>';
|
|
|
|
// Legend
|
|
$html .= '<div class="flex items-center justify-center gap-4 mt-4 text-xs text-gray-400">';
|
|
$html .= '<div class="flex items-center gap-1"><div class="w-3 h-3 rounded" style="background-color: #3b82f6;"></div> Laag</div>';
|
|
$html .= '<div class="flex items-center gap-1"><div class="w-3 h-3 rounded" style="background-color: #22c55e;"></div> Gemiddeld</div>';
|
|
$html .= '<div class="flex items-center gap-1"><div class="w-3 h-3 rounded" style="background-color: #eab308;"></div> Druk</div>';
|
|
$html .= '<div class="flex items-center gap-1"><div class="w-3 h-3 rounded" style="background-color: #ef4444;"></div> Heel druk</div>';
|
|
$html .= '</div>';
|
|
|
|
// Stats
|
|
$totalEntries = array_sum($data);
|
|
$busiestHour = array_search(max($data), $data);
|
|
$html .= '<div class="flex justify-between mt-4 text-sm text-gray-300">';
|
|
$html .= '<span>Totaal: <strong class="text-white">' . $totalEntries . '</strong> kamer bezoeken (30 dagen)</span>';
|
|
$html .= '<span>Drukste uur: <strong class="text-white">' . $busiestHour . ':00</strong></span>';
|
|
$html .= '</div>';
|
|
|
|
$html .= '</div>';
|
|
|
|
return new HtmlString($html);
|
|
} catch (\Exception $e) {
|
|
return new HtmlString('<span class="text-red-400">Kan heatmap niet laden: ' . $e->getMessage() . '</span>');
|
|
}
|
|
}
|
|
|
|
private function getHeatmapColor(int $count, int $max): string
|
|
{
|
|
if ($max === 0) {
|
|
return '#374151';
|
|
}
|
|
$ratio = $count / $max;
|
|
if ($ratio < 0.25) {
|
|
return '#3b82f6';
|
|
}
|
|
if ($ratio < 0.5) {
|
|
return '#22c55e';
|
|
}
|
|
if ($ratio < 0.75) {
|
|
return '#eab308';
|
|
}
|
|
|
|
return '#ef4444';
|
|
}
|
|
|
|
public function testEmail(): void
|
|
{
|
|
if (empty($this->data['alert_email_address'])) {
|
|
Notification::make()
|
|
->warning()
|
|
->title('E-mail Adres Vereist')
|
|
->body('Vul eerst een e-mail adres in.')
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if (empty($this->data['alert_email_enabled'])) {
|
|
Notification::make()
|
|
->warning()
|
|
->title('E-mail Uitgeschakeld')
|
|
->body('Schakel eerst e-mail meldingen in.')
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Cache::forget('website_settings');
|
|
$alertService = app(AlertService::class);
|
|
$result = $alertService->testAlert();
|
|
|
|
if (empty($result)) {
|
|
Notification::make()
|
|
->warning()
|
|
->title('Geen Kanalen Geconfigureerd')
|
|
->body('Schakel eerst e-mail alerts in en vul een e-mail adres in.')
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if (isset($result['email']) && $result['email'] === 'success') {
|
|
Notification::make()
|
|
->success()
|
|
->title('Test Verzonden')
|
|
->body('Test e-mail is verzonden naar ' . $this->data['alert_email_address'])
|
|
->send();
|
|
} elseif (isset($result['email']) && str_contains($result['email'], 'failed')) {
|
|
Notification::make()
|
|
->danger()
|
|
->title(__('Test Failed'))
|
|
->body(str_replace('failed: ', '', $result['email']))
|
|
->send();
|
|
} else {
|
|
Notification::make()
|
|
->danger()
|
|
->title(__('Test Failed'))
|
|
->body('E-mail alerts zijn niet ingeschakeld of niet geconfigureerd.')
|
|
->send();
|
|
}
|
|
} catch (\Exception $e) {
|
|
Notification::make()
|
|
->danger()
|
|
->title(__('Error'))
|
|
->body($e->getMessage())
|
|
->send();
|
|
}
|
|
}
|
|
|
|
public function testDiscord(): void
|
|
{
|
|
if (empty($this->data['alert_discord_webhook_url'])) {
|
|
Notification::make()
|
|
->warning()
|
|
->title('Webhook URL Vereist')
|
|
->body('Vul eerst een Discord webhook URL in.')
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if (empty($this->data['alert_discord_enabled'])) {
|
|
Notification::make()
|
|
->warning()
|
|
->title('Discord Uitgeschakeld')
|
|
->body('Schakel eerst Discord meldingen in.')
|
|
->send();
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Cache::forget('website_settings');
|
|
$alertService = app(AlertService::class);
|
|
$response = Http::post($this->data['alert_discord_webhook_url'], [
|
|
'username' => 'Atom CMS Alerts',
|
|
'embeds' => [
|
|
[
|
|
'title' => 'Test Alert',
|
|
'description' => 'Dit is een testmelding van het Atom CMS Alert Systeem.',
|
|
'color' => 3447003,
|
|
'footer' => ['text' => 'Atom CMS Alert System'],
|
|
'timestamp' => now()->toIso8601String(),
|
|
],
|
|
],
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
Notification::make()
|
|
->success()
|
|
->title('Test Verzonden')
|
|
->body('Testbericht is succesvol naar Discord gestuurd.')
|
|
->send();
|
|
} else {
|
|
Notification::make()
|
|
->danger()
|
|
->title(__('Error'))
|
|
->body('Kon bericht niet versturen. Status: ' . $response->status())
|
|
->send();
|
|
}
|
|
} catch (\Exception $e) {
|
|
Notification::make()
|
|
->danger()
|
|
->title(__('Error'))
|
|
->body($e->getMessage())
|
|
->send();
|
|
}
|
|
}
|
|
|
|
public function checkEmulator(): void
|
|
{
|
|
Artisan::call('monitor:emulator', ['--notify-online' => true]);
|
|
|
|
$rconService = new RconService;
|
|
|
|
if ($rconService->isConnected()) {
|
|
Notification::make()
|
|
->success()
|
|
->title('Emulator Online')
|
|
->body('De emulator is online en reageert.')
|
|
->send();
|
|
} else {
|
|
Notification::make()
|
|
->danger()
|
|
->title('Emulator Offline')
|
|
->body('De emulator is niet bereikbaar via RCON.')
|
|
->send();
|
|
}
|
|
|
|
$this->fillForm();
|
|
}
|
|
|
|
public function startEmulator(): void
|
|
{
|
|
$serviceName = setting('emulator_service_name', 'arcturus');
|
|
|
|
$result = Process::timeout(30)->run("sudo systemctl start {$serviceName} 2>&1");
|
|
|
|
if ($result->successful()) {
|
|
Notification::make()
|
|
->success()
|
|
->title('Emulator Gestart')
|
|
->body("Service '{$serviceName}' is gestart.")
|
|
->send();
|
|
} else {
|
|
Notification::make()
|
|
->danger()
|
|
->title('Start Mislukt')
|
|
->body($result->output() ?: 'Kon de emulator niet starten.')
|
|
->send();
|
|
}
|
|
}
|
|
|
|
public function stopEmulator(): void
|
|
{
|
|
$serviceName = setting('emulator_service_name', 'arcturus');
|
|
|
|
$result = Process::timeout(30)->run("sudo systemctl stop {$serviceName} 2>&1");
|
|
|
|
if ($result->successful()) {
|
|
Notification::make()
|
|
->success()
|
|
->title('Emulator Gestopt')
|
|
->body("Service '{$serviceName}' is gestopt.")
|
|
->send();
|
|
} else {
|
|
Notification::make()
|
|
->danger()
|
|
->title('Stop Mislukt')
|
|
->body($result->output() ?: 'Kon de emulator niet stoppen.')
|
|
->send();
|
|
}
|
|
}
|
|
|
|
public function restartEmulator(): void
|
|
{
|
|
$serviceName = setting('emulator_service_name', 'arcturus');
|
|
|
|
$result = Process::timeout(60)->run("sudo systemctl restart {$serviceName} 2>&1");
|
|
|
|
if ($result->successful()) {
|
|
Notification::make()
|
|
->success()
|
|
->title('Emulator Herstart')
|
|
->body("Service '{$serviceName}' is herstart.")
|
|
->send();
|
|
} else {
|
|
Notification::make()
|
|
->danger()
|
|
->title('Herstart Mislukt')
|
|
->body($result->output() ?: 'Kon de emulator niet herstarten.')
|
|
->send();
|
|
}
|
|
}
|
|
|
|
public function runDdosCheck(): void
|
|
{
|
|
Artisan::call('monitor:ddos', [
|
|
'--threshold' => $this->data['alert_ddos_threshold'] ?? 100,
|
|
]);
|
|
|
|
Notification::make()
|
|
->info()
|
|
->title('DDoS Check Gestart')
|
|
->body('De DDoS detectie check is uitgevoerd.')
|
|
->send();
|
|
}
|
|
|
|
private function formatBytes(int $bytes): string
|
|
{
|
|
if ($bytes >= 1073741824) {
|
|
return round($bytes / 1073741824, 2) . ' GB';
|
|
} elseif ($bytes >= 1048576) {
|
|
return round($bytes / 1048576, 2) . ' MB';
|
|
} elseif ($bytes >= 1024) {
|
|
return round($bytes / 1024, 2) . ' KB';
|
|
}
|
|
|
|
return $bytes . ' B';
|
|
}
|
|
|
|
private function getCurrentSiteUrl(): string
|
|
{
|
|
return setting('site_url', config('app.url', 'https://epicnabbo.nl'));
|
|
}
|
|
|
|
private function clearSettingsCache(): void
|
|
{
|
|
Cache::forget('website_settings');
|
|
SettingsService::clearCache();
|
|
}
|
|
}
|