ab36ba6f81
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
52 lines
1.6 KiB
Python
52 lines
1.6 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class PendingDeviceResponse(BaseModel):
|
|
id: str
|
|
ip: str | None
|
|
mac: str | None
|
|
hostname: str | None
|
|
os: str | None
|
|
services: list[Any]
|
|
suggested_type: str | None
|
|
status: str
|
|
discovery_source: str | None
|
|
ieee_address: str | None = None
|
|
friendly_name: str | None = None
|
|
device_subtype: str | None = None
|
|
model: str | None = None
|
|
vendor: str | None = None
|
|
lqi: int | None = None
|
|
# Display properties carried from discovery (e.g. Proxmox specs). Merged into
|
|
# the node on approve; empty for scan/mesh sources that don't set them.
|
|
properties: list[Any] = []
|
|
discovered_at: datetime
|
|
# Number of distinct canvases (designs) this device already appears on,
|
|
# correlated by ip / ieee_address against existing nodes. Computed per-request.
|
|
canvas_count: int = 0
|
|
# Timestamps from the linked canvas node(s), correlated by ip / ieee_address.
|
|
# Null when the device is not on any canvas yet. Aggregated across matches:
|
|
# created_at = oldest; last_scan / last_modified / last_seen = newest.
|
|
node_created_at: datetime | None = None
|
|
node_last_scan: datetime | None = None
|
|
node_last_modified: datetime | None = None
|
|
node_last_seen: datetime | None = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class ScanRunResponse(BaseModel):
|
|
id: str
|
|
status: str
|
|
kind: str = "ip"
|
|
ranges: list[str]
|
|
devices_found: int
|
|
started_at: datetime
|
|
finished_at: datetime | None
|
|
error: str | None
|
|
|
|
model_config = {"from_attributes": True}
|