Files
Virtnet.bond/extensions/Servers/Proxmox/Livewire/Display.php
2025-11-30 11:57:22 +01:00

289 lines
9.3 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;
if (!isset($options[$this->selectedDisplay])) {
$options[$this->selectedDisplay] = [
'label' => strtoupper($this->selectedDisplay) . ' (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,
]);
}
}