- Add server filter dropdown (populated dynamically from loaded VMs)
- Add node filter dropdown (narrows to nodes of selected server)
- Both filters persisted in URL params and restored on reload
- Toolbar restructured into 2 rows: filters row + actions row
- Responsive breakpoints: ≤900px text labels hidden, ≤768px 2-per-row,
≤480px full-width filters, stats hidden on mobile
- Snapshots toolbar migrated to inline flex (no legacy class dependency)
- Update VERSION, CHANGELOG, README, WIKI to v1.3.1
- Context menu now shows Terminal (xterm.js) for lxc type and VNC Console for qemu
- Added openTerminalConsole() / connectTerminalConsole() / closeTerminalModal() functions
- Added terminalModal HTML alongside existing vncModal
- Backend endpoints /api/{server_id}/container/{vmid}/terminal and
/proxmox/ws/terminal/{server_id}/{node}/{vmid} already existed and are ready
Root cause of 'Connection closed' / VNCAuth failure:
- vncproxy was called without generate-password=1
- vnc_data.password was None → noVNC sent empty DES-encrypted challenge → Proxmox rejected
Fix:
- Add generate-password=1 to both qemu and lxc vncproxy requests
- If Proxmox returns password field (Proxmox 8.x VNCAuth): use it
- If not (Proxmox 9.x NoAuth): fall back to ticket as password
- Add diagnostic logging (password_len, has_generate_password)
Remove the 'onFirstMessage' hack where backend sent VNC password as
first text WebSocket message and frontend passed the already-open WS
to noVNC's RFBClass. This caused RFB handshake failures because noVNC
couldn't properly manage a WebSocket it didn't create.
Changes:
- Backend: remove send_text(password) before proxy start
- Backend: improve proxy error logging from debug to info level
- Frontend (virtual_machines.html, instance_detail.html): pass WebSocket
URL string to RFBClass constructor - noVNC creates and manages its own
WebSocket connection, allowing proper RFB protocol handshake
- configure_vm: capture resize UPID and call wait_for_task(120s)
before returning; prevents lock conflict when VM is started right
after disk resize
- resize_vm_disk / resize_container_disk (x2): same fix applied
- remove unnecessary restart_vm() after manual QEMU disk resize —
resize is a storage-layer operation and does not require a reboot;
the auto-restart was causing a second lock conflict
Fixes: can't lock file '/var/lock/qemu-server/lock-xxx.conf' - got timeout
Fixes: command '/usr/bin/qemu-img resize' failed: got timeout
- Migration 20: user_servers association table (User ↔ ProxmoxServer)
- API: GET/PUT /api/users/{id}/servers — assign servers to users with workspace-conflict validation
- API: GET /api/users/{id}/server-assignments — enriched server list (assigned + compatible flags)
- Non-admin server filter: intersection of assigned_servers and workspace servers
- Migration 21: dashboard:view = false for user role (new key format)
- Migration 22: proxmox.cluster.manage → cluster:manage for admin role
- API: GET/PUT /api/{server_id}/vm/{vmid}/owner — VM/LXC owner assignment (admin-only)
- Login: smart redirect based on user permissions after authentication
- Login: returnUrl param support for post-login redirect
- Roles API: user_count field per role in GET /api/roles
- node_uptime added to server detail API response
- Remove redundant PermissionChecker from GET /users HTML route
- Extract 1149 translation keys from i18n.py into locales/ru.json and locales/en.json
- Rewrite i18n.py as a lightweight JSON loader (5287 → 128 lines)
- Add I18nService.reload() for runtime hot-reload without restart
- Add I18nService.available_languages() to list loaded language codes
- Public API unchanged: t(), get(), get_all(), add_translation()
- New languages require only a new *.json file in locales/ — no code changes
Bump version: 1.1.6 → 1.1.7
Root cause: docker compose down killed the nohup script inside the
container before rebuild could complete — only git pull was visible
(updated VERSION file).
Changes:
- update_host.sh: new host-side watchdog (poll .update_trigger every 3s)
runs: git pull → docker compose down → build --no-cache app → up -d
PROJECT_DIR and User are dynamic (set at install time, not hardcoded)
- update_service.py: perform_update() now writes .update_trigger instead
of running git/docker commands inside the container
- deploy.sh: added install_update_watchdog() + --watchdog CLI option;
generates /etc/systemd/system/pvemanager-update.service dynamically
with correct User= and WorkingDirectory= for any install path/user
- .gitignore: ignore .update_trigger and generated systemd/ files
- CHANGELOG, VERSION, WIKI: bump to v1.1.1, document new update flow
String comparison caused local 1.0.3 to show "update available"
when GitHub had 1.0.2, since "1.0.3" != "1.0.2".
Added _is_newer() with proper semver tuple comparison so update_available
is only set when the remote version is strictly greater than local.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace hardcoded legacy Gitea URL in settings.py fallback
- Skip legacy Gitea URL even if saved in DB (treat as unset)
- Track last_response in branch loop so errors report the real HTTP
status code instead of always showing "HTTP unknown"
- Unify 401/403/404 into one "private or not accessible" message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The fallback repo URL was pointing to the old Gitea instance
(git.tzim.uz/dilshod/pve_manager), causing "Failed to fetch VERSION
file: HTTP unknown" because both main/master branches returned non-200.
Updated to the current GitHub repository.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously seenNodes used only the node name as the dedup key, causing
independent servers that happen to share a default node name (e.g. "pve")
to hide each other's charts.
Fix: /api/{server_id}/nodes now also returns cluster_id — the actual
Proxmox cluster name for clustered servers, or "standalone-{server_id}"
for single-node servers. The dashboard deduplicates by
"cluster_id::node_name", so:
- nodes in the same cluster → deduped correctly (shown once)
- nodes on independent servers with the same name → both shown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /api/auto-import/{server_id} endpoint ran as a background task
and returned {status: "queued"} with no templates field, while the
frontend's executeAutoImport() immediately called data.templates.forEach()
— causing TypeError: can't access property "forEach", data.templates
is undefined.
Replaced the background task with a synchronous handler that imports
templates in-request and returns {templates: [...], imported_count: N},
matching what the frontend expects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- VERSION: 1.0.0 → 1.0.1 (patch — bug fixes only)
- README: full rewrite with modern formatting, accurate badges,
real GitHub URLs, complete feature list and tech stack table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix all `from ..` → `from ...` in app/api/proxmox/*.py after the
proxmox.py split (PR #6) placed modules one level deeper, causing
ModuleNotFoundError on startup
- Broaden Content-Security-Policy to allow CDN sources used in templates:
cdnjs.cloudflare.com (Font Awesome), cdn.jsdelivr.net (Bootstrap,
Chart.js, xterm), fonts.googleapis.com/gstatic.com (Google Fonts),
esm.sh (noVNC) — the overly-strict CSP was blocking all icons and
charts from loading in the browser
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api/ipam.py: convert 30 async def handlers that had no await to plain def,
letting FastAPI run them in its thread pool (correct for sync SQLAlchemy I/O);
cleanup_orphan_allocations kept async (it has await)
- api/proxmox.py: add _get_proxmox_client(server) helper that encapsulates the
use_password/token branching, replace 58 duplicated 10-line blocks with a
single call; get_proxmox_resources() call sites left unchanged (different sig)
https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R