Replace all 57 react-hooks/set-state-in-effect sites with the patterns
React recommends:
- dialog forms reset while rendering when the dialog opens, instead of
in an effect after a frame with stale values;
- forms filled from server data resync during render when a new
response object arrives;
- default selections (server, node, storage, disk, user) are derived
from the user's pick and the current list rather than written back
into state.
This also fixes the migrate dialogs resetting the chosen target when
the guest status refreshed, and the image download dialog resetting
"convert to template" on every render while storages were loading.
Unstable `?? []` / `new Set()` hook dependencies are memoized.
- Land users on their first accessible page instead of /dashboard, so
users without dashboard:view no longer get an "access denied" toast
on every sign-in, impersonation switch or unknown URL.
- Reset the active workspace and the persisted deploy/minimized/bulk
task trays on logout and impersonation, so the next identity in the
browser does not see the previous one's tasks or workspace.
- Keep the session when /api/auth/me fails with a network error or 5xx
(panel restarting) and retry, instead of dropping the token.
- Notifications: mark-read/delete buttons no longer close the menu.
- Command palette starts every opening with an empty query.
- Live instance metrics no longer show the previous instance's data
after switching instances.
- reset/resume are expected to leave the guest running (no "stopped"
flash and no 36 s of pointless polling).
- Execute-script goes through apiClient (workspace header, 401 handling,
readable validation errors).
- Node shell no longer reconnects (re-running apt dist-upgrade) when
the translation function changes; fullscreen icon follows Esc exits.
- Node power/certificate actions use hasPermission so role-bound admins
see them; power toasts are translated.
Security
- Enforce guest ownership and server assignment across Proxmox endpoints
(VM/LXC actions, firewall, backups, tasks, bulk operations, deploy,
LXC/ISO creation, golden template) and WebSocket channels
- Keep the panel's PVE root ticket server-side for consoles; single-use
WebSocket tickets instead of the JWT in URLs; re-check access on open
WebSocket bridges
- Validate identifiers that reach Proxmox API paths and node shell
commands; reject backup archives / foreign volumes in create requests
- Close privilege escalation in user management, TOTP replay, reset-link
poisoning, X-Forwarded-For spoofing, stored XSS (group icons, SVG logos)
- SSH host key pinning (TOFU), encrypted 2FA seeds and notification
secrets, password changes end other sessions, redact secrets in logs
- Quotas count in-flight creations; 2FA failures count toward lockout
Fixes
- Consoles on non-default API ports, large App Store/script files,
interrupted tasks after restart, impersonation lost on token refresh,
HTTPExceptions swallowed into 500s, WebSocket client reconnects
Performance and process
- Fewer DB writes and queries on hot paths, lazy-loaded locales and
heavy pages, off-loop blocking work, indexes (migrations 44-46)
- Deploy waits for backend tests and frontend build; frontend checks
workflow; API bound to loopback without the SSL profile
Match the VM Images tab's layout to the OS Templates tab (sortable
table instead of a card grid) instead of duplicating its own look.
Also drop the auto-selected server id when a workspace switch
invalidates the server list — it used to stick around and the picker
fell back to showing the raw numeric id instead of a name.
Storages tab kept the previously auto-selected server id even after
switching workspaces invalidated the server list, so the picker fell
back to rendering the raw numeric id instead of a name. Reset the
selection when it's no longer in the current server list, matching
the guard already used on the Backups and Instances pages.
Both tabs listed the same qcow2 cloud images already covered by the
OS Templates tab; keep just LXC Templates, ISO Images and
Repositories alongside it.
Replace the OS Templates card grid with a sortable/filterable table
(TanStack Table, matching the Instances/Backups pages) and fold the
former standalone /images page in as four more tabs (VM Images, LXC
Templates, ISO Images, Repositories) reusing its existing logic via a
new ImageCatalogPanel component. /images now redirects to /templates
and the separate sidebar entry is removed; tabs beyond OS Templates
stay gated behind the template:manage permission.
VMInstance.memory is stored in MB and disk_size in GB (see _bytes_to_mb/
_bytes_to_gb in monitoring_worker.py), but the reinstall worker divided
them again by 1024*1024 / 1024**3 as if they were raw bytes. Both
computations collapsed to 0, so the post-clone "re-apply config" and
"restore disk size" steps silently no-op'd - reinstalled VMs/containers
ended up with the template's own memory and disk size instead of the
instance's previous values.
VM delete pre-save was calling save_vm_instance() without template_id/
template_name, which unconditionally overwrites those fields on the
existing cached row - so any non-force delete attempt (even one that
later failed) permanently detached the VM from its source template and
broke reinstall. Now the previous values are carried through.
Also extend reinstall eligibility and the reinstall worker to cover
QEMU VMs created via "Create VM from ISO" (template_name holds the ISO
volid, same storage:content/file pattern already handled for LXC CT
templates), recreating the VM from the same ISO on reinstall.
The function lives in client.py but was missing from the package
__init__, unlike its neighbours clear_all_cache and clear_server_cache.
The monitoring worker imports it from app.proxmox inside a try/except
ImportError, so nothing crashed — the hourly job was silently skipped
with "Proxmox cache cleanup not available" and the connection cache was
never pruned, keeping entries older than an hour alive until restart.
The backend lifespan hook runs an initial VM cache sync across every
Proxmox server in the database before uvicorn starts listening. When a
server is unreachable each connection attempt burns ~21s of timeout, so
on a host that cannot see part of the fleet the container stayed
unhealthy well past the old 30s grace window. Compose then refused to
create the frontend, which waits on `service_healthy`, and the whole
`up` aborted with "dependency failed to start".
The RHEL-family qemu-guest-agent package starts the agent with an allow-list
that omits guest-exec, guest-exec-status and guest-file-* (FILTER_RPC_ARGS in
/etc/sysconfig/qemu-ga; older packages spelled the same restriction as
BLOCK_RPCS or BLACKLIST_RPC). The agent answered "The command guest-exec has
been disabled for this instance", so running scripts in a VM, assigning extra
IP addresses and growing the filesystem after a disk resize did not work on
Rocky Linux, AlmaLinux, CentOS Stream and Fedora guests.
- patch-cloud-images.sh: clear all three variants while building the mirror
(UNBLOCK_RPC=0 opts out) and add an ONLY filter to rebuild only part of the
catalog. Three `[ ... ] && ...` statements that aborted the script under
set -e are now plain ifs -- the one before mirrors.json generation killed
every run that passed BASE_URL
- builtin.py: point the five RHEL-family amd64 entries at the rebuilt mirror
release 2026-08-22 with their new checksums. Ubuntu and Debian images are
unaffected and stay in the previous releases, so older panel versions keep
working
- client.py: annotate the Proxmox error with what to clear inside the guest,
the raw message names neither the file nor the variable
- WIKI, CHANGELOG: document the restriction and the unblock command
Turn the accumulated Unreleased notes into the v1.18.0 section and document
the multi-address feature in both languages.
- CHANGELOG: several IP addresses per instance (model, Network tab card,
aliases inside the guest, surviving a reboot, pending/runtime_only
states, new endpoints, migration 43), the IPAM discovery fixes and the
guest-agent payload fix
- README: version badge, IPAM line and a feature bullet about extra
addresses
- WIKI: the five new guest-address endpoints plus the allocation filters
in the API reference, a feature bullet, and a new section in the IPAM
chapter covering the card actions, how an address is applied (SSH to
the node for LXC, guest-exec for VMs) and the four apply states
- VERSION: 1.17.0 -> 1.18.0
Testing on a live AlmaLinux container showed the alias disappearing after
a restart even though it was reported as applied: Proxmox regenerates the
guest network configuration on every start (pve-container rewrites
/etc/network/interfaces, ifcfg or the NM keyfile from netN), wiping any
address added to the native stack.
before reboot: address1=10.10.10.103/24 address2=10.10.10.106/24
after reboot: addresses=10.10.10.103/24 (file recreated by PVE)
Install a oneshot systemd unit that re-adds the recorded addresses after
network-online.target — /etc/systemd is not touched by Proxmox. The native
branches stay, so persist is now reported as e.g. "nm+systemd"; the unit
and its helper are removed once the last address is released.
Also fixed while testing on the two test instances:
- the alias script is ASCII-only now: it travels through the Proxmox API
and its marker ends up in guest system configs
- a composite persist marker no longer falls through the parser, which
used to report a successful apply as runtime_only
- allocate_ip marks the first address of a guest as primary on its own,
so allocations coming from the creation wizard are flagged correctly
Proxmox base64-encodes the input-data of an agent exec in Perl, which dies
with "Wide character in subroutine entry" as soon as the payload contains
non-ASCII — a Cyrillic comment in a script is enough, so running any such
script on a VM failed with a 500.
Hand the same UTF-8 bytes over as latin-1: the JSON carries plain bytes,
Perl is happy and the guest still receives valid UTF-8.
A guest was implicitly limited to a single allocation: lookups were
"(server, vmid) -> one row", release_ip_by_vmid freed one address, and
the link tool skipped guests that already had one. An instance can now
hold a primary address plus any number of aliases, managed from the
Network tab of the instance and visible on the IPAM pages.
Schema and service:
- ipam_allocations gains is_primary, assignment_kind, target_interface
and apply_* columns; migration 43 marks existing rows as primary
- find_allocations_by_resource/set_primary; release_ip_by_vmid now frees
every address of a deleted guest instead of just the first one
- next-available search may scan the whole subnet when a network has no
auto-assign pool, skipping node and cached guest addresses so it never
hands out an address that is already live
Applying to the guest (services/guest_ip_service.py):
- reuses script_engine, so LXC goes through pct exec and VMs through the
guest agent; the shell script synchronises the whole alias list of an
interface, which makes add, remove and retry the same code path
- persists via ifupdown, netplan, NetworkManager or sysconfig and never
restarts guest networking; an unknown stack degrades to runtime_only
and an unreachable guest keeps the allocation as pending
API and UI:
- /ipam/api/guests/{server_id}/{vmid}/addresses: list, add, apply,
primary, delete; allocations list accepts guest filters
- instance list exposes ips[] alongside the primary ip
- addresses card on the Network tab, "+N" badge in the IP column,
primary badge in the allocations table
Fixes found along the way:
- link-allocations registers every address of a guest, carries the MAC
over, refuses to take an address away from another live guest and no
longer matches allocations by resource name (which leaked an address
between same-named guests on different servers)
- IPAM history no longer fails to serialise datetime values, which used
to abort re-linking with a 500
get_container_interfaces() looked for ipconfigN keys, which exist only on
QEMU cloud-init guests. LXC keeps the address inside the netN string, so
the method never returned anything and containers showed no IP at all.
Parse netN with the existing net_parser, fall back to
/nodes/{node}/lxc/{vmid}/interfaces for DHCP guests (skipped when the
config already carries a static address or the container is stopped), and
validate addresses so loopback, link-local and the literal "dhcp" are
dropped. IPv6 from ip6/inet6 is reported as well.
Flags the removal of GET /api/{server_id}/container/{vmid}/terminal as a
breaking change and documents the termproxy task failures and the black
console screen it came with.
Opening an LXC console left a failed task on the Proxmox node:
failed waiting for client: timed out
TASK ERROR: command '/usr/bin/termproxy 5900 ... vzctlconsole101' failed
connectTerminal() called GET /api/{sid}/container/{vid}/terminal first,
which creates a termproxy session on the node, but only the `node` field
of the response was used - port and ticket were discarded. The ws
endpoint then created a second termproxy session and connected to that
one. Nobody ever attached to the first, so Proxmox waited ~10s for a
client and failed the task.
The console also rendered a black screen in dev: React StrictMode mounts
the effect twice, connect* are async, and the effect cleanup ran before
they had assigned wsRef/termRef, so it had nothing to clean up. Two
xterm instances ended up in the same container - the first one filled
the viewport while the live one was pushed out of the overflow-hidden
area, which is why the badge read "connected" over an empty screen.
- drop the redundant REST call; the ws endpoint owns the termproxy
- remove the now unused GET /api/{server_id}/container/{vmid}/terminal
- add a per-session cancellation guard: a stale connect closes its own
socket and disposes its own terminal without touching refs that may
already point at the live session
- route mount and the reconnect button through a single startSession()
that cancels and tears down the previous session first
- apply the same guard to the node shell page
network:view is no longer implied by server:view, and neither network:view
nor ipam:view is granted to the non-admin default roles (operator, user,
demo). The network topology, node/SDN interface configuration and IPAM are
administrative surfaces, not part of the baseline "can see servers"
umbrella, so they should be granted explicitly rather than inherited.
Clicking a guest card now navigates to /instances/:serverId/:vmid, the
same route used from the Instances list, with a hover highlight to
signal it's clickable.
Add a "scan exports" button next to the server field for NFS storages
that calls Proxmox's /nodes/{node}/scan/nfs and turns the export path
input into a select populated with the real exports found on the
target server, instead of requiring the path to be typed by hand.
Proxmox rejects lxc move_volume on a running container. Instead of
just disabling the button, show a modal that explains the container
will be stopped, walks through stop -> move -> start with progress,
and always restarts the container even if the move itself fails.
For VMs (and already-stopped containers) it's a plain confirm modal.
Proxmox rejects lxc move_volume on a running container
("cannot move volumes of a running container"), unlike qemu
move_disk which works live. Surface this as a disabled button
and hint instead of a raw 500 error.
Disk move was hardcoded to the QEMU move_disk Proxmox API and was
never reachable in the UI for LXC (no Hardware tab for containers,
and the disk list regex only matched VM device names).
- add move_container_disk() using lxc/{vmid}/move_volume
- add POST /api/{server_id}/container/{vmid}/disk/move endpoint
- route useMoveDisk by instance type (vm vs container prefix)
- recognize rootfs/mp\d+ as LXC disk devices
- add a Move disk card on the Settings tab for LXC instances
- filter target storage list by supported content type
(rootdir for LXC, images for VM) instead of showing all storages
Increase the progress bar width and label/detail font size for the
vCPU/RAM rows so they're easier to read at a glance, matching feedback
on the collapsed metrics panel added earlier.
Move CPU/RAM/Disk/Net/Disk I/O/Uptime out of the always-visible table
columns into a per-row expandable panel (opened by clicking the
instance name), matching VMmanager's disclosure UX. Adds a Parameters
link into the panel pointing to the instance settings tab, and
clearer stat cards for network/disk throughput.
- Export: use useReactFlow().getNodesBounds() (measured dimensions) instead
of the standalone helper, which treated every node's size as zero and
clipped the last row/column of the exported image
- Export: give edge paths an explicit inline stroke before capture -
html-to-image clones <svg> natively and skips computed-style inlining for
it, so edges came out with no stroke at all
- Enable the edge flow animation (was hardcoded off)
- Switch the graph from left-to-right to top-to-bottom, including flipping
every node's handle positions, and recolor edges from the barely-visible
border tone to the primary accent
Fix nested h-screen flexbox layout bug that let the noVNC canvas overflow
below the viewport until the browser zoom level was changed (missing
min-h-0 on flex children). Also add a fuller console control bar: power
menu (reboot/shutdown/hard reset/hard stop), extra-keys menu (Ctrl+Alt+Del,
Ctrl+Alt+Backspace, Tab, Esc, Ctrl+Alt+F1-F12), scale toggle, clipboard
paste to guest, and console screenshot. Adds a VM hard-reset backend
action (qemu status/reset) alongside the existing start/stop/restart/shutdown.
getViewportForBounds treats a numeric padding as a fraction of the
canvas size (not pixels) unless given a "Npx" string, so the padding=40
passed in the previous fix was read as 4000% padding and still
collapsed the zoom to its floor - the graph rendered small on a mostly
empty canvas exactly as before. Verified in-browser: PNG/SVG exports
now fill ~98% of the canvas instead of ~20%.
Forcing a 1200x800 minimum canvas made getViewportForBounds compute a
zoom above the 2x cap for small graphs, leaving the exported PNG/SVG
mostly empty with the graph rendered tiny in the corner.
Interactive infrastructure map (panel -> cluster -> node -> guest) rendered
with React Flow, backed by a single aggregated /api/network/topology endpoint
instead of fanning out over servers/nodes/SDN/VMs. Adds a netN NIC parser for
QEMU/LXC guest configs, status/threshold filters, canvas tools, and PNG/SVG
export.
The CT template select listed both the raw archive file name and the
headline on one line, which was long and hard to scan. Render the
headline alone and keep the file name in the title tooltip, matching
the convention already used on the images page. Falls back to the file
name when the repository provides no headline.
The dialog body is a grid whose implicit column is auto-sized, i.e.
max-content. Long unbreakable option labels (e.g. CT template file
names) widened that column and pushed the select fields and footer
buttons past the dialog edge.
Use grid-cols-[minmax(0,1fr)] on DialogContent to cap the track at the
dialog width, and apply the same guard to the Alert grid template.
Verified: dialog scrollWidth now equals clientWidth at both 1280px and
375px viewports, with no horizontal page overflow.
Add min-w-0 and overflow-hidden to SelectTrigger and min-w-0 to
SelectValue so long option labels (e.g. CT template names) and grid
columns no longer push select fields past the modal boundary. Fixes
overflow across all dialogs sharing the Select component.
Derive a human-readable OS name (e.g. Debian 13, Ubuntu 24.04) from the
vztmpl volid instead of showing the raw template path like
local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst in the instances list.
Applied at the display layer, so existing instances are fixed too.
- Surface a topbar indicator with a live count of active tasks
- Propagate exceptions from Proxmox LXC container creation instead of
swallowing them, and validate minimum password length
- Replace the Fedora catalog icon with the official logo artwork
Prevent edit/delete action buttons from overlapping with long template
names by using shrink-0 on icon and button containers with flex-1
min-w-0 on the text area. Make header and filter selects responsive
with flex-wrap and full-width on mobile breakpoints.
Add DNS name validation (RFC 952/1123) for VM, LXC and ISO instance
names on both backend and frontend. Proxmox rejects names with
underscores or special characters but the error was only surfaced as
a raw 400 from the API.
Also require Cloud-Init user and password fields for VM deploys
(highlighted when empty, Next button blocked), and fix numeric input
fields that showed "0" on clear and concatenated values like "04096".