From b530a6e6350bfe485672bd65829c9fbf61a2705e Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:57:58 +0000 Subject: [PATCH 1/6] Enhance API documentation and schemas: add new device field locking/unlocking endpoints and expand allowed column names for updates #1598 --- docs/API_DEVICE.md | 131 +++++++++++++++++++++++++++ server/api_server/openapi/schemas.py | 14 ++- 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/docs/API_DEVICE.md b/docs/API_DEVICE.md index 99692c3c..e07a470b 100755 --- a/docs/API_DEVICE.md +++ b/docs/API_DEVICE.md @@ -165,6 +165,8 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd * **POST** `/device//update-column` Update one specific column for a device. +Allowed `columnName` values: `devName`, `devOwner`, `devType`, `devVendor`, `devGroup`, `devLocation`, `devComments`, `devIcon`, `devFavorite`, `devAlertEvents`, `devAlertDown`, `devCanSleep`, `devSkipRepeated`, `devReqNicsOnline`, `devForceStatus`, `devParentMAC`, `devParentPort`, `devParentRelType`, `devSSID`, `devSite`, `devVlan`, `devStaticIP`, `devIsNew`, `devIsArchived`, `devCustomProps`. + **Request Body**: ```json @@ -190,6 +192,108 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd --- +## 8. Lock / Unlock a Device Field + +* **POST** `/device//field/lock` + Lock a field to prevent plugin overwrites, or unlock it to allow overwrites again. + +**Request Body**: + +```json +{ + "fieldName": "devName", + "lock": true +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `fieldName` | string | ✅ | Field to lock/unlock (e.g. `devName`, `devVendor`) | +| `lock` | boolean | ❌ | `true` to lock (default), `false` to unlock | + +**Response** (success): + +```json +{ + "success": true, + "fieldName": "devName", + "locked": true, + "message": "Field devName locked" +} +``` + +**Error Responses**: + +* Field does not support locking → HTTP 400 +* Unauthorized → HTTP 403 + +--- + +## 9. Unlock / Clear Device Fields (Bulk) + +* **POST** `/devices/fields/unlock` + Unlock fields (clear `LOCKED`/`USER` sources) for one device, a list of devices, or all devices. + +**Request Body**: + +```json +{ + "mac": "AA:BB:CC:DD:EE:FF", + "fields": ["devName", "devVendor"], + "clearAll": false +} +``` + +| Field | Type | Required | Description | +|---|---|---|---| +| `mac` | string or array | ❌ | Single MAC, list of MACs, or omit for all devices | +| `fields` | array of strings | ❌ | Fields to unlock. Omit to unlock all tracked fields | +| `clearAll` | boolean | ❌ | `true` clears all sources; `false` (default) clears only `LOCKED`/`USER` | + +**Response** (success): + +```json +{ + "success": true +} +``` + +**Error Responses**: + +* `fields` is not a list → HTTP 400 +* Unauthorized → HTTP 403 + +--- + +## 10. Set Device Alias + +* **POST** `/device//set-alias` + Convenience wrapper to update the device display name (`devName`). + +**Request Body**: + +```json +{ + "alias": "My Router" +} +``` + +**Response** (success): + +```json +{ + "success": true +} +``` + +**Error Responses**: + +* Missing `alias` → HTTP 400 +* Device not found → HTTP 404 +* Unauthorized → HTTP 403 + +--- + ## Example `curl` Requests **Get Device Details**: @@ -233,3 +337,30 @@ curl -X POST "http://:/device/AA:BB:CC:DD:EE:FF/update- --data '{"columnName":"devName","columnValue":"Updated Device"}' ``` +**Lock a Field**: + +```bash +curl -X POST "http://:/device/AA:BB:CC:DD:EE:FF/field/lock" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + --data '{"fieldName":"devName","lock":true}' +``` + +**Unlock Fields (all devices)**: + +```bash +curl -X POST "http://:/devices/fields/unlock" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + --data '{"fields":["devName","devVendor"]}' +``` + +**Set Device Alias**: + +```bash +curl -X POST "http://:/device/AA:BB:CC:DD:EE:FF/set-alias" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + --data '{"alias":"My Router"}' +``` + diff --git a/server/api_server/openapi/schemas.py b/server/api_server/openapi/schemas.py index 7310f637..f3fbccbc 100644 --- a/server/api_server/openapi/schemas.py +++ b/server/api_server/openapi/schemas.py @@ -33,9 +33,19 @@ COLUMN_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_]+$") # Security whitelists & Literals for documentation ALLOWED_DEVICE_COLUMNS = Literal[ + # Main Info "devName", "devOwner", "devType", "devVendor", - "devGroup", "devLocation", "devComments", "devFavorite", - "devParentMAC", "devCanSleep" + "devGroup", "devLocation", "devComments", "devIcon", + # Alerts & Behavior + "devFavorite", "devAlertEvents", "devAlertDown", + "devCanSleep", "devSkipRepeated", "devReqNicsOnline", "devForceStatus", + # Network topology + "devParentMAC", "devParentPort", "devParentRelType", + "devSSID", "devSite", "devVlan", + # Display / Status + "devStaticIP", "devIsNew", "devIsArchived", + # Custom properties + "devCustomProps", ] ALLOWED_NMAP_MODES = Literal[ From 28e6d62ccb7e0f7405dae63b9244b58845d13663 Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:34:08 +0000 Subject: [PATCH 2/6] Update Device API documentation: clarify full-replace and partial-update semantics for device fields #1597 --- docs/API_DEVICE.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/API_DEVICE.md b/docs/API_DEVICE.md index e07a470b..7aab7e8d 100755 --- a/docs/API_DEVICE.md +++ b/docs/API_DEVICE.md @@ -50,6 +50,10 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd * **POST** `/device/` Create or update a device record. +> ⚠️ **Full-replace (PUT) semantics.** Every editable field is written on each call. Any field omitted from the payload is reset to its default (empty string or `0`). This matches how the frontend edit form works — it always sends the complete device state. +> +> To update a **single field** without affecting others, use [`POST /device//update-column`](#7-update-a-single-column) instead. + **Request Body**: ```json @@ -62,8 +66,8 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd **Behavior**: -* If `createNew=true` → creates a new device -* Otherwise → updates existing device fields +* If `createNew=true` → inserts a new device row +* Otherwise → **replaces all editable fields** on the existing device **Response**: @@ -163,7 +167,11 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd ## 7. Update a Single Column * **POST** `/device//update-column` - Update one specific column for a device. + Update exactly one field for a device without touching any other fields. + +> ✅ **Partial-update (PATCH) semantics.** Only the specified column is written. All other fields are left unchanged. Use this for automation, integrations, and any workflow that needs to update a single attribute. +> +> To replace all fields at once (e.g. saving from the edit form), use [`POST /device/`](#2-update-device-fields). Allowed `columnName` values: `devName`, `devOwner`, `devType`, `devVendor`, `devGroup`, `devLocation`, `devComments`, `devIcon`, `devFavorite`, `devAlertEvents`, `devAlertDown`, `devCanSleep`, `devSkipRepeated`, `devReqNicsOnline`, `devForceStatus`, `devParentMAC`, `devParentPort`, `devParentRelType`, `devSSID`, `devSite`, `devVlan`, `devStaticIP`, `devIsNew`, `devIsArchived`, `devCustomProps`. From da8b694a49b6c2a9e295e35228cc7f2b30a547eb Mon Sep 17 00:00:00 2001 From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:38:43 +0000 Subject: [PATCH 3/6] Update API documentation and schemas: clarify lock/unlock behavior for device fields and enhance error handling in device alias update --- docs/API_DEVICE.md | 2 +- server/api_server/api_server_start.py | 4 ++++ server/api_server/openapi/schemas.py | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/API_DEVICE.md b/docs/API_DEVICE.md index 7aab7e8d..26c8741c 100755 --- a/docs/API_DEVICE.md +++ b/docs/API_DEVICE.md @@ -217,7 +217,7 @@ Allowed `columnName` values: `devName`, `devOwner`, `devType`, `devVendor`, `dev | Field | Type | Required | Description | |---|---|---|---| | `fieldName` | string | ✅ | Field to lock/unlock (e.g. `devName`, `devVendor`) | -| `lock` | boolean | ❌ | `true` to lock (default), `false` to unlock | +| `lock` | boolean | ❌ | `true` to lock, `false` to unlock (default when omitted) | **Response** (success): diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index f85f9a57..b92120d9 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -585,6 +585,10 @@ def api_device_set_alias(mac, payload=None): device_handler = DeviceInstance() result = device_handler.updateDeviceColumn(mac, 'devName', alias) + + if not result.get("success") and result.get("error") == "Device not found": + return jsonify(result), 404 + return jsonify(result) diff --git a/server/api_server/openapi/schemas.py b/server/api_server/openapi/schemas.py index f3fbccbc..66693901 100644 --- a/server/api_server/openapi/schemas.py +++ b/server/api_server/openapi/schemas.py @@ -417,7 +417,7 @@ class UpdateDeviceColumnRequest(BaseModel): class LockDeviceFieldRequest(BaseModel): """Request to lock/unlock a device field.""" fieldName: str = Field(..., description="Field name to lock/unlock (e.g., devName, devVendor). Required.") - lock: bool = Field(True, description="True to lock the field, False to unlock") + lock: bool = Field(False, description="True to lock the field, False (default) to unlock") class UnlockDeviceFieldsRequest(BaseModel): @@ -430,7 +430,7 @@ class UnlockDeviceFieldsRequest(BaseModel): None, description="List of field names to unlock. If omitted, all tracked fields will be unlocked" ) - clear_all: bool = Field( + clearAll: bool = Field( False, description="True to clear all sources, False to clear only LOCKED/USER" ) From e3a4c62d5f93591a533b011bc861eee1067ccfa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:05:54 +0000 Subject: [PATCH 4/6] Fix set_alias 404 check: simplify to match generic updateDeviceColumn pattern Agent-Logs-Url: https://github.com/netalertx/NetAlertX/sessions/661c66ce-45e8-4f96-b51d-1bb0b918c669 Co-authored-by: jokob-sk <96159884+jokob-sk@users.noreply.github.com> --- server/api_server/api_server_start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index b92120d9..1430d169 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -586,7 +586,7 @@ def api_device_set_alias(mac, payload=None): device_handler = DeviceInstance() result = device_handler.updateDeviceColumn(mac, 'devName', alias) - if not result.get("success") and result.get("error") == "Device not found": + if not result.get("success"): return jsonify(result), 404 return jsonify(result) From 6eaa477ed35c4825914db104930b8748f8908a26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:23:52 +0000 Subject: [PATCH 5/6] Initial plan From a6d3b856145b3b5dd7c4968741c1f30eb4832c6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:28:50 +0000 Subject: [PATCH 6/6] Fix set-alias endpoint: return HTTP 200 with normalized error key on failure Agent-Logs-Url: https://github.com/netalertx/NetAlertX/sessions/05ab18a3-4ac2-492d-bb80-67a1cc089bd9 Co-authored-by: jokob-sk <96159884+jokob-sk@users.noreply.github.com> --- server/api_server/api_server_start.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index 1430d169..484e19f0 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -587,7 +587,8 @@ def api_device_set_alias(mac, payload=None): result = device_handler.updateDeviceColumn(mac, 'devName', alias) if not result.get("success"): - return jsonify(result), 404 + err = result.get("error") or result.get("message") or f"Failed to update alias for device {mac}" + return jsonify({"success": False, "error": err}) return jsonify(result)