fillForm(); } protected function fillForm(): void { $paths = $this->autoDetectPaths(); $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', ''), 'discord_webhook_ranks' => json_decode($this->getSetting('discord_webhook_ranks', '[]'), true) ?? [], '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' => $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', $paths['emulator_jar_path']), 'emulator_source_path' => $this->getSetting('emulator_source_path', $paths['emulator_source_path']), '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', $paths['nitro_client_path']), 'nitro_renderer_path' => $this->getSetting('nitro_renderer_path', $paths['nitro_renderer_path']), 'nitro_build_path' => $this->getSetting('nitro_build_path', $paths['nitro_build_path']), 'nitro_webroot' => $this->getSetting('nitro_webroot', $paths['nitro_webroot']), 'gamedata_path' => $this->getSetting('gamedata_path', $paths['gamedata_path']), '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('🎯 Live Status') ->description('Real-time hotel statistieken') ->icon('heroicon-o-heart') ->columns(4) ->schema([ Placeholder::make('online_users') ->label('Online') ->content(fn (): HtmlString => $this->getCardHtml('Online', $this->getOnlineUsersCount(), '#22c55e', 'heroicon-o-users')), Placeholder::make('emulator_status') ->label('Emulator') ->content(fn (): HtmlString => $this->getCardHtml('Emulator', $this->getEmulatorStatusText(), $this->getEmulatorStatusColor(), 'heroicon-o-server')), Placeholder::make('database_status') ->label('Database') ->content(fn (): HtmlString => $this->getCardHtml('Database', $this->isDatabaseOnline() ? 'Online' : 'Offline', $this->isDatabaseOnline() ? '#22c55e' : '#ef4444', 'heroicon-o-circle-stack')), Placeholder::make('server_load') ->label('Load') ->content(fn (): HtmlString => $this->getCardHtml('Load', $this->getServerLoad(), '#3b82f6', 'heroicon-o-cpu-chip')), ]), Section::make('πŸ“Š Server Informatie') ->description('Gedetailleerde server status') ->icon('heroicon-o-information-circle') ->schema([ Placeholder::make('server_info') ->label('') ->content(fn (): HtmlString => $this->renderServerInfo()), ]), Section::make('🏨 Hotel Status') ->description('Emulator en Nitro status') ->icon('heroicon-o-building-office') ->schema([ Placeholder::make('hotel_status') ->label('') ->content(fn (): HtmlString => $this->renderHotelStatus()), ]), Section::make('πŸ“’ Hotel Alert') ->description('Stuur een bericht naar alle online gebruikers') ->icon('heroicon-o-megaphone') ->schema([ Placeholder::make('alert_form') ->label('') ->content(fn (): HtmlString => $this->renderAlertForm()), ]), Section::make('πŸ“œ Emulator Logs') ->description('Live emulator log viewer') ->icon('heroicon-o-document-text') ->columnSpanFull() ->schema([ Placeholder::make('emulator_logs') ->label('') ->content(fn () => view('filament.components.emulator-log-viewer')), ]), Section::make('πŸ–₯️ Emulator Control') ->description('Volledige emulator controle') ->icon('heroicon-o-server') ->columns(3) ->afterHeader([ Action::make('start_emulator') ->label('Start') ->icon('heroicon-o-play') ->color('success') ->action('startEmulator'), Action::make('stop_emulator') ->label('Stop') ->icon('heroicon-o-stop') ->color('danger') ->action('stopEmulator'), Action::make('restart_emulator') ->label('Restart') ->icon('heroicon-o-arrow-path') ->color('warning') ->action('restartEmulator'), Action::make('check_emulator') ->label('Check') ->icon('heroicon-o-check-circle') ->color('info') ->action('checkEmulator'), ]) ->schema([ Placeholder::make('emulator_info') ->label('') ->content(fn (): HtmlString => $this->renderEmulatorInfo()), ]), Section::make('πŸ”„ Emulator Updates') ->description('Configureer en update de emulator') ->icon('heroicon-o-arrow-down-circle') ->afterHeader([ Action::make('check_updates') ->label('Check Updates') ->color('info') ->action('checkEmulatorUpdates'), Action::make('build_emulator') ->label('πŸ”¨ Build') ->color('success') ->action('buildEmulator'), Action::make('run_sql') ->label('SQL Updates') ->color('purple') ->action('runSqlUpdates'), Action::make('save_emulator') ->label('Opslaan') ->color('primary') ->action('saveEmulator'), ]) ->schema([ Placeholder::make('emulator_settings') ->label('') ->content(fn (): HtmlString => $this->renderEmulatorSettings()), ]), Section::make('πŸ’Ύ Emulator Backups') ->description('Bekijk en herstel emulator backups') ->icon('heroicon-s-archive-box') ->schema([ Placeholder::make('backups_list') ->label('') ->content(fn (): HtmlString => $this->renderBackupsList()), ]), Section::make('πŸ“¦ Nitro Client') ->description('Configureer en update Nitro') ->icon('heroicon-o-cloud-arrow-down') ->afterHeader([ Action::make('detect_paths') ->label('πŸ” Auto Detect') ->color('success') ->action('detectAndSavePaths'), Action::make('check_nitro') ->label('Check') ->color('info') ->action('checkNitroUpdates'), Action::make('build_nitro') ->label('Build') ->color('pink') ->action('buildNitro'), Action::make('generate_configs') ->label('Genereer Configs') ->color('indigo') ->action('generateNitroConfigs'), Action::make('save_nitro') ->label('Opslaan') ->color('primary') ->action('saveNitro'), ]) ->schema([ Placeholder::make('nitro_settings') ->label('') ->content(fn (): HtmlString => $this->renderNitroSettings()), ]), Section::make('βš™οΈ Automatische Updates') ->description('Configureer automatische updates') ->icon('heroicon-o-clock') ->columns(2) ->afterHeader([ Action::make('save_auto') ->label('Opslaan') ->color('primary') ->action('saveAutoUpdate'), ]) ->schema([ Toggle::make('auto_update_enabled') ->label('Automatische Updates Inschakelen'), TextInput::make('auto_update_schedule') ->label('Schema (HH:MM)'), TextInput::make('auto_update_days') ->label('Dagen (0-6)'), ]), Section::make('πŸ‘” Kleding Sync') ->description('Sync catalogus kleding uit FigureMap') ->icon('heroicon-o-user') ->afterHeader([ Action::make('sync_clothing') ->label('πŸ”„ Sync') ->color('success') ->action('syncClothing'), ]) ->schema([ Placeholder::make('clothing_status') ->label('') ->content(fn (): HtmlString => $this->renderClothingStatus()), ]), Section::make('πŸ”” Meldingen') ->description('E-mail en Discord alerts') ->icon('heroicon-o-bell') ->columns(2) ->afterHeader([ Action::make('save_alerts') ->label('Opslaan') ->color('primary') ->action('saveAlerts'), Action::make('test_discord') ->label('Test Discord') ->color('info') ->action('testDiscord'), ]) ->schema([ Toggle::make('alert_email_enabled') ->label('E-mail Meldingen'), TextInput::make('alert_email_address') ->label('E-mail Adres') ->email() ->columnSpanFull(), Toggle::make('alert_discord_enabled') ->label('Discord Meldingen'), TextInput::make('alert_discord_webhook_url') ->label('Webhook URL') ->columnSpanFull(), Select::make('discord_webhook_ranks') ->label('Ranks die Discord notificatie krijgen') ->multiple() ->options(fn () => WebsitePermission::pluck('permission', 'min_rank')->mapWithKeys(fn ($perm, $rank) => [$rank => "Rank {$rank} ({$perm})"])->toArray()) ->helperText('Laat leeg voor alleen staff (min_staff_rank)'), ]), Section::make('πŸ“Š Update Geschiedenis') ->description('Laatste systeem updates') ->icon('heroicon-o-clock') ->schema([ Placeholder::make('history') ->label('') ->content(fn (): HtmlString => $this->getUpdateHistoryHtml()), ]), Section::make('πŸ” Social Login (v1.4)') ->description('Enable social login providers') ->icon('heroicon-o-user-circle') ->schema([ Toggle::make('social_login_google_enabled') ->label('Google Login') ->helperText('Allow users to login with Google'), TextInput::make('social_login_google_client_id') ->label('Google Client ID') ->helperText('From Google Cloud Console'), TextInput::make('social_login_google_client_secret') ->label('Google Client Secret') ->type('password'), Toggle::make('social_login_discord_enabled') ->label('Discord Login') ->helperText('Allow users to login with Discord'), TextInput::make('social_login_discord_client_id') ->label('Discord Client ID') ->helperText('From Discord Developer Portal'), TextInput::make('social_login_discord_client_secret') ->label('Discord Client Secret') ->type('password'), Toggle::make('social_login_github_enabled') ->label('GitHub Login') ->helperText('Allow users to login with GitHub'), TextInput::make('social_login_github_client_id') ->label('GitHub Client ID') ->helperText('From GitHub Developer Settings'), TextInput::make('social_login_github_client_secret') ->label('GitHub Client Secret') ->type('password'), ]) ->columns(2), Section::make('πŸ‘” Staff Activity Log') ->description('Recent staff activities in the housekeeping (v1.2)') ->icon('heroicon-o-user-group') ->schema([ Placeholder::make('staff_activity') ->label('') ->content(fn (): HtmlString => $this->getStaffActivityHtml()), ]), ]); } private function getSetting(string $key, string $default = ''): string { try { return app(SettingsService::class)->getOrDefault($key, $default); } catch (Exception) { return $default; } } private function getSettingBool(string $key, bool $default = false): bool { try { $value = app(SettingsService::class)->getOrDefault($key, $default ? '1' : '0'); return in_array($value, ['1', 'true', 'yes'], true); } catch (Exception) { return $default; } } private function getCurrentSiteUrl(): string { try { return config('app.url', 'https://epicnabbo.nl'); } catch (Exception) { return 'https://epicnabbo.nl'; } } private function autoDetectPaths(): array { $autoDetect = AutoDetectService::getInstance(); $autoDetect->clearCache(); return [ 'nitro_client_path' => $autoDetect->detectNitroClientPath(), 'nitro_renderer_path' => $autoDetect->detectNitroRendererPath(), 'nitro_build_path' => $autoDetect->detectNitroBuildPath(), 'nitro_webroot' => $autoDetect->detectNitroWebroot(), 'gamedata_path' => $autoDetect->detectGamedataPath(), 'emulator_jar_path' => $autoDetect->detectEmulatorJarPath(), 'emulator_source_path' => $autoDetect->detectEmulatorSourcePath(), ]; } private function getCardHtml(string $label, string $value, string $color, string $icon): HtmlString { $iconSvg = match ($icon) { 'heroicon-o-users' => '', 'heroicon-o-server' => '', 'heroicon-o-circle-stack' => '', 'heroicon-o-cpu-chip' => '', default => '', }; return new HtmlString(<<
{$iconSvg}
{$label}
{$value}
HTML); } private function getOnlineUsersCount(): string { try { return (string) DB::connection('mysql')->table('users')->where('online', '=', '1')->count(); } catch (Exception) { return 'N/B'; } } private function getEmulatorStatusText(): string { try { $rcon = new RconService; if ($rcon->isConnected()) { return 'Online'; } $response = Http::timeout(2)->get('http://127.0.0.1:3000/api/status'); return $response->successful() ? 'Online' : 'Offline'; } catch (Exception) { return 'Offline'; } } private function getEmulatorStatusColor(): string { return $this->getEmulatorStatusText() === 'Online' ? '#22c55e' : '#ef4444'; } private function isDatabaseOnline(): bool { try { DB::connection('mysql')->select('SELECT 1'); return true; } catch (Exception) { return false; } } private function getServerLoad(): string { try { $load = sys_getloadavg(); return $load ? number_format($load[0], 2) : 'N/B'; } catch (Exception) { return 'N/B'; } } private function renderServerInfo(): HtmlString { $phpVersion = phpversion(); $laravelVersion = app()->version(); $memoryUsage = round(memory_get_usage() / 1024 / 1024, 2); $memoryLimit = ini_get('memory_limit'); $diskUsage = @shell_exec("df -h /var/www | tail -1 | awk '{print $3 \"/\" $2}'"); $diskUsage = trim($diskUsage); $uptime = @shell_exec('uptime -p 2>/dev/null'); $uptime = trim($uptime); $load = sys_getloadavg(); $load1 = $load ? number_format($load[0], 2) : 'N/A'; $load5 = $load ? number_format($load[1], 2) : 'N/A'; $load15 = $load ? number_format($load[2], 2) : 'N/A'; return new HtmlString(<<
PHP & Laravel
PHP {$phpVersion}
Laravel {$laravelVersion}
Memory & Disk
Memory {$memoryUsage}MB / {$memoryLimit}
Disk {$diskUsage}
Uptime
{$uptime}
Load
1m
{$load1}
5m
{$load5}
15m
{$load15}
HTML); } private function renderHotelStatus(): HtmlString { $serviceName = $this->getSetting('emulator_service_name', 'emulator'); $serviceResult = @shell_exec('systemctl is-active ' . escapeshellarg($serviceName) . ' 2>/dev/null'); $serviceStatus = trim($serviceResult); $serviceColor = $serviceStatus === 'active' ? '#22c55e' : '#ef4444'; $nitroClientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client'); $nitroRendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer'); $nitroWebroot = $this->getSetting('nitro_webroot', '/var/www/Client'); $clientCommit = $this->getGitCommit($nitroClientPath); $rendererCommit = $this->getGitCommit($nitroRendererPath); $clientExists = $this->checkPathExists($nitroClientPath); $rendererExists = $this->checkPathExists($nitroRendererPath); $onlineUsers = $this->getOnlineUsersCount(); $emulatorStatus = $this->getEmulatorStatusText(); $dbStatus = $this->isDatabaseOnline() ? 'Online' : 'Offline'; $dbColor = $dbStatus === 'Online' ? '#22c55e' : '#ef4444'; $clientColor = $clientExists ? '#22c55e' : '#ef4444'; $rendererColor = $rendererExists ? '#22c55e' : '#ef4444'; $clientText = $clientExists ? 'βœ“ ' . substr($clientCommit, 0, 7) : 'βœ— Niet gevonden'; $rendererText = $rendererExists ? 'βœ“ ' . substr($rendererCommit, 0, 7) : 'βœ— Niet gevonden'; $webrootText = $rendererExists ? 'βœ“ ' . basename($nitroWebroot) : 'βœ— Niet gevonden'; $webrootColor = $rendererExists ? '#22c55e' : '#ef4444'; return new HtmlString(<<
Emulator
Status {$serviceStatus}
Online {$emulatorStatus}
Gebruikers {$onlineUsers}
Database {$dbStatus}
Nitro
Client {$clientText}
Renderer {$rendererText}
Webroot {$webrootText}
HTML); } private function getEmulatorBranchesHtml(): string { $githubUrl = $this->getSetting('emulator_github_url', ''); $currentBranch = $this->data['emulator_github_branch'] ?? 'main'; $branches = $this->fetchGitHubBranches($githubUrl); $html = ''; foreach ($branches as $branch) { $selected = $branch === $currentBranch ? 'selected' : ''; $html .= ''; } return $html; } private function getNitroBranchesHtml(): string { $githubUrl = $this->getSetting('nitro_github_url', ''); $currentBranch = $this->data['nitro_github_branch'] ?? 'main'; $branches = $this->fetchGitHubBranches($githubUrl); $html = ''; foreach ($branches as $branch) { $selected = $branch === $currentBranch ? 'selected' : ''; $html .= ''; } return $html; } private function fetchGitHubBranches(string $githubUrl): array { if ($githubUrl === '' || $githubUrl === '0' || ! str_contains($githubUrl, 'github.com')) { return []; } try { $repo = $this->extractGitHubRepo($githubUrl); $gitUrl = "https://github.com/{$repo}.git"; // Use git ls-remote to get all branches (doesn't hit rate limit) $result = @shell_exec('git ls-remote --heads ' . escapeshellarg($gitUrl) . ' 2>/dev/null'); if ($result && trim($result)) { $branches = []; foreach (explode("\n", trim($result)) as $line) { $parts = explode("\t", $line); if (isset($parts[1]) && ($parts[1] !== '' && $parts[1] !== '0') && str_starts_with($parts[1], 'refs/heads/')) { $branches[] = str_replace('refs/heads/', '', $parts[1]); } } return $branches; } // Fallback to GitHub API $response = Http::timeout(10)->get("https://api.github.com/repos/{$repo}/branches"); if ($response->successful()) { $data = $response->json(); return array_column($data, 'name'); } } catch (Exception) { // Ignore } return []; } private function extractGitHubRepo(string $url): string { $url = trim($url, '/'); // Handle GitHub URLs with /tree/ or /blob/ if (str_contains($url, 'github.com/')) { $parts = explode('github.com/', $url); if (isset($parts[1])) { $path = trim($parts[1], '/'); // Remove /tree/... or /blob/... if (str_contains($path, '/tree/')) { $path = explode('/tree/', $path)[0]; } if (str_contains($path, '/blob/')) { $path = explode('/blob/', $path)[0]; } return $path; } } return $url; } private function renderAlertForm(): HtmlString { return new HtmlString(<<<'HTML'
HTML); } private function renderEmulatorInfo(): HtmlString { $version = $this->getSetting('emulator_version', 'Onbekend'); $serviceName = $this->getSetting('emulator_service_name', 'arcturus'); $status = $this->getEmulatorStatusText(); $color = $status === 'Online' ? '#22c55e' : '#ef4444'; return new HtmlString(<<
Versie
{$version}
Service
{$serviceName}
Status
● {$status}
HTML); } private function renderEmulatorSettings(): HtmlString { $status = $this->getEmulatorStatusHtml(); return new HtmlString(<<
{$status}
HTML); } private function getEmulatorStatusHtml(): string { $serviceName = $this->getSetting('emulator_service_name', 'arcturus'); $jarPath = $this->getSetting('emulator_jar_path', '/var/www/Emulator'); $emulatorOnline = $this->getEmulatorStatusText() === 'Online'; $jarCheck = @shell_exec('test -e ' . escapeshellarg($jarPath) . ' && echo yes'); $jarExists = $jarCheck && trim($jarCheck) === 'yes'; $onlineStatus = $emulatorOnline ? 'βœ“ Online' : 'βœ— Offline'; $jarStatus = $jarExists ? 'βœ“ JAR OK' : 'βœ— JAR ontbreekt'; $serviceStatus = 'Service: ' . e($serviceName) . ''; $updateInfo = $this->checkEmulatorUpdatesFromGitHub(); return '
' . '
' . $onlineStatus . $jarStatus . $serviceStatus . '
' . '
' . $updateInfo . '
' . '
'; } private function checkEmulatorUpdatesFromGitHub(): string { try { $githubUrl = $this->getSetting('emulator_github_url', ''); $sourcePath = $this->getSetting('emulator_source_path', '/var/www/emulator-source'); $jarPath = $this->getSetting('emulator_jar_path', '/var/www/Emulator'); $sourceCheck = @shell_exec('test -e ' . escapeshellarg($sourcePath) . ' && echo yes'); $sourceExists = $sourceCheck && trim($sourceCheck) === 'yes'; $jarCheck = @shell_exec('test -e ' . escapeshellarg($jarPath) . ' && echo yes'); $jarExists = $jarCheck && trim($jarCheck) === 'yes'; $sourceCommit = $this->getGitCommit($sourcePath); $remoteVersion = $githubUrl !== '' && $githubUrl !== '0' ? $this->getEmulatorRemoteVersion($githubUrl) : 'N/A'; $canBuild = false; $pomPath = ''; $checkDirs = [ $sourcePath, $sourcePath . '/Emulator', $sourcePath . '/Emulator/Emulator', $sourcePath . '/emulator', $sourcePath . '/emulator/emulator', ]; foreach ($checkDirs as $dir) { $check = @shell_exec('test -f ' . escapeshellarg($dir . '/pom.xml') . ' && echo yes'); if ($check && trim($check) === 'yes') { $canBuild = true; $pomPath = $dir; break; } } $hasUpdate = $sourceCommit !== 'N/A' && $remoteVersion !== 'N/A' && substr($sourceCommit, 0, 7) !== $remoteVersion; $updateColor = $hasUpdate ? '#f59e0b' : '#22c55e'; $updateText = $hasUpdate ? 'πŸ”„ Update beschikbaar' : 'βœ“ Up-to-date'; $html = '
GitHub Status:
'; // Update status $html .= '
'; $html .= '
'; $html .= '
'; $html .= $updateText; $html .= '
'; // Always show update button $btnColor = $hasUpdate ? '#f59e0b' : '#3b82f6'; $btnGradient = $hasUpdate ? 'linear-gradient(135deg,#f59e0b,#d97706)' : 'linear-gradient(135deg,#3b82f6,#2563eb)'; $btnText = $hasUpdate ? '⚑ Updaten' : 'πŸ”„ Herbouwen'; $html .= ''; $html .= '
'; // Version info - use short hash for comparison $sourceCommitShort = substr($sourceCommit, 0, 7); $html .= '
Latest:βœ“ ' . e($remoteVersion) . '
'; $html .= '
Source:βœ“ ' . $sourceCommitShort . '
'; // Build method if ($jarExists) { $jarSize = @shell_exec('ls -lh ' . escapeshellarg($jarPath) . '/*.jar 2>/dev/null | head -1'); if ($jarSize) { preg_match('/(\S+\.jar)/', $jarSize, $matches); if (isset($matches[1]) && ($matches[1] !== '' && $matches[1] !== '0')) { $html .= '
JAR:βœ“ ' . basename($matches[1]) . '
'; } } } if ($canBuild) { $html .= '
Bouwen:βœ“ Maven (pom.xml)
'; } else { $html .= '
Bouwen:⚠️ Geen pom.xml
'; } // Auto-update method $html .= '
'; $html .= '
METHODE:
'; if ($jarExists) { $html .= '
πŸ“¦ JAR Download & Herstart
'; } elseif ($canBuild) { $html .= '
πŸ”¨ Maven Build & Herstart
'; } else { $html .= '
Handmatig: Download JAR van GitHub
'; } return $html . '
'; } catch (Exception) { return 'Kon emulator status niet ophalen'; } } private function getEmulatorRemoteVersion(string $githubUrl): string { try { if ($githubUrl === '' || $githubUrl === '0') { return 'N/A'; } // Try to get latest commit using git ls-remote (doesn't hit rate limit) $repo = $this->extractGitHubRepo($githubUrl); // Use nitro branch if it's a nitro repo, otherwise use emulator branch $isNitroRepo = str_contains($githubUrl, 'Nitro'); $branch = $isNitroRepo ? $this->getSetting('nitro_github_branch', 'main') : $this->getSetting('emulator_github_branch', 'main'); $gitUrl = "https://github.com/{$repo}.git"; $result = @shell_exec('git ls-remote ' . escapeshellarg($gitUrl) . ' ' . escapeshellarg($branch) . ' 2>/dev/null'); if ($result && trim($result)) { $parts = explode("\t", trim($result)); if (isset($parts[0]) && ($parts[0] !== '' && $parts[0] !== '0')) { return substr($parts[0], 0, 7); } } // Fallback to GitHub API $response = Http::timeout(10)->get("https://api.github.com/repos/{$repo}/releases/latest"); if ($response->successful()) { $data = $response->json(); if (! empty($data['tag_name'])) { return $data['tag_name']; } } $response = Http::timeout(10)->get("https://api.github.com/repos/{$repo}/commits?per_page=1"); if ($response->successful()) { $data = $response->json(); if (! empty($data[0]['sha'])) { return substr((string) $data[0]['sha'], 0, 7); } } } catch (Exception) { // Ignore } return 'N/A'; } private function renderNitroSettings(): HtmlString { $status = $this->getNitroStatusHtml(); return new HtmlString(<<
{$status}
HTML); } private function getNitroStatusHtml(): string { $clientPath = $this->getSetting('nitro_client_path', '/var/www/atomcms/nitro-client'); $rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/atomcms/nitro-renderer'); $webroot = $this->getSetting('nitro_webroot', '/var/www/Client'); $clientExists = $this->checkPathExists($clientPath); $rendererExists = $this->checkPathExists($rendererPath); $webrootExists = $this->checkPathExists($webroot); $clientStatus = $clientExists ? 'βœ“ Client OK' : 'βœ— Client ontbreekt'; $rendererStatus = $rendererExists ? 'βœ“ Renderer OK' : 'βœ— Renderer ontbreekt'; $webrootStatus = $webrootExists ? 'βœ“ Webroot OK' : 'βœ— Webroot ontbreekt'; $updateInfo = $this->checkNitroUpdatesFromGitHub(); return '
' . $clientStatus . $rendererStatus . $webrootStatus . '
' . $updateInfo . '
' . '
'; } private function checkNitroUpdatesFromGitHub(): string { try { $clientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client'); $rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer'); $clientGithubUrl = $this->getSetting('nitro_github_url', ''); // Use the renderer-specific repo (defaults to Nitro_Render_V3 if not set) $rendererGithubUrl = $this->getSetting('nitro_renderer_github_url', 'https://github.com/duckietm/Nitro_Render_V3'); $clientCommit = $this->getGitCommit($clientPath); $rendererCommit = $this->getGitCommit($rendererPath); $clientRemote = $clientGithubUrl !== '' && $clientGithubUrl !== '0' ? $this->getEmulatorRemoteVersion($clientGithubUrl) : 'N/A'; $rendererRemote = $rendererGithubUrl !== '' && $rendererGithubUrl !== '0' ? $this->getEmulatorRemoteVersion($rendererGithubUrl) : 'N/A'; // Compare only first 7 characters (short hash) $clientCommitShort = substr($clientCommit, 0, 7); $rendererCommitShort = substr($rendererCommit, 0, 7); $hasClientUpdate = $clientCommitShort !== 'N/A' && $clientRemote !== 'N/A' && $clientCommitShort !== $clientRemote; $hasRendererUpdate = $rendererCommitShort !== 'N/A' && $rendererRemote !== 'N/A' && $rendererCommitShort !== $rendererRemote; $hasUpdate = $hasClientUpdate || $hasRendererUpdate; $updateColor = $hasUpdate ? '#f59e0b' : '#22c55e'; $updateText = $hasUpdate ? 'πŸ”„ Update beschikbaar' : 'βœ“ Up-to-date'; $html = '
GitHub Status:
'; // Update status $html .= '
'; $html .= '
'; $html .= '
'; $html .= $updateText; $html .= '
'; // Update button if update available if ($hasUpdate) { $html .= ''; } $html .= '
'; // Client info $clientColor = $hasClientUpdate ? '#f59e0b' : '#22c55e'; $clientIcon = $hasClientUpdate ? 'πŸ”„' : 'βœ“'; $html .= '
Client Remote:βœ“ ' . e($clientRemote) . '
'; $html .= '
Client Local:' . $clientIcon . ' ' . $clientCommitShort . '
'; // Renderer info $rendererColor = $hasRendererUpdate ? '#f59e0b' : '#22c55e'; $rendererIcon = $hasRendererUpdate ? 'πŸ”„' : 'βœ“'; $html .= '
Renderer Remote:βœ“ ' . e($rendererRemote) . '
'; return $html . ('
Renderer Local:' . $rendererIcon . ' ' . $rendererCommitShort . '
'); } catch (Exception) { return 'Kon updates niet ophalen'; } } private function getGitCommit(string $path): string { $checkPath = @shell_exec('test -e ' . escapeshellarg($path) . ' && echo yes'); if (! $checkPath || trim($checkPath) !== 'yes') { return 'N/A'; } $subdirs = ['', 'Emulator', 'emulator', 'src', 'client']; foreach ($subdirs as $subdir) { $fullPath = $subdir !== '' && $subdir !== '0' ? $path . '/' . $subdir : $path; $gitPath = $fullPath . '/.git'; $gitCheck = @shell_exec('test -d ' . escapeshellarg($gitPath) . ' && echo yes'); if ($gitCheck && trim($gitCheck) === 'yes') { $headFile = $gitPath . '/HEAD'; $headContent = @shell_exec('cat ' . escapeshellarg($headFile) . ' 2>/dev/null'); if ($headContent && trim($headContent)) { if (str_contains($headContent, 'ref:')) { preg_match('/ref: refs\/heads\/(\S+)/', $headContent, $matches); if (isset($matches[1]) && ($matches[1] !== '' && $matches[1] !== '0')) { $branchRef = $gitPath . '/refs/heads/' . $matches[1]; $branchContent = @shell_exec('cat ' . escapeshellarg($branchRef) . ' 2>/dev/null'); if ($branchContent && trim($branchContent)) { return trim($branchContent); } } } else { return trim($headContent); } } } } return 'N/A'; } private function getGitHubLatestCommit(string $repo, string $type): string { try { $url = $repo; if (str_contains($repo, 'github.com')) { $parts = explode('github.com/', $repo); if (isset($parts[1])) { $repo = $parts[1]; } } $repo = rtrim($repo, '/'); $response = Http::timeout(10)->get("https://api.github.com/repos/{$repo}/commits?path={$type}&per_page=1"); if ($response->successful()) { $data = $response->json(); if (! empty($data[0]['sha'])) { return $data[0]['sha']; } } } catch (Exception) { // Ignore } return 'N/A'; } private function checkGitHubUpdates(string $repo, string $type): bool { $localCommit = $this->getGitCommit( $type === 'client' ? $this->getSetting('nitro_client_path', '/var/www/atomcms/nitro-client') : $this->getSetting('nitro_renderer_path', '/var/www/atomcms/nitro-renderer'), ); $remoteCommit = $this->getGitHubLatestCommit($repo, $type); return $localCommit !== 'N/A' && $remoteCommit !== 'N/A' && $localCommit !== $remoteCommit; } private function checkPathExists(string $path): bool { $result = @shell_exec('test -e ' . escapeshellarg($path) . " && echo 'yes'"); if (in_array($result, ['', '0', false, null], true)) { return false; } $trimmed = @trim($result); return $trimmed === 'yes'; } public function getUpdateHistoryHtml(): HtmlString { try { $history = app(UpdateHistoryService::class)->getRecent(10); if (empty($history)) { return new HtmlString('
Geen updates gevonden
'); } $html = '
'; foreach ($history as $update) { $statusColor = $update->status === 'success' ? '#22c55e' : ($update->status === 'pending' ? '#f59e0b' : '#ef4444'); $html .= '
'; $html .= '
' . e($update->type) . '' . e($update->message) . '
'; $html .= '
' . e($update->status) . '' . e($update->created_at) . '
'; $html .= '
'; } $html .= '
'; return new HtmlString($html); } catch (Exception) { return new HtmlString('
Kon historie niet laden
'); } } public function sendHotelAlert(): void { try { $message = $this->data['hotel_alert_message'] ?? ''; if (empty($message)) { $this->notify('Error', 'Bericht mag niet leeg zijn', 'danger'); return; } app(RconService::class)->sendCommand('alert', ['message' => $message]); $this->notify('Success', 'Alert verstuurd naar alle gebruikers!', 'success'); $this->data['hotel_alert_message'] = ''; } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function startEmulator(): void { try { $serviceName = $this->getSetting('emulator_service_name', 'arcturus'); $result = Process::timeout(30)->run("sudo systemctl start {$serviceName} 2>&1"); if ($result->successful()) { $this->notify('Success', 'Emulator gestart!', 'success'); } else { $this->notify('Error', $result->output() ?: 'Kon emulator niet starten', 'danger'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function stopEmulator(): void { try { $serviceName = $this->getSetting('emulator_service_name', 'arcturus'); $result = Process::timeout(30)->run("sudo systemctl stop {$serviceName} 2>&1"); if ($result->successful()) { $this->notify('Success', 'Emulator gestopt!', 'success'); } else { $this->notify('Error', $result->output() ?: 'Kon emulator niet stoppen', 'danger'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function restartEmulator(): void { try { $serviceName = $this->getSetting('emulator_service_name', 'arcturus'); $result = Process::timeout(60)->run("sudo systemctl restart {$serviceName} 2>&1"); if ($result->successful()) { $this->notify('Success', 'Emulator herstart!', 'success'); } else { $this->notify('Error', $result->output() ?: 'Kon emulator niet herstarten', 'danger'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function checkEmulator(): void { try { Artisan::call('monitor:emulator', ['--notify-online' => true]); $rconService = new RconService; if ($rconService->isConnected()) { $this->notify('Success', 'Emulator is online en reageert!', 'success'); } else { $this->notify('Warning', 'Emulator is niet bereikbaar via RCON', 'warning'); } $this->fillForm(); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function checkEmulatorUpdates(): void { try { $this->notify('Info', 'πŸ”¨ Emulator wordt gebouwd vanaf source...', 'info'); $updateService = app(EmulatorUpdateService::class); $result = $updateService->buildFromSource(); if ($result['success'] ?? false) { $this->notify('Success', $result['message'] ?? 'βœ… Emulator gebouwd!', 'success'); } else { $this->notify('Error', $result['error'] ?? 'Build mislukt', 'danger'); } Cache::forget('all_updates_check'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function buildEmulator(): void { try { $sourcePath = $this->getSetting('emulator_source_path', '/var/www/emulator-source'); $githubUrl = $this->getSetting('emulator_github_url', ''); $branch = $this->getSetting('emulator_github_branch', 'main'); if ($githubUrl === '' || $githubUrl === '0') { $this->notify('Error', 'Configureer eerst de Emulator GitHub URL', 'danger'); return; } // Pull latest from GitHub $this->notify('Info', 'πŸ”„ Pulling latest changes from GitHub...', 'info'); $pullResult = @shell_exec('cd ' . escapeshellarg($sourcePath) . ' && git pull origin ' . escapeshellarg($branch) . ' 2>&1'); // Check if Maven is available $mavenCheck = @shell_exec('which mvn 2>/dev/null'); if (! $mavenCheck || ! trim($mavenCheck)) { $this->notify('Warning', 'Maven (mvn) is niet geΓ―nstalleerd - kan niet bouwen', 'warning'); return; } // Find pom.xml and build $pomDirs = [$sourcePath, $sourcePath . '/Emulator', $sourcePath . '/Emulator/Emulator']; foreach ($pomDirs as $pomDir) { $pomCheck = @shell_exec('test -f ' . escapeshellarg($pomDir . '/pom.xml') . ' && echo yes'); if ($pomCheck && trim($pomCheck) === 'yes') { $this->notify('Info', 'πŸ”¨ Building emulator with Maven...', 'info'); $buildResult = @shell_exec('cd ' . escapeshellarg($pomDir) . ' && mvn clean package -DskipTests 2>&1'); if ($buildResult && str_contains($buildResult, 'BUILD SUCCESS')) { // Find and move JAR to emulator folder $jarPath = $this->getSetting('emulator_jar_path', '/var/www/Emulator'); $jarFind = @shell_exec('find ' . escapeshellarg($pomDir . '/target') . ' -name "*jar-with-dependencies.jar" -type f 2>/dev/null | head -1'); if ($jarFind && trim($jarFind)) { $sourceJar = trim($jarFind); $jarName = basename($sourceJar); $destJar = $jarPath . '/' . $jarName; // Copy JAR @shell_exec('cp ' . escapeshellarg($sourceJar) . ' ' . escapeshellarg($destJar) . ' 2>&1'); // Also copy to Latest_Compiled_Version if it exists $latestPath = $sourcePath . '/Emulator/Latest_Compiled_Version'; if (is_dir($latestPath)) { @shell_exec('cp ' . escapeshellarg($sourceJar) . ' ' . escapeshellarg($latestPath . '/' . $jarName) . ' 2>&1'); } $this->notify('Success', 'βœ… Build succesvol! JAR verplaatst naar ' . $jarName . '. Herstart de emulator.', 'success'); } else { $this->notify('Success', 'βœ… Build succesvol! Herstart de emulator.', 'success'); } } else { $this->notify('Error', 'Build mislukt - controleer logs', 'danger'); } return; } } $this->notify('Warning', 'Geen pom.xml gevonden - kan niet bouwen vanaf source', 'warning'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function renderBackupsList(): HtmlString { try { $service = new EmulatorUpdateService; $backups = $service->getBackupList(); if ($backups === []) { return new HtmlString(<<<'HTML'
Nog geen backups beschikbaar
Backups worden automatisch aangemaakt bij elke emulator update
HTML); } $html = '
'; foreach ($backups as $backup) { $dateFormatted = str_replace('_', ' ', $backup['date']); $html .= <<
{$backup['jar']}
{$dateFormatted}
HTML; } $html .= ''; return new HtmlString($html); } catch (Exception $e) { return new HtmlString('
Kon backups niet laden: ' . e($e->getMessage()) . '
'); } } public function restoreBackup(string $backupName): void { try { $service = new EmulatorUpdateService; $result = $service->restoreBackup($backupName); if ($result['success']) { $this->notify('Success', $result['message'], 'success'); } else { $this->notify('Error', $result['error'], 'danger'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function runSqlUpdates(): void { try { $exitCode = Artisan::call('update:auto', ['--force' => true]); if ($exitCode === 0) { $this->notify('Success', 'SQL updates toegepast!', 'success'); } else { $this->notify('Warning', 'Update controle voltooid', 'warning'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function saveEmulator(): void { try { $settings = app(SettingsService::class); $settings->set('emulator_github_url', $this->data['emulator_github_url'] ?? ''); $settings->set('emulator_jar_direct_url', $this->data['emulator_jar_direct_url'] ?? ''); $settings->set('emulator_jar_path', $this->data['emulator_jar_path'] ?? '/root/emulator'); $settings->set('emulator_source_repo', $this->data['emulator_source_repo'] ?? ''); $settings->set('emulator_source_path', $this->data['emulator_source_path'] ?? '/var/www/emulator-source'); $settings->set('emulator_github_branch', $this->data['emulator_github_branch'] ?? 'main'); $settings->set('emulator_database_host', $this->data['emulator_database_host'] ?? '127.0.0.1'); $settings->set('emulator_database_name', $this->data['emulator_database_name'] ?? ''); $settings->set('emulator_service_name', $this->data['emulator_service_name'] ?? 'arcturus'); $this->notify('Success', 'Emulator instellingen opgeslagen!', 'success'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function checkNitroUpdates(): void { try { $clientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client'); $rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer'); $branch = $this->getSetting('nitro_github_branch', 'main'); // Get local commits before pull $clientCommitBefore = $this->getGitCommit($clientPath); $rendererCommitBefore = $this->getGitCommit($rendererPath); // Pull latest from GitHub using sudo $this->notify('Info', 'πŸ”„ Pulling Nitro Client van GitHub...', 'info'); @shell_exec('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1'); $this->notify('Info', 'πŸ”„ Pulling Nitro Renderer van GitHub...', 'info'); @shell_exec('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1'); // Get local commits after pull $clientCommitAfter = $this->getGitCommit($clientPath); $rendererCommitAfter = $this->getGitCommit($rendererPath); // Check if anything was updated $clientUpdated = $clientCommitBefore !== $clientCommitAfter; $rendererUpdated = $rendererCommitBefore !== $rendererCommitAfter; if ($clientUpdated || $rendererUpdated) { $this->notify('Success', 'βœ… Nitro bijgewerkt! Build opnieuw met "Build" knop.', 'success'); } else { $this->notify('Success', 'βœ“ Nitro is al up-to-date!', 'success'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function buildNitro(): void { try { $clientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client'); $rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer'); $branch = $this->getSetting('nitro_github_branch', 'main'); // Pull latest from GitHub $this->notify('Info', 'πŸ”„ Pulling Nitro Client...', 'info'); @shell_exec('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1'); $this->notify('Info', 'πŸ”„ Pulling Nitro Renderer...', 'info'); @shell_exec('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1'); // Install dependencies $this->notify('Info', 'πŸ“¦ Installing dependencies...', 'info'); @shell_exec('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data npm install 2>&1'); @shell_exec('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data npm install 2>&1'); // Build $this->notify('Info', 'πŸ”¨ Building Nitro...', 'info'); $exitCode = Artisan::call('build:theme'); if ($exitCode === 0) { $this->notify('Success', 'βœ… Nitro build succesvol!', 'success'); } else { $this->notify('Warning', 'Build gestart - controleer handmatig', 'warning'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function generateNitroConfigs(): void { try { $siteUrl = $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl()); if ($siteUrl === '' || $siteUrl === '0' || ! filter_var($siteUrl, FILTER_VALIDATE_URL)) { $this->notify('Error', 'Voer een geldige URL in (bijv. https://epicnabbo.nl)', 'danger'); return; } $paths = $this->autoDetectPaths(); $settings = app(SettingsService::class); $settings->set('nitro_client_path', $paths['nitro_client_path']); $settings->set('nitro_renderer_path', $paths['nitro_renderer_path']); $settings->set('nitro_build_path', $paths['nitro_build_path']); $settings->set('nitro_webroot', $paths['nitro_webroot']); $settings->set('gamedata_path', $paths['gamedata_path']); $webroot = $paths['nitro_webroot']; $existingConfigs = $this->readExistingConfigs($webroot, $paths['gamedata_path']); $exitCode = Artisan::call('app:generate-nitro-configs', [ '--site-url' => $siteUrl, ]); if ($existingConfigs !== [] && $exitCode === 0) { $this->mergeExistingConfigs($webroot, $existingConfigs); } $settings->set('nitro_last_checked', now()->toIso8601String()); if ($exitCode === 0) { $this->notify('Success', 'Configs gegenereerd & bestaande instellingen behouden!', 'success'); } else { $this->notify('Warning', 'Config gegenereerd (controleer handmatig)', 'warning'); } } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } private function renderClothingStatus(): HtmlString { try { $count = DB::table('catalog_clothing')->count(); $html = '
'; $html .= '
'; $html .= '
Kleding Items
'; $html .= '
' . number_format($count) . '
'; $html .= '
'; $html .= '
'; return new HtmlString($html); } catch (Exception $e) { return new HtmlString('
Fout: ' . e($e->getMessage()) . '
'); } } public function syncClothing(): void { try { $catalogService = app(CatalogService::class); $result = $catalogService->syncCatalogClothing(); $message = 'πŸ‘” Kleding Sync Resultaat:' . PHP_EOL; $message .= 'β€’ Toegevoegd: ' . $result['inserted'] . PHP_EOL; $message .= 'β€’ Totaal: ' . $result['total']; $this->notify('Success', $message, 'success'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } private function readExistingConfigs(string $webroot, string $gamedataPath = ''): array { $configs = []; $files = ['renderer-config.json', 'ui-config.json', 'UITexts.json']; foreach ($files as $file) { $path = $webroot . '/' . $file; $result = @shell_exec('cat ' . escapeshellarg($path) . ' 2>/dev/null'); if ($result) { $decoded = json_decode($result, true); if (json_last_error() === JSON_ERROR_NONE) { $configs[$file] = $decoded; } } } if ($gamedataPath !== '' && $gamedataPath !== '0') { $gamedataConfigs = [ 'ExternalTexts.json' => $gamedataPath . '/config/ExternalTexts.json', 'FurnitureData.json' => $gamedataPath . '/config/FurnitureData.json', 'ProductData.json' => $gamedataPath . '/config/ProductData.json', 'FigureData.json' => $gamedataPath . '/config/FigureData.json', ]; foreach ($gamedataConfigs as $key => $path) { $result = @shell_exec('cat ' . escapeshellarg($path) . ' 2>/dev/null'); if ($result) { $decoded = json_decode($result, true); if (json_last_error() === JSON_ERROR_NONE) { $configs['gamedata.' . $key] = $decoded; } } } } return $configs; } private function mergeExistingConfigs(string $webroot, array $existingConfigs): void { if (isset($existingConfigs['renderer-config.json'])) { $newPath = $webroot . '/renderer-config.json'; $newResult = @shell_exec('cat ' . escapeshellarg($newPath) . ' 2>/dev/null'); $newConfig = json_decode($newResult, true); if ($newConfig && json_last_error() === JSON_ERROR_NONE) { $merged = array_merge($existingConfigs['renderer-config.json'], $newConfig); @file_put_contents($newPath, json_encode($merged, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } } } public function detectAndSavePaths(): void { try { $paths = $this->autoDetectPaths(); $settings = app(SettingsService::class); $settings->set('nitro_client_path', $paths['nitro_client_path']); $settings->set('nitro_renderer_path', $paths['nitro_renderer_path']); $settings->set('nitro_build_path', $paths['nitro_build_path']); $settings->set('nitro_webroot', $paths['nitro_webroot']); $settings->set('gamedata_path', $paths['gamedata_path']); $settings->set('emulator_jar_path', $paths['emulator_jar_path']); $settings->set('emulator_source_path', $paths['emulator_source_path']); $this->fillForm(); $this->notify('Success', 'Paths gedetecteerd en opgeslagen!', 'success'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function saveNitro(): void { try { $settings = app(SettingsService::class); $settings->set('nitro_client_path', $this->data['nitro_client_path'] ?? '/var/www/atomcms/nitro-client'); $settings->set('nitro_renderer_path', $this->data['nitro_renderer_path'] ?? '/var/www/atomcms/nitro-renderer'); $settings->set('nitro_build_path', $this->data['nitro_build_path'] ?? '/var/www/atomcms/nitro-client/dist'); $settings->set('nitro_webroot', $this->data['nitro_webroot'] ?? '/var/www/Client'); $settings->set('gamedata_path', $this->data['gamedata_path'] ?? '/var/www/Gamedata'); $settings->set('nitro_github_url', $this->data['nitro_github_url'] ?? ''); $settings->set('nitro_github_branch', $this->data['nitro_github_branch'] ?? 'main'); $settings->set('nitro_site_url', $this->data['nitro_site_url'] ?? ''); $this->notify('Success', 'Nitro instellingen opgeslagen!', 'success'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function saveAutoUpdate(): void { try { $settings = app(SettingsService::class); $settings->set('auto_update_enabled', ($this->data['auto_update_enabled'] ?? false) ? '1' : '0'); $settings->set('auto_update_schedule', $this->data['auto_update_schedule'] ?? '03:00'); $settings->set('auto_update_days', $this->data['auto_update_days'] ?? '0,6'); $this->notify('Success', 'Auto update instellingen opgeslagen!', 'success'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } public function saveAlerts(): void { try { $settings = app(SettingsService::class); $settings->set('alert_email_enabled', ($this->data['alert_email_enabled'] ?? false) ? '1' : '0'); $settings->set('alert_email_address', $this->data['alert_email_address'] ?? ''); $settings->set('alert_discord_enabled', ($this->data['alert_discord_enabled'] ?? false) ? '1' : '0'); $settings->set('alert_discord_webhook_url', $this->data['alert_discord_webhook_url'] ?? ''); $settings->set('discord_webhook_ranks', json_encode($this->data['discord_webhook_ranks'] ?? [])); $this->notify('Success', 'Meldingen opgeslagen!', 'success'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } private function getStaffActivityHtml(): HtmlString { try { $activities = StaffActivity::with('user:id,username,look') ->orderByDesc('created_at') ->limit(20) ->get(); if ($activities->isEmpty()) { return new HtmlString(<<<'HTML'

No staff activities recorded yet.

Staff actions will appear here automatically.

HTML); } $itemsHtml = ''; foreach ($activities as $activity) { $icon = StaffActivity::getActionIcon($activity->action); $color = StaffActivity::getActionColor($activity->action); $timeAgo = $this->getTimeAgo($activity->created_at); $username = $activity->user?->username ?? 'Unknown'; $userLook = $activity->user?->look ?? ''; $itemsHtml .= <<
{$icon}
{$username}
{$activity->description}
{$timeAgo}
HTML; } return new HtmlString(<<
Recent Staff Activities Last 20 actions
{$itemsHtml}
HTML); } catch (Exception $e) { return new HtmlString(<<<'HTML'

Error loading staff activities

Make sure to run: php artisan migrate

HTML); } } private function getTimeAgo($timestamp): string { try { $carbon = Carbon::parse($timestamp); $now = Carbon::now(); $diff = $now->diffInMinutes($carbon); if ($diff < 1) { return 'Just now'; } elseif ($diff < 60) { return $diff . 'm ago'; } elseif ($diff < 1440) { return floor($diff / 60) . 'h ago'; } else { return floor($diff / 1440) . 'd ago'; } } catch (Exception) { return 'Unknown'; } } public function testDiscord(): void { try { $webhookUrl = $this->data['alert_discord_webhook_url'] ?? ''; if (empty($webhookUrl)) { $this->notify('Error', 'Webhook URL is leeg', 'danger'); return; } Http::post($webhookUrl, ['content' => 'βœ… Test van Atom CMS Commandocentrum']); $this->notify('Success', 'Test bericht verzonden!', 'success'); } catch (Exception $e) { $this->notify('Error', $e->getMessage(), 'danger'); } } private function notify(string $title, string $message, string $color): void { Notification::make() ->title($title) ->body($message) ->color($color) ->send(); } }