/* 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. */ // Define global variables var timeLineChart, queryTypeChart, forwardDestinationChart; function padNumber(num) { return ("00" + num).substr(-2,2); } // Helper function needed for converting the Objects to Arrays function objectToArray(p){ var keys = Object.keys(p); keys.sort(function(a, b) { return a - b; }); var arr = [], idx = []; for (var i = 0; i < keys.length; i++) { arr.push(p[keys[i]]); idx.push(keys[i]); } return [idx,arr]; } // Functions to update data in page function updateSummaryData(runOnce) { var setTimer = function(timeInSeconds) { if (!runOnce) { setTimeout(updateSummaryData, timeInSeconds * 1000); } }; $.getJSON("api.php?summary", function LoadSummaryData(data) { ["ads_blocked_today", "dns_queries_today", "ads_percentage_today"].forEach(function(today) { var todayElement = $("h3#" + today); todayElement.text() !== data[today] && todayElement.addClass("glow"); }); window.setTimeout(function() { ["ads_blocked_today", "dns_queries_today", "domains_being_blocked", "ads_percentage_today"].forEach(function(header, idx) { var textData = idx === 3 ? data[header] + "%" : data[header]; $("h3#" + header).text(textData); }); $("h3.statistic.glow").removeClass("glow"); }, 500); updateSessionTimer(); }).done(function() { setTimer(10); }).fail(function() { setTimer(300); }); } var failures = 0; function updateQueriesOverTime() { $.getJSON("api.php?overTimeData10mins", function(data) { // convert received objects to arrays data.domains_over_time = objectToArray(data.domains_over_time); data.ads_over_time = objectToArray(data.ads_over_time); // remove last data point since it not representative data.ads_over_time[0].splice(-1,1); // Remove possibly already existing data timeLineChart.data.labels = []; timeLineChart.data.datasets[0].data = []; timeLineChart.data.datasets[1].data = []; // Add data for each hour that is available for (var hour in data.ads_over_time[0]) { if ({}.hasOwnProperty.call(data.ads_over_time[0], hour)) { var d,h; h = parseInt(data.domains_over_time[0][hour]); if(parseInt(data.ads_over_time[0][0]) < 1200) { // Fallback - old style d = new Date().setHours(Math.floor(h / 6), 10 * (h % 6), 0, 0); } else { // New style: Get Unix timestamps d = new Date(1000*h); } timeLineChart.data.labels.push(d); timeLineChart.data.datasets[0].data.push(data.domains_over_time[1][hour]); timeLineChart.data.datasets[1].data.push(data.ads_over_time[1][hour]); } } $("#queries-over-time .overlay").remove(); timeLineChart.update(); }).done(function() { // Reload graph after 10 minutes failures = 0; setTimeout(updateQueriesOverTime, 600000); }).fail(function() { failures++; if(failures < 5) { // Try again after 1 minute only if this has not failed more // than five times in a row setTimeout(updateQueriesOverTime, 60000); } }); } function updateForwardedOverTime() { $.getJSON("api.php?overTimeDataForwards&getForwardDestinations", function(data) { // convert received objects to arrays data.over_time = objectToArray(data.over_time); var timestamps = data.over_time[0]; var plotdata = data.over_time[1]; var labels = []; for (var key in data.forward_destinations) { if(key.indexOf("|") > -1) { var idx = key.indexOf("|"); key = key.substr(0, idx)+" ("+key.substr(idx+1, key.length-idx)+")"; } labels.push(key); } // Get colors from AdminLTE var colors = []; $.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); }); var v = [], c = [], k = []; // Collect values and colors, and labels forwardDestinationChart.data.datasets[0].backgroundColor = colors.shift(); forwardDestinationChart.data.datasets[0].pointRadius = 0; forwardDestinationChart.data.datasets[0].label = labels.shift(); console.log(labels); for (i = 1; i < plotdata[0].length; i++) { forwardDestinationChart.data.datasets.push({data: [], backgroundColor: colors.shift(), pointRadius: 0, label: labels}); } // Add data for each dataset that is available console.log(timestamps); for (var j in timestamps) { var sum = 0.0; for (var i in plotdata[j]) { sum += plotdata[j][i]; } var dd = []; for (var i in plotdata[j]) { var singlepoint = plotdata[j][i]; console.log([timestamps[j],singlepoint]); if(singlepoint == 0) { // Don't plot this line singlepoint = NaN; } forwardDestinationChart.data.datasets[i].data.push(singlepoint/sum); } var d = new Date(1000*parseInt(timestamps[j])); forwardDestinationChart.data.labels.push(d); } $("#forward-destinations .overlay").remove(); forwardDestinationChart.update(); }).done(function() { // Reload graph after 10 minutes failures = 0; setTimeout(updateForwardedOverTime, 600000); }).fail(function() { failures++; if(failures < 5) { // Try again after 1 minute only if this has not failed more // than five times in a row setTimeout(updateForwardedOverTime, 60000); } }); } function updateQueryTypes() { $.getJSON("api.php?getQueryTypes", function(data) { var colors = []; // Get colors from AdminLTE $.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); }); var v = [], c = [], k = [], iter; // Collect values and colors, and labels if(data.hasOwnProperty("querytypes")) { iter = data.querytypes; } else { iter = data; } $.each(iter, function(key , value) { v.push(value); c.push(colors.shift()); k.push(key); }); // Build a single dataset with the data to be pushed var dd = {data: v, backgroundColor: c}; // and push it at once queryTypeChart.data.datasets[0] = dd; queryTypeChart.data.labels = k; $("#query-types .overlay").remove(); queryTypeChart.update(); queryTypeChart.chart.config.options.cutoutPercentage=50; queryTypeChart.update(); // Don't use rotation animation for further updates queryTypeChart.options.animation.duration=0; // Update query types data every 10 seconds setTimeout(updateQueryTypes, 10000); }); } // Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406 function escapeHtml(text) { var map = { "&": "&", "<": "<", ">": ">", "\"": """, "\'": "'" }; return text.replace(/[&<>"']/g, function(m) { return map[m]; }); } function updateTopClientsChart() { $.getJSON("api.php?summaryRaw&getQuerySources", function(data) { // Clear tables before filling them with data $("#client-frequency td").parent().remove(); var clienttable = $("#client-frequency").find("tbody:last"); var client, percentage, clientname, clientip; for (client in data.top_sources) { if ({}.hasOwnProperty.call(data.top_sources, client)){ // Sanitize client client = escapeHtml(client); if(client.indexOf("|") > -1) { var idx = client.indexOf("|"); clientname = client.substr(0, idx); clientip = client.substr(idx+1, client.length-idx); } else { clientname = client; clientip = client; } var url = ""+clientname+""; percentage = data.top_sources[client] / data.dns_queries_today * 100; clienttable.append("