/* Pi-hole: A black hole for Internet advertisements * (c) 2017 Pi-hole, LLC (https://pi-hole.net) * Network-wide ad blocking via your own hardware. * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ /* 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(); var alertModal = $("#alertModal"); var alProcessing = alertModal.find(".alProcessing"); var alSuccess = alertModal.find(".alSuccess"); var alFailure = alertModal.find(".alFailure"); var alNetworkErr = alertModal.find(".alFailure #alNetErr"); var alCustomErr = alertModal.find(".alFailure #alCustomErr"); var alList = "#alList"; var alDomain = "#alDomain"; // Exit the function here if the Modal is already shown (multiple running interlock) if (alertModal.css("display") !== "none") { return; } var listtype; if (list === "white") { listtype = "Whitelist"; } else { listtype = "Blacklist"; } alProcessing.children(alDomain).html(domain); alProcessing.children(alList).html(listtype); alertModal.modal("show"); // add Domain to List after Modal has faded in alertModal.one("shown.bs.modal", function () { $.ajax({ url: "scripts/pi-hole/php/groups.php", method: "post", data: { domain: domain, list: list, token: token, action: "add_domain", comment: "Added from Query Log" }, success: function (response) { alProcessing.hide(); if (!response.success) { // Failure alNetworkErr.hide(); alCustomErr.html(response.message); alFailure.fadeIn(1000); setTimeout(function () { alertModal.modal("hide"); }, 3000); } else { // Success alSuccess.children(alDomain).html(domain); alSuccess.children(alList).html(listtype); alSuccess.fadeIn(1000); setTimeout(function () { alertModal.modal("hide"); }, 2000); } }, error: function () { // Network Error alProcessing.hide(); alNetworkErr.show(); alFailure.fadeIn(1000); setTimeout(function () { alertModal.modal("hide"); }, 3000); } }); }); // Reset Modal after it has faded out alertModal.one("hidden.bs.modal", function () { alProcessing.show(); alSuccess.add(alFailure).hide(); alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html(""); alCustomErr.html(""); }); } function handleAjaxError(xhr, textStatus) { if (textStatus === "timeout") { alert("The server took too long to send the data."); } else if (xhr.responseText.indexOf("Connection refused") !== -1) { alert("An error occured while loading the data: Connection refused. Is FTL running?"); } else { alert("An unknown error occured while loading the data.\n" + xhr.responseText); } $("#all-queries_processing").hide(); tableApi.clear(); tableApi.draw(); } $(function () { // Do we want to filter queries? var GETDict = {}; window.location.search .substr(1) .split("&") .forEach(function (item) { GETDict[item.split("=")[0]] = item.split("=")[1]; }); var APIstring = "api.php?getAllQueries"; if ("from" in GETDict && "until" in GETDict) { APIstring += "&from=" + GETDict.from; APIstring += "&until=" + GETDict.until; } else if ("client" in GETDict) { APIstring += "&client=" + GETDict.client; } else if ("domain" in GETDict) { APIstring += "&domain=" + GETDict.domain; } else if ("querytype" in GETDict) { APIstring += "&querytype=" + GETDict.querytype; } else if ("forwarddest" in GETDict) { APIstring += "&forwarddest=" + GETDict.forwarddest; } // If we don't ask filtering and also not for all queries, just request the most recent 100 queries else if (!("all" in GETDict)) { APIstring += "=100"; } tableApi = $("#all-queries").DataTable({ rowCallback: function (row, data) { // DNSSEC status var dnssecStatus; switch (data[6]) { case "1": dnssecStatus = '
SECURE'; break; case "2": dnssecStatus = '
INSECURE'; break; case "3": dnssecStatus = '
BOGUS'; break; case "4": dnssecStatus = '
ABANDONED'; break; case "5": dnssecStatus = '
UNKNOWN'; break; default: // No DNSSEC dnssecStatus = ""; } // Query status var fieldtext, buttontext, colorClass = false, isCNAME = false, regexLink = false; switch (data[4]) { case "1": colorClass = "text-red"; fieldtext = "Blocked (gravity)"; buttontext = ''; break; case "2": colorClass = "text-green"; fieldtext = "OK (forwarded)" + dnssecStatus; buttontext = ''; break; case "3": colorClass = "text-green"; fieldtext = "OK (cached)" + dnssecStatus; buttontext = ''; break; case "4": colorClass = "text-red"; fieldtext = "Blocked (regex blacklist)"; if (data.length > 9 && data[9] > 0) { regexLink = true; } buttontext = ''; break; case "5": colorClass = "text-red"; fieldtext = "Blocked (exact blacklist)"; buttontext = ''; break; case "6": colorClass = "text-red"; fieldtext = "Blocked (external, IP)"; buttontext = ""; break; case "7": colorClass = "text-red"; fieldtext = "Blocked (external, NULL)"; buttontext = ""; break; case "8": colorClass = "text-red"; fieldtext = "Blocked (external, NXRA)"; buttontext = ""; break; case "9": colorClass = "text-red"; fieldtext = "Blocked (gravity, CNAME)"; buttontext = ''; isCNAME = true; break; case "10": colorClass = "text-red"; fieldtext = "Blocked (regex blacklist, CNAME)"; if (data.length > 9 && data[9] > 0) { regexLink = true; } buttontext = ''; isCNAME = true; break; case "11": colorClass = "text-red"; fieldtext = "Blocked (exact blacklist, CNAME)"; buttontext = ''; isCNAME = true; break; default: colorClass = false; fieldtext = "Unknown (" + parseInt(data[4], 10) + ")"; buttontext = ""; } fieldtext += ''; if (colorClass !== false) { $(row).addClass(colorClass); } $("td:eq(4)", row).html(fieldtext); $("td:eq(6)", row).html(buttontext); if (regexLink) { $("td:eq(4)", row).hover( function () { this.title = "Click to show matching regex filter"; this.style.color = "#72afd2"; }, function () { this.style.color = ""; } ); $("td:eq(4)", row).off(); // Release any possible previous onClick event handlers $("td:eq(4)", row).click(function () { var newTab = window.open("groups-domains.php?domainid=" + data[9], "_blank"); if (newTab) { newTab.focus(); } }); $("td:eq(4)", row).addClass("text-underline pointer"); } // Substitute domain by "." if empty var domain = data[2]; if (domain.length === 0) { domain = "."; } if (isCNAME) { var CNAMEDomain = data[8]; // Add domain in CNAME chain causing the query to have been blocked $("td:eq(2)", row).text(domain + "\n(blocked " + CNAMEDomain + ")"); } else { $("td:eq(2)", row).text(domain); } // Check for existence of sixth column and display only if not Pi-holed var replytext, replyid = data[5]; if (replyid >= 0 && replyid < replyTypes.length) { replytext = replyTypes[replyid]; } else { replytext = "? (" + replyid + ")"; } replytext += ''; $("td:eq(5)", row).html(replytext); if (data.length > 7) { var content = $("td:eq(5)", row).html(); $("td:eq(5)", row).html(content + " (" + (0.1 * data[7]).toFixed(1) + "ms)"); } }, dom: "<'row'<'col-sm-12'f>>" + "<'row'<'col-sm-4'l><'col-sm-8'p>>" + "<'row'<'col-sm-12'<'table-responsive'tr>>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>", ajax: { url: APIstring, error: handleAjaxError, dataSrc: function (data) { 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; }); } }, autoWidth: false, processing: true, order: [[0, "desc"]], columns: [ { width: "15%", render: function (data, type) { if (type === "display") { return moment .unix(Math.floor(data / 1e6)) .format("Y-MM-DD []HH:mm:ss z"); } return data; } }, { width: "4%" }, { width: "36%", render: $.fn.dataTable.render.text() }, { width: "8%", type: "ip-address", render: $.fn.dataTable.render.text() }, { width: "14%", orderData: 4 }, { width: "8%", orderData: 6 }, { width: "10%", orderData: 4 } ], lengthMenu: [ [10, 25, 50, 100, -1], [10, 25, 50, 100, "All"] ], stateSave: true, stateSaveCallback: function (settings, data) { utils.stateSaveCallback("query_log_table", data); }, stateLoadCallback: function () { return utils.stateLoadCallback("query_log_table"); }, columnDefs: [ { targets: -1, data: null, defaultContent: "" } ], initComplete: function () { var api = this.api(); // Query type IPv4 / IPv6 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"); } ); // Domain 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"); } ); // Client 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"); } ); // 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") { add(data[2], "black"); } else { add(data[2], "white"); } }); $("#resetButton").click(function () { 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(); }