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