Files
web/scripts/pi-hole/js/db_graph.js
2022-10-28 13:01:34 -04:00

418 lines
12 KiB
JavaScript

/* 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 utils:false, Chart:false, moment:false */
var start__ = moment().subtract(7, "days");
var from = Math.round(moment(start__).utc().valueOf() / 1000);
var end__ = moment();
var until = Math.round(moment(end__).utc().valueOf() / 1000);
var interval = 0;
var dateformat = "MMMM Do YYYY, HH:mm";
// get the database min timestamp
var mintimestamp;
$.getJSON("api_db.php?getMinTimestamp", function (ts) {
mintimestamp = ts.mintimestamp * 1000 || 0; // return the timestamp in milliseconds or zero (in case of NaN)
});
$(function () {
$("#querytime").daterangepicker(
{
timePicker: true,
timePickerIncrement: 15,
timePicker24Hour: true,
locale: { format: dateformat },
startDate: start__,
endDate: end__,
ranges: {
Today: [moment().startOf("day"), moment()],
Yesterday: [
moment().subtract(1, "days").startOf("day"),
moment().subtract(1, "days").endOf("day"),
],
"Last 7 Days": [moment().subtract(7, "days"), moment()],
"Last 30 Days": [moment().subtract(30, "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(mintimestamp), moment()],
},
opens: "center",
showDropdowns: true,
autoUpdateInput: false,
},
function (startt, endt) {
from = Math.round(moment(startt).utc().valueOf() / 1000);
until = Math.round(moment(endt).utc().valueOf() / 1000);
}
);
});
var timeLineChart;
function compareNumbers(a, b) {
return a - b;
}
function computeInterval(from, until) {
// Compute interval to obtain about 200 values
var num = 200;
// humanly understandable intervals (in seconds)
var intervals = [
10,
20,
30,
60,
120,
180,
300,
600,
900,
1200,
1800,
3600,
3600 * 2,
3600 * 3,
3600 * 4,
3600 * 6,
3600 * 8,
3600 * 12,
3600 * 24,
3600 * 24 * 7,
3600 * 24 * 30,
];
var duration = until - from;
if (duration / (num * intervals[0]) < 1) {
return intervals[0];
}
var preverr = Number.MAX_VALUE,
err;
for (var i = 0; i < intervals.length; i++) {
err = Math.abs(1 - duration / (num * intervals[i]));
// pick the interval with least deviation
// from selected duration
if (preverr < err) {
return intervals[i - 1];
}
preverr = err;
}
return intervals[intervals.length - 1];
}
function updateQueriesOverTime() {
var timeoutWarning = $("#timeoutWarning");
$("#queries-over-time .overlay").show();
timeoutWarning.show();
interval = computeInterval(from, until);
// Default displaying axis scaling
timeLineChart.options.scales.xAxes.time.unit = "hour";
var duration = until - from;
// Xaxis scaling based on selected daterange
if (duration > 4 * 365 * 24 * 60 * 60) {
// If the requested data is more than 4 years, set ticks interval to year
timeLineChart.options.scales.xAxes.time.unit = "year";
} else if (duration >= 366 * 24 * 60 * 60) {
// If the requested data is more than 1 year, set ticks interval to quarter
timeLineChart.options.scales.xAxes.time.unit = "quarter";
} else if (duration >= 92 * 24 * 60 * 60) {
// If the requested data is more than 3 months, set ticks interval to months
timeLineChart.options.scales.xAxes.time.unit = "month";
} else if (duration >= 31 * 24 * 60 * 60) {
// If the requested data is 1 month or more, set ticks interval to weeks
timeLineChart.options.scales.xAxes.time.unit = "week";
} else if (duration > 3 * 24 * 60 * 60) {
// If the requested data is more than 3 days (72 hours), set ticks interval to days
timeLineChart.options.scales.xAxes.time.unit = "day";
}
$.getJSON(
"api_db.php?getGraphData&from=" + from + "&until=" + until + "&interval=" + interval,
function (data) {
// convert received objects to arrays
data.domains_over_time = utils.objectToArray(data.domains_over_time);
data.ads_over_time = utils.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 (Object.prototype.hasOwnProperty.call(data.domains_over_time[0], hour)) {
dates.push(parseInt(data.domains_over_time[0][hour], 10));
}
}
for (hour in data.ads_over_time[0]) {
if (
Object.prototype.hasOwnProperty.call(data.ads_over_time[0], hour) &&
dates.indexOf(parseInt(data.ads_over_time[0][hour], 10)) === -1
) {
dates.push(parseInt(data.ads_over_time[0][hour], 10));
}
}
dates.sort(compareNumbers);
// Add data for each hour that is available
for (hour in dates) {
if (Object.prototype.hasOwnProperty.call(dates, hour)) {
var date,
total = 0,
blocked = 0;
date = new Date(1000 * dates[hour]);
var idx = data.domains_over_time[0].indexOf(dates[hour].toString());
if (idx > -1) {
total = data.domains_over_time[1][idx];
}
idx = data.ads_over_time[0].indexOf(dates[hour].toString());
if (idx > -1) {
blocked = data.ads_over_time[1][idx];
}
timeLineChart.data.labels.push(date);
timeLineChart.data.datasets[0].data.push(blocked);
timeLineChart.data.datasets[1].data.push(total - blocked);
}
}
timeLineChart.options.scales.xAxes.ticks.min = from * 1000;
timeLineChart.options.scales.xAxes.ticks.max = until * 1000;
timeLineChart.options.scales.xAxes.display = true;
$("#queries-over-time .overlay").hide();
timeoutWarning.hide();
timeLineChart.update();
}
);
}
$(function () {
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
var blockedColor = $(".queries-blocked").css("background-color");
var permittedColor = $(".queries-permitted").css("background-color");
var gridColor = $(".graphs-grid").css("background-color");
var ticksColor = $(".graphs-ticks").css("color");
timeLineChart = new Chart(ctx, {
type: utils.getGraphType(),
data: {
labels: [],
datasets: [
{
label: "Blocked DNS Queries",
backgroundColor: blockedColor,
borderColor: blockedColor,
pointBorderColor: blockedColor,
data: [],
},
{
label: "Permitted DNS Queries",
backgroundColor: permittedColor,
borderColor: permittedColor,
pointBorderColor: permittedColor,
data: [],
},
],
},
options: {
responsive: true,
interaction: {
mode: "nearest",
axis: "x",
},
plugins: {
tooltip: {
enabled: true,
yAlign: "bottom",
intersect: false,
itemSort: function (a, b) {
return b.datasetIndex - a.datasetIndex;
},
callbacks: {
label: function (tooltipLabel) {
var label = tooltipLabel.dataset.label;
// Add percentage only for blocked queries
if (tooltipLabel.datasetIndex === 0) {
var percentage = 0;
var permitted = parseInt(tooltipLabel.parsed._stacks.y[1], 10);
var blocked = parseInt(tooltipLabel.parsed._stacks.y[0], 10);
if (permitted + blocked > 0) {
percentage = (100 * blocked) / (permitted + blocked);
}
label += ": " + tooltipLabel.parsed.y + " (" + percentage.toFixed(1) + "%)";
} else {
label += ": " + tooltipLabel.parsed.y;
}
return label;
},
title: function (tooltipTitle) {
var title = tooltipTitle[0].label;
var time = new Date(title);
var fromDate =
time.getFullYear() +
"-" +
utils.padNumber(time.getMonth() + 1) +
"-" +
utils.padNumber(time.getDate());
var fromTime =
utils.padNumber(time.getHours()) +
":" +
utils.padNumber(time.getMinutes()) +
":" +
utils.padNumber(time.getSeconds());
time = new Date(time.valueOf() + 1000 * interval);
var untilDate =
time.getFullYear() +
"-" +
utils.padNumber(time.getMonth() + 1) +
"-" +
utils.padNumber(time.getDate());
var untilTime =
utils.padNumber(time.getHours()) +
":" +
utils.padNumber(time.getMinutes()) +
":" +
utils.padNumber(time.getSeconds());
if (fromDate === untilDate) {
// Abbreviated form for intervals on the same day
// We split title in two lines on small screens
if ($(window).width() < 992) {
untilTime += "\n";
}
return ("Queries from " + fromTime + " to " + untilTime + " on " + fromDate).split(
"\n "
);
}
// Full tooltip for intervals spanning more than one day
// We split title in two lines on small screens
if ($(window).width() < 992) {
fromDate += "\n";
}
return (
"Queries from " +
fromDate +
" " +
fromTime +
" to " +
untilDate +
" " +
untilTime
).split("\n ");
},
},
},
legend: {
display: false,
},
},
scales: {
xAxes: {
type: "time",
stacked: true,
offset: false,
time: {
unit: "hour",
displayFormats: {
minute: "HH:mm",
hour: "HH:mm",
day: "MMM DD",
week: "MMM DD",
month: "MMM",
quarter: "YYYY MMM",
year: "YYYY",
},
},
grid: {
color: gridColor,
drawBorder: false,
offset: false,
},
ticks: {
color: ticksColor,
},
},
yAxes: {
stacked: true,
beginAtZero: true,
ticks: {
color: ticksColor,
precision: 0,
},
grid: {
color: gridColor,
drawBorder: false,
},
},
},
elements: {
line: {
borderWidth: 0,
spanGaps: false,
fill: true,
},
point: {
radius: 0,
hoverRadius: 5,
hitRadius: 5,
},
},
maintainAspectRatio: false,
},
});
});
$("#querytime").on("apply.daterangepicker", function (ev, picker) {
$(this).val(picker.startDate.format(dateformat) + " to " + picker.endDate.format(dateformat));
$("#queries-over-time").show();
updateQueriesOverTime();
});
$("#queryOverTimeChart").on("click", function (evt) {
var activePoints = timeLineChart.getElementsAtEventForMode(
evt,
"nearest",
{ intersect: true },
false
);
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 + interval;
window.location.href = "db_queries.php?from=" + from + "&until=" + until;
}
return false;
});