Browser Frontend¶
The netbox-proxbox plugin delivers its UI through standard Django templates bundled with the plugin, augmented by JavaScript for real-time SSE streaming, keepalive polling, WebSocket updates, and dashboard card hydration.
Template Hierarchy¶
All plugin templates live under netbox_proxbox/templates/netbox_proxbox/. They extend NetBox's base templates to inherit the standard NetBox chrome (navigation, header, breadcrumbs).
templates/netbox_proxbox/
├── base/ Base plugin page template (extends NetBox layout)
├── home/ Plugin dashboard / home page
│ ├── index.html Main plugin home
│ └── partials/ Card and widget fragments
├── fastapi/ FastAPI endpoint detail and test pages
├── proxmox/ ProxmoxEndpoint detail and cluster/node pages
├── table/ Shared table fragment templates
├── test/ WebSocket and SSE test/debug pages
└── partials/ Reusable fragment templates (status badges, etc.)
Template Extensions¶
netbox_proxbox/template_content.py injects custom panels and buttons into standard NetBox pages outside the plugin:
| Extension class | Target model | What it adds |
|---|---|---|
ProxboxJobButtons |
core.Job |
"Cancel job" / "Run now" buttons on Job detail pages for Proxbox Sync jobs |
ProxboxVMPanel |
virtualization.VirtualMachine |
ProxBox data panel (snapshots, backups, task history) on VM detail pages |
Static Assets¶
static/netbox_proxbox/
├── css/ Compiled CSS for specific plugin pages
├── js/ Browser-side JavaScript (12 files)
└── styles/ SCSS sources and compiled theme assets
JavaScript Files¶
| File | Purpose |
|---|---|
common.js |
Shared helpers: badge state, tooltip setup, CSRF token retrieval |
home.js |
Dashboard initialization: WebSocket setup, keepalive badge refresh, Proxmox card hydration |
sync.js |
SSE consumer: EventSource management, frame parsing, DOM progress updates |
job_log_view.js |
Renders streamed Proxbox job log payloads on Job detail pages |
job_live_panel.js |
Live job progress panel on the home page (polls job status, updates summary panel) |
websocket.js |
WebSocket integration: onSyncEnd(listener) and notifySyncEnd(obj) hooks |
endpoint-status.js |
Periodic keepalive/status badge refresh for dashboard and list pages |
polling.js |
Generic repeated-polling helpers |
logs.js |
Backend logs page behavior |
device.js |
Client behavior for device/node sync views |
virtual_machine.js |
Client behavior for VM sync views |
table.js |
Table-specific dynamic behavior |
Browser Page Lifecycle¶
flowchart TD
A["Page Load"] --> B["Django renders template\n(initial HTML)"]
B --> C["DOMContentLoaded"]
C --> D["home.js: init WebSocket\n(if WS endpoint configured)"]
C --> E["endpoint-status.js: start\nkeepalive polling (badge refresh)"]
C --> F["home.js: hydrate Proxmox\ncards via /proxbox/card-data/"]
D --> G["websocket.js: connect to\nws://.../plugins/proxbox/ws/"]
G --> H{Sync triggered?}
F --> I["Cards show cluster\nmetadata + warnings"]
H -- "User clicks Sync" --> J["Form POST to sync URL"]
J --> K["Job enqueued in RQ"]
K --> L["Browser redirected to Job\ndetail page or SSE stream page"]
L --> M["sync.js: open EventSource\nfor SSE stream"]
M --> N["Parse SSE frames:\ndiscovery, step, item_progress"]
N --> O["Update DOM progress elements\n(stage indicators, counts)"]
O --> P{complete event?}
P -- "ok=true" --> Q["Show success state\nnotifySyncEnd()"]
P -- "ok=false" --> R["Show error state\nShow error_detail message"]
Q --> S["websocket.js:\nbroadcast onSyncEnd to listeners"]
R --> S
S --> T["home.js: refresh cards\nand job live panel"]
Dashboard Hydration¶
The plugin home page inlines home_inline.js, loads with skeleton card elements, and then hydrates them asynchronously:
- One worker pool, with a default concurrency of four, processes FastAPI badges, dependent endpoint badges, and Proxmox cards without an endpoint-count-sized request burst.
- The card task calls the card-data route for each configured Proxmox endpoint.
- The Django view returns a JSON payload with cluster metadata and any warnings.
- The script updates the DOM card with the received data.
The home-page keepalive badges are refreshed by home_inline.js; endpoint list pages use endpoint-status.js. A successful keepalive badge is authoritative and card hydration never downgrades it. Backend HTTP 429/503 responses render as a neutral Throttled state. Card hydration honors the server-provided Retry-After delay, coalesces each card to one pending timer, and permits at most three retries. Timers enqueue work into the same persistent four-worker scheduler as initial badge and card requests; they never call the network path directly. The keepalive JSON views are protected by the normal login requirement and use restricted querysets.
The poller is deliberately bounded, because every keepalive request can land on a freshly spawned WSGI worker thread (Granian in netbox-docker spawns blocking threads on demand and reaps them after 30 s idle) and Django keeps one persistent database connection per thread for CONN_MAX_AGE seconds. An unbounded poller therefore turns into a PostgreSQL connection leak on any long-lived endpoint page:
- One self-rescheduling loop per page (a
setTimeoutarmed only after the previous round settles), never a fixedsetInterval, so a slow backend cannot stack overlapping rounds. - Badges are refreshed sequentially, one request at a time, instead of fanning out one request per badge.
- Polling pauses while the tab is hidden (
document.hiddenvia the Page Visibility API) and resumes with one immediate refresh when it becomes visible again. - The keepalive view (
views/keepalive_status.py) retires the request thread's Django database connection once the JSON payload is built: it moves the connection'sclose_atdeadline to now so Django's ownrequest_finishedhandler (close_old_connections) closes it after response middleware has run. It never closes inside an atomic block, soATOMIC_REQUESTSdeployments keep their transaction intact. A reaped worker thread therefore cannot leave an idle connection behind. - The NetBox endpoint push and the Proxmox mode detection that piggyback on keepalive are throttled through the shared Django cache with an atomic
cache.addclaim (5 minutes), keyed per FastAPI endpoint or per Proxmox endpoint so every configured backend still gets its own push, and fall back to a process-local throttle only when no cache is available. - FastAPI probe results are cached for 30 seconds by endpoint and a hash of its connection settings. Failures use a three-second TTL. A successful Proxmox endpoint push caches a secret-safe payload fingerprint and backend endpoint ID for five minutes. The cache is only a discovery hint: each use verifies that single backend row still has the endpoint's immutable identity suffix and resolved host/port. A missing or reassigned ID invalidates the hint and immediately falls back to full discovery and registration. Endpoint saves also invalidate the optimization; sync-job preflight retains its explicit batch push behavior.
SSE Rendering¶
sync.js is the SSE consumer for both the direct streaming views and the background job stream:
function connectToSyncStream(streamUrl, onFrame, onComplete) {
const evtSource = new EventSource(streamUrl);
evtSource.addEventListener("step", (e) => {
const data = JSON.parse(e.data);
updateStageIndicator(data.step, data.status, data.result);
});
evtSource.addEventListener("item_progress", (e) => {
const data = JSON.parse(e.data);
incrementProgressCounter(data.type, data.status);
});
evtSource.addEventListener("complete", (e) => {
const data = JSON.parse(e.data);
evtSource.close();
onComplete(data);
notifySyncEnd(data);
});
evtSource.addEventListener("error", (e) => {
const data = JSON.parse(e.data);
showErrorBanner(data.error, data.detail);
});
}
Job Log Viewer¶
When an operator opens a NetBox Job detail page for a Proxbox Sync job, job_log_view.js is loaded by the template extension. It renders the stored SSE log payload — which was captured by the on_frame callback during the job's run() execution — into a structured UI with per-stage indicators, object counts, and error messages.
WebSocket Integration¶
websocket.js provides a thin integration layer on top of the browser WebSocket API:
// Register a callback that fires when any sync operation completes
onSyncEnd(function(syncData) {
refreshDashboardCards();
updateJobLivePanel(syncData);
});
// Called by sync.js when the complete event arrives
notifySyncEnd(completePayload);
The WebSocket URL is injected into the page template from FastAPIEndpoint.websocket_url. If no WebSocket endpoint is configured, websocket.js degrades gracefully and the dashboard falls back to polling-only mode.
The server-side polling bridge returns both a process-local message cursor and,
when supported by the backend, a run identifier from its CSRF-protected start
request. polling.js carries both values: the cursor prevents replay of older
buffer entries, while the run identifier prevents a stale or unrelated terminal
from completing the current page's run. Correlated full updates wait for the
full-update aggregate terminal and report its failed status through the
error callback. Backends without the ws_run_id capability continue to use the
legacy bare command and per-stage terminal behavior. Legacy polling excludes
all frames that carry a run identifier. If dispatch-time capability validation
or an invalid-envelope response downgrades a queued command, a run-matched
control record switches that page to legacy matching without attributing other
workers' correlated broadcasts to it. If endpoint trust changes before a
command or compatibility retry is transmitted, the bridge closes the
connection and publishes a failed sync-run record; the poller reports that
record through the error callback instead of waiting for its idle timeout.
Forms and Sync Actions¶
Sync action buttons are implemented as native HTML forms with data-sync-url attributes rather than fetch/AJAX calls. The form submission navigates to the sync URL, which either:
- Returns a
StreamingHttpResponse(SSE stream) for browser-facing streaming views - Redirects to the NetBox Job detail page after enqueuing a background job
This means sync actions work correctly without JavaScript — JS only adds the real-time progress overlay.