diff --git a/groups-adlists.php b/groups-adlists.php
index 5a4688e5..c5aacdd9 100644
--- a/groups-adlists.php
+++ b/groups-adlists.php
@@ -63,6 +63,7 @@
| ID |
|
+ |
Address |
Status |
Comment |
diff --git a/groups-clients.php b/groups-clients.php
index 2257b0de..c48c8585 100644
--- a/groups-clients.php
+++ b/groups-clients.php
@@ -70,6 +70,7 @@
| ID |
+ |
Client |
Comment |
Group assignment |
diff --git a/groups-domains.php b/groups-domains.php
index 3877c89a..a2040307 100644
--- a/groups-domains.php
+++ b/groups-domains.php
@@ -121,6 +121,7 @@
| ID |
+ |
Domain/RegEx |
Type |
Status |
diff --git a/groups.php b/groups.php
index 22e741e7..7c70549f 100644
--- a/groups.php
+++ b/groups.php
@@ -61,6 +61,7 @@
| ID |
+ |
Name |
Status |
Description |
diff --git a/scripts/pi-hole/js/groups-adlists.js b/scripts/pi-hole/js/groups-adlists.js
index 1ff87a0d..f41f9310 100644
--- a/scripts/pi-hole/js/groups-adlists.js
+++ b/scripts/pi-hole/js/groups-adlists.js
@@ -117,8 +117,9 @@ function initTable() {
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
- { data: "status", searchable: false, class: "details-control" },
- { data: "address" },
+ { data: null, visible: true, orderable: false, width: "15px" },
+ { data: "status", searchable: false, orderable: false, class: "details-control" },
+ { data: "address", orderable: false },
{ data: "enabled", searchable: false },
{ data: "comment" },
{ data: "groups", searchable: false },
@@ -126,8 +127,11 @@ function initTable() {
],
columnDefs: [
{
- targets: [0, 2],
- orderable: false,
+ targets: 1,
+ className: "select-checkbox",
+ render: function () {
+ return "";
+ },
},
{
targets: "_all",
@@ -135,6 +139,10 @@ function initTable() {
},
],
drawCallback: function () {
+ // Hide buttons if all messages were deleted
+ var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
+ $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
+
$('button[id^="deleteAdlist_"]').on("click", deleteAdlist);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
@@ -176,19 +184,19 @@ function initTable() {
extra = "";
}
- $("td:eq(0)", row).addClass("list-status-" + statusCode);
- $("td:eq(0)", row).html(
+ $("td:eq(1)", row).addClass("list-status-" + statusCode);
+ $("td:eq(1)", row).html(
"" + extra
);
if (data.address.startsWith("file://")) {
// Local files cannot be downloaded from a distant client so don't show
// a link to such a list here
- $("td:eq(1)", row).html(
+ $("td:eq(2)", row).html(
'' + data.address + ""
);
} else {
- $("td:eq(1)", row).html(
+ $("td:eq(2)", row).html(
'"
);
var statusEl = $("#status_" + data.id, row);
@@ -212,13 +220,13 @@ function initTable() {
});
statusEl.on("change", editAdlist);
- $("td:eq(3)", row).html('');
+ $("td:eq(4)", row).html('');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(utils.unescapeHtml(data.comment));
commentEl.on("change", editAdlist);
- $("td:eq(4)", row).empty();
- $("td:eq(4)", row).append(
+ $("td:eq(5)", row).empty();
+ $("td:eq(5)", row).append(
''
);
var selectEl = $("#multiselect_" + data.id, row);
@@ -285,20 +293,67 @@ function initTable() {
var button =
'";
- $("td:eq(5)", row).html(button);
+ $("td:eq(6)", row).html(button);
},
dom:
- "<'row'<'col-sm-12'f>>" +
- "<'row'<'col-sm-4'l><'col-sm-8'p>>" +
+ "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
- "<'row'<'col-sm-5'i><'col-sm-7'p>>",
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'i>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"],
],
+ select: {
+ style: "multi",
+ selector: "td:not(:last-child)",
+ info: false,
+ },
+ buttons: [
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectAll",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectMore",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ extend: "selectNone",
+ text: '',
+ titleAttr: "Deselect All",
+ className: "btn-sm datatable-bt removeAll",
+ },
+ {
+ text: '',
+ titleAttr: "Delete Selected",
+ className: "btn-sm datatable-bt deleteSelected",
+ action: function () {
+ // For each ".selected" row ...
+ var ids = [];
+ $("tr.selected").each(function () {
+ // ... add the row identified by "data-id".
+ ids.push(parseInt($(this).attr("data-id"), 10));
+ });
+ // Delete all selected rows at once
+ delItems(ids);
+ },
+ },
+ ],
stateSave: true,
stateDuration: 0,
stateSaveCallback: function (settings, data) {
@@ -319,6 +374,10 @@ function initTable() {
},
});
+ table.on("init select deselect", function () {
+ utils.changeBulkDeleteStates(table);
+ });
+
table.on("order.dt", function () {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
@@ -327,6 +386,7 @@ function initTable() {
$("#resetButton").addClass("hidden");
}
});
+
$("#resetButton").on("click", function () {
table.order([[0, "asc"]]).draw();
$("#resetButton").addClass("hidden");
@@ -358,6 +418,66 @@ function initTable() {
}
}
+// Remove 'bnt-group' class from container, to avoid grouping
+$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
+
+function deleteAdlist() {
+ // Passes the button data-del-id attribute as ID
+ var ids = [parseInt($(this).attr("data-del-id"), 10)];
+ delItems(ids);
+}
+
+function delItems(ids) {
+ // Check input validity
+ if (!Array.isArray(ids)) return;
+
+ var address = "";
+
+ // Exploit prevention: Return early for non-numeric IDs
+ for (var id of ids) {
+ if (typeof id !== "number") return;
+ address += "" + utils.escapeHtml($("#address_" + id).text()) + "";
+ }
+
+ utils.disableAll();
+ var idstring = ids.join(", ");
+ utils.showAlert("info", "", "Deleting Adlists: " + idstring, "...");
+
+ $.ajax({
+ url: "scripts/pi-hole/php/groups.php",
+ method: "post",
+ dataType: "json",
+ data: { action: "delete_adlist", id: JSON.stringify(ids), token: token },
+ })
+ .done(function (response) {
+ utils.enableAll();
+ if (response.success) {
+ utils.showAlert(
+ "success",
+ "far fa-trash-alt",
+ "Successfully deleted adlists: " + idstring,
+ ""
+ );
+ for (var id in ids) {
+ if (Object.hasOwnProperty.call(ids, id)) {
+ table.row(id).remove().draw(false).ajax.reload(null, false);
+ }
+ }
+ } else {
+ utils.showAlert("error", "", "Error while deleting adlists: " + idstring, response.message);
+ }
+
+ // Clear selection after deletion
+ table.rows().deselect();
+ utils.changeBulkDeleteStates(table);
+ })
+ .fail(function (jqXHR, exception) {
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while deleting adlists: " + idstring, jqXHR.responseText);
+ console.log(exception); // eslint-disable-line no-console
+ });
+}
+
function addAdlist() {
var address = utils.escapeHtml($("#new_address").val());
var comment = utils.escapeHtml($("#new_comment").val());
@@ -490,32 +610,3 @@ function editAdlist() {
},
});
}
-
-function deleteAdlist() {
- var tr = $(this).closest("tr");
- var id = tr.attr("data-id");
- var address = utils.escapeHtml(tr.find("#address_" + id).text());
-
- utils.disableAll();
- utils.showAlert("info", "", "Deleting adlist...", address);
- $.ajax({
- url: "scripts/pi-hole/php/groups.php",
- method: "post",
- dataType: "json",
- data: { action: "delete_adlist", id: id, token: token },
- success: function (response) {
- utils.enableAll();
- if (response.success) {
- utils.showAlert("success", "far fa-trash-alt", "Successfully deleted adlist ", address);
- table.row(tr).remove().draw(false).ajax.reload(null, false);
- } else {
- utils.showAlert("error", "", "Error while deleting adlist with ID " + id, response.message);
- }
- },
- error: function (jqXHR, exception) {
- utils.enableAll();
- utils.showAlert("error", "", "Error while deleting adlist with ID " + id, jqXHR.responseText);
- console.log(exception); // eslint-disable-line no-console
- },
- });
-}
diff --git a/scripts/pi-hole/js/groups-clients.js b/scripts/pi-hole/js/groups-clients.js
index a2907426..444e55c3 100644
--- a/scripts/pi-hole/js/groups-clients.js
+++ b/scripts/pi-hole/js/groups-clients.js
@@ -92,18 +92,31 @@ function initTable() {
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
+ { data: null, visible: true, width: "15px" },
{ data: "ip", type: "ip-address" },
{ data: "comment" },
{ data: "groups", searchable: false },
{ data: "name", width: "22px", orderable: false },
],
columnDefs: [
+ {
+ targets: 1,
+ orderable: false,
+ className: "select-checkbox",
+ render: function () {
+ return "";
+ },
+ },
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
drawCallback: function () {
+ // Hide buttons if all messages were deleted
+ var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
+ $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
+
$('button[id^="deleteClient_"]').on("click", deleteClient);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
@@ -134,15 +147,15 @@ function initTable() {
'" class="breakall">' +
data.name +
"";
- $("td:eq(0)", row).html(ipName);
+ $("td:eq(1)", row).html(ipName);
- $("td:eq(1)", row).html('');
+ $("td:eq(2)", row).html('');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(utils.unescapeHtml(data.comment));
commentEl.on("change", editClient);
- $("td:eq(2)", row).empty();
- $("td:eq(2)", row).append(
+ $("td:eq(3)", row).empty();
+ $("td:eq(3)", row).append(
''
);
var selectEl = $("#multiselect_" + data.id, row);
@@ -209,16 +222,63 @@ function initTable() {
var button =
'";
- $("td:eq(3)", row).html(button);
+ $("td:eq(4)", row).html(button);
},
+ select: {
+ style: "multi",
+ selector: "td:not(:last-child)",
+ info: false,
+ },
+ buttons: [
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectAll",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectMore",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ extend: "selectNone",
+ text: '',
+ titleAttr: "Deselect All",
+ className: "btn-sm datatable-bt removeAll",
+ },
+ {
+ text: '',
+ titleAttr: "Delete Selected",
+ className: "btn-sm datatable-bt deleteSelected",
+ action: function () {
+ // For each ".selected" row ...
+ var ids = [];
+ $("tr.selected").each(function () {
+ // ... add the row identified by "data-id".
+ ids.push(parseInt($(this).attr("data-id"), 10));
+ });
+ // Delete all selected rows at once
+ delItems(ids);
+ },
+ },
+ ],
dom:
- "<'row'<'col-sm-12'f>>" +
- "<'row'<'col-sm-4'l><'col-sm-8'p>>" +
+ "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
- "<'row'<'col-sm-5'i><'col-sm-7'p>>",
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'i>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"],
@@ -252,6 +312,10 @@ function initTable() {
input.setAttribute("spellcheck", false);
}
+ table.on("init select deselect", function () {
+ utils.changeBulkDeleteStates(table);
+ });
+
table.on("order.dt", function () {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
@@ -267,6 +331,75 @@ function initTable() {
});
}
+// Remove 'bnt-group' class from container, to avoid grouping
+$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
+
+function deleteClient() {
+ // Passes the button data-del-id attribute as ID
+ var ids = [parseInt($(this).attr("data-del-id"), 10)];
+ delItems(ids);
+}
+
+function delItems(ids) {
+ // Check input validity
+ if (!Array.isArray(ids)) return;
+
+ var items = "";
+ var name = "";
+
+ for (var id of ids) {
+ // Exploit prevention: Return early for non-numeric IDs
+ if (typeof id !== "number") return;
+
+ // Retrieve details
+ name = utils.escapeHtml($("#name_" + id).text());
+ if (name.length > 0) {
+ name = " (" + utils.escapeHtml($("#name_" + id).text()) + ")";
+ }
+
+ // Add client
+ items += "" + utils.escapeHtml($("#ip_" + id).text()) + name + "";
+ }
+
+ utils.disableAll();
+ var idstring = ids.join(", ");
+ utils.showAlert("info", "", "Deleting clients: " + idstring, "...");
+
+ $.ajax({
+ url: "scripts/pi-hole/php/groups.php",
+ method: "post",
+ dataType: "json",
+ data: { action: "delete_client", id: JSON.stringify(ids), token: token },
+ })
+ .done(function (response) {
+ utils.enableAll();
+ if (response.success) {
+ utils.showAlert(
+ "success",
+ "far fa-trash-alt",
+ "Successfully deleted clients: " + idstring,
+ ""
+ );
+ for (var id in ids) {
+ if (Object.hasOwnProperty.call(ids, id)) {
+ table.row(id).remove().draw(false).ajax.reload(null, false);
+ }
+ }
+ } else {
+ utils.showAlert("error", "", "Error while deleting clients: " + idstring, response.message);
+ }
+
+ // Clear selection after deletion
+ table.rows().deselect();
+ utils.changeBulkDeleteStates(table);
+ })
+ .fail(function (jqXHR, exception) {
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while deleting clients: " + idstring, jqXHR.responseText);
+ console.log(exception); // eslint-disable-line no-console
+ });
+}
+
function addClient() {
var ip = utils.escapeHtml($("#select").val().trim());
var comment = utils.escapeHtml($("#new_comment").val());
@@ -389,38 +522,3 @@ function editClient() {
},
});
}
-
-function deleteClient() {
- var tr = $(this).closest("tr");
- var id = tr.attr("data-id");
- var ip = utils.escapeHtml(tr.find("#ip_" + id).text());
- var name = utils.escapeHtml(tr.find("#name_" + id).text());
-
- if (name.length > 0) {
- ip += " (" + name + ")";
- }
-
- utils.disableAll();
- utils.showAlert("info", "", "Deleting client...", ip);
- $.ajax({
- url: "scripts/pi-hole/php/groups.php",
- method: "post",
- dataType: "json",
- data: { action: "delete_client", id: id, token: token },
- success: function (response) {
- utils.enableAll();
- if (response.success) {
- utils.showAlert("success", "far fa-trash-alt", "Successfully deleted client ", ip);
- table.row(tr).remove().draw(false).ajax.reload(null, false);
- reloadClientSuggestions();
- } else {
- utils.showAlert("error", "", "Error while deleting client with ID " + id, response.message);
- }
- },
- error: function (jqXHR, exception) {
- utils.enableAll();
- utils.showAlert("error", "", "Error while deleting client with ID " + id, jqXHR.responseText);
- console.log(exception); // eslint-disable-line no-console
- },
- });
-}
diff --git a/scripts/pi-hole/js/groups-domains.js b/scripts/pi-hole/js/groups-domains.js
index 2faa9a83..6a9440cf 100644
--- a/scripts/pi-hole/js/groups-domains.js
+++ b/scripts/pi-hole/js/groups-domains.js
@@ -69,6 +69,7 @@ function initTable() {
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
+ { data: null, visible: true, orderable: false, width: "15px" },
{ data: "domain" },
{ data: "type", searchable: false },
{ data: "enabled", searchable: false },
@@ -77,12 +78,24 @@ function initTable() {
{ data: null, width: "22px", orderable: false },
],
columnDefs: [
+ {
+ targets: 1,
+ orderable: false,
+ className: "select-checkbox",
+ render: function () {
+ return "";
+ },
+ },
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
drawCallback: function () {
+ // Hide buttons if all messages were deleted
+ var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
+ $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
+
$('button[id^="deleteDomain_"]').on("click", deleteDomain);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
@@ -96,7 +109,7 @@ function initTable() {
utils.datetime(data.date_modified, false) +
"\nDatabase ID: " +
data.id;
- $("td:eq(0)", row).html(
+ $("td:eq(1)", row).html(
'Regex blacklist";
}
- $("td:eq(1)", row).html(
+ $("td:eq(2)", row).html(
'