- Change listStates method to accept nullable integer for limit parameter. - Modify TechnitiumDns class to return structured error messages on failure. - Enhance VNC and xterm popup routes with user authorization checks. - Update backups view to improve loading states and add unique keys for dynamic elements. - Refactor cloud-init view to ensure proper data binding and improve user experience. - Add wire:key attributes to various elements in views for better Livewire performance. - Improve error handling and user feedback in the firewall view. - Update server overview and statistics views to handle potential null values gracefully. - Add new migration files to extend database schema with user-set icons and notes for Proxmox nodes.
291 lines
9.4 KiB
PHP
291 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace Paymenter\Extensions\Servers\Proxmox\Livewire;
|
|
|
|
use Illuminate\Validation\Rule;
|
|
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
|
|
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
|
|
|
|
class Display extends Component
|
|
{
|
|
/**
|
|
* Map of supported display devices and their metadata.
|
|
*/
|
|
protected array $displayPresets = [
|
|
'std' => [
|
|
'label' => 'Standard VGA (std)',
|
|
'description' => 'Compatible with most operating systems. Recommended fallback option.',
|
|
'supports_memory' => false,
|
|
],
|
|
'virtio' => [
|
|
'label' => 'VirtIO-GPU',
|
|
'description' => 'Modern paravirtualized GPU with low overhead and better performance.',
|
|
'supports_memory' => true,
|
|
],
|
|
'vmware' => [
|
|
'label' => 'VMware Compatible (vmware)',
|
|
'description' => 'Useful when guest OS expects VMware virtual hardware.',
|
|
'supports_memory' => false,
|
|
],
|
|
'cirrus' => [
|
|
'label' => 'Legacy Cirrus (cirrus)',
|
|
'description' => 'Legacy compatibility mode for older operating systems.',
|
|
'supports_memory' => false,
|
|
],
|
|
'serial0' => [
|
|
'label' => 'Serial Console (serial0)',
|
|
'description' => 'Disables graphical console and enables serial output only.',
|
|
'supports_memory' => false,
|
|
],
|
|
'none' => [
|
|
'label' => 'No Display (none)',
|
|
'description' => 'Detach the VGA device entirely (headless).',
|
|
'supports_memory' => false,
|
|
],
|
|
];
|
|
|
|
public string $selectedDisplay = 'std';
|
|
|
|
public ?int $displayMemory = null;
|
|
|
|
public ?string $currentRawValue = null;
|
|
|
|
public bool $tabletEnabled = true;
|
|
|
|
public array $original = [];
|
|
|
|
public function mount(Server $server): void
|
|
{
|
|
parent::mount($server);
|
|
|
|
$this->loadDisplayConfiguration(false);
|
|
}
|
|
|
|
/**
|
|
* Reload the display configuration from Proxmox.
|
|
*/
|
|
public function loadDisplayConfiguration(bool $notify = true): void
|
|
{
|
|
try {
|
|
$proxmox = new Proxmox;
|
|
$config = $proxmox->request(
|
|
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config'
|
|
)->json()['data'] ?? [];
|
|
|
|
[$type, $memory] = $this->parseDisplayString($config['vga'] ?? null);
|
|
|
|
$this->selectedDisplay = $type;
|
|
$this->displayMemory = $memory;
|
|
$this->currentRawValue = $config['vga'] ?? null;
|
|
$this->tabletEnabled = !array_key_exists('tablet', $config) || (bool) $config['tablet'];
|
|
|
|
// Always update original values to reflect current Proxmox state for synced comparison
|
|
$this->original = [
|
|
'selectedDisplay' => $this->selectedDisplay,
|
|
'displayMemory' => $this->displayMemory,
|
|
'tabletEnabled' => $this->tabletEnabled,
|
|
];
|
|
|
|
if ($notify) {
|
|
$this->notify('Display configuration refreshed.', 'success');
|
|
}
|
|
} catch (\Exception $e) {
|
|
if ($notify) {
|
|
$this->notify('Failed to load display settings: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the chosen display configuration.
|
|
*/
|
|
public function saveDisplaySettings(): void
|
|
{
|
|
$validated = $this->validate($this->rules(), $this->messages());
|
|
|
|
$payload = $validated['selectedDisplay'];
|
|
if ($this->supportsMemory($validated['selectedDisplay']) && isset($validated['displayMemory'])) {
|
|
$payload .= ',memory=' . $validated['displayMemory'];
|
|
}
|
|
|
|
try {
|
|
$proxmox = new Proxmox;
|
|
$proxmox->request(
|
|
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
|
|
'put',
|
|
[
|
|
'vga' => $payload,
|
|
'tablet' => $this->tabletEnabled ? 1 : 0,
|
|
]
|
|
);
|
|
|
|
$this->currentRawValue = $payload;
|
|
|
|
// Update original values after successful save
|
|
$this->original = [
|
|
'selectedDisplay' => $this->selectedDisplay,
|
|
'displayMemory' => $this->displayMemory,
|
|
'tabletEnabled' => $this->tabletEnabled,
|
|
];
|
|
|
|
$this->notify('Display configuration updated successfully. Reboot the VPS for changes to take effect.', 'success');
|
|
} catch (\Exception $e) {
|
|
$this->notify('Unable to update display settings: ' . $e->getMessage(), 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validation rules.
|
|
*/
|
|
protected function rules(): array
|
|
{
|
|
return [
|
|
'selectedDisplay' => [
|
|
'required',
|
|
Rule::in(array_keys($this->displayOptions)),
|
|
],
|
|
'displayMemory' => [
|
|
function ($attribute, $value, $fail) {
|
|
if (!$this->supportsMemory($this->selectedDisplay)) {
|
|
return;
|
|
}
|
|
|
|
if ($value === null || $value === '') {
|
|
return $fail('Select the amount of video memory to allocate.');
|
|
}
|
|
|
|
if (!is_numeric($value) || (int) $value != $value) {
|
|
return $fail('Video memory must be a whole number.');
|
|
}
|
|
|
|
$intValue = (int) $value;
|
|
if ($intValue < 16 || $intValue > 256) {
|
|
return $fail('Video memory must be between 16 MB and 256 MB.');
|
|
}
|
|
},
|
|
],
|
|
'tabletEnabled' => [
|
|
'boolean',
|
|
],
|
|
];
|
|
}
|
|
|
|
protected function messages(): array
|
|
{
|
|
return [
|
|
'selectedDisplay.in' => 'The selected display adapter is not available.',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Handle Livewire updates when display type changes.
|
|
*/
|
|
public function updatedSelectedDisplay(string $value): void
|
|
{
|
|
if (!$this->supportsMemory($value)) {
|
|
$this->displayMemory = null;
|
|
} elseif ($this->displayMemory === null) {
|
|
$this->displayMemory = 64;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derived list of display options that ensures the current selection is available.
|
|
*/
|
|
public function getDisplayOptionsProperty(): array
|
|
{
|
|
$options = $this->displayPresets;
|
|
|
|
// Only add the original loaded display type as a custom option, not player-controlled input
|
|
$originalDisplay = $this->original['selectedDisplay'] ?? null;
|
|
if ($originalDisplay && !isset($options[$originalDisplay])) {
|
|
$options[$originalDisplay] = [
|
|
'label' => strtoupper($originalDisplay) . ' (custom)',
|
|
'description' => 'Currently configured in Proxmox. You can keep it or choose another option.',
|
|
'supports_memory' => false,
|
|
];
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
public function getSupportsMemoryProperty(): bool
|
|
{
|
|
return $this->supportsMemory($this->selectedDisplay);
|
|
}
|
|
|
|
protected function supportsMemory(?string $type): bool
|
|
{
|
|
return $type !== null
|
|
&& ($this->displayOptions[$type]['supports_memory'] ?? false);
|
|
}
|
|
|
|
/**
|
|
* Parse Proxmox display string into [type, memory].
|
|
*/
|
|
protected function parseDisplayString(?string $value): array
|
|
{
|
|
if (!$value || trim($value) === '') {
|
|
return ['std', null];
|
|
}
|
|
|
|
$segments = array_map('trim', explode(',', $value));
|
|
$type = array_shift($segments) ?: 'std';
|
|
$memory = null;
|
|
|
|
foreach ($segments as $segment) {
|
|
if (!str_contains($segment, '=')) {
|
|
continue;
|
|
}
|
|
|
|
[$key, $val] = array_map('trim', explode('=', $segment, 2));
|
|
if (strtolower($key) === 'memory' && is_numeric($val)) {
|
|
$memory = min(256, max(16, (int) $val));
|
|
}
|
|
}
|
|
|
|
if (!$this->supportsMemory($type)) {
|
|
$memory = null;
|
|
} elseif ($memory === null) {
|
|
$memory = 64;
|
|
}
|
|
|
|
return [$type, $memory];
|
|
}
|
|
|
|
/**
|
|
* Check if Display Adapter settings are synced with the original values.
|
|
*/
|
|
public function getDisplayAdapterSyncedProperty(): bool
|
|
{
|
|
// If original hasn't been loaded yet, consider synced
|
|
if (!isset($this->original['selectedDisplay'])) {
|
|
return true;
|
|
}
|
|
|
|
$originalSelectedDisplay = (string)($this->original['selectedDisplay'] ?? 'std');
|
|
$currentSelectedDisplay = (string)$this->selectedDisplay;
|
|
$displayMatch = $currentSelectedDisplay === $originalSelectedDisplay;
|
|
|
|
$originalDisplayMemory = $this->original['displayMemory'] ?? null;
|
|
$currentDisplayMemory = $this->displayMemory;
|
|
// Both null means no memory setting, which matches
|
|
$memoryMatch = ($originalDisplayMemory === null && $currentDisplayMemory === null)
|
|
|| ($originalDisplayMemory === $currentDisplayMemory);
|
|
|
|
$originalTabletEnabled = (bool)($this->original['tabletEnabled'] ?? true);
|
|
$currentTabletEnabled = (bool)$this->tabletEnabled;
|
|
$tabletMatch = $originalTabletEnabled === $currentTabletEnabled;
|
|
|
|
return $displayMatch && $memoryMatch && $tabletMatch;
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('proxmox::display')->layoutData([
|
|
'sidebar' => true,
|
|
]);
|
|
}
|
|
}
|
|
|