mirror of
https://github.com/pi-hole/web.git
synced 2025-12-24 20:55:28 +00:00
Use new special POST :batchDelete callbacks for /api/groups, /api/domains/, /api/clients, and /api/lists and remove some code duplication along the way. Also ensure that the "Default" group cannot be selected for deletion (we already suppress the trash button for this entry)
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/* global utils:false, groups:false,, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false */
|
||||
/* global utils:false, groups:false,, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
@@ -277,10 +277,10 @@ function initTable() {
|
||||
var ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push($(this).attr("data-id"));
|
||||
ids.push({ item: $(this).attr("data-id") });
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
delGroupItems("client", ids, table);
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -347,59 +347,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteClient() {
|
||||
// Passes the button data-id attribute as ID
|
||||
const ids = [$(this).attr("data-id")];
|
||||
delItems(ids);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
// Get first element from array
|
||||
const clientRaw = ids[0];
|
||||
const client = utils.hexDecode(clientRaw);
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting client...", client);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/clients/" + encodeURIComponent(client),
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted client: ", client);
|
||||
table.row(clientRaw).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of clients in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"error",
|
||||
"",
|
||||
"Error while deleting client(s): " + idstring,
|
||||
data.responseText
|
||||
);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
const ids = [{ item: $(this).attr("data-id") }];
|
||||
delGroupItems("client", ids, table);
|
||||
}
|
||||
|
||||
function addClient() {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/* global apiFailure:false, utils:false, initTable:false */
|
||||
/* global apiFailure:false, utils:false, initTable:false, updateFtlInfo:false */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var groups = [];
|
||||
@@ -39,3 +39,51 @@ function processGroupResult(data, type, done, notDone) {
|
||||
utils.showAlert("error", "", `Error while ${notDone} ${type} ${error.item}`, error.error);
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function delGroupItems(type, ids, table) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
const url = "/api/" + type + "s:batchDelete";
|
||||
|
||||
// use utils.hexDecode() to decode all clients
|
||||
let idstring = "";
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
ids[i].item = utils.hexDecode(ids[i].item);
|
||||
idstring += ids[i].item + ", ";
|
||||
}
|
||||
|
||||
// Remove last comma and space from idstring
|
||||
idstring = idstring.substring(0, idstring.length - 2);
|
||||
|
||||
// Append "s" to type if more than one item is deleted
|
||||
type += ids.length > 1 ? "s" : "";
|
||||
|
||||
utils.disableAll();
|
||||
utils.showAlert("info", "", "Deleting " + ids.length + " " + type + "...", idstring);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
data: JSON.stringify(ids),
|
||||
method: "POST",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted " + type, idstring);
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of <type> items in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while deleting " + type, data.responseText);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/* global utils:false, groups:false,, getGroups:false, updateFtlInfo:false, apiFailure:false, processGroupResult:false */
|
||||
/* global utils:false, groups:false,, getGroups:false, updateFtlInfo:false, apiFailure:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
@@ -333,7 +333,7 @@ function initTable() {
|
||||
ids.push($(this).attr("data-id"));
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
deleteDomains(ids);
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -430,60 +430,22 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteDomain() {
|
||||
// Passes the button data-id attribute as ID
|
||||
const ids = [$(this).attr("data-id")];
|
||||
delItems(ids);
|
||||
deleteDomains([$(this).attr("data-id")]);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
function deleteDomains(encodedIds) {
|
||||
const decodedIds = [];
|
||||
for (let i = 0; i < encodedIds.length; i++) {
|
||||
// Decode domain, type, and kind and add to array
|
||||
const parts = encodedIds[i].split("_");
|
||||
decodedIds[i] = {
|
||||
item: parts[0],
|
||||
type: parts[1],
|
||||
kind: parts[2],
|
||||
};
|
||||
}
|
||||
|
||||
// Get first element from array
|
||||
const domainRaw = ids[0];
|
||||
const domain = utils.hexDecode(domainRaw.split("_")[0]);
|
||||
const typestr = $("#old_type_" + domainRaw).val();
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting domain...", domain);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/domains/" + typestr + "/" + encodeURIComponent(domain),
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted domain: ", domain);
|
||||
table.row(domainRaw).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of lists in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"error",
|
||||
"",
|
||||
"Error while deleting domain(s): " + idstring,
|
||||
data.responseText
|
||||
);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
delGroupItems("domain", decodedIds, table);
|
||||
}
|
||||
|
||||
function addDomain() {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/* global utils:false, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false */
|
||||
/* global utils:false, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
@@ -400,7 +400,7 @@ function initTable() {
|
||||
ids.push($(this).attr("data-id"));
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
delGroupItems("list", ids, table);
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -485,54 +485,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteList() {
|
||||
// Passes the button data-id attribute as ID
|
||||
const ids = [$(this).attr("data-id")];
|
||||
delItems(ids);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
// Get first element from array
|
||||
const addressRaw = ids[0];
|
||||
const address = utils.hexDecode(addressRaw);
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting list(s) ...", address);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/lists/" + encodeURIComponent(address),
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted list: ", address);
|
||||
table.row(addressRaw).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of lists in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while deleting list(s): " + idstring, data.responseText);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
const ids = [{ item: $(this).attr("data-id") }];
|
||||
delGroupItems("list", ids, table);
|
||||
}
|
||||
|
||||
function addList(event) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/* global utils:false, apiFailure:false, updateFtlInfo:false, processGroupResult:false */
|
||||
/* global utils:false, apiFailure:false, updateFtlInfo:false, processGroupResult:false, delGroupItems:false */
|
||||
|
||||
var table,
|
||||
idNames = {};
|
||||
@@ -56,14 +56,15 @@ $(function () {
|
||||
],
|
||||
drawCallback: function () {
|
||||
// Hide buttons if all groups were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
// if there is one row, it's the default group
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 1;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
$('button[id^="deleteGroup_"]').on("click", deleteGroup);
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
idNames[data.id] = data.name;
|
||||
$(row).attr("data-id", data.id);
|
||||
var dataId = utils.hexEncode(data.name);
|
||||
$(row).attr("data-id", dataId);
|
||||
var tooltip =
|
||||
"Added: " +
|
||||
utils.datetime(data.date_added, false) +
|
||||
@@ -102,12 +103,13 @@ $(function () {
|
||||
commentEl.on("change", editGroup);
|
||||
|
||||
$("td:eq(4)", row).empty();
|
||||
// Show delete button for all but the default group
|
||||
if (data.id !== 0) {
|
||||
var button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteGroup_' +
|
||||
data.id +
|
||||
'" data-del-id="' +
|
||||
data.id +
|
||||
dataId +
|
||||
'" data-id="' +
|
||||
dataId +
|
||||
'">' +
|
||||
'<span class="far fa-trash-alt"></span>' +
|
||||
"</button>";
|
||||
@@ -151,10 +153,10 @@ $(function () {
|
||||
var ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push(parseInt($(this).attr("data-id"), 10));
|
||||
ids.push({ item: $(this).attr("data-id") });
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
delGroupItems("group", ids, table);
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -198,6 +200,11 @@ $(function () {
|
||||
}
|
||||
|
||||
table.on("init select deselect", function () {
|
||||
// if the Default group is selected, undo the selection of it
|
||||
if (table.rows({ selected: true }).data().pluck("id").indexOf(0) !== -1) {
|
||||
table.rows(0).deselect();
|
||||
}
|
||||
|
||||
utils.changeBulkDeleteStates(table);
|
||||
});
|
||||
|
||||
@@ -221,59 +228,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteGroup() {
|
||||
// Passes the button data-del-id attribute as ID
|
||||
const ids = [parseInt($(this).attr("data-del-id"), 10)];
|
||||
delItems(ids);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
for (const id of ids) {
|
||||
// Exploit prevention: Return early for non-numeric IDs
|
||||
if (typeof id !== "number") return;
|
||||
}
|
||||
|
||||
// Get first element from array
|
||||
const id = ids[0];
|
||||
const name = idNames[id];
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting group...", name);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/groups/" + name,
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted group: ", name);
|
||||
table.row(id).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of groups in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while deleting group(s): " + idstring, data.responseText);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
const ids = [{ item: $(this).attr("data-id") }];
|
||||
delGroupItems("group", ids, table);
|
||||
}
|
||||
|
||||
function addGroup() {
|
||||
|
||||
@@ -561,35 +561,35 @@ function listAlert(type, items, data) {
|
||||
// Show a list of successful items if there are any
|
||||
if (data.processed.success.length > 0) {
|
||||
message +=
|
||||
"<strong>Successfully added " +
|
||||
"Successfully added " +
|
||||
data.processed.success.length +
|
||||
" " +
|
||||
type +
|
||||
(data.processed.success.length !== 1 ? "s" : "") +
|
||||
":</strong>";
|
||||
":";
|
||||
|
||||
// Loop over data.processed.success and print "item"
|
||||
for (const item in data.processed.success) {
|
||||
if (Object.prototype.hasOwnProperty.call(data.processed.success, item)) {
|
||||
message += "<br>- <strong>" + data.processed.success[item].item + "</strong>";
|
||||
message += "\n- " + data.processed.success[item].item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a line break if there are both successful and failed items
|
||||
if (data.processed.success.length > 0 && data.processed.errors.length > 0) {
|
||||
message += "<br><br>";
|
||||
message += "\n\n";
|
||||
}
|
||||
|
||||
// Show a list of failed items if there are any
|
||||
if (data.processed.errors.length > 0) {
|
||||
message +=
|
||||
"<strong>Failed to add " +
|
||||
"Failed to add " +
|
||||
data.processed.errors.length +
|
||||
" " +
|
||||
type +
|
||||
(data.processed.errors.length !== 1 ? "s" : "") +
|
||||
":</strong>\n";
|
||||
":\n";
|
||||
|
||||
// Loop over data.processed.errors and print "item: error"
|
||||
for (const item in data.processed.errors) {
|
||||
@@ -600,7 +600,7 @@ function listAlert(type, items, data) {
|
||||
error = "Already present";
|
||||
}
|
||||
|
||||
message += "<br>- <strong>" + data.processed.errors[item].item + "</strong>: " + error;
|
||||
message += "\n- " + data.processed.errors[item].item + ": " + error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user