Skip to content

Commit ef0d521

Browse files
authored
Merge pull request #1599 from netalertx/next_release
Fix set_alias 404 handling: simplify failure check to prevent silent regression
2 parents 548d237 + 16e2249 commit ef0d521

File tree

3 files changed

+161
-7
lines changed

3 files changed

+161
-7
lines changed

docs/API_DEVICE.md

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
5050
* **POST** `/device/<mac>`
5151
Create or update a device record.
5252

53+
> ⚠️ **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.
54+
>
55+
> To update a **single field** without affecting others, use [`POST /device/<mac>/update-column`](#7-update-a-single-column) instead.
56+
5357
**Request Body**:
5458

5559
```json
@@ -62,8 +66,8 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
6266

6367
**Behavior**:
6468

65-
* If `createNew=true`creates a new device
66-
* Otherwise → updates existing device fields
69+
* If `createNew=true`inserts a new device row
70+
* Otherwise → **replaces all editable fields** on the existing device
6771

6872
**Response**:
6973

@@ -163,7 +167,13 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
163167
## 7. Update a Single Column
164168

165169
* **POST** `/device/<mac>/update-column`
166-
Update one specific column for a device.
170+
Update exactly one field for a device without touching any other fields.
171+
172+
> **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.
173+
>
174+
> To replace all fields at once (e.g. saving from the edit form), use [`POST /device/<mac>`](#2-update-device-fields).
175+
176+
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`.
167177

168178
**Request Body**:
169179

@@ -190,6 +200,108 @@ Manage a **single device** by its MAC address. Operations include retrieval, upd
190200

191201
---
192202

203+
## 8. Lock / Unlock a Device Field
204+
205+
* **POST** `/device/<mac>/field/lock`
206+
Lock a field to prevent plugin overwrites, or unlock it to allow overwrites again.
207+
208+
**Request Body**:
209+
210+
```json
211+
{
212+
"fieldName": "devName",
213+
"lock": true
214+
}
215+
```
216+
217+
| Field | Type | Required | Description |
218+
|---|---|---|---|
219+
| `fieldName` | string || Field to lock/unlock (e.g. `devName`, `devVendor`) |
220+
| `lock` | boolean || `true` to lock, `false` to unlock (default when omitted) |
221+
222+
**Response** (success):
223+
224+
```json
225+
{
226+
"success": true,
227+
"fieldName": "devName",
228+
"locked": true,
229+
"message": "Field devName locked"
230+
}
231+
```
232+
233+
**Error Responses**:
234+
235+
* Field does not support locking → HTTP 400
236+
* Unauthorized → HTTP 403
237+
238+
---
239+
240+
## 9. Unlock / Clear Device Fields (Bulk)
241+
242+
* **POST** `/devices/fields/unlock`
243+
Unlock fields (clear `LOCKED`/`USER` sources) for one device, a list of devices, or all devices.
244+
245+
**Request Body**:
246+
247+
```json
248+
{
249+
"mac": "AA:BB:CC:DD:EE:FF",
250+
"fields": ["devName", "devVendor"],
251+
"clearAll": false
252+
}
253+
```
254+
255+
| Field | Type | Required | Description |
256+
|---|---|---|---|
257+
| `mac` | string or array || Single MAC, list of MACs, or omit for all devices |
258+
| `fields` | array of strings || Fields to unlock. Omit to unlock all tracked fields |
259+
| `clearAll` | boolean || `true` clears all sources; `false` (default) clears only `LOCKED`/`USER` |
260+
261+
**Response** (success):
262+
263+
```json
264+
{
265+
"success": true
266+
}
267+
```
268+
269+
**Error Responses**:
270+
271+
* `fields` is not a list → HTTP 400
272+
* Unauthorized → HTTP 403
273+
274+
---
275+
276+
## 10. Set Device Alias
277+
278+
* **POST** `/device/<mac>/set-alias`
279+
Convenience wrapper to update the device display name (`devName`).
280+
281+
**Request Body**:
282+
283+
```json
284+
{
285+
"alias": "My Router"
286+
}
287+
```
288+
289+
**Response** (success):
290+
291+
```json
292+
{
293+
"success": true
294+
}
295+
```
296+
297+
**Error Responses**:
298+
299+
* Missing `alias` → HTTP 400
300+
* Device not found → HTTP 404
301+
* Unauthorized → HTTP 403
302+
303+
---
304+
193305
## Example `curl` Requests
194306

195307
**Get Device Details**:
@@ -233,3 +345,30 @@ curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/update-
233345
--data '{"columnName":"devName","columnValue":"Updated Device"}'
234346
```
235347

348+
**Lock a Field**:
349+
350+
```bash
351+
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/field/lock" \
352+
-H "Authorization: Bearer <API_TOKEN>" \
353+
-H "Content-Type: application/json" \
354+
--data '{"fieldName":"devName","lock":true}'
355+
```
356+
357+
**Unlock Fields (all devices)**:
358+
359+
```bash
360+
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/devices/fields/unlock" \
361+
-H "Authorization: Bearer <API_TOKEN>" \
362+
-H "Content-Type: application/json" \
363+
--data '{"fields":["devName","devVendor"]}'
364+
```
365+
366+
**Set Device Alias**:
367+
368+
```bash
369+
curl -X POST "http://<server_ip>:<GRAPHQL_PORT>/device/AA:BB:CC:DD:EE:FF/set-alias" \
370+
-H "Authorization: Bearer <API_TOKEN>" \
371+
-H "Content-Type: application/json" \
372+
--data '{"alias":"My Router"}'
373+
```
374+

server/api_server/api_server_start.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@ def api_device_set_alias(mac, payload=None):
585585

586586
device_handler = DeviceInstance()
587587
result = device_handler.updateDeviceColumn(mac, 'devName', alias)
588+
589+
if not result.get("success"):
590+
err = result.get("error") or result.get("message") or f"Failed to update alias for device {mac}"
591+
return jsonify({"success": False, "error": err})
592+
588593
return jsonify(result)
589594

590595

server/api_server/openapi/schemas.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,19 @@
3333

3434
# Security whitelists & Literals for documentation
3535
ALLOWED_DEVICE_COLUMNS = Literal[
36+
# Main Info
3637
"devName", "devOwner", "devType", "devVendor",
37-
"devGroup", "devLocation", "devComments", "devFavorite",
38-
"devParentMAC", "devCanSleep"
38+
"devGroup", "devLocation", "devComments", "devIcon",
39+
# Alerts & Behavior
40+
"devFavorite", "devAlertEvents", "devAlertDown",
41+
"devCanSleep", "devSkipRepeated", "devReqNicsOnline", "devForceStatus",
42+
# Network topology
43+
"devParentMAC", "devParentPort", "devParentRelType",
44+
"devSSID", "devSite", "devVlan",
45+
# Display / Status
46+
"devStaticIP", "devIsNew", "devIsArchived",
47+
# Custom properties
48+
"devCustomProps",
3949
]
4050

4151
ALLOWED_NMAP_MODES = Literal[
@@ -407,7 +417,7 @@ class UpdateDeviceColumnRequest(BaseModel):
407417
class LockDeviceFieldRequest(BaseModel):
408418
"""Request to lock/unlock a device field."""
409419
fieldName: str = Field(..., description="Field name to lock/unlock (e.g., devName, devVendor). Required.")
410-
lock: bool = Field(True, description="True to lock the field, False to unlock")
420+
lock: bool = Field(False, description="True to lock the field, False (default) to unlock")
411421

412422

413423
class UnlockDeviceFieldsRequest(BaseModel):
@@ -420,7 +430,7 @@ class UnlockDeviceFieldsRequest(BaseModel):
420430
None,
421431
description="List of field names to unlock. If omitted, all tracked fields will be unlocked"
422432
)
423-
clear_all: bool = Field(
433+
clearAll: bool = Field(
424434
False,
425435
description="True to clear all sources, False to clear only LOCKED/USER"
426436
)

0 commit comments

Comments
 (0)