diff --git a/queries.php b/queries.php
index 3723ed17..cd5978bd 100644
--- a/queries.php
+++ b/queries.php
@@ -138,11 +138,11 @@ if(strlen($showing) > 0)
-
-
-
-
-
+ Filtering options:
+
+ - Use Ctrl or ⌘ + to add columns to the current filter
+ - Use Shift + to remove columns from the current filter
+
diff --git a/scripts/pi-hole/js/queries.js b/scripts/pi-hole/js/queries.js
index e9da5b30..d7008d93 100644
--- a/scripts/pi-hole/js/queries.js
+++ b/scripts/pi-hole/js/queries.js
@@ -8,6 +8,22 @@
/* global moment:false, utils:false */
var tableApi;
+var tableFilters = [];
+
+var replyTypes = [
+ "N/A",
+ "NODATA",
+ "NXDOMAIN",
+ "CNAME",
+ "IP",
+ "DOMAIN",
+ "RRNAME",
+ "SERVFAIL",
+ "REFUSED",
+ "NOTIMP",
+ "upstream error"
+];
+var colTypes = ["time", "query type", "domain", "client", "status", "reply type"];
function add(domain, list) {
var token = $("#token").text();
@@ -103,10 +119,6 @@ function handleAjaxError(xhr, textStatus) {
tableApi.draw();
}
-function autofilter() {
- return $("#autofilter").prop("checked");
-}
-
$(function () {
// Do we want to filter queries?
var GETDict = {};
@@ -140,7 +152,7 @@ $(function () {
rowCallback: function (row, data) {
// DNSSEC status
var dnssecStatus;
- switch (data[5]) {
+ switch (data[6]) {
case "1":
dnssecStatus = '
SECURE';
break;
@@ -162,37 +174,32 @@ $(function () {
}
// Query status
- var blocked,
- fieldtext,
+ var fieldtext,
buttontext,
- colorClass,
+ colorClass = false,
isCNAME = false,
regexLink = false;
switch (data[4]) {
case "1":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked (gravity)";
buttontext =
'';
break;
case "2":
- blocked = false;
colorClass = "text-green";
fieldtext = "OK
(forwarded)" + dnssecStatus;
buttontext =
'';
break;
case "3":
- blocked = false;
colorClass = "text-green";
fieldtext = "OK
(cached)" + dnssecStatus;
buttontext =
'';
break;
case "4":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked
(regex blacklist)";
@@ -204,32 +211,27 @@ $(function () {
'';
break;
case "5":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked
(exact blacklist)";
buttontext =
'';
break;
case "6":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked
(external, IP)";
buttontext = "";
break;
case "7":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked
(external, NULL)";
buttontext = "";
break;
case "8":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked
(external, NXRA)";
buttontext = "";
break;
case "9":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked (gravity, CNAME)";
buttontext =
@@ -237,7 +239,6 @@ $(function () {
isCNAME = true;
break;
case "10":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked
(regex blacklist, CNAME)";
@@ -250,7 +251,6 @@ $(function () {
isCNAME = true;
break;
case "11":
- blocked = true;
colorClass = "text-red";
fieldtext = "Blocked
(exact blacklist, CNAME)";
buttontext =
@@ -258,12 +258,13 @@ $(function () {
isCNAME = true;
break;
default:
- blocked = false;
colorClass = false;
fieldtext = "Unknown (" + parseInt(data[4], 10) + ")";
buttontext = "";
}
+ fieldtext += '';
+
if (colorClass !== false) {
$(row).addClass(colorClass);
}
@@ -306,49 +307,17 @@ $(function () {
}
// Check for existence of sixth column and display only if not Pi-holed
- var replytext;
- if (data.length > 6 && !blocked) {
- switch (data[6]) {
- case "0":
- replytext = "N/A";
- break;
- case "1":
- replytext = "NODATA";
- break;
- case "2":
- replytext = "NXDOMAIN";
- break;
- case "3":
- replytext = "CNAME";
- break;
- case "4":
- replytext = "IP";
- break;
- case "5":
- replytext = "DOMAIN";
- break;
- case "6":
- replytext = "RRNAME";
- break;
- case "7":
- replytext = "SERVFAIL";
- break;
- case "8":
- replytext = "REFUSED";
- break;
- case "9":
- replytext = "NOTIMP";
- break;
- case "10":
- replytext = "upstream error";
- break;
- default:
- replytext = "? (" + parseInt(data[6], 10) + ")";
- }
+ var replytext,
+ replyid = data[5];
+
+ if (replyid >= 0 && replyid < replyTypes.length) {
+ replytext = replyTypes[replyid];
} else {
- replytext = "-";
+ replytext = "? (" + replyid + ")";
}
+ replytext += '';
+
$("td:eq(5)", row).html(replytext);
if (data.length > 7) {
@@ -368,6 +337,10 @@ $(function () {
var dataIndex = 0;
return data.data.map(function (x) {
x[0] = x[0] * 1e6 + dataIndex++;
+ var dnssec = x[5];
+ var reply = x[6];
+ x[5] = reply;
+ x[6] = dnssec;
return x;
});
}
@@ -415,77 +388,100 @@ $(function () {
],
initComplete: function () {
var api = this.api();
+
// Query type IPv4 / IPv6
- api.$("td:eq(1)").click(function () {
- if (autofilter()) {
- api.search(this.textContent).draw();
- $("#resetButton").removeClass("hidden");
- }
- });
- api.$("td:eq(1)").hover(
- function () {
- if (autofilter()) {
- this.title = "Click to show only " + this.textContent + " queries";
- this.style.color = "#72afd2";
- } else {
- this.title = "";
- this.style.color = "";
+ api
+ .$("td:eq(1)")
+ .click(function (event) {
+ addColumnFilter(event, 1, this.textContent);
+ })
+ .hover(
+ function () {
+ $(this).addClass("pointer").attr("title", tooltipText(1, this.textContent));
+ },
+ function () {
+ $(this).removeClass("pointer");
}
- },
- function () {
- this.style.color = "";
- }
- );
- api.$("td:eq(1)").addClass("pointer");
+ );
+
// Domain
- api.$("td:eq(2)").click(function () {
- if (autofilter()) {
- var domain = this.textContent.split("\n")[0];
- api.search(domain).draw();
- $("#resetButton").removeClass("hidden");
- }
- });
- api.$("td:eq(2)").hover(
- function () {
- if (autofilter()) {
- var domain = this.textContent.split("\n")[0];
- this.title = "Click to show only queries with domain " + domain;
- this.style.color = "#72afd2";
- } else {
- this.title = "";
- this.style.color = "";
+ api
+ .$("td:eq(2)")
+ .click(function (event) {
+ addColumnFilter(event, 2, this.textContent.split("\n")[0]);
+ })
+ .hover(
+ function () {
+ $(this).addClass("pointer").attr("title", tooltipText(2, this.textContent));
+ },
+ function () {
+ $(this).removeClass("pointer");
}
- },
- function () {
- this.style.color = "";
- }
- );
- api.$("td:eq(2)").addClass("pointer");
+ );
+
// Client
- api.$("td:eq(3)").click(function () {
- if (autofilter()) {
- api.search(this.textContent).draw();
- $("#resetButton").removeClass("hidden");
- }
- });
- api.$("td:eq(3)").hover(
- function () {
- if (autofilter()) {
- this.title = "Click to show only queries made by " + this.textContent;
- this.style.color = "#72afd2";
- } else {
- this.title = "";
- this.style.color = "";
+ api
+ .$("td:eq(3)")
+ .click(function (event) {
+ addColumnFilter(event, 3, this.textContent);
+ })
+ .hover(
+ function () {
+ $(this).addClass("pointer").attr("title", tooltipText(3, this.textContent));
+ },
+ function () {
+ $(this).removeClass("pointer");
}
- },
- function () {
- this.style.color = "";
- }
- );
- api.$("td:eq(3)").addClass("pointer");
+ );
+
+ // Status
+ api
+ .$("td:eq(4)")
+ .click(function (event) {
+ var id = this.children.id.value;
+ var text = this.textContent;
+ addColumnFilter(event, 4, id + "#" + text);
+ })
+ .hover(
+ function () {
+ $(this).addClass("pointer").attr("title", tooltipText(4, this.textContent));
+ },
+ function () {
+ $(this).removeClass("pointer");
+ }
+ );
+
+ // Reply type
+ api
+ .$("td:eq(5)")
+ .click(function (event) {
+ var id = this.children.id.value;
+ var text = this.textContent.split(" ")[0];
+ addColumnFilter(event, 5, id + "#" + text);
+ })
+ .hover(
+ function () {
+ $(this).addClass("pointer").attr("title", tooltipText(5, this.textContent));
+ },
+ function () {
+ $(this).removeClass("pointer");
+ }
+ );
+
+ // Disable autocorrect in the search box
+ var input = $("input[type=search]");
+ if (input !== null) {
+ input.attr("autocomplete", "off");
+ input.attr("autocorrect", "off");
+ input.attr("autocapitalize", "off");
+ input.attr("spellcheck", false);
+ input.attr("placeholder", "Type / Domain / Client");
+ }
}
});
+ resetColumnsFilters();
+
$("#all-queries tbody").on("click", "button", function () {
var data = tableApi.row($(this).parents("tr")).data();
if (data[4] === "2" || data[4] === "3") {
@@ -496,28 +492,84 @@ $(function () {
});
$("#resetButton").click(function () {
- tableApi.search("").draw();
- $("#resetButton").addClass("hidden");
- });
-
- // Disable autocorrect in the search box
- var input = document.querySelector("input[type=search]");
- input.setAttribute("autocomplete", "off");
- input.setAttribute("autocorrect", "off");
- input.setAttribute("autocapitalize", "off");
- input.setAttribute("spellcheck", false);
-
- var chkboxData = localStorage.getItem("query_log_filter_chkbox");
- if (chkboxData !== null) {
- // Restore checkbox state
- $("#autofilter").prop("checked", chkboxData === "true");
- } else {
- // Initialize checkbox
- $("#autofilter").prop("checked", true);
- localStorage.setItem("query_log_filter_chkbox", true);
- }
-
- $("#autofilter").click(function () {
- localStorage.setItem("query_log_filter_chkbox", $("#autofilter").prop("checked"));
+ tableApi.search("");
+ resetColumnsFilters();
});
});
+
+function tooltipText(index, text) {
+ if (index === 5) {
+ // Strip reply time from tooltip text
+ text = text.split(" ")[0];
+ }
+
+ if (index in tableFilters && tableFilters[index].length > 0) {
+ return "Clear filter on " + colTypes[index] + ' "' + text + '" using Shift + Click.';
+ }
+
+ return "Add filter on " + colTypes[index] + ' "' + text + '" using Ctrl + Click.';
+}
+
+function addColumnFilter(event, colID, filterstring) {
+ // Don't do anything when NOT explicitly requesting multi-selection functions
+ if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
+ return;
+ }
+
+ if (event.shiftKey) {
+ filterstring = "";
+ }
+
+ tableFilters[colID] = filterstring;
+
+ applyColumnFiltering();
+}
+
+function resetColumnsFilters() {
+ tableFilters.forEach(function (value, index) {
+ tableFilters[index] = "";
+ });
+
+ // Clear filter reset button
+ applyColumnFiltering();
+}
+
+function applyColumnFiltering() {
+ var showReset = false;
+ tableFilters.forEach(function (value, index) {
+ // Prepare regex filter string
+ var regex = "";
+
+ // Split filter string if we received a combined ID#Name column
+ var valArr = value.split("#");
+ if (valArr.length > 0) {
+ value = valArr[0];
+ }
+
+ if (value.length > 0) {
+ // Exact matching
+ regex = "^" + value + "$";
+
+ // Add background color
+ tableApi.$("td:eq(" + index + ")").addClass("highlight");
+
+ // Remember to show reset button
+ showReset = true;
+ } else {
+ // Clear background color
+ tableApi.$("td:eq(" + index + ")").removeClass("highlight");
+ }
+
+ // Apply filtering on this column (regex may be empty -> no filtering)
+ tableApi.column(index).search(regex, true, true);
+ });
+
+ if (showReset) {
+ $("#resetButton").removeClass("hidden");
+ } else {
+ $("#resetButton").addClass("hidden");
+ }
+
+ // Trigger table update
+ tableApi.draw();
+}
diff --git a/scripts/pi-hole/js/utils.js b/scripts/pi-hole/js/utils.js
index 4e5a4af6..fa74b4d1 100644
--- a/scripts/pi-hole/js/utils.js
+++ b/scripts/pi-hole/js/utils.js
@@ -198,7 +198,14 @@ function stateLoadCallback(itemName) {
return null;
}
+ // Parse JSON string
data = JSON.parse(data);
+
+ // Clear possible filtering settings
+ data.columns.forEach(function (value, index) {
+ data.columns[index].search.search = "";
+ });
+
// Always start on the first page to show most recent queries
data.start = 0;
// Always start with empty search field
diff --git a/style/themes/default-dark.css b/style/themes/default-dark.css
index 1d63c1f4..c1f44a33 100644
--- a/style/themes/default-dark.css
+++ b/style/themes/default-dark.css
@@ -385,7 +385,7 @@ pre {
color: #007997 !important;
}
td.highlight {
- background-color: yellow;
+ background-color: rgba(255, 204, 0, 0.333);
}
.btn-default {
box-shadow: none;
diff --git a/style/themes/default-light.css b/style/themes/default-light.css
index 859af1c3..ac3b852e 100644
--- a/style/themes/default-light.css
+++ b/style/themes/default-light.css
@@ -192,7 +192,7 @@
}
td.highlight {
- background-color: #ff0 !important;
+ background-color: rgba(255, 204, 0, 0.333);
}
.network-never {