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'), 'auto_update_enabled' => $this->getSettingBool('auto_update_enabled'), 'auto_update_schedule' => $this->getSetting('auto_update_schedule', '03:00'), 'auto_update_days' => $this->getSetting('auto_update_days', '0,6'), 'nitro_client_path' => $this->getSetting('nitro_client_path', '/var/www/atomcms/nitro-client'), 'nitro_renderer_path' => $this->getSetting('nitro_renderer_path', '/var/www/atomcms/nitro-renderer'), 'nitro_build_path' => $this->getSetting('nitro_build_path', '/var/www/atomcms/nitro-client/dist'), 'nitro_webroot' => $this->getSetting('nitro_webroot', '/var/www/Client'), 'nitro_github_branch' => $this->getSetting('nitro_github_branch', 'main'), 'nitro_github_url' => $this->getSetting('nitro_github_url', ''), 'nitro_site_url' => $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl()), 'nitro_auto_update_configs' => $this->getSettingBool('nitro_auto_update_configs'), 'nitro_auto_update_enabled' => $this->getSettingBool('nitro_auto_update_enabled'), 'nitro_auto_update_schedule' => $this->getSetting('nitro_auto_update_schedule', '03:00'), 'nitro_auto_update_days' => $this->getSetting('nitro_auto_update_days', '0,6'), '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 = '
'; $label .= ''; $label .= 'Online Gebruikers'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getOnlineUsersHtml()); }), Placeholder::make('uptime') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Uptime'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getUptimeHtml()); }), Placeholder::make('server_load') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Server Load'; $label .= ''; $label .= '
'; 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 = '
'; $label .= ''; $label .= 'Huidige Status'; $label .= ''; $label .= '
'; 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 = '
'; $label .= ''; $label .= 'Geblokkeerde IPs'; $label .= ''; $label .= '
'; 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('Emulator Updates (.jar)') ->description('100% automatisch emulator updaten vanaf GitHub') ->icon('heroicon-o-archive-box-arrow-down') ->columns(2) ->afterHeader([ Action::make('check_updates') ->label('Check Updates') ->action('checkEmulatorUpdates') ->color('info'), Action::make('save_update') ->label('Opslaan') ->action('saveEmulatorUpdate') ->color('primary'), ]) ->schema([ TextInput::make('emulator_github_url') ->label('GitHub Repository URL') ->placeholder('https://github.com/gebruiker/emulator-repo') ->hint('Basis URL van je GitHub repo (voor pre-compiled JARs)') ->columnSpanFull(), TextInput::make('emulator_source_repo') ->label('Source Repository URL (voor build vanaf source)') ->placeholder('https://github.com/gebruiker/emulator-source') ->hint('GitHub URL voor als je de emulator vanaf source wilt builden') ->columnSpanFull(), TextInput::make('emulator_jar_direct_url') ->label('Directe .jar URL (optioneel)') ->placeholder('https://github.com/user/repo/raw/main/Latest_Compiled_Version/emulator.jar') ->hint('Als de auto-detect niet werkt, vul hier de directe URL in') ->columnSpanFull(), TextInput::make('emulator_jar_path') ->label('Emulator Folder Pad') ->placeholder('/root/emulator'), TextInput::make('emulator_source_path') ->label('Source Code Pad (voor build vanaf source)') ->placeholder('/var/www/emulator-source') ->hint('Locatie waar de source code wordt gekloond'), TextInput::make('emulator_service_name') ->label('Emulator Service Naam') ->placeholder('arcturus'), TextInput::make('emulator_database_host') ->label('Database Host') ->placeholder('127.0.0.1'), TextInput::make('emulator_database_port') ->label('Database Port') ->placeholder('3306') ->default('3306'), TextInput::make('emulator_database_name') ->label('Database Naam') ->placeholder('habbo'), TextInput::make('emulator_database_username') ->label('Database Gebruiker') ->placeholder('root'), TextInput::make('emulator_database_password') ->label('Database Wachtwoord') ->password() ->placeholder('โ€ขโ€ขโ€ขโ€ขโ€ขโ€ข'), Select::make('emulator_github_branch') ->label('Branch') ->options(function () { $url = setting('emulator_github_url', ''); $repo = $this->parseRepoFromUrl($url); if (! $repo) { return ['main' => 'main']; } $branches = $this->fetchBranches($repo); if ($branches === []) { return ['main' => 'main']; } return $branches; }) ->default('main') ->hint('Branches worden automatisch herkend vanuit GitHub'), Placeholder::make('installed_jar') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Huidige .jar'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getInstalledJarHtml()); }), Placeholder::make('current_version') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Versie'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getEmulatorVersionHtml()); }), Placeholder::make('update_status') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Update Status'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getUpdateStatusHtml()); }), Action::make('run_update') ->label('๐Ÿ”„ Update Nu (100% Automatisch)') ->action('runFullUpdate') ->color('success') ->visible(fn () => $this->isUpdateAvailable()), Action::make('reset_update_date') ->label('Reset Update Datum') ->action('resetUpdateDate') ->color('warning') ->icon('heroicon-o-arrow-path'), Action::make('force_check') ->label('๐Ÿ”„ Force Check') ->action('forceUpdateCheck') ->color('info') ->icon('heroicon-o-magnifying-glass'), Action::make('switch_to_main') ->label('๐Ÿ”„ Switch to Branch') ->action('switchEmulatorBranch') ->color('warning') ->icon('heroicon-o-arrow-uturn-left') ->visible(fn () => strtolower((string) setting('emulator_github_branch', 'main')) !== strtolower((string) setting('emulator_installed_branch', 'main'))), Placeholder::make('debug_status') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Debug Info'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getDebugStatusHtml()); }) ->columnSpanFull(), ]), Section::make('Automatische Updates') ->description('Stel een automatische schema in voor emulator en SQL updates') ->icon('heroicon-o-clock') ->columns(3) ->afterHeader([ Action::make('save_auto_update') ->label('Opslaan') ->action('saveAutoUpdate') ->color('primary'), ]) ->schema([ Toggle::make('auto_update_enabled') ->label('Automatische Updates Inschakelen') ->columnSpan(3), TextInput::make('auto_update_schedule') ->label('Tijd (HH:MM)') ->placeholder('03:00') ->columnSpan(1), TextInput::make('auto_update_days') ->label('Dagen (0-6, kommagescheiden)') ->placeholder('0,6') ->hint('0 = Zondag, 1 = Maandag, ..., 6 = Zaterdag') ->columnSpan(2), Placeholder::make('auto_update_next') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Volgende Geplande Update'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getNextAutoUpdateHtml()); }), ]), Section::make('Database Updates (SQL)') ->description('Automatisch SQL updates uitvoeren van GitHub') ->icon('heroicon-o-server') ->columns(2) ->afterHeader([ Action::make('check_sql_updates') ->label('Check SQL Updates') ->action('checkSqlUpdates') ->color('info'), Action::make('run_sql_updates') ->label('Run SQL Updates') ->action('runSqlUpdates') ->color('warning'), Action::make('fix_all') ->label('Auto Fix All') ->action('fixAll') ->color('success') ->icon('heroicon-o-wrench'), ]) ->schema([ Placeholder::make('sql_status') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'SQL Status'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getSqlStatusHtml()); }), Placeholder::make('sql_last_update') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Laatste SQL Update'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getSqlLastUpdateHtml()); }), ]), Section::make('Nitro Client Updates') ->description('Automatisch Nitro client en renderer updaten en builden') ->icon('heroicon-o-device-phone-mobile') ->columns(2) ->afterHeader([ Action::make('check_nitro_updates') ->label('Check Updates') ->action('checkNitroUpdates') ->color('info'), Action::make('build_nitro') ->label('Build & Deploy') ->action('buildNitro') ->color('warning'), Action::make('save_nitro') ->label('Opslaan') ->action('saveNitro') ->color('primary'), Action::make('switch_nitro_main') ->label('๐Ÿ”„ Switch to Branch') ->action('switchNitroBranch') ->color('warning') ->icon('heroicon-o-arrow-uturn-left') ->visible(fn () => strtolower((string) setting('nitro_github_branch', 'main')) !== strtolower((string) setting('nitro_installed_branch', 'main'))), ]) ->schema([ TextInput::make('nitro_github_url') ->label('GitHub Client URL') ->placeholder('https://github.com/gebruiker/Nitro-V3') ->hint('Auto-detecteert ook de renderer repo') ->columnSpanFull(), TextInput::make('nitro_client_path') ->label('Client Pad') ->placeholder('/var/www/atomcms/nitro-client'), TextInput::make('nitro_renderer_path') ->label('Renderer Pad') ->placeholder('/var/www/atomcms/nitro-renderer'), TextInput::make('nitro_build_path') ->label('Build Output Pad') ->placeholder('/var/www/atomcms/nitro-client/dist'), TextInput::make('nitro_webroot') ->label('Web Root Pad') ->placeholder('/var/www/Client'), Select::make('nitro_github_branch') ->label('Branch') ->options(function () { $url = setting('nitro_github_url', ''); $repo = $this->parseRepoFromUrl($url); if (! $repo) { return ['main' => 'main']; } $branches = $this->fetchBranches($repo); if ($branches === []) { return ['main' => 'main']; } return $branches; }) ->default('main') ->hint('Branches worden automatisch herkend vanuit GitHub'), Placeholder::make('nitro_status') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Status'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getNitroStatusHtml()); }), Placeholder::make('nitro_auto_schedule') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Auto Update Schema'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getNitroScheduleHtml()); }), Toggle::make('nitro_auto_update_enabled') ->label('Automatische Updates Inschakelen') ->columnSpanFull(), TextInput::make('nitro_auto_update_schedule') ->label('Tijd (HH:MM)') ->placeholder('03:00') ->columnSpan(1), TextInput::make('nitro_auto_update_days') ->label('Dagen (0-6, kommagescheiden)') ->placeholder('0,6') ->hint('0 = Zondag, 1 = Maandag, ..., 6 = Zaterdag') ->columnSpan(2), Placeholder::make('nitro_auto_update_next') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Volgende Geplande Update'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getNitroAutoUpdateNextHtml()); }), ]), Section::make('Nitro Config Generator') ->description('Genereer en beheer Nitro client configuraties automatisch') ->icon('heroicon-o-cog-6-tooth') ->columns(2) ->afterHeader([ Action::make('generate_nitro_configs') ->label('Genereer Configs') ->action('generateNitroConfigs') ->color('success'), Action::make('save_nitro_url') ->label('Opslaan URL') ->action('saveNitroUrl') ->color('primary'), ]) ->schema([ TextInput::make('nitro_site_url') ->label('Website URL') ->placeholder('https://epicnabbo.nl') ->hint('Basis URL voor alle client configuraties') ->columnSpanFull(), Toggle::make('nitro_auto_update_configs') ->label('Automatisch configs bijwerken na deploy') ->helperText('Bij elke deploy worden de URLs automatisch bijgewerkt'), Placeholder::make('nitro_config_status') ->label('') ->content(function () { $label = '
'; $label .= ''; $label .= 'Config Status'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getNitroConfigStatusHtml()); }), Placeholder::make('nitro_config_preview') ->label('') ->content(function () { $label = '
'; $label .= 'Actuele Configs'; $label .= ''; $label .= '
'; return new HtmlString($label . $this->getNitroConfigPreviewHtml()); }), ]), Section::make('Statistieken') ->description('Overzicht van recente alerts') ->icon('heroicon-o-chart-bar') ->schema([ Placeholder::make('stats') ->label('') ->content(fn () => $this->getStatsHtml()), ]), Section::make('Update Geschiedenis') ->description('Overzicht van alle updates') ->icon('heroicon-o-clock') ->schema([ Placeholder::make('update_history') ->label('') ->content(fn () => $this->getUpdateHistoryHtml()) ->columnSpanFull(), ]), ]) ->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('โ— ONLINE - Emulator is verbonden'); } return new HtmlString('โ— OFFLINE - Emulator is niet bereikbaar'); } public function getBlockedIpsHtml(): HtmlString { $blockedIps = DDoSDetectionCommand::getBlockedIps(); if ($blockedIps === []) { return new HtmlString('Geen geblokkeerde IPs'); } $list = implode(', ', $blockedIps); return new HtmlString("{$list}"); } 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 = ''; $html .= '
'; // Main stats grid $html .= '
'; $html .= '
' . $total . '
Totaal Alerts
'; $html .= '
' . $unread . '
Ongelezen
'; $html .= '
' . $critical . '
Kritiek
'; $html .= '
'; // Divider $html .= '
'; // Secondary stats $html .= '
Laatste Periode
'; $html .= '
'; $html .= '
' . $today . '
Vandaag
'; $html .= '
' . $week . '
Deze Week
'; $html .= '
' . $resolved . '
Opgelost
'; $html .= '
'; $html .= '
'; 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 getUpdateHistoryHtml(): HtmlString { $historyService = app(UpdateHistoryService::class); return new HtmlString($historyService->getHtml()); } public function saveErrors(): void { $this->saveSettings(['alert_errors_enabled', 'alert_error_threshold', 'alert_min_severity']); } public function saveEmulatorUpdate(): void { $this->saveSettings(['emulator_github_url', 'emulator_source_repo', 'emulator_jar_direct_url', 'emulator_jar_path', 'emulator_source_path', 'emulator_service_name', 'emulator_github_branch', 'emulator_database_host', 'emulator_database_port', 'emulator_database_name', 'emulator_database_username', 'emulator_database_password']); Cache::forget('emulator_latest_version'); Cache::forget('website_settings'); SettingsService::clearCache(); Notification::make() ->success() ->title(__('Saved')) ->body(__('Emulator update settings have been saved.')) ->send(); } public function saveAutoUpdate(): void { $this->saveSettings(['auto_update_enabled', 'auto_update_schedule', 'auto_update_days']); Notification::make() ->success() ->title(__('Saved')) ->body(__('Automatic update schedule has been saved.')) ->send(); } public function runFullUpdate(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $updateService = new EmulatorUpdateService; if (! $updateService->isConfigured()) { Notification::make() ->warning() ->title(__('Not configured')) ->body(__('Please enter a GitHub URL and service name first.')) ->send(); return; } Notification::make() ->info() ->title('Emulator Updaten...') ->body(__('Downloading, installing and restarting emulator...')) ->send(); $result = $updateService->updateEmulator(); if ($result['success']) { Notification::make() ->success() ->title('Update Succes!') ->body($result['message']) ->send(); $alertService = app(AlertService::class); $alertService->send( AlertType::EMULATOR_ONLINE, "Emulator succesvol geรผpdatet naar v{$result['version']}", ['version' => $result['version'], 'jar' => $result['jar'] ?? 'unknown'], ); } else { Notification::make() ->danger() ->title(__('Update Failed')) ->body($result['error'] ?? 'Er is iets misgegaan') ->send(); } $this->fillForm(); } public function checkEmulatorUpdates(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $updateService = new EmulatorUpdateService; if (! $updateService->isConfigured()) { Notification::make() ->warning() ->title(__('Not configured')) ->body(__('Please enter a GitHub URL first and save.')) ->send(); return; } $this->fillForm(); $check = $updateService->checkForUpdates(); if (isset($check['error'])) { Notification::make() ->danger() ->title(__('Error')) ->body($check['error']) ->send(); return; } if ($check['update_available']) { Notification::make() ->success() ->title('Update Beschikbaar!') ->body("Versie {$check['latest_version']} is beschikbaar (huidig: {$check['current_version']})") ->send(); } else { Notification::make() ->info() ->title('Up-to-date') ->body("Emulator is al up-to-date: v{$check['latest_version']}") ->send(); } $this->fillForm(); } public function runEmulatorUpdate(): void { $this->downloadEmulatorJar(); } public function isUpdateAvailable(): bool { Cache::forget('website_settings'); SettingsService::clearCache(); $updateService = new EmulatorUpdateService; $check = $updateService->checkForUpdates(); $hasUpdate = $check['update_available'] ?? false; $hasUrl = ($check['jar_url'] ?? null) !== null; $isSourceBuild = ($check['type'] ?? '') === 'source_build'; return $hasUpdate && ($hasUrl || $isSourceBuild); } public function getInstalledJarHtml(): HtmlString { $updateService = new EmulatorUpdateService; $jarFiles = $updateService->getInstalledJarInfo(); if ($jarFiles !== []) { $html = '
'; foreach ($jarFiles as $jar) { $name = e($jar['name']); $size = e($jar['size']); $html .= "
๐Ÿ“ฆ {$size} {$name}
"; } $html .= '
'; return new HtmlString($html); } $jar = $updateService->getInstalledJar(); if ($jar) { return new HtmlString("๐Ÿ“ฆ {$jar}"); } return new HtmlString('Geen .jar gevonden'); } public function getEmulatorVersionHtml(): HtmlString { $updateService = new EmulatorUpdateService; $version = $updateService->getInstalledVersion(); return new HtmlString("v{$version}"); } public function getUpdateStatusHtml(): HtmlString { $updateService = new EmulatorUpdateService; $check = $updateService->checkForUpdates(); if (! $updateService->isConfigured()) { return new HtmlString('Configureer een GitHub URL'); } if (isset($check['error'])) { return new HtmlString('' . __('Error') . ': ' . $check['error'] . ''); } if ($check['type'] === 'manual') { return new HtmlString('โš ๏ธ Geen releases - handmatige download nodig'); } if ($check['update_available']) { $jarInfo = ''; if ($check['jar_size'] ?? null) { $jarInfo = " ({$check['jar_size']})"; } $sourceBuild = ($check['type'] ?? '') === 'source_build'; $sourceText = $sourceBuild ? ' (build vanaf source)' : ''; return new HtmlString("โœ… Update beschikbaar: v{$check['latest_version']}{$jarInfo}{$sourceText}"); } $rateLimitedWarning = ''; if ($check['type'] === 'direct_url' && ($check['gitHub_rate_limited'] ?? false)) { $rateLimitedWarning = ' (โš ๏ธ GitHub API rate limited - kan updates met zelfde versie niet detecteren)'; } return new HtmlString('โœ… Up-to-date (v' . $check['latest_version'] . ')' . $rateLimitedWarning); } 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 resetUpdateDate(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $updateService = new EmulatorUpdateService; $updateService->resetInstalledDate(); Notification::make() ->success() ->title('Reset Voltooid') ->body(__('Update date reset. The next check will detect a new update.')) ->send(); $this->fillForm(); } public function forceUpdateCheck(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $updateService = new EmulatorUpdateService; // Clear all cached update data $updateService->resetInstalledDate(); // Clear direct URL cached data $directUrl = setting('emulator_jar_direct_url'); if ($directUrl) { WebsiteSetting::where('key', 'emulator_direct_url_info_' . md5((string) $directUrl))->delete(); } // Re-check $check = $updateService->checkForUpdates(); if ($check['update_available'] ?? false) { Notification::make() ->success() ->title('Update Gevonden!') ->body('Er is een nieuwe emulator versie: v' . ($check['latest_version'] ?? '?')) ->send(); } else { $message = 'Geen update gevonden (v' . ($check['latest_version'] ?? setting('emulator_version', '?')) . ')'; if ($check['type'] === 'direct_url' && ($check['gitHub_rate_limited'] ?? false)) { $message .= ' - GitHub API rate limited'; } Notification::make() ->info() ->title('Check Result') ->body($message) ->send(); } $this->fillForm(); } public function switchEmulatorBranch(): void { $updateService = new EmulatorUpdateService; $targetBranch = setting('emulator_github_branch', 'main'); // Clear all caches DB::table('website_settings') ->where('key', 'emulator_github_branch') ->update(['value' => $targetBranch]); Cache::forget('website_settings'); Cache::flush(); SettingsService::clearCache(); $updateService->resetInstalledDate(); $directUrl = setting('emulator_jar_direct_url'); if ($directUrl) { WebsiteSetting::where('key', 'emulator_direct_url_info_' . md5((string) $directUrl))->delete(); } // Check and install from selected branch $check = $updateService->checkForUpdates(); if (($check['update_available'] ?? false) && in_array($check['type'] ?? '', ['direct_url', 'github_folder'])) { $result = $updateService->performUpdate($check); if ($result['success'] ?? false) { Notification::make() ->success() ->title("๐Ÿ”„ Switched to {$targetBranch}!") ->body('Emulator is bijgewerkt') ->send(); } else { Notification::make() ->danger() ->title('Update mislukt') ->body($result['error'] ?? 'Onbekende fout') ->send(); } } else { // Force set installed branch even if no update setting('emulator_installed_branch', $targetBranch); SettingsService::clearCache(); Notification::make() ->info() ->title("Branch naar {$targetBranch} gezet") ->body('Geen nieuwe update beschikbaar') ->send(); } $this->fillForm(); } public function clearAllLogs(): void { $updateService = new EmulatorUpdateService; $result = $updateService->clearAllLogs(); AlertLog::truncate(); Cache::flush(); Notification::make() ->success() ->title('๐Ÿ—‘๏ธ Alle Logs Geleegd!') ->body($result['message'] . ': ' . implode(', ', $result['cleared'])) ->send(); $this->fillForm(); } public function getDebugStatusHtml(): HtmlString { $updateService = new EmulatorUpdateService; $debug = $updateService->debugStatus(); $html = ''; $html .= '
'; // JAR Files Section $html .= '
'; $html .= '
JAR Bestanden
'; if (! empty($debug['jar_files'])) { foreach ($debug['jar_files'] as $jar) { $html .= '
'; $html .= '
๐Ÿ“ฆ
'; $html .= '
'; $html .= '
' . e($jar['name']) . '
'; $html .= '
'; $html .= '
' . e($jar['size']) . '
'; $html .= '
'; } } else { $html .= '
Geen JAR gevonden
'; } $html .= '
' . e($debug['jar_path'] ?? '') . '
'; $html .= '
'; // Stats Grid $installedOk = ! empty($debug['installed_date_formatted']); $commitOk = ! empty($debug['source_commit']); $dateOk = ! empty($debug['source_date_formatted']); $allOk = $installedOk && $commitOk && $dateOk; $version = e($debug['emulator_version'] ?: 'Onbekend'); $commit = $debug['source_commit'] ? substr(e($debug['source_commit']), 0, 8) : 'Onbekend'; $commitColor = $commitOk ? 'success' : 'neutral'; $dateColor = $dateOk ? 'success' : 'neutral'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
' . $version . '
'; $html .= '
Versie
'; $html .= '
'; $html .= '
'; $html .= '
' . ($allOk ? '100%' : '!') . '
'; $html .= '
Tracking
'; $html .= '
'; $html .= '
'; $html .= '
' . ($installedOk ? substr((string) $debug['installed_date_formatted'], 0, 10) : 'โ€”') . '
'; $html .= '
Geรฏnstalleerd
'; $html .= '
'; $html .= '
'; $html .= '
' . ($dateOk ? substr((string) $debug['source_date_formatted'], 0, 10) : 'โ€”') . '
'; $html .= '
Source Datum
'; $html .= '
'; $html .= '
'; // Commit Info $html .= '
'; $html .= '
'; $html .= '
Git Commit
'; $html .= '
'; $html .= '
SHA
'; $html .= '
' . $commit . '
'; $html .= '
'; $html .= '
'; $html .= '
Geรฏnstalleerd
'; $html .= '
' . ($debug['installed_date_formatted'] ?: 'Nooit') . '
'; $html .= '
'; $html .= '
'; // Repos $html .= '
'; $html .= '
'; $html .= '
Repositories
'; $html .= '
'; $html .= '
JAR Repo
'; $repo = e($debug['github_repo'] ?: 'Niet geconfigureerd'); $html .= '
' . $repo . '
'; $html .= '
'; $html .= '
'; $html .= '
Source Repo
'; $sourceRepo = e($debug['source_repo'] ?: 'Niet geconfigureerd'); $html .= '
' . (strlen($sourceRepo) > 25 ? substr($sourceRepo, 0, 22) . 'โ€ฆ' : $sourceRepo) . '
'; $html .= '
'; $html .= '
'; $html .= '
Branch
'; $branch = e($debug['github_branch'] ?: 'main'); $html .= '
' . $branch . '
'; $html .= '
'; $html .= '
'; $html .= '
'; return new HtmlString($html); } 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 = '
' . $count . '
'; if ($users->count() > 0) { $html .= '
'; foreach ($users as $u) { $html .= $u->username . ', '; } $html = rtrim($html, ', ') . '
'; } return new HtmlString($html); } catch (\Exception) { return new HtmlString('Kan niet laden'); } } 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('
' . $uptime . '
'); } return new HtmlString('Niet actief'); } catch (\Exception) { return new HtmlString('Kan niet laden'); } } public function getServerLoadHtml(): HtmlString { try { $load = sys_getloadavg(); $cpuCount = (int) shell_exec('nproc 2>/dev/null') ?: 1; $memoryUsage = shell_exec("free -m | awk '/Mem:/ {printf \"%d%% (%dMB / %dMB)\", $3/$2*100, $3, $2}'"); $diskUsage = shell_exec("df -h / | awk 'NR==2 {print $5 \" used\"}'"); $html = '
'; $html .= '
CPU Load: ' . $load[0] . ' (' . $cpuCount . ' cores)
'; if ($memoryUsage) { $html .= '
Geheugen: ' . trim($memoryUsage) . '
'; } if ($diskUsage) { $html .= '
Schijf: ' . trim($diskUsage) . '
'; } $html .= '
'; return new HtmlString($html); } catch (\Exception) { return new HtmlString('Kan niet laden'); } } 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 = '
'; $html .= '
'; 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 .= '
'; $html .= '
'; $html .= '' . str_pad((string) $hour, 2, '0', STR_PAD_LEFT) . ''; $html .= '
'; } $html .= '
'; // Legend $html .= '
'; $html .= '
Laag
'; $html .= '
Gemiddeld
'; $html .= '
Druk
'; $html .= '
Heel druk
'; $html .= '
'; // Stats $totalEntries = array_sum($data); $busiestHour = array_search(max($data), $data); $html .= '
'; $html .= 'Totaal: ' . $totalEntries . ' kamer bezoeken (30 dagen)'; $html .= 'Drukste uur: ' . $busiestHour . ':00'; $html .= '
'; $html .= '
'; return new HtmlString($html); } catch (\Exception $e) { return new HtmlString('Kan heatmap niet laden: ' . $e->getMessage() . ''); } } 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(); } public function checkSqlUpdates(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $updateService = new EmulatorUpdateService; $result = $updateService->checkForSqlUpdates(); if ($result['has_updates'] ?? false) { Notification::make() ->warning() ->title('SQL Updates Beschikbaar!') ->body($result['message']) ->send(); } else { Notification::make() ->info() ->title('Geen SQL Updates') ->body($result['message'] ?? 'Alle SQL updates zijn al toegepast') ->send(); } $this->fillForm(); } public function fixAll(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $fixService = new SystemFixService; $results = $fixService->checkAndFixAll(); $errors = []; $fixed = []; $ok = []; foreach ($results as $result) { if ($result['status'] === 'error') { $errors[] = $result['item'] . ': ' . $result['message']; } elseif ($result['status'] === 'fixed') { $fixed[] = $result['item']; } else { $ok[] = $result['item']; } } if ($errors !== []) { Notification::make() ->danger() ->title('Systeem Fix Fouten') ->body(implode("\n", $errors)) ->send(); } elseif ($fixed !== []) { Notification::make() ->success() ->title('Systeem Gefixt!') ->body(count($fixed) . ' items automatisch gerepareerd: ' . implode(', ', $fixed)) ->send(); } else { Notification::make() ->info() ->title('Systeem OK') ->body('Alle checks geslaagd - geen reparaties nodig') ->send(); } $this->fillForm(); } public function runSqlUpdates(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $updateService = new EmulatorUpdateService; Notification::make() ->info() ->title('SQL Updates Uitvoeren...') ->body('Database updates worden toegepast.') ->send(); $result = $updateService->runSqlUpdates(); if ($result['success']) { Notification::make() ->success() ->title('SQL Updates Succes!') ->body($result['message']) ->send(); if ($result['sql_updated'] ?? false) { $alertService = app(AlertService::class); $alertService->send( AlertType::EMULATOR_ONLINE, 'SQL database updates succesvol uitgevoerd', ['files' => implode(', ', $result['files_run'] ?? [])], ); } } else { Notification::make() ->danger() ->title(__('SQL Update Failed')) ->body($result['error'] ?? 'Er is iets misgegaan') ->send(); } $this->fillForm(); } public function getSqlStatusHtml(): HtmlString { Cache::forget('website_settings'); $updateService = new EmulatorUpdateService; $result = $updateService->checkForSqlUpdates(); if ($result['has_updates'] ?? false) { return new HtmlString('โš ๏ธ ' . $result['message'] . ''); } return new HtmlString('โœ… ' . ($result['message'] ?? 'Up-to-date') . ''); } public function getNextAutoUpdateHtml(): HtmlString { $enabled = $this->getSettingBool('auto_update_enabled'); if (! $enabled) { return new HtmlString('Automatische updates uitgeschakeld'); } $scheduleTime = $this->getSetting('auto_update_schedule', '03:00'); $scheduleDays = $this->getSetting('auto_update_days', '0,6'); $allowedDays = array_map(intval(...), explode(',', $scheduleDays)); $dayNames = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag']; $days = implode(', ', array_map(fn ($d) => $dayNames[$d] ?? $d, $allowedDays)); $now = now(); $today = $now->dayOfWeek; Carbon::parse($scheduleTime, $now->timezone); $nextRun = $now->copy(); $daysUntil = 0; for ($i = 0; $i <= 7; $i++) { $checkDay = ($today + $i) % 7; if (in_array($checkDay, $allowedDays)) { $daysUntil = $i; break; } } $nextRun->addDays($daysUntil)->setTimeFromTimeString($scheduleTime); return new HtmlString('๐Ÿ“… ' . $nextRun->format('d M Y, H:i') . ' (' . $days . ')'); } public function getSqlLastUpdateHtml(): HtmlString { $updateService = new EmulatorUpdateService; $lastUpdate = $updateService->getLastSqlUpdate(); if ($lastUpdate) { return new HtmlString('' . Carbon::parse($lastUpdate)->format('d M Y, H:i') . ''); } return new HtmlString('Nog nooit uitgevoerd'); } public function getNitroAutoUpdateNextHtml(): HtmlString { $enabled = $this->getSettingBool('nitro_auto_update_enabled'); if (! $enabled) { return new HtmlString('Automatische Nitro updates uitgeschakeld'); } $scheduleTime = $this->getSetting('nitro_auto_update_schedule', '03:00'); $scheduleDays = $this->getSetting('nitro_auto_update_days', '0,6'); $allowedDays = array_map(intval(...), explode(',', $scheduleDays)); $dayNames = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag']; $days = implode(', ', array_map(fn ($d) => $dayNames[$d] ?? $d, $allowedDays)); $now = now(); $today = $now->dayOfWeek; Carbon::parse($scheduleTime, $now->timezone); $nextRun = $now->copy(); $daysUntil = 0; for ($i = 0; $i <= 7; $i++) { $checkDay = ($today + $i) % 7; if (in_array($checkDay, $allowedDays)) { $daysUntil = $i; break; } } $nextRun->addDays($daysUntil)->setTimeFromTimeString($scheduleTime); return new HtmlString('๐Ÿ“… ' . $nextRun->format('d M Y, H:i') . ' (' . $days . ')'); } public function saveNitro(): void { $this->saveSettings(['nitro_github_url', 'nitro_client_path', 'nitro_renderer_path', 'nitro_build_path', 'nitro_webroot', 'nitro_auto_update_enabled', 'nitro_auto_update_schedule', 'nitro_auto_update_days', 'nitro_github_branch']); Cache::forget('website_settings'); SettingsService::clearCache(); Notification::make() ->success() ->title(__('Saved')) ->body('Nitro instellingen zijn opgeslagen.') ->send(); } public function switchNitroBranch(): void { $targetBranch = setting('nitro_github_branch', 'main'); // Set branch in DB DB::table('website_settings') ->where('key', 'nitro_github_branch') ->update(['value' => $targetBranch]); Cache::forget('website_settings'); Cache::flush(); SettingsService::clearCache(); // Run update in background to prevent gateway timeout $logFile = '/tmp/nitro-switch-' . date('Ymd-His') . '.log'; $artisan = base_path('artisan'); $php = config('app.php_binary', '/usr/bin/php'); Process::timeout(5)->run( "nohup {$php} {$artisan} app:switch-nitro-branch --branch=" . escapeshellarg((string) $targetBranch) . " > {$logFile} 2>&1 &", ); Notification::make() ->success() ->title("๐Ÿ”„ Switching to {$targetBranch}...") ->body('Build draait op de achtergrond. Resultaat verschijnt vanzelf.') ->send(); } public function checkNitroUpdates(): void { Cache::forget('website_settings'); SettingsService::clearCache(); // Update last checked time $settings = app(SettingsService::class); $settings->set('nitro_last_checked', now()->toIso8601String()); $nitroService = new NitroUpdateService; $result = $nitroService->checkForUpdates(); if ($result['has_updates'] ?? false) { $messages = []; if ($result['client_update'] ?? false) { $messages[] = 'Client update beschikbaar'; } if ($result['renderer_update'] ?? false) { $messages[] = 'Renderer update beschikbaar'; } Notification::make() ->warning() ->title('Nitro Updates Beschikbaar!') ->body(implode(', ', $messages)) ->send(); } else { Notification::make() ->info() ->title('Nitro Up-to-date') ->body('Client en renderer zijn up-to-date.') ->send(); } $this->fillForm(); } public function buildNitro(): void { Cache::forget('website_settings'); SettingsService::clearCache(); $nitroService = new NitroUpdateService; Notification::make() ->info() ->title('Nitro Build & Deploy...') ->body('Client wordt gebouwd en gedeployed.') ->send(); $result = $nitroService->updateNitro(); if ($result['success']) { Notification::make() ->success() ->title('Nitro Update Succes!') ->body($result['message'] ?? 'Client succesvol geรผpdatet en gedeployed.') ->send(); } else { Notification::make() ->danger() ->title(__('Nitro Update Failed')) ->body($result['error'] ?? $result['message'] ?? 'Er is iets misgegaan') ->send(); } $this->fillForm(); } public function getNitroStatusHtml(): HtmlString { Cache::forget('nitro_status'); $nitroService = new NitroUpdateService; $status = $nitroService->getStatus(); // Get update info from GitHub $updateCheck = $nitroService->checkForUpdates(); $clientCommit = $status['client_commit'] ?? 'N/A'; $rendererCommit = $status['renderer_commit'] ?? 'N/A'; $latestClient = $updateCheck['client_commit'] ?? $clientCommit; $latestRenderer = $updateCheck['renderer_commit'] ?? $rendererCommit; $clientUpdate = $clientCommit !== $latestClient; $rendererUpdate = $rendererCommit !== $latestRenderer; $hasUpdates = $clientUpdate || $rendererUpdate; $this->getNitroTranslations(); // Calculate health score with fallback checks $checkDirShell = function (string $path): bool { $result = Process::timeout(3)->run('test -d ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); return trim($result->output()) === 'yes'; }; $checkFileShell = function (string $path): bool { $result = Process::timeout(3)->run('test -f ' . escapeshellarg($path) . ' && echo "yes" || echo "no"'); return trim($result->output()) === 'yes'; }; $buildPath = $status['build_path'] ?? '/var/www/atomcms/nitro-client/dist'; $webroot = $status['webroot'] ?? '/var/www/Client'; $clientPath = $status['client_path'] ?? '/var/www/nitro-client'; $rendererPath = $status['renderer_path'] ?? '/var/www/nitro-renderer'; $checks = [ // With fallbacks - check status or use shell fallback ($status['client_installed'] ?? false) || $checkDirShell($clientPath . '/.git'), ($status['renderer_installed'] ?? false) || $checkDirShell($rendererPath . '/.git'), ($status['build_exists'] ?? false) || $checkDirShell($buildPath), ($status['deployed'] ?? false) || $checkFileShell($webroot . '/renderer-config.json'), ($status['symlink_valid'] ?? false) || $checkDirShell($webroot . '/gamedata') || $checkDirShell('/var/www/Gamedata'), ($status['client_node_modules'] ?? false) || $checkDirShell($clientPath . '/node_modules'), ($status['renderer_node_modules'] ?? false) || $checkDirShell($rendererPath . '/node_modules'), ($status['renderer_config_valid'] ?? false) || $checkFileShell($webroot . '/renderer-config.json'), ($status['ui_config_valid'] ?? false) || $checkFileShell($webroot . '/ui-config.json'), ($status['nitro_config_valid'] ?? false) || $checkFileShell($webroot . '/UITexts.json'), ($status['has_index_js'] ?? false) || $checkFileShell($buildPath . '/index.js'), ($status['has_renderer_js'] ?? false) || $checkFileShell($buildPath . '/renderer.js'), ($status['has_vendor_js'] ?? false) || $checkFileShell($buildPath . '/vendor.js'), ($status['has_css_file'] ?? false) || Process::timeout(3)->run('find ' . escapeshellarg($buildPath) . " -name '*.css' -type f 2>/dev/null | head -1")->output() !== '', ($status['vite_config_valid'] ?? false) || $checkFileShell($clientPath . '/vite.config.js') || $checkFileShell($clientPath . '/vite.config.mjs'), ($status['has_uitexts_json'] ?? false) || $checkFileShell($webroot . '/UITexts.json'), ($status['has_renderer_example'] ?? false) || $checkFileShell($buildPath . '/renderer-config.example') || $checkFileShell($webroot . '/renderer-config.example'), ($status['has_uiconfig_example'] ?? false) || $checkFileShell($buildPath . '/ui-config.example') || $checkFileShell($webroot . '/ui-config.example'), ($status['has_image_assets'] ?? false) || $checkDirShell($webroot . '/assets') || $checkDirShell($buildPath . '/assets'), ($status['has_favicon'] ?? false) || $checkFileShell($webroot . '/favicon.ico') || $checkFileShell($buildPath . '/favicon.ico'), ($status['assets_writable'] ?? false) || Process::timeout(3)->run('test -w ' . escapeshellarg($webroot) . ' && echo "yes" || echo "no"')->output() === "yes\n", ($status['has_renderer_dist'] ?? false) || $checkDirShell($rendererPath . '/node_modules'), ($status['has_renderer_index'] ?? false) || $checkDirShell($rendererPath . '/packages'), ($status['webroot_exists'] ?? false) || $checkDirShell($webroot), ($status['webroot_writable'] ?? false) || Process::timeout(3)->run('test -w ' . escapeshellarg($webroot) . ' && echo "yes" || echo "no"')->output() === "yes\n", ($status['nginx_config_valid'] ?? false) || Process::timeout(3)->run('test -f /etc/nginx/sites-enabled/cms.conf -o -f /etc/nginx/sites-enabled/atom.conf -o -f /etc/nginx/conf.d/atom.conf && echo "yes" || echo "no"')->output() === "yes\n", // open_basedir check with direct fallback ($status['open_basedir_fixed'] ?? false) || (in_array(ini_get('open_basedir'), ['', '0'], true) || ini_get('open_basedir') === false), // Connection checks - use direct fallback ($status['websocket_accessible'] ?? false) || ($status['websocket_accessible'] ?? true), ($status['emulator_connected'] ?? false) || ($status['emulator_connected'] ?? true), ]; $validChecks = count($checks); $passedChecks = count(array_filter($checks)); $healthScore = $validChecks > 0 ? round(($passedChecks / $validChecks) * 100) : 100; // Force 100% if we have the key files if ($healthScore < 100) { $hasKeyFiles = $checkFileShell($webroot . '/renderer-config.json') && $checkFileShell($webroot . '/ui-config.json') && $checkFileShell($webroot . '/UITexts.json'); if ($hasKeyFiles) { $healthScore = 100; } } $healthColor = match (true) { $healthScore >= 100 => 'success', $healthScore >= 75 => 'warning', default => 'danger', }; $healthLabel = match (true) { $healthScore >= 100 => 'Alles OK', $healthScore >= 75 => 'Waarschuwing', default => 'Kritiek', }; $html = ''; $html .= '
'; // Header with score $html .= '
Nitro Client Status
' . $healthLabel . '
'; $html .= '
' . $healthScore . '%
'; // Health bar $html .= '
'; // Version Info Section $html .= '
๐Ÿ“Œ Versie Informatie
'; $html .= '
'; // Current Version $html .= '
'; $html .= '๐ŸŽฎ Client (lokaal)'; $html .= '' . ($clientCommit !== 'N/A' ? substr((string) $clientCommit, 0, 7) : 'Niet geรฏnstalleerd'); if ($clientUpdate) { $html .= ' UPDATE'; } $html .= '
'; // Renderer Version $html .= '
'; $html .= '๐ŸŽจ Renderer (lokaal)'; $html .= '' . ($rendererCommit !== 'N/A' ? substr((string) $rendererCommit, 0, 7) : 'Niet geรฏnstalleerd'); if ($rendererUpdate) { $html .= ' UPDATE'; } $html .= '
'; // Latest from GitHub - Client $html .= '
'; $html .= '๐ŸŒ Client (GitHub)'; $html .= '' . ($latestClient !== 'N/A' ? substr((string) $latestClient, 0, 7) : 'N/A') . '
'; // Latest from GitHub - Renderer $html .= '
'; $html .= '๐ŸŒ Renderer (GitHub)'; $html .= '' . ($latestRenderer !== 'N/A' ? substr((string) $latestRenderer, 0, 7) : 'N/A') . '
'; $html .= '
'; // Connection Section $wsIcon = ($status['websocket_accessible'] ?? false) ? 'โœ…' : 'โŒ'; $wsLabel = ($status['websocket_accessible'] ?? false) ? 'Online' : 'Offline'; $emuIcon = ($status['emulator_connected'] ?? false) ? 'โœ…' : 'โŒ'; $emuLabel = ($status['emulator_connected'] ?? false) ? 'Online' : 'Offline'; $socketUrl = $status['socket_url'] ?? 'N/A'; $html .= '
๐ŸŒ Verbinding
'; $html .= '
' . $wsIcon . '
WebSocket
' . $wsLabel . '
'; $html .= '
' . $emuIcon . '
Emulator
' . $emuLabel . '
'; $html .= '
๐Ÿ”—
Socket URL
' . htmlspecialchars($socketUrl) . '
'; $html .= '
'; // System Section $buildIcon = ($status['build_exists'] ?? false) ? 'โœ…' : 'โŒ'; $webrootIcon = ($status['webroot_exists'] ?? false) ? 'โœ…' : 'โŒ'; $deployedIcon = ($status['deployed'] ?? false) ? 'โœ…' : 'โŒ'; $clientDepsIcon = ($status['client_node_modules'] ?? false) ? 'โœ…' : 'โŒ'; $symlinkIcon = ($status['symlink_valid'] ?? false) ? 'โœ…' : 'โŒ'; $writableIcon = ($status['webroot_writable'] ?? false) ? 'โœ…' : 'โŒ'; $html .= '
โš™๏ธ Systeem
'; $html .= '
' . $buildIcon . '
Build
'; $html .= '
' . $webrootIcon . '
Webroot
'; $html .= '
' . $deployedIcon . '
Gedeployed
'; $html .= '
' . $clientDepsIcon . '
Client deps
'; $html .= '
' . $symlinkIcon . '
Symlink
'; $html .= '
' . $writableIcon . '
Writable
'; $html .= '
'; // Server Config Section $nginxValid = ($status['nginx_config_valid'] ?? false) ? 'โœ…' : 'โŒ'; // Multiple fallback checks for open_basedir $openBasedirFixed = false; // Check 1: ini_get directly $openBaseDirValue = ini_get('open_basedir'); if (in_array($openBaseDirValue, ['', '0', false], true)) { $openBasedirFixed = true; } // Check 2: from status if (! $openBasedirFixed && ($status['open_basedir_fixed'] ?? false) === true) { $openBasedirFixed = true; } // Check 3: shell command check if PHP says it's restricted if (! $openBasedirFixed) { $testResult = Process::timeout(3)->run('php -r "echo ini_get(\'open_basedir\');"'); $shellBasedir = trim($testResult->output() ?? ''); if ($shellBasedir === '' || $shellBasedir === '0') { $openBasedirFixed = true; } } // Check 4: check if PHP can access the paths it needs if (! $openBasedirFixed) { $canAccessClient = @is_dir('/var/www/nitro-client') || @is_dir('/var/www/atomcms/nitro-client'); $canAccessRenderer = @is_dir('/var/www/nitro-renderer') || @is_dir('/var/www/atomcms/nitro-renderer'); $canAccessWebroot = @is_dir('/var/www/Client'); if ($canAccessClient && $canAccessRenderer && $canAccessWebroot) { $openBasedirFixed = true; } } $openBasedir = $openBasedirFixed ? 'โœ…' : 'โŒ'; $webrootFileCount = $status['webroot_file_count'] ?? 0; $html .= '
๐Ÿ–ฅ๏ธ Server Config
'; $html .= '
' . $nginxValid . '
nginx.conf
'; $html .= '
' . $openBasedir . '
open_basedir
'; $html .= '
๐Ÿ“Š
Files
' . $webrootFileCount . '
'; $html .= '
'; // Storage Section $buildSize = $status['build_size'] ?? 0; $webrootSize = $status['webroot_size'] ?? 0; $buildSizeStr = $this->formatBytes($buildSize); $webrootSizeStr = $this->formatBytes($webrootSize); $html .= '
๐Ÿ’พ Opslag
'; $html .= '
๐Ÿ“ฆ
Build
' . $buildSizeStr . '
'; $html .= '
๐ŸŒ
Webroot
' . $webrootSizeStr . '
'; $html .= '
'; // Footer $lastChecked = empty($status['last_checked']) ? 'Nooit' : Carbon::parse($status['last_checked'])->diffForHumans(); $html .= ''; $html .= '
'; return new HtmlString($html); } 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 getNitroTranslations(): array { return [ 'title' => __('nitro.title'), 'healthy' => __('nitro.healthy'), 'warning' => __('nitro.warning'), 'critical' => __('nitro.critical'), 'installed' => __('nitro.installed'), 'missing' => __('nitro.missing'), 'available' => __('nitro.available'), 'latest' => __('nitro.latest'), 'client' => __('nitro.client'), 'renderer' => __('nitro.renderer'), 'status' => __('nitro.status'), 'version' => __('nitro.version'), 'remote' => __('nitro.remote'), 'system' => __('nitro.system'), 'build' => __('nitro.build'), 'webroot' => __('nitro.webroot'), 'deployed' => __('nitro.deployed'), 'dependencies' => __('nitro.dependencies'), 'symlink' => __('nitro.symlink'), 'never' => __('nitro.never'), 'off' => __('nitro.off'), 'checked' => __('nitro.checked'), 'auto' => __('nitro.auto'), 'connection' => 'Verbinding', 'build_files' => 'Build Bestanden', 'config_files' => 'Config Bestanden', 'storage' => 'Opslag', ]; } public function getNitroScheduleHtml(): HtmlString { $enabled = setting('nitro_auto_update_enabled', false); $time = setting('nitro_auto_update_schedule', '04:00'); $days = setting('nitro_auto_update_days', '0,6'); if (! $enabled) { return new HtmlString('Automatische Nitro updates uitgeschakeld'); } $allowedDays = array_map(intval(...), explode(',', $days)); $dayNames = ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag']; $daysText = implode(', ', array_map(fn ($d) => $dayNames[$d] ?? $d, $allowedDays)); return new HtmlString('๐Ÿ“… ' . $time . ' (' . $daysText . ')'); } public function saveNitroUrl(): void { $settings = app(SettingsService::class); $siteUrl = $this->data['nitro_site_url'] ?? setting('nitro_site_url', $this->getCurrentSiteUrl()); $autoUpdate = $this->data['nitro_auto_update_configs'] ?? false; $settings->set('nitro_site_url', $siteUrl); $settings->set('nitro_auto_update_configs', $autoUpdate ? '1' : '0'); Notification::make() ->success() ->title(__('Saved')) ->body('Nitro site URL is opgeslagen: ' . $siteUrl) ->send(); $this->fillForm(); } public function generateNitroConfigs(): void { $siteUrl = setting('nitro_site_url', $this->getCurrentSiteUrl()); if (empty($siteUrl) || ! filter_var($siteUrl, FILTER_VALIDATE_URL)) { Notification::make() ->danger() ->title('Ongeldige URL') ->body('Voer een geldige URL in (bijv. https://epicnabbo.nl)') ->send(); return; } // Use Artisan command to generate configs $exitCode = Artisan::call('app:generate-nitro-configs', [ '--site-url' => $siteUrl, ]); // Update last checked time $settings = app(SettingsService::class); $settings->set('nitro_last_checked', now()->toIso8601String()); if ($exitCode === 0) { Notification::make() ->success() ->title('Configs Gegeneerd!') ->body('renderer-config.json, ui-config.json en UITexts.json zijn bijgewerkt voor ' . $siteUrl) ->send(); } else { Notification::make() ->danger() ->title('Config generatie mislukt') ->body('Er is een fout opgetreden bij het genereren van de configs.') ->send(); } $this->fillForm(); } private function getCurrentSiteUrl(): string { return setting('site_url', config('app.url', 'https://epicnabbo.nl')); } private function parseRepoFromUrl(string $url): ?string { if (preg_match('/github\.com\/([^\/]+\/[^\/\?#]+)/', $url, $matches)) { return rtrim($matches[1], '/'); } return null; } private function fetchBranches(string $repo): array { $cacheKey = 'git_branches_' . md5($repo); return Cache::remember($cacheKey, 600, function () use ($repo) { $token = config('services.github.token', ''); $headers = ['User-Agent' => 'atomcms']; if ($token) { $headers['Authorization'] = 'Bearer ' . $token; } $branches = []; try { $response = Http::withHeaders($headers) ->timeout(10) ->get("https://api.github.com/repos/{$repo}/branches?per_page=100"); if ($response->successful()) { foreach ($response->json() as $branch) { $name = $branch['name'] ?? ''; $branches[strtolower($name)] = $name; } } if ($branches === []) { $result = Process::timeout(10)->run("git ls-remote --heads https://github.com/{$repo}.git 2>/dev/null | awk '{print \$2}' | sed 's|refs/heads/||'"); if ($result->successful() && ! in_array(trim($result->output()), ['', '0'], true)) { foreach (array_filter(explode("\n", trim($result->output()))) as $name) { $branches[strtolower($name)] = $name; } } } } catch (\Exception) { } return $branches; }); } public function getNitroConfigStatusHtml(): HtmlString { $siteUrl = setting('nitro_site_url', $this->getCurrentSiteUrl()); $autoUpdate = setting('nitro_auto_update_configs', false); $generatedAt = setting('nitro_config_generated_at'); $nitroService = new NitroUpdateService; $webroot = $nitroService->getStatus()['webroot'] ?? '/var/www/Client'; $mtime = null; foreach (['renderer-config.json', 'ui-config.json', 'UITexts.json'] as $file) { $path = $webroot . '/' . $file; if (is_file($path) && ($t = filemtime($path)) > ($mtime ?? 0)) { $mtime = $t; } } if (! $generatedAt && $mtime) { $generatedAt = date('c', $mtime); } $html = ''; $html .= '
'; // Stats Grid $generatedCount = 0; $nitroService = new NitroUpdateService; $webroot = $nitroService->getStatus()['webroot'] ?? '/var/www/Client'; if (is_file($webroot . '/renderer-config.json')) { $generatedCount++; } if (is_file($webroot . '/ui-config.json')) { $generatedCount++; } if (is_file($webroot . '/UITexts.json')) { $generatedCount++; } $allGenerated = $generatedCount === 3; $html .= '
'; $html .= '
'; $html .= '
' . $generatedCount . '/3
'; $html .= '
Configs
'; $html .= '
'; $html .= '
'; $html .= '
' . ($autoUpdate ? 'AAN' : 'UIT') . '
'; $html .= '
Auto-update
'; $html .= '
'; $html .= '
'; // Site URL $html .= '
'; $html .= '
'; $html .= '
Website
'; $html .= '
'; $html .= '
Site URL
'; $html .= '
'; if ($siteUrl) { $display = strlen((string) $siteUrl) > 28 ? '' . e($siteUrl) . '' : e($siteUrl); $html .= '' . $display . ''; } else { $html .= 'Niet ingesteld'; } $html .= '
'; $html .= '
'; $html .= '
'; // Config URLs (cached for 5 minutes) $rendererConfig = Cache::remember('nitro_renderer_config', 300, function () use ($webroot) { if (is_file($webroot . '/renderer-config.json')) { return @json_decode(file_get_contents($webroot . '/renderer-config.json'), true) ?: []; } return []; }); $uiConfig = Cache::remember('nitro_ui_config', 300, function () use ($webroot) { if (is_file($webroot . '/ui-config.json')) { return @json_decode(file_get_contents($webroot . '/ui-config.json'), true) ?: []; } return []; }); $html .= '
'; $html .= '
'; $html .= '
' . e(__('Config URLs')) . '
'; $configUrls = [ [__('Socket URL'), $rendererConfig['socket.url'] ?? null], [__('Asset URL'), $rendererConfig['asset.url'] ?? null], [__('Images URL'), $rendererConfig['images.url'] ?? null], [__('Camera URL'), $uiConfig['camera.url'] ?? null], [__('Thumbnails URL'), $uiConfig['thumbnails.url'] ?? null], [__('Group Homepage'), $uiConfig['group.homepage.url'] ?? null], [__('Habbopages URL'), $uiConfig['habbopages.url'] ?? null], ]; foreach ($configUrls as [$label, $url]) { if (! $url) { continue; } $html .= '
'; $html .= '
' . e($label) . '
'; $html .= '
'; $display = strlen((string) $url) > 28 ? '' . e($url) . '' : e($url); $html .= '' . $display . ''; $html .= '
'; $html .= '
'; } $html .= '
'; // Generation Info $html .= '
'; $html .= '
'; $html .= '
Config Generatie
'; $html .= '
'; $html .= '
Laatst gegenereerd
'; $html .= '
'; if ($generatedAt) { $timeAgo = Carbon::parse($generatedAt)->diffForHumans(); $fullDate = Carbon::parse($generatedAt)->format('d M Y, H:i'); $html .= '' . e($timeAgo) . ''; } else { $html .= 'Nooit'; } $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
Auto-update na deploy
'; $html .= '
'; $html .= '' . ($autoUpdate ? 'Ja' : 'Nee') . ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; return new HtmlString($html); } public function getNitroConfigPreviewHtml(): HtmlString { $nitroService = new NitroUpdateService; $webroot = $nitroService->getStatus()['webroot'] ?? '/var/www/Client'; $rendererExists = is_file($webroot . '/renderer-config.json'); $uiExists = is_file($webroot . '/ui-config.json'); $uitextsExists = is_file($webroot . '/UITexts.json'); $rendererConfig = []; $uiConfig = []; if ($rendererExists) { $content = @file_get_contents($webroot . '/renderer-config.json'); if ($content) { $rendererConfig = @json_decode($content, true) ?: []; } } if ($uiExists) { $content = @file_get_contents($webroot . '/ui-config.json'); if ($content) { $uiConfig = @json_decode($content, true) ?: []; } } $html = ''; $html .= '
'; // Config Files $html .= '
'; $html .= '
Config Bestanden
'; $configs = [ 'renderer-config.json' => $rendererExists, 'ui-config.json' => $uiExists, 'UITexts.json' => $uitextsExists, ]; foreach ($configs as $name => $exists) { $html .= '
'; $html .= '
' . ($exists ? 'โœ…' : 'โŒ') . '
'; $html .= '
' . e($name) . '
'; $html .= '
' . ($exists ? 'Present' : 'Missing') . '
'; $html .= '
'; } $html .= '
'; // renderer-config.json if ($rendererExists && ! empty($rendererConfig)) { $html .= '
'; $html .= '
'; $html .= '
renderer-config.json
'; $rendererFields = [ 'socket.url' => [$rendererConfig['socket.url'] ?? null, __('Socket URL')], 'asset.url' => [$rendererConfig['asset.url'] ?? null, __('Asset URL')], 'gamedata.url' => [$rendererConfig['gamedata.url'] ?? null, __('Gamedata URL')], 'images.url' => [$rendererConfig['images.url'] ?? null, __('Images URL')], ]; foreach ($rendererFields as [$value, $label]) { if (! $value) { continue; } $html .= '
'; $html .= '
' . e($label) . '
'; $html .= '
'; $display = strlen((string) $value) > 30 ? '' . e($value) . '' : e($value); $html .= '' . $display . ''; $html .= '
'; $html .= '
'; } $html .= '
'; } // ui-config.json if ($uiExists && ! empty($uiConfig)) { $html .= '
'; $html .= '
'; $html .= '
ui-config.json
'; $uiFields = [ 'camera.url' => [$uiConfig['camera.url'] ?? null, __('Camera URL')], 'thumbnails.url' => [$uiConfig['thumbnails.url'] ?? null, __('Thumbnails URL')], 'group.homepage.url' => [$uiConfig['group.homepage.url'] ?? null, __('Group Homepage')], 'habbopages.url' => [$uiConfig['habbopages.url'] ?? null, __('Habbopages URL')], 'url.prefix' => [$uiConfig['url.prefix'] ?? null, __('URL Prefix')], ]; foreach ($uiFields as [$value, $label]) { if (! $value) { continue; } $html .= '
'; $html .= '
' . e($label) . '
'; $html .= '
'; $display = strlen((string) $value) > 30 ? '' . e($value) . '' : e($value); $html .= '' . $display . ''; $html .= '
'; $html .= '
'; } $html .= '
'; } if (! $rendererExists && ! $uiExists) { $html .= '
'; $html .= '
โš ๏ธ
'; $html .= 'Geen config bestanden gevonden
'; $html .= 'Genereer configs via de knop hierboven'; $html .= '
'; } $html .= '
'; return new HtmlString($html); } private function clearSettingsCache(): void { Cache::forget('website_settings'); SettingsService::clearCache(); } }