diff --git a/api_db.php b/api_db.php index 876e20e0..a4de0002 100644 --- a/api_db.php +++ b/api_db.php @@ -159,6 +159,53 @@ if (isset($_GET['getDBfilesize']) && $auth) $data = array_merge($data, $result); } +if (isset($_GET['getGraphData']) && $auth) +{ + $limit = ""; + + if(isset($_GET["from"]) && isset($_GET["until"])) + { + $limit = " AND timestamp >= ".intval($_GET["from"])." AND timestamp <= ".intval($_GET["until"]); + } + elseif(isset($_GET["from"]) && !isset($_GET["until"])) + { + $limit = " AND timestamp >= ".intval($_GET["from"]); + } + elseif(!isset($_GET["from"]) && isset($_GET["until"])) + { + $limit = " AND timestamp <= ".intval($_GET["until"]); + } + + $interval = 600; + + if(isset($_GET["interval"])) + { + $q = intval($_GET["interval"]); + if($q > 10) + $interval = $q; + } + + // Count permitted queries in intervals + $results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 2 OR status == 3)'.$limit.' GROUP by interval ORDER by interval'); + $domains = array(); + while ($row = $results->fetchArray()) + { + $domains[$row[0]] = intval($row[1]); + } + $result = array('domains_over_time' => $domains); + $data = array_merge($data, $result); + + // Count blocked queries in intervals + $results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 1 OR status == 4 OR status == 5)'.$limit.' GROUP by interval ORDER by interval'); + $addomains = array(); + while ($row = $results->fetchArray()) + { + $addomains[$row[0]] = intval($row[1]); + } + $result = array('ads_over_time' => $addomains); + $data = array_merge($data, $result); +} + if(isset($_GET["jsonForceObject"])) { echo json_encode($data, JSON_FORCE_OBJECT); diff --git a/db_graph.php b/db_graph.php new file mode 100644 index 00000000..9dbdba73 --- /dev/null +++ b/db_graph.php @@ -0,0 +1,67 @@ + + + + + + + + +
+
+ +
+ + +
+
+ +
+ +
+ +
+
+
+
+
+
+
+

Queries over the selected time period

+
+
+
+ +
+
+ + +
+
+
+ + + + + + diff --git a/db_lists.php b/db_lists.php index 86da0c2d..0ddc9dda 100644 --- a/db_lists.php +++ b/db_lists.php @@ -34,9 +34,6 @@ $token = $_SESSION['token']; - - - diff --git a/db_queries.php b/db_queries.php index bca28c4e..189c97cd 100644 --- a/db_queries.php +++ b/db_queries.php @@ -34,9 +34,6 @@ $token = $_SESSION['token']; - - - diff --git a/scripts/pi-hole/js/db_graph.js b/scripts/pi-hole/js/db_graph.js new file mode 100644 index 00000000..201cad5d --- /dev/null +++ b/scripts/pi-hole/js/db_graph.js @@ -0,0 +1,262 @@ +/* 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 +*/ + +var start__ = moment().subtract(6, "days"); +var from = moment(start__).utc().valueOf()/1000; +var end__ = moment(); +var until = moment(end__).utc().valueOf()/1000; + +$(function () { + $("#querytime").daterangepicker( + { + timePicker: true, timePickerIncrement: 15, + locale: { format: "MMMM Do YYYY, HH:mm" }, + ranges: { + "Today": [moment().startOf("day"), moment()], + "Yesterday": [moment().subtract(1, "days").startOf("day"), moment().subtract(1, "days").endOf("day")], + "Last 7 Days": [moment().subtract(6, "days"), moment()], + "Last 30 Days": [moment().subtract(29, "days"), moment()], + "This Month": [moment().startOf("month"), moment()], + "Last Month": [moment().subtract(1, "month").startOf("month"), moment().subtract(1, "month").endOf("month")], + "This Year": [moment().startOf("year"), moment()], + "All Time": [moment(0), moment()] + }, + "opens": "center", "showDropdowns": true + }, + function (startt, endt) { + from = moment(startt).utc().valueOf()/1000; + until = moment(endt).utc().valueOf()/1000; + }); + + +}); + +// 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 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]; +} + +var timeLineChart; + +function compareNumbers(a, b) { + return a - b; +} + +function updateQueriesOverTime() { + $("#queries-over-time .overlay").show(); + $.getJSON("api_db.php?getGraphData&from="+from+"&until="+until, 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 possibly already existing data + timeLineChart.data.labels = []; + timeLineChart.data.datasets[0].data = []; + timeLineChart.data.datasets[1].data = []; + + var dates = [], hour; + + for (hour in data.domains_over_time[0]) { + if ({}.hasOwnProperty.call(data.domains_over_time[0], hour)) { + dates.push(parseInt(data.domains_over_time[0][hour])); + } + } + + for (hour in data.ads_over_time[0]) { + if ({}.hasOwnProperty.call(data.ads_over_time[0], hour)) { + if(!dates.includes(parseInt(data.ads_over_time[0][hour]))) + { + dates.push(parseInt(data.ads_over_time[0][hour])); + } + } + } + + dates.sort(compareNumbers); + + // Add data for each hour that is available + for (hour in dates) { + if ({}.hasOwnProperty.call(dates, hour)) { + var d, dom = 0, ads = 0; + d = new Date(1000*dates[hour]); + + var idx = data.domains_over_time[0].indexOf(dates[hour].toString()); + if (idx > -1) + { + dom = data.domains_over_time[1][idx]; + } + + idx = data.ads_over_time[0].indexOf(dates[hour].toString()); + if (idx > -1) + { + ads = data.ads_over_time[1][idx]; + } + + timeLineChart.data.labels.push(d); + timeLineChart.data.datasets[0].data.push(dom); + timeLineChart.data.datasets[1].data.push(ads); + } + } + + timeLineChart.options.scales.xAxes[0].display=true; + $("#queries-over-time .overlay").hide(); + timeLineChart.update(); + }); +} + +/* global Chart */ +$(document).ready(function() { + var ctx = document.getElementById("queryOverTimeChart").getContext("2d"); + timeLineChart = new Chart(ctx, { + type: "line", + data: { + labels: [ 0 ], + datasets: [ + { + label: "Total DNS Queries", + fill: true, + backgroundColor: "rgba(220,220,220,0.5)", + borderColor: "rgba(0, 166, 90,.8)", + pointBorderColor: "rgba(0, 166, 90,.8)", + pointRadius: 1, + pointHoverRadius: 5, + data: [], + pointHitRadius: 5, + cubicInterpolationMode: "monotone" + }, + { + label: "Blocked DNS Queries", + fill: true, + backgroundColor: "rgba(0,192,239,0.5)", + borderColor: "rgba(0,192,239,1)", + pointBorderColor: "rgba(0,192,239,1)", + pointRadius: 1, + pointHoverRadius: 5, + data: [], + pointHitRadius: 5, + cubicInterpolationMode: "monotone" + } + ] + }, + options: { + tooltips: { + enabled: true, + responsive: true, + mode: "x-axis", + callbacks: { + title: function(tooltipItem, data) { + var label = tooltipItem[0].xLabel; + var time = new Date(label); + var date = time.getFullYear()+"-"+padNumber(time.getMonth())+"-"+padNumber(time.getDate()); + var h = time.getHours(); + var m = time.getMinutes(); + var from = padNumber(h)+":"+padNumber(m)+":00"; + var to = padNumber(h)+":"+padNumber(m+9)+":59"; + return "Queries from "+from+" to "+to+" on "+date; + }, + label: function(tooltipItems, data) { + if(tooltipItems.datasetIndex === 1) + { + var percentage = 0.0; + var total = parseInt(data.datasets[0].data[tooltipItems.index]); + var blocked = parseInt(data.datasets[1].data[tooltipItems.index]); + if(total > 0) + { + percentage = 100.0*blocked/total; + } + return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel + " (" + percentage.toFixed(1) + "%)"; + } + else + { + return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel; + } + } + } + }, + legend: { + display: false + }, + scales: { + xAxes: [{ + type: "time", + display: false, + time: { + displayFormats: { + "minute": "HH:mm", + "hour": "HH:mm", + "day": "HH:mm", + "week": "MMM DD HH:mm", + "month": "MMM DD", + "quarter": "MMM DD", + "year": "MMM DD" + } + } + }], + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + }, + maintainAspectRatio: false + } + }); +}); + +$("#querytime").on("apply.daterangepicker", function(ev, picker) { + $("#queries-over-time").show(); + updateQueriesOverTime(); +}); + +$("#queryOverTimeChart").click(function(evt){ + var activePoints = timeLineChart.getElementAtEvent(evt); + if(activePoints.length > 0) + { + //get the internal index in the chart + var clickedElementindex = activePoints[0]["_index"]; + + //get specific label by index + var label = timeLineChart.data.labels[clickedElementindex]; + + //get value by index + var from = label/1000; + var until = label/1000 + 600; + window.location.href = "db_queries.php?from="+from+"&until="+until; + } + return false; +}); diff --git a/scripts/pi-hole/js/db_lists.js b/scripts/pi-hole/js/db_lists.js index c35b7244..9a7cdfbc 100644 --- a/scripts/pi-hole/js/db_lists.js +++ b/scripts/pi-hole/js/db_lists.js @@ -15,15 +15,6 @@ var end__ = moment(); var until = moment(end__).utc().valueOf()/1000; $(function () { - // Get first time stamp we have valid data for to limit selectable date/time range - $.getJSON("api_db.php?getMinTimestamp", function(data) { - var minDate = parseInt(data.mintimestamp); - if(!isNaN(minDate)) - { - $("#querytime").data("daterangepicker").minDate = moment.unix(minDate); - } - }); - $("#querytime").daterangepicker( { timePicker: true, timePickerIncrement: 15, @@ -38,7 +29,6 @@ $(function () { "This Year": [moment().startOf("year"), moment()], "All Time": [moment(0), moment()] }, - startDate: start__, endDate: end__, "opens": "center", "showDropdowns": true }, function (startt, endt) { @@ -184,8 +174,7 @@ function updateTopAdsChart() { }); } -// Handle "Go" Button -$("#btnGo").on("click", function() { +$("#querytime").on("apply.daterangepicker", function(ev, picker) { updateTopClientsChart(); updateTopDomainsChart(); updateTopAdsChart(); diff --git a/scripts/pi-hole/js/db_queries.js b/scripts/pi-hole/js/db_queries.js index ad89ec05..8551e9dc 100644 --- a/scripts/pi-hole/js/db_queries.js +++ b/scripts/pi-hole/js/db_queries.js @@ -13,18 +13,24 @@ var start__ = moment().subtract(6, "days"); var from = moment(start__).utc().valueOf()/1000; var end__ = moment(); var until = moment(end__).utc().valueOf()/1000; +var instantquery = false; +var daterange; + +// Do we want to filter queries? +var GETDict = {}; +location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];}); + +if("from" in GETDict && "until" in GETDict) +{ + from = parseInt(GETDict["from"]); + until = parseInt(GETDict["until"]); + start__ = moment(1000*from); + end__ = moment(1000*until); + instantquery = true; +} $(function () { - // Get first time stamp we have valid data for to limit selectable date/time range - $.getJSON("api_db.php?getMinTimestamp", function(data) { - var minDate = parseInt(data.mintimestamp); - if(!isNaN(minDate)) - { - $("#querytime").data("daterangepicker").minDate = moment.unix(minDate); - } - }); - - $("#querytime").daterangepicker( + daterange = $("#querytime").daterangepicker( { timePicker: true, timePickerIncrement: 15, locale: { format: "MMMM Do YYYY, HH:mm" }, @@ -38,7 +44,6 @@ $(function () { "This Year": [moment().startOf("year"), moment()], "All Time": [moment(0), moment()] }, - startDate: start__, endDate: end__, "opens": "center", "showDropdowns": true }, function (startt, endt) { @@ -174,6 +179,16 @@ function refreshTableData() { $(document).ready(function() { var status; + var APIstring; + if(instantquery) + { + APIstring = "api_db.php?getAllQueries&from="+from+"&until="+until; + } + else + { + APIstring = "api_db.php?getAllQueries=empty"; + } + tableApi = $("#all-queries").DataTable( { "rowCallback": function( row, data, index ){ if (data[4] === 1) @@ -215,7 +230,7 @@ $(document).ready(function() { "<'row'<'col-sm-4'l><'col-sm-8'p>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>", - "ajax": {"url": "api_db.php?getAllQueries=empty", "error": handleAjaxError }, + "ajax": {"url": APIstring, "error": handleAjaxError }, "autoWidth" : false, "processing": true, "deferRender": true, @@ -233,7 +248,8 @@ $(document).ready(function() { "targets": -1, "data": null, "defaultContent": "" - } ] + } ], + "initComplete": reloadCallback }); $("#all-queries tbody").on( "click", "button", function () { var data = tableApi.row( $(this).parents("tr") ).data(); @@ -246,9 +262,14 @@ $(document).ready(function() { add(data[2],"black"); } } ); + + if(instantquery) + { + daterange.val(start__.format("MMMM Do YYYY, HH:mm") + " - " + end__.format("MMMM Do YYYY, HH:mm")); + } } ); -// Handle "Go" Button -$("#btnGo").on("click", function() { +$("#querytime").on("apply.daterangepicker", function(ev, picker) { refreshTableData(); }); + diff --git a/scripts/pi-hole/php/header.php b/scripts/pi-hole/php/header.php index ae150da5..05f98af2 100644 --- a/scripts/pi-hole/php/header.php +++ b/scripts/pi-hole/php/header.php @@ -417,7 +417,7 @@ if($auth) { Query Log -
  • active"> +
  • active"> Long term data @@ -425,6 +425,11 @@ if($auth) {