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:

+
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 {