mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-09 11:41:56 -07:00
Merge pull request #1599 from netalertx/next_release
Fix set_alias 404 handling: simplify failure check to prevent silent regression
This commit is contained in:
@@ -50,6 +50,10 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
|
|||||||
* **POST** `/device/<mac>`
|
* **POST** `/device/<mac>`
|
||||||
Create or update a device record.
|
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/<mac>/update-column`](#7-update-a-single-column) instead.
|
||||||
|
|
||||||
**Request Body**:
|
**Request Body**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -62,8 +66,8 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
|
|||||||
|
|
||||||
**Behavior**:
|
**Behavior**:
|
||||||
|
|
||||||
* If `createNew=true` → creates a new device
|
* If `createNew=true` → inserts a new device row
|
||||||
* Otherwise → updates existing device fields
|
* Otherwise → **replaces all editable fields** on the existing device
|
||||||
|
|
||||||
**Response**:
|
**Response**:
|
||||||
|
|
||||||
@@ -163,7 +167,13 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
|
|||||||
## 7. Update a Single Column
|
## 7. Update a Single Column
|
||||||
|
|
||||||
* **POST** `/device/<mac>/update-column`
|
* **POST** `/device/<mac>/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/<mac>`](#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`.
|
||||||
|
|
||||||
**Request Body**:
|
**Request Body**:
|
||||||
|
|
||||||
@@ -190,6 +200,108 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 8. Lock / Unlock a Device Field
|
||||||
|
|
||||||
|
* **POST** `/device/<mac>/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, `false` to unlock (default when omitted) |
|
||||||
|
|
||||||
|
**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/<mac>/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
|
## Example `curl` Requests
|
||||||
|
|
||||||
**Get Device Details**:
|
**Get Device Details**:
|
||||||
@@ -233,3 +345,30 @@ curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/update-
|
|||||||
--data '{"columnName":"devName","columnValue":"Updated Device"}'
|
--data '{"columnName":"devName","columnValue":"Updated Device"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Lock a Field**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/field/lock" \
|
||||||
|
-H "Authorization: Bearer <API_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data '{"fieldName":"devName","lock":true}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Unlock Fields (all devices)**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/devices/fields/unlock" \
|
||||||
|
-H "Authorization: Bearer <API_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data '{"fields":["devName","devVendor"]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set Device Alias**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/set-alias" \
|
||||||
|
-H "Authorization: Bearer <API_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data '{"alias":"My Router"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -585,6 +585,11 @@ def api_device_set_alias(mac, payload=None):
|
|||||||
|
|
||||||
device_handler = DeviceInstance()
|
device_handler = DeviceInstance()
|
||||||
result = device_handler.updateDeviceColumn(mac, 'devName', alias)
|
result = device_handler.updateDeviceColumn(mac, 'devName', alias)
|
||||||
|
|
||||||
|
if not result.get("success"):
|
||||||
|
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)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,19 @@ COLUMN_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_]+$")
|
|||||||
|
|
||||||
# Security whitelists & Literals for documentation
|
# Security whitelists & Literals for documentation
|
||||||
ALLOWED_DEVICE_COLUMNS = Literal[
|
ALLOWED_DEVICE_COLUMNS = Literal[
|
||||||
|
# Main Info
|
||||||
"devName", "devOwner", "devType", "devVendor",
|
"devName", "devOwner", "devType", "devVendor",
|
||||||
"devGroup", "devLocation", "devComments", "devFavorite",
|
"devGroup", "devLocation", "devComments", "devIcon",
|
||||||
"devParentMAC", "devCanSleep"
|
# 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[
|
ALLOWED_NMAP_MODES = Literal[
|
||||||
@@ -407,7 +417,7 @@ class UpdateDeviceColumnRequest(BaseModel):
|
|||||||
class LockDeviceFieldRequest(BaseModel):
|
class LockDeviceFieldRequest(BaseModel):
|
||||||
"""Request to lock/unlock a device field."""
|
"""Request to lock/unlock a device field."""
|
||||||
fieldName: str = Field(..., description="Field name to lock/unlock (e.g., devName, devVendor). Required.")
|
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):
|
class UnlockDeviceFieldsRequest(BaseModel):
|
||||||
@@ -420,7 +430,7 @@ class UnlockDeviceFieldsRequest(BaseModel):
|
|||||||
None,
|
None,
|
||||||
description="List of field names to unlock. If omitted, all tracked fields will be unlocked"
|
description="List of field names to unlock. If omitted, all tracked fields will be unlocked"
|
||||||
)
|
)
|
||||||
clear_all: bool = Field(
|
clearAll: bool = Field(
|
||||||
False,
|
False,
|
||||||
description="True to clear all sources, False to clear only LOCKED/USER"
|
description="True to clear all sources, False to clear only LOCKED/USER"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user