Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds the ability to delete multiple warnings at once #2133

Merged
merged 9 commits into from
Mar 2, 2022
Merged
3 changes: 2 additions & 1 deletion messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<thead>
<tr>
<th>ID</th>
<th>&nbsp;</th>
<th>Time</th>
<th>Type</th>
<th>Message</th>
Expand All @@ -31,7 +32,7 @@
<th>Data3</th>
<th>Data4</th>
<th>Data5</th>
<th>Action</th>
<th>&nbsp;</th>
</tr>
</thead>
</table>
Expand Down
20 changes: 3 additions & 17 deletions scripts/pi-hole/js/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +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 */
//The following functions allow us to display time until pi-hole is enabled after disabling.
//Works between all pages

Expand Down Expand Up @@ -101,21 +102,6 @@ function piholeChange(action, duration) {
}
}

function checkMessages() {
$.getJSON("api_db.php?status", function (data) {
if ("message_count" in data && data.message_count > 0) {
var title =
data.message_count > 1
? "There are " + data.message_count + " warnings. Click for further details."
: "There is one warning. Click for further details.";

$("#pihole-diagnosis").prop("title", title);
$("#pihole-diagnosis-count").text(data.message_count);
$("#pihole-diagnosis").removeClass("hidden");
}
});
}

function testCookies() {
if (navigator.cookieEnabled) {
return true;
Expand Down Expand Up @@ -235,9 +221,9 @@ $(function () {
initCPUtemp();

// Run check immediately after page loading ...
checkMessages();
utils.checkMessages();
// ... and once again with five seconds delay
setTimeout(checkMessages, 5000);
setTimeout(utils.checkMessages, 5000);
});

// Handle Enable/Disable
Expand Down
169 changes: 141 additions & 28 deletions scripts/pi-hole/js/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ $(function () {
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
{ data: null, visible: true, width: "15px" },
{ data: "timestamp", width: "8%", render: renderTimestamp },
{ data: "type", width: "8%" },
{ data: "message", orderable: false, render: renderMessage },
Expand All @@ -150,16 +151,29 @@ $(function () {
{ data: "blob3", visible: false },
{ data: "blob4", visible: false },
{ data: "blob5", visible: false },
{ data: null, width: "80px", orderable: false },
{ 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 () {
$('button[id^="deleteMessage_"]').on("click", deleteMessage);

// Hide buttons if all messages were deleted
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");

// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
},
Expand All @@ -168,16 +182,63 @@ $(function () {
var button =
'<button type="button" class="btn btn-danger btn-xs" id="deleteMessage_' +
data.id +
'" data-del-id="' +
data.id +
'">' +
'<span class="far fa-trash-alt"></span>' +
"</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: '<span class="far fa-square"></span>',
titleAttr: "Select All",
className: "btn-sm datatable-bt selectAll",
action: function () {
table.rows({ page: "current" }).select();
},
},
{
text: '<span class="far fa-plus-square"></span>',
titleAttr: "Select All",
className: "btn-sm datatable-bt selectMore",
action: function () {
table.rows({ page: "current" }).select();
},
},
{
extend: "selectNone",
text: '<span class="far fa-check-square"></span>',
titleAttr: "Deselect All",
className: "btn-sm datatable-bt removeAll",
},
{
text: '<span class="far fa-trash-alt"></span>',
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
delMsg(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"],
Expand All @@ -198,7 +259,7 @@ $(function () {
}

// Reset visibility of ID and blob columns
var hiddenCols = [0, 4, 5, 6, 7, 8];
var hiddenCols = [0, 5, 6, 7, 8, 9];
for (var key in hiddenCols) {
if (Object.prototype.hasOwnProperty.call(hiddenCols, key)) {
data.columns[hiddenCols[key]].visible = false;
Expand All @@ -209,42 +270,94 @@ $(function () {
return data;
},
});
table.on("init select deselect", function () {
changeButtonStates();
});
});

// Show only the appropriate buttons
function changeButtonStates() {
var allRows = table.rows({ filter: "applied" }).data().length;
var pageLength = table.page.len();
var selectedRows = table.rows(".selected").data().length;

if (selectedRows === 0) {
// Nothing selected
$(".selectAll").removeClass("hidden");
$(".selectMore").addClass("hidden");
$(".removeAll").addClass("hidden");
$(".deleteSelected").addClass("hidden");
} else if (selectedRows >= pageLength || selectedRows === allRows) {
// Whole page is selected (or all available messages were selected)
$(".selectAll").addClass("hidden");
$(".selectMore").addClass("hidden");
$(".removeAll").removeClass("hidden");
$(".deleteSelected").removeClass("hidden");
} else {
// Some rows are selected, but not all
$(".selectAll").addClass("hidden");
$(".selectMore").removeClass("hidden");
$(".removeAll").addClass("hidden");
$(".deleteSelected").removeClass("hidden");
}
}

// Remove 'bnt-group' class from container, to avoid grouping
$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";

function deleteMessage() {
var tr = $(this).closest("tr");
var id = tr.attr("data-id");
// Passes the button data-del-id attribute as ID
var ids = [parseInt($(this).attr("data-del-id"), 10)];
delMsg(ids);
}

function delMsg(ids) {
// Check input validity
if (!Array.isArray(ids)) return;

// Exploit prevention: Return early for non-numeric IDs
for (var id in ids) {
if (Object.hasOwnProperty.call(ids, id) && typeof ids[id] !== "number") return;
}

utils.disableAll();
utils.showAlert("info", "", "Deleting message with ID " + parseInt(id, 10), "...");
var idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting messages: " + idstring, "...");

$.ajax({
url: "scripts/pi-hole/php/message.php",
method: "post",
dataType: "json",
data: { action: "delete_message", id: id, token: token },
success: function (response) {
data: { action: "delete_message", id: JSON.stringify(ids), token: token },
})
.done(function (response) {
utils.enableAll();
if (response.success) {
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted message # ", id);
table.row(tr).remove().draw(false).ajax.reload(null, false);
} else {
utils.showAlert(
"error",
"",
"Error while deleting message with ID " + id,
response.message
"success",
"far fa-trash-alt",
"Successfully deleted messages: " + 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 message: " + idstring, response.message);
}
},
error: function (jqXHR, exception) {

// Clear selection after deletion
table.rows().deselect();
changeButtonStates();
})
.done(
utils.checkMessages // Update icon warnings count
)
.fail(function (jqXHR, exception) {
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while deleting message with ID " + id,
jqXHR.responseText
);
utils.showAlert("error", "", "Error while deleting message: " + idstring, jqXHR.responseText);
console.log(exception); // eslint-disable-line no-console
},
});
});
}
18 changes: 18 additions & 0 deletions scripts/pi-hole/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,23 @@ function colorBar(percentage, total, cssClass) {
return '<div class="progress progress-sm" title="' + title + '"> ' + bar + " </div>";
}

function checkMessages() {
$.getJSON("api_db.php?status", function (data) {
if ("message_count" in data && data.message_count > 0) {
var title =
data.message_count > 1
? "There are " + data.message_count + " warnings. Click for further details."
: "There is one warning. Click for further details.";

$("#pihole-diagnosis").prop("title", title);
$("#pihole-diagnosis-count").text(data.message_count);
$("#pihole-diagnosis").removeClass("hidden");
} else {
$("#pihole-diagnosis").addClass("hidden");
}
});
}

window.utils = (function () {
return {
escapeHtml: escapeHtml,
Expand All @@ -382,5 +399,6 @@ window.utils = (function () {
addFromQueryLog: addFromQueryLog,
addTD: addTD,
colorBar: colorBar,
checkMessages: checkMessages,
};
})();
3 changes: 3 additions & 0 deletions scripts/pi-hole/php/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ function pidofFTL()
<link rel="stylesheet" href="style/vendor/SourceSansPro/SourceSansPro.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/bootstrap/css/bootstrap.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/datatables.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/datatables_extensions.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/daterangepicker.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/AdminLTE.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/select2.min.css?v=<?=$cacheVer?>">
Expand All @@ -223,6 +224,8 @@ function pidofFTL()
<script src="scripts/vendor/bootstrap-notify.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/select2.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/datatables.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/datatables.select.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/datatables.buttons.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/moment.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/Chart.min.js?v=<?=$cacheVer?>"></script>
<script src="style/vendor/font-awesome/js/all.min.js?v=<?=$cacheVer?>"></script>
Expand Down
24 changes: 12 additions & 12 deletions scripts/pi-hole/php/message.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,23 @@ function JSON_error($message = null)
echo json_encode($response);
}

// Delete message identified by IDs
if ($_POST['action'] == 'delete_message' && isset($_POST['id'])) {
// Delete message identified by ID
try {

$stmt = $db->prepare('DELETE FROM message WHERE id=:id');
if (!$stmt) {
throw new Exception('While preparing message statement: ' . $db->lastErrorMsg());
}

if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
throw new Exception('While binding id to message statement: ' . $db->lastErrorMsg());
$ids = json_decode($_POST['id']);
if(!is_array($ids))
throw new Exception('Invalid payload: id is not an array');
// Explot prevention: Ensure all entries in the ID array are integers
foreach($ids as $value) {
if (!is_numeric($value))
throw new Exception('Invalid payload: id contains non-numeric entries');
}
$stmt = $db->prepare('DELETE FROM message WHERE id IN ('.implode(",",$ids).')');
if (!$stmt)
throw new Exception('While preparing message statement: ' . $db->lastErrorMsg());

if (!$stmt->execute()) {
if (!$stmt->execute())
throw new Exception('While executing message statement: ' . $db->lastErrorMsg());
}


$reload = true;
JSON_success();
Expand Down
Loading