Commit Graph

114 Commits

Author SHA1 Message Date
2dff9f8c5c feat(images): node-arch endpoint for arch-aware filtering
GET /proxmox/api/{server_id}/images/node-arch derives amd64/arm64 from
node status current-kernel.machine; returns null when node unreachable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:28:04 +05:00
ed84e00e7b feat(images): Phase 3 backend — custom mirror CRUD
GET/POST/PUT/DELETE /proxmox/api/images/mirrors (perm template:manage).
Enabled mirrors merge into the catalog feed automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:17:00 +05:00
c7edf72d19 feat(images): Phase 2 — auto-convert qcow2 to VM template
- client.create_vm_with_import: API-only disk import via scsi0 import-from (PVE 8.2+)
- _do_image_template_sync worker: download(import) -> create+import -> cloud-init
  -> convert_to_template -> register OSTemplate (Cloud Images group)
- images download endpoint: to_template branch + cloud-init/disk_storage params
- clear fallback error when storage lacks import support (file kept)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:11:20 +05:00
7aed5dd97e feat(images): backend catalog + mirror model + download API/worker
Phase 1 of VM image mirror feature:
- StorageMixin.download_url (PVE download-url/aria2) + get_download_target_storages
- builtin curated cloud-image catalog (Ubuntu/Debian/Rocky/Alma, amd64+arm64)
- ImageMirror model for admin custom mirrors
- /proxmox/api/images: catalog, lxc-templates proxy, storages, download
- _do_image_download_sync worker with log-tailing progress (DeployTask)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:51:21 +05:00
6e67ad7e77 fix(disk-resize): keep growpart script ASCII-only
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Successful in 51s
Deploy to Production / Trigger production update (push) Has been cancelled
Proxmox's guest-agent exec (PVE/QemuServer/Agent.pm) dies with
"Wide character in subroutine entry" when input-data contains non-ASCII
bytes. The growpart script had Cyrillic comments, so the whole auto-grow
call returned 500 and the FS was never resized. Replace those comments
with ASCII so the script reaches the guest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 03:09:43 +05:00
f67ee24155 Merge branch 'feature/metrics-history'
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 50s
Deploy to Production / Trigger production update (push) Successful in 7s
2026-06-22 02:47:45 +05:00
38d485be4b fix(disk-resize): honest task status, auto-grow FS, CRLF-safe guest exec
Backend fixes for VM/LXC disk resize:
- resize_vm_disk/resize_container_disk now honor wait_for_task and raise
  the real Proxmox exitstatus (e.g. "disk 'scsi0' does not exist") instead
  of always returning success.
- add grow_vm_filesystem: after a VM resize, rescan the disk (retry loop,
  needed for SATA/SCSI), growpart the partition and resize the FS
  (ext*/xfs/btrfs) via the guest agent; surface its output to the API.
- execute_script normalizes CRLF/CR to LF so pasted scripts and growpart
  don't break on trailing \r ("partition-number must be a number").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:43:03 +05:00
1a17ceed82 fix(sync): store VM memory/disk in MB/GB, not raw Proxmox bytes
The monitoring worker wrote Proxmox maxmem/maxdisk (bytes) straight into
the memory/disk_size columns, which the rest of the app stores as MB/GB.
Every sync cycle clobbered correct values with bytes, so the saved-config
card showed huge byte numbers under MB/GB labels. Convert via new
_bytes_to_mb/_bytes_to_gb helpers; existing rows self-heal on next sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:42:49 +05:00
074983a1f6 fix(metrics): correct cpu/disk% chart scaling, evict stale collector state, lighten nic query, drop polling & dead RRD code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 21:35:02 +05:00
bbec17abd1 feat(metrics): add collector + retention loops and wiring 2026-06-21 21:35:02 +05:00
6769e5b218 feat(metrics): add /metrics read endpoints for vm and container 2026-06-21 21:35:02 +05:00
e380ec1ab2 feat(metrics): add query_instance_metrics read aggregation 2026-06-21 21:35:02 +05:00
8f9f78ef91 feat(metrics): add blockstats + netstat client wrappers 2026-06-21 21:35:02 +05:00
3d55b1ab8d feat(metrics): add timeframe/bucket math and series aggregation 2026-06-21 21:35:02 +05:00
3a2c87bd0a feat(metrics): add rate + blockstats/netstat/fsinfo parsing helpers
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:35:02 +05:00
3b3841fc0e fix(metrics): portable BigInteger PKs + split test asserts 2026-06-21 21:35:02 +05:00
572bb44ffa feat(metrics): add instance_metric and instance_nic_metric models
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 21:35:02 +05:00
1cc4d86b18 feat(clone): real disk-copy progress, owner assignment and DB registration
- _do_clone_sync now tails the PVE clone task log and maps the disk
  transfer percentage to deploy progress 50→95% instead of a static 70%.
- Add owner_id to CloneRequest; admins may assign the clone to another
  user (mirrors LXC deploy). Register the cloned instance via
  save_vm_instance so it has an owner and is visible to limited users.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:38:26 +05:00
7d003f7fa1 fix(snapshots): wait for Proxmox task to report real outcome
create_vm_snapshot/create_container_snapshot returned success as soon as
the POST returned a UPID, so a snapshot that failed asynchronously (e.g.
storage without snapshot support) still reported "created". Now wait on
the task and surface the actual exitstatus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:35:26 +05:00
11d2c06fb1 fix(updates): parse version build suffix like 1.5.2-1
_parse_version split on "." only, so "2-1" hit int() and the whole
version collapsed to (0,) — update checks ignored build bumps and could
even offer a "downgrade". Extract every numeric run via regex instead:
"1.5.2-1" -> (1, 5, 2, 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:23 +05:00
9ced08b88c feat(settings): per-user language preference and panel improvements
Store UI language per user (users.language column, migration 26).
Language selector moves from Panel tab to Profile tab — each account
picks its own language, saved to DB and applied via i18n.changeLanguage()
on save so the interface switches immediately.

Panel tab: panel_name read-only (falls back to env PANEL_NAME),
log_retention_days field exposed in UI, language selector removed.
Panel API accepts panel_name in PUT so it can be overridden in DB
without touching the env var.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 08:47:18 +05:00
4451b3e45a fix(settings): align SMTP/security settings fields with API
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 46s
Deploy to Production / Trigger production update (push) Successful in 8s
Rename SMTP request/response fields (host/port/username/password/from_email/tls)
and point security settings hooks at /admin/api/security/settings with a typed
SecuritySettings shape. Add minutes / security_saved i18n keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 08:26:39 +05:00
a507e3c4bc feat(metrics): real-time VM disk fill, IOPS and network I/O
Add live per-disk usage, disk read/write and network in/out rates to the
instance Overview tab, the instances list and the dashboard top-loaded list.

Backend:
- get_vm_fsinfo() pulls per-filesystem usage via QEMU guest agent
- metrics_broadcaster computes I/O rates (bytes/sec) from cumulative counters
- instances-list loop now refreshes each running VM via status/current
  (1s-granular) instead of cluster/resources (10s pvestatd), with bounded
  concurrency, and paces both realtime loops to a 1s cadence

Frontend:
- OverviewTab: per-disk cards, Network I/O and I/O-rate cards via WS
- InstancesPage: Net and Disk I/O columns; Disk% shown for LXC only
- DashboardPage: top-loaded list overlaid with realtime metrics

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 08:26:29 +05:00
965fb858da fix(ipam): require resource name before allocating IP and fill missing fields on VM link
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 46s
Deploy to Production / Trigger production update (push) Successful in 8s
- Validate resource name in auto-allocate form before sending request
- Fill resource_name, proxmox_node, resource_type, mac_address when syncing
  a VM that already has matching vmid/server_id but missing metadata

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 03:23:57 +05:00
4feb802c4f fix(ipam): filter unlinked allocations to VMs with IPs inside active IPAM networks
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 46s
Deploy to Production / Trigger production update (push) Successful in 8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 02:53:50 +05:00
58e73d4af4 fix(tests): repair Proxmox client mock after from_server refactor
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 46s
Deploy to Production / Trigger production update (push) Successful in 8s
The mock_proxmox fixture patched ProxmoxClient with return_value= (constructor
style), but since commit d66928e clients are built via the
ProxmoxClient.from_server classmethod. from_server() therefore returned an
unconfigured child mock, so endpoints serialized a MagicMock and responded 500
— failing 5 tests in test_api_vms.py on every CI run (Backend Tests workflow).

Wire .from_server.return_value (and .return_value) on all patched ProxmoxClient
references to the configured instance so every construction path resolves to
the same mock. Full suite now green (60 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 02:08:28 +05:00
d0badd0a2e refactor(rbac): drop runtime legacy-format resolution from engine
Roles are stored in the new resource:action format and any legacy keys are
converted once at startup by the RBAC v2 DB migration (migrate_rbac_v2), so
the permission engine no longer needs to resolve legacy codes on every check.

- get_user_permissions: store role permission codes as-is (no dual add).
- has_permission: match new-format codes only; drop resolve_permission and the
  dotted-code fallbacks.
- Remove now-unused resolve_permission import in middleware.
- LEGACY_PERMISSION_MAP / convert_* are retained as one-shot DB-migration
  utilities (upgrade path), not a runtime bridge.
- Tests: adapt fixtures to new-format role permissions; assert legacy keys are
  no longer honored at request time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 02:08:19 +05:00
de967c35e0 refactor(rbac): migrate API permission checks to resource:action format
Replace all legacy dotted permission codes (vms.view, proxmox.manage, ...)
in PermissionChecker/has_permission across app/api with the new
resource:action format (vm:view, server:manage, ...), per LEGACY_PERMISSION_MAP.
All 33 codes used by the API are now registered in the permission registry.

Also switch the /api/auth/me admin branch to get_all_permissions_v2 so the
endpoint emits permissions in the new format only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 02:07:48 +05:00
57513c4dc9 fix(rbac): register user:manage and vm:manage, close legacy map gaps
These permissions were referenced by the API (users.manage, proxmox.vm.manage)
but never registered in the permission registry, so they only worked through
the is_admin bypass — non-admin users granted them silently received 403.

- Register user:manage and vm:manage in the registry.
- Add missing legacy mappings: users.manage->user:manage,
  settings.manage->setting:manage.
- Grant both new permissions to the admin role (defaults + init.sql seed);
  ensure_default_roles_new_format propagates them to existing deployments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 02:07:36 +05:00
d66928ee78 refactor(proxmox): unify ProxmoxClient construction via from_server
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Failing after 48s
Deploy to Production / Trigger production update (push) Successful in 8s
Collapse four duplicated host+auth client-building blocks into a single
ProxmoxClient.from_server() classmethod (the canonical source of truth):

- _helpers._get_proxmox_client now delegates to from_server (107 callers
  untouched)
- ipam._build_proxmox_client removed; its one call site uses from_server
- task_queue_service keeps its per-server cache pool, builds via from_server
- monitoring_worker keeps its ValueError-on-missing-auth contract, builds
  via from_server

Behaviour preserved (token-first, then password; non-default port appended).
Verified: containers rebuild healthy, smoke test of both auth paths passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:30:57 +05:00
f18ba4d597 revert(nodes): remove server auto-setup feature
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 1m35s
Deploy to Production / Trigger production update (push) Successful in 8s
Drop the /api/servers/auto-setup endpoint, the AutoSetupDialog UI and the
useAutoSetupServer hook. Rename the auto_setup locale strings to
auto_create_token. Also normalize the storages response to an array.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:18:38 +05:00
508be2423a refactor(proxmox): extract _wait_exec_status helper from execute_command
All checks were successful
Deploy to Production / Trigger production update (push) Successful in 8s
Move the guest-agent exec-status polling loop into a shared _wait_exec_status
method reused by execute_command and execute_script. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:14:35 +05:00
e581f19e60 refactor(api): remove duplicate/superseded endpoints
Drop dead route handlers that have an active replacement and zero callers
(frontend, backend, tests):
- create-lxc, create-lxc-smart  -> superseded by POST /lxc/deploy
- clone-lxc                      -> superseded by container/{vmid}/clone
- {server_id}/lxc-templates      -> duplicate of /lxc-templates/{server_id}
- dashboard/stats               -> dashboard uses resources/all + alerts
- permissions (v1)              -> superseded by permissions/v2

The identically-named ProxmoxClient/SecurityService methods are kept (they
back the surviving endpoints).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:14:35 +05:00
681aa2f303 feat(backups): restore target node/storage selection + task progress tracking
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 1m6s
Deploy to Production / Trigger production update (push) Successful in 8s
Add target node and storage overrides to single and bulk restore flows, and
track Proxmox task progress (parse_task_progress in the monitoring worker,
new ProxmoxTask progress column + migration, enriched task API responses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:26:01 +05:00
49f888d4e2 fix(monitoring): break infinite loop on invalid UPID parsing
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 47s
When Proxmox API returns 400 "unable to parse worker upid", the monitoring
worker was getting empty status {}, treating it as running, and retrying
forever every 5 seconds, causing 100% CPU and log spam.

Now when get_task_status() returns empty dict, we immediately mark the task
as failed with exit_status="Invalid UPID" and move on instead of looping.

Fixes production deployment hang.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-13 19:04:26 +05:00
9dd992c6b9 fix(security): enforce VM access control on status endpoints; fix CI artifact upload
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Has been cancelled
- get_vm_status and get_container_status were missing require_vm_access(),
  allowing any user with vms.view to see any VM status regardless of ownership.
  Now users with vms.view.own can only access their own VMs (403 otherwise).
- Downgrade upload-artifact@v4 → @v3: v4 is not supported on Gitea (GHES)
  and caused CI failures on every run.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-13 18:58:15 +05:00
fd592f8b59 fix(api): add missing ssh_keys router import
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Failing after 46s
The ssh_keys module was imported in main.py but missing from api/__init__.py,
potentially causing import issues. Added to ensure all API routers are
explicitly declared and available.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-13 18:53:13 +05:00
5d3f3c2b3b fix(docker): include migrations directory in backend image
The migrations/ directory was missing from the Dockerfile COPY instructions,
causing ModuleNotFoundError when app.main tries to import and run migrations
during startup. This caused production deployments to fail with:
  ModuleNotFoundError: No module named 'migrations'

The automatic migration runner in main.py (line 157) needs this directory to:
1. Detect database schema changes
2. Run pending migrations
3. Initialize default admin user

Fixes production deployment failure when containers start.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-13 18:52:06 +05:00
2e8558fa51 fix(backend): restore operations — add force param, fix QEMU restore API
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Failing after 47s
- restore_vm/restore_lxc: add force=True param (needed for in-place overwrite)
- QEMU restore: remove `restore: 1` from params (breaks Proxmox API — restore
  is triggered by archive presence alone, unlike LXC where restore: 1 required).
  This fixes both single and bulk QEMU restores.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 16:32:23 +05:00
50f2ee1966 fix(security): authenticate VNC/terminal WebSockets and harden SSH/config
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Failing after 46s
Critical security and robustness fixes from a code audit:

- WS terminal/VNC now require a JWT (?token=), validate the session,
  vms.console permission and VM ownership before bridging to Proxmox
  (previously the LXC terminal gave unauthenticated root shell access).
  Frontend ConsolePage passes the token in both WS URLs.
- Fix SSH fallback for `pvecm delnode`: SSHClient now supports password
  auth (it previously crashed with TypeError); ProxmoxClient stores the
  password for SSH fallbacks and the host port suffix is stripped.
- Add production startup warnings for default ADMIN_PASSWORD and wildcard
  CORS_ORIGINS.
- dashboard: use tz-aware utcnow() instead of naive datetime.utcnow().
- crypto: narrow decrypt_value except to InvalidToken and log failures.
- ssh_client: guard the shared connection cache with a lock; make
  unknown-host-key rejection opt-in via SSH_REJECT_UNKNOWN_HOSTS.
- logging: send loguru console output to stderr instead of stdout.
- Sync version fallback to 1.5.1; remove one-off refactor scripts.
- Add CODE_REVIEW.md audit report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 06:31:26 +05:00
bfd92102cf Refactor backend: Split models and proxmox_client into modules, add alembic
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Failing after 2m27s
2026-06-13 03:53:27 +05:00
markmorado
453c18f192 feat: real-time server CRUD sync and pve CLI auto-install
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Has been cancelled
Backend:
- servers.py: instant connectivity check + VM cache sync after create/update;
  broadcast server_added/updated/deleted events via WebSocket
- websocket_manager: add broadcast_event() helper for global fire-and-forget events

Frontend:
- use-nodes: invalidate topology, VM list and resources after server create/update/delete
- use-realtime-sync: new hook with useGlobalRealtimeSync subscribing to server_*
  WS events and invalidating relevant React Query caches
- AppLayout: mount useGlobalRealtimeSync globally

Deploy:
- deploy.sh: auto-install pve CLI during main/quick deploy (idempotent, --auto mode)
2026-05-11 05:31:52 +02:00
markmorado
c317c4d81a fix: deduplicate SSH keys on LXC/VM create; filter qemu templates in wizard
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Has been cancelled
2026-05-08 13:06:23 +02:00
markmorado
323d1e57fa feat: SSH keys management, backups fixes, instances/nodes/templates improvements, dev mode support
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Has been cancelled
2026-05-07 00:30:21 +02:00
markmorado
a92cb0df33 fix(vms): auto-stop running VM/CT before destroy
The destroy endpoints returned 400 'Cannot delete a running container.
Stop it first.' when the user clicked Destroy on a running instance.
Since the destroy dialog already requires typing the instance name to
confirm, the action is unambiguously intentional — now both endpoints
issue stop_container/stop_vm with force=True, wait up to 15s for the
status to leave 'running', and only then proceed with deletion.
2026-05-06 21:11:15 +02:00
markmorado
9da65a46aa fix(lxc): preserve credentials and IP across reinstall
Three related fixes so login/password/SSH key/IP survive a reinstall:

1. backend/app/api/proxmox/lxc.py — on initial LXC deploy save
   cloud_init_user='root' and cloud_init_password=req.password to VMInstance
   (previously both were None, so reinstall could not restore root password).

2. backend/app/api/proxmox/async_ops.py — _do_reinstall_sync passes
   cached.cloud_init_password to create_lxc_container so the new container
   gets the same root password as the original.

3. backend/app/workers/monitoring_worker.py — VM sync worker no longer
   overwrites VMInstance.ip_address with NULL when no IPAM allocation
   exists for that (server_id, vmid). It only updates the field when
   IPAM has a value, otherwise the IP saved at deploy/wizard time is
   preserved.
2026-05-06 20:22:50 +02:00
markmorado
aba2eb3583 feat(lxc): support reinstall for LXC created from CT template file
Previously the reinstall endpoint required cached.template_id (clone-from-VM-
template flow), so LXC instances created via the wizard from an OS template
file (e.g. local:vztmpl/debian-13-...tar.zst) returned 'VM has no associated
template; reinstall is not available'.

Now reinstall works in two modes:
  1) clone-from-template (existing): VM/LXC with cached.template_id pointing
     to OSTemplate.vmid
  2) ostemplate-file (new, LXC only): cached.template_name like
     'storage:vztmpl/...tar.zst'. Reinstall stops, deletes, then re-creates
     the container with the same VMID using create_lxc_container, preserving
     network config, storage, rootfs size, swap, features, ssh_keys, etc.
     pulled from the previous container config + VMInstance cache.
2026-05-06 20:11:05 +02:00
markmorado
daeddbc6c7 fix(lxc): enable nesting=1 by default for systemd 255+ compat
Ubuntu 24.04 / Debian 13 ship with systemd >= 255 which requires
features=nesting=1 in unprivileged LXC containers. Without it,
container start emits 'WARN: Systemd 255 detected. You may need
to enable nesting.' Now nesting is enabled by default unless the
caller explicitly passes a 'features' string.
2026-05-06 20:03:22 +02:00
markmorado
5d905361b6 fix(lxc): remove double URL-encoding of SSH public keys
proxmoxer URL-encodes parameters automatically. The extra urllib.parse.quote()
call was double-encoding SSH keys, causing Proxmox to reject them with
'500 Internal Server Error: SSH public key validation error'.

Now passes the SSH key as-is (only stripped of whitespace), letting proxmoxer
handle encoding.
2026-05-06 19:55:25 +02:00
markmorado
1cf099ba1d feat: LXC container creation from CT templates
- Add LXC deploy API endpoints: GET /proxmox/api/lxc-templates/{id},
  GET /proxmox/api/lxc-storages/{id}, POST /proxmox/api/lxc/deploy
- Add ProxmoxClient.get_lxc_storage_templates() and get_node_storages()
- Fix duplicate create_lxc_container() — removed dead copy, updated
  canonical with nameserver/searchdomain params
- Fix wait_for_task(): accept WARNINGS:N exitstatus as success
- Add IPAM IP release on deploy failure
- Add frontend hooks: useLXCTemplates, useLXCStorages, useDeployLXC
  with 5-min staleTime caching
- Add LXC wizard steps to CreateInstanceWizard (type, template, config)
- Add LXC i18n keys to en.json and ru.json
- Add LXCTemplate and LXCDeployRequest TypeScript interfaces
2026-05-06 19:17:04 +02:00