Remove all auto-update functionality (commands, services, widgets, blades, translations)
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Actions\Commandocentrum;
|
namespace App\Actions\Commandocentrum;
|
||||||
|
|
||||||
use App\Services\EmulatorUpdateService;
|
|
||||||
use App\Services\RconService;
|
use App\Services\RconService;
|
||||||
use App\Services\SettingsService;
|
use App\Services\SettingsService;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
@@ -13,7 +12,6 @@ class EmulatorControlAction
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly SettingsService $settings,
|
private readonly SettingsService $settings,
|
||||||
private readonly EmulatorUpdateService $updateService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function start(): array
|
public function start(): array
|
||||||
@@ -59,29 +57,4 @@ class EmulatorControlAction
|
|||||||
|
|
||||||
return ['success' => true, 'message' => 'Alert verstuurd naar alle gebruikers!'];
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
<?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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Enums\AlertChannel;
|
|
||||||
use App\Enums\AlertType;
|
|
||||||
use App\Services\AlertService;
|
|
||||||
use App\Services\EmulatorUpdateService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class AutoUpdateCommand extends Command
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected $signature = 'update:auto
|
|
||||||
{--force : Forceer update ook al is het niet de geplande tijd}
|
|
||||||
{--sql-only : Alleen SQL updates draaien}
|
|
||||||
{--emu-only : Alleen emulator updates draaien}';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected $description = 'Automatische emulator en SQL updates uitvoeren';
|
|
||||||
|
|
||||||
private const string CACHE_KEY_LAST_EMU_UPDATE = 'auto_update_last_emu';
|
|
||||||
|
|
||||||
private const string CACHE_KEY_LAST_SQL_UPDATE = 'auto_update_last_sql';
|
|
||||||
|
|
||||||
public function handle(AlertService $alertService, EmulatorUpdateService $updateService): int
|
|
||||||
{
|
|
||||||
$this->info('Automatische update check...');
|
|
||||||
|
|
||||||
$isForced = $this->option('force');
|
|
||||||
$sqlOnly = $this->option('sql-only');
|
|
||||||
$emuOnly = $this->option('emu-only');
|
|
||||||
|
|
||||||
if (! $isForced && ! $this->isScheduledTime()) {
|
|
||||||
$this->line('Niet de geplande tijd, overslaan.');
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $sqlOnly) {
|
|
||||||
$this->runEmulatorUpdate($alertService, $updateService);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $emuOnly) {
|
|
||||||
$this->runSqlUpdates($alertService, $updateService);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isScheduledTime(): bool
|
|
||||||
{
|
|
||||||
$enabled = setting('auto_update_enabled', true);
|
|
||||||
|
|
||||||
if (! $enabled) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scheduleTime = setting('auto_update_schedule', '03:00');
|
|
||||||
$scheduleDays = setting('auto_update_days', '0,1,2,3,4,5,6');
|
|
||||||
|
|
||||||
$now = now();
|
|
||||||
$currentTime = $now->format('H:i');
|
|
||||||
$currentDay = (int) $now->dayOfWeek;
|
|
||||||
|
|
||||||
$allowedDays = array_map(intval(...), explode(',', $scheduleDays));
|
|
||||||
|
|
||||||
if (! in_array($currentDay, $allowedDays)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($currentTime !== $scheduleTime) {
|
|
||||||
$minuteDiff = abs(strtotime($currentTime) - strtotime((string) $scheduleTime)) / 60;
|
|
||||||
if ($minuteDiff > 5) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function runEmulatorUpdate(AlertService $alertService, EmulatorUpdateService $updateService): void
|
|
||||||
{
|
|
||||||
$check = $updateService->checkForUpdates();
|
|
||||||
|
|
||||||
if (! ($check['update_available'] ?? false)) {
|
|
||||||
$this->line('Emulator is al up-to-date.');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->warn('Nieuwe emulator versie beschikbaar: ' . ($check['latest_version'] ?? 'onbekend'));
|
|
||||||
$this->info('Emulator updaten...');
|
|
||||||
|
|
||||||
$result = $updateService->updateEmulator();
|
|
||||||
|
|
||||||
if ($result['success']) {
|
|
||||||
$this->info('Emulator succesvol geüpdatet!');
|
|
||||||
$alertService->sendEmulatorUpdate($result['version'] ?? 'onbekend', $result['message'] ?? '');
|
|
||||||
Log::info('[AutoUpdate] Emulator updated to v' . ($result['version'] ?? 'unknown'));
|
|
||||||
} else {
|
|
||||||
$this->error('Emulator update mislukt: ' . ($result['error'] ?? 'onbekende fout'));
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_ERROR,
|
|
||||||
'Emulator Auto-Update Mislukt: ' . ($result['error'] ?? 'Onbekende fout'),
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::put(self::CACHE_KEY_LAST_EMU_UPDATE, now()->toIso8601String());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function runSqlUpdates(AlertService $alertService, EmulatorUpdateService $updateService): void
|
|
||||||
{
|
|
||||||
$check = $updateService->checkForSqlUpdates();
|
|
||||||
|
|
||||||
if (! ($check['has_updates'] ?? false)) {
|
|
||||||
$this->line('Geen SQL updates beschikbaar.');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->warn($check['message']);
|
|
||||||
$this->info('SQL updates uitvoeren...');
|
|
||||||
|
|
||||||
$result = $updateService->runSqlUpdates();
|
|
||||||
|
|
||||||
if ($result['success'] && ! empty($result['files_run'])) {
|
|
||||||
$count = count($result['files_run']);
|
|
||||||
$this->info("{$count} SQL updates succesvol uitgevoerd!");
|
|
||||||
$alertService->sendSqlUpdate($count, $result['message'] ?? '');
|
|
||||||
Log::info('[AutoUpdate] SQL updates completed', ['files' => $result['files_run']]);
|
|
||||||
} elseif (! empty($result['errors'])) {
|
|
||||||
$this->warn('SQL updates met fouten: ' . implode(', ', $result['errors']));
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::CRITICAL_ERROR,
|
|
||||||
'SQL Updates Met Fouten: ' . implode(', ', $result['errors']),
|
|
||||||
['files' => $result['errors']],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::put(self::CACHE_KEY_LAST_SQL_UPDATE, now()->toIso8601String());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Enums\AlertChannel;
|
|
||||||
use App\Enums\AlertType;
|
|
||||||
use App\Services\AlertService;
|
|
||||||
use App\Services\EmulatorUpdateService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class EmulatorUpdateCommand extends Command
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected $signature = 'emulator:update
|
|
||||||
{--check : Alleen controleren op updates}
|
|
||||||
{--force : Forceer update ook al is er geen nieuwe versie}
|
|
||||||
{--repair : Probeer emulator te repareren}
|
|
||||||
{--rebuild : Forceer build vanaf source}';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected $description = 'Update de emulator vanaf GitHub';
|
|
||||||
|
|
||||||
public function handle(EmulatorUpdateService $updateService, AlertService $alertService): int
|
|
||||||
{
|
|
||||||
if ($this->option('repair')) {
|
|
||||||
return $this->repairEmulator($updateService, $alertService);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $updateService->isConfigured()) {
|
|
||||||
$this->error('Geen GitHub URL geconfigureerd voor emulator updates.');
|
|
||||||
$this->info('Configureer dit in Filament > Settings > Emulator');
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info('Emulator update service gestart...');
|
|
||||||
|
|
||||||
$checkResult = $updateService->checkForUpdates();
|
|
||||||
|
|
||||||
if (isset($checkResult['error'])) {
|
|
||||||
$this->error($checkResult['error']);
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->table(
|
|
||||||
['Property', 'Value'],
|
|
||||||
[
|
|
||||||
['Huidige Versie', $checkResult['current_version']],
|
|
||||||
['Nieuwste Versie', $checkResult['latest_version']],
|
|
||||||
['Update Beschikbaar', $checkResult['update_available'] ? 'JA' : 'NEE'],
|
|
||||||
['Type', $checkResult['update_type'] ?? 'N/A'],
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->option('check')) {
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$force = $this->option('force');
|
|
||||||
$rebuild = $this->option('rebuild');
|
|
||||||
|
|
||||||
if (! $checkResult['update_available'] && ! $force && ! $rebuild) {
|
|
||||||
$this->info('Emulator is al up-to-date!');
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $checkResult['update_available'] && ($force || $rebuild)) {
|
|
||||||
$this->warn('Geen nieuwe versie beschikbaar, maar force/rebuild aangevraagd.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $force && ! $rebuild && ! $this->confirm('Wil je de emulator updaten naar v' . $checkResult['latest_version'] . '?')) {
|
|
||||||
$this->info('Update geannuleerd.');
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info('Emulator wordt geüpdatet...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($rebuild || $checkResult['type'] === 'source_build') {
|
|
||||||
$this->info('Build vanaf source...');
|
|
||||||
$result = $updateService->buildFromSource($force);
|
|
||||||
} else {
|
|
||||||
$result = $updateService->updateEmulator();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($result['success']) {
|
|
||||||
$this->info($result['message'] ?? 'Emulator succesvol geüpdatet!');
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_ONLINE,
|
|
||||||
'Emulator succesvol geüpdatet naar v' . ($result['version'] ?? 'onbekend'),
|
|
||||||
[
|
|
||||||
'version' => $result['version'] ?? 'onbekend',
|
|
||||||
'jar' => $result['jar'] ?? 'N/A',
|
|
||||||
],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
Log::info('[EmulatorUpdateCommand] Update successful', $result);
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->error('Update mislukt: ' . ($result['error'] ?? 'Onbekende fout'));
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_ERROR,
|
|
||||||
'Emulator update mislukt: ' . ($result['error'] ?? 'Onbekende fout'),
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
Log::error('[EmulatorUpdateCommand] Update failed', $result);
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('Update exception: ' . $e->getMessage());
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::CRITICAL_ERROR,
|
|
||||||
'Emulator update exception: ' . $e->getMessage(),
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
Log::error('[EmulatorUpdateCommand] Exception', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function repairEmulator(EmulatorUpdateService $updateService, AlertService $alertService): int
|
|
||||||
{
|
|
||||||
$this->warn('🔧 Emulator repair modus gestart...');
|
|
||||||
$this->info('Dit zal de emulator status controleren en proberen te repareren.');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$repairResult = $updateService->repairEmulator();
|
|
||||||
|
|
||||||
if ($repairResult['success']) {
|
|
||||||
$this->info('✅ Repair succesvol!');
|
|
||||||
foreach ($repairResult['actions'] ?? [] as $action) {
|
|
||||||
$this->line(' - ' . $action);
|
|
||||||
}
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_ONLINE,
|
|
||||||
'Emulator gerepareerd',
|
|
||||||
['actions' => $repairResult['actions'] ?? []],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->error('❌ Repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout'));
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_ERROR,
|
|
||||||
'Emulator repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout'),
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('❌ Repair exception: ' . $e->getMessage());
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,796 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Services\NitroUpdateService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
class GenerateNitroConfigs extends Command
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected $signature = 'app:generate-nitro-configs
|
|
||||||
{--site-url= : The site URL to use}';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected $description = 'Generate Nitro configuration files (renderer-config.json, ui-config.json, UITexts.json)';
|
|
||||||
|
|
||||||
private array $checks = [];
|
|
||||||
|
|
||||||
public function handle(): int
|
|
||||||
{
|
|
||||||
$siteUrl = $this->option('site-url') ?? setting('nitro_site_url', config('app.url', 'https://epicnabbo.nl'));
|
|
||||||
|
|
||||||
$this->info('🔧 Nitro Config Generator');
|
|
||||||
$this->line('═══════════════════════════════════════════════');
|
|
||||||
|
|
||||||
// Check 1: Validate URL
|
|
||||||
$this->addCheck(function () use ($siteUrl) {
|
|
||||||
if (empty($siteUrl) || ! filter_var($siteUrl, FILTER_VALIDATE_URL)) {
|
|
||||||
throw new \Exception("Invalid URL: {$siteUrl}");
|
|
||||||
}
|
|
||||||
$this->line(" ✓ URL: {$siteUrl}");
|
|
||||||
});
|
|
||||||
|
|
||||||
$nitroService = new NitroUpdateService;
|
|
||||||
$status = $nitroService->getStatus();
|
|
||||||
$webroot = $status['webroot'] ?? '/var/www/Client';
|
|
||||||
$buildPath = $status['build_path'] ?? '/var/www/atomcms/nitro-client/dist';
|
|
||||||
|
|
||||||
// Check 2: Webroot exists
|
|
||||||
$this->addCheck(function () use ($webroot) {
|
|
||||||
$result = Process::timeout(5)->run('test -d ' . escapeshellarg((string) $webroot));
|
|
||||||
if ($result->exitCode() !== 0) {
|
|
||||||
throw new \Exception("Webroot does not exist: {$webroot}");
|
|
||||||
}
|
|
||||||
$this->line(" ✓ Webroot exists: {$webroot}");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check 3: Webroot is writable
|
|
||||||
$this->addCheck(function () use ($webroot) {
|
|
||||||
$result = Process::timeout(5)->run('test -w ' . escapeshellarg((string) $webroot));
|
|
||||||
if ($result->exitCode() !== 0) {
|
|
||||||
throw new \Exception("Webroot is not writable: {$webroot}");
|
|
||||||
}
|
|
||||||
$this->line(' ✓ Webroot is writable');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check 4: Build path exists
|
|
||||||
$this->addCheck(function () use ($buildPath) {
|
|
||||||
$result = Process::timeout(5)->run('test -d ' . escapeshellarg((string) $buildPath));
|
|
||||||
if ($result->exitCode() !== 0) {
|
|
||||||
throw new \Exception("Build path does not exist: {$buildPath}");
|
|
||||||
}
|
|
||||||
$this->line(" ✓ Build path exists: {$buildPath}");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check both buildPath and webroot for example files
|
|
||||||
$this->addCheck(function () use ($buildPath, $webroot) {
|
|
||||||
// Map .example to .json fallback
|
|
||||||
$fileMap = [
|
|
||||||
'renderer-config.example' => 'renderer-config.json',
|
|
||||||
'ui-config.example' => 'ui-config.json',
|
|
||||||
'UITexts.example' => 'UITexts.example',
|
|
||||||
];
|
|
||||||
|
|
||||||
$allFound = true;
|
|
||||||
|
|
||||||
foreach ($fileMap as $exampleFile => $jsonFile) {
|
|
||||||
// Check .example file first
|
|
||||||
$buildPathResult = Process::timeout(5)->run('test -f ' . escapeshellarg($buildPath . '/' . $exampleFile));
|
|
||||||
$webrootResult = Process::timeout(5)->run('test -f ' . escapeshellarg($webroot . '/' . $exampleFile));
|
|
||||||
|
|
||||||
if ($buildPathResult->exitCode() === 0) {
|
|
||||||
$this->line(" ✓ Found in build path: {$exampleFile}");
|
|
||||||
} elseif ($webrootResult->exitCode() === 0) {
|
|
||||||
$this->line(" ✓ Found in webroot: {$exampleFile}");
|
|
||||||
} elseif ($jsonFile !== $exampleFile) {
|
|
||||||
// Check json fallback for .example files
|
|
||||||
$jsonBuildResult = Process::timeout(5)->run('test -f ' . escapeshellarg($buildPath . '/' . $jsonFile));
|
|
||||||
$jsonWebrootResult = Process::timeout(5)->run('test -f ' . escapeshellarg($webroot . '/' . $jsonFile));
|
|
||||||
if ($jsonBuildResult->exitCode() === 0) {
|
|
||||||
$this->line(" ✓ Found in build path: {$jsonFile}");
|
|
||||||
} elseif ($jsonWebrootResult->exitCode() === 0) {
|
|
||||||
$this->line(" ✓ Found in webroot: {$jsonFile}");
|
|
||||||
} else {
|
|
||||||
$this->warn(" ⚠ Missing: {$exampleFile} or {$jsonFile}");
|
|
||||||
$allFound = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->warn(" ⚠ Missing: {$exampleFile}");
|
|
||||||
$allFound = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $allFound) {
|
|
||||||
throw new \Exception('Some example files are missing');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check 6: Check disk space
|
|
||||||
$this->addCheck(function () use ($webroot) {
|
|
||||||
$result = Process::timeout(10)->run("df -h {$webroot} | tail -1 | awk '{print $4}'");
|
|
||||||
if ($result->successful()) {
|
|
||||||
$free = trim($result->output());
|
|
||||||
$this->line(" ✓ Free space: {$free}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check 7: Validate bundled/config gamedata files
|
|
||||||
$this->addCheck(function () {
|
|
||||||
$bundledConfigDir = '/var/www/Gamedata/bundled/config';
|
|
||||||
$configDir = '/var/www/Gamedata/config';
|
|
||||||
$requiredFiles = ['HabboAvatarActions.json', 'FurnitureData.json', 'ExternalTexts.json', 'ProductData.json', 'FigureData.json', 'FigureMap.json', 'EffectMap.json'];
|
|
||||||
|
|
||||||
// Create bundled/config if it doesn't exist
|
|
||||||
if (! is_dir($bundledConfigDir)) {
|
|
||||||
mkdir($bundledConfigDir, 0755, true);
|
|
||||||
$this->line(" 📁 Created directory: {$bundledConfigDir}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy missing files from config to bundled/config
|
|
||||||
foreach ($requiredFiles as $file) {
|
|
||||||
$targetPath = $bundledConfigDir . '/' . $file;
|
|
||||||
if (! file_exists($targetPath)) {
|
|
||||||
$sourcePath = $configDir . '/' . $file;
|
|
||||||
if (file_exists($sourcePath)) {
|
|
||||||
copy($sourcePath, $targetPath);
|
|
||||||
$this->line(" 📋 Copied: {$file} to bundled/config");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->line(' ✓ Gamedata bundled/config files ready');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check 8: Validate existing config files
|
|
||||||
$this->addCheck(function () use ($webroot) {
|
|
||||||
$configFiles = ['renderer-config.json', 'ui-config.json', 'UITexts.json'];
|
|
||||||
foreach ($configFiles as $file) {
|
|
||||||
$path = $webroot . '/' . $file;
|
|
||||||
$result = Process::timeout(5)->run('test -f ' . escapeshellarg($path));
|
|
||||||
if ($result->exitCode() === 0) {
|
|
||||||
// Validate JSON
|
|
||||||
$jsonCheck = Process::timeout(5)->run('python3 -c "import json; json.load(open(\'' . $path . '\'))" 2>&1 || echo "INVALID"');
|
|
||||||
if (str_contains($jsonCheck->output(), 'INVALID')) {
|
|
||||||
$this->warn(" ⚠ Invalid JSON: {$file}");
|
|
||||||
} else {
|
|
||||||
$this->line(" ✓ Valid JSON: {$file}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->line(" - New file: {$file}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run all checks
|
|
||||||
$this->line('');
|
|
||||||
$this->info('Running pre-flight checks...');
|
|
||||||
$this->line('───────────────────────────────────────────────');
|
|
||||||
|
|
||||||
try {
|
|
||||||
foreach ($this->checks as $check) {
|
|
||||||
$check();
|
|
||||||
}
|
|
||||||
$this->line('───────────────────────────────────────────────');
|
|
||||||
$this->info('✅ All checks passed!');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('❌ Check failed: ' . $e->getMessage());
|
|
||||||
$this->newLine();
|
|
||||||
$this->error('Please fix the issue before generating configs.');
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate configs
|
|
||||||
$this->newLine();
|
|
||||||
$this->info('Generating configuration files...');
|
|
||||||
$this->line('───────────────────────────────────────────────');
|
|
||||||
|
|
||||||
$protocol = str_starts_with((string) $siteUrl, 'https') ? 'https' : 'http';
|
|
||||||
$host = preg_replace('/^https?:\/\//', '', (string) $siteUrl);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Step 0: Sync latest example files from GitHub
|
|
||||||
$this->syncExampleFromGithub($buildPath, $webroot);
|
|
||||||
|
|
||||||
$rendererConfig = $this->generateRendererConfig($protocol, $host, $buildPath, $webroot);
|
|
||||||
$uiConfig = $this->generateUiConfig($protocol, $host, $buildPath, $webroot);
|
|
||||||
|
|
||||||
// Compare with existing deployed configs
|
|
||||||
$this->compareConfigs($rendererConfig, 'renderer-config', $webroot);
|
|
||||||
$this->compareConfigs($uiConfig, 'ui-config', $webroot);
|
|
||||||
|
|
||||||
// Validate generated JSON
|
|
||||||
$this->validateJson($rendererConfig, 'renderer-config');
|
|
||||||
$this->validateJson($uiConfig, 'ui-config');
|
|
||||||
|
|
||||||
$tempFile = '/tmp/nitro_config_' . uniqid();
|
|
||||||
file_put_contents($tempFile . '_renderer', json_encode($rendererConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
||||||
file_put_contents($tempFile . '_ui', json_encode($uiConfig, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
||||||
|
|
||||||
// Copy generated configs
|
|
||||||
$commands = [
|
|
||||||
'cp "' . $tempFile . '_renderer" "' . $webroot . '/renderer-config.json"',
|
|
||||||
'cp "' . $tempFile . '_ui" "' . $webroot . '/ui-config.json"',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Copy UITexts.json if it exists (check both buildPath and webroot)
|
|
||||||
$uitextsInBuild = Process::timeout(5)->run('test -f "' . $buildPath . '/UITexts.example"')->exitCode() === 0;
|
|
||||||
$uitextsInWebroot = Process::timeout(5)->run('test -f "' . $webroot . '/UITexts.example"')->exitCode() === 0;
|
|
||||||
if ($uitextsInBuild) {
|
|
||||||
$commands[] = 'cp "' . $buildPath . '/UITexts.example" "' . $webroot . '/UITexts.json"';
|
|
||||||
$this->line(' ✓ Generated: UITexts.json (from build path)');
|
|
||||||
} elseif ($uitextsInWebroot) {
|
|
||||||
$commands[] = 'cp "' . $webroot . '/UITexts.example" "' . $webroot . '/UITexts.json"';
|
|
||||||
$this->line(' ✓ Generated: UITexts.json (from webroot)');
|
|
||||||
}
|
|
||||||
|
|
||||||
$commands[] = 'rm "' . $tempFile . '_renderer" "' . $tempFile . '_ui"';
|
|
||||||
|
|
||||||
Process::timeout(10)->run(implode(' && ', $commands));
|
|
||||||
|
|
||||||
// Set proper ownership
|
|
||||||
Process::timeout(10)->run("chown www-data:www-data {$webroot}/renderer-config.json {$webroot}/ui-config.json {$webroot}/UITexts.json 2>/dev/null");
|
|
||||||
|
|
||||||
setting('nitro_config_generated_at', now()->toIso8601String());
|
|
||||||
|
|
||||||
$this->line(' ✓ Generated: renderer-config.json');
|
|
||||||
$this->line(' ✓ Generated: ui-config.json');
|
|
||||||
|
|
||||||
// Verify generated files
|
|
||||||
$this->verifyGeneratedFiles($webroot);
|
|
||||||
|
|
||||||
$this->line('───────────────────────────────────────────────');
|
|
||||||
$this->info('✅ Configs generated successfully!');
|
|
||||||
|
|
||||||
Log::info('[NitroConfig] Generated successfully', [
|
|
||||||
'webroot' => $webroot,
|
|
||||||
'host' => $host,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('❌ Generation failed: ' . $e->getMessage());
|
|
||||||
Log::error('[NitroConfig] Generation failed', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addCheck(callable $check): void
|
|
||||||
{
|
|
||||||
$this->checks[] = function () use ($check) {
|
|
||||||
$check();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateJson(array $data, string $name): void
|
|
||||||
{
|
|
||||||
$encoded = json_encode($data, JSON_THROW_ON_ERROR);
|
|
||||||
$decoded = json_decode($encoded, true);
|
|
||||||
|
|
||||||
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
|
|
||||||
throw new \Exception("Invalid JSON generated for {$name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->line(" ✓ Validated: {$name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private function verifyGeneratedFiles(string $webroot): void
|
|
||||||
{
|
|
||||||
$files = ['renderer-config.json', 'ui-config.json', 'UITexts.json'];
|
|
||||||
$allValid = true;
|
|
||||||
|
|
||||||
foreach ($files as $file) {
|
|
||||||
$path = $webroot . '/' . $file;
|
|
||||||
$result = Process::timeout(5)->run('test -f ' . escapeshellarg($path));
|
|
||||||
|
|
||||||
if ($result->exitCode() === 0) {
|
|
||||||
$size = Process::timeout(5)->run('stat -c%s ' . escapeshellarg($path));
|
|
||||||
$sizeStr = trim($size->output()) . ' bytes';
|
|
||||||
$this->line(" ✓ Verified: {$file} ({$sizeStr})");
|
|
||||||
} else {
|
|
||||||
$this->warn(" ⚠ Missing: {$file}");
|
|
||||||
$allValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $allValid) {
|
|
||||||
throw new \Exception('Some files were not generated');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateRendererConfig(string $protocol, string $host, string $buildPath, string $webroot): array
|
|
||||||
{
|
|
||||||
$httpProtocol = $protocol === 'https' ? 'https' : 'http';
|
|
||||||
$wssProtocol = $protocol === 'https' ? 'wss' : 'ws';
|
|
||||||
$wsHost = 'ws.' . $host;
|
|
||||||
|
|
||||||
$rendererConfig = [];
|
|
||||||
|
|
||||||
// Check build path first, then webroot for .example file
|
|
||||||
$examplePath = $buildPath . '/renderer-config.example';
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) {
|
|
||||||
$examplePath = $webroot . '/renderer-config.example';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to .json version (newer Nitro-V3 format)
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) {
|
|
||||||
$jsonPath = $buildPath . '/renderer-config.json';
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) {
|
|
||||||
$examplePath = $jsonPath;
|
|
||||||
} else {
|
|
||||||
$jsonPath = $webroot . '/renderer-config.json';
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) {
|
|
||||||
$examplePath = $jsonPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load COMPLETE example file as base
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() === 0) {
|
|
||||||
$content = file_get_contents($examplePath);
|
|
||||||
// Fix invalid escape sequences (literal \n, \r, \t in JSON values)
|
|
||||||
$content = $this->fixInvalidJsonEscapeSequences($content);
|
|
||||||
$rendererConfig = @json_decode($content, true) ?: [];
|
|
||||||
$this->line(' ✓ Loaded renderer config from: ' . basename($examplePath) . ' (' . count($rendererConfig) . ' keys)');
|
|
||||||
} else {
|
|
||||||
$this->warn(' ⚠ renderer-config.example/json not found');
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively replace ALL URLs in the entire config
|
|
||||||
$rendererConfig = $this->replaceUrlsRecursively($rendererConfig, $httpProtocol, $wssProtocol, $host, $wsHost);
|
|
||||||
|
|
||||||
// Auto-detect asset directory names on disk and fix paths
|
|
||||||
$rendererConfig = $this->autoDetectAssetPaths($rendererConfig, $webroot);
|
|
||||||
|
|
||||||
// Special handling for socket.url - use ws subdomain
|
|
||||||
if (isset($rendererConfig['socket.url'])) {
|
|
||||||
$rendererConfig['socket.url'] = $wssProtocol . '://ws.' . $host;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-detect local asset paths from Gamedata directory
|
|
||||||
$gamedataBase = $httpProtocol . '://' . $host;
|
|
||||||
|
|
||||||
// Check what directories exist in Gamedata and set URLs accordingly
|
|
||||||
$gamedataPath = '/var/www/Gamedata';
|
|
||||||
if (is_dir($gamedataPath)) {
|
|
||||||
// Check for bundled directory - serve via /gamedata/bundled (nginx alias)
|
|
||||||
if (is_dir($gamedataPath . '/bundled')) {
|
|
||||||
$rendererConfig['asset.url'] = $gamedataBase . '/gamedata/bundled';
|
|
||||||
}
|
|
||||||
// JSON config files are in /gamedata/config/
|
|
||||||
if (is_dir($gamedataPath . '/config')) {
|
|
||||||
$rendererConfig['gamedata.url'] = $gamedataBase . '/gamedata/config';
|
|
||||||
}
|
|
||||||
// Check for c_images directory - serve via /gamedata/c_images
|
|
||||||
if (is_dir($gamedataPath . '/c_images')) {
|
|
||||||
$rendererConfig['image.library.url'] = $gamedataBase . '/gamedata/c_images/';
|
|
||||||
// Use icons folder for furni icons
|
|
||||||
if (is_dir($gamedataPath . '/icons')) {
|
|
||||||
$rendererConfig['hof.furni.url'] = $gamedataBase . '/gamedata/icons';
|
|
||||||
} else {
|
|
||||||
$rendererConfig['hof.furni.url'] = $gamedataBase . '/gamedata/c_images/dcr/hof_furni';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fix furni icon path - icons are directly in hof.furni folder, not in icons subfolder
|
|
||||||
if (isset($rendererConfig['furni.asset.icon.url'])) {
|
|
||||||
$rendererConfig['furni.asset.icon.url'] = '${hof.furni.url}/%libname%%param%_icon.png';
|
|
||||||
}
|
|
||||||
// Fix sound machine samples path - sounds are in /gamedata/sounds/
|
|
||||||
if (isset($rendererConfig['external.samples.url'])) {
|
|
||||||
$rendererConfig['external.samples.url'] = $gamedataBase . '/gamedata/sounds/sound_machine_sample_%sample%.mp3';
|
|
||||||
}
|
|
||||||
// Check for images directory
|
|
||||||
if (is_dir($gamedataPath . '/images')) {
|
|
||||||
$rendererConfig['images.url'] = $gamedataBase . '/gamedata/images';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add missing keys that might not be in the example
|
|
||||||
if (! isset($rendererConfig['external.plugins'])) {
|
|
||||||
$rendererConfig['external.plugins'] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add YouTube API key from settings
|
|
||||||
$youtubeApiKey = setting('youtube_api_key', '');
|
|
||||||
if (! empty($youtubeApiKey)) {
|
|
||||||
$rendererConfig['youtube.api.key'] = $youtubeApiKey;
|
|
||||||
$this->line(' ✓ Added YouTube API key to renderer config');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure pet.types matches the exact required list in order
|
|
||||||
if (isset($rendererConfig['pet.types'])) {
|
|
||||||
$requiredPetTypes = [
|
|
||||||
'dog',
|
|
||||||
'cat',
|
|
||||||
'croco',
|
|
||||||
'terrier',
|
|
||||||
'bear',
|
|
||||||
'pig',
|
|
||||||
'lion',
|
|
||||||
'rhino',
|
|
||||||
'spider',
|
|
||||||
'turtle',
|
|
||||||
'chicken',
|
|
||||||
'frog',
|
|
||||||
'dragon',
|
|
||||||
'monster',
|
|
||||||
'monkey',
|
|
||||||
'horse',
|
|
||||||
'monsterplant',
|
|
||||||
'bunnyeaster',
|
|
||||||
'bunnyevil',
|
|
||||||
'bunnydepressed',
|
|
||||||
'bunnylove',
|
|
||||||
'pigeongood',
|
|
||||||
'pigeonevil',
|
|
||||||
'demonmonkey',
|
|
||||||
'bearbaby',
|
|
||||||
'terrierbaby',
|
|
||||||
'gnome',
|
|
||||||
'leprechaun',
|
|
||||||
'kittenbaby',
|
|
||||||
'puppybaby',
|
|
||||||
'pigletbaby',
|
|
||||||
'haloompa',
|
|
||||||
'fools',
|
|
||||||
'pterosaur',
|
|
||||||
'velociraptor',
|
|
||||||
'cow',
|
|
||||||
'dragondog',
|
|
||||||
'pkmshaymin2',
|
|
||||||
'LeetEendjes',
|
|
||||||
'pkmnentei',
|
|
||||||
'squirtle',
|
|
||||||
'LeetBH',
|
|
||||||
'LeetCaviaaa',
|
|
||||||
'LeetFantj',
|
|
||||||
'LeetHotelMario',
|
|
||||||
'LeetUil',
|
|
||||||
'LeetWolf',
|
|
||||||
'pokemon_mewblu',
|
|
||||||
'LeetBB',
|
|
||||||
'LeetHotelMari1',
|
|
||||||
'LeetMewtw',
|
|
||||||
'LeetPikachu',
|
|
||||||
'LeetYos',
|
|
||||||
'LeetE',
|
|
||||||
'LeetMewt1',
|
|
||||||
'LeetPen',
|
|
||||||
'slendermn',
|
|
||||||
'pkmnPAPI0',
|
|
||||||
'pkmnPAPI1',
|
|
||||||
'pkmnPAPI2',
|
|
||||||
'pkmnPAPI3',
|
|
||||||
'pkmnPAPI4',
|
|
||||||
'pokmn_mew',
|
|
||||||
'pkmashhhpet',
|
|
||||||
'pkmbeautfly',
|
|
||||||
'pkmcelebipe',
|
|
||||||
'pkmdarkraip',
|
|
||||||
'pkmeeveepet',
|
|
||||||
'pkmjirachip',
|
|
||||||
'pkmpichupet',
|
|
||||||
'pkmriolupet',
|
|
||||||
'pkmshayminp',
|
|
||||||
'pkmtogepipe',
|
|
||||||
'pkmvictinip',
|
|
||||||
'slenderm1',
|
|
||||||
'LeetEendj16',
|
|
||||||
'pkmnente1',
|
|
||||||
'LeetBa',
|
|
||||||
'babymeisje',
|
|
||||||
'babyBH',
|
|
||||||
'bb_hbx',
|
|
||||||
'LeetUi1',
|
|
||||||
];
|
|
||||||
|
|
||||||
$rendererConfig['pet.types'] = $requiredPetTypes;
|
|
||||||
$this->line(' ✓ Set pet.types to exact required list');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rendererConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateUiConfig(string $protocol, string $host, string $buildPath, string $webroot): array
|
|
||||||
{
|
|
||||||
$httpProtocol = $protocol === 'https' ? 'https' : 'http';
|
|
||||||
$wssProtocol = $protocol === 'https' ? 'wss' : 'ws';
|
|
||||||
$wsHost = 'ws.' . $host;
|
|
||||||
|
|
||||||
$uiConfig = [];
|
|
||||||
|
|
||||||
// Check build path first, then webroot for .example file
|
|
||||||
$examplePath = $buildPath . '/ui-config.example';
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) {
|
|
||||||
$examplePath = $webroot . '/ui-config.example';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to .json version (newer Nitro-V3 format)
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() !== 0) {
|
|
||||||
$jsonPath = $buildPath . '/ui-config.json';
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) {
|
|
||||||
$examplePath = $jsonPath;
|
|
||||||
} else {
|
|
||||||
$jsonPath = $webroot . '/ui-config.json';
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($jsonPath))->exitCode() === 0) {
|
|
||||||
$examplePath = $jsonPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load COMPLETE example file as base
|
|
||||||
if (Process::timeout(5)->run('test -f ' . escapeshellarg($examplePath))->exitCode() === 0) {
|
|
||||||
$content = file_get_contents($examplePath);
|
|
||||||
// Fix invalid escape sequences
|
|
||||||
$content = $this->fixInvalidJsonEscapeSequences($content);
|
|
||||||
$uiConfig = @json_decode($content, true) ?: [];
|
|
||||||
$this->line(' ✓ Loaded ui config from: ' . basename($examplePath) . ' (' . count($uiConfig) . ' keys)');
|
|
||||||
} else {
|
|
||||||
$this->warn(' ⚠ ui-config.example/json not found');
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively replace ALL URLs in the entire config
|
|
||||||
$uiConfig = $this->replaceUrlsRecursively($uiConfig, $httpProtocol, $wssProtocol, $host, $wsHost);
|
|
||||||
|
|
||||||
return $uiConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fixInvalidJsonEscapeSequences(string $content): string
|
|
||||||
{
|
|
||||||
// The example file has literal backslash + any letter in JSON values
|
|
||||||
// which breaks JSON. We need to escape all of these.
|
|
||||||
|
|
||||||
$backslash = chr(92);
|
|
||||||
$letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
|
|
||||||
|
|
||||||
foreach ($letters as $letter) {
|
|
||||||
$content = str_replace($backslash . $letter, $backslash . $backslash . $letter, $content);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function replaceUrlsRecursively(array $config, string $httpProtocol, string $wsProtocol, string $host, string $wsHost): array
|
|
||||||
{
|
|
||||||
foreach ($config as &$value) {
|
|
||||||
if (is_array($value)) {
|
|
||||||
// Handle nested arrays (like navigator.room.models)
|
|
||||||
if ($this->isAssociativeArray($value)) {
|
|
||||||
$value = $this->replaceUrlsRecursively($value, $httpProtocol, $wsProtocol, $host, $wsHost);
|
|
||||||
} else {
|
|
||||||
// Handle arrays of URLs
|
|
||||||
$value = array_map(function ($item) use ($httpProtocol, $wsProtocol, $host, $wsHost) {
|
|
||||||
if (is_string($item)) {
|
|
||||||
return $this->replaceUrl($item, $httpProtocol, $wsProtocol, $host, $wsHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $item;
|
|
||||||
}, $value);
|
|
||||||
}
|
|
||||||
} elseif (is_string($value)) {
|
|
||||||
$value = $this->replaceUrl($value, $httpProtocol, $wsProtocol, $host, $wsHost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function replaceUrl(string $url, string $httpProtocol, string $wsProtocol, string $host, string $wsHost): string
|
|
||||||
{
|
|
||||||
// Replace ws/wss URLs with ws subdomain
|
|
||||||
if (str_starts_with($url, 'ws://') || str_starts_with($url, 'wss://')) {
|
|
||||||
$path = parse_url($url, PHP_URL_PATH) ?? '/';
|
|
||||||
|
|
||||||
return $wsProtocol . '://' . $wsHost . ($path !== '/' ? $path : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace localhost in all URLs
|
|
||||||
$url = preg_replace('#https?://localhost(?::\d+)?#', $httpProtocol . '://' . $host, $url);
|
|
||||||
$url = preg_replace('#wss?://localhost(?::\d+)?#', $wsProtocol . '://' . $wsHost, (string) $url);
|
|
||||||
$url = preg_replace('#localhost(?::\d+)?#', $host, (string) $url);
|
|
||||||
|
|
||||||
// Fix broken escape sequences in URL paths (from invalid JSON in example files)
|
|
||||||
// Pattern: /public\nitro-assets\gamedata or any variation
|
|
||||||
$url = preg_replace('#/public\\n[a-z_-]+\\\\gamedata#i', '/gamedata', (string) $url);
|
|
||||||
$url = preg_replace('#/public\\\\[a-z_-]+\\\\gamedata#i', '/gamedata', (string) $url);
|
|
||||||
$url = preg_replace('#/nitro-assets\\\\gamedata#i', '/gamedata', (string) $url);
|
|
||||||
$url = preg_replace('#/nitro\\\\[a-z_-]+#i', '/gamedata', (string) $url);
|
|
||||||
|
|
||||||
// Fix known asset path patterns
|
|
||||||
$url = str_replace('/public/nitro-assets/gamedata', '/gamedata', $url);
|
|
||||||
$url = str_replace('/swf/gamedata', '/gamedata', $url);
|
|
||||||
|
|
||||||
// Clean up any remaining backslashes in URLs
|
|
||||||
$url = str_replace('\\', '/', $url);
|
|
||||||
|
|
||||||
// Clean up any remaining double slashes (but keep protocol slashes)
|
|
||||||
$url = preg_replace('#([^:])//+#', '$1/', $url);
|
|
||||||
|
|
||||||
return $url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isAssociativeArray(array $arr): bool
|
|
||||||
{
|
|
||||||
if ($arr === []) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_keys($arr) !== range(0, count($arr) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function autoDetectAssetPaths(array $config, string $webroot): array
|
|
||||||
{
|
|
||||||
// Check multiple possible gamedata locations
|
|
||||||
$possiblePaths = [
|
|
||||||
$webroot . '/gamedata',
|
|
||||||
'/var/www/Gamedata',
|
|
||||||
'/var/www/gamedata',
|
|
||||||
];
|
|
||||||
$gamedataPath = array_find($possiblePaths, fn ($path) => is_dir($path));
|
|
||||||
|
|
||||||
if (! $gamedataPath) {
|
|
||||||
return $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
$assetDir = opendir($gamedataPath);
|
|
||||||
$actualDirs = [];
|
|
||||||
while (($entry = readdir($assetDir)) !== false) {
|
|
||||||
if ($entry !== '.' && $entry !== '..' && is_dir($gamedataPath . '/' . $entry)) {
|
|
||||||
$actualDirs[strtolower($entry)] = $entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir($assetDir);
|
|
||||||
|
|
||||||
$pathChecks = [
|
|
||||||
'pet.asset.url' => 'pets',
|
|
||||||
'furni.asset.url' => 'furniture',
|
|
||||||
'avatar.asset.url' => 'clothes',
|
|
||||||
'avatar.asset.effect.url' => 'effect',
|
|
||||||
'generic.asset.url' => 'generic_custom',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($config as $key => &$value) {
|
|
||||||
if (! is_string($value) || ! isset($pathChecks[$key])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$expectedDir = $pathChecks[$key];
|
|
||||||
$lowerExpected = strtolower($expectedDir);
|
|
||||||
$actualName = null;
|
|
||||||
|
|
||||||
// Special case: "figure" is often used instead of "clothes" for avatars
|
|
||||||
if ($lowerExpected === 'clothes' && isset($actualDirs['figure'])) {
|
|
||||||
$actualName = $actualDirs['figure'];
|
|
||||||
} elseif (isset($actualDirs[$lowerExpected])) {
|
|
||||||
$actualName = $actualDirs[$lowerExpected];
|
|
||||||
} else {
|
|
||||||
foreach ($actualDirs as $actualLower => $actual) {
|
|
||||||
if (str_starts_with($actualLower, rtrim($lowerExpected, 's')) ||
|
|
||||||
str_starts_with(rtrim($actualLower, 's'), rtrim($lowerExpected, 's'))) {
|
|
||||||
$actualName = $actual;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($actualName && $actualName !== $expectedDir) {
|
|
||||||
$value = str_replace("/{$expectedDir}/", "/{$actualName}/", $value);
|
|
||||||
$this->line(" 🔍 Auto-detected: {$key} -> /{$actualName}/ (was /{$expectedDir}/)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function syncExampleFromGithub(string $buildPath, string $webroot): void
|
|
||||||
{
|
|
||||||
$this->info('Syncing latest examples from GitHub...');
|
|
||||||
|
|
||||||
$rendererRepo = setting('nitro_github_url', '');
|
|
||||||
$repo = $this->parseRepoFromUrl($rendererRepo);
|
|
||||||
if (! $repo) {
|
|
||||||
$this->warn(' ⚠ No GitHub repo configured, skipping sync');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$branch = setting('nitro_github_branch', 'main');
|
|
||||||
|
|
||||||
$examples = [
|
|
||||||
'renderer-config.example' => 'renderer-config.example',
|
|
||||||
'ui-config.example' => 'ui-config.example',
|
|
||||||
'UITexts.example' => 'UITexts.json',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach (array_keys($examples) as $remoteFile) {
|
|
||||||
$tempFile = '/tmp/' . $remoteFile . '_' . uniqid();
|
|
||||||
$fetched = false;
|
|
||||||
|
|
||||||
// Try multiple paths: root, public/, nitro-client/dist/, Nitro-V3 paths
|
|
||||||
$paths = ['', 'public/', 'nitro-client/dist/', 'dist/', 'src/', 'assets/'];
|
|
||||||
foreach ($paths as $path) {
|
|
||||||
$url = "https://raw.githubusercontent.com/{$repo}/{$branch}/{$path}{$remoteFile}";
|
|
||||||
$result = Process::timeout(15)->run("curl -sL -o {$tempFile} '{$url}'");
|
|
||||||
|
|
||||||
if ($result->successful() && file_exists($tempFile) && filesize($tempFile) > 10) {
|
|
||||||
$content = file_get_contents($tempFile);
|
|
||||||
$data = @json_decode($content, true);
|
|
||||||
|
|
||||||
if (is_array($data) && $data !== []) {
|
|
||||||
// Save to both buildPath and webroot
|
|
||||||
file_put_contents($buildPath . '/' . $remoteFile, $content);
|
|
||||||
file_put_contents($webroot . '/' . $remoteFile, $content);
|
|
||||||
$this->line(" ✓ Synced: {$remoteFile} (" . count($data) . " keys from {$path}{$remoteFile})");
|
|
||||||
$fetched = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@unlink($tempFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $fetched) {
|
|
||||||
$this->line(" - Skipped: {$remoteFile} (not found in any path)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function parseRepoFromUrl(string $url): ?string
|
|
||||||
{
|
|
||||||
if (preg_match('/github\.com\/([^\/]+\/[^\/\?#]+)/', $url, $matches)) {
|
|
||||||
return rtrim($matches[1], '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function compareConfigs(array $generated, string $name, string $webroot): void
|
|
||||||
{
|
|
||||||
$currentPath = $webroot . '/' . $name . '.json';
|
|
||||||
if (! file_exists($currentPath)) {
|
|
||||||
$this->line(" ℹ {$name}: Geen bestaande config — nieuwe generatie");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$current = @json_decode(file_get_contents($currentPath), true);
|
|
||||||
if (! is_array($current)) {
|
|
||||||
$this->warn(" ⚠ {$name}: Bestaande config is ongeldig JSON");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$newKeys = array_diff(array_keys($generated), array_keys($current));
|
|
||||||
$removedKeys = array_diff(array_keys($current), array_keys($generated));
|
|
||||||
$changedKeys = [];
|
|
||||||
|
|
||||||
foreach (array_intersect(array_keys($generated), array_keys($current)) as $key) {
|
|
||||||
if ($generated[$key] !== $current[$key]) {
|
|
||||||
$changedKeys[] = $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($newKeys !== []) {
|
|
||||||
$this->line(" 🆕 {$name}: " . count($newKeys) . ' nieuwe key(s): ' . implode(', ', array_slice($newKeys, 0, 10)));
|
|
||||||
if (count($newKeys) > 10) {
|
|
||||||
$this->line(' ... en ' . (count($newKeys) - 10) . ' meer');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($removedKeys !== []) {
|
|
||||||
$this->line(" 🗑 {$name}: " . count($removedKeys) . ' verwijderde key(s): ' . implode(', ', array_slice($removedKeys, 0, 5)));
|
|
||||||
}
|
|
||||||
if ($changedKeys !== []) {
|
|
||||||
$this->line(" 🔄 {$name}: " . count($changedKeys) . ' gewijzigde key(s): ' . implode(', ', array_slice($changedKeys, 0, 5)));
|
|
||||||
}
|
|
||||||
if ($newKeys === [] && $removedKeys === [] && $changedKeys === []) {
|
|
||||||
$this->line(" ✓ {$name}: Geen wijzigingen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Enums\AlertChannel;
|
|
||||||
use App\Enums\AlertType;
|
|
||||||
use App\Services\AlertService;
|
|
||||||
use App\Services\NitroUpdateService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
class NitroUpdateCommand extends Command
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected $signature = 'nitro:auto
|
|
||||||
{--force : Forceer update ook al is het niet de geplande tijd}
|
|
||||||
{--build-only : Alleen build en deploy uitvoeren}
|
|
||||||
{--full : Volledige reset en reinstall}
|
|
||||||
{--repair : Probeer Nitro te repareren}
|
|
||||||
{--diagnose : Toon diagnose informatie}';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected $description = 'Automatische Nitro client en renderer updates';
|
|
||||||
|
|
||||||
private const string CACHE_KEY_LAST_UPDATE = 'nitro_last_update';
|
|
||||||
|
|
||||||
public function handle(AlertService $alertService, NitroUpdateService $nitroService): int
|
|
||||||
{
|
|
||||||
$this->info('Nitro update check...');
|
|
||||||
|
|
||||||
if ($this->option('diagnose')) {
|
|
||||||
return $this->diagnose($nitroService);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->option('repair')) {
|
|
||||||
return $this->repair($alertService, $nitroService);
|
|
||||||
}
|
|
||||||
|
|
||||||
$isForced = $this->option('force');
|
|
||||||
$buildOnly = $this->option('build-only');
|
|
||||||
$full = $this->option('full');
|
|
||||||
|
|
||||||
if ($buildOnly) {
|
|
||||||
return $this->buildOnly($alertService, $nitroService);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $isForced && ! $this->isScheduledTime()) {
|
|
||||||
$this->line('Niet de geplande tijd, overslaan.');
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$repo = setting('nitro_github_url', 'duckietm/Nitro-V3 (default)');
|
|
||||||
$this->info("GitHub: {$repo}");
|
|
||||||
|
|
||||||
if ($full) {
|
|
||||||
return $this->fullReinstall($alertService, $nitroService);
|
|
||||||
}
|
|
||||||
|
|
||||||
$status = $nitroService->getStatus();
|
|
||||||
|
|
||||||
$this->info('Client: ' . ($status['client_installed'] ? '✅' : '❌'));
|
|
||||||
$this->info('Renderer: ' . ($status['renderer_installed'] ? '✅' : '❌'));
|
|
||||||
$this->info('Build: ' . ($status['build_exists'] ? '✅' : '❌'));
|
|
||||||
$this->info('Deployed: ' . ($status['deployed'] ? '✅' : '❌'));
|
|
||||||
|
|
||||||
if (! $status['client_installed'] || ! $status['renderer_installed']) {
|
|
||||||
$this->warn('Nitro niet volledig geïnstalleerd. Voer --full uit voor volledige installatie.');
|
|
||||||
if ($this->confirm('Wil je nu volledig installeren?')) {
|
|
||||||
return $this->fullReinstall($alertService, $nitroService);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->line('✅ Nitro client is up-to-date.');
|
|
||||||
|
|
||||||
if ($isForced) {
|
|
||||||
$this->warn('Force update aangevraagd...');
|
|
||||||
|
|
||||||
return $this->fullReinstall($alertService, $nitroService);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::put(self::CACHE_KEY_LAST_UPDATE, now()->toIso8601String());
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function diagnose(NitroUpdateService $nitroService): int
|
|
||||||
{
|
|
||||||
$this->info('🔍 Nitro diagnose...');
|
|
||||||
$diagnosis = $nitroService->diagnose();
|
|
||||||
|
|
||||||
$this->newLine();
|
|
||||||
$this->info('=== Controle Resultaten ===');
|
|
||||||
|
|
||||||
$checks = $diagnosis['checks'] ?? [];
|
|
||||||
foreach ($checks as $key => $value) {
|
|
||||||
if (is_bool($value)) {
|
|
||||||
$this->line(($value ? '✅' : '❌') . ' ' . $key);
|
|
||||||
} elseif (is_array($value)) {
|
|
||||||
$this->line($key . ': ' . count($value) . ' items');
|
|
||||||
} else {
|
|
||||||
$this->line($key . ': ' . $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! empty($diagnosis['issues'])) {
|
|
||||||
$this->newLine();
|
|
||||||
$this->error('=== Problemen ===');
|
|
||||||
foreach ($diagnosis['issues'] as $issue) {
|
|
||||||
$this->line('❌ ' . $issue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! empty($diagnosis['recommendations'])) {
|
|
||||||
$this->newLine();
|
|
||||||
$this->info('=== Aanbevelingen ===');
|
|
||||||
foreach ($diagnosis['recommendations'] as $rec) {
|
|
||||||
$this->line('💡 ' . $rec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function repair(AlertService $alertService, NitroUpdateService $nitroService): int
|
|
||||||
{
|
|
||||||
$this->warn('🔧 Nitro repair modus gestart...');
|
|
||||||
$this->info('Dit zal de Nitro installatie controleren en repareren.');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$repairResult = $nitroService->repair();
|
|
||||||
|
|
||||||
if ($repairResult['success']) {
|
|
||||||
$this->info('✅ Repair succesvol!');
|
|
||||||
foreach ($repairResult['actions'] ?? [] as $action) {
|
|
||||||
$this->line(' - ' . $action);
|
|
||||||
}
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_UPDATE,
|
|
||||||
'Nitro client gerepareerd',
|
|
||||||
['actions' => $repairResult['actions'] ?? []],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->error('❌ Repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout'));
|
|
||||||
if (! empty($repairResult['actions'])) {
|
|
||||||
$this->line('Uitgevoerde acties:');
|
|
||||||
foreach ($repairResult['actions'] as $action) {
|
|
||||||
$this->line(' - ' . $action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (! empty($repairResult['errors'])) {
|
|
||||||
$this->line('Fouten:');
|
|
||||||
foreach ($repairResult['errors'] as $error) {
|
|
||||||
$this->line(' ❌ ' . $error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_ERROR,
|
|
||||||
'Nitro repair mislukt: ' . ($repairResult['error'] ?? 'Onbekende fout'),
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('❌ Repair exception: ' . $e->getMessage());
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildOnly(AlertService $alertService, NitroUpdateService $nitroService): int
|
|
||||||
{
|
|
||||||
$this->info('Build en deploy uitvoeren...');
|
|
||||||
|
|
||||||
$buildResult = $nitroService->buildClient();
|
|
||||||
|
|
||||||
if (! $buildResult['success']) {
|
|
||||||
$this->error('Build mislukt: ' . ($buildResult['error'] ?? 'Onbekend'));
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
$nitroService->deployClient();
|
|
||||||
$nitroService->generateConfigs();
|
|
||||||
|
|
||||||
$this->info('Client succesvol gedeployed!');
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_UPDATE,
|
|
||||||
'Nitro client opnieuw gedeployed',
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fullReinstall(AlertService $alertService, NitroUpdateService $nitroService): int
|
|
||||||
{
|
|
||||||
$this->warn('Volledige reinstall wordt uitgevoerd...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = $nitroService->updateNitro();
|
|
||||||
|
|
||||||
if ($result['success']) {
|
|
||||||
$this->info('Nitro succesvol opnieuw geïnstalleerd!');
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::EMULATOR_UPDATE,
|
|
||||||
'Nitro client succesvol geüpdatet en gedeployed',
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->error('Reinstall mislukt: ' . ($result['error'] ?? 'Onbekende fout'));
|
|
||||||
$alertService->send(
|
|
||||||
AlertType::CRITICAL_ERROR,
|
|
||||||
'Nitro Update Mislukt: ' . ($result['error'] ?? 'Onbekende fout'),
|
|
||||||
[],
|
|
||||||
AlertChannel::DISCORD,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('Reinstall exception: ' . $e->getMessage());
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isScheduledTime(): bool
|
|
||||||
{
|
|
||||||
$scheduleTime = setting('nitro_auto_update_schedule', '03:00');
|
|
||||||
$scheduleDays = setting('nitro_auto_update_days', '0,6');
|
|
||||||
$enabled = setting('nitro_auto_update_enabled', false);
|
|
||||||
|
|
||||||
if (! $enabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$now = now();
|
|
||||||
$currentTime = $now->format('H:i');
|
|
||||||
$currentDay = (int) $now->dayOfWeek;
|
|
||||||
|
|
||||||
$allowedDays = array_map(intval(...), explode(',', $scheduleDays));
|
|
||||||
|
|
||||||
if (! in_array($currentDay, $allowedDays)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($currentTime !== $scheduleTime) {
|
|
||||||
$minuteDiff = abs(strtotime($currentTime) - strtotime((string) $scheduleTime)) / 60;
|
|
||||||
if ($minuteDiff > 5) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Services\NitroUpdateService;
|
|
||||||
use App\Services\SettingsService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class SwitchNitroBranch extends Command
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected $signature = 'app:switch-nitro-branch {--branch=main}';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected $description = 'Switch Nitro to a specific branch (runs in background)';
|
|
||||||
|
|
||||||
public function handle(): int
|
|
||||||
{
|
|
||||||
$branch = $this->option('branch') ?? 'main';
|
|
||||||
|
|
||||||
$this->info("🔄 Switching Nitro to branch: {$branch}");
|
|
||||||
|
|
||||||
try {
|
|
||||||
$nitroService = new NitroUpdateService;
|
|
||||||
$result = $nitroService->updateNitro(true);
|
|
||||||
|
|
||||||
if ($result['success'] ?? false) {
|
|
||||||
$this->info("✅ Switched to {$branch} successfully!");
|
|
||||||
$this->info($result['message'] ?? '');
|
|
||||||
Log::info('[NitroSwitch] Success', ['branch' => $branch, 'message' => $result['message'] ?? '']);
|
|
||||||
} else {
|
|
||||||
$this->error('❌ Switch failed: ' . ($result['error'] ?? 'Unknown error'));
|
|
||||||
Log::error('[NitroSwitch] Failed', ['branch' => $branch, 'error' => $result['error'] ?? 'Unknown']);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::forget('website_settings');
|
|
||||||
SettingsService::clearCache();
|
|
||||||
|
|
||||||
return ($result['success'] ?? false) ? 0 : 1;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error('❌ Exception: ' . $e->getMessage());
|
|
||||||
Log::error('[NitroSwitch] Exception', ['branch' => $branch, 'error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Services\EmulatorUpdateService;
|
|
||||||
use App\Services\NitroUpdateService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
class SystemHealthCommand extends Command
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected $signature = 'system:health
|
|
||||||
{--json : Output als JSON}
|
|
||||||
{--details : Toon meer details}';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected $description = 'Controleer systeem gezondheid';
|
|
||||||
|
|
||||||
public function handle(EmulatorUpdateService $emuService, NitroUpdateService $nitroService): int
|
|
||||||
{
|
|
||||||
$json = $this->option('json');
|
|
||||||
$this->option('details');
|
|
||||||
|
|
||||||
$health = $this->performHealthCheck($emuService, $nitroService);
|
|
||||||
|
|
||||||
if ($json) {
|
|
||||||
$this->line(json_encode($health, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
||||||
|
|
||||||
return $health['status'] === 'healthy' ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->displayHealthCheck($health);
|
|
||||||
|
|
||||||
return $health['status'] === 'healthy' ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function performHealthCheck(EmulatorUpdateService $emuService, NitroUpdateService $nitroService): array
|
|
||||||
{
|
|
||||||
$checks = [];
|
|
||||||
$issues = [];
|
|
||||||
$warnings = [];
|
|
||||||
|
|
||||||
$checks['timestamp'] = now()->toIso8601String();
|
|
||||||
|
|
||||||
$checks['php'] = [
|
|
||||||
'version' => PHP_VERSION,
|
|
||||||
'status' => 'ok',
|
|
||||||
];
|
|
||||||
|
|
||||||
$checks['system'] = [
|
|
||||||
'os' => PHP_OS,
|
|
||||||
'user' => posix_getpwuid(posix_geteuid())['name'] ?? 'unknown',
|
|
||||||
];
|
|
||||||
|
|
||||||
$diskFree = @disk_free_space('/');
|
|
||||||
$diskTotal = @disk_total_space('/');
|
|
||||||
$diskPercent = $diskTotal > 0 ? round(($diskFree / $diskTotal) * 100, 1) : 0;
|
|
||||||
$checks['disk'] = [
|
|
||||||
'free' => $this->formatBytes($diskFree),
|
|
||||||
'total' => $this->formatBytes($diskTotal),
|
|
||||||
'percent_free' => $diskPercent,
|
|
||||||
'status' => $diskPercent > 10 ? 'ok' : 'critical',
|
|
||||||
];
|
|
||||||
if ($diskPercent < 10) {
|
|
||||||
$issues[] = 'Disk space critically low: ' . $diskPercent . '% free';
|
|
||||||
} elseif ($diskPercent < 20) {
|
|
||||||
$warnings[] = 'Disk space low: ' . $diskPercent . '% free';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$emuDiagnosis = $emuService->diagnose();
|
|
||||||
$checks['emulator'] = [
|
|
||||||
'configured' => $emuDiagnosis['checks']['is_configured'] ?? false,
|
|
||||||
'jar_exists' => $emuDiagnosis['checks']['jar_exists'] ?? false,
|
|
||||||
'service_running' => $emuDiagnosis['checks']['service_running'] ?? false,
|
|
||||||
'db_connected' => $emuDiagnosis['checks']['emulator_db_connected'] ?? false,
|
|
||||||
'status' => empty($emuDiagnosis['issues']) ? 'ok' : 'issues',
|
|
||||||
'issues' => $emuDiagnosis['issues'] ?? [],
|
|
||||||
];
|
|
||||||
|
|
||||||
if (! empty($emuDiagnosis['issues'])) {
|
|
||||||
foreach ($emuDiagnosis['issues'] as $issue) {
|
|
||||||
$issues[] = 'Emulator: ' . $issue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$checks['emulator'] = [
|
|
||||||
'status' => 'error',
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
];
|
|
||||||
$issues[] = 'Emulator check failed: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$nitroDiagnosis = $nitroService->diagnose();
|
|
||||||
$checks['nitro'] = [
|
|
||||||
'client_installed' => $nitroDiagnosis['checks']['client_installed'] ?? false,
|
|
||||||
'renderer_installed' => $nitroDiagnosis['checks']['renderer_installed'] ?? false,
|
|
||||||
'deployed' => $nitroDiagnosis['checks']['deployed'] ?? false,
|
|
||||||
'status' => empty($nitroDiagnosis['issues']) ? 'ok' : 'issues',
|
|
||||||
'issues' => $nitroDiagnosis['issues'] ?? [],
|
|
||||||
];
|
|
||||||
|
|
||||||
if (! empty($nitroDiagnosis['issues'])) {
|
|
||||||
foreach ($nitroDiagnosis['issues'] as $issue) {
|
|
||||||
$issues[] = 'Nitro: ' . $issue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$checks['nitro'] = [
|
|
||||||
'status' => 'error',
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
];
|
|
||||||
$issues[] = 'Nitro check failed: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$sqlDiagnosis = $emuService->diagnoseSqlUpdates();
|
|
||||||
$checks['sql_updates'] = [
|
|
||||||
'table_exists' => $sqlDiagnosis['table_exists'] ?? false,
|
|
||||||
'applied' => $sqlDiagnosis['applied_count'] ?? 0,
|
|
||||||
'pending' => $sqlDiagnosis['pending_count'] ?? 0,
|
|
||||||
'status' => ($sqlDiagnosis['pending_count'] ?? 0) > 0 ? 'pending' : 'ok',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (($sqlDiagnosis['pending_count'] ?? 0) > 0) {
|
|
||||||
$warnings[] = $sqlDiagnosis['pending_count'] . ' SQL updates pending';
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$checks['sql_updates'] = [
|
|
||||||
'status' => 'error',
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$webserverCheck = Process::timeout(5)->run('which nginx || which apache2 || which httpd');
|
|
||||||
$checks['webserver'] = [
|
|
||||||
'installed' => $webserverCheck->successful(),
|
|
||||||
'status' => $webserverCheck->successful() ? 'ok' : 'unknown',
|
|
||||||
];
|
|
||||||
|
|
||||||
$mysqlCheck = Process::timeout(5)->run('which mysql || which mariadb');
|
|
||||||
$checks['database'] = [
|
|
||||||
'client_installed' => $mysqlCheck->successful(),
|
|
||||||
'status' => $mysqlCheck->successful() ? 'ok' : 'unknown',
|
|
||||||
];
|
|
||||||
|
|
||||||
$nodeCheck = Process::timeout(5)->run('which node && which yarn');
|
|
||||||
$checks['node'] = [
|
|
||||||
'installed' => $nodeCheck->successful(),
|
|
||||||
'status' => $nodeCheck->successful() ? 'ok' : 'missing',
|
|
||||||
];
|
|
||||||
if (! $nodeCheck->successful()) {
|
|
||||||
$warnings[] = 'Node.js/Yarn niet geïnstalleerd';
|
|
||||||
}
|
|
||||||
|
|
||||||
$status = $issues === [] ? 'healthy' : ($warnings === [] ? 'warning' : 'degraded');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'status' => $status,
|
|
||||||
'checks' => $checks,
|
|
||||||
'issues' => $issues,
|
|
||||||
'warnings' => $warnings,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function displayHealthCheck(array $health): void
|
|
||||||
{
|
|
||||||
$status = $health['status'];
|
|
||||||
|
|
||||||
$statusIcon = match ($status) {
|
|
||||||
'healthy' => '✅',
|
|
||||||
'warning' => '⚠️',
|
|
||||||
'degraded' => '❌',
|
|
||||||
default => '❓',
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->info("{$statusIcon} Systeem Gezondheid: " . strtoupper($status));
|
|
||||||
$this->line('═══════════════════════════════════════════════');
|
|
||||||
|
|
||||||
$checks = $health['checks'] ?? [];
|
|
||||||
|
|
||||||
$this->line('📦 Systeem:');
|
|
||||||
$this->line(' PHP: ' . ($checks['php']['version'] ?? '?'));
|
|
||||||
$this->line(' User: ' . ($checks['system']['user'] ?? '?'));
|
|
||||||
|
|
||||||
$diskStatus = $checks['disk']['status'] ?? '?';
|
|
||||||
$diskIcon = $diskStatus === 'ok' ? '✅' : '❌';
|
|
||||||
$this->line(" {$diskIcon} Disk: " . ($checks['disk']['percent_free'] ?? '?') . '% vrij');
|
|
||||||
|
|
||||||
$emuStatus = $checks['emulator']['status'] ?? '?';
|
|
||||||
$emuIcon = $emuStatus === 'ok' ? '✅' : '⚠️';
|
|
||||||
$this->line(" {$emuIcon} Emulator: " . ucfirst($emuStatus));
|
|
||||||
|
|
||||||
$nitroStatus = $checks['nitro']['status'] ?? '?';
|
|
||||||
$nitroIcon = $nitroStatus === 'ok' ? '✅' : '⚠️';
|
|
||||||
$this->line(" {$nitroIcon} Nitro: " . ucfirst($nitroStatus));
|
|
||||||
|
|
||||||
$sqlStatus = $checks['sql_updates']['status'] ?? '?';
|
|
||||||
$sqlIcon = $sqlStatus === 'ok' ? '✅' : '⚠️';
|
|
||||||
$this->line(" {$sqlIcon} SQL Updates: " . ucfirst($sqlStatus) . ' (' . ($checks['sql_updates']['applied'] ?? 0) . ' toegepast)');
|
|
||||||
|
|
||||||
$nodeStatus = $checks['node']['status'] ?? '?';
|
|
||||||
$nodeIcon = $nodeStatus === 'ok' ? '✅' : '❌';
|
|
||||||
$this->line(" {$nodeIcon} Node.js: " . ucfirst($nodeStatus));
|
|
||||||
|
|
||||||
if (! empty($health['issues'])) {
|
|
||||||
$this->line('');
|
|
||||||
$this->error('❌ Problemen:');
|
|
||||||
foreach ($health['issues'] as $issue) {
|
|
||||||
$this->line(' - ' . $issue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! empty($health['warnings'])) {
|
|
||||||
$this->line('');
|
|
||||||
$this->warn('⚠️ Waarschuwingen:');
|
|
||||||
foreach ($health['warnings'] as $warning) {
|
|
||||||
$this->line(' - ' . $warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->line('═══════════════════════════════════════════════');
|
|
||||||
|
|
||||||
if ($status === 'healthy') {
|
|
||||||
$this->info('✅ Alles werkt correct!');
|
|
||||||
} else {
|
|
||||||
$this->warn('Run "php artisan system:repair" om problemen op te lossen');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function formatBytes(?float $bytes): string
|
|
||||||
{
|
|
||||||
if ($bytes === null) {
|
|
||||||
return 'N/A';
|
|
||||||
}
|
|
||||||
|
|
||||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
$i = 0;
|
|
||||||
|
|
||||||
while ($bytes >= 1024 && $i < count($units) - 1) {
|
|
||||||
$bytes /= 1024;
|
|
||||||
$i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return round($bytes, 2) . ' ' . $units[$i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Services\AlertService;
|
|
||||||
use App\Services\EmulatorUpdateService;
|
|
||||||
use App\Services\NitroUpdateService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
class SystemRepairCommand extends Command
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected $signature = 'system:repair
|
|
||||||
{--emu : Alleen emulator repareren}
|
|
||||||
{--nitro : Alleen Nitro repareren}
|
|
||||||
{--check : Alleen controleren, niet repareren}
|
|
||||||
{--force : Forceer reparatie ook al is alles OK}
|
|
||||||
{--full : Volledige reset en reinstall}
|
|
||||||
{--nuke : Alles verwijderen en opnieuw installeren (gevaarlijk!)}';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected $description = 'Automatische systeem reparatie voor emulator en Nitro';
|
|
||||||
|
|
||||||
private const string CACHE_KEY_LAST_REPAIR = 'system_last_repair';
|
|
||||||
|
|
||||||
public function handle(AlertService $alertService, EmulatorUpdateService $emuService, NitroUpdateService $nitroService): int
|
|
||||||
{
|
|
||||||
$this->info('🔧 System Repair Service gestart...');
|
|
||||||
$this->line('═══════════════════════════════════════════════');
|
|
||||||
|
|
||||||
$checkOnly = $this->option('check');
|
|
||||||
$force = $this->option('force');
|
|
||||||
$full = $this->option('full');
|
|
||||||
$emuOnly = $this->option('emu');
|
|
||||||
$nitroOnly = $this->option('nitro');
|
|
||||||
|
|
||||||
$results = [
|
|
||||||
'emulator' => null,
|
|
||||||
'nitro' => null,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (! $nitroOnly) {
|
|
||||||
$results['emulator'] = $this->repairEmulator($emuService, $checkOnly, $force, $full);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $emuOnly) {
|
|
||||||
$results['nitro'] = $this->repairNitro($nitroService, $checkOnly, $force, $full);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::put(self::CACHE_KEY_LAST_REPAIR, now()->toIso8601String());
|
|
||||||
|
|
||||||
$emuOk = $results['emulator'] === null || ($results['emulator']['success'] ?? false);
|
|
||||||
$nitroOk = $results['nitro'] === null || ($results['nitro']['success'] ?? false);
|
|
||||||
|
|
||||||
$this->line('═══════════════════════════════════════════════');
|
|
||||||
if ($emuOk && $nitroOk) {
|
|
||||||
$this->info('✅ Alle systemen OK');
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Command::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function ensureBaseDirectories(): void
|
|
||||||
{
|
|
||||||
$basePaths = [
|
|
||||||
'/var/www',
|
|
||||||
'/var/www/atomcms',
|
|
||||||
storage_path('app'),
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($basePaths as $path) {
|
|
||||||
if (! is_dir($path)) {
|
|
||||||
@mkdir($path, 0755, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process::timeout(5)->run('chown -R www-data:www-data /var/www/atomcms 2>/dev/null || true');
|
|
||||||
Process::timeout(5)->run('chmod -R 755 /var/www/atomcms 2>/dev/null || true');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function repairEmulator(EmulatorUpdateService $service, bool $checkOnly, bool $force, bool $full): array
|
|
||||||
{
|
|
||||||
$this->info('Emulator controleren...');
|
|
||||||
|
|
||||||
$this->ensureBaseDirectories();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$diagnosis = $service->diagnose();
|
|
||||||
|
|
||||||
if (empty($diagnosis['issues']) && ! $force && ! $full) {
|
|
||||||
$this->line(' ✅ Emulator is OK');
|
|
||||||
|
|
||||||
return ['success' => true, 'status' => 'ok'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($checkOnly) {
|
|
||||||
$this->warn(' ⚠️ Emulator problemen gevonden:');
|
|
||||||
foreach ($diagnosis['issues'] ?? [] as $issue) {
|
|
||||||
$this->line(' - ' . $issue);
|
|
||||||
}
|
|
||||||
foreach ($diagnosis['recommendations'] ?? [] as $rec) {
|
|
||||||
$this->line(' 💡 ' . $rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['success' => false, 'status' => 'issues_found', 'issues' => $diagnosis['issues']];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->warn(' 🔧 Emulator wordt gerepareerd...');
|
|
||||||
|
|
||||||
if ($full) {
|
|
||||||
$this->line(' 📦 Volledige reset...');
|
|
||||||
$repairResult = $service->repairEmulator();
|
|
||||||
} else {
|
|
||||||
$repairResult = $service->repairEmulator();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($repairResult['success']) {
|
|
||||||
$this->info(' ✅ Emulator gerepareerd!');
|
|
||||||
foreach ($repairResult['actions'] ?? [] as $action) {
|
|
||||||
$this->line(' - ' . $action);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['success' => true, 'status' => 'repaired', 'actions' => $repairResult['actions']];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->error(' ❌ Emulator repair mislukt: ' . ($repairResult['error'] ?? 'Onbekend'));
|
|
||||||
if (! empty($repairResult['actions'])) {
|
|
||||||
$this->line(' Uitgevoerde acties:');
|
|
||||||
foreach ($repairResult['actions'] as $action) {
|
|
||||||
$this->line(' - ' . $action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['success' => false, 'status' => 'failed', 'error' => $repairResult['error']];
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error(' ❌ Emulator exception: ' . $e->getMessage());
|
|
||||||
Log::error('[SystemRepair] Emulator exception', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return ['success' => false, 'status' => 'exception', 'error' => $e->getMessage()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function repairNitro(NitroUpdateService $service, bool $checkOnly, bool $force, bool $full): array
|
|
||||||
{
|
|
||||||
$this->info('Nitro controleren...');
|
|
||||||
|
|
||||||
$this->ensureBaseDirectories();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$diagnosis = $service->diagnose();
|
|
||||||
|
|
||||||
if (empty($diagnosis['issues']) && ! $force && ! $full) {
|
|
||||||
$this->line(' ✅ Nitro is OK');
|
|
||||||
|
|
||||||
return ['success' => true, 'status' => 'ok'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($checkOnly) {
|
|
||||||
$this->warn(' ⚠️ Nitro problemen gevonden:');
|
|
||||||
foreach ($diagnosis['issues'] ?? [] as $issue) {
|
|
||||||
$this->line(' - ' . $issue);
|
|
||||||
}
|
|
||||||
foreach ($diagnosis['recommendations'] ?? [] as $rec) {
|
|
||||||
$this->line(' 💡 ' . $rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['success' => false, 'status' => 'issues_found', 'issues' => $diagnosis['issues']];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->warn(' 🔧 Nitro wordt gerepareerd...');
|
|
||||||
|
|
||||||
if ($full) {
|
|
||||||
$this->line(' 📦 Volledige reset...');
|
|
||||||
$repairResult = $service->updateNitro();
|
|
||||||
} else {
|
|
||||||
$repairResult = $service->repair();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($repairResult['success']) {
|
|
||||||
$this->info(' ✅ Nitro gerepareerd!');
|
|
||||||
foreach ($repairResult['actions'] ?? [] as $action) {
|
|
||||||
$this->line(' - ' . $action);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['success' => true, 'status' => 'repaired', 'actions' => $repairResult['actions']];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->error(' ❌ Nitro repair mislukt: ' . ($repairResult['error'] ?? 'Onbekend'));
|
|
||||||
if (! empty($repairResult['actions'])) {
|
|
||||||
$this->line(' Uitgevoerde acties:');
|
|
||||||
foreach ($repairResult['actions'] as $action) {
|
|
||||||
$this->line(' - ' . $action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['success' => false, 'status' => 'failed', 'error' => $repairResult['error']];
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->error(' ❌ Nitro exception: ' . $e->getMessage());
|
|
||||||
Log::error('[SystemRepair] Nitro exception', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return ['success' => false, 'status' => 'exception', 'error' => $e->getMessage()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,16 +4,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Console\Commands\AutoUpdateCommand;
|
|
||||||
use App\Console\Commands\DDoSDetectionCommand;
|
use App\Console\Commands\DDoSDetectionCommand;
|
||||||
use App\Console\Commands\EmulatorMonitorCommand;
|
use App\Console\Commands\EmulatorMonitorCommand;
|
||||||
use App\Console\Commands\EmulatorUpdateCommand;
|
|
||||||
use App\Console\Commands\FixCodeCommand;
|
use App\Console\Commands\FixCodeCommand;
|
||||||
use App\Console\Commands\GenerateNitroConfigs;
|
|
||||||
use App\Console\Commands\NitroUpdateCommand;
|
|
||||||
use App\Console\Commands\SystemCheckCommand;
|
use App\Console\Commands\SystemCheckCommand;
|
||||||
use App\Console\Commands\SystemHealthCommand;
|
|
||||||
use App\Console\Commands\SystemRepairCommand;
|
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
@@ -28,9 +22,6 @@ class Kernel extends ConsoleKernel
|
|||||||
$schedule->command('maintenance:check-scheduled')->everyMinute()->withoutOverlapping();
|
$schedule->command('maintenance:check-scheduled')->everyMinute()->withoutOverlapping();
|
||||||
$schedule->command('monitor:emulator')->everyMinute()->withoutOverlapping();
|
$schedule->command('monitor:emulator')->everyMinute()->withoutOverlapping();
|
||||||
$schedule->command('monitor:ddos')->everyFiveMinutes()->withoutOverlapping();
|
$schedule->command('monitor:ddos')->everyFiveMinutes()->withoutOverlapping();
|
||||||
$schedule->command('update:auto')->everyMinute()->withoutOverlapping();
|
|
||||||
$schedule->command('nitro:auto')->everyMinute()->withoutOverlapping();
|
|
||||||
$schedule->command('system:repair')->everyTenMinutes()->withoutOverlapping();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[\Override]
|
#[\Override]
|
||||||
@@ -42,12 +33,6 @@ class Kernel extends ConsoleKernel
|
|||||||
$this->commands[] = FixCodeCommand::class;
|
$this->commands[] = FixCodeCommand::class;
|
||||||
$this->commands[] = EmulatorMonitorCommand::class;
|
$this->commands[] = EmulatorMonitorCommand::class;
|
||||||
$this->commands[] = DDoSDetectionCommand::class;
|
$this->commands[] = DDoSDetectionCommand::class;
|
||||||
$this->commands[] = EmulatorUpdateCommand::class;
|
|
||||||
$this->commands[] = AutoUpdateCommand::class;
|
|
||||||
$this->commands[] = NitroUpdateCommand::class;
|
|
||||||
$this->commands[] = SystemRepairCommand::class;
|
|
||||||
$this->commands[] = SystemHealthCommand::class;
|
|
||||||
$this->commands[] = GenerateNitroConfigs::class;
|
|
||||||
|
|
||||||
require base_path('routes/console.php');
|
require base_path('routes/console.php');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Pages\Monitoring;
|
namespace App\Filament\Pages\Monitoring;
|
||||||
|
|
||||||
use App\Actions\Commandocentrum\EmulatorControlAction;
|
use App\Actions\Commandocentrum\EmulatorControlAction;
|
||||||
use App\Actions\Commandocentrum\NitroControlAction;
|
|
||||||
use App\Enums\AlertSeverity;
|
use App\Enums\AlertSeverity;
|
||||||
use App\Models\Miscellaneous\WebsitePermission;
|
use App\Models\Miscellaneous\WebsitePermission;
|
||||||
use App\Models\StaffActivity;
|
use App\Models\StaffActivity;
|
||||||
@@ -16,7 +15,6 @@ use App\Services\Diagnostics\DiagnosticRunner;
|
|||||||
use App\Services\GitHubService;
|
use App\Services\GitHubService;
|
||||||
use App\Services\RconService;
|
use App\Services\RconService;
|
||||||
use App\Services\SettingsService;
|
use App\Services\SettingsService;
|
||||||
use App\Services\UpdateHistoryService;
|
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
@@ -106,21 +104,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
'emulator_database_username' => $this->getSetting('emulator_database_username', ''),
|
'emulator_database_username' => $this->getSetting('emulator_database_username', ''),
|
||||||
'emulator_database_password' => $this->getSetting('emulator_database_password', ''),
|
'emulator_database_password' => $this->getSetting('emulator_database_password', ''),
|
||||||
'emulator_version' => $this->getSetting('emulator_version', 'Onbekend'),
|
'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' => '',
|
'hotel_alert_message' => '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -233,92 +216,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
->content(fn () => $this->renderEmulatorInfoView()),
|
->content(fn () => $this->renderEmulatorInfoView()),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Section::make(__('commandocentrum.emulator_updates'))
|
|
||||||
->description(__('commandocentrum.emulator_updates_desc'))
|
|
||||||
->icon('heroicon-o-arrow-down-circle')
|
|
||||||
->afterHeader([
|
|
||||||
Action::make('check_updates')
|
|
||||||
->label(__('commandocentrum.check_updates'))
|
|
||||||
->color('info')
|
|
||||||
->action('checkEmulatorUpdates'),
|
|
||||||
Action::make('build_emulator')
|
|
||||||
->label('🔨 ' . __('commandocentrum.build'))
|
|
||||||
->color('success')
|
|
||||||
->action('buildEmulator'),
|
|
||||||
Action::make('run_sql')
|
|
||||||
->label(__('commandocentrum.sql_updates'))
|
|
||||||
->color('purple')
|
|
||||||
->action('runSqlUpdates'),
|
|
||||||
Action::make('save_emulator')
|
|
||||||
->label(__('commandocentrum.save'))
|
|
||||||
->color('primary')
|
|
||||||
->action('saveEmulator'),
|
|
||||||
])
|
|
||||||
->schema([
|
|
||||||
Placeholder::make('emulator_settings')
|
|
||||||
->label('')
|
|
||||||
->content(fn () => $this->renderEmulatorSettingsView()),
|
|
||||||
]),
|
|
||||||
|
|
||||||
Section::make(__('commandocentrum.emulator_backups'))
|
|
||||||
->description(__('commandocentrum.emulator_backups_desc'))
|
|
||||||
->icon('heroicon-s-archive-box')
|
|
||||||
->schema([
|
|
||||||
Placeholder::make('backups_list')
|
|
||||||
->label('')
|
|
||||||
->content(fn () => $this->renderBackupsListView()),
|
|
||||||
]),
|
|
||||||
|
|
||||||
Section::make(__('commandocentrum.nitro_client'))
|
|
||||||
->description(__('commandocentrum.nitro_client_desc'))
|
|
||||||
->icon('heroicon-o-cloud-arrow-down')
|
|
||||||
->afterHeader([
|
|
||||||
Action::make('detect_paths')
|
|
||||||
->label('🔍 ' . __('commandocentrum.auto_detect'))
|
|
||||||
->color('success')
|
|
||||||
->action('detectAndSavePaths'),
|
|
||||||
Action::make('check_nitro')
|
|
||||||
->label(__('commandocentrum.check'))
|
|
||||||
->color('info')
|
|
||||||
->action('checkNitroUpdates'),
|
|
||||||
Action::make('build_nitro')
|
|
||||||
->label(__('commandocentrum.build'))
|
|
||||||
->color('pink')
|
|
||||||
->action('buildNitro'),
|
|
||||||
Action::make('generate_configs')
|
|
||||||
->label(__('commandocentrum.generate_configs'))
|
|
||||||
->color('indigo')
|
|
||||||
->action('generateNitroConfigs'),
|
|
||||||
Action::make('save_nitro')
|
|
||||||
->label(__('commandocentrum.save'))
|
|
||||||
->color('primary')
|
|
||||||
->action('saveNitro'),
|
|
||||||
])
|
|
||||||
->schema([
|
|
||||||
Placeholder::make('nitro_settings')
|
|
||||||
->label('')
|
|
||||||
->content(fn () => $this->renderNitroSettingsView()),
|
|
||||||
]),
|
|
||||||
|
|
||||||
Section::make(__('commandocentrum.auto_updates'))
|
|
||||||
->description(__('commandocentrum.auto_updates_desc'))
|
|
||||||
->icon('heroicon-o-clock')
|
|
||||||
->columns(2)
|
|
||||||
->afterHeader([
|
|
||||||
Action::make('save_auto')
|
|
||||||
->label(__('commandocentrum.save'))
|
|
||||||
->color('primary')
|
|
||||||
->action('saveAutoUpdate'),
|
|
||||||
])
|
|
||||||
->schema([
|
|
||||||
Toggle::make('auto_update_enabled')
|
|
||||||
->label(__('commandocentrum.enable_auto_updates')),
|
|
||||||
TextInput::make('auto_update_schedule')
|
|
||||||
->label(__('commandocentrum.schedule')),
|
|
||||||
TextInput::make('auto_update_days')
|
|
||||||
->label(__('commandocentrum.days')),
|
|
||||||
]),
|
|
||||||
|
|
||||||
Section::make(__('commandocentrum.clothing_sync'))
|
Section::make(__('commandocentrum.clothing_sync'))
|
||||||
->description(__('commandocentrum.clothing_sync_desc'))
|
->description(__('commandocentrum.clothing_sync_desc'))
|
||||||
->icon('heroicon-o-user')
|
->icon('heroicon-o-user')
|
||||||
@@ -367,15 +264,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
->helperText(__('commandocentrum.discord_ranks_helper')),
|
->helperText(__('commandocentrum.discord_ranks_helper')),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Section::make(__('commandocentrum.update_history'))
|
|
||||||
->description(__('commandocentrum.update_history_desc'))
|
|
||||||
->icon('heroicon-o-clock')
|
|
||||||
->schema([
|
|
||||||
Placeholder::make('history')
|
|
||||||
->label('')
|
|
||||||
->content(fn () => $this->renderUpdateHistoryView()),
|
|
||||||
]),
|
|
||||||
|
|
||||||
Section::make(__('commandocentrum.social_login'))
|
Section::make(__('commandocentrum.social_login'))
|
||||||
->description(__('commandocentrum.social_login_desc'))
|
->description(__('commandocentrum.social_login_desc'))
|
||||||
->icon('heroicon-o-user-circle')
|
->icon('heroicon-o-user-circle')
|
||||||
@@ -491,100 +379,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderEmulatorSettingsView(): View
|
|
||||||
{
|
|
||||||
return view('filament.components.commandocentrum.emulator-settings', [
|
|
||||||
'emulatorBranchesHtml' => $this->getEmulatorBranchesHtml(),
|
|
||||||
'emulatorStatusHtml' => $this->renderEmulatorStatusView()->render(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderEmulatorStatusView(): View
|
|
||||||
{
|
|
||||||
$serviceName = $this->getSetting('emulator_service_name', 'arcturus');
|
|
||||||
$jarPath = $this->getSetting('emulator_jar_path', '/var/www/Emulator');
|
|
||||||
$sourcePath = $this->getSetting('emulator_source_path', '/var/www/emulator-source');
|
|
||||||
$githubUrl = $this->getSetting('emulator_github_url', '');
|
|
||||||
$branch = $this->getSetting('emulator_github_branch', 'main');
|
|
||||||
|
|
||||||
$jarExists = $this->fileExists($jarPath);
|
|
||||||
$sourceExists = $this->fileExists($sourcePath);
|
|
||||||
$sourceCommit = $this->getGitCommit($sourcePath);
|
|
||||||
$remoteVersion = $githubUrl !== '' && $githubUrl !== '0' ? $this->getRemoteCommit($githubUrl, $branch) : 'N/A';
|
|
||||||
|
|
||||||
$canBuild = false;
|
|
||||||
$checkDirs = [
|
|
||||||
$sourcePath,
|
|
||||||
$sourcePath . '/Emulator',
|
|
||||||
$sourcePath . '/Emulator/Emulator',
|
|
||||||
$sourcePath . '/emulator',
|
|
||||||
$sourcePath . '/emulator/emulator',
|
|
||||||
];
|
|
||||||
foreach ($checkDirs as $dir) {
|
|
||||||
$check = $this->runCommand('test -f ' . escapeshellarg($dir . '/pom.xml') . ' && echo yes');
|
|
||||||
if ($check && trim($check) === 'yes') {
|
|
||||||
$canBuild = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('filament.components.commandocentrum.emulator-status', [
|
|
||||||
'emulatorOnline' => $this->getEmulatorStatusText() === 'Online',
|
|
||||||
'jarExists' => $jarExists,
|
|
||||||
'serviceName' => $serviceName,
|
|
||||||
'sourceCommit' => $sourceCommit,
|
|
||||||
'remoteVersion' => $remoteVersion,
|
|
||||||
'canBuild' => $canBuild,
|
|
||||||
'jarPath' => $jarPath,
|
|
||||||
'sourcePath' => $sourcePath,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderNitroSettingsView(): View
|
|
||||||
{
|
|
||||||
return view('filament.components.commandocentrum.nitro-settings', [
|
|
||||||
'nitroBranchesHtml' => $this->getNitroBranchesHtml(),
|
|
||||||
'nitroStatusHtml' => $this->renderNitroStatusView()->render(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderNitroStatusView(): View
|
|
||||||
{
|
|
||||||
$clientPath = $this->getSetting('nitro_client_path', '/var/www/nitro-client');
|
|
||||||
$rendererPath = $this->getSetting('nitro_renderer_path', '/var/www/nitro-renderer');
|
|
||||||
$webroot = $this->getSetting('nitro_webroot', '/var/www/Client');
|
|
||||||
$clientGithubUrl = $this->getSetting('nitro_github_url', '');
|
|
||||||
$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->getRemoteCommit($clientGithubUrl, $this->getSetting('nitro_github_branch', 'main')) : 'N/A';
|
|
||||||
$rendererRemote = $rendererGithubUrl !== '' && $rendererGithubUrl !== '0' ? $this->getRemoteCommit($rendererGithubUrl, $this->getSetting('nitro_renderer_github_branch', 'main')) : 'N/A';
|
|
||||||
|
|
||||||
return view('filament.components.commandocentrum.nitro-status', [
|
|
||||||
'clientExists' => $this->checkPathExists($clientPath),
|
|
||||||
'rendererExists' => $this->checkPathExists($rendererPath),
|
|
||||||
'webrootExists' => $this->checkPathExists($webroot),
|
|
||||||
'clientCommit' => $clientCommit,
|
|
||||||
'rendererCommit' => $rendererCommit,
|
|
||||||
'clientRemote' => $clientRemote,
|
|
||||||
'rendererRemote' => $rendererRemote,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderBackupsListView(): View
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$backups = app(EmulatorControlAction::class)->getBackups();
|
|
||||||
} catch (Exception) {
|
|
||||||
$backups = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('filament.components.commandocentrum.backups-list', [
|
|
||||||
'backups' => $backups,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderClothingStatusView(): View
|
private function renderClothingStatusView(): View
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -614,19 +408,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderUpdateHistoryView(): View
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$history = app(UpdateHistoryService::class)->getRecent(10);
|
|
||||||
} catch (Exception) {
|
|
||||||
$history = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('filament.components.commandocentrum.update-history', [
|
|
||||||
'history' => $history,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getSetting(string $key, string $default = ''): string
|
private function getSetting(string $key, string $default = ''): string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -878,33 +659,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkEmulatorUpdates(): void
|
|
||||||
{
|
|
||||||
$result = app(EmulatorControlAction::class)->update();
|
|
||||||
$this->notify($result['success'] ?? false ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? $result['error'] ?? __('commandocentrum.unknown'), ($result['success'] ?? false) ? 'success' : 'danger');
|
|
||||||
Cache::forget('all_updates_check');
|
|
||||||
$this->fillForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildEmulator(): void
|
|
||||||
{
|
|
||||||
$result = app(EmulatorControlAction::class)->build();
|
|
||||||
$this->notify($result['success'] ?? false ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? $result['error'] ?? __('commandocentrum.unknown'), ($result['success'] ?? false) ? 'success' : 'danger');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function runSqlUpdates(): void
|
|
||||||
{
|
|
||||||
$result = app(EmulatorControlAction::class)->runSqlUpdates();
|
|
||||||
$this->notify($result['success'] ?? false ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? __('commandocentrum.unknown'), ($result['success'] ?? false) ? 'success' : 'danger');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function restoreBackup(string $backupName): void
|
|
||||||
{
|
|
||||||
$result = app(EmulatorControlAction::class)->restoreBackup($backupName);
|
|
||||||
$this->notify($result['success'] ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'] ?? $result['error'] ?? __('commandocentrum.unknown'), $result['success'] ? 'success' : 'danger');
|
|
||||||
$this->fillForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveEmulator(): void
|
public function saveEmulator(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -924,38 +678,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkNitroUpdates(): void
|
|
||||||
{
|
|
||||||
$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');
|
|
||||||
|
|
||||||
$result = app(NitroControlAction::class)->pullUpdates($clientPath, $rendererPath, $branch);
|
|
||||||
$this->notify(__('commandocentrum.success'), $result['message'], 'success');
|
|
||||||
$this->fillForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildNitro(): void
|
|
||||||
{
|
|
||||||
$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');
|
|
||||||
|
|
||||||
$result = app(NitroControlAction::class)->build($clientPath, $rendererPath, $branch);
|
|
||||||
$this->notify($result['success'] ? __('commandocentrum.success') : __('commandocentrum.warning'), $result['message'], $result['success'] ? 'success' : 'warning');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generateNitroConfigs(): void
|
|
||||||
{
|
|
||||||
$siteUrl = $this->getSetting('nitro_site_url', $this->getCurrentSiteUrl());
|
|
||||||
$webroot = $this->getSetting('nitro_webroot', '/var/www/Client');
|
|
||||||
$gamedataPath = $this->getSetting('gamedata_path', '/var/www/Gamedata');
|
|
||||||
|
|
||||||
$result = app(NitroControlAction::class)->generateConfigs($siteUrl, $webroot, $gamedataPath);
|
|
||||||
$this->notify($result['success'] ? __('commandocentrum.success') : __('commandocentrum.error'), $result['message'], $result['success'] ? 'success' : 'danger');
|
|
||||||
$this->fillForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function syncClothing(): void
|
public function syncClothing(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -972,58 +694,6 @@ final class Commandocentrum extends Page implements HasForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(__('commandocentrum.success'), __('commandocentrum.paths_detected'), 'success');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->notify(__('commandocentrum.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(__('commandocentrum.success'), __('commandocentrum.nitro_settings_saved'), 'success');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->notify(__('commandocentrum.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(__('commandocentrum.success'), __('commandocentrum.auto_update_saved'), 'success');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->notify(__('commandocentrum.error'), $e->getMessage(), 'danger');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveAlerts(): void
|
public function saveAlerts(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||||||
namespace App\Filament\Resources\Miscellaneous\AlertLogResource;
|
namespace App\Filament\Resources\Miscellaneous\AlertLogResource;
|
||||||
|
|
||||||
use App\Models\Miscellaneous\AlertLog;
|
use App\Models\Miscellaneous\AlertLog;
|
||||||
use App\Services\EmulatorUpdateService;
|
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
@@ -136,14 +135,12 @@ class AlertLogResource extends Resource
|
|||||||
->icon('heroicon-o-trash')
|
->icon('heroicon-o-trash')
|
||||||
->color('gray')
|
->color('gray')
|
||||||
->action(function () {
|
->action(function () {
|
||||||
$updateService = new EmulatorUpdateService;
|
|
||||||
$result = $updateService->clearAllLogs();
|
|
||||||
Cache::flush();
|
Cache::flush();
|
||||||
AlertLog::truncate();
|
AlertLog::truncate();
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->success()
|
->success()
|
||||||
->title('🗑️ Alle Logs Geleegd!')
|
->title('🗑️ Alle Logs Geleegd!')
|
||||||
->body($result['message'])
|
->body('Alle logs zijn gewist.')
|
||||||
->send();
|
->send();
|
||||||
})
|
})
|
||||||
->requiresConfirmation(),
|
->requiresConfirmation(),
|
||||||
|
|||||||
@@ -1,343 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Filament\Widgets;
|
|
||||||
|
|
||||||
use App\Services\EmulatorUpdateService;
|
|
||||||
use App\Services\NitroUpdateService;
|
|
||||||
use App\Services\RconService;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
use Filament\Widgets\Widget;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class UpdateCheckerWidget extends Widget
|
|
||||||
{
|
|
||||||
#[\Override]
|
|
||||||
protected string $view = 'filament.widgets.update-checker';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected int|string|array $columnSpan = 'full';
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
protected static ?int $sort = 0;
|
|
||||||
|
|
||||||
public ?string $emulatorVersion = null;
|
|
||||||
|
|
||||||
public ?string $latestEmulatorVersion = null;
|
|
||||||
|
|
||||||
public bool $emulatorUpdate = false;
|
|
||||||
|
|
||||||
public ?string $nitroVersion = null;
|
|
||||||
|
|
||||||
public ?string $latestNitroVersion = null;
|
|
||||||
|
|
||||||
public bool $nitroUpdate = false;
|
|
||||||
|
|
||||||
public int $onlineUsers = 0;
|
|
||||||
|
|
||||||
public string $dbSize = '0 MB';
|
|
||||||
|
|
||||||
public bool $hasAnyUpdate = false;
|
|
||||||
|
|
||||||
public int $sqlApplied = 0;
|
|
||||||
|
|
||||||
public int $sqlPending = 0;
|
|
||||||
|
|
||||||
public bool $sqlTableExists = false;
|
|
||||||
|
|
||||||
public function mount(): void
|
|
||||||
{
|
|
||||||
$this->emulatorVersion = setting('emulator_version', '?');
|
|
||||||
$this->nitroVersion = setting('nitro_client_version', '?');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->onlineUsers = (int) DB::connection('mysql')->table('users')->where('online', '1')->count();
|
|
||||||
$sizeResult = DB::connection('mysql')->select('SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 1) as db_size FROM information_schema.tables WHERE table_schema = DATABASE()');
|
|
||||||
$this->dbSize = ($sizeResult[0]->db_size ?? '0') . ' MB';
|
|
||||||
} catch (\Exception) {
|
|
||||||
$this->dbSize = '? MB';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->loadSqlStatus();
|
|
||||||
|
|
||||||
$cached = Cache::get('all_updates_check');
|
|
||||||
|
|
||||||
if ($cached !== null) {
|
|
||||||
$this->emulatorUpdate = $cached['emulator'] ?? false;
|
|
||||||
$this->latestEmulatorVersion = $cached['emulator_version'] ?? null;
|
|
||||||
$this->nitroUpdate = $cached['nitro'] ?? false;
|
|
||||||
$this->latestNitroVersion = $cached['nitro_version'] ?? null;
|
|
||||||
$this->hasAnyUpdate = $this->emulatorUpdate || $this->nitroUpdate;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->performCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadSqlStatus(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$updateService = new EmulatorUpdateService;
|
|
||||||
$sqlDiagnosis = $updateService->diagnoseSqlUpdates();
|
|
||||||
|
|
||||||
$this->sqlTableExists = $sqlDiagnosis['table_exists'] ?? false;
|
|
||||||
$this->sqlApplied = $sqlDiagnosis['applied_count'] ?? 0;
|
|
||||||
$this->sqlPending = $sqlDiagnosis['pending_count'] ?? 0;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[UpdateChecker] SQL status failed: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function performCheck(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$updateService = new EmulatorUpdateService;
|
|
||||||
$check = $updateService->checkForUpdates();
|
|
||||||
$this->emulatorUpdate = $check['update_available'] ?? false;
|
|
||||||
$this->latestEmulatorVersion = $check['latest_version'] ?? null;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[UpdateChecker] Emulator check failed: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$nitroService = new NitroUpdateService;
|
|
||||||
$nitroCheck = $nitroService->checkForUpdates();
|
|
||||||
$this->nitroUpdate = $nitroCheck['has_updates'] ?? false;
|
|
||||||
if ($this->nitroUpdate) {
|
|
||||||
$parts = [];
|
|
||||||
if ($nitroCheck['client_update'] ?? false) {
|
|
||||||
$parts[] = 'Client';
|
|
||||||
}
|
|
||||||
if ($nitroCheck['renderer_update'] ?? false) {
|
|
||||||
$parts[] = 'Renderer';
|
|
||||||
}
|
|
||||||
$this->latestNitroVersion = implode(' + ', $parts);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[UpdateChecker] Nitro check failed: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->hasAnyUpdate = $this->emulatorUpdate || $this->nitroUpdate;
|
|
||||||
|
|
||||||
Cache::put('all_updates_check', [
|
|
||||||
'emulator' => $this->emulatorUpdate,
|
|
||||||
'emulator_version' => $this->latestEmulatorVersion,
|
|
||||||
'nitro' => $this->nitroUpdate,
|
|
||||||
'nitro_version' => $this->latestNitroVersion,
|
|
||||||
], now()->addMinutes(15));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function forceCheck(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Cache::forget('all_updates_check');
|
|
||||||
$updateService = new EmulatorUpdateService;
|
|
||||||
$updateService->resetInstalledDate();
|
|
||||||
$this->performCheck();
|
|
||||||
|
|
||||||
if ($this->hasAnyUpdate) {
|
|
||||||
Notification::make()->success()->title('Updates Gevonden!')->body('Klik op "Alles Updaten" om te installeren')->send();
|
|
||||||
} else {
|
|
||||||
Notification::make()->info()->title('Geen Updates')->body('Alles is al up-to-date')->send();
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Notification::make()->danger()->title('Fout')->body($e->getMessage())->send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function repairSystem(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Cache::forget('all_updates_check');
|
|
||||||
|
|
||||||
$messages = [];
|
|
||||||
$errors = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$emuService = new EmulatorUpdateService;
|
|
||||||
$repairResult = $emuService->repairEmulator();
|
|
||||||
|
|
||||||
if ($repairResult['success']) {
|
|
||||||
$messages[] = '🖥️ Emulator: ' . ($repairResult['message'] ?? 'Gerepareerd');
|
|
||||||
} else {
|
|
||||||
$errors[] = 'Emulator: ' . ($repairResult['error'] ?? 'Onbekende fout');
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$errors[] = 'Emulator: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$nitroService = new NitroUpdateService;
|
|
||||||
$repairResult = $nitroService->repair();
|
|
||||||
|
|
||||||
if ($repairResult['success']) {
|
|
||||||
$messages[] = '🎮 Nitro: ' . ($repairResult['message'] ?? 'Gerepareerd');
|
|
||||||
} else {
|
|
||||||
$errors[] = 'Nitro: ' . ($repairResult['error'] ?? 'Onbekende fout');
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$errors[] = 'Nitro: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($messages !== []) {
|
|
||||||
Notification::make()->success()->title('Reparatie Voltooid!')->body(implode(' | ', $messages))->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($errors !== []) {
|
|
||||||
Notification::make()->danger()->title('Reparatie Fout')->body(implode(' | ', $errors))->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::forget('all_updates_check');
|
|
||||||
$this->mount();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Notification::make()->danger()->title('Fout')->body($e->getMessage())->send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function diagnoseSystem(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$emuService = new EmulatorUpdateService;
|
|
||||||
$nitroService = new NitroUpdateService;
|
|
||||||
|
|
||||||
$emuDiagnosis = $emuService->diagnose();
|
|
||||||
$nitroDiagnosis = $nitroService->diagnose();
|
|
||||||
|
|
||||||
$issues = array_merge(
|
|
||||||
$emuDiagnosis['issues'] ?? [],
|
|
||||||
$nitroDiagnosis['issues'] ?? [],
|
|
||||||
);
|
|
||||||
|
|
||||||
$recommendations = array_merge(
|
|
||||||
$emuDiagnosis['recommendations'] ?? [],
|
|
||||||
$nitroDiagnosis['recommendations'] ?? [],
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($issues === []) {
|
|
||||||
Notification::make()->info()->title('Diagnose')->body('Geen problemen gevonden')->send();
|
|
||||||
} else {
|
|
||||||
$body = 'Problemen: ' . implode(', ', $issues);
|
|
||||||
if ($recommendations !== []) {
|
|
||||||
$body .= "\n\nAanbevelingen: " . implode(', ', $recommendations);
|
|
||||||
}
|
|
||||||
Notification::make()->warning()->title('Diagnose Resultaat')->body($body)->send();
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Notification::make()->danger()->title('Fout')->body($e->getMessage())->send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateAll(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
Cache::forget('all_updates_check');
|
|
||||||
Cache::forget('website_settings');
|
|
||||||
|
|
||||||
$messages = [];
|
|
||||||
$errors = [];
|
|
||||||
$updateService = new EmulatorUpdateService;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = $updateService->updateEmulator();
|
|
||||||
if ($result['success']) {
|
|
||||||
$messages[] = '🖥️ ' . ($result['message'] ?? 'Emulator geüpdatet');
|
|
||||||
} else {
|
|
||||||
$error = $result['error'] ?? '';
|
|
||||||
if (str_contains($error, 'al up-to-date')) {
|
|
||||||
$messages[] = '🖥️ Emulator al up-to-date';
|
|
||||||
} else {
|
|
||||||
$errors[] = 'Emulator: ' . $error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$errors[] = 'Emulator: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$nitroService = new NitroUpdateService;
|
|
||||||
$nitroCheck = $nitroService->checkForUpdates();
|
|
||||||
|
|
||||||
if ($nitroCheck['has_updates'] ?? false) {
|
|
||||||
$result = $nitroService->updateNitro();
|
|
||||||
if ($result['success']) {
|
|
||||||
$parts = [];
|
|
||||||
if ($result['renderer_updated'] ?? false) {
|
|
||||||
$parts[] = 'Renderer';
|
|
||||||
}
|
|
||||||
if ($result['client_updated'] ?? false) {
|
|
||||||
$parts[] = 'Client';
|
|
||||||
}
|
|
||||||
if ($result['built'] ?? false) {
|
|
||||||
$parts[] = 'Build';
|
|
||||||
}
|
|
||||||
if ($result['deployed'] ?? false) {
|
|
||||||
$parts[] = 'Deploy';
|
|
||||||
}
|
|
||||||
if ($parts !== []) {
|
|
||||||
$messages[] = '🎮 Nitro: ' . implode(', ', $parts);
|
|
||||||
}
|
|
||||||
} elseif (! empty($result['errors'] ?? [])) {
|
|
||||||
$errors = array_merge($errors, $result['errors']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$messages[] = '🎮 Nitro al up-to-date';
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$errors[] = 'Nitro: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$sqlResult = $updateService->runSqlUpdates();
|
|
||||||
if ($sqlResult['sql_updated'] ?? false) {
|
|
||||||
$count = count($sqlResult['files_run'] ?? []);
|
|
||||||
if ($count > 0) {
|
|
||||||
$messages[] = "📊 {$count} SQL updates";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$errors[] = 'SQL: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$updateService->restartEmulator();
|
|
||||||
$messages[] = '🔄 Emulator herstart';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[UpdateChecker] Restart failed: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$rcon = new RconService;
|
|
||||||
$rcon->sendCommand('updatecatalog');
|
|
||||||
$rcon->sendCommand('updatewordfilter');
|
|
||||||
$messages[] = '📚 Catalogus + filter vernieuwd';
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[UpdateChecker] RCON failed: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($messages !== []) {
|
|
||||||
Notification::make()->success()->title('Updates Voltooid!')->body(implode(' | ', $messages))->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($errors !== []) {
|
|
||||||
Notification::make()->danger()->title('Sommige Updates Mislukt')->body(implode(' | ', $errors))->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($messages === [] && $errors === []) {
|
|
||||||
Notification::make()->info()->title('Geen Updates')->body('Alles was al up-to-date')->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::forget('all_updates_check');
|
|
||||||
$this->mount();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Notification::make()->danger()->title('Fout')->body($e->getMessage())->send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public static function canView(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Emulator;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
class EmulatorBuildService
|
|
||||||
{
|
|
||||||
use EmulatorConfiguration;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->loadConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildFromSource(bool $force = false): array
|
|
||||||
{
|
|
||||||
$repo = $this->sourceRepo ?: $this->githubRepo;
|
|
||||||
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
||||||
|
|
||||||
if (! $repo) {
|
|
||||||
return ['success' => false, 'error' => 'Geen source repo geconfigureerd'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$sourcePath = $this->emulatorSourcePath;
|
|
||||||
$serviceName = $this->emulatorService;
|
|
||||||
|
|
||||||
Log::info('[EmulatorBuild] Starting source build', [
|
|
||||||
'repo' => $repo,
|
|
||||||
'branch' => $branch,
|
|
||||||
'path' => $sourcePath,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->ensureJavaInstalled();
|
|
||||||
|
|
||||||
Process::timeout(10)->run('mkdir -p ' . escapeshellarg(dirname((string) $sourcePath)));
|
|
||||||
Process::timeout(10)->run('mkdir -p ' . escapeshellarg((string) $this->jarPath));
|
|
||||||
Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg(dirname((string) $sourcePath)));
|
|
||||||
|
|
||||||
$maxRetries = 2;
|
|
||||||
$lastError = '';
|
|
||||||
|
|
||||||
for ($retry = 0; $retry <= $maxRetries; $retry++) {
|
|
||||||
Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg(dirname((string) $sourcePath)) . ' 2>/dev/null || true');
|
|
||||||
Process::timeout(10)->run('chown -R www-data:www-data ' . escapeshellarg((string) $sourcePath) . ' 2>/dev/null || true');
|
|
||||||
|
|
||||||
if ($retry > 0) {
|
|
||||||
Log::info('[EmulatorBuild] Retry attempt', ['attempt' => $retry]);
|
|
||||||
Process::timeout(30)->run('rm -rf ' . escapeshellarg((string) $sourcePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$existsCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . " ] && echo 'exists'");
|
|
||||||
$sourceExists = $existsCheck->successful() && trim($existsCheck->output()) === 'exists';
|
|
||||||
|
|
||||||
$gitCheck = Process::timeout(5)->run('[ -d ' . escapeshellarg((string) $sourcePath) . "/.git ] && echo 'git'");
|
|
||||||
$isGitRepo = $gitCheck->successful() && trim($gitCheck->output()) === 'git';
|
|
||||||
|
|
||||||
if ($sourceExists && $isGitRepo) {
|
|
||||||
Log::info('[EmulatorBuild] Pulling latest changes');
|
|
||||||
$commands = [
|
|
||||||
'cd ' . escapeshellarg((string) $sourcePath) . ' && git fetch origin',
|
|
||||||
'cd ' . escapeshellarg((string) $sourcePath) . ' && git checkout ' . escapeshellarg($branch),
|
|
||||||
'cd ' . escapeshellarg((string) $sourcePath) . ' && git pull origin ' . escapeshellarg($branch),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
Log::info('[EmulatorBuild] Cloning repository');
|
|
||||||
$commands = [
|
|
||||||
'sudo rm -rf ' . escapeshellarg((string) $sourcePath) . ' 2>/dev/null || rm -rf ' . escapeshellarg((string) $sourcePath),
|
|
||||||
'mkdir -p ' . escapeshellarg(dirname((string) $sourcePath)),
|
|
||||||
'git clone --branch ' . escapeshellarg($branch) . ' --depth 1 https://github.com/' . escapeshellarg($repo) . '.git ' . escapeshellarg((string) $sourcePath),
|
|
||||||
'chown -R www-data:www-data ' . escapeshellarg((string) $sourcePath),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$command = implode(' && ', $commands);
|
|
||||||
$result = Process::timeout(300)->run($command);
|
|
||||||
|
|
||||||
if ($result->failed()) {
|
|
||||||
$lastError = 'Git clone/pull failed: ' . substr($result->errorOutput(), 0, 300);
|
|
||||||
Log::warning('[EmulatorBuild] Git operation failed', ['error' => $lastError, 'attempt' => $retry]);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$buildCommands = $this->getBuildCommands($sourcePath);
|
|
||||||
|
|
||||||
Log::info('[EmulatorBuild] Running build', ['command' => $buildCommands]);
|
|
||||||
$buildResult = Process::timeout(600)->run('cd ' . escapeshellarg((string) $sourcePath) . ' && ' . $buildCommands);
|
|
||||||
|
|
||||||
$hasSignalError = str_contains($buildResult->errorOutput(), 'signal') || str_contains($buildResult->output(), 'signal');
|
|
||||||
|
|
||||||
if ($hasSignalError) {
|
|
||||||
Log::warning('[EmulatorBuild] Build process received signal, checking if JAR was built anyway');
|
|
||||||
}
|
|
||||||
|
|
||||||
$jarPath = $this->findBuiltJar($sourcePath);
|
|
||||||
|
|
||||||
if ($jarPath) {
|
|
||||||
Log::info('[EmulatorBuild] JAR found despite build status', ['jar' => $jarPath]);
|
|
||||||
} elseif ($buildResult->failed() && ! $hasSignalError) {
|
|
||||||
$lastError = 'Build failed: ' . substr($buildResult->errorOutput(), 0, 500);
|
|
||||||
Log::warning('[EmulatorBuild] Build failed', ['error' => $lastError, 'attempt' => $retry]);
|
|
||||||
|
|
||||||
if ($retry < $maxRetries) {
|
|
||||||
$cleanCommands = [
|
|
||||||
'cd ' . escapeshellarg((string) $sourcePath) . ' && mvn clean 2>/dev/null || ./gradlew clean 2>/dev/null || true',
|
|
||||||
];
|
|
||||||
Process::timeout(60)->run(implode(' && ', $cleanCommands));
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $jarPath) {
|
|
||||||
$lastError = 'Build succeeded but JAR not found. Check build output.';
|
|
||||||
Log::warning('[EmulatorBuild] JAR not found', ['attempt' => $retry]);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->deployJar($jarPath, $serviceName);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$lastError = $e->getMessage();
|
|
||||||
Log::error('[EmulatorBuild] Source build exception', ['error' => $lastError, 'attempt' => $retry]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Build mislukt na ' . ($maxRetries + 1) . ' pogingen. Laatste fout: ' . $lastError,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBuildCommands(string $sourcePath): string
|
|
||||||
{
|
|
||||||
$pomPath = $sourcePath;
|
|
||||||
$pomCheck = Process::timeout(5)->run("[ -f {$pomPath}/pom.xml ] && echo 'pom'");
|
|
||||||
|
|
||||||
if (! $pomCheck->successful() || trim($pomCheck->output()) !== 'pom') {
|
|
||||||
$pomPath = $sourcePath . '/Emulator';
|
|
||||||
$pomCheck = Process::timeout(5)->run("[ -f {$pomPath}/pom.xml ] && echo 'pom'");
|
|
||||||
}
|
|
||||||
|
|
||||||
$gradlewPath = $sourcePath . '/gradlew';
|
|
||||||
$gradlewCheck = Process::timeout(5)->run("[ -f {$gradlewPath} ] && echo 'gradlew'");
|
|
||||||
|
|
||||||
$gradlePath = $sourcePath . '/build.gradle';
|
|
||||||
$gradleCheck = Process::timeout(5)->run("[ -f {$gradlePath} ] && echo 'gradle'");
|
|
||||||
|
|
||||||
if ($pomCheck->successful() && trim($pomCheck->output()) === 'pom') {
|
|
||||||
return "cd {$pomPath} && mvn clean package -DskipTests 2>&1";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($gradlewCheck->successful() && trim($gradlewCheck->output()) === 'gradlew') {
|
|
||||||
return "cd {$sourcePath} && chmod +x gradlew && ./gradlew clean build -x test 2>&1";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($gradleCheck->successful() && trim($gradleCheck->output()) === 'gradle') {
|
|
||||||
return "cd {$sourcePath} && gradle clean build -x test 2>&1";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "cd {$sourcePath} && ls -la";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findBuiltJar(string $sourcePath): ?string
|
|
||||||
{
|
|
||||||
$patterns = [
|
|
||||||
$sourcePath . '/target/*.jar',
|
|
||||||
$sourcePath . '/build/libs/*.jar',
|
|
||||||
$sourcePath . '/*/*.jar',
|
|
||||||
$sourcePath . '/*.jar',
|
|
||||||
$sourcePath . '/Emulator/target/*.jar',
|
|
||||||
$sourcePath . '/Emulator/build/libs/*.jar',
|
|
||||||
$sourcePath . '/Emulator/*/*.jar',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($patterns as $pattern) {
|
|
||||||
$result = Process::timeout(10)->run('ls -t ' . $pattern . ' 2>/dev/null | head -1');
|
|
||||||
if ($result->successful()) {
|
|
||||||
$jarPath = trim($result->output());
|
|
||||||
if ($jarPath !== '' && $jarPath !== '0' && str_contains($jarPath, '.jar')) {
|
|
||||||
return $jarPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deployJar(string $jarPath, string $serviceName): array
|
|
||||||
{
|
|
||||||
$jarName = basename($jarPath);
|
|
||||||
$version = $this->extractVersionFromFilename($jarName);
|
|
||||||
if ($version === '' || $version === '0') {
|
|
||||||
$version = date('Y.m.d');
|
|
||||||
}
|
|
||||||
|
|
||||||
$deployCommands = [
|
|
||||||
'mkdir -p ' . escapeshellarg((string) $this->jarPath),
|
|
||||||
'chown -R www-data:www-data ' . escapeshellarg((string) $this->jarPath),
|
|
||||||
'if ls ' . escapeshellarg((string) $this->jarPath) . '/*.jar 1>/dev/null 2>&1; then mkdir -p ' . escapeshellarg((string) $this->jarPath) . '/backup && mv ' . escapeshellarg((string) $this->jarPath) . '/*.jar ' . escapeshellarg((string) $this->jarPath) . '/backup/; fi',
|
|
||||||
'cp -f ' . escapeshellarg($jarPath) . ' ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName),
|
|
||||||
'chown www-data:www-data ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName),
|
|
||||||
'chmod 755 ' . escapeshellarg((string) $this->jarPath) . '/' . escapeshellarg($jarName),
|
|
||||||
'ls -la ' . escapeshellarg((string) $this->jarPath) . '/',
|
|
||||||
'systemctl restart ' . escapeshellarg((string) $serviceName) . ' 2>&1 || service ' . escapeshellarg((string) $serviceName) . ' restart 2>&1 || true',
|
|
||||||
];
|
|
||||||
|
|
||||||
$deployCommand = implode(' && ', $deployCommands);
|
|
||||||
|
|
||||||
Log::info('[EmulatorBuild] Deploying jar', [
|
|
||||||
'source' => $jarPath,
|
|
||||||
'destination' => "{$this->jarPath}/{$jarName}",
|
|
||||||
]);
|
|
||||||
|
|
||||||
$deployResult = Process::timeout(60)->run($deployCommand);
|
|
||||||
|
|
||||||
if (! $deployResult->successful()) {
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Deploy failed: ' . substr($deployResult->errorOutput(), 0, 300),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$sourceService = new EmulatorSourceService;
|
|
||||||
$sourceInfo = $sourceService->checkForUpdates();
|
|
||||||
$installedDate = $sourceInfo['latest_timestamp'] ?? time();
|
|
||||||
|
|
||||||
$this->settings->set('emulator_version', $version);
|
|
||||||
$this->settings->set('emulator_jar_installed_date', (string) $installedDate);
|
|
||||||
$this->settings->set('emulator_source_commit', $sourceInfo['latest_sha'] ?? 'unknown');
|
|
||||||
$this->settings->set('emulator_source_date', (string) $installedDate);
|
|
||||||
|
|
||||||
$currentBranch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
||||||
$this->settings->set('emulator_installed_branch', $currentBranch);
|
|
||||||
|
|
||||||
$sqlService = new EmulatorSqlService;
|
|
||||||
$sqlService->runUpdates();
|
|
||||||
|
|
||||||
Log::info('[EmulatorBuild] Source build successful');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'version' => $version,
|
|
||||||
'jar' => $jarName,
|
|
||||||
'built' => true,
|
|
||||||
'message' => "✅ Emulator vanaf source gebouwd!\n📦 {$jarName}\n🔄 Service herstart",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function ensureJavaInstalled(): void
|
|
||||||
{
|
|
||||||
$javaCheck = Process::timeout(5)->run('java -version 2>&1');
|
|
||||||
if (! $javaCheck->successful()) {
|
|
||||||
Log::warning('[EmulatorBuild] Java not found, attempting to install');
|
|
||||||
Process::timeout(180)->run('apt-get update && apt-get install -y default-jdk 2>&1');
|
|
||||||
}
|
|
||||||
|
|
||||||
$mavenCheck = Process::timeout(5)->run('which mvn');
|
|
||||||
if (! $mavenCheck->successful()) {
|
|
||||||
Log::warning('[EmulatorBuild] Maven not found, attempting to install');
|
|
||||||
Process::timeout(180)->run('apt-get install -y maven 2>&1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,509 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Emulator;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class EmulatorJarService
|
|
||||||
{
|
|
||||||
use EmulatorConfiguration;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->loadConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isConfigured(): bool
|
|
||||||
{
|
|
||||||
return ! in_array($this->githubUrl, [null, '', '0'], true) || ! in_array($this->jarDirectUrl, [null, '', '0'], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkForUpdates(): array
|
|
||||||
{
|
|
||||||
if (! $this->isConfigured()) {
|
|
||||||
return [
|
|
||||||
'update_available' => false,
|
|
||||||
'error' => 'Configureer een GitHub URL of directe .jar URL',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$sourceService = new EmulatorSourceService;
|
|
||||||
$sourceInfo = $sourceService->checkForUpdates();
|
|
||||||
$hasSourceUpdates = $sourceInfo && $sourceInfo['has_update'];
|
|
||||||
|
|
||||||
if (! in_array($this->jarDirectUrl, [null, '', '0'], true)) {
|
|
||||||
return $this->checkDirectUrlUpdates($sourceInfo, $hasSourceUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->checkGitHubFolderUpdates($sourceInfo, $hasSourceUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function performUpdate(array $check): array
|
|
||||||
{
|
|
||||||
$jarUrl = $check['jar_url'];
|
|
||||||
$jarName = $check['jar_name'];
|
|
||||||
$version = $check['latest_version'];
|
|
||||||
$serviceName = $this->emulatorService;
|
|
||||||
|
|
||||||
$tempDir = '/tmp/emulator-update-' . Str::random(8);
|
|
||||||
$tempJar = $tempDir . '/' . $jarName;
|
|
||||||
|
|
||||||
$updateScript = $this->buildUpdateScript($jarUrl, $jarName, $tempDir, $tempJar, $serviceName);
|
|
||||||
|
|
||||||
$scriptPath = '/tmp/emulator_update_' . uniqid() . '.sh';
|
|
||||||
file_put_contents($scriptPath, $updateScript);
|
|
||||||
chmod($scriptPath, 0755);
|
|
||||||
|
|
||||||
Log::info('[EmulatorJar] Starting update', [
|
|
||||||
'version' => $version,
|
|
||||||
'jar' => $jarName,
|
|
||||||
'url' => $jarUrl,
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = Process::timeout(600)->run('bash ' . $scriptPath . ' 2>&1');
|
|
||||||
@unlink($scriptPath);
|
|
||||||
|
|
||||||
if ($result->exitCode() !== 0) {
|
|
||||||
Log::error('[EmulatorJar] Update failed', [
|
|
||||||
'output' => $result->output(),
|
|
||||||
'error' => $result->errorOutput(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Update mislukt: ' . substr($result->output(), 0, 300),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->storeUpdateInfo($version, $check);
|
|
||||||
|
|
||||||
Log::info('[EmulatorJar] Update successful');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'version' => $version,
|
|
||||||
'jar' => $jarName,
|
|
||||||
'message' => "✅ Emulator geüpdatet naar v{$version}!\n📦 {$jarName}\n🔄 Service herstart",
|
|
||||||
];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[EmulatorJar] Exception', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findLatestJar(): ?array
|
|
||||||
{
|
|
||||||
if (! $this->githubRepo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$branch = $this->githubBranch ?: 'main';
|
|
||||||
|
|
||||||
$commonNames = ['arcturus.jar', 'Arcturus.jar', 'emulator.jar', 'habbo.jar', 'hotel.jar'];
|
|
||||||
|
|
||||||
foreach ($commonNames as $name) {
|
|
||||||
try {
|
|
||||||
$apiUrl = "https://api.github.com/repos/{$this->githubRepo}/contents/Latest_Compiled_Version/{$name}?ref={$branch}";
|
|
||||||
$response = Http::timeout(10)
|
|
||||||
->withHeaders([
|
|
||||||
'Accept' => 'application/vnd.github.v3+json',
|
|
||||||
'User-Agent' => 'AtomCMS-Emulator-Updater',
|
|
||||||
])
|
|
||||||
->get($apiUrl);
|
|
||||||
|
|
||||||
if (! $response->successful()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode($response->body(), true);
|
|
||||||
|
|
||||||
if (! isset($data['sha']) || ! isset($data['download_url'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$commitDate = null;
|
|
||||||
$commitSha = $data['sha'];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$commitsResponse = Http::timeout(10)
|
|
||||||
->withHeaders([
|
|
||||||
'Accept' => 'application/vnd.github.v3+json',
|
|
||||||
'User-Agent' => 'AtomCMS-Emulator-Updater',
|
|
||||||
])
|
|
||||||
->get("https://api.github.com/repos/{$this->githubRepo}/commits", [
|
|
||||||
'path' => "Latest_Compiled_Version/{$name}",
|
|
||||||
'sha' => $branch,
|
|
||||||
'per_page' => 1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($commitsResponse->successful()) {
|
|
||||||
$commits = $commitsResponse->json();
|
|
||||||
if (! empty($commits) && isset($commits[0]['commit']['committer']['date'])) {
|
|
||||||
$commitDate = strtotime($commits[0]['commit']['committer']['date']);
|
|
||||||
$commitSha = $commits[0]['sha'] ?? $data['sha'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception) {
|
|
||||||
Log::debug('[EmulatorJar] Could not fetch commit date for ' . $name);
|
|
||||||
}
|
|
||||||
|
|
||||||
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
|
|
||||||
$installedJarCommit = $this->settings->getOrDefault('emulator_jar_commit', null);
|
|
||||||
|
|
||||||
$isUpdate = false;
|
|
||||||
if ($installedJarCommit !== null) {
|
|
||||||
$isUpdate = $installedJarCommit !== $commitSha;
|
|
||||||
} elseif ($installedDate !== null && $commitDate !== null) {
|
|
||||||
$isUpdate = (int) $installedDate < $commitDate;
|
|
||||||
} elseif ($installedDate === null && $commitDate !== null) {
|
|
||||||
$isUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$version = $this->extractVersionFromFilename($name);
|
|
||||||
if ($version === '' || $version === '0') {
|
|
||||||
$version = $commitDate ? date('Y.m.d', $commitDate) : date('Y.m.d');
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'name' => $name,
|
|
||||||
'url' => $data['download_url'],
|
|
||||||
'version' => $version,
|
|
||||||
'commit' => $commitSha,
|
|
||||||
'commit_date' => $commitDate,
|
|
||||||
'is_update' => $isUpdate,
|
|
||||||
'installed_date' => $installedDate,
|
|
||||||
];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::warning('[EmulatorJar] Error checking JAR file ' . $name, ['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkDirectUrlUpdates(?array $sourceInfo, bool $hasSourceUpdates): array
|
|
||||||
{
|
|
||||||
$jarInfo = $this->validateDirectUrl($this->jarDirectUrl);
|
|
||||||
|
|
||||||
if ($jarInfo) {
|
|
||||||
if (! empty($jarInfo['version'])) {
|
|
||||||
$currentVersion = $this->settings->getOrDefault('emulator_version', '0.0.0');
|
|
||||||
if ($jarInfo['version'] !== $currentVersion) {
|
|
||||||
$this->settings->set('emulator_version', $jarInfo['version']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$hasJarUpdates = $jarInfo['is_update'] ?? false;
|
|
||||||
|
|
||||||
if ($hasSourceUpdates && ! $hasJarUpdates) {
|
|
||||||
return [
|
|
||||||
'update_available' => true,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : $jarInfo['version'],
|
|
||||||
'release_name' => 'Source Update',
|
|
||||||
'jar_url' => null,
|
|
||||||
'jar_name' => null,
|
|
||||||
'jar_size' => 'Onbekend',
|
|
||||||
'type' => 'source_build',
|
|
||||||
'source_info' => $sourceInfo,
|
|
||||||
'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'update_available' => $hasJarUpdates,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'latest_version' => $jarInfo['version'],
|
|
||||||
'release_name' => $hasSourceUpdates ? 'Source Update' : 'Direct URL',
|
|
||||||
'jar_url' => $this->jarDirectUrl,
|
|
||||||
'jar_name' => $jarInfo['name'],
|
|
||||||
'jar_size' => 'Onbekend',
|
|
||||||
'type' => $hasSourceUpdates ? 'source_build' : 'direct_url',
|
|
||||||
'commit' => $jarInfo['commit_sha'] ?? null,
|
|
||||||
'source_info' => $sourceInfo,
|
|
||||||
'has_source_updates' => $hasSourceUpdates,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($hasSourceUpdates) {
|
|
||||||
return [
|
|
||||||
'update_available' => true,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : 'Onbekend',
|
|
||||||
'release_name' => 'Source Update',
|
|
||||||
'jar_url' => null,
|
|
||||||
'jar_name' => null,
|
|
||||||
'jar_size' => 'Onbekend',
|
|
||||||
'type' => 'source_build',
|
|
||||||
'source_info' => $sourceInfo,
|
|
||||||
'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'update_available' => false,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'error' => 'Directe .jar URL is niet bereikbaar',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkGitHubFolderUpdates(?array $sourceInfo, bool $hasSourceUpdates): array
|
|
||||||
{
|
|
||||||
$jarInfo = $this->findLatestJar();
|
|
||||||
|
|
||||||
if (! $jarInfo) {
|
|
||||||
if ($hasSourceUpdates) {
|
|
||||||
return [
|
|
||||||
'update_available' => true,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : 'Onbekend',
|
|
||||||
'release_name' => 'Source Update',
|
|
||||||
'jar_url' => null,
|
|
||||||
'jar_name' => null,
|
|
||||||
'jar_size' => 'Onbekend',
|
|
||||||
'type' => 'source_build',
|
|
||||||
'source_info' => $sourceInfo,
|
|
||||||
'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'update_available' => false,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'latest_version' => 'Onbekend',
|
|
||||||
'error' => 'Kon geen .jar bestand vinden',
|
|
||||||
'type' => 'not_found',
|
|
||||||
'source_available' => $sourceInfo !== null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$version = $jarInfo['version'];
|
|
||||||
$hasJarUpdates = $jarInfo['is_update'] ?? false;
|
|
||||||
|
|
||||||
if ($hasSourceUpdates && ! $hasJarUpdates) {
|
|
||||||
return [
|
|
||||||
'update_available' => true,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'latest_version' => $sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : $version,
|
|
||||||
'release_name' => 'Source Update',
|
|
||||||
'jar_url' => null,
|
|
||||||
'jar_name' => null,
|
|
||||||
'jar_size' => 'Onbekend',
|
|
||||||
'type' => 'source_build',
|
|
||||||
'source_info' => $sourceInfo,
|
|
||||||
'message' => 'Nieuwe commits beschikbaar - build vanaf source nodig',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'update_available' => $hasJarUpdates,
|
|
||||||
'current_version' => $this->settings->getOrDefault('emulator_version', '0.0.0'),
|
|
||||||
'latest_version' => $hasSourceUpdates ? ($sourceInfo['latest_timestamp'] ? date('Y.m.d', $sourceInfo['latest_timestamp']) : $version) : $version,
|
|
||||||
'release_name' => $hasSourceUpdates ? 'Source Update' : 'Latest from GitHub',
|
|
||||||
'jar_url' => $jarInfo['url'],
|
|
||||||
'jar_name' => $jarInfo['name'],
|
|
||||||
'jar_size' => 'Onbekend',
|
|
||||||
'type' => $hasSourceUpdates ? 'source_build' : 'github_folder',
|
|
||||||
'commit' => $jarInfo['commit'] ?? null,
|
|
||||||
'commit_date' => $jarInfo['commit_date'] ?? null,
|
|
||||||
'source_info' => $sourceInfo,
|
|
||||||
'has_source_updates' => $hasSourceUpdates,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateDirectUrl(string $url): ?array
|
|
||||||
{
|
|
||||||
if ($url === '' || $url === '0' || ! str_ends_with(strtolower($url), '.jar')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$jarName = basename(parse_url($url, PHP_URL_PATH));
|
|
||||||
$version = $this->extractVersionFromFilename($jarName);
|
|
||||||
$storedVersion = $this->settings->getOrDefault('emulator_version', '0.0.0');
|
|
||||||
|
|
||||||
$lastModified = null;
|
|
||||||
$commitSha = null;
|
|
||||||
$gitHubInfoAvailable = false;
|
|
||||||
|
|
||||||
if (preg_match('/github\.com\/([^\/]+)\/([^\/]+)\/raw\/refs\/heads\/([^\/]+)\/(.+)/', $url, $matches)) {
|
|
||||||
$owner = $matches[1];
|
|
||||||
$repo = $matches[2];
|
|
||||||
$branch = $matches[3];
|
|
||||||
$path = $matches[4];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$apiResponse = Http::timeout(10)
|
|
||||||
->withHeaders([
|
|
||||||
'Accept' => 'application/vnd.github.v3+json',
|
|
||||||
'User-Agent' => 'AtomCMS-Emulator-Updater',
|
|
||||||
])
|
|
||||||
->get("https://api.github.com/repos/{$owner}/{$repo}/contents/{$path}", [
|
|
||||||
'ref' => $branch,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($apiResponse->successful()) {
|
|
||||||
$data = $apiResponse->json();
|
|
||||||
|
|
||||||
if (isset($data['sha']) && ! isset($data['message'])) {
|
|
||||||
$gitHubInfoAvailable = true;
|
|
||||||
$commitSha = $data['sha'];
|
|
||||||
|
|
||||||
if (isset($data['commit']['committer']['date'])) {
|
|
||||||
$lastModified = strtotime($data['commit']['committer']['date']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception) {
|
|
||||||
Log::debug('[EmulatorJar] Could not fetch GitHub commit info for direct URL');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($lastModified === null) {
|
|
||||||
$response = Http::timeout(10)->head($url);
|
|
||||||
if ($response->successful()) {
|
|
||||||
$modifiedSince = $response->header('Last-Modified');
|
|
||||||
if ($modifiedSince) {
|
|
||||||
$lastModified = strtotime($modifiedSince);
|
|
||||||
if ($lastModified === false) {
|
|
||||||
$lastModified = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$isUpdate = false;
|
|
||||||
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
|
|
||||||
|
|
||||||
$storedData = $this->settings->getOrDefault('emulator_direct_url_info_' . md5($url));
|
|
||||||
if ($storedData !== null && is_string($storedData)) {
|
|
||||||
$storedDataArray = json_decode($storedData, true);
|
|
||||||
if (is_array($storedDataArray)) {
|
|
||||||
if ($gitHubInfoAvailable && $commitSha !== null && isset($storedDataArray['commit_sha'])) {
|
|
||||||
$isUpdate = $commitSha !== $storedDataArray['commit_sha'];
|
|
||||||
} elseif ($lastModified !== null && $installedDate !== null) {
|
|
||||||
$isUpdate = (int) $installedDate < $lastModified;
|
|
||||||
} elseif ($lastModified !== null && isset($storedDataArray['last_modified'])) {
|
|
||||||
$isUpdate = $lastModified > $storedDataArray['last_modified'];
|
|
||||||
} elseif (! in_array($version, ['', '0', $storedVersion], true)) {
|
|
||||||
$isUpdate = version_compare($version, $storedVersion) > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$isUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$infoToStore = [
|
|
||||||
'last_checked' => time(),
|
|
||||||
'version' => $version,
|
|
||||||
];
|
|
||||||
if ($lastModified) {
|
|
||||||
$infoToStore['last_modified'] = $lastModified;
|
|
||||||
}
|
|
||||||
if ($commitSha) {
|
|
||||||
$infoToStore['commit_sha'] = $commitSha;
|
|
||||||
}
|
|
||||||
$this->settings->set('emulator_direct_url_info_' . md5($url), json_encode($infoToStore));
|
|
||||||
|
|
||||||
return [
|
|
||||||
'name' => $jarName,
|
|
||||||
'version' => $version,
|
|
||||||
'last_modified' => $lastModified,
|
|
||||||
'commit_sha' => $commitSha,
|
|
||||||
'is_update' => $isUpdate,
|
|
||||||
'gitHub_rate_limited' => ! $gitHubInfoAvailable && preg_match('/github\.com/', $url),
|
|
||||||
];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::warning('[EmulatorJar] Direct URL not reachable', ['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildUpdateScript(string $jarUrl, string $jarName, string $tempDir, string $tempJar, string $serviceName): string
|
|
||||||
{
|
|
||||||
return <<<BASH
|
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
JAR_URL='{$jarUrl}'
|
|
||||||
JAR_NAME='{$jarName}'
|
|
||||||
JAR_PATH='{$this->jarPath}'
|
|
||||||
SERVICE='{$serviceName}'
|
|
||||||
TEMP_DIR='{$tempDir}'
|
|
||||||
TEMP_JAR='{$tempJar}'
|
|
||||||
|
|
||||||
mkdir -p "\$TEMP_DIR" "\$JAR_PATH"
|
|
||||||
|
|
||||||
if ls "\$JAR_PATH"/*.jar 1>/dev/null 2>&1; then
|
|
||||||
mkdir -p "\$JAR_PATH/backup"
|
|
||||||
mv "\$JAR_PATH"/*.jar "\$JAR_PATH/backup/" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
download_success=false
|
|
||||||
for attempt in 1 2 3; do
|
|
||||||
if curl -L --max-time 300 --retry 3 --retry-delay 5 -o "\$TEMP_JAR" "\$JAR_URL" 2>&1; then
|
|
||||||
FILE_TYPE=\$(file -b "\$TEMP_JAR" 2>/dev/null)
|
|
||||||
JAR_SIZE=\$(stat -c%s "\$TEMP_JAR" 2>/dev/null || echo 0)
|
|
||||||
|
|
||||||
if echo "\$FILE_TYPE" | grep -qi "zip\|jar\|archive" && [ "\$JAR_SIZE" -gt 1000 ]; then
|
|
||||||
download_success=true
|
|
||||||
break
|
|
||||||
else
|
|
||||||
rm -f "\$TEMP_JAR" 2>/dev/null || true
|
|
||||||
sleep 3
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
sleep 5
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "\$download_success" = false ]; then
|
|
||||||
if ls "\$JAR_PATH/backup"/*.jar 1>/dev/null 2>&1; then
|
|
||||||
mv "\$JAR_PATH/backup"/*.jar "\$JAR_PATH/" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
rm -rf "\$TEMP_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
mv "\$TEMP_JAR" "\$JAR_PATH/"
|
|
||||||
chown -R www-data:www-data "\$JAR_PATH"
|
|
||||||
chmod 755 "\$JAR_PATH/\$JAR_NAME"
|
|
||||||
|
|
||||||
systemctl restart "\$SERVICE" 2>&1 || service "\$SERVICE" restart 2>&1 || true
|
|
||||||
|
|
||||||
rm -rf "\$TEMP_DIR" 2>/dev/null || true
|
|
||||||
BASH;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function storeUpdateInfo(string $version, array $check): void
|
|
||||||
{
|
|
||||||
setting('emulator_version', $version);
|
|
||||||
|
|
||||||
$commitDate = $check['commit_date'] ?? time();
|
|
||||||
$this->settings->set('emulator_jar_installed_date', (string) $commitDate);
|
|
||||||
$this->settings->set('emulator_jar_commit', $check['commit'] ?? null);
|
|
||||||
|
|
||||||
$sourceSha = $check['source_info']['latest_sha'] ?? $check['commit'] ?? null;
|
|
||||||
$sourceDate = $check['source_info']['latest_timestamp'] ?? ($check['commit_date'] ?? time());
|
|
||||||
if ($sourceSha) {
|
|
||||||
$this->settings->set('emulator_source_commit', $sourceSha);
|
|
||||||
}
|
|
||||||
if ($sourceDate) {
|
|
||||||
$this->settings->set('emulator_source_date', (string) $sourceDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentBranch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
||||||
$this->settings->set('emulator_installed_branch', $currentBranch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Emulator;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class EmulatorSourceService
|
|
||||||
{
|
|
||||||
use EmulatorConfiguration;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->loadConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkForUpdates(): ?array
|
|
||||||
{
|
|
||||||
$repo = $this->sourceRepo ?: $this->githubRepo;
|
|
||||||
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
||||||
|
|
||||||
if (! $repo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$localCheck = $this->checkLocalSourceUpdates();
|
|
||||||
if ($localCheck !== null) {
|
|
||||||
return $localCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->checkRemoteSourceUpdates($repo, $branch);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isSourceBuildAvailable(): bool
|
|
||||||
{
|
|
||||||
$hasRepo = ! in_array($this->sourceRepo, [null, '', '0'], true) || ! in_array($this->githubRepo, [null, '', '0'], true);
|
|
||||||
$hasPath = ! in_array($this->emulatorSourcePath, [null, '', '0'], true);
|
|
||||||
|
|
||||||
if (! $hasRepo || ! $hasPath) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = Process::timeout(5)->run("[ -d {$this->emulatorSourcePath} ] && echo 'exists' || echo 'not_exists'");
|
|
||||||
|
|
||||||
return trim($result->output()) === 'exists';
|
|
||||||
} catch (\Exception) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkLocalSourceUpdates(): ?array
|
|
||||||
{
|
|
||||||
$sourcePath = $this->emulatorSourcePath;
|
|
||||||
|
|
||||||
$existsCheck = Process::timeout(5)->run("[ -d {$sourcePath} ] && echo 'exists'");
|
|
||||||
if (! $existsCheck->successful() || trim($existsCheck->output()) !== 'exists') {
|
|
||||||
return $this->checkSourceByCloning();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$gitCheck = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse --is-inside-work-tree 2>/dev/null");
|
|
||||||
if (! $gitCheck->successful()) {
|
|
||||||
return $this->checkSourceByCloning();
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentCommitResult = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse HEAD");
|
|
||||||
if (! $currentCommitResult->successful()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$currentSha = trim($currentCommitResult->output());
|
|
||||||
|
|
||||||
$fetchResult = Process::timeout(30)->run("cd {$sourcePath} && git fetch origin 2>&1");
|
|
||||||
if ($fetchResult->failed()) {
|
|
||||||
Log::debug('[EmulatorSource] Git fetch failed, trying local check only');
|
|
||||||
}
|
|
||||||
|
|
||||||
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
||||||
$latestResult = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse origin/{$branch} 2>/dev/null || git rev-parse HEAD");
|
|
||||||
if (! $latestResult->successful()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$latestSha = trim($latestResult->output());
|
|
||||||
|
|
||||||
$dateResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%ci' {$latestSha}");
|
|
||||||
$latestDate = null;
|
|
||||||
$latestTimestamp = time();
|
|
||||||
if ($dateResult->successful()) {
|
|
||||||
$latestDate = trim($dateResult->output(), "'");
|
|
||||||
if ($latestDate !== '' && $latestDate !== '0') {
|
|
||||||
$latestTimestamp = strtotime($latestDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->persistSourceCommitInfo($latestSha, $latestTimestamp);
|
|
||||||
|
|
||||||
$msgResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%s' {$latestSha}");
|
|
||||||
$latestMessage = $msgResult->successful() ? trim($msgResult->output()) : '';
|
|
||||||
|
|
||||||
$authorResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%an' {$latestSha}");
|
|
||||||
$latestAuthor = $authorResult->successful() ? trim($authorResult->output()) : '';
|
|
||||||
|
|
||||||
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
|
|
||||||
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
|
|
||||||
|
|
||||||
$isUpdate = $storedSha !== null && $storedSha !== $latestSha;
|
|
||||||
if ($storedSha === null) {
|
|
||||||
$isUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'has_update' => $isUpdate,
|
|
||||||
'latest_sha' => $latestSha,
|
|
||||||
'latest_date' => $latestDate,
|
|
||||||
'latest_timestamp' => $latestTimestamp,
|
|
||||||
'latest_message' => $latestMessage,
|
|
||||||
'latest_author' => $latestAuthor,
|
|
||||||
'stored_sha' => $storedSha,
|
|
||||||
'stored_date' => $storedDate,
|
|
||||||
'source' => 'local',
|
|
||||||
];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::debug('[EmulatorSource] Local source check failed: ' . $e->getMessage());
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkSourceByCloning(): ?array
|
|
||||||
{
|
|
||||||
$repo = $this->sourceRepo ?: $this->githubRepo;
|
|
||||||
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
|
|
||||||
|
|
||||||
if (! $repo) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$repo = preg_replace('#/tree/[^/]+/.*$#', '', $repo);
|
|
||||||
$repo = str_replace(['https://github.com/', 'http://github.com/'], '', $repo);
|
|
||||||
$repo = rtrim($repo, '/');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$tempDir = '/tmp/emulator-source-check-' . Str::random(8);
|
|
||||||
|
|
||||||
$cloneResult = Process::timeout(120)->run(
|
|
||||||
"git clone --branch {$branch} --depth 1 https://github.com/{$repo}.git {$tempDir} 2>&1",
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($cloneResult->failed()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$shaResult = Process::timeout(5)->run("cd {$tempDir} && git rev-parse HEAD");
|
|
||||||
$latestSha = $shaResult->successful() ? trim($shaResult->output()) : '';
|
|
||||||
|
|
||||||
$dateResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%ci'");
|
|
||||||
$latestDate = null;
|
|
||||||
$latestTimestamp = time();
|
|
||||||
if ($dateResult->successful()) {
|
|
||||||
$latestDate = trim($dateResult->output());
|
|
||||||
if ($latestDate !== '' && $latestDate !== '0') {
|
|
||||||
$latestTimestamp = strtotime($latestDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->persistSourceCommitInfo($latestSha, $latestTimestamp);
|
|
||||||
|
|
||||||
$msgResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%s'");
|
|
||||||
$latestMessage = $msgResult->successful() ? trim($msgResult->output()) : '';
|
|
||||||
|
|
||||||
$authorResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%an'");
|
|
||||||
$latestAuthor = $authorResult->successful() ? trim($authorResult->output()) : '';
|
|
||||||
|
|
||||||
Process::timeout(10)->run("rm -rf {$tempDir}");
|
|
||||||
|
|
||||||
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
|
|
||||||
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
|
|
||||||
|
|
||||||
$isUpdate = $storedSha !== null && $storedSha !== $latestSha;
|
|
||||||
if ($storedSha === null) {
|
|
||||||
$isUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'has_update' => $isUpdate,
|
|
||||||
'latest_sha' => $latestSha,
|
|
||||||
'latest_date' => $latestDate,
|
|
||||||
'latest_timestamp' => $latestTimestamp,
|
|
||||||
'latest_message' => $latestMessage,
|
|
||||||
'latest_author' => $latestAuthor,
|
|
||||||
'stored_sha' => $storedSha,
|
|
||||||
'stored_date' => $storedDate,
|
|
||||||
'source' => 'cloned',
|
|
||||||
];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::debug('[EmulatorSource] Source clone check failed: ' . $e->getMessage());
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkRemoteSourceUpdates(string $repo, string $branch): ?array
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$response = Http::timeout(10)
|
|
||||||
->withHeaders([
|
|
||||||
'Accept' => 'application/vnd.github.v3+json',
|
|
||||||
'User-Agent' => 'AtomCMS-Emulator-Updater',
|
|
||||||
])
|
|
||||||
->get("https://api.github.com/repos/{$repo}/commits", [
|
|
||||||
'sha' => $branch,
|
|
||||||
'per_page' => 1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (! $response->successful()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$commits = $response->json();
|
|
||||||
|
|
||||||
if (empty($commits) || ! isset($commits[0]['sha'])) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$latestCommit = $commits[0];
|
|
||||||
$latestSha = $latestCommit['sha'];
|
|
||||||
$latestDate = $latestCommit['commit']['committer']['date'] ?? null;
|
|
||||||
$latestTimestamp = $latestDate ? strtotime((string) $latestDate) : time();
|
|
||||||
|
|
||||||
$this->persistSourceCommitInfo($latestSha, $latestTimestamp);
|
|
||||||
|
|
||||||
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
|
|
||||||
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
|
|
||||||
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
|
|
||||||
|
|
||||||
if ($storedSha !== null && $storedSha === $latestSha) {
|
|
||||||
$isUpdate = false;
|
|
||||||
} elseif ($storedSha !== null && $storedSha !== $latestSha) {
|
|
||||||
$isUpdate = true;
|
|
||||||
} elseif ($installedDate !== null) {
|
|
||||||
$installedTimestamp = is_numeric($installedDate) ? (int) $installedDate : strtotime((string) $installedDate);
|
|
||||||
$isUpdate = $installedTimestamp < $latestTimestamp;
|
|
||||||
} else {
|
|
||||||
$isUpdate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'has_update' => $isUpdate,
|
|
||||||
'latest_sha' => $latestSha,
|
|
||||||
'latest_date' => $latestDate,
|
|
||||||
'latest_timestamp' => $latestTimestamp,
|
|
||||||
'latest_message' => $latestCommit['commit']['message'] ?? '',
|
|
||||||
'latest_author' => $latestCommit['commit']['author']['name'] ?? '',
|
|
||||||
'stored_sha' => $storedSha,
|
|
||||||
'stored_date' => $storedDate,
|
|
||||||
];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::warning('[EmulatorSource] Could not check source updates via GitHub API', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function persistSourceCommitInfo(string $sha, int $timestamp): void
|
|
||||||
{
|
|
||||||
$this->settings->set('emulator_source_commit', $sha);
|
|
||||||
$this->settings->set('emulator_source_date', (string) $timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,510 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services\Emulator;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
class EmulatorSqlService
|
|
||||||
{
|
|
||||||
use EmulatorConfiguration;
|
|
||||||
|
|
||||||
private const string SQL_TABLE = 'emulator_sql_updates';
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->loadConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isConfigured(): bool
|
|
||||||
{
|
|
||||||
return ! in_array($this->githubUrl, [null, '', '0'], true) || ! in_array($this->jarDirectUrl, [null, '', '0'], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkForUpdates(bool $recentOnly = true): array
|
|
||||||
{
|
|
||||||
if (! $this->githubRepo) {
|
|
||||||
return [
|
|
||||||
'has_updates' => false,
|
|
||||||
'error' => 'Geen GitHub repo geconfigureerd',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->ensureSqlTableExists();
|
|
||||||
|
|
||||||
$result = $this->fetchSqlFilesFromGitHub($recentOnly);
|
|
||||||
|
|
||||||
if (isset($result['error'])) {
|
|
||||||
return [
|
|
||||||
'has_updates' => false,
|
|
||||||
'error' => $result['error'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$sqlFiles = $result;
|
|
||||||
|
|
||||||
if ($sqlFiles === []) {
|
|
||||||
return [
|
|
||||||
'has_updates' => false,
|
|
||||||
'message' => $recentOnly
|
|
||||||
? 'Geen SQL updates van de afgelopen week gevonden'
|
|
||||||
: 'Geen SQL updates gevonden',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$appliedHashes = $this->getAppliedSqlHashes();
|
|
||||||
$newSqlFiles = [];
|
|
||||||
$alreadyApplied = [];
|
|
||||||
|
|
||||||
foreach ($sqlFiles as $file) {
|
|
||||||
$hash = $file['sha'] ?? md5((string) $file['name']);
|
|
||||||
if (in_array($hash, $appliedHashes)) {
|
|
||||||
$alreadyApplied[] = $file['name'];
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$newSqlFiles[] = $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($newSqlFiles === []) {
|
|
||||||
return [
|
|
||||||
'has_updates' => false,
|
|
||||||
'message' => 'Alle SQL updates zijn al toegepast (' . count($alreadyApplied) . ' stuks)',
|
|
||||||
'applied_count' => count($alreadyApplied),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
usort($newSqlFiles, fn ($a, $b) => strcmp((string) $a['name'], (string) $b['name']));
|
|
||||||
|
|
||||||
return [
|
|
||||||
'has_updates' => true,
|
|
||||||
'count' => count($newSqlFiles),
|
|
||||||
'files' => $newSqlFiles,
|
|
||||||
'message' => count($newSqlFiles) . ' nieuwe SQL update(s) van deze week',
|
|
||||||
'applied_count' => count($alreadyApplied),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function runUpdates(): array
|
|
||||||
{
|
|
||||||
$sqlCheck = $this->checkForUpdates(false);
|
|
||||||
|
|
||||||
if (! ($sqlCheck['has_updates'] ?? false)) {
|
|
||||||
return ['success' => true, 'sql_updated' => false, 'message' => 'Geen nieuwe SQL updates'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = [
|
|
||||||
'success' => true,
|
|
||||||
'sql_updated' => true,
|
|
||||||
'files_run' => [],
|
|
||||||
'errors' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($sqlCheck['files'] as $file) {
|
|
||||||
$sqlResult = $this->downloadAndRunSql($file);
|
|
||||||
|
|
||||||
if ($sqlResult['success']) {
|
|
||||||
$results['files_run'][] = $file['name'];
|
|
||||||
$this->markSqlAsApplied($file);
|
|
||||||
} else {
|
|
||||||
$results['errors'][] = $file['name'] . ': ' . $sqlResult['error'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($results['errors']) > 0) {
|
|
||||||
$results['message'] = count($results['files_run']) . ' SQL updates succesvol, ' . count($results['errors']) . ' met fouten';
|
|
||||||
} else {
|
|
||||||
$results['message'] = count($results['files_run']) . ' SQL updates succesvol uitgevoerd!';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAppliedUpdates(): array
|
|
||||||
{
|
|
||||||
$this->ensureSqlTableExists();
|
|
||||||
|
|
||||||
return DB::table(self::SQL_TABLE)
|
|
||||||
->orderBy('applied_at', 'desc')
|
|
||||||
->get()
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function diagnose(): array
|
|
||||||
{
|
|
||||||
$diagnosis = [
|
|
||||||
'table_exists' => false,
|
|
||||||
'applied_count' => 0,
|
|
||||||
'pending_count' => 0,
|
|
||||||
'error' => null,
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$diagnosis['table_exists'] = Schema::hasTable(self::SQL_TABLE);
|
|
||||||
|
|
||||||
if ($diagnosis['table_exists']) {
|
|
||||||
$diagnosis['applied_count'] = DB::table(self::SQL_TABLE)->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->githubRepo && $diagnosis['table_exists']) {
|
|
||||||
$sqlCheck = $this->checkForUpdates(false);
|
|
||||||
if (isset($sqlCheck['count'])) {
|
|
||||||
$diagnosis['pending_count'] = $sqlCheck['count'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$diagnosis['error'] = $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $diagnosis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function repair(): array
|
|
||||||
{
|
|
||||||
$actions = [];
|
|
||||||
$errors = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->ensureSqlTableExists();
|
|
||||||
$actions[] = 'SQL update tabel gecontroleerd';
|
|
||||||
|
|
||||||
$appliedCount = DB::table(self::SQL_TABLE)->count();
|
|
||||||
$actions[] = "SQL updates tabel OK ({$appliedCount} records)";
|
|
||||||
|
|
||||||
if ($this->githubRepo) {
|
|
||||||
$sqlCheck = $this->checkForUpdates(false);
|
|
||||||
|
|
||||||
if (isset($sqlCheck['error'])) {
|
|
||||||
$actions[] = 'SQL update check: ' . $sqlCheck['error'];
|
|
||||||
} elseif (! ($sqlCheck['has_updates'] ?? false)) {
|
|
||||||
$actions[] = 'SQL updates: ' . ($sqlCheck['message'] ?? 'Allemaal up-to-date');
|
|
||||||
} else {
|
|
||||||
$count = $sqlCheck['count'] ?? 0;
|
|
||||||
$actions[] = "{$count} nieuwe SQL updates beschikbaar";
|
|
||||||
|
|
||||||
if ($this->isConfigured()) {
|
|
||||||
$actions[] = 'SQL updates worden toegepast...';
|
|
||||||
$runResult = $this->runUpdates();
|
|
||||||
|
|
||||||
if ($runResult['success'] ?? false) {
|
|
||||||
$filesRun = count($runResult['files_run'] ?? []);
|
|
||||||
$actions[] = "{$filesRun} SQL updates toegepast";
|
|
||||||
} else {
|
|
||||||
$errors[] = 'SQL updates: ' . ($runResult['error'] ?? 'Onbekende fout');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$errors[] = 'SQL repair: ' . $e->getMessage();
|
|
||||||
Log::error('[EmulatorSql] Repair failed', ['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => $errors === [],
|
|
||||||
'actions' => $actions,
|
|
||||||
'errors' => $errors,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function ensureSqlTableExists(): void
|
|
||||||
{
|
|
||||||
if (Schema::hasTable(self::SQL_TABLE)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Schema::create(self::SQL_TABLE, function ($table) {
|
|
||||||
$table->id();
|
|
||||||
$table->string('file_name');
|
|
||||||
$table->string('file_hash')->unique();
|
|
||||||
$table->timestamp('applied_at');
|
|
||||||
$table->text('sql_content')->nullable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getAppliedSqlHashes(): array
|
|
||||||
{
|
|
||||||
$this->ensureSqlTableExists();
|
|
||||||
|
|
||||||
return DB::table(self::SQL_TABLE)
|
|
||||||
->pluck('file_hash')
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function markSqlAsApplied(array $file): void
|
|
||||||
{
|
|
||||||
$this->ensureSqlTableExists();
|
|
||||||
|
|
||||||
$hash = $file['sha'] ?? md5((string) $file['name']);
|
|
||||||
|
|
||||||
DB::table(self::SQL_TABLE)->updateOrInsert(
|
|
||||||
['file_hash' => $hash],
|
|
||||||
[
|
|
||||||
'file_name' => $file['name'],
|
|
||||||
'applied_at' => now(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fetchSqlFilesFromGitHub(bool $recentOnly = false): array
|
|
||||||
{
|
|
||||||
if (! $this->githubRepo) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$branch = $this->githubBranch ?: 'main';
|
|
||||||
|
|
||||||
$folderNames = [
|
|
||||||
'Database%20Updates',
|
|
||||||
'Database Updates',
|
|
||||||
'database_updates',
|
|
||||||
'database/updates',
|
|
||||||
'sql/updates',
|
|
||||||
'sql',
|
|
||||||
'updates',
|
|
||||||
];
|
|
||||||
|
|
||||||
$knownSqlFiles = [
|
|
||||||
'07012026_UpdateDatabase_to_4-0-1.sql',
|
|
||||||
'09012026_UpdateDatabase_to_4-0-2.sql',
|
|
||||||
'12012026_Battle Banzai.sql',
|
|
||||||
'12012026_Breeding Fixes.sql',
|
|
||||||
'12012026_ChatBubbles.sql',
|
|
||||||
'16032026_updateall_command.sql',
|
|
||||||
'17032026_allow_underpass.sql',
|
|
||||||
'19032026_hotel_timezone.sql',
|
|
||||||
'21022026_user_prefixes.sql',
|
|
||||||
'Default_Camera.sql',
|
|
||||||
'UpdateDatabase_Allow_diagonale.sql',
|
|
||||||
'UpdateDatabase_BOT.sql',
|
|
||||||
'UpdateDatabase_Banners.sql',
|
|
||||||
'UpdateDatabase_DanceCMD.sql',
|
|
||||||
'UpdateDatabase_Happiness.sql',
|
|
||||||
'UpdateDatabase_Websocket.sql',
|
|
||||||
'UpdateDatabase_unignorable.sql',
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($recentOnly) {
|
|
||||||
return $this->fetchRecentSqlFiles($branch);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sqlFiles = [];
|
|
||||||
|
|
||||||
foreach ($knownSqlFiles as $filename) {
|
|
||||||
foreach ($folderNames as $folderName) {
|
|
||||||
$encodedFilename = str_replace(' ', '%20', $filename);
|
|
||||||
$url = "https://raw.githubusercontent.com/{$this->githubRepo}/{$branch}/{$folderName}/{$encodedFilename}";
|
|
||||||
$response = Http::timeout(10)->get($url);
|
|
||||||
|
|
||||||
if ($response->successful()) {
|
|
||||||
$sqlFiles[] = [
|
|
||||||
'name' => $filename,
|
|
||||||
'url' => $url,
|
|
||||||
'sha' => md5($response->body()),
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sqlFiles !== []) {
|
|
||||||
usort($sqlFiles, fn ($a, $b) => strcmp($a['name'], $b['name']));
|
|
||||||
|
|
||||||
return $sqlFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::warning('[EmulatorSql] Could not fetch SQL files', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fetchRecentSqlFiles(string $branch): array
|
|
||||||
{
|
|
||||||
$weekAgo = now()->subDays(7)->toIso8601String();
|
|
||||||
|
|
||||||
$githubToken = setting('github_token', '');
|
|
||||||
$headers = [
|
|
||||||
'Accept' => 'application/vnd.github+json',
|
|
||||||
'User-Agent' => 'AtomCMS-EmulatorUpdate/1.0',
|
|
||||||
];
|
|
||||||
if (! empty($githubToken)) {
|
|
||||||
$headers['Authorization'] = 'Bearer ' . $githubToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
$folderNames = ['Database Updates', 'database_updates', 'sql', 'updates'];
|
|
||||||
|
|
||||||
try {
|
|
||||||
foreach ($folderNames as $folderName) {
|
|
||||||
$response = Http::withHeaders($headers)->timeout(30)->get("https://api.github.com/repos/{$this->githubRepo}/commits", [
|
|
||||||
'path' => $folderName,
|
|
||||||
'sha' => $branch,
|
|
||||||
'since' => $weekAgo,
|
|
||||||
'per_page' => 100,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($response->successful()) {
|
|
||||||
$commits = $response->json();
|
|
||||||
|
|
||||||
if (! is_array($commits) || $commits === []) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sqlFiles = [];
|
|
||||||
|
|
||||||
foreach ($commits as $commit) {
|
|
||||||
$sha = $commit['sha'] ?? null;
|
|
||||||
$commitDate = $commit['commit']['committer']['date'] ?? null;
|
|
||||||
|
|
||||||
if (! $sha) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$commitResponse = Http::withHeaders($headers)->timeout(30)->get("https://api.github.com/repos/{$this->githubRepo}/commits/{$sha}");
|
|
||||||
|
|
||||||
if (! $commitResponse->successful()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$commitData = $commitResponse->json();
|
|
||||||
$files = $commitData['files'] ?? [];
|
|
||||||
|
|
||||||
foreach ($files as $file) {
|
|
||||||
$filename = $file['filename'] ?? '';
|
|
||||||
|
|
||||||
if (! str_ends_with(strtolower((string) $filename), '.sql')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = basename((string) $filename);
|
|
||||||
$sqlFiles[] = [
|
|
||||||
'name' => $name,
|
|
||||||
'url' => $file['raw_url'] ?? "https://raw.githubusercontent.com/{$this->githubRepo}/{$sha}/{$filename}",
|
|
||||||
'sha' => $sha,
|
|
||||||
'date' => $commitDate,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sqlFiles !== []) {
|
|
||||||
usort($sqlFiles, fn ($a, $b) => strcmp($a['name'], $b['name']));
|
|
||||||
|
|
||||||
return $sqlFiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::warning('[EmulatorSql] Could not fetch recent SQL files', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function downloadAndRunSql(array $file): array
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$response = Http::timeout(60)->get($file['url']);
|
|
||||||
|
|
||||||
if (! $response->successful()) {
|
|
||||||
return ['success' => false, 'error' => 'Download mislukt'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = $this->cleanSql($response->body());
|
|
||||||
|
|
||||||
if (in_array(trim($sql), ['', '0'], true)) {
|
|
||||||
return ['success' => true, 'message' => 'Lege SQL file overgeslagen'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$host = setting('emulator_database_host', config('database.connections.emulator.host', '127.0.0.1'));
|
|
||||||
$port = setting('emulator_database_port', config('database.connections.emulator.port', '3306'));
|
|
||||||
$name = setting('emulator_database_name', config('database.connections.emulator.database', ''));
|
|
||||||
$username = setting('emulator_database_username', config('database.connections.emulator.username', ''));
|
|
||||||
$password = setting('emulator_database_password', config('database.connections.emulator.password', ''));
|
|
||||||
|
|
||||||
if (empty($name) || empty($username)) {
|
|
||||||
return ['success' => false, 'error' => 'Emulator database niet geconfigureerd'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo = new \PDO(
|
|
||||||
"mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4",
|
|
||||||
$username,
|
|
||||||
$password,
|
|
||||||
[\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION],
|
|
||||||
);
|
|
||||||
|
|
||||||
$statements = $this->splitSqlStatements($sql);
|
|
||||||
$successCount = 0;
|
|
||||||
$skipCount = 0;
|
|
||||||
|
|
||||||
foreach ($statements as $statement) {
|
|
||||||
$statement = trim((string) $statement);
|
|
||||||
if ($statement === '' || $statement === '0') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo->exec($statement);
|
|
||||||
$successCount++;
|
|
||||||
} catch (\PDOException $e) {
|
|
||||||
if ($this->isDuplicateError($e)) {
|
|
||||||
$skipCount++;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Log::warning('[SQL Update] Statement error: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('[SQL Update] Successfully ran: ' . $file['name'] . " ({$successCount} statements, {$skipCount} skipped)");
|
|
||||||
|
|
||||||
return ['success' => true, 'message' => "SQL uitgevoerd ({$successCount} statements, {$skipCount} duplicate)"];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[SQL Update] Failed: ' . $file['name'], ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return ['success' => false, 'error' => $e->getMessage()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function cleanSql(string $sql): string
|
|
||||||
{
|
|
||||||
$sql = preg_replace('/--.*$/m', '', $sql);
|
|
||||||
$sql = preg_replace('/#.*$/m', '', (string) $sql);
|
|
||||||
$sql = preg_replace('/SET FOREIGN_KEY_CHECKS.*?;/i', '', (string) $sql);
|
|
||||||
|
|
||||||
return preg_replace('/SET @@SESSION.SQL_MODE.*?;/i', '', (string) $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function splitSqlStatements(string $sql): array
|
|
||||||
{
|
|
||||||
$parts = explode(';', $sql);
|
|
||||||
|
|
||||||
return array_filter($parts, fn ($part) => trim((string) $part) !== '');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isDuplicateError(\PDOException $e): bool
|
|
||||||
{
|
|
||||||
$message = strtolower($e->getMessage());
|
|
||||||
$patterns = [
|
|
||||||
'duplicate entry',
|
|
||||||
"table '",
|
|
||||||
'already exists',
|
|
||||||
"can't create",
|
|
||||||
'duplicate key',
|
|
||||||
'duplicate index',
|
|
||||||
'error 1061',
|
|
||||||
'error 1062',
|
|
||||||
'error 1826',
|
|
||||||
];
|
|
||||||
|
|
||||||
return array_any($patterns, fn ($pattern) => str_contains($message, (string) $pattern));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use App\Models\Miscellaneous\WebsiteSetting;
|
|
||||||
use App\Services\Emulator\EmulatorBackupService;
|
|
||||||
use App\Services\Emulator\EmulatorBuildService;
|
|
||||||
use App\Services\Emulator\EmulatorJarService;
|
|
||||||
use App\Services\Emulator\EmulatorSourceService;
|
|
||||||
use App\Services\Emulator\EmulatorSqlService;
|
|
||||||
use App\Services\Emulator\EmulatorStatusService;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
class EmulatorUpdateService
|
|
||||||
{
|
|
||||||
private readonly EmulatorStatusService $statusService;
|
|
||||||
|
|
||||||
private readonly EmulatorJarService $jarService;
|
|
||||||
|
|
||||||
private readonly EmulatorSourceService $sourceService;
|
|
||||||
|
|
||||||
private readonly EmulatorBuildService $buildService;
|
|
||||||
|
|
||||||
private readonly EmulatorSqlService $sqlService;
|
|
||||||
|
|
||||||
private readonly EmulatorBackupService $backupService;
|
|
||||||
|
|
||||||
private readonly SettingsService $settings;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->statusService = new EmulatorStatusService;
|
|
||||||
$this->jarService = new EmulatorJarService;
|
|
||||||
$this->sourceService = new EmulatorSourceService;
|
|
||||||
$this->buildService = new EmulatorBuildService;
|
|
||||||
$this->sqlService = new EmulatorSqlService;
|
|
||||||
$this->backupService = new EmulatorBackupService;
|
|
||||||
$this->settings = app(SettingsService::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isConfigured(): bool
|
|
||||||
{
|
|
||||||
return $this->statusService->isConfigured();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatus(): array
|
|
||||||
{
|
|
||||||
$status = $this->statusService->getStatus();
|
|
||||||
$updateCheck = $this->jarService->checkForUpdates();
|
|
||||||
$sourceInfo = $this->sourceService->checkForUpdates();
|
|
||||||
|
|
||||||
return array_merge($status, [
|
|
||||||
'update_available' => $updateCheck['update_available'] ?? false,
|
|
||||||
'current_version' => $updateCheck['current_version'] ?? setting('emulator_version', 'N/A'),
|
|
||||||
'latest_version' => $updateCheck['latest_version'] ?? 'N/A',
|
|
||||||
'update_type' => $updateCheck['type'] ?? 'unknown',
|
|
||||||
'has_source_updates' => $sourceInfo['has_update'] ?? false,
|
|
||||||
'latest_sha' => $sourceInfo['latest_sha'] ?? null,
|
|
||||||
'latest_message' => $sourceInfo['latest_message'] ?? null,
|
|
||||||
'latest_author' => $sourceInfo['latest_author'] ?? null,
|
|
||||||
'latest_date' => $sourceInfo['latest_date'] ?? null,
|
|
||||||
'stored_sha' => $sourceInfo['stored_sha'] ?? null,
|
|
||||||
'stored_date' => $sourceInfo['stored_date'] ?? null,
|
|
||||||
'source_info' => $sourceInfo,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkForUpdates(): array
|
|
||||||
{
|
|
||||||
return $this->jarService->checkForUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkForSqlUpdates(bool $recentOnly = true): array
|
|
||||||
{
|
|
||||||
return $this->sqlService->checkForUpdates($recentOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function runSqlUpdates(): array
|
|
||||||
{
|
|
||||||
return $this->sqlService->runUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAppliedSqlUpdates(): array
|
|
||||||
{
|
|
||||||
return $this->sqlService->getAppliedUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateEmulator(): array
|
|
||||||
{
|
|
||||||
if (! $this->isConfigured()) {
|
|
||||||
return ['success' => false, 'error' => 'Geen GitHub URL geconfigureerd'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$check = $this->checkForUpdates();
|
|
||||||
|
|
||||||
if (! ($check['update_available'] ?? false)) {
|
|
||||||
if ($check['type'] === 'not_found' && ($check['source_available'] ?? false)) {
|
|
||||||
return $this->buildFromSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['success' => false, 'error' => 'Emulator is al up-to-date'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$hasSourceUpdates = ($check['has_source_updates'] ?? false) || ($check['type'] ?? '') === 'source_build';
|
|
||||||
|
|
||||||
if ($hasSourceUpdates && $this->sourceService->isSourceBuildAvailable()) {
|
|
||||||
return $this->buildFromSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($check['type'] === 'source_build') {
|
|
||||||
return $this->buildFromSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! ($check['jar_url'] ?? null)) {
|
|
||||||
return ['success' => false, 'error' => 'Geen .jar gevonden'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $this->jarService->performUpdate($check);
|
|
||||||
|
|
||||||
if ($result['success']) {
|
|
||||||
$this->runSqlUpdates();
|
|
||||||
|
|
||||||
if ($this->restartEmulator()) {
|
|
||||||
$result['restarted'] = true;
|
|
||||||
$result['message'] = ($result['message'] ?? '') . ' | 🔄 Emulator herstart';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function performUpdate(array $check): array
|
|
||||||
{
|
|
||||||
return $this->jarService->performUpdate($check);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildFromSource(bool $force = false): array
|
|
||||||
{
|
|
||||||
return $this->buildService->buildFromSource($force);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function restartEmulator(): bool
|
|
||||||
{
|
|
||||||
$serviceName = $this->settings->getOrDefault('emulator_service_name', 'emulator');
|
|
||||||
|
|
||||||
try {
|
|
||||||
Log::info('[EmulatorUpdate] Restarting emulator service: ' . $serviceName);
|
|
||||||
|
|
||||||
$result = Process::timeout(30)->run("systemctl restart {$serviceName} 2>&1");
|
|
||||||
if ($result->successful()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = Process::timeout(30)->run("service {$serviceName} restart 2>&1");
|
|
||||||
|
|
||||||
return $result->successful();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[EmulatorUpdate] Failed to restart emulator', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBackupList(): array
|
|
||||||
{
|
|
||||||
return $this->backupService->getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function restoreBackup(string $backupName): array
|
|
||||||
{
|
|
||||||
return $this->backupService->restore($backupName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInstalledVersion(): string
|
|
||||||
{
|
|
||||||
return $this->statusService->getInstalledVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInstalledJar(): ?string
|
|
||||||
{
|
|
||||||
return $this->statusService->getInstalledJar();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInstalledJarInfo(): array
|
|
||||||
{
|
|
||||||
return $this->statusService->getInstalledJarInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastSqlUpdate(): ?string
|
|
||||||
{
|
|
||||||
return setting('emulator_last_sql_update');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function debugStatus(): array
|
|
||||||
{
|
|
||||||
return Cache::remember('emulator_debug_status', 120, function () {
|
|
||||||
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
|
|
||||||
$sourceCommit = $this->settings->getOrDefault('emulator_source_commit', null);
|
|
||||||
$sourceDate = $this->settings->getOrDefault('emulator_source_date', null);
|
|
||||||
$emulatorVersion = $this->settings->getOrDefault('emulator_version', null);
|
|
||||||
$jarFiles = $this->statusService->getInstalledJarInfo();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'github_url' => $this->settings->getOrDefault('emulator_github_url', ''),
|
|
||||||
'github_repo' => $this->settings->getOrDefault('emulator_source_repo', ''),
|
|
||||||
'github_branch' => $this->settings->getOrDefault('emulator_github_branch', 'main'),
|
|
||||||
'source_repo' => $this->settings->getOrDefault('emulator_source_repo', ''),
|
|
||||||
'source_branch' => $this->settings->getOrDefault('emulator_github_branch', 'main'),
|
|
||||||
'installed_date' => $installedDate,
|
|
||||||
'installed_date_formatted' => $installedDate ? date('Y-m-d H:i:s', (int) $installedDate) : null,
|
|
||||||
'source_commit' => $sourceCommit,
|
|
||||||
'source_date' => $sourceDate,
|
|
||||||
'source_date_formatted' => $sourceDate ? date('Y-m-d H:i:s', (int) $sourceDate) : null,
|
|
||||||
'emulator_version' => $emulatorVersion,
|
|
||||||
'jar_files' => $jarFiles,
|
|
||||||
'installed_branch' => $this->settings->getOrDefault('emulator_installed_branch', null),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetInstalledDate(): void
|
|
||||||
{
|
|
||||||
WebsiteSetting::where('key', 'emulator_jar_installed_date')->delete();
|
|
||||||
WebsiteSetting::where('key', 'emulator_source_commit')->delete();
|
|
||||||
WebsiteSetting::where('key', 'emulator_source_date')->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clearAllLogs(): array
|
|
||||||
{
|
|
||||||
$cleared = [];
|
|
||||||
$paths = [
|
|
||||||
storage_path('logs') => 'Laravel Logs',
|
|
||||||
storage_path('logs/emulator.log') => 'Emulator Log',
|
|
||||||
'/tmp/emulator-update-*' => 'Emulator Update Temp',
|
|
||||||
'/tmp/nitro-switch-*' => 'Nitro Switch Logs',
|
|
||||||
'/tmp/nitro_*' => 'Nitro Temp',
|
|
||||||
'/var/www/Emulator/logs' => 'Emulator Folder Logs',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($paths as $path => $label) {
|
|
||||||
try {
|
|
||||||
if (str_contains($path, '*')) {
|
|
||||||
Process::timeout(10)->run("rm -f {$path} 2>/dev/null || true");
|
|
||||||
$cleared[] = $label;
|
|
||||||
} elseif (is_dir($path)) {
|
|
||||||
Process::timeout(10)->run("find {$path} -name '*.log' -mtime +1 -delete 2>/dev/null || true");
|
|
||||||
$cleared[] = $label;
|
|
||||||
} elseif (is_file($path)) {
|
|
||||||
@unlink($path);
|
|
||||||
$cleared[] = $label;
|
|
||||||
}
|
|
||||||
} catch (\Exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$laravelLog = storage_path('logs/laravel.log');
|
|
||||||
if (is_file($laravelLog)) {
|
|
||||||
file_put_contents($laravelLog, '');
|
|
||||||
$cleared[] = 'laravel.log';
|
|
||||||
}
|
|
||||||
} catch (\Exception) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Process::timeout(10)->run("find /tmp -name 'emulator_*' -mtime +1 -delete 2>/dev/null || true");
|
|
||||||
Process::timeout(10)->run("find /tmp -name 'nitro_*' -mtime +1 -delete 2>/dev/null || true");
|
|
||||||
Process::timeout(10)->run("find /tmp -name 'deploy_*' -mtime +1 -delete 2>/dev/null || true");
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'cleared' => $cleared,
|
|
||||||
'message' => count($cleared) . ' log locaties geleegd',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function repairEmulator(): array
|
|
||||||
{
|
|
||||||
$actions = [];
|
|
||||||
$errors = [];
|
|
||||||
|
|
||||||
Log::info('[EmulatorUpdate] Starting repair process');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$status = $this->getStatus();
|
|
||||||
|
|
||||||
if (! ($status['jar_exists'] ?? false)) {
|
|
||||||
$actions[] = 'JAR bestand ontbreekt - downloaden...';
|
|
||||||
$updateResult = $this->updateEmulator();
|
|
||||||
if (! $updateResult['success']) {
|
|
||||||
$errors[] = 'Kon JAR niet herstellen: ' . ($updateResult['error'] ?? 'Onbekende fout');
|
|
||||||
} else {
|
|
||||||
$actions[] = 'JAR bestand hersteld';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! ($status['service_running'] ?? false)) {
|
|
||||||
$actions[] = 'Emulator service niet actief - starten...';
|
|
||||||
if ($this->restartEmulator()) {
|
|
||||||
$actions[] = 'Emulator service gestart';
|
|
||||||
} else {
|
|
||||||
$errors[] = 'Kon emulator service niet starten';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sqlRepairResult = $this->sqlService->repair();
|
|
||||||
if (! empty($sqlRepairResult['actions'])) {
|
|
||||||
$actions = array_merge($actions, $sqlRepairResult['actions']);
|
|
||||||
}
|
|
||||||
if (! empty($sqlRepairResult['errors'])) {
|
|
||||||
$errors = array_merge($errors, $sqlRepairResult['errors']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($errors !== []) {
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'actions' => $actions,
|
|
||||||
'errors' => $errors,
|
|
||||||
'error' => implode('; ', $errors),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'actions' => $actions,
|
|
||||||
'message' => count($actions) . ' acties uitgevoerd',
|
|
||||||
];
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('[EmulatorUpdate] Repair exception', ['error' => $e->getMessage()]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'actions' => $actions,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function diagnoseSqlUpdates(): array
|
|
||||||
{
|
|
||||||
return $this->sqlService->diagnose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function diagnose(): array
|
|
||||||
{
|
|
||||||
$diagnosis = [
|
|
||||||
'timestamp' => now()->toIso8601String(),
|
|
||||||
'checks' => [],
|
|
||||||
'issues' => [],
|
|
||||||
'recommendations' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$status = $this->getStatus();
|
|
||||||
|
|
||||||
$diagnosis['checks']['jar_exists'] = $status['jar_exists'] ?? false;
|
|
||||||
$diagnosis['checks']['jar_files'] = $status['jar_files'] ?? [];
|
|
||||||
$diagnosis['checks']['service_running'] = $status['service_running'] ?? false;
|
|
||||||
$diagnosis['checks']['source_exists'] = $status['source_exists'] ?? false;
|
|
||||||
$diagnosis['checks']['emulator_db_connected'] = $status['emulator_db_connected'] ?? false;
|
|
||||||
$diagnosis['checks']['is_configured'] = $this->isConfigured();
|
|
||||||
$diagnosis['checks']['update_available'] = $status['update_available'] ?? false;
|
|
||||||
|
|
||||||
$sqlDiagnosis = $this->sqlService->diagnose();
|
|
||||||
$diagnosis['checks']['sql_table_exists'] = $sqlDiagnosis['table_exists'] ?? false;
|
|
||||||
$diagnosis['checks']['sql_updates_applied'] = $sqlDiagnosis['applied_count'] ?? 0;
|
|
||||||
$diagnosis['checks']['sql_pending'] = $sqlDiagnosis['pending_count'] ?? 0;
|
|
||||||
|
|
||||||
if (! ($status['jar_exists'] ?? false)) {
|
|
||||||
$diagnosis['issues'][] = 'JAR bestand ontbreekt';
|
|
||||||
$diagnosis['recommendations'][] = 'Voer emulator:update uit om de JAR te downloaden';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! ($status['service_running'] ?? false)) {
|
|
||||||
$diagnosis['issues'][] = 'Emulator service draait niet';
|
|
||||||
$diagnosis['recommendations'][] = 'Start de service met: sudo systemctl start ' . $this->settings->getOrDefault('emulator_service_name', 'emulator');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! ($status['emulator_db_connected'] ?? false)) {
|
|
||||||
$diagnosis['issues'][] = 'Emulator database niet bereikbaar';
|
|
||||||
$diagnosis['recommendations'][] = 'Controleer de database credentials in de settings';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! ($status['source_exists'] ?? false) && $this->sourceService->isSourceBuildAvailable()) {
|
|
||||||
$diagnosis['issues'][] = 'Source code niet gevonden';
|
|
||||||
$diagnosis['recommendations'][] = 'Voer emulator:update --rebuild uit om vanaf source te bouwen';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! ($sqlDiagnosis['table_exists'] ?? false)) {
|
|
||||||
$diagnosis['issues'][] = 'SQL update tabel ontbreekt';
|
|
||||||
$diagnosis['recommendations'][] = 'Reparatie zal de tabel aanmaken';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($sqlDiagnosis['pending_count'] ?? 0) > 0) {
|
|
||||||
$diagnosis['issues'][] = $sqlDiagnosis['pending_count'] . ' SQL updates pending';
|
|
||||||
$diagnosis['recommendations'][] = 'Voer reparatie uit om SQL updates toe te passen';
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$diagnosis['error'] = $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $diagnosis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
class UpdateHistoryService
|
|
||||||
{
|
|
||||||
private const string TABLE = 'update_history';
|
|
||||||
|
|
||||||
public function ensureTableExists(): void
|
|
||||||
{
|
|
||||||
if (! Schema::hasTable(self::TABLE)) {
|
|
||||||
Schema::create(self::TABLE, function ($table) {
|
|
||||||
$table->id();
|
|
||||||
$table->string('type'); // nitro, emulator, sql, config
|
|
||||||
$table->string('action'); // update, build, deploy, fix
|
|
||||||
$table->string('item')->nullable(); // filename, version, etc
|
|
||||||
$table->string('status'); // success, failed, pending
|
|
||||||
$table->text('message')->nullable();
|
|
||||||
$table->string('user')->nullable();
|
|
||||||
$table->string('ip')->nullable();
|
|
||||||
$table->timestamp('created_at')->useCurrent();
|
|
||||||
$table->index(['type', 'created_at']);
|
|
||||||
$table->index('status');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function log(string $type, string $action, ?string $item = null, string $status = 'success', ?string $message = null): void
|
|
||||||
{
|
|
||||||
$this->ensureTableExists();
|
|
||||||
|
|
||||||
DB::table(self::TABLE)->insert([
|
|
||||||
'type' => $type,
|
|
||||||
'action' => $action,
|
|
||||||
'item' => $item,
|
|
||||||
'status' => $status,
|
|
||||||
'message' => $message,
|
|
||||||
'user' => auth()->user()?->name ?? 'System',
|
|
||||||
'ip' => request()->ip(),
|
|
||||||
'created_at' => now(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRecent(int $limit = 50): array
|
|
||||||
{
|
|
||||||
$this->ensureTableExists();
|
|
||||||
|
|
||||||
return DB::table(self::TABLE)
|
|
||||||
->orderBy('created_at', 'desc')
|
|
||||||
->limit($limit)
|
|
||||||
->get()
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByType(string $type, int $limit = 50): array
|
|
||||||
{
|
|
||||||
$this->ensureTableExists();
|
|
||||||
|
|
||||||
return DB::table(self::TABLE)
|
|
||||||
->where('type', $type)
|
|
||||||
->orderBy('created_at', 'desc')
|
|
||||||
->limit($limit)
|
|
||||||
->get()
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByStatus(string $status, int $limit = 50): array
|
|
||||||
{
|
|
||||||
$this->ensureTableExists();
|
|
||||||
|
|
||||||
return DB::table(self::TABLE)
|
|
||||||
->where('status', $status)
|
|
||||||
->orderBy('created_at', 'desc')
|
|
||||||
->limit($limit)
|
|
||||||
->get()
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStats(): array
|
|
||||||
{
|
|
||||||
$this->ensureTableExists();
|
|
||||||
|
|
||||||
$total = DB::table(self::TABLE)->count();
|
|
||||||
$success = DB::table(self::TABLE)->where('status', 'success')->count();
|
|
||||||
$failed = DB::table(self::TABLE)->where('status', 'failed')->count();
|
|
||||||
|
|
||||||
$lastUpdate = DB::table(self::TABLE)
|
|
||||||
->orderBy('created_at', 'desc')
|
|
||||||
->first();
|
|
||||||
|
|
||||||
$byType = DB::table(self::TABLE)
|
|
||||||
->select('type', DB::raw('count(*) as count'))
|
|
||||||
->groupBy('type')
|
|
||||||
->pluck('count', 'type')
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'total' => $total,
|
|
||||||
'success' => $success,
|
|
||||||
'failed' => $failed,
|
|
||||||
'success_rate' => $total > 0 ? round(($success / $total) * 100) : 100,
|
|
||||||
'last_update' => $lastUpdate,
|
|
||||||
'by_type' => $byType,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHtml(): string
|
|
||||||
{
|
|
||||||
$this->ensureTableExists();
|
|
||||||
|
|
||||||
$updates = $this->getRecent(20);
|
|
||||||
$stats = $this->getStats();
|
|
||||||
|
|
||||||
$html = '<div style="font-family: -apple-system, BlinkMacSystemFont, sans-serif;">';
|
|
||||||
|
|
||||||
// Stats
|
|
||||||
$html .= '<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px;">';
|
|
||||||
$html .= '<div style="background: rgba(255,255,255,0.05); padding: 12px; border-radius: 8px; text-align: center;">';
|
|
||||||
$html .= '<div style="font-size: 24px; font-weight: 700; color: #4ade80;">' . $stats['total'] . '</div>';
|
|
||||||
$html .= '<div style="font-size: 10px; color: #64748b; text-transform: uppercase;">Totaal</div></div>';
|
|
||||||
$html .= '<div style="background: rgba(255,255,255,0.05); padding: 12px; border-radius: 8px; text-align: center;">';
|
|
||||||
$html .= '<div style="font-size: 24px; font-weight: 700; color: #4ade80;">' . $stats['success'] . '</div>';
|
|
||||||
$html .= '<div style="font-size: 10px; color: #64748b; text-transform: uppercase;">Geslaagd</div></div>';
|
|
||||||
$html .= '<div style="background: rgba(255,255,255,0.05); padding: 12px; border-radius: 8px; text-align: center;">';
|
|
||||||
$html .= '<div style="font-size: 24px; font-weight: 700; color: #f87171;">' . $stats['failed'] . '</div>';
|
|
||||||
$html .= '<div style="font-size: 10px; color: #64748b; text-transform: uppercase;">Mislukt</div></div>';
|
|
||||||
$html .= '<div style="background: rgba(255,255,255,0.05); padding: 12px; border-radius: 8px; text-align: center;">';
|
|
||||||
$html .= '<div style="font-size: 24px; font-weight: 700; color: #60a5fa;">' . $stats['success_rate'] . '%</div>';
|
|
||||||
$html .= '<div style="font-size: 10px; color: #64748b; text-transform: uppercase;">Succes</div></div>';
|
|
||||||
$html .= '</div>';
|
|
||||||
|
|
||||||
// History list
|
|
||||||
if ($updates === []) {
|
|
||||||
$html .= '<div style="text-align: center; padding: 24px; color: #64748b;">';
|
|
||||||
$html .= '<div style="font-size: 32px; margin-bottom: 8px;">📋</div>';
|
|
||||||
$html .= 'Nog geen update geschiedenis</div>';
|
|
||||||
} else {
|
|
||||||
$html .= '<div style="max-height: 400px; overflow-y: auto;">';
|
|
||||||
foreach ($updates as $update) {
|
|
||||||
$icon = match ($update->status) {
|
|
||||||
'success' => '✅',
|
|
||||||
'failed' => '❌',
|
|
||||||
'pending' => '⏳',
|
|
||||||
default => '⚪'
|
|
||||||
};
|
|
||||||
|
|
||||||
$typeColor = match ($update->type) {
|
|
||||||
'nitro' => '#4ade80',
|
|
||||||
'emulator' => '#60a5fa',
|
|
||||||
'sql' => '#fbbf24',
|
|
||||||
'config' => '#a78bfa',
|
|
||||||
default => '#94a3b8'
|
|
||||||
};
|
|
||||||
|
|
||||||
$time = Carbon::parse($update->created_at)->diffForHumans();
|
|
||||||
|
|
||||||
$html .= '<div style="display: flex; align-items: center; gap: 12px; padding: 10px 12px; background: rgba(255,255,255,0.03); border-radius: 8px; margin-bottom: 6px;">';
|
|
||||||
$html .= '<div style="font-size: 16px;">' . $icon . '</div>';
|
|
||||||
$html .= '<div style="flex: 1;">';
|
|
||||||
$html .= '<div style="display: flex; align-items: center; gap: 8px;">';
|
|
||||||
$html .= '<span style="background: ' . $typeColor . '20; color: ' . $typeColor . '; padding: 2px 8px; border-radius: 12px; font-size: 10px; font-weight: 600; text-transform: uppercase;">' . e($update->type) . '</span>';
|
|
||||||
$html .= '<span style="color: #e2e8f0; font-size: 13px;">' . e($update->action) . '</span>';
|
|
||||||
if ($update->item) {
|
|
||||||
$html .= '<span style="color: #94a3b8; font-size: 12px;">' . e($update->item) . '</span>';
|
|
||||||
}
|
|
||||||
$html .= '</div>';
|
|
||||||
if ($update->message) {
|
|
||||||
$html .= '<div style="color: #64748b; font-size: 11px; margin-top: 2px;">' . e($update->message) . '</div>';
|
|
||||||
}
|
|
||||||
$html .= '</div>';
|
|
||||||
$html .= '<div style="text-align: right;">';
|
|
||||||
$html .= '<div style="color: #64748b; font-size: 10px;">' . e($update->user) . '</div>';
|
|
||||||
$html .= '<div style="color: #475569; font-size: 9px;">' . e($time) . '</div>';
|
|
||||||
$html .= '</div>';
|
|
||||||
$html .= '</div>';
|
|
||||||
}
|
|
||||||
$html .= '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $html . '</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,15 +17,15 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'paths' => ['api/*', 'sanctum/csrf-cookie', 'client/*'],
|
'paths' => ['api/*', 'sanctum/csrf-cookie', 'client/*', 'imaging/*'],
|
||||||
|
|
||||||
'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''),
|
'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''),
|
||||||
|
|
||||||
'allowed_origins' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', ''))), fn ($v) => $v !== ''),
|
'allowed_origins' => ['*'], // Zorgt ervoor dat alle origins (zoals je client/CMS) de imaging mogen inladen
|
||||||
|
|
||||||
'allowed_origins_patterns' => [],
|
'allowed_origins_patterns' => [],
|
||||||
|
|
||||||
'allowed_headers' => ['Content-Type', 'X-Requested-With', 'Authorization', 'X-XSRF-TOKEN'],
|
'allowed_headers' => ['*'], // Flexibel instellen zodat er geen headers geblokkeerd worden
|
||||||
|
|
||||||
'exposed_headers' => [],
|
'exposed_headers' => [],
|
||||||
|
|
||||||
@@ -33,4 +33,4 @@ return [
|
|||||||
|
|
||||||
'supports_credentials' => true,
|
'supports_credentials' => true,
|
||||||
|
|
||||||
];
|
];
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo " AtomCMS Deployment Script"
|
|
||||||
echo "=========================================="
|
|
||||||
|
|
||||||
PROJECT_DIR="/var/www/atomcms"
|
|
||||||
WEB_USER="www-data"
|
|
||||||
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
|
|
||||||
echo "[1/8] Installing PHP dependencies..."
|
|
||||||
composer install --no-dev --optimize-autoloader --no-interaction
|
|
||||||
|
|
||||||
echo "[2/8] Installing JS dependencies..."
|
|
||||||
npm install --production=false
|
|
||||||
|
|
||||||
echo "[3/8] Building frontend assets..."
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
echo "[4/8] Running database migrations..."
|
|
||||||
php artisan migrate --force
|
|
||||||
|
|
||||||
echo "[5/8] Clearing all caches..."
|
|
||||||
php artisan optimize:clear
|
|
||||||
|
|
||||||
echo "[6/8] Caching configuration, routes, and views..."
|
|
||||||
php artisan config:cache
|
|
||||||
php artisan route:cache
|
|
||||||
php artisan view:cache
|
|
||||||
|
|
||||||
echo "[7/8] Fixing file permissions..."
|
|
||||||
chown -R "$WEB_USER":"$WEB_USER" storage bootstrap/cache public/build
|
|
||||||
chmod -R 775 storage bootstrap/cache public/build
|
|
||||||
|
|
||||||
echo "[8/8] Clearing OPcache..."
|
|
||||||
php -r "if (function_exists('opcache_reset')) { opcache_reset(); echo 'OPcache cleared'.PHP_EOL; } else { echo 'OPcache not enabled'.PHP_EOL; }"
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo " Deployment complete!"
|
|
||||||
echo "=========================================="
|
|
||||||
@@ -1,351 +1,306 @@
|
|||||||
{
|
{
|
||||||
"commandocentrum.live_status": "Live Status",
|
"commandocentrum.live_status": "Live Status",
|
||||||
"commandocentrum.live_status_desc": "Real-time hotel statistics",
|
"commandocentrum.live_status_desc": "Real-time hotel statistics",
|
||||||
"commandocentrum.online": "Online",
|
"commandocentrum.online": "Online",
|
||||||
"commandocentrum.emulator": "Emulator",
|
"commandocentrum.emulator": "Emulator",
|
||||||
"commandocentrum.database": "Database",
|
"commandocentrum.database": "Database",
|
||||||
"commandocentrum.load": "Load",
|
"commandocentrum.load": "Load",
|
||||||
"commandocentrum.server_info": "Server Information",
|
"commandocentrum.server_info": "Server Information",
|
||||||
"commandocentrum.server_info_desc": "Detailed server status",
|
"commandocentrum.server_info_desc": "Detailed server status",
|
||||||
"commandocentrum.php_laravel": "PHP & Laravel",
|
"commandocentrum.php_laravel": "PHP & Laravel",
|
||||||
"commandocentrum.memory_disk": "Memory & Disk",
|
"commandocentrum.memory_disk": "Memory & Disk",
|
||||||
"commandocentrum.memory": "Memory",
|
"commandocentrum.memory": "Memory",
|
||||||
"commandocentrum.disk": "Disk",
|
"commandocentrum.disk": "Disk",
|
||||||
"commandocentrum.uptime": "Uptime",
|
"commandocentrum.uptime": "Uptime",
|
||||||
"commandocentrum.system_health": "System Health",
|
"commandocentrum.system_health": "System Health",
|
||||||
"commandocentrum.system_health_desc": "Automatic system diagnostics",
|
"commandocentrum.system_health_desc": "Automatic system diagnostics",
|
||||||
"commandocentrum.refresh": "Refresh",
|
"commandocentrum.refresh": "Refresh",
|
||||||
"commandocentrum.healthy": "Healthy",
|
"commandocentrum.healthy": "Healthy",
|
||||||
"commandocentrum.warnings": "Warnings",
|
"commandocentrum.warnings": "Warnings",
|
||||||
"commandocentrum.errors": "Errors",
|
"commandocentrum.errors": "Errors",
|
||||||
"commandocentrum.system_status": "System Status",
|
"commandocentrum.system_status": "System Status",
|
||||||
"commandocentrum.critical_issues": "Critical Issues",
|
"commandocentrum.critical_issues": "Critical Issues",
|
||||||
"commandocentrum.hotel_status": "Hotel Status",
|
"commandocentrum.hotel_status": "Hotel Status",
|
||||||
"commandocentrum.hotel_status_desc": "Emulator and Nitro status",
|
"commandocentrum.hotel_status_desc": "Emulator and Nitro status",
|
||||||
"commandocentrum.hotel_alert": "Hotel Alert",
|
"commandocentrum.hotel_alert": "Hotel Alert",
|
||||||
"commandocentrum.hotel_alert_desc": "Send a message to all online users",
|
"commandocentrum.hotel_alert_desc": "Send a message to all online users",
|
||||||
"commandocentrum.send_alert": "Send Alert",
|
"commandocentrum.send_alert": "Send Alert",
|
||||||
"commandocentrum.alert_message_placeholder": "Type your alert message here...",
|
"commandocentrum.alert_message_placeholder": "Type your alert message here...",
|
||||||
"commandocentrum.emulator_logs": "Emulator Logs",
|
"commandocentrum.emulator_logs": "Emulator Logs",
|
||||||
"commandocentrum.emulator_logs_desc": "Live emulator log viewer",
|
"commandocentrum.emulator_logs_desc": "Live emulator log viewer",
|
||||||
"commandocentrum.emulator_control": "Emulator Control",
|
"commandocentrum.emulator_control": "Emulator Control",
|
||||||
"commandocentrum.emulator_control_desc": "Full emulator control",
|
"commandocentrum.emulator_control_desc": "Full emulator control",
|
||||||
"commandocentrum.start": "Start",
|
"commandocentrum.start": "Start",
|
||||||
"commandocentrum.stop": "Stop",
|
"commandocentrum.stop": "Stop",
|
||||||
"commandocentrum.restart": "Restart",
|
"commandocentrum.restart": "Restart",
|
||||||
"commandocentrum.check": "Check",
|
"commandocentrum.check": "Check",
|
||||||
"commandocentrum.version": "Version",
|
"commandocentrum.version": "Version",
|
||||||
"commandocentrum.service": "Service",
|
"commandocentrum.service": "Service",
|
||||||
"commandocentrum.status": "Status",
|
"commandocentrum.status": "Status",
|
||||||
"commandocentrum.emulator_updates": "Emulator Updates",
|
"commandocentrum.emulator_updates_desc": "Configure and update the emulator",
|
||||||
"commandocentrum.emulator_updates_desc": "Configure and update the emulator",
|
"commandocentrum.build": "Build",
|
||||||
"commandocentrum.check_updates": "Check Updates",
|
"commandocentrum.save": "Save",
|
||||||
"commandocentrum.build": "Build",
|
"commandocentrum.github_url": "GitHub URL",
|
||||||
"commandocentrum.sql_updates": "SQL Updates",
|
"commandocentrum.jar_direct_url": "JAR Direct URL",
|
||||||
"commandocentrum.save": "Save",
|
"commandocentrum.jar_path": "JAR Path",
|
||||||
"commandocentrum.github_url": "GitHub URL",
|
"commandocentrum.source_repo": "Source Repo",
|
||||||
"commandocentrum.jar_direct_url": "JAR Direct URL",
|
"commandocentrum.source_path": "Source Path",
|
||||||
"commandocentrum.jar_path": "JAR Path",
|
"commandocentrum.branch": "Branch",
|
||||||
"commandocentrum.source_repo": "Source Repo",
|
"commandocentrum.db_host": "DB Host",
|
||||||
"commandocentrum.source_path": "Source Path",
|
"commandocentrum.db_name": "DB Name",
|
||||||
"commandocentrum.branch": "Branch",
|
"commandocentrum.service_name": "Service Name",
|
||||||
"commandocentrum.db_host": "DB Host",
|
"commandocentrum.emulator_backups_desc": "View and restore emulator backups",
|
||||||
"commandocentrum.db_name": "DB Name",
|
"commandocentrum.restore": "Restore",
|
||||||
"commandocentrum.service_name": "Service Name",
|
"commandocentrum.nitro_client": "Nitro Client",
|
||||||
"commandocentrum.emulator_backups": "Emulator Backups",
|
"commandocentrum.clothing_sync": "Clothing Sync",
|
||||||
"commandocentrum.emulator_backups_desc": "View and restore emulator backups",
|
"commandocentrum.clothing_sync_desc": "Sync catalog clothing from FigureMap",
|
||||||
"commandocentrum.no_backups": "No backups available yet",
|
"commandocentrum.sync": "Sync",
|
||||||
"commandocentrum.backups_auto": "Backups are automatically created on every emulator update",
|
"commandocentrum.clothing_items": "Clothing Items",
|
||||||
"commandocentrum.restore": "Restore",
|
"commandocentrum.notifications": "Notifications",
|
||||||
"commandocentrum.nitro_client": "Nitro Client",
|
"commandocentrum.notifications_desc": "Email and Discord alerts",
|
||||||
"commandocentrum.nitro_client_desc": "Configure and update Nitro",
|
"commandocentrum.test_discord": "Test Discord",
|
||||||
"commandocentrum.auto_detect": "Auto Detect",
|
"commandocentrum.email_notifications": "Email Notifications",
|
||||||
"commandocentrum.generate_configs": "Generate Configs",
|
"commandocentrum.email_address": "Email Address",
|
||||||
"commandocentrum.client_path": "Client Path",
|
"commandocentrum.discord_notifications": "Discord Notifications",
|
||||||
"commandocentrum.renderer_path": "Renderer Path",
|
"commandocentrum.webhook_url": "Webhook URL",
|
||||||
"commandocentrum.build_path": "Build Path",
|
"commandocentrum.discord_ranks": "Ranks that receive Discord notifications",
|
||||||
"commandocentrum.webroot": "Webroot",
|
"commandocentrum.discord_ranks_helper": "Leave empty for staff only (min_staff_rank)",
|
||||||
"commandocentrum.site_url": "Site URL",
|
"commandocentrum.social_login": "Social Login (v1.4)",
|
||||||
"commandocentrum.auto_updates": "Automatic Updates",
|
"commandocentrum.social_login_desc": "Enable social login providers",
|
||||||
"commandocentrum.auto_updates_desc": "Configure automatic updates",
|
"commandocentrum.google_login": "Google Login",
|
||||||
"commandocentrum.enable_auto_updates": "Enable Automatic Updates",
|
"commandocentrum.google_login_helper": "Allow users to login with Google",
|
||||||
"commandocentrum.schedule": "Schedule (HH:MM)",
|
"commandocentrum.google_client_id": "Google Client ID",
|
||||||
"commandocentrum.days": "Days (0-6)",
|
"commandocentrum.google_client_id_helper": "From Google Cloud Console",
|
||||||
"commandocentrum.clothing_sync": "Clothing Sync",
|
"commandocentrum.google_client_secret": "Google Client Secret",
|
||||||
"commandocentrum.clothing_sync_desc": "Sync catalog clothing from FigureMap",
|
"commandocentrum.discord_login": "Discord Login",
|
||||||
"commandocentrum.sync": "Sync",
|
"commandocentrum.discord_login_helper": "Allow users to login with Discord",
|
||||||
"commandocentrum.clothing_items": "Clothing Items",
|
"commandocentrum.discord_client_id": "Discord Client ID",
|
||||||
"commandocentrum.notifications": "Notifications",
|
"commandocentrum.discord_client_id_helper": "From Discord Developer Portal",
|
||||||
"commandocentrum.notifications_desc": "Email and Discord alerts",
|
"commandocentrum.discord_client_secret": "Discord Client Secret",
|
||||||
"commandocentrum.test_discord": "Test Discord",
|
"commandocentrum.github_login": "GitHub Login",
|
||||||
"commandocentrum.email_notifications": "Email Notifications",
|
"commandocentrum.github_login_helper": "Allow users to login with GitHub",
|
||||||
"commandocentrum.email_address": "Email Address",
|
"commandocentrum.github_client_id": "GitHub Client ID",
|
||||||
"commandocentrum.discord_notifications": "Discord Notifications",
|
"commandocentrum.github_client_id_helper": "From GitHub Developer Settings",
|
||||||
"commandocentrum.webhook_url": "Webhook URL",
|
"commandocentrum.github_client_secret": "GitHub Client Secret",
|
||||||
"commandocentrum.discord_ranks": "Ranks that receive Discord notifications",
|
"commandocentrum.staff_activity": "Staff Activity Log",
|
||||||
"commandocentrum.discord_ranks_helper": "Leave empty for staff only (min_staff_rank)",
|
"commandocentrum.staff_activity_desc": "Recent staff activities in the housekeeping (v1.2)",
|
||||||
"commandocentrum.update_history": "Update History",
|
"commandocentrum.recent_staff_activities": "Recent Staff Activities",
|
||||||
"commandocentrum.update_history_desc": "Latest system updates",
|
"commandocentrum.last_20_actions": "Last 20 actions",
|
||||||
"commandocentrum.no_updates_found": "No updates found",
|
"commandocentrum.no_staff_activities": "No staff activities recorded yet.",
|
||||||
"commandocentrum.social_login": "Social Login (v1.4)",
|
"commandocentrum.staff_actions_auto": "Staff actions will appear here automatically.",
|
||||||
"commandocentrum.social_login_desc": "Enable social login providers",
|
"commandocentrum.error_loading_activities": "Error loading staff activities",
|
||||||
"commandocentrum.google_login": "Google Login",
|
"commandocentrum.run_migrations": "Make sure to run: php artisan migrate",
|
||||||
"commandocentrum.google_login_helper": "Allow users to login with Google",
|
"commandocentrum.just_now": "Just now",
|
||||||
"commandocentrum.google_client_id": "Google Client ID",
|
"commandocentrum.minutes_ago": "m ago",
|
||||||
"commandocentrum.google_client_id_helper": "From Google Cloud Console",
|
"commandocentrum.hours_ago": "h ago",
|
||||||
"commandocentrum.google_client_secret": "Google Client Secret",
|
"commandocentrum.days_ago": "d ago",
|
||||||
"commandocentrum.discord_login": "Discord Login",
|
"commandocentrum.success": "Success",
|
||||||
"commandocentrum.discord_login_helper": "Allow users to login with Discord",
|
"commandocentrum.error": "Error",
|
||||||
"commandocentrum.discord_client_id": "Discord Client ID",
|
"commandocentrum.warning": "Warning",
|
||||||
"commandocentrum.discord_client_id_helper": "From Discord Developer Portal",
|
"commandocentrum.info": "Info",
|
||||||
"commandocentrum.discord_client_secret": "Discord Client Secret",
|
"commandocentrum.emulator_started": "Emulator started!",
|
||||||
"commandocentrum.github_login": "GitHub Login",
|
"commandocentrum.emulator_start_failed": "Could not start emulator",
|
||||||
"commandocentrum.github_login_helper": "Allow users to login with GitHub",
|
"commandocentrum.emulator_stopped": "Emulator stopped!",
|
||||||
"commandocentrum.github_client_id": "GitHub Client ID",
|
"commandocentrum.emulator_stop_failed": "Could not stop emulator",
|
||||||
"commandocentrum.github_client_id_helper": "From GitHub Developer Settings",
|
"commandocentrum.emulator_restarted": "Emulator restarted!",
|
||||||
"commandocentrum.github_client_secret": "GitHub Client Secret",
|
"commandocentrum.emulator_restart_failed": "Could not restart emulator",
|
||||||
"commandocentrum.staff_activity": "Staff Activity Log",
|
"commandocentrum.emulator_online": "Emulator is online and responding!",
|
||||||
"commandocentrum.staff_activity_desc": "Recent staff activities in the housekeeping (v1.2)",
|
"commandocentrum.emulator_unreachable": "Emulator is not reachable via RCON",
|
||||||
"commandocentrum.recent_staff_activities": "Recent Staff Activities",
|
"commandocentrum.emulator_settings_saved": "Emulator settings saved!",
|
||||||
"commandocentrum.last_20_actions": "Last 20 actions",
|
"commandocentrum.alerts_saved": "Notifications saved!",
|
||||||
"commandocentrum.no_staff_activities": "No staff activities recorded yet.",
|
"commandocentrum.test_sent": "Test message sent!",
|
||||||
"commandocentrum.staff_actions_auto": "Staff actions will appear here automatically.",
|
"commandocentrum.webhook_empty": "Webhook URL is empty",
|
||||||
"commandocentrum.error_loading_activities": "Error loading staff activities",
|
"commandocentrum.diagnostics_refreshed": "Diagnostics refreshed",
|
||||||
"commandocentrum.run_migrations": "Make sure to run: php artisan migrate",
|
"commandocentrum.unknown": "Unknown",
|
||||||
"commandocentrum.just_now": "Just now",
|
"commandocentrum.not_applicable": "N/A",
|
||||||
"commandocentrum.minutes_ago": "m ago",
|
"commandocentrum.offline": "Offline",
|
||||||
"commandocentrum.hours_ago": "h ago",
|
"commandocentrum.active": "Active",
|
||||||
"commandocentrum.days_ago": "d ago",
|
"commandocentrum.inactive": "Inactive",
|
||||||
"commandocentrum.success": "Success",
|
"commandocentrum.not_found": "Not found",
|
||||||
"commandocentrum.error": "Error",
|
"commandocentrum.ok": "OK",
|
||||||
"commandocentrum.warning": "Warning",
|
"commandocentrum.missing": "Missing",
|
||||||
"commandocentrum.info": "Info",
|
"commandocentrum.jars": "JARs",
|
||||||
"commandocentrum.emulator_started": "Emulator started!",
|
"commandocentrum.source": "Source",
|
||||||
"commandocentrum.emulator_start_failed": "Could not start emulator",
|
"commandocentrum.method": "Method",
|
||||||
"commandocentrum.emulator_stopped": "Emulator stopped!",
|
"commandocentrum.jar_download_restart": "JAR Download & Restart",
|
||||||
"commandocentrum.emulator_stop_failed": "Could not stop emulator",
|
"commandocentrum.maven_build_restart": "Maven Build & Restart",
|
||||||
"commandocentrum.emulator_restarted": "Emulator restarted!",
|
"commandocentrum.manual_download": "Manual: Download JAR from GitHub",
|
||||||
"commandocentrum.emulator_restart_failed": "Could not restart emulator",
|
"commandocentrum.maven_pom": "Maven (pom.xml)",
|
||||||
"commandocentrum.emulator_online": "Emulator is online and responding!",
|
"commandocentrum.no_pom": "No pom.xml",
|
||||||
"commandocentrum.emulator_unreachable": "Emulator is not reachable via RCON",
|
"commandocentrum.update_available": "Update available",
|
||||||
"commandocentrum.building_emulator": "Emulator is being built from source...",
|
"commandocentrum.up_to_date": "Up-to-date",
|
||||||
"commandocentrum.emulator_built": "Emulator built!",
|
"commandocentrum.update": "Update",
|
||||||
"commandocentrum.build_failed": "Build failed",
|
"commandocentrum.rebuild": "Rebuild",
|
||||||
"commandocentrum.configure_github_url": "Please configure the Emulator GitHub URL first",
|
"commandocentrum.latest": "Latest",
|
||||||
"commandocentrum.maven_not_installed": "Maven (mvn) is not installed - cannot build",
|
"commandocentrum.remote": "Remote",
|
||||||
"commandocentrum.building_maven": "Building emulator with Maven...",
|
"commandocentrum.local": "Local",
|
||||||
"commandocentrum.build_success_jar": "Build successful! JAR moved to :jar. Restart the emulator.",
|
"commandocentrum.client": "Client",
|
||||||
"commandocentrum.build_success": "Build successful! Restart the emulator.",
|
"commandocentrum.renderer": "Renderer",
|
||||||
"commandocentrum.build_failed_logs": "Build failed - check logs",
|
"commandocentrum.webroot_status": "Webroot",
|
||||||
"commandocentrum.no_pom_xml": "No pom.xml found - cannot build from source",
|
"commandocentrum.rank": "Rank",
|
||||||
"commandocentrum.sql_applied": "SQL updates applied!",
|
"radio.title": "Radio",
|
||||||
"commandocentrum.update_complete": "Update check completed",
|
"radio.music": "Music",
|
||||||
"commandocentrum.emulator_settings_saved": "Emulator settings saved!",
|
"radio.loading": "Loading...",
|
||||||
"commandocentrum.nitro_updated": "Nitro updated! Build again with \"Build\" button.",
|
"radio.navigation_label": "Radio",
|
||||||
"commandocentrum.nitro_up_to_date": "Nitro is already up-to-date!",
|
"radio.setup_page_title": "Radio Setup",
|
||||||
"commandocentrum.building_nitro": "Building Nitro...",
|
"radio.setup_page_subtitle": "Configure your radio system in one go",
|
||||||
"commandocentrum.nitro_build_success": "Nitro build successful!",
|
"radio.setup.success_title": "Radio Installed!",
|
||||||
"commandocentrum.nitro_build_warning": "Build started - check manually",
|
"radio.setup.success_body": "Radio system has been successfully installed and configured!",
|
||||||
"commandocentrum.valid_url_required": "Please enter a valid URL (e.g. https://epicnabbo.nl)",
|
"radio.setup.error_title": "Installation Failed",
|
||||||
"commandocentrum.configs_generated": "Configs generated & existing settings preserved!",
|
"radio.setup.error_body": "An error occurred: :message",
|
||||||
"commandocentrum.config_generated_warning": "Config generated (check manually)",
|
"radio.setup.button_label": "Install Everything",
|
||||||
"commandocentrum.paths_detected": "Paths detected and saved!",
|
"radio.setup.modal_heading": "Install Radio?",
|
||||||
"commandocentrum.nitro_settings_saved": "Nitro settings saved!",
|
"radio.setup.modal_description": "This will configure all radio settings with default values.",
|
||||||
"commandocentrum.auto_update_saved": "Auto update settings saved!",
|
"radio.setup.modal_submit": "Yes, install!",
|
||||||
"commandocentrum.alerts_saved": "Notifications saved!",
|
"radio.setup.tooltip": "Install the complete radio system",
|
||||||
"commandocentrum.test_sent": "Test message sent!",
|
"radio.setup_complete": "✅ Installation Complete!",
|
||||||
"commandocentrum.webhook_empty": "Webhook URL is empty",
|
"radio.what_gets_configured": "What gets configured?",
|
||||||
"commandocentrum.diagnostics_refreshed": "Diagnostics refreshed",
|
"radio.radio_stream": "Radio Stream",
|
||||||
"commandocentrum.unknown": "Unknown",
|
"radio.radio_stream_desc": "Set your stream URL with support for SHOUTcast, Icecast, AzureCast and other streaming platforms.",
|
||||||
"commandocentrum.not_applicable": "N/A",
|
"radio.points_system": "Points System",
|
||||||
"commandocentrum.offline": "Offline",
|
"radio.points_system_desc": "Let users earn points by listening, requesting songs and participating in contests.",
|
||||||
"commandocentrum.active": "Active",
|
"radio.community_features": "Community Features",
|
||||||
"commandocentrum.inactive": "Inactive",
|
"radio.community_features_desc": "Shouts, song requests, DJ applications and more community interactions.",
|
||||||
"commandocentrum.not_found": "Not found",
|
"radio.dj_management": "DJ Management",
|
||||||
"commandocentrum.ok": "OK",
|
"radio.dj_management_desc": "DJ ranks, schedule, auto-detection and Sambroadcaster/Virtual DJ integration.",
|
||||||
"commandocentrum.missing": "Missing",
|
"radio.monitoring": "Stream Monitoring",
|
||||||
"commandocentrum.jars": "JARs",
|
"radio.monitoring_desc": "Monitor your stream uptime with real-time monitoring.",
|
||||||
"commandocentrum.source": "Source",
|
"radio.display_options": "Display Options",
|
||||||
"commandocentrum.method": "Method",
|
"radio.display_options_desc": "Widget, player styles, colors and custom CSS/JS.",
|
||||||
"commandocentrum.jar_download_restart": "JAR Download & Restart",
|
"radio.default_settings": "Default Settings",
|
||||||
"commandocentrum.maven_build_restart": "Maven Build & Restart",
|
"radio.radio_label": "Radio",
|
||||||
"commandocentrum.manual_download": "Manual: Download JAR from GitHub",
|
"radio.enabled": "Enabled",
|
||||||
"commandocentrum.maven_pom": "Maven (pom.xml)",
|
"radio.points_label": "Points",
|
||||||
"commandocentrum.no_pom": "No pom.xml",
|
"radio.per_min": " per min",
|
||||||
"commandocentrum.update_available": "Update available",
|
"radio.daily_limit": "Daily limit",
|
||||||
"commandocentrum.up_to_date": "Up-to-date",
|
"radio.shouts_label": "Shouts",
|
||||||
"commandocentrum.update": "Update",
|
"radio.on": "On",
|
||||||
"commandocentrum.rebuild": "Rebuild",
|
"radio.widget": "Widget",
|
||||||
"commandocentrum.latest": "Latest",
|
"radio.global": "Global",
|
||||||
"commandocentrum.remote": "Remote",
|
"radio.dj_apps": "DJ Applications",
|
||||||
"commandocentrum.local": "Local",
|
"radio.open": "Open",
|
||||||
"commandocentrum.client": "Client",
|
"radio.monitoring_label": "Monitoring",
|
||||||
"commandocentrum.renderer": "Renderer",
|
"radio.contests_label": "Contests",
|
||||||
"commandocentrum.webroot_status": "Webroot",
|
"radio.install_radio_system": "🚀 Install Radio System",
|
||||||
"commandocentrum.rank": "Rank",
|
"radio.reset_settings": "Reset Settings",
|
||||||
"radio.title": "Radio",
|
"radio.reset_confirm": "Are you sure you want to reset all radio settings?",
|
||||||
"radio.music": "Music",
|
"radio.go_to_radio_settings": "Go to Radio Settings",
|
||||||
"radio.loading": "Loading...",
|
"radio.open_wizard": "🎯 Open Radio Wizard",
|
||||||
"radio.navigation_label": "Radio",
|
"radio.wizard_desc": "Step-by-step wizard with connection test",
|
||||||
"radio.setup_page_title": "Radio Setup",
|
"radio.wizard.title": "Radio Installation Wizard",
|
||||||
"radio.setup_page_subtitle": "Configure your radio system in one go",
|
"radio.wizard.step_short": "Step",
|
||||||
"radio.setup.success_title": "Radio Installed!",
|
"radio.wizard.step_prefix": "Step",
|
||||||
"radio.setup.success_body": "Radio system has been successfully installed and configured!",
|
"radio.wizard.of": "of",
|
||||||
"radio.setup.error_title": "Installation Failed",
|
"radio.wizard.next_step": "Next Step →",
|
||||||
"radio.setup.error_body": "An error occurred: :message",
|
"radio.wizard.previous_step": "← Previous Step",
|
||||||
"radio.setup.button_label": "Install Everything",
|
"radio.wizard.back_to_setup": "Back to setup",
|
||||||
"radio.setup.modal_heading": "Install Radio?",
|
"radio.wizard.step1_label": "Platform",
|
||||||
"radio.setup.modal_description": "This will configure all radio settings with default values.",
|
"radio.wizard.step2_label": "Stream",
|
||||||
"radio.setup.modal_submit": "Yes, install!",
|
"radio.wizard.step3_label": "API",
|
||||||
"radio.setup.tooltip": "Install the complete radio system",
|
"radio.wizard.step4_label": "Features",
|
||||||
"radio.setup_complete": "✅ Installation Complete!",
|
"radio.wizard.step5_label": "Test",
|
||||||
"radio.what_gets_configured": "What gets configured?",
|
"radio.wizard.step1_subtitle": "Choose your streaming platform",
|
||||||
"radio.radio_stream": "Radio Stream",
|
"radio.wizard.step2_title": "Stream Configuration",
|
||||||
"radio.radio_stream_desc": "Set your stream URL with support for SHOUTcast, Icecast, AzureCast and other streaming platforms.",
|
"radio.wizard.step3_title": "API Configuration",
|
||||||
"radio.points_system": "Points System",
|
"radio.wizard.step3_subtitle": "Now Playing & Listeners",
|
||||||
"radio.points_system_desc": "Let users earn points by listening, requesting songs and participating in contests.",
|
"radio.wizard.step4_title": "Configure Features",
|
||||||
"radio.community_features": "Community Features",
|
"radio.wizard.step4_subtitle": "Choose which radio features to enable",
|
||||||
"radio.community_features_desc": "Shouts, song requests, DJ applications and more community interactions.",
|
"radio.wizard.step5_title": "Test & Install",
|
||||||
"radio.dj_management": "DJ Management",
|
"radio.wizard.step5_subtitle": "Check the connection and complete the installation",
|
||||||
"radio.dj_management_desc": "DJ ranks, schedule, auto-detection and Sambroadcaster/Virtual DJ integration.",
|
"radio.wizard.platform_shoutcast": "SHOUTcast",
|
||||||
"radio.monitoring": "Stream Monitoring",
|
"radio.wizard.platform_shoutcast_desc": "For SHOUTcast servers. Auto-detection of now playing and listeners via stats endpoint.",
|
||||||
"radio.monitoring_desc": "Monitor your stream uptime with real-time monitoring.",
|
"radio.wizard.platform_icecast": "Icecast",
|
||||||
"radio.display_options": "Display Options",
|
"radio.wizard.platform_icecast_desc": "For Icecast servers. Uses status-json.xsl for auto-detection.",
|
||||||
"radio.display_options_desc": "Widget, player styles, colors and custom CSS/JS.",
|
"radio.wizard.platform_azurecast": "AzureCast",
|
||||||
"radio.default_settings": "Default Settings",
|
"radio.wizard.platform_azurecast_desc": "AzureCast hosting. Full API integration with now-playing, listeners and auto-configuration.",
|
||||||
"radio.radio_label": "Radio",
|
"radio.wizard.platform_other": "Other",
|
||||||
"radio.enabled": "Enabled",
|
"radio.wizard.platform_other_desc": "Another stream provider. Manual configuration of stream URL and API endpoints.",
|
||||||
"radio.points_label": "Points",
|
"radio.wizard.shoutcast_info_title": "SHOUTcast",
|
||||||
"radio.per_min": " per min",
|
"radio.wizard.shoutcast_info_desc": "Enter your SHOUTcast stream URL. The wizard will try to find the stats endpoint automatically.",
|
||||||
"radio.daily_limit": "Daily limit",
|
"radio.wizard.icecast_info_title": "Icecast",
|
||||||
"radio.shouts_label": "Shouts",
|
"radio.wizard.icecast_info_desc": "Enter your Icecast stream URL. The wizard uses status-json.xsl for auto-detection.",
|
||||||
"radio.on": "On",
|
"radio.wizard.azurecast_info_title": "AzureCast",
|
||||||
"radio.widget": "Widget",
|
"radio.wizard.azurecast_info_desc": "AzureCast stream URL + server configuration. The wizard configures everything via the AzureCast API.",
|
||||||
"radio.global": "Global",
|
"radio.wizard.other_info_title": "Other Stream",
|
||||||
"radio.dj_apps": "DJ Applications",
|
"radio.wizard.other_info_desc": "Enter your stream URL. You can manually configure API endpoints for now playing and listeners later.",
|
||||||
"radio.open": "Open",
|
"radio.wizard.stream_url_label": "Stream URL *",
|
||||||
"radio.monitoring_label": "Monitoring",
|
"radio.wizard.stream_url_hint": "The direct URL to your audio stream (MP3, AAC, OGG, etc.)",
|
||||||
"radio.contests_label": "Contests",
|
"radio.wizard.stream_name_label": "Stream Name",
|
||||||
"radio.install_radio_system": "🚀 Install Radio System",
|
"radio.wizard.stream_name_placeholder": "My Radio",
|
||||||
"radio.reset_settings": "Reset Settings",
|
"radio.wizard.stream_name_hint": "A name for your radio stream (optional)",
|
||||||
"radio.reset_confirm": "Are you sure you want to reset all radio settings?",
|
"radio.wizard.azurecast_section": "AzureCast Server Configuration",
|
||||||
"radio.go_to_radio_settings": "Go to Radio Settings",
|
"radio.wizard.azurecast_base_url_label": "AzureCast Base URL",
|
||||||
"radio.open_wizard": "🎯 Open Radio Wizard",
|
"radio.wizard.azurecast_base_url_hint": "The base URL of your AzureCast server. Auto-detected if left empty.",
|
||||||
"radio.wizard_desc": "Step-by-step wizard with connection test",
|
"radio.wizard.azurecast_station_id_label": "Station ID",
|
||||||
"radio.wizard.title": "Radio Installation Wizard",
|
"radio.wizard.azurecast_station_id_hint": "The station ID in AzureCast (default: 1)",
|
||||||
"radio.wizard.step_short": "Step",
|
"radio.wizard.enable_now_playing": "Enable Now Playing",
|
||||||
"radio.wizard.step_prefix": "Step",
|
"radio.wizard.now_playing_api_label": "Now Playing API URL",
|
||||||
"radio.wizard.of": "of",
|
"radio.wizard.now_playing_api_hint": "API endpoint that returns the current song. Usually auto-detected.",
|
||||||
"radio.wizard.next_step": "Next Step →",
|
"radio.wizard.enable_listeners": "Enable Listeners Counter",
|
||||||
"radio.wizard.previous_step": "← Previous Step",
|
"radio.wizard.listeners_api_label": "Listeners API URL",
|
||||||
"radio.wizard.back_to_setup": "Back to setup",
|
"radio.wizard.listeners_api_hint": "API endpoint that returns the listener count.",
|
||||||
"radio.wizard.step1_label": "Platform",
|
"radio.wizard.enable_current_dj": "Show Current DJ",
|
||||||
"radio.wizard.step2_label": "Stream",
|
"radio.wizard.detected": "detected!",
|
||||||
"radio.wizard.step3_label": "API",
|
"radio.wizard.detected_desc": "API endpoints were automatically found and filled in.",
|
||||||
"radio.wizard.step4_label": "Features",
|
"radio.wizard.not_detected": "No automatic detection",
|
||||||
"radio.wizard.step5_label": "Test",
|
"radio.wizard.not_detected_desc": "Fill in the API URLs manually or skip this step.",
|
||||||
"radio.wizard.step1_subtitle": "Choose your streaming platform",
|
"radio.wizard.section_community": "Community Features",
|
||||||
"radio.wizard.step2_title": "Stream Configuration",
|
"radio.wizard.feature_shouts": "Shouts",
|
||||||
"radio.wizard.step3_title": "API Configuration",
|
"radio.wizard.feature_shouts_desc": "Leave messages",
|
||||||
"radio.wizard.step3_subtitle": "Now Playing & Listeners",
|
"radio.wizard.feature_applications": "DJ Applications",
|
||||||
"radio.wizard.step4_title": "Configure Features",
|
"radio.wizard.feature_applications_desc": "Apply as DJ",
|
||||||
"radio.wizard.step4_subtitle": "Choose which radio features to enable",
|
"radio.wizard.feature_requests": "Song Requests",
|
||||||
"radio.wizard.step5_title": "Test & Install",
|
"radio.wizard.feature_requests_desc": "Request songs",
|
||||||
"radio.wizard.step5_subtitle": "Check the connection and complete the installation",
|
"radio.wizard.section_display": "Display",
|
||||||
"radio.wizard.platform_shoutcast": "SHOUTcast",
|
"radio.wizard.feature_widget": "Radio Widget",
|
||||||
"radio.wizard.platform_shoutcast_desc": "For SHOUTcast servers. Auto-detection of now playing and listeners via stats endpoint.",
|
"radio.wizard.feature_widget_desc": "Mini player on the site",
|
||||||
"radio.wizard.platform_icecast": "Icecast",
|
"radio.wizard.feature_widget_global": "Widget Everywhere",
|
||||||
"radio.wizard.platform_icecast_desc": "For Icecast servers. Uses status-json.xsl for auto-detection.",
|
"radio.wizard.feature_widget_global_desc": "Show on all pages",
|
||||||
"radio.wizard.platform_azurecast": "AzureCast",
|
"radio.wizard.widget_position_label": "Widget Position",
|
||||||
"radio.wizard.platform_azurecast_desc": "AzureCast hosting. Full API integration with now-playing, listeners and auto-configuration.",
|
"radio.wizard.position_bottom_right": "Bottom Right",
|
||||||
"radio.wizard.platform_other": "Other",
|
"radio.wizard.position_bottom_left": "Bottom Left",
|
||||||
"radio.wizard.platform_other_desc": "Another stream provider. Manual configuration of stream URL and API endpoints.",
|
"radio.wizard.position_top_right": "Top Right",
|
||||||
"radio.wizard.shoutcast_info_title": "SHOUTcast",
|
"radio.wizard.position_top_left": "Top Left",
|
||||||
"radio.wizard.shoutcast_info_desc": "Enter your SHOUTcast stream URL. The wizard will try to find the stats endpoint automatically.",
|
"radio.wizard.section_gamification": "Gamification",
|
||||||
"radio.wizard.icecast_info_title": "Icecast",
|
"radio.wizard.feature_points": "Points System",
|
||||||
"radio.wizard.icecast_info_desc": "Enter your Icecast stream URL. The wizard uses status-json.xsl for auto-detection.",
|
"radio.wizard.feature_points_desc": "Earn points by listening",
|
||||||
"radio.wizard.azurecast_info_title": "AzureCast",
|
"radio.wizard.feature_contests": "Contests",
|
||||||
"radio.wizard.azurecast_info_desc": "AzureCast stream URL + server configuration. The wizard configures everything via the AzureCast API.",
|
"radio.wizard.feature_contests_desc": "Organize competitions",
|
||||||
"radio.wizard.other_info_title": "Other Stream",
|
"radio.wizard.feature_giveaways": "Giveaways",
|
||||||
"radio.wizard.other_info_desc": "Enter your stream URL. You can manually configure API endpoints for now playing and listeners later.",
|
"radio.wizard.feature_giveaways_desc": "Give away prizes",
|
||||||
"radio.wizard.stream_url_label": "Stream URL *",
|
"radio.wizard.section_integrations": "Integrations",
|
||||||
"radio.wizard.stream_url_hint": "The direct URL to your audio stream (MP3, AAC, OGG, etc.)",
|
"radio.wizard.feature_discord": "Discord Notifications",
|
||||||
"radio.wizard.stream_name_label": "Stream Name",
|
"radio.wizard.feature_discord_desc": "Notifications when DJ goes live / song changes",
|
||||||
"radio.wizard.stream_name_placeholder": "My Radio",
|
"radio.wizard.discord_webhook_label": "Discord Webhook URL",
|
||||||
"radio.wizard.stream_name_hint": "A name for your radio stream (optional)",
|
"radio.wizard.discord_webhook_hint": "Create a webhook in your Discord server channel.",
|
||||||
"radio.wizard.azurecast_section": "AzureCast Server Configuration",
|
"radio.wizard.test_title": "Test Connection",
|
||||||
"radio.wizard.azurecast_base_url_label": "AzureCast Base URL",
|
"radio.wizard.test_desc": "Click Test Connection to check if your stream and APIs are reachable.",
|
||||||
"radio.wizard.azurecast_base_url_hint": "The base URL of your AzureCast server. Auto-detected if left empty.",
|
"radio.wizard.test_loading": "Testing connection...",
|
||||||
"radio.wizard.azurecast_station_id_label": "Station ID",
|
"radio.wizard.test_prompt": "Click the button to test the connection.",
|
||||||
"radio.wizard.azurecast_station_id_hint": "The station ID in AzureCast (default: 1)",
|
"radio.wizard.test_button": "Test Connection",
|
||||||
"radio.wizard.enable_now_playing": "Enable Now Playing",
|
"radio.wizard.test_retry": "Test Again",
|
||||||
"radio.wizard.now_playing_api_label": "Now Playing API URL",
|
"radio.wizard.settings_overview": "Settings Overview",
|
||||||
"radio.wizard.now_playing_api_hint": "API endpoint that returns the current song. Usually auto-detected.",
|
"radio.wizard.settings_overview_desc": "These are the settings that will be saved:",
|
||||||
"radio.wizard.enable_listeners": "Enable Listeners Counter",
|
"radio.wizard.install_confirm": "Are you sure you want to install the radio with these settings?",
|
||||||
"radio.wizard.listeners_api_label": "Listeners API URL",
|
"radio.wizard.install_button": "Install Radio",
|
||||||
"radio.wizard.listeners_api_hint": "API endpoint that returns the listener count.",
|
"radio.wizard.test_result_stream": "Stream Connection",
|
||||||
"radio.wizard.enable_current_dj": "Show Current DJ",
|
"radio.wizard.test_result_now_playing": "Now Playing",
|
||||||
"radio.wizard.detected": "detected!",
|
"radio.wizard.test_result_listeners": "Listeners",
|
||||||
"radio.wizard.detected_desc": "API endpoints were automatically found and filled in.",
|
"radio.wizard.status_success": "Success",
|
||||||
"radio.wizard.not_detected": "No automatic detection",
|
"radio.wizard.status_warning": "Warning",
|
||||||
"radio.wizard.not_detected_desc": "Fill in the API URLs manually or skip this step.",
|
"radio.wizard.status_error": "Error",
|
||||||
"radio.wizard.section_community": "Community Features",
|
"radio.wizard.status_skipped": "Skipped",
|
||||||
"radio.wizard.feature_shouts": "Shouts",
|
"radio.wizard.status_untested": "Not tested",
|
||||||
"radio.wizard.feature_shouts_desc": "Leave messages",
|
"radio.wizard.content_type": "Content-Type",
|
||||||
"radio.wizard.feature_applications": "DJ Applications",
|
"radio.wizard.http_status": "HTTP Status",
|
||||||
"radio.wizard.feature_applications_desc": "Apply as DJ",
|
"radio.wizard.song": "Song",
|
||||||
"radio.wizard.feature_requests": "Song Requests",
|
"radio.wizard.artist": "Artist",
|
||||||
"radio.wizard.feature_requests_desc": "Request songs",
|
"radio.wizard.listeners": "Listeners",
|
||||||
"radio.wizard.section_display": "Display",
|
"radio.wizard.api_url": "API URL",
|
||||||
"radio.wizard.feature_widget": "Radio Widget",
|
"radio.wizard.test_stream_ok": "Stream is reachable! You can install the radio.",
|
||||||
"radio.wizard.feature_widget_desc": "Mini player on the site",
|
"radio.wizard.test_stream_fail": "Stream is not reachable. Check the URL and try again.",
|
||||||
"radio.wizard.feature_widget_global": "Widget Everywhere",
|
"radio.wizard.test_not_run": "Not tested yet.",
|
||||||
"radio.wizard.feature_widget_global_desc": "Show on all pages",
|
"radio.wizard.test_connection_fail": "Could not run test: ",
|
||||||
"radio.wizard.widget_position_label": "Widget Position",
|
"radio.wizard.error": "Error",
|
||||||
"radio.wizard.position_bottom_right": "Bottom Right",
|
"radio.wizard.unknown_error": "Unknown error"
|
||||||
"radio.wizard.position_bottom_left": "Bottom Left",
|
|
||||||
"radio.wizard.position_top_right": "Top Right",
|
|
||||||
"radio.wizard.position_top_left": "Top Left",
|
|
||||||
"radio.wizard.section_gamification": "Gamification",
|
|
||||||
"radio.wizard.feature_points": "Points System",
|
|
||||||
"radio.wizard.feature_points_desc": "Earn points by listening",
|
|
||||||
"radio.wizard.feature_contests": "Contests",
|
|
||||||
"radio.wizard.feature_contests_desc": "Organize competitions",
|
|
||||||
"radio.wizard.feature_giveaways": "Giveaways",
|
|
||||||
"radio.wizard.feature_giveaways_desc": "Give away prizes",
|
|
||||||
"radio.wizard.section_integrations": "Integrations",
|
|
||||||
"radio.wizard.feature_discord": "Discord Notifications",
|
|
||||||
"radio.wizard.feature_discord_desc": "Notifications when DJ goes live / song changes",
|
|
||||||
"radio.wizard.discord_webhook_label": "Discord Webhook URL",
|
|
||||||
"radio.wizard.discord_webhook_hint": "Create a webhook in your Discord server channel.",
|
|
||||||
"radio.wizard.test_title": "Test Connection",
|
|
||||||
"radio.wizard.test_desc": "Click Test Connection to check if your stream and APIs are reachable.",
|
|
||||||
"radio.wizard.test_loading": "Testing connection...",
|
|
||||||
"radio.wizard.test_prompt": "Click the button to test the connection.",
|
|
||||||
"radio.wizard.test_button": "Test Connection",
|
|
||||||
"radio.wizard.test_retry": "Test Again",
|
|
||||||
"radio.wizard.settings_overview": "Settings Overview",
|
|
||||||
"radio.wizard.settings_overview_desc": "These are the settings that will be saved:",
|
|
||||||
"radio.wizard.install_confirm": "Are you sure you want to install the radio with these settings?",
|
|
||||||
"radio.wizard.install_button": "Install Radio",
|
|
||||||
"radio.wizard.test_result_stream": "Stream Connection",
|
|
||||||
"radio.wizard.test_result_now_playing": "Now Playing",
|
|
||||||
"radio.wizard.test_result_listeners": "Listeners",
|
|
||||||
"radio.wizard.status_success": "Success",
|
|
||||||
"radio.wizard.status_warning": "Warning",
|
|
||||||
"radio.wizard.status_error": "Error",
|
|
||||||
"radio.wizard.status_skipped": "Skipped",
|
|
||||||
"radio.wizard.status_untested": "Not tested",
|
|
||||||
"radio.wizard.content_type": "Content-Type",
|
|
||||||
"radio.wizard.http_status": "HTTP Status",
|
|
||||||
"radio.wizard.song": "Song",
|
|
||||||
"radio.wizard.artist": "Artist",
|
|
||||||
"radio.wizard.listeners": "Listeners",
|
|
||||||
"radio.wizard.api_url": "API URL",
|
|
||||||
"radio.wizard.test_stream_ok": "Stream is reachable! You can install the radio.",
|
|
||||||
"radio.wizard.test_stream_fail": "Stream is not reachable. Check the URL and try again.",
|
|
||||||
"radio.wizard.test_not_run": "Not tested yet.",
|
|
||||||
"radio.wizard.test_connection_fail": "Could not run test: ",
|
|
||||||
"radio.wizard.error": "Error",
|
|
||||||
"radio.wizard.unknown_error": "Unknown error"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,352 +1,307 @@
|
|||||||
{
|
{
|
||||||
"commandocentrum.live_status": "Live Status",
|
"commandocentrum.live_status": "Live Status",
|
||||||
"commandocentrum.live_status_desc": "Real-time hotel statistieken",
|
"commandocentrum.live_status_desc": "Real-time hotel statistieken",
|
||||||
"commandocentrum.online": "Online",
|
"commandocentrum.online": "Online",
|
||||||
"commandocentrum.emulator": "Emulator",
|
"commandocentrum.emulator": "Emulator",
|
||||||
"commandocentrum.database": "Database",
|
"commandocentrum.database": "Database",
|
||||||
"commandocentrum.load": "Load",
|
"commandocentrum.load": "Load",
|
||||||
"commandocentrum.server_info": "Server Informatie",
|
"commandocentrum.server_info": "Server Informatie",
|
||||||
"commandocentrum.server_info_desc": "Gedetailleerde server status",
|
"commandocentrum.server_info_desc": "Gedetailleerde server status",
|
||||||
"commandocentrum.php_laravel": "PHP & Laravel",
|
"commandocentrum.php_laravel": "PHP & Laravel",
|
||||||
"commandocentrum.memory_disk": "Memory & Disk",
|
"commandocentrum.memory_disk": "Memory & Disk",
|
||||||
"commandocentrum.memory": "Memory",
|
"commandocentrum.memory": "Memory",
|
||||||
"commandocentrum.disk": "Disk",
|
"commandocentrum.disk": "Disk",
|
||||||
"commandocentrum.uptime": "Uptime",
|
"commandocentrum.uptime": "Uptime",
|
||||||
"commandocentrum.system_health": "Systeem Gezondheid",
|
"commandocentrum.system_health": "Systeem Gezondheid",
|
||||||
"commandocentrum.system_health_desc": "Automatische systeem diagnostiek",
|
"commandocentrum.system_health_desc": "Automatische systeem diagnostiek",
|
||||||
"commandocentrum.refresh": "Vernieuwen",
|
"commandocentrum.refresh": "Vernieuwen",
|
||||||
"commandocentrum.healthy": "Gezond",
|
"commandocentrum.healthy": "Gezond",
|
||||||
"commandocentrum.warnings": "Waarschuwingen",
|
"commandocentrum.warnings": "Waarschuwingen",
|
||||||
"commandocentrum.errors": "Fouten",
|
"commandocentrum.errors": "Fouten",
|
||||||
"commandocentrum.system_status": "Systeem Status",
|
"commandocentrum.system_status": "Systeem Status",
|
||||||
"commandocentrum.critical_issues": "Kritieke Problemen",
|
"commandocentrum.critical_issues": "Kritieke Problemen",
|
||||||
"commandocentrum.hotel_status": "Hotel Status",
|
"commandocentrum.hotel_status": "Hotel Status",
|
||||||
"commandocentrum.hotel_status_desc": "Emulator en Nitro status",
|
"commandocentrum.hotel_status_desc": "Emulator en Nitro status",
|
||||||
"commandocentrum.hotel_alert": "Hotel Alert",
|
"commandocentrum.hotel_alert": "Hotel Alert",
|
||||||
"commandocentrum.hotel_alert_desc": "Stuur een bericht naar alle online gebruikers",
|
"commandocentrum.hotel_alert_desc": "Stuur een bericht naar alle online gebruikers",
|
||||||
"commandocentrum.send_alert": "Verstuur Alert",
|
"commandocentrum.send_alert": "Verstuur Alert",
|
||||||
"commandocentrum.alert_message_placeholder": "Typ hier je alert bericht...",
|
"commandocentrum.alert_message_placeholder": "Typ hier je alert bericht...",
|
||||||
"commandocentrum.emulator_logs": "Emulator Logs",
|
"commandocentrum.emulator_logs": "Emulator Logs",
|
||||||
"commandocentrum.emulator_logs_desc": "Live emulator log viewer",
|
"commandocentrum.emulator_logs_desc": "Live emulator log viewer",
|
||||||
"commandocentrum.emulator_control": "Emulator Control",
|
"commandocentrum.emulator_control": "Emulator Control",
|
||||||
"commandocentrum.emulator_control_desc": "Volledige emulator controle",
|
"commandocentrum.emulator_control_desc": "Volledige emulator controle",
|
||||||
"commandocentrum.start": "Start",
|
"commandocentrum.start": "Start",
|
||||||
"commandocentrum.stop": "Stop",
|
"commandocentrum.stop": "Stop",
|
||||||
"commandocentrum.restart": "Restart",
|
"commandocentrum.restart": "Restart",
|
||||||
"commandocentrum.check": "Check",
|
"commandocentrum.check": "Check",
|
||||||
"commandocentrum.version": "Versie",
|
"commandocentrum.version": "Versie",
|
||||||
"commandocentrum.service": "Service",
|
"commandocentrum.service": "Service",
|
||||||
"commandocentrum.status": "Status",
|
"commandocentrum.status": "Status",
|
||||||
"commandocentrum.emulator_updates": "Emulator Updates",
|
"commandocentrum.emulator_updates_desc": "Configureer en update de emulator",
|
||||||
"commandocentrum.emulator_updates_desc": "Configureer en update de emulator",
|
"commandocentrum.build": "Bouwen",
|
||||||
"commandocentrum.check_updates": "Check Updates",
|
"commandocentrum.save": "Opslaan",
|
||||||
"commandocentrum.build": "Bouwen",
|
"commandocentrum.github_url": "GitHub URL",
|
||||||
"commandocentrum.sql_updates": "SQL Updates",
|
"commandocentrum.jar_direct_url": "JAR Direct URL",
|
||||||
"commandocentrum.save": "Opslaan",
|
"commandocentrum.jar_path": "JAR Pad",
|
||||||
"commandocentrum.github_url": "GitHub URL",
|
"commandocentrum.source_repo": "Source Repo",
|
||||||
"commandocentrum.jar_direct_url": "JAR Direct URL",
|
"commandocentrum.source_path": "Source Pad",
|
||||||
"commandocentrum.jar_path": "JAR Pad",
|
"commandocentrum.branch": "Branch",
|
||||||
"commandocentrum.source_repo": "Source Repo",
|
"commandocentrum.db_host": "DB Host",
|
||||||
"commandocentrum.source_path": "Source Pad",
|
"commandocentrum.db_name": "DB Naam",
|
||||||
"commandocentrum.branch": "Branch",
|
"commandocentrum.service_name": "Service Naam",
|
||||||
"commandocentrum.db_host": "DB Host",
|
"commandocentrum.emulator_backups_desc": "Bekijk en herstel emulator backups",
|
||||||
"commandocentrum.db_name": "DB Naam",
|
"commandocentrum.restore": "Herstellen",
|
||||||
"commandocentrum.service_name": "Service Naam",
|
"commandocentrum.nitro_client": "Nitro Client",
|
||||||
"commandocentrum.emulator_backups": "Emulator Backups",
|
"commandocentrum.clothing_sync": "Kleding Sync",
|
||||||
"commandocentrum.emulator_backups_desc": "Bekijk en herstel emulator backups",
|
"commandocentrum.clothing_sync_desc": "Sync catalogus kleding uit FigureMap",
|
||||||
"commandocentrum.no_backups": "Nog geen backups beschikbaar",
|
"commandocentrum.sync": "Sync",
|
||||||
"commandocentrum.backups_auto": "Backups worden automatisch aangemaakt bij elke emulator update",
|
"commandocentrum.clothing_items": "Kleding Items",
|
||||||
"commandocentrum.restore": "Herstellen",
|
"commandocentrum.notifications": "Meldingen",
|
||||||
"commandocentrum.nitro_client": "Nitro Client",
|
"commandocentrum.notifications_desc": "E-mail en Discord alerts",
|
||||||
"commandocentrum.nitro_client_desc": "Configureer en update Nitro",
|
"commandocentrum.test_discord": "Test Discord",
|
||||||
"commandocentrum.auto_detect": "Auto Detect",
|
"commandocentrum.email_notifications": "E-mail Meldingen",
|
||||||
"commandocentrum.generate_configs": "Genereer Configs",
|
"commandocentrum.email_address": "E-mail Adres",
|
||||||
"commandocentrum.client_path": "Client Pad",
|
"commandocentrum.discord_notifications": "Discord Meldingen",
|
||||||
"commandocentrum.renderer_path": "Renderer Pad",
|
"commandocentrum.webhook_url": "Webhook URL",
|
||||||
"commandocentrum.build_path": "Build Pad",
|
"commandocentrum.discord_ranks": "Ranks die Discord notificatie krijgen",
|
||||||
"commandocentrum.webroot": "Webroot",
|
"commandocentrum.discord_ranks_helper": "Laat leeg voor alleen staff (min_staff_rank)",
|
||||||
"commandocentrum.site_url": "Site URL",
|
"commandocentrum.social_login": "Social Login (v1.4)",
|
||||||
"commandocentrum.auto_updates": "Automatische Updates",
|
"commandocentrum.social_login_desc": "Enable social login providers",
|
||||||
"commandocentrum.auto_updates_desc": "Configureer automatische updates",
|
"commandocentrum.google_login": "Google Login",
|
||||||
"commandocentrum.enable_auto_updates": "Automatische Updates Inschakelen",
|
"commandocentrum.google_login_helper": "Allow users to login with Google",
|
||||||
"commandocentrum.schedule": "Schema (HH:MM)",
|
"commandocentrum.google_client_id": "Google Client ID",
|
||||||
"commandocentrum.days": "Dagen (0-6)",
|
"commandocentrum.google_client_id_helper": "From Google Cloud Console",
|
||||||
"commandocentrum.clothing_sync": "Kleding Sync",
|
"commandocentrum.google_client_secret": "Google Client Secret",
|
||||||
"commandocentrum.clothing_sync_desc": "Sync catalogus kleding uit FigureMap",
|
"commandocentrum.discord_login": "Discord Login",
|
||||||
"commandocentrum.sync": "Sync",
|
"commandocentrum.discord_login_helper": "Allow users to login with Discord",
|
||||||
"commandocentrum.clothing_items": "Kleding Items",
|
"commandocentrum.discord_client_id": "Discord Client ID",
|
||||||
"commandocentrum.notifications": "Meldingen",
|
"commandocentrum.discord_client_id_helper": "From Discord Developer Portal",
|
||||||
"commandocentrum.notifications_desc": "E-mail en Discord alerts",
|
"commandocentrum.discord_client_secret": "Discord Client Secret",
|
||||||
"commandocentrum.test_discord": "Test Discord",
|
"commandocentrum.github_login": "GitHub Login",
|
||||||
"commandocentrum.email_notifications": "E-mail Meldingen",
|
"commandocentrum.github_login_helper": "Allow users to login with GitHub",
|
||||||
"commandocentrum.email_address": "E-mail Adres",
|
"commandocentrum.github_client_id": "GitHub Client ID",
|
||||||
"commandocentrum.discord_notifications": "Discord Meldingen",
|
"commandocentrum.github_client_id_helper": "From GitHub Developer Settings",
|
||||||
"commandocentrum.webhook_url": "Webhook URL",
|
"commandocentrum.github_client_secret": "GitHub Client Secret",
|
||||||
"commandocentrum.discord_ranks": "Ranks die Discord notificatie krijgen",
|
"commandocentrum.staff_activity": "Staff Activity Log",
|
||||||
"commandocentrum.discord_ranks_helper": "Laat leeg voor alleen staff (min_staff_rank)",
|
"commandocentrum.staff_activity_desc": "Recent staff activities in the housekeeping (v1.2)",
|
||||||
"commandocentrum.update_history": "Update Geschiedenis",
|
"commandocentrum.recent_staff_activities": "Recent Staff Activities",
|
||||||
"commandocentrum.update_history_desc": "Laatste systeem updates",
|
"commandocentrum.last_20_actions": "Last 20 actions",
|
||||||
"commandocentrum.no_updates_found": "Geen updates gevonden",
|
"commandocentrum.no_staff_activities": "No staff activities recorded yet.",
|
||||||
"commandocentrum.social_login": "Social Login (v1.4)",
|
"commandocentrum.staff_actions_auto": "Staff actions will appear here automatically.",
|
||||||
"commandocentrum.social_login_desc": "Enable social login providers",
|
"commandocentrum.error_loading_activities": "Error loading staff activities",
|
||||||
"commandocentrum.google_login": "Google Login",
|
"commandocentrum.run_migrations": "Make sure to run: php artisan migrate",
|
||||||
"commandocentrum.google_login_helper": "Allow users to login with Google",
|
"commandocentrum.just_now": "Just now",
|
||||||
"commandocentrum.google_client_id": "Google Client ID",
|
"commandocentrum.minutes_ago": "m ago",
|
||||||
"commandocentrum.google_client_id_helper": "From Google Cloud Console",
|
"commandocentrum.hours_ago": "h ago",
|
||||||
"commandocentrum.google_client_secret": "Google Client Secret",
|
"commandocentrum.days_ago": "d ago",
|
||||||
"commandocentrum.discord_login": "Discord Login",
|
"commandocentrum.success": "Success",
|
||||||
"commandocentrum.discord_login_helper": "Allow users to login with Discord",
|
"commandocentrum.error": "Error",
|
||||||
"commandocentrum.discord_client_id": "Discord Client ID",
|
"commandocentrum.warning": "Warning",
|
||||||
"commandocentrum.discord_client_id_helper": "From Discord Developer Portal",
|
"commandocentrum.info": "Info",
|
||||||
"commandocentrum.discord_client_secret": "Discord Client Secret",
|
"commandocentrum.emulator_started": "Emulator gestart!",
|
||||||
"commandocentrum.github_login": "GitHub Login",
|
"commandocentrum.emulator_start_failed": "Kon emulator niet starten",
|
||||||
"commandocentrum.github_login_helper": "Allow users to login with GitHub",
|
"commandocentrum.emulator_stopped": "Emulator gestopt!",
|
||||||
"commandocentrum.github_client_id": "GitHub Client ID",
|
"commandocentrum.emulator_stop_failed": "Kon emulator niet stoppen",
|
||||||
"commandocentrum.github_client_id_helper": "From GitHub Developer Settings",
|
"commandocentrum.emulator_restarted": "Emulator herstart!",
|
||||||
"commandocentrum.github_client_secret": "GitHub Client Secret",
|
"commandocentrum.emulator_restart_failed": "Kon emulator niet herstarten",
|
||||||
"commandocentrum.staff_activity": "Staff Activity Log",
|
"commandocentrum.emulator_online": "Emulator is online en reageert!",
|
||||||
"commandocentrum.staff_activity_desc": "Recent staff activities in the housekeeping (v1.2)",
|
"commandocentrum.emulator_unreachable": "Emulator is niet bereikbaar via RCON",
|
||||||
"commandocentrum.recent_staff_activities": "Recent Staff Activities",
|
"commandocentrum.emulator_settings_saved": "Emulator instellingen opgeslagen!",
|
||||||
"commandocentrum.last_20_actions": "Last 20 actions",
|
"commandocentrum.alerts_saved": "Meldingen opgeslagen!",
|
||||||
"commandocentrum.no_staff_activities": "No staff activities recorded yet.",
|
"commandocentrum.test_sent": "Test bericht verzonden!",
|
||||||
"commandocentrum.staff_actions_auto": "Staff actions will appear here automatically.",
|
"commandocentrum.webhook_empty": "Webhook URL is leeg",
|
||||||
"commandocentrum.error_loading_activities": "Error loading staff activities",
|
"commandocentrum.diagnostics_refreshed": "Diagnostiek vernieuwd",
|
||||||
"commandocentrum.run_migrations": "Make sure to run: php artisan migrate",
|
"commandocentrum.unknown": "Unknown",
|
||||||
"commandocentrum.just_now": "Just now",
|
"commandocentrum.not_applicable": "N/B",
|
||||||
"commandocentrum.minutes_ago": "m ago",
|
"commandocentrum.offline": "Offline",
|
||||||
"commandocentrum.hours_ago": "h ago",
|
"commandocentrum.active": "Active",
|
||||||
"commandocentrum.days_ago": "d ago",
|
"commandocentrum.inactive": "Inactive",
|
||||||
"commandocentrum.success": "Success",
|
"commandocentrum.not_found": "Niet gevonden",
|
||||||
"commandocentrum.error": "Error",
|
"commandocentrum.ok": "OK",
|
||||||
"commandocentrum.warning": "Warning",
|
"commandocentrum.missing": "Ontbreekt",
|
||||||
"commandocentrum.info": "Info",
|
"commandocentrum.jars": "JARs",
|
||||||
"commandocentrum.emulator_started": "Emulator gestart!",
|
"commandocentrum.source": "Source",
|
||||||
"commandocentrum.emulator_start_failed": "Kon emulator niet starten",
|
"commandocentrum.method": "Methode",
|
||||||
"commandocentrum.emulator_stopped": "Emulator gestopt!",
|
"commandocentrum.jar_download_restart": "JAR Download & Herstart",
|
||||||
"commandocentrum.emulator_stop_failed": "Kon emulator niet stoppen",
|
"commandocentrum.maven_build_restart": "Maven Build & Herstart",
|
||||||
"commandocentrum.emulator_restarted": "Emulator herstart!",
|
"commandocentrum.manual_download": "Handmatig: Download JAR van GitHub",
|
||||||
"commandocentrum.emulator_restart_failed": "Kon emulator niet herstarten",
|
"commandocentrum.maven_pom": "Maven (pom.xml)",
|
||||||
"commandocentrum.emulator_online": "Emulator is online en reageert!",
|
"commandocentrum.no_pom": "Geen pom.xml",
|
||||||
"commandocentrum.emulator_unreachable": "Emulator is niet bereikbaar via RCON",
|
"commandocentrum.update_available": "Update beschikbaar",
|
||||||
"commandocentrum.building_emulator": "Emulator wordt gebouwd vanaf source...",
|
"commandocentrum.up_to_date": "Up-to-date",
|
||||||
"commandocentrum.emulator_built": "Emulator gebouwd!",
|
"commandocentrum.update": "Updaten",
|
||||||
"commandocentrum.build_failed": "Build mislukt",
|
"commandocentrum.rebuild": "Herbouwen",
|
||||||
"commandocentrum.configure_github_url": "Configureer eerst de Emulator GitHub URL",
|
"commandocentrum.latest": "Latest",
|
||||||
"commandocentrum.maven_not_installed": "Maven (mvn) is niet geïnstalleerd - kan niet bouwen",
|
"commandocentrum.remote": "Remote",
|
||||||
"commandocentrum.building_maven": "Building emulator with Maven...",
|
"commandocentrum.local": "Local",
|
||||||
"commandocentrum.build_success_jar": "Build succesvol! JAR verplaatst naar :jar. Herstart de emulator.",
|
"commandocentrum.client": "Client",
|
||||||
"commandocentrum.build_success": "Build succesvol! Herstart de emulator.",
|
"commandocentrum.renderer": "Renderer",
|
||||||
"commandocentrum.build_failed_logs": "Build mislukt - controleer logs",
|
"commandocentrum.webroot_status": "Webroot",
|
||||||
"commandocentrum.no_pom_xml": "Geen pom.xml gevonden - kan niet bouwen vanaf source",
|
"commandocentrum.rank": "Rank",
|
||||||
"commandocentrum.sql_applied": "SQL updates toegepast!",
|
"radio.title": "Radio",
|
||||||
"commandocentrum.update_complete": "Update controle voltooid",
|
"radio.music": "Muziek",
|
||||||
"commandocentrum.emulator_settings_saved": "Emulator instellingen opgeslagen!",
|
"radio.loading": "Laden...",
|
||||||
"commandocentrum.nitro_updated": "Nitro bijgewerkt! Build opnieuw met \"Build\" knop.",
|
"radio.navigation_label": "Radio",
|
||||||
"commandocentrum.nitro_up_to_date": "Nitro is al up-to-date!",
|
"radio.setup_page_title": "Radio Setup",
|
||||||
"commandocentrum.building_nitro": "Building Nitro...",
|
"radio.setup_page_subtitle": "Configureer je radio systeem in één keer",
|
||||||
"commandocentrum.nitro_build_success": "Nitro build succesvol!",
|
"radio.setup.success_title": "Radio Geïnstalleerd!",
|
||||||
"commandocentrum.nitro_build_warning": "Build gestart - controleer handmatig",
|
"radio.setup.success_body": "Radio systeem is succesvol geïnstalleerd en geconfigureerd!",
|
||||||
"commandocentrum.valid_url_required": "Voer een geldige URL in (bijv. https://epicnabbo.nl)",
|
"radio.setup.error_title": "Installatie Mislukt",
|
||||||
"commandocentrum.configs_generated": "Configs gegenereerd & bestaande instellingen behouden!",
|
"radio.setup.error_body": "Er is een fout opgetreden: :message",
|
||||||
"commandocentrum.config_generated_warning": "Config gegenereerd (controleer handmatig)",
|
"radio.setup.button_label": "Alles Installeren",
|
||||||
"commandocentrum.paths_detected": "Paths gedetecteerd en opgeslagen!",
|
"radio.setup.modal_heading": "Radio Installeren?",
|
||||||
"commandocentrum.nitro_settings_saved": "Nitro instellingen opgeslagen!",
|
"radio.setup.modal_description": "Dit zal alle radio instellingen configureren met standaard waarden.",
|
||||||
"commandocentrum.auto_update_saved": "Auto update instellingen opgeslagen!",
|
"radio.setup.modal_submit": "Ja, installeer!",
|
||||||
"commandocentrum.alerts_saved": "Meldingen opgeslagen!",
|
"radio.setup.tooltip": "Installeer het complete radio systeem",
|
||||||
"commandocentrum.test_sent": "Test bericht verzonden!",
|
"radio.setup_complete": "✅ Installatie Voltooid!",
|
||||||
"commandocentrum.webhook_empty": "Webhook URL is leeg",
|
"radio.what_gets_configured": "Wat wordt er geconfigureerd?",
|
||||||
"commandocentrum.diagnostics_refreshed": "Diagnostiek vernieuwd",
|
"radio.radio_stream": "Radio Stream",
|
||||||
"commandocentrum.unknown": "Unknown",
|
"radio.radio_stream_desc": "Stel je stream URL in met ondersteuning voor SHOUTcast, Icecast, AzureCast en andere streaming platforms.",
|
||||||
"commandocentrum.not_applicable": "N/B",
|
"radio.points_system": "Punten Systeem",
|
||||||
"commandocentrum.offline": "Offline",
|
"radio.points_system_desc": "Laat gebruikers punten verdienen door te luisteren, nummers aan te vragen en deel te nemen aan contests.",
|
||||||
"commandocentrum.active": "Active",
|
"radio.community_features": "Community Functies",
|
||||||
"commandocentrum.inactive": "Inactive",
|
"radio.community_features_desc": "Shouts, song requests, DJ aanmeldingen en meer community interacties.",
|
||||||
"commandocentrum.not_found": "Niet gevonden",
|
"radio.dj_management": "DJ Beheer",
|
||||||
"commandocentrum.ok": "OK",
|
"radio.dj_management_desc": "DJ ranks, schema, auto-detectie en Sambroadcaster/Virtual DJ integratie.",
|
||||||
"commandocentrum.missing": "Ontbreekt",
|
"radio.monitoring": "Stream Monitoring",
|
||||||
"commandocentrum.jars": "JARs",
|
"radio.monitoring_desc": "Houd je stream uptime in de gaten met real-time monitoring.",
|
||||||
"commandocentrum.source": "Source",
|
"radio.display_options": "Weergave Opties",
|
||||||
"commandocentrum.method": "Methode",
|
"radio.display_options_desc": "Widget, player stijlen, kleuren en aanpasbare CSS/JS.",
|
||||||
"commandocentrum.jar_download_restart": "JAR Download & Herstart",
|
"radio.default_settings": "Standaard Instellingen",
|
||||||
"commandocentrum.maven_build_restart": "Maven Build & Herstart",
|
"radio.radio_label": "Radio",
|
||||||
"commandocentrum.manual_download": "Handmatig: Download JAR van GitHub",
|
"radio.enabled": "Ingeschakeld",
|
||||||
"commandocentrum.maven_pom": "Maven (pom.xml)",
|
"radio.points_label": "Punten",
|
||||||
"commandocentrum.no_pom": "Geen pom.xml",
|
"radio.per_min": " per min",
|
||||||
"commandocentrum.update_available": "Update beschikbaar",
|
"radio.daily_limit": "Dagelijkse limiet",
|
||||||
"commandocentrum.up_to_date": "Up-to-date",
|
"radio.shouts_label": "Shouts",
|
||||||
"commandocentrum.update": "Updaten",
|
"radio.on": "Aan",
|
||||||
"commandocentrum.rebuild": "Herbouwen",
|
"radio.widget": "Widget",
|
||||||
"commandocentrum.latest": "Latest",
|
"radio.global": "Globaal",
|
||||||
"commandocentrum.remote": "Remote",
|
"radio.dj_apps": "DJ Aanmeldingen",
|
||||||
"commandocentrum.local": "Local",
|
"radio.open": "Open",
|
||||||
"commandocentrum.client": "Client",
|
"radio.monitoring_label": "Monitoring",
|
||||||
"commandocentrum.renderer": "Renderer",
|
"radio.contests_label": "Contesten",
|
||||||
"commandocentrum.webroot_status": "Webroot",
|
"radio.install_radio_system": "🚀 Radio Systeem Installeren",
|
||||||
"commandocentrum.rank": "Rank",
|
"radio.reset_settings": "Instellingen Resetten",
|
||||||
"radio.title": "Radio",
|
"radio.reset_confirm": "Weet je zeker dat je alle radio instellingen wilt resetten?",
|
||||||
"radio.music": "Muziek",
|
"radio.go_to_radio_settings": "Naar Radio Instellingen",
|
||||||
"radio.loading": "Laden...",
|
"radio.open_wizard": "🎯 Open Radio Wizard",
|
||||||
"radio.navigation_label": "Radio",
|
"radio.wizard_desc": "Stap-voor-stap wizard met verbindingstest",
|
||||||
"radio.setup_page_title": "Radio Setup",
|
"radio.wizard.title": "Radio Installatie Wizard",
|
||||||
"radio.setup_page_subtitle": "Configureer je radio systeem in één keer",
|
"radio.wizard.step_short": "Stap",
|
||||||
"radio.setup.success_title": "Radio Geïnstalleerd!",
|
"radio.wizard.step_prefix": "Stap",
|
||||||
"radio.setup.success_body": "Radio systeem is succesvol geïnstalleerd en geconfigureerd!",
|
"radio.wizard.of": "van",
|
||||||
"radio.setup.error_title": "Installatie Mislukt",
|
"radio.wizard.next_step": "Volgende Stap →",
|
||||||
"radio.setup.error_body": "Er is een fout opgetreden: :message",
|
"radio.wizard.previous_step": "← Vorige Stap",
|
||||||
"radio.setup.button_label": "Alles Installeren",
|
"radio.wizard.back_to_setup": "Terug naar setup",
|
||||||
"radio.setup.modal_heading": "Radio Installeren?",
|
"radio.wizard.step1_label": "Platform",
|
||||||
"radio.setup.modal_description": "Dit zal alle radio instellingen configureren met standaard waarden.",
|
"radio.wizard.step2_label": "Stream",
|
||||||
"radio.setup.modal_submit": "Ja, installeer!",
|
"radio.wizard.step3_label": "API",
|
||||||
"radio.setup.tooltip": "Installeer het complete radio systeem",
|
"radio.wizard.step4_label": "Functies",
|
||||||
"radio.setup_complete": "✅ Installatie Voltooid!",
|
"radio.wizard.step5_label": "Testen",
|
||||||
"radio.what_gets_configured": "Wat wordt er geconfigureerd?",
|
"radio.wizard.step1_subtitle": "Kies je streaming platform",
|
||||||
"radio.radio_stream": "Radio Stream",
|
"radio.wizard.step2_title": "Stream Configuratie",
|
||||||
"radio.radio_stream_desc": "Stel je stream URL in met ondersteuning voor SHOUTcast, Icecast, AzureCast en andere streaming platforms.",
|
"radio.wizard.step3_title": "API Configuratie",
|
||||||
"radio.points_system": "Punten Systeem",
|
"radio.wizard.step3_subtitle": "Now Playing & Luisteraars",
|
||||||
"radio.points_system_desc": "Laat gebruikers punten verdienen door te luisteren, nummers aan te vragen en deel te nemen aan contests.",
|
"radio.wizard.step4_title": "Functies Configureren",
|
||||||
"radio.community_features": "Community Functies",
|
"radio.wizard.step4_subtitle": "Kies welke radio functies je wilt inschakelen",
|
||||||
"radio.community_features_desc": "Shouts, song requests, DJ aanmeldingen en meer community interacties.",
|
"radio.wizard.step5_title": "Test & Installeren",
|
||||||
"radio.dj_management": "DJ Beheer",
|
"radio.wizard.step5_subtitle": "Controleer de verbinding en voltooi de installatie",
|
||||||
"radio.dj_management_desc": "DJ ranks, schema, auto-detectie en Sambroadcaster/Virtual DJ integratie.",
|
"radio.wizard.platform_shoutcast": "SHOUTcast",
|
||||||
"radio.monitoring": "Stream Monitoring",
|
"radio.wizard.platform_shoutcast_desc": "Geschikt voor SHOUTcast servers. Automatische detectie van nu afspelen en luisteraars via stats endpoint.",
|
||||||
"radio.monitoring_desc": "Houd je stream uptime in de gaten met real-time monitoring.",
|
"radio.wizard.platform_icecast": "Icecast",
|
||||||
"radio.display_options": "Weergave Opties",
|
"radio.wizard.platform_icecast_desc": "Geschikt voor Icecast servers. Gebruikt status-json.xsl voor automatische detectie.",
|
||||||
"radio.display_options_desc": "Widget, player stijlen, kleuren en aanpasbare CSS/JS.",
|
"radio.wizard.platform_azurecast": "AzureCast",
|
||||||
"radio.default_settings": "Standaard Instellingen",
|
"radio.wizard.platform_azurecast_desc": "AzureCast hosting. Volledige API integratie met now-playing, listeners en auto-configuratie.",
|
||||||
"radio.radio_label": "Radio",
|
"radio.wizard.platform_other": "Anders",
|
||||||
"radio.enabled": "Ingeschakeld",
|
"radio.wizard.platform_other_desc": "Een andere stream provider. Handmatige configuratie van stream URL en API endpoints.",
|
||||||
"radio.points_label": "Punten",
|
"radio.wizard.shoutcast_info_title": "SHOUTcast",
|
||||||
"radio.per_min": " per min",
|
"radio.wizard.shoutcast_info_desc": "Voer je SHOUTcast stream URL in. De wizard probeert automatisch de stats endpoint te vinden.",
|
||||||
"radio.daily_limit": "Dagelijkse limiet",
|
"radio.wizard.icecast_info_title": "Icecast",
|
||||||
"radio.shouts_label": "Shouts",
|
"radio.wizard.icecast_info_desc": "Voer je Icecast stream URL in. De wizard gebruikt status-json.xsl voor automatische detectie.",
|
||||||
"radio.on": "Aan",
|
"radio.wizard.azurecast_info_title": "AzureCast",
|
||||||
"radio.widget": "Widget",
|
"radio.wizard.azurecast_info_desc": "AzureCast stream URL + server configuratie. De wizard configureert alles via de AzureCast API.",
|
||||||
"radio.global": "Globaal",
|
"radio.wizard.other_info_title": "Andere Stream",
|
||||||
"radio.dj_apps": "DJ Aanmeldingen",
|
"radio.wizard.other_info_desc": "Voer je stream URL in. Je kunt later handmatig API endpoints configureren voor nu afspelen en luisteraars.",
|
||||||
"radio.open": "Open",
|
"radio.wizard.stream_url_label": "Stream URL *",
|
||||||
"radio.monitoring_label": "Monitoring",
|
"radio.wizard.stream_url_hint": "De directe URL naar je audiostreem (MP3, AAC, OGG, etc.)",
|
||||||
"radio.contests_label": "Contesten",
|
"radio.wizard.stream_name_label": "Stream Naam",
|
||||||
"radio.install_radio_system": "🚀 Radio Systeem Installeren",
|
"radio.wizard.stream_name_placeholder": "Mijn Radio",
|
||||||
"radio.reset_settings": "Instellingen Resetten",
|
"radio.wizard.stream_name_hint": "Een naam voor je radiostream (optioneel)",
|
||||||
"radio.reset_confirm": "Weet je zeker dat je alle radio instellingen wilt resetten?",
|
"radio.wizard.azurecast_section": "AzureCast Server Configuratie",
|
||||||
"radio.go_to_radio_settings": "Naar Radio Instellingen",
|
"radio.wizard.azurecast_base_url_label": "AzureCast Basis URL",
|
||||||
"radio.open_wizard": "🎯 Open Radio Wizard",
|
"radio.wizard.azurecast_base_url_hint": "De basis URL van je AzureCast server. Wordt automatisch gedetecteerd als leeg gelaten.",
|
||||||
"radio.wizard_desc": "Stap-voor-stap wizard met verbindingstest",
|
"radio.wizard.azurecast_station_id_label": "Station ID",
|
||||||
"radio.wizard.title": "Radio Installatie Wizard",
|
"radio.wizard.azurecast_station_id_hint": "Het station ID in AzureCast (standaard: 1)",
|
||||||
"radio.wizard.step_short": "Stap",
|
"radio.wizard.enable_now_playing": "Nu Afspelen inschakelen",
|
||||||
"radio.wizard.step_prefix": "Stap",
|
"radio.wizard.now_playing_api_label": "Now Playing API URL",
|
||||||
"radio.wizard.of": "van",
|
"radio.wizard.now_playing_api_hint": "API endpoint dat het huidige nummer teruggeeft. Meestal automatisch gedetecteerd.",
|
||||||
"radio.wizard.next_step": "Volgende Stap →",
|
"radio.wizard.enable_listeners": "Luisteraars teller inschakelen",
|
||||||
"radio.wizard.previous_step": "← Vorige Stap",
|
"radio.wizard.listeners_api_label": "Listeners API URL",
|
||||||
"radio.wizard.back_to_setup": "Terug naar setup",
|
"radio.wizard.listeners_api_hint": "API endpoint dat het aantal luisteraars teruggeeft.",
|
||||||
"radio.wizard.step1_label": "Platform",
|
"radio.wizard.enable_current_dj": "Huidige DJ tonen",
|
||||||
"radio.wizard.step2_label": "Stream",
|
"radio.wizard.detected": "gedetecteerd!",
|
||||||
"radio.wizard.step3_label": "API",
|
"radio.wizard.detected_desc": "API endpoints zijn automatisch gevonden en ingevuld.",
|
||||||
"radio.wizard.step4_label": "Functies",
|
"radio.wizard.not_detected": "Geen automatische detectie",
|
||||||
"radio.wizard.step5_label": "Testen",
|
"radio.wizard.not_detected_desc": "Vul de API URLs handmatig in of sla deze stap over.",
|
||||||
"radio.wizard.step1_subtitle": "Kies je streaming platform",
|
"radio.wizard.section_community": "Community Functies",
|
||||||
"radio.wizard.step2_title": "Stream Configuratie",
|
"radio.wizard.feature_shouts": "Shouts",
|
||||||
"radio.wizard.step3_title": "API Configuratie",
|
"radio.wizard.feature_shouts_desc": "Berichten achterlaten",
|
||||||
"radio.wizard.step3_subtitle": "Now Playing & Luisteraars",
|
"radio.wizard.feature_applications": "DJ Aanmeldingen",
|
||||||
"radio.wizard.step4_title": "Functies Configureren",
|
"radio.wizard.feature_applications_desc": "Solliciteren als DJ",
|
||||||
"radio.wizard.step4_subtitle": "Kies welke radio functies je wilt inschakelen",
|
"radio.wizard.feature_requests": "Song Verzoeken",
|
||||||
"radio.wizard.step5_title": "Test & Installeren",
|
"radio.wizard.feature_requests_desc": "Nummers aanvragen",
|
||||||
"radio.wizard.step5_subtitle": "Controleer de verbinding en voltooi de installatie",
|
"radio.wizard.section_display": "Weergave",
|
||||||
"radio.wizard.platform_shoutcast": "SHOUTcast",
|
"radio.wizard.feature_widget": "Radio Widget",
|
||||||
"radio.wizard.platform_shoutcast_desc": "Geschikt voor SHOUTcast servers. Automatische detectie van nu afspelen en luisteraars via stats endpoint.",
|
"radio.wizard.feature_widget_desc": "Miniplayer op de site",
|
||||||
"radio.wizard.platform_icecast": "Icecast",
|
"radio.wizard.feature_widget_global": "Widget Overal",
|
||||||
"radio.wizard.platform_icecast_desc": "Geschikt voor Icecast servers. Gebruikt status-json.xsl voor automatische detectie.",
|
"radio.wizard.feature_widget_global_desc": "Op alle pagina's tonen",
|
||||||
"radio.wizard.platform_azurecast": "AzureCast",
|
"radio.wizard.widget_position_label": "Widget Positie",
|
||||||
"radio.wizard.platform_azurecast_desc": "AzureCast hosting. Volledige API integratie met now-playing, listeners en auto-configuratie.",
|
"radio.wizard.position_bottom_right": "Rechtsonder",
|
||||||
"radio.wizard.platform_other": "Anders",
|
"radio.wizard.position_bottom_left": "Linksonder",
|
||||||
"radio.wizard.platform_other_desc": "Een andere stream provider. Handmatige configuratie van stream URL en API endpoints.",
|
"radio.wizard.position_top_right": "Rechtsboven",
|
||||||
"radio.wizard.shoutcast_info_title": "SHOUTcast",
|
"radio.wizard.position_top_left": "Linksboven",
|
||||||
"radio.wizard.shoutcast_info_desc": "Voer je SHOUTcast stream URL in. De wizard probeert automatisch de stats endpoint te vinden.",
|
"radio.wizard.section_gamification": "Gamification",
|
||||||
"radio.wizard.icecast_info_title": "Icecast",
|
"radio.wizard.feature_points": "Punten Systeem",
|
||||||
"radio.wizard.icecast_info_desc": "Voer je Icecast stream URL in. De wizard gebruikt status-json.xsl voor automatische detectie.",
|
"radio.wizard.feature_points_desc": "Verdien punten door te luisteren",
|
||||||
"radio.wizard.azurecast_info_title": "AzureCast",
|
"radio.wizard.feature_contests": "Contesten",
|
||||||
"radio.wizard.azurecast_info_desc": "AzureCast stream URL + server configuratie. De wizard configureert alles via de AzureCast API.",
|
"radio.wizard.feature_contests_desc": "Wedstrijden organiseren",
|
||||||
"radio.wizard.other_info_title": "Andere Stream",
|
"radio.wizard.feature_giveaways": "Giveaways",
|
||||||
"radio.wizard.other_info_desc": "Voer je stream URL in. Je kunt later handmatig API endpoints configureren voor nu afspelen en luisteraars.",
|
"radio.wizard.feature_giveaways_desc": "Cadeautjes weggeven",
|
||||||
"radio.wizard.stream_url_label": "Stream URL *",
|
"radio.wizard.section_integrations": "Integraties",
|
||||||
"radio.wizard.stream_url_hint": "De directe URL naar je audiostreem (MP3, AAC, OGG, etc.)",
|
"radio.wizard.feature_discord": "Discord Notificaties",
|
||||||
"radio.wizard.stream_name_label": "Stream Naam",
|
"radio.wizard.feature_discord_desc": "Meldingen bij DJ live / nummer wijziging",
|
||||||
"radio.wizard.stream_name_placeholder": "Mijn Radio",
|
"radio.wizard.discord_webhook_label": "Discord Webhook URL",
|
||||||
"radio.wizard.stream_name_hint": "Een naam voor je radiostream (optioneel)",
|
"radio.wizard.discord_webhook_hint": "Maak een webhook aan in je Discord server kanaal.",
|
||||||
"radio.wizard.azurecast_section": "AzureCast Server Configuratie",
|
"radio.wizard.test_title": "Verbinding Testen",
|
||||||
"radio.wizard.azurecast_base_url_label": "AzureCast Basis URL",
|
"radio.wizard.test_desc": "Klik op Test Verbinding om te controleren of je stream en APIs bereikbaar zijn.",
|
||||||
"radio.wizard.azurecast_base_url_hint": "De basis URL van je AzureCast server. Wordt automatisch gedetecteerd als leeg gelaten.",
|
"radio.wizard.test_loading": "Bezig met testen van de verbinding...",
|
||||||
"radio.wizard.azurecast_station_id_label": "Station ID",
|
"radio.wizard.test_prompt": "Klik op de knop om de verbinding te testen.",
|
||||||
"radio.wizard.azurecast_station_id_hint": "Het station ID in AzureCast (standaard: 1)",
|
"radio.wizard.test_button": "Test Verbinding",
|
||||||
"radio.wizard.enable_now_playing": "Nu Afspelen inschakelen",
|
"radio.wizard.test_retry": "Opnieuw Testen",
|
||||||
"radio.wizard.now_playing_api_label": "Now Playing API URL",
|
"radio.wizard.settings_overview": "Overzicht van Instellingen",
|
||||||
"radio.wizard.now_playing_api_hint": "API endpoint dat het huidige nummer teruggeeft. Meestal automatisch gedetecteerd.",
|
"radio.wizard.settings_overview_desc": "Dit zijn de instellingen die worden opgeslagen:",
|
||||||
"radio.wizard.enable_listeners": "Luisteraars teller inschakelen",
|
"radio.wizard.install_confirm": "Weet je zeker dat je de radio wilt installeren met deze instellingen?",
|
||||||
"radio.wizard.listeners_api_label": "Listeners API URL",
|
"radio.wizard.install_button": "Radio Installeren",
|
||||||
"radio.wizard.listeners_api_hint": "API endpoint dat het aantal luisteraars teruggeeft.",
|
"radio.wizard.test_result_stream": "Stream Verbinding",
|
||||||
"radio.wizard.enable_current_dj": "Huidige DJ tonen",
|
"radio.wizard.test_result_now_playing": "Now Playing",
|
||||||
"radio.wizard.detected": "gedetecteerd!",
|
"radio.wizard.test_result_listeners": "Luisteraars",
|
||||||
"radio.wizard.detected_desc": "API endpoints zijn automatisch gevonden en ingevuld.",
|
"radio.wizard.status_success": "Succes",
|
||||||
"radio.wizard.not_detected": "Geen automatische detectie",
|
"radio.wizard.status_warning": "Waarschuwing",
|
||||||
"radio.wizard.not_detected_desc": "Vul de API URLs handmatig in of sla deze stap over.",
|
"radio.wizard.status_error": "Fout",
|
||||||
"radio.wizard.section_community": "Community Functies",
|
"radio.wizard.status_skipped": "Overgeslagen",
|
||||||
"radio.wizard.feature_shouts": "Shouts",
|
"radio.wizard.status_untested": "Niet getest",
|
||||||
"radio.wizard.feature_shouts_desc": "Berichten achterlaten",
|
"radio.wizard.content_type": "Content-Type",
|
||||||
"radio.wizard.feature_applications": "DJ Aanmeldingen",
|
"radio.wizard.http_status": "HTTP Status",
|
||||||
"radio.wizard.feature_applications_desc": "Solliciteren als DJ",
|
"radio.wizard.song": "Nummer",
|
||||||
"radio.wizard.feature_requests": "Song Verzoeken",
|
"radio.wizard.artist": "Artiest",
|
||||||
"radio.wizard.feature_requests_desc": "Nummers aanvragen",
|
"radio.wizard.listeners": "Luisteraars",
|
||||||
"radio.wizard.section_display": "Weergave",
|
"radio.wizard.api_url": "API URL",
|
||||||
"radio.wizard.feature_widget": "Radio Widget",
|
"radio.wizard.test_stream_ok": "Stream is bereikbaar! Je kunt de radio installeren.",
|
||||||
"radio.wizard.feature_widget_desc": "Miniplayer op de site",
|
"radio.wizard.test_stream_fail": "Stream is niet bereikbaar. Controleer de URL en probeer het opnieuw.",
|
||||||
"radio.wizard.feature_widget_global": "Widget Overal",
|
"radio.wizard.test_not_run": "Nog niet getest.",
|
||||||
"radio.wizard.feature_widget_global_desc": "Op alle pagina's tonen",
|
"radio.wizard.test_connection_fail": "Kon de test niet uitvoeren: ",
|
||||||
"radio.wizard.widget_position_label": "Widget Positie",
|
"radio.wizard.error": "Fout",
|
||||||
"radio.wizard.position_bottom_right": "Rechtsonder",
|
"radio.wizard.unknown_error": "Onbekende fout",
|
||||||
"radio.wizard.position_bottom_left": "Linksonder",
|
"Homepage": "Homepage"
|
||||||
"radio.wizard.position_top_right": "Rechtsboven",
|
|
||||||
"radio.wizard.position_top_left": "Linksboven",
|
|
||||||
"radio.wizard.section_gamification": "Gamification",
|
|
||||||
"radio.wizard.feature_points": "Punten Systeem",
|
|
||||||
"radio.wizard.feature_points_desc": "Verdien punten door te luisteren",
|
|
||||||
"radio.wizard.feature_contests": "Contesten",
|
|
||||||
"radio.wizard.feature_contests_desc": "Wedstrijden organiseren",
|
|
||||||
"radio.wizard.feature_giveaways": "Giveaways",
|
|
||||||
"radio.wizard.feature_giveaways_desc": "Cadeautjes weggeven",
|
|
||||||
"radio.wizard.section_integrations": "Integraties",
|
|
||||||
"radio.wizard.feature_discord": "Discord Notificaties",
|
|
||||||
"radio.wizard.feature_discord_desc": "Meldingen bij DJ live / nummer wijziging",
|
|
||||||
"radio.wizard.discord_webhook_label": "Discord Webhook URL",
|
|
||||||
"radio.wizard.discord_webhook_hint": "Maak een webhook aan in je Discord server kanaal.",
|
|
||||||
"radio.wizard.test_title": "Verbinding Testen",
|
|
||||||
"radio.wizard.test_desc": "Klik op Test Verbinding om te controleren of je stream en APIs bereikbaar zijn.",
|
|
||||||
"radio.wizard.test_loading": "Bezig met testen van de verbinding...",
|
|
||||||
"radio.wizard.test_prompt": "Klik op de knop om de verbinding te testen.",
|
|
||||||
"radio.wizard.test_button": "Test Verbinding",
|
|
||||||
"radio.wizard.test_retry": "Opnieuw Testen",
|
|
||||||
"radio.wizard.settings_overview": "Overzicht van Instellingen",
|
|
||||||
"radio.wizard.settings_overview_desc": "Dit zijn de instellingen die worden opgeslagen:",
|
|
||||||
"radio.wizard.install_confirm": "Weet je zeker dat je de radio wilt installeren met deze instellingen?",
|
|
||||||
"radio.wizard.install_button": "Radio Installeren",
|
|
||||||
"radio.wizard.test_result_stream": "Stream Verbinding",
|
|
||||||
"radio.wizard.test_result_now_playing": "Now Playing",
|
|
||||||
"radio.wizard.test_result_listeners": "Luisteraars",
|
|
||||||
"radio.wizard.status_success": "Succes",
|
|
||||||
"radio.wizard.status_warning": "Waarschuwing",
|
|
||||||
"radio.wizard.status_error": "Fout",
|
|
||||||
"radio.wizard.status_skipped": "Overgeslagen",
|
|
||||||
"radio.wizard.status_untested": "Niet getest",
|
|
||||||
"radio.wizard.content_type": "Content-Type",
|
|
||||||
"radio.wizard.http_status": "HTTP Status",
|
|
||||||
"radio.wizard.song": "Nummer",
|
|
||||||
"radio.wizard.artist": "Artiest",
|
|
||||||
"radio.wizard.listeners": "Luisteraars",
|
|
||||||
"radio.wizard.api_url": "API URL",
|
|
||||||
"radio.wizard.test_stream_ok": "Stream is bereikbaar! Je kunt de radio installeren.",
|
|
||||||
"radio.wizard.test_stream_fail": "Stream is niet bereikbaar. Controleer de URL en probeer het opnieuw.",
|
|
||||||
"radio.wizard.test_not_run": "Nog niet getest.",
|
|
||||||
"radio.wizard.test_connection_fail": "Kon de test niet uitvoeren: ",
|
|
||||||
"radio.wizard.error": "Fout",
|
|
||||||
"radio.wizard.unknown_error": "Onbekende fout",
|
|
||||||
"Homepage": "Homepage"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
includes:
|
|
||||||
- phpstan-baseline.neon
|
|
||||||
|
|
||||||
parameters:
|
|
||||||
level: 5
|
|
||||||
paths:
|
|
||||||
- app
|
|
||||||
reportUnmatchedIgnoredErrors: false
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
:root{--swiper-navigation-size: 44px}.swiper-button-prev,.swiper-button-next{position:absolute;width:var(--swiper-navigation-size);height:var(--swiper-navigation-size);z-index:10;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--swiper-navigation-color, var(--swiper-theme-color));&.swiper-button-disabled{opacity:.35;cursor:auto;pointer-events:none}&.swiper-button-hidden{opacity:0;cursor:auto;pointer-events:none}.swiper-navigation-disabled &{display:none!important}::slotted(svg),svg{width:100%;height:100%;-o-object-fit:contain;object-fit:contain;transform-origin:center;fill:currentColor;pointer-events:none}}.swiper-button-lock{display:none}.swiper-button-prev,.swiper-button-next{top:var(--swiper-navigation-top-offset, 50%);margin-top:calc(0px - (var(--swiper-navigation-size) / 2))}.swiper-button-prev{left:var(--swiper-navigation-sides-offset, 4px);right:auto;::slotted(.swiper-navigation-icon),.swiper-navigation-icon{transform:rotate(180deg)}}.swiper-button-next{right:var(--swiper-navigation-sides-offset, 4px);left:auto}.swiper-horizontal{.swiper-button-prev,.swiper-button-next,~.swiper-button-prev,~.swiper-button-next{top:var(--swiper-navigation-top-offset, 50%);margin-top:calc(0px - (var(--swiper-navigation-size) / 2));margin-left:0}.swiper-button-prev,~.swiper-button-prev,&.swiper-rtl .swiper-button-next,&.swiper-rtl~.swiper-button-next{left:var(--swiper-navigation-sides-offset, 4px);right:auto}.swiper-button-next,~.swiper-button-next,&.swiper-rtl .swiper-button-prev,&.swiper-rtl~.swiper-button-prev{right:var(--swiper-navigation-sides-offset, 4px);left:auto}.swiper-button-prev,~.swiper-button-prev,&.swiper-rtl .swiper-button-next,&.swiper-rtl~.swiper-button-next{::slotted(.swiper-navigation-icon),.swiper-navigation-icon{transform:rotate(180deg)}}&.swiper-rtl .swiper-button-prev,&.swiper-rtl~.swiper-button-prev{::slotted(.swiper-navigation-icon),.swiper-navigation-icon{transform:rotate(0)}}}.swiper-vertical{.swiper-button-prev,.swiper-button-next,~.swiper-button-prev,~.swiper-button-next{left:var(--swiper-navigation-top-offset, 50%);right:auto;margin-left:calc(0px - (var(--swiper-navigation-size) / 2));margin-top:0}.swiper-button-prev,~.swiper-button-prev{top:var(--swiper-navigation-sides-offset, 4px);bottom:auto;::slotted(.swiper-navigation-icon),.swiper-navigation-icon{transform:rotate(-90deg)}}.swiper-button-next,~.swiper-button-next{bottom:var(--swiper-navigation-sides-offset, 4px);top:auto;::slotted(.swiper-navigation-icon),.swiper-navigation-icon{transform:rotate(90deg)}}}
|
|
||||||
|
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 260 B |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 678 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 381 B |
@@ -1 +0,0 @@
|
|||||||
var c=Object.create,_=Object.defineProperty,v=Object.getOwnPropertyDescriptor,O=Object.getOwnPropertyNames,b=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,g=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),f=(e,r)=>{let t={};for(var n in e)_(t,n,{get:e[n],enumerable:!0});return r||_(t,Symbol.toStringTag,{value:"Module"}),t},P=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(var o=O(r),p=0,l=o.length,a;p<l;p++)a=o[p],!s.call(e,a)&&a!==t&&_(e,a,{get:(u=>r[u]).bind(null,a),enumerable:!(n=v(r,a))||n.enumerable});return e},i=(e,r,t)=>(t=e!=null?c(b(e)):{},P(r||!e||!e.__esModule?_(t,"default",{value:e,enumerable:!0}):t,e));export{f as n,i as r,g as t};
|
|
||||||
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 473 B After Width: | Height: | Size: 473 B |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 471 B |
|
Before Width: | Height: | Size: 690 B After Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 626 B |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 491 B After Width: | Height: | Size: 491 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 166 B After Width: | Height: | Size: 166 B |
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 630 B |
|
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 350 B |
@@ -1,25 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_axios-BgauIbGG.js": {
|
"_axios-B2OU-7LW.js": {
|
||||||
"file": "assets/axios-BgauIbGG.js",
|
"file": "assets/axios-B2OU-7LW.js",
|
||||||
"name": "axios",
|
"name": "axios"
|
||||||
"imports": [
|
|
||||||
"_chunk-BqhQeaEc.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"_chunk-BqhQeaEc.js": {
|
|
||||||
"file": "assets/chunk-BqhQeaEc.js",
|
|
||||||
"name": "chunk"
|
|
||||||
},
|
|
||||||
"_swiper-B7Yxk788.js": {
|
|
||||||
"file": "assets/swiper-B7Yxk788.js",
|
|
||||||
"name": "swiper",
|
|
||||||
"css": [
|
|
||||||
"assets/swiper-CrMA9oas.css"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"_swiper-CrMA9oas.css": {
|
|
||||||
"file": "assets/swiper-CrMA9oas.css",
|
|
||||||
"src": "_swiper-CrMA9oas.css"
|
|
||||||
},
|
},
|
||||||
"public/assets/images/background-dark.jpg": {
|
"public/assets/images/background-dark.jpg": {
|
||||||
"file": "assets/background-dark-BfkMu3-0.jpg",
|
"file": "assets/background-dark-BfkMu3-0.jpg",
|
||||||
@@ -29,18 +11,6 @@
|
|||||||
"file": "assets/background-light-CP7oKwVT.jpg",
|
"file": "assets/background-light-CP7oKwVT.jpg",
|
||||||
"src": "public/assets/images/background-light.jpg"
|
"src": "public/assets/images/background-light.jpg"
|
||||||
},
|
},
|
||||||
"public/assets/images/dusk/background_image.png": {
|
|
||||||
"file": "assets/background_image-BH7pVpv1.png",
|
|
||||||
"src": "public/assets/images/dusk/background_image.png"
|
|
||||||
},
|
|
||||||
"public/assets/images/dusk/leaderboard_circle_image.png": {
|
|
||||||
"file": "assets/leaderboard_circle_image-BYkDVX69.png",
|
|
||||||
"src": "public/assets/images/dusk/leaderboard_circle_image.png"
|
|
||||||
},
|
|
||||||
"public/assets/images/dusk/store_icon.png": {
|
|
||||||
"file": "assets/store_icon-B52tsSKO.png",
|
|
||||||
"src": "public/assets/images/dusk/store_icon.png"
|
|
||||||
},
|
|
||||||
"public/assets/images/icons/article.gif": {
|
"public/assets/images/icons/article.gif": {
|
||||||
"file": "assets/article-CYhGsSKA.gif",
|
"file": "assets/article-CYhGsSKA.gif",
|
||||||
"src": "public/assets/images/icons/article.gif"
|
"src": "public/assets/images/icons/article.gif"
|
||||||
@@ -134,7 +104,7 @@
|
|||||||
"src": "public/assets/images/profile/profile-bg.png"
|
"src": "public/assets/images/profile/profile-bg.png"
|
||||||
},
|
},
|
||||||
"resources/css/global.css": {
|
"resources/css/global.css": {
|
||||||
"file": "assets/global-DVSCrBhf.css",
|
"file": "assets/global-owlIrRiH.css",
|
||||||
"name": "global",
|
"name": "global",
|
||||||
"names": [
|
"names": [
|
||||||
"global.css"
|
"global.css"
|
||||||
@@ -170,57 +140,30 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resources/js/global.js": {
|
"resources/js/global.js": {
|
||||||
"file": "assets/global-Bkbv5Qui.js",
|
"file": "assets/global-TU7mFC54.js",
|
||||||
"name": "global",
|
"name": "global",
|
||||||
"src": "resources/js/global.js",
|
"src": "resources/js/global.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_chunk-BqhQeaEc.js",
|
"_axios-B2OU-7LW.js"
|
||||||
"_axios-BgauIbGG.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"resources/js/ssr.jsx": {
|
|
||||||
"file": "assets/ssr-dFzH1dUH.js",
|
|
||||||
"name": "ssr",
|
|
||||||
"src": "resources/js/ssr.jsx",
|
|
||||||
"isEntry": true,
|
|
||||||
"imports": [
|
|
||||||
"_chunk-BqhQeaEc.js"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resources/themes/atom/css/app.css": {
|
"resources/themes/atom/css/app.css": {
|
||||||
"file": "assets/app-DrKvMbgn.css",
|
"file": "assets/app-9mCg36im.css",
|
||||||
"src": "resources/themes/atom/css/app.css"
|
|
||||||
},
|
|
||||||
"resources/themes/atom/js/app.js": {
|
|
||||||
"file": "assets/app-BBe1NbvP.js",
|
|
||||||
"name": "app",
|
|
||||||
"src": "resources/themes/atom/js/app.js",
|
|
||||||
"isEntry": true,
|
|
||||||
"imports": [
|
|
||||||
"_chunk-BqhQeaEc.js",
|
|
||||||
"_swiper-B7Yxk788.js",
|
|
||||||
"_axios-BgauIbGG.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"resources/themes/dusk/css/app.css": {
|
|
||||||
"file": "assets/app-tY2AX6sE.css",
|
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"names": [
|
"names": [
|
||||||
"app.css"
|
"app.css"
|
||||||
],
|
],
|
||||||
"src": "resources/themes/dusk/css/app.css",
|
"src": "resources/themes/atom/css/app.css",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"css": [
|
|
||||||
"assets/app-DrKvMbgn.css"
|
|
||||||
],
|
|
||||||
"assets": [
|
"assets": [
|
||||||
"assets/background_image-BH7pVpv1.png",
|
"assets/camera-fu-bmGhB.png",
|
||||||
|
"assets/discord-rbcnEh-j.png",
|
||||||
"assets/feeds-BtHcJdHX.png",
|
"assets/feeds-BtHcJdHX.png",
|
||||||
"assets/chat-r5H1PnTg.png",
|
"assets/chat-r5H1PnTg.png",
|
||||||
"assets/article-CYhGsSKA.gif",
|
"assets/article-CYhGsSKA.gif",
|
||||||
"assets/lighthouse-BON6qnQ0.png",
|
"assets/lighthouse-BON6qnQ0.png",
|
||||||
"assets/store_icon-B52tsSKO.png",
|
"assets/currency-LKXzczCA.png",
|
||||||
"assets/catalog-D-956oDx.png",
|
"assets/catalog-D-956oDx.png",
|
||||||
"assets/inventory-BlHYLNGT.png",
|
"assets/inventory-BlHYLNGT.png",
|
||||||
"assets/due-chat-CeO4yxLu.png",
|
"assets/due-chat-CeO4yxLu.png",
|
||||||
@@ -228,23 +171,29 @@
|
|||||||
"assets/credits-Dpg5Nmby.png",
|
"assets/credits-Dpg5Nmby.png",
|
||||||
"assets/duckets-CaGJI1Oy.png",
|
"assets/duckets-CaGJI1Oy.png",
|
||||||
"assets/diamonds-BtfqKoQu.png",
|
"assets/diamonds-BtfqKoQu.png",
|
||||||
|
"assets/profile-bg-BWx4iuHa.png",
|
||||||
"assets/trophy-gold-bbKmpkii.png",
|
"assets/trophy-gold-bbKmpkii.png",
|
||||||
"assets/trophy-silver-bGfHJkQ_.png",
|
"assets/trophy-silver-bGfHJkQ_.png",
|
||||||
"assets/trophy-bronze-CgV5j1MU.png",
|
"assets/trophy-bronze-CgV5j1MU.png",
|
||||||
"assets/leaderboard_circle_image-BYkDVX69.png"
|
"assets/background-light-CP7oKwVT.jpg",
|
||||||
|
"assets/background-dark-BfkMu3-0.jpg",
|
||||||
|
"assets/shop-D3NfN6cF.png",
|
||||||
|
"assets/leaderboards-CGasq3cL.png",
|
||||||
|
"assets/rules--xzBmecz.gif",
|
||||||
|
"assets/home-DIMFC97Y.png",
|
||||||
|
"assets/community-Do_t1zw9.png"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resources/themes/dusk/js/app.js": {
|
"resources/themes/atom/js/app.js": {
|
||||||
"file": "assets/app-Dnwim5HE.js",
|
"file": "assets/app-wUWplMFd.js",
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"src": "resources/themes/dusk/js/app.js",
|
"src": "resources/themes/atom/js/app.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"imports": [
|
"imports": [
|
||||||
"_swiper-B7Yxk788.js",
|
"_axios-B2OU-7LW.js"
|
||||||
"_axios-BgauIbGG.js"
|
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"assets/app-DU8Y3NnC.css"
|
"assets/app-CeYfhhVD.css"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
<div class="w-22 h-22 rounded-full overflow-hidden border-4 transition-all duration-300 group-hover:scale-110 group-hover:rotate-3"
|
<div class="w-22 h-22 rounded-full overflow-hidden border-4 transition-all duration-300 group-hover:scale-110 group-hover:rotate-3"
|
||||||
style="border-color: {{ $staffColor }}; box-shadow: 0 0 15px {{ $staffColor }}50, inset 0 0 15px {{ $staffColor }}20;">
|
style="border-color: {{ $staffColor }}; box-shadow: 0 0 15px {{ $staffColor }}50, inset 0 0 15px {{ $staffColor }}20;">
|
||||||
<img style="image-rendering: pixelated;"
|
<img style="image-rendering: pixelated;"
|
||||||
src="{{ setting('avatar_imager') }}{{ $user->look }}&direction=2&head_direction=3&gesture=sml&action=wav&size=n"
|
src="{{ setting('avatar_imager') }}{{ $user->look }}&direction=3&head_direction=3&gesture=sml&action=wlk%2Cwav&size=l&img_format=gif&size=n"
|
||||||
alt="{{ $user->username }}"
|
alt="{{ $user->username }}"
|
||||||
class="w-full h-full object-cover">
|
class="w-full h-full object-cover">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,140 +1,139 @@
|
|||||||
<x-app-layout>
|
<x-app-layout>
|
||||||
@push('title', auth()->user()->username)
|
@push('title', auth()->user()->username)
|
||||||
|
|
||||||
<div class="col-span-12 space-y-4 lg:col-span-9">
|
<div class="col-span-12 space-y-4 lg:col-span-9">
|
||||||
<x-user.me-backdrop :user="$user" />
|
<x-user.me-backdrop :user="$user" />
|
||||||
|
|
||||||
<div class="rounded-xl border-2 shadow-sm lg:flex lg:items-center lg:justify-between overflow-hidden"
|
<div class="rounded-xl border-2 shadow-sm lg:flex lg:items-center lg:justify-between overflow-hidden"
|
||||||
style="background-color: var(--color-surface); border-color: var(--border-color);">
|
style="background-color: var(--color-surface); border-color: var(--border-color);">
|
||||||
<div class="flex items-center gap-3 p-3" style="background-color: var(--color-primary);">
|
<div class="flex items-center gap-3 p-3" style="background-color: var(--color-primary);">
|
||||||
<img src="{{ asset('/assets/images/icons/online-friends.png') }}" alt="{{ __('Online Friends') }}"
|
<img src="{{ asset('/assets/images/icons/online-friends.png') }}" alt="{{ __('Online Friends') }}"
|
||||||
class="w-6 h-6">
|
class="w-6 h-6">
|
||||||
<span class="text-sm font-semibold" style="color: var(--button-text-color)">{{ __('Online Friends') }}</span>
|
<span class="text-sm font-semibold" style="color: var(--button-text-color)">{{ __('Online Friends') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex flex-wrap items-center justify-center gap-3 p-3">
|
<div class="relative flex flex-wrap items-center justify-center gap-3 p-3">
|
||||||
@foreach ($onlineFriends as $friend)
|
@foreach ($onlineFriends as $friend)
|
||||||
<div data-popover-target="friend-{{ $friend->username }}"
|
<div data-popover-target="friend-{{ $friend->username }}"
|
||||||
style="image-rendering: pixelated; background-image: url({{ setting('avatar_imager') }}{{ $friend->look }}&direction=2&head_direction=3&gesture=sml&action=wav&headonly=1&size=s)"
|
class="inline-block h-12 w-12 rounded-full border-2 bg-center bg-no-repeat hover:border-[var(--border-color)] hover:scale-110 transition-all duration-300 cursor-pointer"
|
||||||
class="inline-block h-12 w-12 rounded-full border-2 bg-center bg-no-repeat hover:border-[var(--border-color)] hover:scale-110 transition-all duration-300 cursor-pointer"
|
style="image-rendering: pixelated; border-color: var(--color-text-muted); background-image: url({{ setting('avatar_imager') }}{{ $friend->look }}&direction=3&head_direction=3&gesture=sml&action=wlk,wav&size=l&img_format=gif);">
|
||||||
style="border-color: var(--color-text-muted)">
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-popover id="friend-{{ $friend->username }}" role="tooltip"
|
<div data-popover id="friend-{{ $friend->username }}" role="tooltip"
|
||||||
class="invisible absolute z-10 inline-block w-64 rounded-lg border shadow-xl opacity-0 transition-opacity duration-300"
|
class="invisible absolute z-10 inline-block w-64 rounded-lg border shadow-xl opacity-0 transition-opacity duration-300"
|
||||||
style="background-color: var(--color-surface); border-color: var(--color-text-muted); color: var(--color-text);">
|
style="background-color: var(--color-surface); border-color: var(--color-text-muted); color: var(--color-text);">
|
||||||
<div class="rounded-t-lg border-b px-4 py-3" style="border-color: var(--color-text-muted); background-color: var(--color-surface);">
|
<div class="rounded-t-lg border-b px-4 py-3" style="border-color: var(--color-text-muted); background-color: var(--color-surface);">
|
||||||
<div class="flex items-center justify-between font-semibold" style="color: var(--color-text)">
|
<div class="flex items-center justify-between font-semibold" style="color: var(--color-text)">
|
||||||
{{ $friend->username }}
|
{{ $friend->username }}
|
||||||
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-3 space-y-1">
|
<div class="px-4 py-3 space-y-1">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span class="font-medium" style="color: var(--color-text-muted)">{{ __('Motto') }}:</span>
|
<span class="font-medium" style="color: var(--color-text-muted)">{{ __('Motto') }}:</span>
|
||||||
<span class="ml-1" style="color: var(--color-text)">{{ $friend->motto }}</span>
|
<span class="ml-1" style="color: var(--color-text)">{{ $friend->motto }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span class="font-medium" style="color: var(--color-text-muted)">{{ __('Online Since') }}:</span>
|
<span class="font-medium" style="color: var(--color-text-muted)">{{ __('Online Since') }}:</span>
|
||||||
<span class="ml-1" style="color: var(--color-text)">{{ date(config('habbo.site.date_format'), $friend->last_online) }}</span>
|
<span class="ml-1" style="color: var(--color-text)">{{ date(config('habbo.site.date_format'), $friend->last_online) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-popper-arrow class="absolute h-2 w-2 rotate-45 border-l border-b" style="border-color: var(--color-text-muted); background-color: var(--color-surface);"></div>
|
<div data-popper-arrow class="absolute h-2 w-2 rotate-45 border-l border-b" style="border-color: var(--color-text-muted); background-color: var(--color-surface);"></div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-content.content-card icon="friends-icon" classes="border-2 dark:border-gray-900" style="border-color: var(--border-color);">
|
<x-content.content-card icon="friends-icon" classes="border-2 dark:border-gray-900" style="border-color: var(--border-color);">
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ sprintf(__('User Referrals (%s/%s)'), auth()->user()->referrals->referrals_total ?? 0, setting('referrals_needed')) }}
|
{{ sprintf(__('User Referrals (%s/%s)'), auth()->user()->referrals->referrals_total ?? 0, setting('referrals_needed')) }}
|
||||||
</x-slot:title>
|
</x-slot:title>
|
||||||
|
|
||||||
<x-slot:under-title>
|
<x-slot:under-title>
|
||||||
{{ __('Referral new users and be rewarded by in-game goods') }}
|
{{ __('Referral new users and be rewarded by in-game goods') }}
|
||||||
</x-slot:under-title>
|
</x-slot:under-title>
|
||||||
|
|
||||||
<div class="px-2 text-sm text-[var(--color-text)] space-y-4">
|
<div class="px-2 text-sm text-[var(--color-text)] space-y-4">
|
||||||
<p>
|
<p>
|
||||||
{{ __('Here at :hotel we have added a referral system, allowing you to obtain a bonus for every :needed users that registers through your referral link will allow you to claim a reward of :amount diamonds!', ['hotel' => setting('hotel_name'), 'needed' => setting('referrals_needed'), 'amount' => setting('referral_reward_amount')]) }}
|
{{ __('Here at :hotel we have added a referral system, allowing you to obtain a bonus for every :needed users that registers through your referral link will allow you to claim a reward of :amount diamonds!', ['hotel' => setting('hotel_name'), 'needed' => setting('referrals_needed'), 'amount' => setting('referral_reward_amount')]) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-3">
|
<div class="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-3">
|
||||||
<p class="text-yellow-400/80 text-xs">
|
<p class="text-yellow-400/80 text-xs">
|
||||||
⚠️ {{ __('Boosting referrals by making own accounts will lead to removal of all progress, currency, inventory and a potential ban') }}
|
⚠️ {{ __('Boosting referrals by making own accounts will lead to removal of all progress, currency, inventory and a potential ban') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-12 gap-2">
|
<div class="grid grid-cols-12 gap-2">
|
||||||
<x-form.input classes="col-span-12 md:col-span-10 text-sm" name="referral"
|
<x-form.input classes="col-span-12 md:col-span-10 text-sm" name="referral"
|
||||||
value="{{ sprintf('%s/register/%s', config('habbo.site.site_url'), auth()->user()->referral_code) }}"
|
value="{{ sprintf('%s/register/%s', config('habbo.site.site_url'), auth()->user()->referral_code) }}"
|
||||||
:autofocus="false" :readonly="true" />
|
:autofocus="false" :readonly="true" />
|
||||||
|
|
||||||
<div class="col-span-12 flex md:col-span-2" onclick="copyCode()">
|
<div class="col-span-12 flex md:col-span-2" onclick="copyCode()">
|
||||||
<x-form.secondary-button>
|
<x-form.secondary-button>
|
||||||
{{ __('Copy code') }}
|
{{ __('Copy code') }}
|
||||||
</x-form.secondary-button>
|
</x-form.secondary-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (auth()->user()->referrals?->referrals_total >= (int) setting('referrals_needed'))
|
@if ((auth()->user()->referrals->referrals_total ?? 0) >= (int) setting('referrals_needed'))
|
||||||
<a href="{{ route('claim.referral-reward') }}" class="text-decoration-none">
|
<a href="{{ route('claim.referral-reward') }}" class="text-decoration-none">
|
||||||
<x-form.secondary-button classes="mt-2">
|
<x-form.secondary-button classes="mt-2">
|
||||||
{{ __('Claim your referrals reward!') }}
|
{{ __('Claim your referrals reward!') }}
|
||||||
</x-form.secondary-button>
|
</x-form.secondary-button>
|
||||||
</a>
|
</a>
|
||||||
@else
|
@else
|
||||||
<button disabled class="mt-2 w-full rounded bg-[var(--color-text-muted)] p-3 text-[var(--color-text)] cursor-not-allowed opacity-60">
|
<button disabled class="mt-2 w-full rounded bg-[var(--color-text-muted)] p-3 text-[var(--color-text)] cursor-not-allowed opacity-60">
|
||||||
{{ sprintf(__('You need to refer :needed more users, before being able to claim your reward', ['needed' =>auth()->user()->referralsNeeded() ?? 0]),auth()->user()->referrals->referrals_total ?? 0) }}
|
{{ sprintf(__('You need to refer :needed more users, before being able to claim your reward', ['needed' => max(0, (int) setting('referrals_needed') - (auth()->user()->referrals->referrals_total ?? 0))]), auth()->user()->referrals->referrals_total ?? 0) }}
|
||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</x-content.content-card>
|
</x-content.content-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-12 space-y-4 lg:col-span-3">
|
<div class="col-span-12 space-y-4 lg:col-span-3">
|
||||||
<div class="relative w-full" style="height: 213px">
|
<div class="relative w-full" style="height: 213px">
|
||||||
<div class="relative swiper articles-slider">
|
<div class="relative swiper articles-slider">
|
||||||
<div class="swiper-wrapper">
|
<div class="swiper-wrapper">
|
||||||
@forelse ($articles as $article)
|
@forelse ($articles as $article)
|
||||||
<x-article-card :for-slider="true" :article="$article" />
|
<x-article-card :for-slider="true" :article="$article" />
|
||||||
@empty
|
@empty
|
||||||
<x-filler-article-card />
|
<x-filler-article-card />
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="swiper-pagination" style="bottom: 0px !important; z-index: 0;"></div>
|
<div class="swiper-pagination" style="bottom: 0px !important; z-index: 0;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="!mt-3">
|
<div class="!mt-3">
|
||||||
<x-user.discord-widget />
|
<x-user.discord-widget />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@push('javascript')
|
@push('javascript')
|
||||||
<script>
|
<script>
|
||||||
var Toast = Swal.mixin({
|
var Toast = Swal.mixin({
|
||||||
toast: true,
|
toast: true,
|
||||||
position: "top-end",
|
position: "top-end",
|
||||||
showConfirmButton: false,
|
showConfirmButton: false,
|
||||||
timer: 4000,
|
timer: 4000,
|
||||||
timerProgressBar: true,
|
timerProgressBar: true,
|
||||||
didOpen: (toast) => {
|
didOpen: (toast) => {
|
||||||
toast.addEventListener("mouseenter", Swal.stopTimer);
|
toast.addEventListener("mouseenter", Swal.stopTimer);
|
||||||
toast.addEventListener("mouseleave", Swal.resumeTimer);
|
toast.addEventListener("mouseleave", Swal.resumeTimer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function copyCode() {
|
function copyCode() {
|
||||||
let copyText = document.querySelector("#referral");
|
let copyText = document.querySelector("#referral");
|
||||||
copyText.select();
|
copyText.select();
|
||||||
document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
|
|
||||||
Toast.fire({
|
Toast.fire({
|
||||||
icon: "success",
|
icon: "success",
|
||||||
title: '{{ __('Your referral code has been copied to your clipbord!') }}'
|
title: '{{ __('Your referral code has been copied to your clipboard!') }}'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
@props(['backups'])
|
|
||||||
|
|
||||||
@if (empty($backups))
|
|
||||||
<div style="text-align:center;padding:32px;color:#64748b;background:#f8fafc;border-radius:12px;border:1px solid #e2e8f0;">
|
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2" style="margin:0 auto 16px;display:block;">
|
|
||||||
<path d="M21 8v13H3V8M1 3h22v5H1zM10 12h4"/>
|
|
||||||
</svg>
|
|
||||||
<div style="font-size:14px;">{{ __('commandocentrum.no_backups') }}</div>
|
|
||||||
<div style="font-size:12px;margin-top:8px;">{{ __('commandocentrum.backups_auto') }}</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;">
|
|
||||||
@foreach ($backups as $backup)
|
|
||||||
@php
|
|
||||||
$dateFormatted = str_replace('_', ' ', $backup['date']);
|
|
||||||
@endphp
|
|
||||||
<div style="background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
|
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
|
|
||||||
<div style="display:flex;align-items:center;gap:8px;">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2">
|
|
||||||
<path d="M21 8v13H3V8M1 3h22v5H1zM10 12h4"/>
|
|
||||||
</svg>
|
|
||||||
<span style="font-weight:600;color:#1e293b;font-size:14px;">{{ $backup['jar'] }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="font-size:12px;color:#64748b;margin-bottom:12px;">{{ $dateFormatted }}</div>
|
|
||||||
<button
|
|
||||||
wire:click="restoreBackup('{{ $backup['name'] }}')"
|
|
||||||
style="width:100%;background:linear-gradient(135deg,#3b82f6,#2563eb);padding:10px;border-radius:8px;color:white;border:none;cursor:pointer;font-weight:600;font-size:13px;box-shadow:0 2px 8px rgba(37,99,235,0.3);transition:transform 0.2s;"
|
|
||||||
onmouseover="this.style.transform='translateY(-1px)'"
|
|
||||||
onmouseout="this.style.transform='translateY(0)'"
|
|
||||||
>
|
|
||||||
🔄 {{ __('commandocentrum.restore') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
@props([
|
|
||||||
'emulatorBranchesHtml',
|
|
||||||
'emulatorStatusHtml',
|
|
||||||
])
|
|
||||||
|
|
||||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;">
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.github_url') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_github_url" placeholder="https://github.com/..." style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.jar_direct_url') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_jar_direct_url" placeholder="https://..." style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.jar_path') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_jar_path" placeholder="/root/emulator" style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.source_repo') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_source_repo" placeholder="user/repo" style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.source_path') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_source_path" placeholder="/var/www/emulator" style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.branch') }}</label>
|
|
||||||
<select wire:model="data.emulator_github_branch" style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;background:#fff;">
|
|
||||||
{!! $emulatorBranchesHtml !!}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.db_host') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_database_host" placeholder="127.0.0.1" style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.db_name') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_database_name" placeholder="habbo" style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.service_name') }}</label>
|
|
||||||
<input type="text" wire:model="data.emulator_service_name" placeholder="arcturus" style="width:100%;padding:10px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;" />
|
|
||||||
</div>
|
|
||||||
<div style="grid-column:span 3;">
|
|
||||||
<label style="display:block;font-size:12px;font-weight:600;color:#475569;margin-bottom:6px;">{{ __('commandocentrum.status') }}</label>
|
|
||||||
{!! $emulatorStatusHtml !!}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
@props([
|
|
||||||
'emulatorOnline',
|
|
||||||
'jarExists',
|
|
||||||
'serviceName',
|
|
||||||
'sourceCommit',
|
|
||||||
'remoteVersion',
|
|
||||||
'canBuild',
|
|
||||||
'jarPath',
|
|
||||||
'sourcePath',
|
|
||||||
])
|
|
||||||
|
|
||||||
@php
|
|
||||||
$sourceCommitShort = substr($sourceCommit, 0, 7);
|
|
||||||
$hasUpdate = $sourceCommit !== 'N/A' && $remoteVersion !== 'N/A' && $sourceCommitShort !== $remoteVersion;
|
|
||||||
$updateColor = $hasUpdate ? '#f59e0b' : '#22c55e';
|
|
||||||
$updateText = $hasUpdate ? '🔄 ' . __('commandocentrum.update_available') : '✓ ' . __('commandocentrum.up_to_date');
|
|
||||||
$btnColor = $hasUpdate ? '#f59e0b' : '#3b82f6';
|
|
||||||
$btnGradient = $hasUpdate ? 'linear-gradient(135deg,#f59e0b,#d97706)' : 'linear-gradient(135deg,#3b82f6,#2563eb)';
|
|
||||||
$btnText = $hasUpdate ? '⚡ ' . __('commandocentrum.update') : '🔄 ' . __('commandocentrum.rebuild');
|
|
||||||
|
|
||||||
$jarFileName = '';
|
|
||||||
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])) {
|
|
||||||
$jarFileName = basename($matches[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div style="display:flex;flex-direction:column;gap:8px;font-size:13px;">
|
|
||||||
<div style="display:flex;gap:16px;">
|
|
||||||
@if ($emulatorOnline)
|
|
||||||
<span style="color:#22c55e;">✓ {{ __('commandocentrum.online') }}</span>
|
|
||||||
@else
|
|
||||||
<span style="color:#ef4444;">✗ {{ __('commandocentrum.offline') }}</span>
|
|
||||||
@endif
|
|
||||||
@if ($jarExists)
|
|
||||||
<span style="color:#22c55e;">✓ JAR {{ __('commandocentrum.ok') }}</span>
|
|
||||||
@else
|
|
||||||
<span style="color:#ef4444;">✗ JAR {{ __('commandocentrum.missing') }}</span>
|
|
||||||
@endif
|
|
||||||
<span style="color:#3b82f6;">{{ __('commandocentrum.service') }}: {{ e($serviceName) }}</span>
|
|
||||||
</div>
|
|
||||||
<div style="padding-top:8px;border-top:1px solid #e2e8f0;">
|
|
||||||
<div style="font-weight:600;color:#475569;margin-bottom:8px;">GitHub {{ __('commandocentrum.status') }}:</div>
|
|
||||||
<div style="background:{{ $hasUpdate ? '#fef3c7' : '#dcfce7' }};padding:12px;border-radius:8px;margin-bottom:12px;">
|
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;">
|
|
||||||
<div style="display:flex;align-items:center;gap:8px;font-weight:600;color:{{ $updateColor }};">
|
|
||||||
{{ $updateText }}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
wire:click="checkEmulatorUpdates"
|
|
||||||
style="background:{{ $btnGradient }};padding:8px 16px;border-radius:6px;color:white;border:none;cursor:pointer;font-weight:600;font-size:12px;box-shadow:0 2px 8px rgba(0,0,0,0.2);transition:transform 0.2s,box-shadow 0.2s;"
|
|
||||||
onmouseover="this.style.transform='translateY(-1px)';this.style.boxShadow='0 4px 12px rgba(0,0,0,0.3)'"
|
|
||||||
onmouseout="this.style.transform='translateY(0)';this.style.boxShadow='0 2px 8px rgba(0,0,0,0.2)'"
|
|
||||||
>
|
|
||||||
{{ $btnText }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #f1f5f9;"><span>{{ __('commandocentrum.latest') }}:</span><span style="color:#22c55e;font-weight:600;">✓ {{ e($remoteVersion) }}</span></div>
|
|
||||||
<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #f1f5f9;"><span>{{ __('commandocentrum.source') }}:</span><span style="color:#22c55e;font-weight:600;">✓ {{ $sourceCommitShort }}</span></div>
|
|
||||||
@if ($jarFileName !== '')
|
|
||||||
<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #f1f5f9;"><span>{{ __('commandocentrum.jars') }}:</span><span style="color:#22c55e;font-weight:600;">✓ {{ e($jarFileName) }}</span></div>
|
|
||||||
@endif
|
|
||||||
@if ($canBuild)
|
|
||||||
<div style="display:flex;justify-content:space-between;padding:6px 0;"><span>{{ __('commandocentrum.method') }}:</span><span style="color:#22c55e;font-weight:600;">✓ {{ __('commandocentrum.maven_pom') }}</span></div>
|
|
||||||
@else
|
|
||||||
<div style="display:flex;justify-content:space-between;padding:6px 0;"><span>{{ __('commandocentrum.method') }}:</span><span style="color:#f59e0b;font-weight:600;">⚠️ {{ __('commandocentrum.no_pom') }}</span></div>
|
|
||||||
@endif
|
|
||||||
<div style="margin-top:12px;padding:12px;background:#f8fafc;border-radius:8px;">
|
|
||||||
<div style="font-size:11px;color:#64748b;font-weight:600;margin-bottom:8px;">{{ __('commandocentrum.method') }}:</div>
|
|
||||||
@if ($jarExists)
|
|
||||||
<div style="color:#3b82f6;font-weight:600;">📦 {{ __('commandocentrum.jar_download_restart') }}</div>
|
|
||||||
@elseif ($canBuild)
|
|
||||||
<div style="color:#3b82f6;font-weight:600;">🔨 {{ __('commandocentrum.maven_build_restart') }}</div>
|
|
||||||
@else
|
|
||||||
<div style="color:#64748b;">{{ __('commandocentrum.manual_download') }}</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||