BE+FE: work on bulk deleting devices and code cleanup #1493

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2026-02-07 10:37:31 +11:00
parent 6bc2de6e24
commit 0ce4e5f70c
8 changed files with 41 additions and 60 deletions

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ front/api/*
/api/*
**/plugins/**/*.log
**/plugins/cloud_services/*
**/plugins/cloud_connector/*
**/%40eaDir/
**/@eaDir/

View File

@@ -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()">
<i class="fas fa-trash-alt"></i>
<?= lang("DevDetail_button_Delete") ?>
</button>

View File

@@ -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;

View File

@@ -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()

View File

@@ -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"));

View File

@@ -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))

View File

@@ -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

View File

@@ -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()