From 0ce4e5f70c9aa030300156feafc6b41382104839 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sat, 7 Feb 2026 10:37:31 +1100 Subject: [PATCH] BE+FE: work on bulk deleting devices and code cleanup #1493 Signed-off-by: jokob-sk --- .gitignore | 1 + front/deviceDetailsEdit.php | 2 +- front/devices.php | 2 +- front/js/device.js | 45 ++------------------------- front/maintenance.php | 5 ++- server/api_server/api_server_start.py | 15 ++++----- server/api_server/openapi/schemas.py | 29 ++++++++++++++--- server/models/device_instance.py | 2 +- 8 files changed, 41 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index dea40523..cf9ed162 100755 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ front/api/* /api/* **/plugins/**/*.log **/plugins/cloud_services/* +**/plugins/cloud_connector/* **/%40eaDir/ **/@eaDir/ diff --git a/front/deviceDetailsEdit.php b/front/deviceDetailsEdit.php index 55dd73e5..ef447800 100755 --- a/front/deviceDetailsEdit.php +++ b/front/deviceDetailsEdit.php @@ -17,7 +17,7 @@ require_once $_SERVER["DOCUMENT_ROOT"] . "/php/templates/security.php"; ?> class="btn btn-default pa-btn pa-btn-delete" style="margin-left:0px;" id="btnDelete" - onclick="askDeleteDevice()"> + onclick="askDeleteDeviceByMac()"> diff --git a/front/devices.php b/front/devices.php index 52c1619c..08da9254 100755 --- a/front/devices.php +++ b/front/devices.php @@ -1148,7 +1148,7 @@ function renderCustomProps(custProps, mac) { onClickEvent = `alert('Not implemented')`; break; case "delete_dev": - onClickEvent = `askDelDevDTInline('${mac}')`; + onClickEvent = `askDeleteDeviceByMac('${mac}')`; break; default: break; diff --git a/front/js/device.js b/front/js/device.js index 934878c9..7dbffa1c 100755 --- a/front/js/device.js +++ b/front/js/device.js @@ -1,21 +1,5 @@ - - // ----------------------------------------------------------------------------- -function askDeleteDevice() { - - mac = getMac() - - // Ask delete device - showModalWarning( - getString("DevDetail_button_Delete"), - getString("DevDetail_button_Delete_ask"), - getString('Gen_Cancel'), - getString('Gen_Delete'), - 'deleteDevice'); -} - -// ----------------------------------------------------------------------------- -function askDelDevDTInline(mac) { +function askDeleteDeviceByMac(mac) { // only try getting mac from URL if not supplied - used in inline buttons on in the my devices listing pages if(isEmpty(mac)) @@ -31,34 +15,9 @@ function askDelDevDTInline(mac) { () => deleteDeviceByMac(mac)) } - -// ----------------------------------------------------------------------------- -function deleteDevice() { - // Check MAC - mac = getMac() - - const apiBase = getApiBase(); - const apiToken = getSetting("API_TOKEN"); - const url = `${apiBase}/device/${mac}/delete`; - - $.ajax({ - url, - method: "DELETE", - headers: { "Authorization": `Bearer ${apiToken}` }, - success: function(response) { - showMessage(response.success ? "Device deleted successfully" : (response.error || "Unknown error")); - updateApi("devices,appevents"); - }, - error: function(xhr, status, error) { - console.error("Error deleting device:", status, error); - showMessage("Error: " + (xhr.responseJSON?.error || error)); - } - }); -} - // ----------------------------------------------------------------------------- function deleteDeviceByMac(mac) { - // only try getting mac from URL if not supplied - used in inline buttons on in teh my devices listing pages + // only try getting mac from URL if not supplied - used in inline buttons on in the my devices listing pages if(isEmpty(mac)) { mac = getMac() diff --git a/front/maintenance.php b/front/maintenance.php index 553ca391..6648a3ee 100755 --- a/front/maintenance.php +++ b/front/maintenance.php @@ -373,7 +373,10 @@ function deleteAllDevices() url, method: "DELETE", headers: { "Authorization": `Bearer ${apiToken}` }, - data: JSON.stringify({ macs: null }), + data: JSON.stringify({ + macs: [], + confirm_delete_all: true + }), contentType: "application/json", success: function(response) { showMessage(response.success ? "All devices deleted successfully" : (response.error || "Unknown error")); diff --git a/server/api_server/api_server_start.py b/server/api_server/api_server_start.py index 9ace8a78..8a0eb1ef 100755 --- a/server/api_server/api_server_start.py +++ b/server/api_server/api_server_start.py @@ -638,20 +638,17 @@ def api_get_devices(payload=None): @app.route("/devices", methods=["DELETE"]) @validate_request( operation_id="delete_devices", - summary="Delete Multiple Devices", - description="Delete multiple devices by MAC address.", + summary="Delete Devices (Bulk / All)", + description="Delete devices by MAC address. Provide a list of MACs to delete specific devices, set confirm_delete_all=true with an empty macs list to delete ALL devices. Supports wildcard '*' matching.", request_model=DeleteDevicesRequest, tags=["devices"], auth_callable=is_authorized ) -def api_devices_delete(payload=None): - data = request.get_json(silent=True) or {} - macs = data.get('macs', []) - - if not macs: - return jsonify({"success": False, "message": "ERROR: Missing parameters", "error": "macs list is required"}), 400 - +def api_devices_delete(payload: DeleteDevicesRequest = None): device_handler = DeviceInstance() + + macs = None if payload.confirm_delete_all else payload.macs + return jsonify(device_handler.deleteDevices(macs)) diff --git a/server/api_server/openapi/schemas.py b/server/api_server/openapi/schemas.py index 0501b2b1..fe8f9618 100644 --- a/server/api_server/openapi/schemas.py +++ b/server/api_server/openapi/schemas.py @@ -444,8 +444,27 @@ class DeviceUpdateRequest(BaseModel): class DeleteDevicesRequest(BaseModel): """Request to delete multiple devices.""" - macs: List[str] = Field([], description="List of MACs to delete") - confirm_delete_all: bool = Field(False, description="Explicit flag to delete ALL devices when macs is empty") + macs: List[str] = Field( + default_factory=list, + description="List of MACs to delete (supports '*' wildcard at the end or start for individual macs)" + ) + confirm_delete_all: bool = Field( + default=False, + description="Explicit flag to delete ALL devices when macs is empty" + ) + model_config = { + "json_schema_extra": { + "examples": [ + { + "summary": "Delete specific devices", + "value": { + "macs": ["AA:BB:CC:DD:EE:FF", "AA:BB:CC:DD:*"], + "confirm_delete_all": False + } + } + ] + } + } @field_validator("macs") @classmethod @@ -453,9 +472,11 @@ class DeleteDevicesRequest(BaseModel): return [validate_mac(mac) for mac in v] @model_validator(mode="after") - def check_delete_all_safety(self) -> DeleteDevicesRequest: + def check_delete_all_safety(self): if not self.macs and not self.confirm_delete_all: - raise ValueError("Must provide at least one MAC or set confirm_delete_all=True") + raise ValueError( + "Must provide at least one MAC or set confirm_delete_all=True" + ) return self diff --git a/server/models/device_instance.py b/server/models/device_instance.py index 4aa1544b..0712ad55 100755 --- a/server/models/device_instance.py +++ b/server/models/device_instance.py @@ -171,7 +171,7 @@ class DeviceInstance: conn = get_temp_db_connection() cur = conn.cursor() - if not macs: + if macs is None: # No MACs provided → delete all cur.execute("DELETE FROM Devices") conn.commit()