Topology & Bulk Operations
FreeSDN builds a live topology graph of every device it knows about and lets you act on many devices at once through bulk operations. This page covers both features: how the topology graph works, how to save and restore layouts, and how bulk jobs are dispatched, tracked, and optionally rolled back automatically.
Topology graph
Section titled “Topology graph”What it does
Section titled “What it does”GET /api/v1/topology/graph returns a graph object whose nodes are devices and whose
edges are links between them. You can request the graph at site scope or across the whole
organisation. The optional health overlay (enabled by default) annotates each node with the
device’s current health_score (0-100) and health_status
(HEALTHY / WARNING / DEGRADED / CRITICAL).
The frontend at /topology renders this graph interactively. Node positions can be set manually
by drag-and-drop or computed automatically by one of three built-in layout algorithms.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/topology/graph | Graph (nodes + edges), optional health overlay | device:read |
| GET | /api/v1/topology/layout/{site_id} | Saved layout for a site (null if none saved) | device:read |
| PUT | /api/v1/topology/layout/{site_id} | Save or update layout positions | device:write |
| POST | /api/v1/topology/auto-layout/{site_id} | Compute an auto-layout (does not persist) | device:read |
| DELETE | /api/v1/topology/layout/{site_id} | Delete a saved layout | device:write |
Query parameters - graph endpoint
Section titled “Query parameters - graph endpoint”| Parameter | Type | Default | Description |
|---|---|---|---|
site_id | UUID | - | Scope the graph to one site. Omit for the full org graph. |
include_health | bool | true | Attach health scores and status to each node. |
Auto-layout algorithms
Section titled “Auto-layout algorithms”Pass the algorithm query parameter to POST /api/v1/topology/auto-layout/{site_id}:
| Value | Description |
|---|---|
auto | System chooses the most appropriate algorithm for the device count. |
hierarchical | Top-down tree layout; useful for switch stacks with clear parent-leaf relationships. |
force_directed | Physics-based spring layout; tends to work better for mesh or partially-connected topologies. |
Auto-layout does not persist. It returns new x/y values for each node. If you are
satisfied with the result, follow with a PUT /api/v1/topology/layout/{site_id} to save it.
Saved layouts
Section titled “Saved layouts”Layouts are per-user and per-site. Two operators viewing the same site can have independent
layouts. Saving a layout stores the x/y position of every node. On the next page load the
frontend retrieves the saved layout and places nodes accordingly.
To reset to an unsaved state, call DELETE /api/v1/topology/layout/{site_id}.
Permissions and access control
Section titled “Permissions and access control”Every site-scoped topology request verifies the site_id against the caller’s organisation
before responding. A foreign site_id returns 404, not an empty graph, to avoid leaking
the existence of other organisations’ sites. Users with site-limited grants (operator or viewer
scoped to specific sites) are also checked via per-user site access before the graph is returned.
Bulk operations
Section titled “Bulk operations”What it does
Section titled “What it does”The bulk-operations engine lets you dispatch a single job - reboot, config push, or firmware
upgrade - to many devices at once. You target devices by site, explicit list, device group, or
tag. The job runs asynchronously on the sync Celery queue. You can watch progress through the
status endpoint or the /bulk-operations page in the UI.
Optionally, you can supply a staged rollout strategy: the job processes devices in percentage- based waves, pausing between waves, and can automatically roll back all completed operations if the failure rate exceeds a threshold.
Supported operations
Section titled “Supported operations”| Operation | Required permission |
|---|---|
reboot | device:reboot |
push_config | config:push |
firmware_update | firmware:upgrade |
The permission check runs twice: once at job creation (403 if the caller lacks the permission entirely) and again at job execution for each device (ensuring a permission change between submission and execution is caught).
Targeting
Section titled “Targeting”Use the target object in the create request body. Exactly one scope must be provided:
| Scope | What it targets | Additional fields |
|---|---|---|
site | All matching devices at one site | scope_id (site UUID) |
device_list | An explicit list of device UUIDs | device_ids |
device_group | All members of a device group | scope_id (group UUID) |
tag | All devices with a specific tag | tag (string) |
All scopes accept an optional device_type filter to restrict the operation to, for example,
only access_point or switch devices within the target.
Staged rollout
Section titled “Staged rollout”Bulk jobs support an optional rollout object:
{ "rollout": { "strategy": "staged", "stages": [ { "percent": 10, "wait_minutes": 15 }, { "percent": 40, "wait_minutes": 30 }, { "percent": 50, "wait_minutes": 0 } ], "failure_threshold_percent": 20, "rollback_on_failure": true }}| Field | Description |
|---|---|
stages | Ordered list of wave definitions. percent is the share of total devices in that wave; wait_minutes is the pause before the next wave starts. |
failure_threshold_percent | Percentage of devices allowed to fail before the job aborts. Defaults to 5 if omitted. |
rollback_on_failure | If true and the failure rate exceeds the threshold, the engine aborts the job and skips all remaining devices. Operations already completed on earlier-wave devices are not reversed - rollback_on_failure controls job-abort behaviour, not device-state restoration. |
If you omit rollout, all devices are processed concurrently with no staged pausing.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose | Permission |
|---|---|---|---|
| POST | /api/v1/enterprise/bulk-operations | Create and dispatch a bulk job | Per-operation (see above) |
| GET | /api/v1/enterprise/bulk-operations | List jobs | config:read |
| GET | /api/v1/enterprise/bulk-operations/{job_id} | Job status and per-device results | config:read |
| POST | /api/v1/enterprise/bulk-operations/{job_id}/cancel | Cancel a pending or running job | Per-operation |
Create request body
Section titled “Create request body”{ "operation": "firmware_update", "target": { "scope": "device_group", "scope_id": "<device-group-uuid>" }, "config": { "firmware_url": "https://dl.example.com/fw-8.4.59.bin", "firmware_version": "8.4.59" }, "rollout": { "strategy": "staged", "stages": [ { "percent": 25, "wait_minutes": 10 }, { "percent": 75, "wait_minutes": 0 } ], "failure_threshold_percent": 10, "rollback_on_failure": false }}Job status response
Section titled “Job status response”The GET /api/v1/enterprise/bulk-operations/{job_id} response includes:
| Field | Description |
|---|---|
job_id | UUID of the job |
operation | reboot, push_config, or firmware_update |
status | pending, running, completed, failed, cancelled |
devices_total | Total devices targeted |
devices_completed | Devices finished successfully |
devices_failed | Devices that returned an error |
devices_skipped | Devices excluded (e.g. access-denied or offline) |
current_stage | Index of the active rollout stage (0-indexed) |
created_at | ISO-8601 timestamp |
started_at | When the Celery task began execution |
completed_at | When the job reached a terminal state |
error_message | Set if the job failed at the job level (not per-device) |
Listing and filtering jobs
Section titled “Listing and filtering jobs”GET /api/v1/enterprise/bulk-operations accepts:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by job status (pending, running, completed, failed, cancelled). |
limit | int | Maximum results (up to 200). |
Cancellation
Section titled “Cancellation”POST /api/v1/enterprise/bulk-operations/{job_id}/cancel stops a job if it is still pending
or running. The same per-operation permission check applies to cancellation. Devices already
processed before cancellation are not automatically reversed.
Required roles
Section titled “Required roles”The UI page /bulk-operations is gated on the frontend device:update permission. The backend
checks the more granular per-operation permission at both create and cancel time.
| Role | Can view topology | Can save layouts | Can create bulk jobs | Can cancel bulk jobs |
|---|---|---|---|---|
| viewer | Yes | No | No | No |
| operator | Yes | No | Depends on per-op perm | Depends on per-op perm |
| site_admin | Yes | Yes (own) | Yes - reboot + config push only (no firmware_update) | Yes - reboot + config push only (no firmware_update) |
| org_admin | Yes | Yes (own) | Yes - reboot + config push only (no firmware_update) | Yes - reboot + config push only (no firmware_update) |
| admin | Yes | Yes (own) | Yes | Yes |
| super_admin | Yes | Yes (own) | Yes | Yes |
Layout saves are always scoped to the individual user - site_admin saving a layout does not
overwrite another user’s layout for the same site.
Events emitted
Section titled “Events emitted”Both subsystems publish events to the internal event bus, which downstream automation rules, notifications, and WebSocket clients can subscribe to.
| Event | Source | When |
|---|---|---|
bulkop.created | Bulk operations | Job accepted and queued |
bulkop.cancelled | Bulk operations | Job cancelled |
device.lifecycle.* | Lifecycle FSM (POST /enterprise/devices/{id}/lifecycle) | On each manual lifecycle state transition |
Caveats
Section titled “Caveats”Next steps
Section titled “Next steps”- SLA Management - understand the health scores and SLA policies shown on each topology node.
- Device Groups & Lifecycle - build the device groups you target in bulk jobs.
- Enterprise Overview - see the full list of enterprise features and caveats.
- Alerting & Notifications - wire
bulkop.createdevents into notification channels. - Roles & Permissions - understand which roles map to
device:reboot,config:push, andfirmware:upgrade.