feat: add LXC container support to Proxmox extension

- Add is_lxc boolean column to ext_proxmox_nodes (default false, existing nodes unaffected)
- Add LXC checkbox to admin node creation form (disabled on edit)
- Dynamic API path selection via Node::vmType() (returns 'lxc' or 'qemu')
- LXC deployment: syncLxcSettings() for cores, memory, swap, hostname, rootfs, net config
- LXC network uses name=eth0 format with applyLxcPoolSettings() for VLAN/firewall/MTU
- All lifecycle operations LXC-aware: install, suspend, unsuspend, terminate, upgrade, reinstall
- VNC/xterm console uses correct type (lxc vs kvm)
- All 20+ Livewire components use vmType() for Proxmox API paths
- LXC user interface shows basic-only view (no advanced toggle, no KVM-specific tabs)
- Basic tabs for LXC: Overview, Network Configuration, Backups, Console
This commit is contained in:
2026-03-02 07:11:25 -05:00
parent a98bbd6d82
commit 5f2e591c6e
21 changed files with 330 additions and 140 deletions

View File

@@ -10,16 +10,18 @@
$vmStatus = optional($status)->status;
$pollSuffix = $statusPollingInterval ? '.' . $statusPollingInterval : '';
$pollAttribute = $statusPolling ? 'wire:poll' . $pollSuffix . '="loadVmStatus"' : '';
$isLxc = $server->node?->is_lxc ?? false;
@endphp
<div {!! $pollAttribute !!} class="flex flex-col" style="min-height: calc(100vh - 30rem);">
@if($server->status === 'active')
<div class="flex flex-col flex-1" x-data="{
isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced',
isAdvancedView: {{ $isLxc ? 'false' : "localStorage.getItem('proxmox-view-mode') === 'advanced'" }},
isAdvancedOnlyView: {{ in_array($view, ['boot-order', 'cloud-init', 'display', 'system', 'storage', 'subdomain', 'statistics', 'help', 'firewall']) ? 'true' : 'false' }},
isLxc: {{ $isLxc ? 'true' : 'false' }},
toggleView() {
if (!this.isAdvancedOnlyView) {
if (!this.isAdvancedOnlyView && !this.isLxc) {
this.isAdvancedView = !this.isAdvancedView;
localStorage.setItem('proxmox-view-mode', this.isAdvancedView ? 'advanced' : 'basic');
window.dispatchEvent(new CustomEvent('proxmox-view-mode-changed', {
@@ -28,7 +30,7 @@
}
}
}" x-init="
if (!localStorage.getItem('proxmox-view-mode')) {
if (!isLxc && !localStorage.getItem('proxmox-view-mode')) {
localStorage.setItem('proxmox-view-mode', 'basic');
isAdvancedView = false;
}">
@@ -1631,6 +1633,29 @@
];
}
$tabs[] = ['label' => 'Help', 'view' => 'help', 'route' => route('proxmox.server.help', ['server' => $server->id]), 'advanced' => true];
// For LXC containers, remove all advanced-only and KVM-specific tabs
if ($isLxc) {
$tabs = array_values(array_filter($tabs, function ($tab) {
// Remove top-level advanced tabs
if (!empty($tab['advanced'])) return false;
// Remove Hardware dropdown entirely (Boot, Display, Storage, System are KVM-only)
if (($tab['label'] ?? '') === 'Hardware') return false;
// For Networking dropdown, keep only Configuration (remove Firewall, Subdomain)
if (isset($tab['dropdown']) && $tab['dropdown'] && isset($tab['items'])) {
$tab['items'] = array_values(array_filter($tab['items'], fn($item) => empty($item['advanced'])));
return count($tab['items']) > 0;
}
return true;
}));
// Re-filter Networking dropdown items in-place
foreach ($tabs as &$tab) {
if (isset($tab['dropdown']) && $tab['dropdown'] && isset($tab['items'])) {
$tab['items'] = array_values(array_filter($tab['items'], fn($item) => empty($item['advanced'])));
}
}
unset($tab);
}
@endphp
<div class="mb-10 mt-3">
@@ -2124,7 +2149,8 @@
</div>
<div class="modern-toggle-container"
:class="isAdvancedOnlyView ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''">
:class="isAdvancedOnlyView ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''"
x-show="!isLxc">
<span class="modern-toggle-label"
:class="!isAdvancedView ? 'active' : 'inactive'">
Standard
@@ -2145,10 +2171,11 @@
<div class="lg:hidden" x-data="{
open: false,
isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced',
isAdvancedView: {{ $isLxc ? 'false' : "localStorage.getItem('proxmox-view-mode') === 'advanced'" }},
isAdvancedOnlyView: {{ in_array($view, ['boot-order', 'cloud-init', 'display', 'system', 'storage', 'subdomain', 'statistics', 'help', 'firewall']) ? 'true' : 'false' }},
isLxc: {{ $isLxc ? 'true' : 'false' }},
toggleView() {
if (!this.isAdvancedOnlyView) {
if (!this.isAdvancedOnlyView && !this.isLxc) {
this.isAdvancedView = !this.isAdvancedView;
localStorage.setItem('proxmox-view-mode', this.isAdvancedView ? 'advanced' : 'basic');
window.dispatchEvent(new CustomEvent('proxmox-view-mode-changed', {
@@ -2157,11 +2184,11 @@
}
}
}" x-init="
if (!localStorage.getItem('proxmox-view-mode')) {
if (!isLxc && !localStorage.getItem('proxmox-view-mode')) {
localStorage.setItem('proxmox-view-mode', 'basic');
isAdvancedView = false;
}
isAdvancedView = localStorage.getItem('proxmox-view-mode') === 'advanced';
isAdvancedView = {{ $isLxc ? 'false' : "localStorage.getItem('proxmox-view-mode') === 'advanced'" }};
$watch('open', () => {
if (open) {
isAdvancedView = localStorage.getItem('proxmox-view-mode') === 'advanced';
@@ -2333,7 +2360,8 @@
<x-ri-menu-line class="size-5" />
</button>
<div class="mobile-toggle-compact flex-shrink-0"
:class="isAdvancedOnlyView ? 'opacity-50 pointer-events-none' : ''">
:class="isAdvancedOnlyView ? 'opacity-50 pointer-events-none' : ''"
x-show="!isLxc">
<span class="mobile-toggle-label-compact"
:class="!isAdvancedView ? 'text-base' : 'text-base/40'">
Std