mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-03-30 23:03:03 -07:00
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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,6 +24,7 @@ front/api/*
|
|||||||
/api/*
|
/api/*
|
||||||
**/plugins/**/*.log
|
**/plugins/**/*.log
|
||||||
**/plugins/cloud_services/*
|
**/plugins/cloud_services/*
|
||||||
|
**/plugins/cloud_connector/*
|
||||||
**/%40eaDir/
|
**/%40eaDir/
|
||||||
**/@eaDir/
|
**/@eaDir/
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ require_once $_SERVER["DOCUMENT_ROOT"] . "/php/templates/security.php"; ?>
|
|||||||
class="btn btn-default pa-btn pa-btn-delete"
|
class="btn btn-default pa-btn pa-btn-delete"
|
||||||
style="margin-left:0px;"
|
style="margin-left:0px;"
|
||||||
id="btnDelete"
|
id="btnDelete"
|
||||||
onclick="askDeleteDevice()">
|
onclick="askDeleteDeviceByMac()">
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
<?= lang("DevDetail_button_Delete") ?>
|
<?= lang("DevDetail_button_Delete") ?>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1148,7 +1148,7 @@ function renderCustomProps(custProps, mac) {
|
|||||||
onClickEvent = `alert('Not implemented')`;
|
onClickEvent = `alert('Not implemented')`;
|
||||||
break;
|
break;
|
||||||
case "delete_dev":
|
case "delete_dev":
|
||||||
onClickEvent = `askDelDevDTInline('${mac}')`;
|
onClickEvent = `askDeleteDeviceByMac('${mac}')`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function askDeleteDevice() {
|
function askDeleteDeviceByMac(mac) {
|
||||||
|
|
||||||
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) {
|
|
||||||
|
|
||||||
// only try getting mac from URL if not supplied - used in inline buttons on in the 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))
|
if(isEmpty(mac))
|
||||||
@@ -31,34 +15,9 @@ function askDelDevDTInline(mac) {
|
|||||||
() => deleteDeviceByMac(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) {
|
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))
|
if(isEmpty(mac))
|
||||||
{
|
{
|
||||||
mac = getMac()
|
mac = getMac()
|
||||||
|
|||||||
@@ -373,7 +373,10 @@ function deleteAllDevices()
|
|||||||
url,
|
url,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: { "Authorization": `Bearer ${apiToken}` },
|
headers: { "Authorization": `Bearer ${apiToken}` },
|
||||||
data: JSON.stringify({ macs: null }),
|
data: JSON.stringify({
|
||||||
|
macs: [],
|
||||||
|
confirm_delete_all: true
|
||||||
|
}),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
showMessage(response.success ? "All devices deleted successfully" : (response.error || "Unknown error"));
|
showMessage(response.success ? "All devices deleted successfully" : (response.error || "Unknown error"));
|
||||||
|
|||||||
@@ -638,20 +638,17 @@ def api_get_devices(payload=None):
|
|||||||
@app.route("/devices", methods=["DELETE"])
|
@app.route("/devices", methods=["DELETE"])
|
||||||
@validate_request(
|
@validate_request(
|
||||||
operation_id="delete_devices",
|
operation_id="delete_devices",
|
||||||
summary="Delete Multiple Devices",
|
summary="Delete Devices (Bulk / All)",
|
||||||
description="Delete multiple devices by MAC address.",
|
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,
|
request_model=DeleteDevicesRequest,
|
||||||
tags=["devices"],
|
tags=["devices"],
|
||||||
auth_callable=is_authorized
|
auth_callable=is_authorized
|
||||||
)
|
)
|
||||||
def api_devices_delete(payload=None):
|
def api_devices_delete(payload: DeleteDevicesRequest = 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
|
|
||||||
|
|
||||||
device_handler = DeviceInstance()
|
device_handler = DeviceInstance()
|
||||||
|
|
||||||
|
macs = None if payload.confirm_delete_all else payload.macs
|
||||||
|
|
||||||
return jsonify(device_handler.deleteDevices(macs))
|
return jsonify(device_handler.deleteDevices(macs))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -444,8 +444,27 @@ class DeviceUpdateRequest(BaseModel):
|
|||||||
|
|
||||||
class DeleteDevicesRequest(BaseModel):
|
class DeleteDevicesRequest(BaseModel):
|
||||||
"""Request to delete multiple devices."""
|
"""Request to delete multiple devices."""
|
||||||
macs: List[str] = Field([], description="List of MACs to delete")
|
macs: List[str] = Field(
|
||||||
confirm_delete_all: bool = Field(False, description="Explicit flag to delete ALL devices when macs is empty")
|
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")
|
@field_validator("macs")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -453,9 +472,11 @@ class DeleteDevicesRequest(BaseModel):
|
|||||||
return [validate_mac(mac) for mac in v]
|
return [validate_mac(mac) for mac in v]
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@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:
|
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
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class DeviceInstance:
|
|||||||
conn = get_temp_db_connection()
|
conn = get_temp_db_connection()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
if not macs:
|
if macs is None:
|
||||||
# No MACs provided → delete all
|
# No MACs provided → delete all
|
||||||
cur.execute("DELETE FROM Devices")
|
cur.execute("DELETE FROM Devices")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
Reference in New Issue
Block a user