refactor: extract action classes, add Blade components, reduce Commandocentrum

- Create EmulatorControlAction and NitroControlAction classes
- Extract business logic from Commandocentrum controller methods
- Add Blade components for status cards, diagnostics, and summary cards
- Replace shell_exec with file_get_contents in config reading
- Remove duplicate methods and unused code
- Commandocentrum reduced from 2033 to 1780 lines
This commit is contained in:
root
2026-05-19 20:57:31 +02:00
parent 976b990a8a
commit cbe189fd96
6 changed files with 364 additions and 294 deletions
+87
View File
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace App\Actions\Commandocentrum;
use App\Services\EmulatorUpdateService;
use App\Services\RconService;
use App\Services\SettingsService;
use Illuminate\Support\Facades\Process;
class EmulatorControlAction
{
public function __construct(
private readonly SettingsService $settings,
private readonly EmulatorUpdateService $updateService,
) {}
public function start(): array
{
$serviceName = $this->settings->getOrDefault('emulator_service_name', 'arcturus');
$result = Process::timeout(30)->run("systemctl start {$serviceName} 2>&1");
return [
'success' => $result->successful(),
'message' => $result->successful() ? 'Emulator gestart!' : ($result->output() ?: 'Kon emulator niet starten'),
];
}
public function stop(): array
{
$serviceName = $this->settings->getOrDefault('emulator_service_name', 'arcturus');
$result = Process::timeout(30)->run("systemctl stop {$serviceName} 2>&1");
return [
'success' => $result->successful(),
'message' => $result->successful() ? 'Emulator gestopt!' : ($result->output() ?: 'Kon emulator niet stoppen'),
];
}
public function restart(): array
{
$serviceName = $this->settings->getOrDefault('emulator_service_name', 'arcturus');
$result = Process::timeout(60)->run("systemctl restart {$serviceName} 2>&1");
return [
'success' => $result->successful(),
'message' => $result->successful() ? 'Emulator herstart!' : ($result->output() ?: 'Kon emulator niet herstarten'),
];
}
public function sendAlert(string $message): array
{
if (empty($message)) {
return ['success' => false, 'message' => 'Bericht mag niet leeg zijn'];
}
app(RconService::class)->sendCommand('alert', ['message' => $message]);
return ['success' => true, 'message' => 'Alert verstuurd naar alle gebruikers!'];
}
public function build(): array
{
return $this->updateService->buildFromSource();
}
public function update(): array
{
return $this->updateService->updateEmulator();
}
public function runSqlUpdates(): array
{
return $this->updateService->runSqlUpdates();
}
public function getBackups(): array
{
return $this->updateService->getBackupList();
}
public function restoreBackup(string $backupName): array
{
return $this->updateService->restoreBackup($backupName);
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace App\Actions\Commandocentrum;
use App\Services\SettingsService;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Process;
class NitroControlAction
{
public function __construct(
private readonly SettingsService $settings,
) {}
public function pullUpdates(string $clientPath, string $rendererPath, string $branch): array
{
$this->runGitPull($clientPath, $branch);
$this->runGitPull($rendererPath, $branch);
return ['success' => true, 'message' => 'Nitro bijgewerkt van GitHub'];
}
public function build(string $clientPath, string $rendererPath, string $branch): array
{
$this->runGitPull($clientPath, $branch);
$this->runGitPull($rendererPath, $branch);
Process::timeout(120)->run('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data npm install 2>&1');
Process::timeout(120)->run('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data npm install 2>&1');
$exitCode = Artisan::call('build:theme');
return [
'success' => $exitCode === 0,
'message' => $exitCode === 0 ? 'Nitro build succesvol!' : 'Build gestart - controleer handmatig',
];
}
public function generateConfigs(string $siteUrl, string $webroot, string $gamedataPath): array
{
if (! filter_var($siteUrl, FILTER_VALIDATE_URL)) {
return ['success' => false, 'message' => 'Voer een geldige URL in'];
}
$existingConfigs = $this->readExistingConfigs($webroot, $gamedataPath);
$exitCode = Artisan::call('app:generate-nitro-configs', ['--site-url' => $siteUrl]);
if ($existingConfigs !== [] && $exitCode === 0) {
$this->mergeExistingConfigs($webroot, $existingConfigs);
}
$this->settings->set('nitro_last_checked', now()->toIso8601String());
return [
'success' => $exitCode === 0,
'message' => $exitCode === 0 ? 'Configs gegenereerd & bestaande instellingen behouden!' : 'Config gegenereerd (controleer handmatig)',
];
}
private function runGitPull(string $path, string $branch): void
{
Process::timeout(60)->run('cd ' . escapeshellarg($path) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1');
}
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;
$content = @file_get_contents($path);
if ($content) {
$decoded = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE) {
$configs[$file] = $decoded;
}
}
}
if ($gamedataPath !== '') {
$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) {
$content = @file_get_contents($path);
if ($content) {
$decoded = json_decode($content, 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';
$newContent = @file_get_contents($newPath);
$newConfig = json_decode($newContent, 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));
}
}
}
}
+44 -294
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Filament\Pages\Monitoring;
use App\Actions\Commandocentrum\EmulatorControlAction;
use App\Actions\Commandocentrum\NitroControlAction;
use App\Enums\AlertSeverity;
use App\Models\Miscellaneous\WebsitePermission;
use App\Models\StaffActivity;
@@ -1267,64 +1269,29 @@ final class Commandocentrum extends Page implements HasForms
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');
$result = app(EmulatorControlAction::class)->sendAlert($this->data['hotel_alert_message'] ?? '');
$this->notify($result['success'] ? 'Success' : 'Error', $result['message'], $result['success'] ? 'success' : 'danger');
if ($result['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');
}
$result = app(EmulatorControlAction::class)->start();
$this->notify($result['success'] ? 'Success' : 'Error', $result['message'], $result['success'] ? 'success' : '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');
}
$result = app(EmulatorControlAction::class)->stop();
$this->notify($result['success'] ? 'Success' : 'Error', $result['message'], $result['success'] ? 'success' : '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');
}
$result = app(EmulatorControlAction::class)->restart();
$this->notify($result['success'] ? 'Success' : 'Error', $result['message'], $result['success'] ? 'success' : 'danger');
}
public function checkEmulator(): void
@@ -1345,95 +1312,22 @@ final class Commandocentrum extends Page implements HasForms
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');
}
$result = app(EmulatorControlAction::class)->update();
$this->notify($result['success'] ?? false ? 'Success' : 'Error', $result['message'] ?? $result['error'] ?? 'Onbekende fout', ($result['success'] ?? false) ? 'success' : 'danger');
Cache::forget('all_updates_check');
$this->fillForm();
}
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 = $this->runCommand('cd ' . escapeshellarg($sourcePath) . ' && git pull origin ' . escapeshellarg($branch) . ' 2>&1', 60);
$mavenCheck = $this->runCommand('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 = $this->runCommand('test -f ' . escapeshellarg($pomDir . '/pom.xml') . ' && echo yes');
if ($pomCheck === 'yes') {
$this->notify('Info', '🔨 Building emulator with Maven...', 'info');
$buildResult = $this->runCommand('cd ' . escapeshellarg($pomDir) . ' && mvn clean package -DskipTests 2>&1', 600);
if ($buildResult && str_contains($buildResult, 'BUILD SUCCESS')) {
$jarPath = $this->getSetting('emulator_jar_path', '/var/www/Emulator');
$jarFind = $this->runCommand('find ' . escapeshellarg($pomDir . '/target') . ' -name "*jar-with-dependencies.jar" -type f 2>/dev/null | head -1', 30);
if ($jarFind) {
$sourceJar = trim($jarFind);
$jarName = basename($sourceJar);
$destJar = $jarPath . '/' . $jarName;
$this->runCommand('cp ' . escapeshellarg($sourceJar) . ' ' . escapeshellarg($destJar) . ' 2>&1', 30);
$latestPath = $sourcePath . '/Emulator/Latest_Compiled_Version';
if (is_dir($latestPath)) {
$this->runCommand('cp ' . escapeshellarg($sourceJar) . ' ' . escapeshellarg($latestPath . '/' . $jarName) . ' 2>&1', 30);
}
$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');
}
$result = app(EmulatorControlAction::class)->build();
$this->notify($result['success'] ?? false ? 'Success' : 'Error', $result['message'] ?? $result['error'] ?? 'Onbekende fout', ($result['success'] ?? false) ? 'success' : 'danger');
}
public function renderBackupsList(): HtmlString
{
try {
$service = new EmulatorUpdateService;
$backups = $service->getBackupList();
$backups = app(EmulatorControlAction::class)->getBackups();
if ($backups === []) {
return new HtmlString(<<<'HTML'
@@ -1480,34 +1374,17 @@ final class Commandocentrum extends Page implements HasForms
}
}
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');
}
$result = app(EmulatorControlAction::class)->runSqlUpdates();
$this->notify($result['success'] ?? false ? 'Success' : 'Error', $result['message'] ?? 'Onbekende fout', ($result['success'] ?? false) ? 'success' : 'danger');
}
public function restoreBackup(string $backupName): void
{
$result = app(EmulatorControlAction::class)->restoreBackup($backupName);
$this->notify($result['success'] ? 'Success' : 'Error', $result['message'] ?? $result['error'] ?? 'Onbekende fout', $result['success'] ? 'success' : 'danger');
$this->fillForm();
}
public function saveEmulator(): void
@@ -1531,109 +1408,34 @@ final class Commandocentrum extends Page implements HasForms
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');
$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');
$this->runCommand('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1', 60);
$this->runCommand('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1', 60);
// 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');
}
$result = app(NitroControlAction::class)->pullUpdates($clientPath, $rendererPath, $branch);
$this->notify('Success', $result['message'], 'success');
$this->fillForm();
}
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');
$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');
$this->runCommand('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1', 60);
$this->runCommand('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data git pull origin ' . escapeshellarg($branch) . ' 2>&1', 60);
$this->runCommand('cd ' . escapeshellarg($clientPath) . ' && sudo -u www-data npm install 2>&1', 120);
$this->runCommand('cd ' . escapeshellarg($rendererPath) . ' && sudo -u www-data npm install 2>&1', 120);
// 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');
}
$result = app(NitroControlAction::class)->build($clientPath, $rendererPath, $branch);
$this->notify($result['success'] ? 'Success' : 'Warning', $result['message'], $result['success'] ? 'success' : 'warning');
}
public function generateNitroConfigs(): void
{
try {
$siteUrl = $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl());
$siteUrl = $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl());
$webroot = $this->getSetting('nitro_webroot', '/var/www/Client');
$gamedataPath = $this->getSetting('gamedata_path', '/var/www/Gamedata');
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');
}
$result = app(NitroControlAction::class)->generateConfigs($siteUrl, $webroot, $gamedataPath);
$this->notify($result['success'] ? 'Success' : 'Error', $result['message'], $result['success'] ? 'success' : 'danger');
$this->fillForm();
}
private function renderClothingStatus(): HtmlString
@@ -1669,58 +1471,6 @@ final class Commandocentrum extends Page implements HasForms
}
}
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;
$content = $this->readFile($path);
if ($content) {
$decoded = json_decode($content, 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 = $this->readFile($path);
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';
$newContent = $this->readFile($newPath);
$newConfig = json_decode($newContent, 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 {