Files
Virtnet.bond/extensions/Servers/Proxmox/Livewire/System.php

890 lines
31 KiB
PHP

<?php
namespace Paymenter\Extensions\Servers\Proxmox\Livewire;
use Illuminate\Support\Collection;
use Livewire\Attributes\Locked;
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
class System extends Component
{
public string $machineType = '';
public string $biosType = 'seabios';
public string $cpuType = 'kvm64';
public array $selectedCpuFlags = [];
public bool $kvmEnabled = true;
public bool $acpiEnabled = true;
public string $smbiosSettings = '';
public array $availableMachines = [];
public array $availableCpuTypes = [];
public array $suggestedMachines = [];
protected array $machineAliases = [
'pc-q35' => 'pc-q35-8.1',
'pc-i440fx' => 'pc-i440fx-8.1',
];
public array $availableBiosOptions = [
['value' => 'seabios', 'label' => 'SeaBIOS (Legacy)'],
['value' => 'ovmf', 'label' => 'OVMF (UEFI)'],
];
public array $availableCpuFlags = [];
public array $flagOptions = [
'aes' => 'AES',
'hv-evmcs' => 'Hyper-V EVMCS',
'hvlibflush' => 'Hyper-V LIBFLUSH',
'pdpe1gb' => '1GB Pages',
'amd-no-ssb' => 'AMD No SSB',
'amd-ssbd' => 'AMD SSBD',
'virt-ssbd' => 'Virt SSBD',
'ibpd' => 'IBPD',
'ssbd' => 'SSBD',
'spec-ctrl' => 'Speculative Control',
'pcid' => 'PCID',
'md-clear' => 'MD Clear',
'nested-virt' => 'Nested Virtualization',
];
public array $efiDisks = [];
public array $tpmDevices = [];
public bool $newEfiPreenroll = false;
public string $newTpmVersion = 'v2.0';
#[Locked]
public array $original = [];
public function mount(Server $server): void
{
parent::mount($server);
$this->loadSystemConfiguration(false);
$this->loadCapabilities();
}
public function loadSystemConfiguration(bool $notify = true): void
{
try {
$proxmox = new Proxmox;
$config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? [];
$this->machineType = (string) ($config['machine'] ?? '');
$this->biosType = (string) ($config['bios'] ?? $this->biosType);
[$detectedCpuType, $detectedFlags] = $this->extractCpuDetails(
(string) ($config['cpu'] ?? ''),
(string) ($config['flags'] ?? '')
);
$this->cpuType = $detectedCpuType !== '' ? $detectedCpuType : $this->cpuType;
$this->selectedCpuFlags = $this->filterAllowedFlags($detectedFlags);
$this->kvmEnabled = !array_key_exists('kvm', $config) || (bool) $config['kvm'];
$this->acpiEnabled = !array_key_exists('acpi', $config) || (bool) $config['acpi'];
$this->smbiosSettings = (string) ($config['smbios1'] ?? '');
$this->parseDevices($config);
// Store original values for change tracking
$this->storeOriginalValues();
if ($notify) {
$this->notify('System configuration refreshed.', 'success');
}
} catch (\Exception $e) {
if ($notify) {
$this->notify('Failed to load system configuration: ' . $e->getMessage(), 'error');
}
}
}
protected function parseDevices(array $config): void
{
$efi = [];
$tpm = [];
foreach ($config as $key => $value) {
if (!is_string($key)) {
continue;
}
if (str_starts_with($key, 'efidisk')) {
$efi[$key] = $value;
} elseif (str_starts_with($key, 'tpmstate')) {
$tpm[$key] = $value;
}
}
$this->efiDisks = $efi;
$this->tpmDevices = $tpm;
}
protected function loadCapabilities(): void
{
$this->availableMachines = $this->fetchMachineOptions();
$this->suggestedMachines = $this->buildSuggestedMachines($this->availableMachines);
$this->availableCpuTypes = $this->fetchCpuOptions();
$this->availableCpuFlags = $this->fetchCpuFlags();
$this->selectedCpuFlags = $this->filterAllowedFlags($this->selectedCpuFlags);
}
protected function fetchMachineOptions(): array
{
try {
$proxmox = new Proxmox;
$response = $proxmox->request('/nodes/' . $this->server->node->name . '/capabilities/qemu/machines')->json()['data'] ?? [];
return collect($response)->map(function ($machine) {
return [
'value' => $machine['name'] ?? '',
'label' => trim(($machine['name'] ?? '') . ' ' . ($machine['desc'] ?? '')),
];
})->filter(fn ($item) => $item['value'] !== '')->values()->toArray();
} catch (\Exception $e) {
return [
['value' => 'pc-q35-8.1', 'label' => 'pc-q35-8.1 (Q35)'],
['value' => 'pc-i440fx-8.1', 'label' => 'pc-i440fx-8.1 (i440FX)'],
];
}
}
protected function buildSuggestedMachines(array $machines): array
{
$targets = collect([
'pc-q35' => 'q35 (PCIe / Modern)',
'pc-i440fx' => 'i440fx (Legacy PCI)',
])->map(function ($label, $base) {
return [
'label' => $label,
'match' => null,
'fallback' => $this->machineAliases[$base] ?? $base,
];
});
foreach ($machines as $machine) {
$value = $machine['value'] ?? '';
if ($value === '') {
continue;
}
foreach ($targets->keys() as $key) {
if (str_contains($value, $key) && is_null($targets[$key]['match'])) {
$targets[$key]['match'] = [
'value' => $value,
'label' => $targets[$key]['label'] . ' • ' . $value,
];
}
}
}
return $targets
->map(function ($entry) {
if ($entry['match']) {
return $entry['match'];
}
return [
'value' => $entry['fallback'],
'label' => $entry['label'],
];
})
->values()
->toArray();
}
protected function resolveMachineValue(string $value): string
{
if ($value === '') {
return $value;
}
$exactMatch = collect($this->availableMachines)
->pluck('value')
->contains($value);
if ($exactMatch) {
return $value;
}
foreach ($this->machineAliases as $alias => $resolved) {
if ($value === $alias || str_starts_with($value, $alias . '-')) {
return $resolved;
}
}
// Reject unknown/unrecognised values to prevent arbitrary strings reaching the API
return '';
}
protected function fetchCpuOptions(): array
{
$allowed = [
'x86-64-v2',
'x86-64-v2-aes',
'x86-64-v3',
'kvm64',
'qemu64',
];
try {
$proxmox = new Proxmox;
$response = $proxmox->request('/nodes/' . $this->server->node->name . '/capabilities/qemu/cpu')->json()['data'] ?? [];
return collect($response)
->map(function ($cpu) {
return [
'value' => $cpu['name'] ?? '',
'label' => trim(($cpu['name'] ?? '') . ' ' . ($cpu['vendor'] ?? '')),
];
})
->filter(fn ($item) => $item['value'] !== '' && in_array($item['value'], $allowed, true))
->whenEmpty(function () use ($allowed) {
return collect($allowed)->map(fn ($value) => [
'value' => $value,
'label' => strtoupper($value),
]);
})
->values()
->toArray();
} catch (\Exception $e) {
return collect($allowed)->map(fn ($value) => [
'value' => $value,
'label' => strtoupper($value),
])->toArray();
}
}
protected function fetchCpuFlags(): array
{
try {
$proxmox = new Proxmox;
$response = $proxmox->request('/nodes/' . $this->server->node->name . '/capabilities/qemu/cpu')->json()['data'] ?? [];
// Try to get flags from the 'host' CPU entry first, as it contains the actual node flags
$hostCpu = collect($response)->firstWhere('name', 'host');
if ($hostCpu && isset($hostCpu['flags']) && is_array($hostCpu['flags'])) {
return collect($hostCpu['flags'])
->unique()
->sort()
->values()
->toArray();
}
// Fallback: collect flags from all CPU entries
return collect($response)
->pluck('flags')
->filter(function ($flags) {
return is_array($flags) && !empty($flags);
})
->flatten()
->unique()
->sort()
->values()
->toArray();
} catch (\Exception $e) {
return [
'aes',
'avx',
'avx2',
'pcid',
'ssse3',
'ibpb',
'amd-ssbd',
'amd-no-ssb',
'md-clear',
'spec-ctrl',
];
}
}
public function saveSystemSettings(): void
{
// Validate biosType against allowed options
$allowedBios = array_column($this->availableBiosOptions, 'value');
if ($this->biosType !== '' && !in_array($this->biosType, $allowedBios, true)) {
$this->notify('Invalid BIOS type selected.', 'error');
return;
}
// Validate cpuType against allowed options
if ($this->cpuType !== '') {
$allowedCpuTypes = array_column($this->availableCpuTypes, 'value');
if (!empty($allowedCpuTypes) && !in_array($this->cpuType, $allowedCpuTypes, true)) {
$this->notify('Invalid CPU type selected.', 'error');
return;
}
}
$flags = $this->formatFlagsForSave($this->selectedCpuFlags);
$resolvedCpu = $this->cpuType !== '' ? $this->cpuType : null;
if ($resolvedCpu && $flags) {
$resolvedCpu .= ',flags=' . $flags;
}
$resolvedMachine = $this->machineType !== '' ? $this->resolveMachineValue($this->machineType) : null;
$payload = array_filter([
'machine' => $resolvedMachine !== '' ? $resolvedMachine : null,
'bios' => $this->biosType !== '' ? $this->biosType : null,
'cpu' => $resolvedCpu,
'kvm' => $this->kvmEnabled ? 1 : 0,
'acpi' => $this->acpiEnabled ? 1 : 0,
'smbios1' => $this->smbiosSettings !== '' ? $this->smbiosSettings : null,
], function ($value) {
return !is_null($value);
});
try {
$proxmox = new Proxmox;
$proxmox->request(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
'put',
$payload
);
$this->notify('System configuration updated. Reboot may be required for some changes.', 'success');
$this->loadSystemConfiguration(false);
} catch (\Exception $e) {
$this->notify('Failed to update system configuration: ' . $e->getMessage(), 'error');
}
}
protected function formatFlagsForSave(array $flags): ?string
{
$normalized = $this->filterAllowedFlags($flags);
if (empty($normalized)) {
return null;
}
return collect($normalized)
->map(fn ($flag) => '+' . $flag)
->implode(';');
}
protected function extractCpuDetails(string $rawCpu, string $separateFlags): array
{
$cpuType = trim($rawCpu);
$flagsString = trim($separateFlags);
if ($cpuType !== '' && str_contains($cpuType, ',')) {
$parts = array_map('trim', explode(',', $cpuType));
$cpuType = array_shift($parts) ?: '';
foreach ($parts as $part) {
if (str_starts_with($part, 'flags=')) {
$flagsString = substr($part, strlen('flags='));
}
}
}
$flags = $this->normalizeFlagList($flagsString);
return [$cpuType, $flags];
}
protected function normalizeFlagList(?string $flagsString): array
{
$clean = trim((string) $flagsString);
$clean = trim($clean, "\"'");
$clean = str_replace(["\r", "\n", ' '], '', $clean);
if ($clean === '') {
return [];
}
return collect(explode(';', $clean))
->map(fn ($flag) => ltrim($flag, '+'))
->filter(fn ($flag) => $flag !== '' && !str_starts_with($flag, '-'))
->unique()
->values()
->toArray();
}
protected function filterAllowedFlags($flags): array
{
return collect($flags ?? [])
->flatten()
->map(fn ($flag) => trim((string) $flag))
->filter(fn ($flag) => $flag !== '' && array_key_exists($flag, $this->flagOptions))
->unique()
->values()
->toArray();
}
public function updatedSelectedCpuFlags(): void
{
$this->selectedCpuFlags = $this->filterAllowedFlags($this->selectedCpuFlags);
}
public function removeDevice(string $deviceKey): void
{
if ($deviceKey === '') {
$this->notify('Invalid device reference.', 'error');
return;
}
// Only allow removal of EFI disk and TPM devices via this method
$isAllowed = (str_starts_with($deviceKey, 'efidisk') && isset($this->efiDisks[$deviceKey]))
|| (str_starts_with($deviceKey, 'tpmstate') && isset($this->tpmDevices[$deviceKey]));
if (!$isAllowed) {
$this->notify('Only EFI disk and TPM devices can be removed here.', 'error');
return;
}
// Check VM status - must be stopped to remove EFI/TPM devices
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to remove system devices.', 'error');
return;
}
// Get the device definition before removing it
$deviceDefinition = null;
if (str_starts_with($deviceKey, 'efidisk') && isset($this->efiDisks[$deviceKey])) {
$deviceDefinition = $this->efiDisks[$deviceKey];
} elseif (str_starts_with($deviceKey, 'tpmstate') && isset($this->tpmDevices[$deviceKey])) {
$deviceDefinition = $this->tpmDevices[$deviceKey];
}
try {
$proxmox = new Proxmox;
// Extract storage volume from definition before removing device
$storageVolume = null;
if ($deviceDefinition !== null) {
$parts = explode(',', $deviceDefinition, 2);
if (!empty($parts[0]) && str_contains($parts[0], ':')) {
$storageVolume = trim($parts[0]);
}
}
// Remove device from VM config FIRST
// This will log "update VM {vmid}: -delete {deviceKey}" in Proxmox
// Note: This may create an "unused" entry if the volume still exists
$proxmox->request(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
'put',
['delete' => $deviceKey]
);
// Immediately clean up any unused entries created by the removal
// This prevents orphaned "unused0" entries
if ($storageVolume !== null) {
$this->cleanupUnusedEntries($proxmox, $storageVolume);
}
// Then delete the storage volume (after device is removed from config)
if ($storageVolume !== null) {
$this->deleteStorageVolume($proxmox, $deviceDefinition);
}
$this->notify(strtoupper($deviceKey) . ' removed successfully.', 'success');
$this->loadSystemConfiguration(false);
} catch (\Exception $e) {
$this->notify('Failed to remove device: ' . $e->getMessage(), 'error');
}
}
protected function deleteStorageVolume(Proxmox $proxmox, string $deviceDefinition): void
{
// Parse storage:volume from definition (format: storage:volume,options...)
$parts = explode(',', $deviceDefinition, 2);
if (empty($parts[0])) {
return;
}
$storageVolume = trim($parts[0]);
// Check if it's a storage volume (format: storage:volume)
if (!str_contains($storageVolume, ':')) {
return;
}
[$storage, $volume] = explode(':', $storageVolume, 2);
// Only delete from cephpool (RBD storage) for now
if ($storage !== 'cephpool') {
return;
}
try {
// Delete the volume from storage using the full volid format
$proxmox->request(
'/nodes/' . $this->server->node->name . '/storage/' . $storage . '/content/' . $storageVolume,
'delete'
);
} catch (\Exception $e) {
// Volume might already be deleted or deletion might not be needed
// Continue silently as the device has already been removed from VM config
}
}
protected function cleanupUnusedEntries(Proxmox $proxmox, string $storageVolume): void
{
try {
// Get current VM config to check for unused entries
$config = $proxmox->request(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config'
)->json()['data'] ?? [];
// Extract just the volume name part (e.g., "vm-202500014-efidisk0" from "cephpool:vm-202500014-efidisk0")
$volumeName = $storageVolume;
if (str_contains($storageVolume, ':')) {
[, $volumeName] = explode(':', $storageVolume, 2);
}
// Find all unused entries that reference the deleted volume
foreach ($config as $key => $value) {
if (preg_match('/^unused\d+$/', $key) && is_string($value)) {
// Check if this unused entry references the deleted volume
// Match either the full storage volume or just the volume name
if (str_contains($value, $storageVolume) || str_contains($value, $volumeName)) {
// Delete the unused entry
try {
$proxmox->request(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
'put',
['delete' => $key]
);
} catch (\Exception $e) {
// Unused entry might already be cleaned up
// Continue to check other entries
}
}
}
}
} catch (\Exception $e) {
// Config fetch or cleanup might fail, but device is already removed
// Continue silently
}
}
public function addEfiDisk(): void
{
if (count($this->efiDisks) > 0) {
$this->notify('Only one EFI disk is allowed. Remove the existing EFI disk first.', 'error');
return;
}
// Check VM status - must be stopped to add EFI disk
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to add an EFI disk.', 'error');
return;
}
$slot = 'efidisk0';
$volumeName = 'vm-' . $this->server->vm_id . '-efidisk0';
try {
$proxmox = new Proxmox;
// For RBD storage, create the volume first
try {
$proxmox->request(
'/nodes/' . $this->server->node->name . '/storage/cephpool/content',
'post',
[
'filename' => $volumeName,
'size' => '4M',
'vmid' => $this->server->vm_id,
]
);
} catch (\Exception $e) {
// Volume might already exist or creation might not be needed
// Continue to add device to config
}
$definition = 'cephpool:' . $volumeName . ',size=4M';
if ($this->newEfiPreenroll) {
$definition .= ',pre-enrolled-keys=1';
}
$this->applyDeviceChange($slot, $definition, 'EFI disk added.');
$this->newEfiPreenroll = false;
} catch (\Exception $e) {
$this->notify('Failed to add EFI disk: ' . $e->getMessage(), 'error');
}
}
public function addTpmDevice(): void
{
if (count($this->tpmDevices) > 0) {
$this->notify('Only one TPM device is allowed. Remove the existing TPM device first.', 'error');
return;
}
// Check VM status - must be stopped to add TPM device
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to add a TPM device.', 'error');
return;
}
$version = trim($this->newTpmVersion);
$slot = 'tpmstate0';
if ($version === '') {
$this->notify('Select a TPM version.', 'error');
return;
}
// Validate TPM version against allowed values
$allowedVersions = ['v1.2', 'v2.0'];
if (!in_array($version, $allowedVersions, true)) {
$this->notify('Invalid TPM version. Must be v1.2 or v2.0.', 'error');
return;
}
$volumeName = 'vm-' . $this->server->vm_id . '-tpm';
try {
$proxmox = new Proxmox;
// For RBD storage, create the volume first
try {
$proxmox->request(
'/nodes/' . $this->server->node->name . '/storage/cephpool/content',
'post',
[
'filename' => $volumeName,
'size' => '4M',
'vmid' => $this->server->vm_id,
]
);
} catch (\Exception $e) {
// Volume might already exist or creation might not be needed
// Continue to add device to config
}
$definition = 'cephpool:' . $volumeName . ',size=4M,version=' . $version;
$this->applyDeviceChange($slot, $definition, 'TPM device added.');
$this->newTpmVersion = 'v2.0';
} catch (\Exception $e) {
$this->notify('Failed to add TPM device: ' . $e->getMessage(), 'error');
}
}
protected function applyDeviceChange(string $slot, string $definition, string $successMessage): void
{
try {
$proxmox = new Proxmox;
$proxmox->request(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
'put',
[$slot => $definition]
);
$this->notify($successMessage, 'success');
$this->loadSystemConfiguration(false);
} catch (\Exception $e) {
$this->notify('Failed to update device: ' . $e->getMessage(), 'error');
}
}
protected function storeOriginalValues(): void
{
$flags = array_values(array_unique($this->selectedCpuFlags ?? []));
sort($flags);
$this->original = [
'machineType' => $this->machineType,
'biosType' => $this->biosType,
'cpuType' => $this->cpuType,
'selectedCpuFlags' => $flags,
'kvmEnabled' => $this->kvmEnabled,
'acpiEnabled' => $this->acpiEnabled,
'smbiosSettings' => $this->smbiosSettings,
'efiDisks' => json_decode(json_encode($this->efiDisks), true),
'tpmDevices' => json_decode(json_encode($this->tpmDevices), true),
];
}
protected function hydrateFromOriginal(): void
{
$this->machineType = $this->original['machineType'] ?? '';
$this->biosType = $this->original['biosType'] ?? 'seabios';
$this->cpuType = $this->original['cpuType'] ?? 'kvm64';
$this->selectedCpuFlags = $this->original['selectedCpuFlags'] ?? [];
$this->kvmEnabled = $this->original['kvmEnabled'] ?? true;
$this->acpiEnabled = $this->original['acpiEnabled'] ?? true;
$this->smbiosSettings = $this->original['smbiosSettings'] ?? '';
}
protected function computeHasChanges(): bool
{
if (empty($this->original)) {
return false;
}
$currentFlags = array_values(array_unique($this->selectedCpuFlags ?? []));
$originalFlags = $this->original['selectedCpuFlags'] ?? [];
// Normalize arrays for comparison (sort and re-index)
sort($currentFlags);
sort($originalFlags);
$currentFlags = array_values($currentFlags);
$originalFlags = array_values($originalFlags);
$machineTypeChanged = $this->machineType !== ($this->original['machineType'] ?? '');
$biosTypeChanged = $this->biosType !== ($this->original['biosType'] ?? 'seabios');
$cpuTypeChanged = $this->cpuType !== ($this->original['cpuType'] ?? 'kvm64');
$flagsChanged = json_encode($currentFlags) !== json_encode($originalFlags);
$kvmChanged = (bool) $this->kvmEnabled !== (bool) ($this->original['kvmEnabled'] ?? true);
$acpiChanged = (bool) $this->acpiEnabled !== (bool) ($this->original['acpiEnabled'] ?? true);
$smbiosChanged = $this->smbiosSettings !== ($this->original['smbiosSettings'] ?? '');
return $machineTypeChanged
|| $biosTypeChanged
|| $cpuTypeChanged
|| $flagsChanged
|| $kvmChanged
|| $acpiChanged
|| $smbiosChanged;
}
public function getHasChangesProperty(): bool
{
return $this->computeHasChanges();
}
/**
* Check if Firmware & Machine settings are synced with the original values.
*/
public function getFirmwareMachineSyncedProperty(): bool
{
// If original hasn't been loaded yet, consider synced
if (!isset($this->original['machineType'])) {
return true;
}
$machineTypeMatch = $this->machineType === ($this->original['machineType'] ?? '');
$biosTypeMatch = $this->biosType === ($this->original['biosType'] ?? 'seabios');
$smbiosMatch = $this->smbiosSettings === ($this->original['smbiosSettings'] ?? '');
$acpiMatch = (bool) $this->acpiEnabled === (bool) ($this->original['acpiEnabled'] ?? true);
$kvmMatch = (bool) $this->kvmEnabled === (bool) ($this->original['kvmEnabled'] ?? true);
return $machineTypeMatch && $biosTypeMatch && $smbiosMatch && $acpiMatch && $kvmMatch;
}
/**
* Check if CPU & Virtualization settings are synced with the original values.
*/
public function getCpuVirtualizationSyncedProperty(): bool
{
// If original hasn't been loaded yet, consider synced
if (!isset($this->original['cpuType'])) {
return true;
}
$cpuTypeMatch = $this->cpuType === ($this->original['cpuType'] ?? 'kvm64');
$currentFlags = array_values(array_unique($this->selectedCpuFlags ?? []));
$originalFlags = $this->original['selectedCpuFlags'] ?? [];
// Normalize arrays for comparison (sort and re-index)
sort($currentFlags);
sort($originalFlags);
$currentFlags = array_values($currentFlags);
$originalFlags = array_values($originalFlags);
$flagsMatch = json_encode($currentFlags) === json_encode($originalFlags);
return $cpuTypeMatch && $flagsMatch;
}
/**
* Check if EFI Disks settings are synced with the original values.
*/
public function getEfiDisksSyncedProperty(): bool
{
// If original hasn't been loaded yet, consider synced
if (!isset($this->original['efiDisks'])) {
return true;
}
$originalEfiDisks = isset($this->original['efiDisks']) && is_array($this->original['efiDisks'])
? $this->original['efiDisks']
: [];
$currentEfiDisks = is_array($this->efiDisks) ? $this->efiDisks : [];
// Normalize arrays: sort by keys and compare
ksort($originalEfiDisks);
ksort($currentEfiDisks);
if (count($originalEfiDisks) !== count($currentEfiDisks)) {
return false;
}
foreach ($originalEfiDisks as $key => $value) {
if (!isset($currentEfiDisks[$key]) || $currentEfiDisks[$key] !== $value) {
return false;
}
}
return true;
}
/**
* Check if TPM Devices settings are synced with the original values.
*/
public function getTpmDevicesSyncedProperty(): bool
{
// If original hasn't been loaded yet, consider synced
if (!isset($this->original['tpmDevices'])) {
return true;
}
$originalTpmDevices = isset($this->original['tpmDevices']) && is_array($this->original['tpmDevices'])
? $this->original['tpmDevices']
: [];
$currentTpmDevices = is_array($this->tpmDevices) ? $this->tpmDevices : [];
// Normalize arrays: sort by keys and compare
ksort($originalTpmDevices);
ksort($currentTpmDevices);
if (count($originalTpmDevices) !== count($currentTpmDevices)) {
return false;
}
foreach ($originalTpmDevices as $key => $value) {
if (!isset($currentTpmDevices[$key]) || $currentTpmDevices[$key] !== $value) {
return false;
}
}
return true;
}
public function resetSystemSettings(): void
{
$this->hydrateFromOriginal();
$this->notify('Changes reverted to the values currently stored on the server.', 'info');
}
public function render()
{
return view('proxmox::system')->layoutData([
'sidebar' => true,
]);
}
}