feat: import hosts/VMs/LXC from Proxmox VE with optional auto-sync

Add a Proxmox VE importer that reads the /api2/json REST API with a read-only
API token and drops hosts (proxmox), VMs (vm) and LXC containers (lxc) onto the
canvas as typed nodes with run state and hardware specs (vCPU/RAM/disk).

- Backend: proxmox_service (httpx) + proxmox routes (test-connection, import,
  import-pending, config). Two-tier dedupe — merge onto an existing scanned node
  by IP, else synthetic pve-{host}-{vmid} identity. Update-in-place, never
  deletes. Host->guest rendered as a 'virtual' edge via the pending-link flow.
- Security: token is env-only (PROXMOX_TOKEN_*), never written to disk by the
  app, never returned by any endpoint; errors are credential-sanitized.
- Auto-sync: optional scheduled re-import into pending (APScheduler job).
- PendingDevice.properties carries specs through approve (+ migration).
- Frontend: ProxmoxImportModal, sidebar entry, pending inventory source filter,
  Settings auto-sync section, proxmoxApi client.
- Docs: docs/proxmox-import.md, README + FEATURES sections, .env.example keys.
- Tests: backend service/router/scheduler, frontend modal/client/pending.

ha-relevant: maybe
This commit is contained in:
Pouzor
2026-07-05 18:58:12 +02:00
parent 1d40d70150
commit ab36ba6f81
29 changed files with 2349 additions and 29 deletions
+4
View File
@@ -126,6 +126,10 @@ class PendingDevice(Base):
model: Mapped[str | None] = mapped_column(String, nullable=True)
vendor: Mapped[str | None] = mapped_column(String, nullable=True)
lqi: Mapped[int | None] = mapped_column(Integer, nullable=True)
# Display properties carried from discovery (e.g. Proxmox specs: CPU/RAM/Disk,
# VMID). Generic NodeProperty shape {key,value,icon,visible}; merged into the
# Node's properties on approve. Empty for scan/mesh sources that don't set it.
properties: Mapped[list[Any]] = mapped_column(JSON, default=list)
discovered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
# Transient (not persisted): populated per-request by the scan routes to report