Update chart.js from 2.9.4 to 3.9.1. (#2373)

This commit is contained in:
yubiuser
2022-10-05 23:27:45 +02:00
committed by GitHub
9 changed files with 485 additions and 489 deletions

View File

@@ -79,8 +79,8 @@ require_once 'scripts/pi-hole/php/gravity.php';
<h3 class="box-title">Total queries over last <span class="maxlogage-interval">24</span> hours</h3>
</div>
<div class="box-body">
<div class="chart">
<canvas id="queryOverTimeChart" width="800" height="140"></canvas>
<div class="chart" style="width: 100%; height: 180px">
<canvas id="queryOverTimeChart"></canvas>
</div>
</div>
<div class="overlay">
@@ -97,8 +97,8 @@ require_once 'scripts/pi-hole/php/gravity.php';
<h3 class="box-title">Client activity over last <span class="maxlogage-interval">24</span> hours</h3>
</div>
<div class="box-body">
<div class="chart">
<canvas id="clientsChart" width="800" height="140" class="extratooltipcanvas no-user-select"></canvas>
<div class="chart" style="width: 100%; height: 180px">
<canvas id="clientsChart" class="extratooltipcanvas no-user-select"></canvas>
</div>
</div>
<div class="overlay">
@@ -116,12 +116,10 @@ require_once 'scripts/pi-hole/php/gravity.php';
<h3 class="box-title">Query Types</h3>
</div>
<div class="box-body">
<div class="pull-left" style="width:50%">
<canvas id="queryTypePieChart" width="120" height="120"></canvas>
</div>
<div class="pull-left" style="width:50%">
<div id="query-types-legend" class="chart-legend"></div>
<div style="width:50%">
<canvas id="queryTypePieChart" width="280" height="280"></canvas>
</div>
<div class="chart-legend" style="width:50%" id="query-types-legend" ></div>
</div>
<div class="overlay">
<i class="fa fa-sync fa-spin"></i>
@@ -135,12 +133,10 @@ require_once 'scripts/pi-hole/php/gravity.php';
<h3 class="box-title">Upstream servers</h3>
</div>
<div class="box-body">
<div class="pull-left" style="width:50%">
<canvas id="forwardDestinationPieChart" width="120" height="120" class="extratooltipcanvas no-user-select"></canvas>
</div>
<div class="pull-left" style="width:50%">
<div id="forward-destinations-legend" class="chart-legend extratooltipcanvas no-user-select"></div>
<div style="width:50%">
<canvas id="forwardDestinationPieChart" width="280" height="280" class="extratooltipcanvas no-user-select"></canvas>
</div>
<div class="chart-legend" style="width:50%" id="forward-destinations-legend"></div>
</div>
<div class="overlay">
<i class="fa fa-sync fa-spin"></i>

View File

@@ -120,25 +120,25 @@ function updateQueriesOverTime() {
interval = computeInterval(from, until);
// Default displaying axis scaling
timeLineChart.options.scales.xAxes[0].time.unit = "hour";
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[0].time.unit = "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[0].time.unit = "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[0].time.unit = "month";
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[0].time.unit = "week";
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[0].time.unit = "day";
timeLineChart.options.scales.xAxes.time.unit = "day";
}
$.getJSON(
@@ -196,9 +196,9 @@ function updateQueriesOverTime() {
}
}
timeLineChart.options.scales.xAxes[0].ticks.min = from * 1000;
timeLineChart.options.scales.xAxes[0].ticks.max = until * 1000;
timeLineChart.options.scales.xAxes[0].display = true;
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();
@@ -220,163 +220,163 @@ $(function () {
datasets: [
{
label: "Blocked DNS Queries",
fill: true,
backgroundColor: blockedColor,
borderColor: blockedColor,
pointBorderColor: blockedColor,
pointRadius: 0,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
},
{
label: "Permitted DNS Queries",
fill: true,
backgroundColor: permittedColor,
borderColor: permittedColor,
pointBorderColor: permittedColor,
pointRadius: 0,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
},
],
},
options: {
tooltips: {
enabled: true,
itemSort: function (a, b) {
return b.datasetIndex - a.datasetIndex;
},
mode: "x-axis",
callbacks: {
title: function (tooltipItem) {
var label = tooltipItem[0].xLabel;
var time = new Date(label);
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());
responsive: true,
plugins: {
tooltip: {
enabled: true,
yAlign: "bottom",
intersect: false,
mode: "x",
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);
}
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";
label += ": " + tooltipLabel.parsed.y + " (" + percentage.toFixed(1) + "%)";
} else {
label += ": " + tooltipLabel.parsed.y;
}
return ("Queries from " + fromTime + " to " + untilTime + " on " + fromDate).split(
"\n "
);
}
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());
// Full tooltip for intervals spanning more than one day
// We split title in two lines on small screens
if ($(window).width() < 992) {
fromDate += "\n";
}
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 " +
fromDate +
" " +
fromTime +
" to " +
untilDate +
" " +
untilTime
).split("\n ");
},
label: function (tooltipItems, data) {
if (tooltipItems.datasetIndex === 0) {
var percentage = 0;
var permitted = parseInt(data.datasets[1].data[tooltipItems.index], 10);
var blocked = parseInt(data.datasets[0].data[tooltipItems.index], 10);
if (permitted + blocked > 0) {
percentage = (100 * blocked) / (permitted + blocked);
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 (
data.datasets[tooltipItems.datasetIndex].label +
": " +
tooltipItems.yLabel +
" (" +
percentage.toFixed(1) +
"%)"
);
}
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
"Queries from " +
fromDate +
" " +
fromTime +
" to " +
untilDate +
" " +
untilTime
).split("\n ");
},
},
},
},
legend: {
display: false,
legend: {
display: false,
},
},
scales: {
xAxes: [
{
type: "time",
stacked: true,
time: {
unit: "hour",
displayFormats: {
minute: "HH:mm",
hour: "HH:mm",
day: "MMM DD",
week: "MMM DD",
month: "MMM",
quarter: "YYYY MMM",
year: "YYYY",
},
},
gridLines: {
color: gridColor,
zeroLineColor: gridColor,
},
ticks: {
fontColor: ticksColor,
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",
},
},
],
yAxes: [
{
stacked: true,
ticks: {
beginAtZero: true,
fontColor: ticksColor,
},
gridLines: {
color: gridColor,
},
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,
@@ -391,10 +391,15 @@ $("#querytime").on("apply.daterangepicker", function (ev, picker) {
});
$("#queryOverTimeChart").click(function (evt) {
var activePoints = timeLineChart.getElementAtEvent(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;
var clickedElementindex = activePoints[0].index;
//get specific label by index
var label = timeLineChart.data.labels[clickedElementindex];

View File

@@ -30,7 +30,8 @@ var THEME_COLORS = [
"#d2d6de",
];
var customTooltips = function (tooltip) {
var customTooltips = function (context) {
var tooltip = context.tooltip;
var tooltipEl = document.getElementById(this._chart.canvas.id + "-customTooltip");
if (!tooltipEl) {
// Create Tooltip Element once per chart
@@ -42,11 +43,12 @@ var customTooltips = function (tooltip) {
// font-size was set to 14px by bootstrap's css
var fontZoom = parseFloat($("body").css("font-size")) / 14;
// set styles and font
tooltipEl.style.padding = tooltip.yPadding + "px " + tooltip.xPadding + "px";
tooltipEl.style.borderRadius = tooltip.cornerRadius + "px";
tooltipEl.style.fontFamily = tooltip._bodyFontFamily;
tooltipEl.style.fontSize = tooltip.bodyFontSize / fontZoom + "px";
tooltipEl.style.fontStyle = tooltip._bodyFontStyle;
tooltipEl.style.padding = tooltip.options.padding + "px " + tooltip.options.padding + "px";
tooltipEl.style.borderRadius = tooltip.options.cornerRadius + "px";
tooltipEl.style.font = tooltip.options.bodyFont.string;
tooltipEl.style.fontFamily = tooltip.options.bodyFont.family;
tooltipEl.style.fontSize = tooltip.options.bodyFont.size / fontZoom + "px";
tooltipEl.style.fontStyle = tooltip.options.bodyFont.style;
// append Tooltip next to canvas-containing box
tooltipEl.ancestor = this._chart.canvas.closest(".box[id]").parentNode;
tooltipEl.ancestor.append(tooltipEl);
@@ -110,9 +112,9 @@ var customTooltips = function (tooltip) {
var tooltipHeight = tooltipEl.offsetHeight;
var caretX = tooltip.caretX;
var caretY = tooltip.caretY;
var caretPadding = tooltip.caretPadding;
var caretPadding = tooltip.options.caretPadding;
var tooltipX, tooltipY, arrowX;
var arrowMinIndent = 2 * tooltip.cornerRadius;
var arrowMinIndent = 2 * tooltip.options.cornerRadius;
var arrowSize = 5;
// Compute X position
@@ -315,117 +317,16 @@ function updateQueryTypesPie() {
queryTypePieChart.data.datasets[0] = dd;
queryTypePieChart.data.labels = k;
$("#query-types-pie .overlay").hide();
queryTypePieChart.chart.config.options.cutoutPercentage = 50;
queryTypePieChart.update();
// Don't use rotation animation for further updates
queryTypePieChart.options.animation.duration = 0;
queryTypePieChart.options.legendCallback = customLegend;
// Generate legend in separate div
$("#query-types-legend").html(queryTypePieChart.generateLegend());
$("#query-types-legend > ul > li").click(function (e) {
if (iscolorBox(e.target)) {
return false;
}
window.location.href = "queries.php?querytype=" + querytypeids[$(this).index()];
});
$("#query-types-legend .colorBoxWrapper").click(function (e) {
hidePieSlice(e);
});
}).done(function () {
// Reload graph after minute
setTimeout(updateQueryTypesPie, 60000);
});
}
function customLegend(chart) {
var text = [];
var data = chart.data;
var datasets = data.datasets;
var labels = data.labels;
text.push('<ul class="' + chart.id + '-legend">');
if (datasets.length > 0) {
for (var i = 0; i < datasets[0].data.length; ++i) {
var color = datasets[0].backgroundColor[i];
var txt = "";
// legend box icon
txt =
'<span class="colorBoxWrapper" style="color: ' +
color +
'" title="Toggle visibility">' +
'<i class="fa fa-check-square"></i>' +
"</span>";
// color block
txt += '<span class="legend-color-box" style="background-color:' + color + '"></span>';
// label
if (labels[i]) {
txt +=
'<span class="legend-label-text" title="List ' +
labels[i] +
' queries">' +
labels[i] +
"</span>";
}
text.push("<li>" + txt + "</li>");
}
}
text.push("</ul>");
return text.join("");
}
function hidePieSlice(event) {
togglecolorBox(event.target);
var legendID = $(event.target).closest(".chart-legend").attr("id");
var ci =
legendID === "query-types-legend"
? event.view.queryTypePieChart
: event.view.forwardDestinationPieChart;
var listItemParent = $(event.target).closest("li");
$(listItemParent).toggleClass("strike");
var index = $(listItemParent).index();
var mobj = ci.data.datasets[0]._meta;
var metas = Object.keys(mobj).map(function (e) {
return mobj[e];
});
metas.forEach(function (meta) {
var curr = meta.data[index];
curr.hidden = !curr.hidden;
});
ci.update();
}
function togglecolorBox(target) {
var parentListItem = $(target).closest("li");
var colorBox = $(parentListItem).find(".fa-check-square, .fa-square");
if (colorBox) {
$(colorBox).toggleClass("fa-check-square");
$(colorBox).toggleClass("fa-square");
}
}
function iscolorBox(target) {
// See if click happened on colorBoxWrapper or child SVG
if ($(target).closest(".colorBoxWrapper")[0]) {
return true;
}
return false;
}
function updateClientsOverTime() {
$.getJSON("api.php?overTimeDataClients&getClientNames", function (data) {
if ("FTLnotrunning" in data) {
@@ -561,27 +462,10 @@ function updateForwardDestinationsPie() {
forwardDestinationPieChart.data.datasets[0] = dd;
// and push it at once
$("#forward-destinations-pie .overlay").hide();
forwardDestinationPieChart.chart.config.options.cutoutPercentage = 50;
forwardDestinationPieChart.update();
// Don't use rotation animation for further updates
forwardDestinationPieChart.options.animation.duration = 0;
forwardDestinationPieChart.options.legendCallback = customLegend;
// Generate legend in separate div
$("#forward-destinations-legend").html(forwardDestinationPieChart.generateLegend());
$("#forward-destinations-legend > ul > li").click(function (e) {
if (iscolorBox(e.target)) {
return false;
}
var obj = encodeURIComponent(e.target.textContent);
if (obj.length > 0) {
window.location.href = "queries.php?forwarddest=" + obj;
}
});
$("#forward-destinations-legend .colorBoxWrapper").click(function (e) {
hidePieSlice(e);
});
}).done(function () {
// Reload graph after one minute
setTimeout(updateForwardDestinationsPie, 60000);
@@ -839,32 +723,23 @@ function updateSummaryData(runOnce) {
});
}
function doughnutTooltip(tooltipItems, data) {
var dataset = data.datasets[tooltipItems.datasetIndex];
var label = " " + data.labels[tooltipItems.index];
// Compute share of total and of displayed
var scale = 0,
total = 0;
var metas = Object.keys(dataset._meta).map(function (e) {
return dataset._meta[e];
});
metas.forEach(function (meta) {
meta.data.forEach(function (val, i) {
if (val.hidden) scale += dataset.data[i];
total += dataset.data[i];
});
});
if (scale === 0)
function doughnutTooltip(tooltipLabel) {
var percentageTotalShown = tooltipLabel.chart._metasets[0].total.toFixed(2);
var label = " " + tooltipLabel.label;
if (percentageTotalShown >= 100) {
// All items shown
return label + ": " + dataset.data[tooltipItems.index].toFixed(1) + "%";
return (
label +
":<br>&bull; " +
dataset.data[tooltipItems.index].toFixed(1) +
"% of all queries<br>&bull; " +
((dataset.data[tooltipItems.index] * 100) / (total - scale)).toFixed(1) +
"% of shown items"
);
return label + ": " + tooltipLabel.parsed.toFixed(1) + "%";
} else {
return (
label +
":<br>&bull; " +
tooltipLabel.parsed.toFixed(1) +
"% of all queries<br>&bull; " +
((tooltipLabel.parsed * 100) / percentageTotalShown).toFixed(1) +
"% of shown items"
);
}
}
var maxlogage = "24";
@@ -878,6 +753,91 @@ function getMaxlogage() {
});
}
// chartjs plugin used by the custom doughnut legend
const getOrCreateLegendList = (chart, id) => {
const legendContainer = document.getElementById(id);
let listContainer = legendContainer.querySelector("ul");
if (!listContainer) {
listContainer = document.createElement("ul");
listContainer.style.display = "flex";
listContainer.style.flexDirection = "column";
listContainer.style.margin = 0;
listContainer.style.padding = 0;
legendContainer.append(listContainer);
}
return listContainer;
};
const htmlLegendPlugin = {
id: "htmlLegend",
afterUpdate(chart, args, options) {
const ul = getOrCreateLegendList(chart, options.containerID);
// Remove old legend items
while (ul.firstChild) {
ul.firstChild.remove();
}
// Reuse the built-in legendItems generator
const items = chart.options.plugins.legend.labels.generateLabels(chart);
items.forEach(item => {
const li = document.createElement("li");
li.style.alignItems = "center";
li.style.cursor = "pointer";
li.style.display = "flex";
li.style.flexDirection = "row";
// Color checkbox (toggle visibility)
const boxSpan = document.createElement("span");
boxSpan.title = "Toggle visibility";
boxSpan.style.color = item.fillStyle;
boxSpan.style.display = "inline-block";
boxSpan.style.margin = "0 10px";
boxSpan.innerHTML = item.hidden
? '<i class="colorBoxWrapper fa fa-square"></i>'
: '<i class="colorBoxWrapper fa fa-check-square"></i>';
boxSpan.addEventListener("click", () => {
const { type } = chart.config;
if (type === "pie" || type === "doughnut") {
// Pie and doughnut charts only have a single dataset and visibility is per item
chart.toggleDataVisibility(item.index);
} else {
chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
}
chart.update();
});
// Text (link to query log page)
const textLink = document.createElement("p");
textLink.title = "List " + item.text + " queries";
textLink.style.color = item.fontColor;
textLink.style.margin = 0;
textLink.style.padding = 0;
textLink.style.textDecoration = item.hidden ? "line-through" : "";
textLink.className = "legend-label-text";
textLink.append(item.text);
textLink.addEventListener("click", () => {
if (chart.canvas.id === "queryTypePieChart") {
window.location.href = "queries.php?querytype=" + querytypeids[item.index];
} else if (chart.canvas.id === "forwardDestinationPieChart") {
window.location.href = "queries.php?forwarddest=" + encodeURIComponent(item.text);
}
});
li.append(boxSpan, textLink);
ul.append(li);
});
},
};
$(function () {
// Pull in data via AJAX
getMaxlogage();
@@ -890,100 +850,104 @@ $(function () {
type: utils.getGraphType(),
data: {
labels: [],
datasets: [{ data: [] }],
datasets: [{ data: [], parsing: false }],
},
options: {
tooltips: {
enabled: true,
mode: "x-axis",
itemSort: function (a, b) {
return b.datasetIndex - a.datasetIndex;
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
callbacks: {
title: function (tooltipItem) {
var label = tooltipItem[0].xLabel;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = utils.padNumber(h) + ":" + utils.padNumber(m - 5) + ":00";
var to = utils.padNumber(h) + ":" + utils.padNumber(m + 4) + ":59";
return "Queries from " + from + " to " + to;
tooltip: {
enabled: true,
intersect: false,
mode: "x",
yAlign: "bottom",
itemSort: function (a, b) {
return b.datasetIndex - a.datasetIndex;
},
label: function (tooltipItems, data) {
if (tooltipItems.datasetIndex === 0) {
var percentage = 0;
var permitted = parseInt(data.datasets[1].data[tooltipItems.index], 10);
var blocked = parseInt(data.datasets[0].data[tooltipItems.index], 10);
var total = permitted + blocked;
if (total > 0) {
percentage = (100 * blocked) / total;
callbacks: {
title: function (tooltipTitle) {
var label = tooltipTitle[0].label;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = utils.padNumber(h) + ":" + utils.padNumber(m - 5) + ":00";
var to = utils.padNumber(h) + ":" + utils.padNumber(m + 4) + ":59";
return "Queries from " + from + " to " + to;
},
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 (
data.datasets[tooltipItems.datasetIndex].label +
": " +
tooltipItems.yLabel +
" (" +
percentage.toFixed(1) +
"%)"
);
}
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
return label;
},
},
},
},
legend: {
display: false,
},
scales: {
xAxes: [
{
type: "time",
stacked: true,
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm",
},
tooltipFormat: "HH:mm",
},
gridLines: {
color: gridColor,
zeroLineColor: gridColor,
},
ticks: {
fontColor: ticksColor,
xAxes: {
type: "time",
stacked: true,
offset: false,
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm",
},
tooltipFormat: "HH:mm",
},
],
yAxes: [
{
stacked: true,
ticks: {
beginAtZero: true,
fontColor: ticksColor,
precision: 0,
},
gridLines: {
color: gridColor,
zeroLineColor: gridColor,
},
grid: {
color: gridColor,
offset: false,
drawBorder: 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,
},
});
// Pull in data via AJAX
updateQueriesOverTime();
// Create / load "Top Clients over Time" only if authorized
@@ -994,78 +958,84 @@ $(function () {
type: utils.getGraphType(),
data: {
labels: [],
datasets: [{ data: [] }],
datasets: [{ data: [], parsing: false }],
},
options: {
tooltips: {
enabled: false,
mode: "x-axis",
custom: customTooltips,
yAlign: "top",
itemSort: function (a, b) {
return b.yLabel - a.yLabel;
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
callbacks: {
title: function (tooltipItem) {
var label = tooltipItem[0].xLabel;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = utils.padNumber(h) + ":" + utils.padNumber(m - 5) + ":00";
var to = utils.padNumber(h) + ":" + utils.padNumber(m + 4) + ":59";
return "Client activity from " + from + " to " + to;
tooltip: {
// Disable the on-canvas tooltip
enabled: false,
intersect: false,
mode: "x",
external: customTooltips,
yAlign: "top",
itemSort: function (a, b) {
return b.raw - a.raw;
},
label: function (tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
callbacks: {
title: function (tooltipTitle) {
var label = tooltipTitle[0].label;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = utils.padNumber(h) + ":" + utils.padNumber(m - 5) + ":00";
var to = utils.padNumber(h) + ":" + utils.padNumber(m + 4) + ":59";
return "Client activity from " + from + " to " + to;
},
},
},
},
legend: {
display: false,
},
scales: {
xAxes: [
{
type: "time",
stacked: true,
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm",
},
tooltipFormat: "HH:mm",
},
gridLines: {
color: gridColor,
zeroLineColor: gridColor,
},
ticks: {
fontColor: ticksColor,
xAxes: {
type: "time",
stacked: true,
offset: false,
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm",
},
tooltipFormat: "HH:mm",
},
],
yAxes: [
{
ticks: {
beginAtZero: true,
fontColor: ticksColor,
precision: 0,
},
stacked: true,
gridLines: {
color: gridColor,
zeroLineColor: gridColor,
},
grid: {
color: gridColor,
offset: false,
drawBorder: false,
},
],
ticks: {
color: ticksColor,
},
},
yAxes: {
beginAtZero: true,
ticks: {
color: ticksColor,
precision: 0,
},
stacked: true,
grid: {
color: gridColor,
drawBorder: false,
},
},
},
elements: {
line: {
borderWidth: 0,
spanGaps: false,
fill: true,
point: {
radius: 0,
hoverRadius: 5,
hitRadius: 5,
},
},
},
maintainAspectRatio: false,
hover: {
animationDuration: 0,
},
@@ -1087,11 +1057,15 @@ $(function () {
}
$("#queryOverTimeChart").click(function (evt) {
var activePoints = timeLineChart.getElementAtEvent(evt);
var activePoints = timeLineChart.getElementsAtEventForMode(
evt,
"nearest",
{ intersect: true },
false
);
if (activePoints.length > 0) {
//get the internal index of slice in pie chart
var clickedElementindex = activePoints[0]._index;
//get the internal index
var clickedElementindex = activePoints[0].index;
//get specific label by index
var label = timeLineChart.data.labels[clickedElementindex];
@@ -1105,10 +1079,15 @@ $(function () {
});
$("#clientsChart").click(function (evt) {
var activePoints = clientsChart.getElementAtEvent(evt);
var activePoints = clientsChart.getElementsAtEventForMode(
evt,
"nearest",
{ intersect: true },
false
);
if (activePoints.length > 0) {
//get the internal index of slice in pie chart
var clickedElementindex = activePoints[0]._index;
//get the internal index
var clickedElementindex = activePoints[0].index;
//get specific label by index
var label = clientsChart.data.labels[clickedElementindex];
@@ -1128,33 +1107,39 @@ $(function () {
type: "doughnut",
data: {
labels: [],
datasets: [{ data: [] }],
datasets: [{ data: [], parsing: false }],
},
plugins: [htmlLegendPlugin],
options: {
responsive: true,
maintainAspectRatio: true,
elements: {
arc: {
borderColor: $(".box").css("background-color"),
},
},
legend: {
display: false,
},
tooltips: {
enabled: false,
custom: customTooltips,
callbacks: {
title: function () {
return "Query types";
},
label: function (tooltipItems, data) {
return doughnutTooltip(tooltipItems, data);
plugins: {
htmlLegend: {
containerID: "query-types-legend",
},
legend: {
display: false,
},
tooltip: {
// Disable the on-canvas tooltip
enabled: false,
external: customTooltips,
callbacks: {
title: function () {
return "Query type";
},
label: doughnutTooltip,
},
},
},
animation: {
duration: 750,
},
cutoutPercentage: 0,
},
});
@@ -1168,33 +1153,39 @@ $(function () {
type: "doughnut",
data: {
labels: [],
datasets: [{ data: [] }],
datasets: [{ data: [], parsing: false }],
},
plugins: [htmlLegendPlugin],
options: {
responsive: true,
maintainAspectRatio: true,
elements: {
arc: {
borderColor: $(".box").css("background-color"),
},
},
legend: {
display: false,
},
tooltips: {
enabled: false,
custom: customTooltips,
callbacks: {
title: function () {
return "Forward destinations";
},
label: function (tooltipItems, data) {
return doughnutTooltip(tooltipItems, data);
plugins: {
htmlLegend: {
containerID: "forward-destinations-legend",
},
legend: {
display: false,
},
tooltip: {
// Disable the on-canvas tooltip
enabled: false,
external: customTooltips,
callbacks: {
title: function () {
return "Upstream server";
},
label: doughnutTooltip,
},
},
},
animation: {
duration: 750,
},
cutoutPercentage: 0,
},
});

View File

@@ -93,7 +93,8 @@ $cacheVer = filemtime(__FILE__);
<script src="scripts/vendor/datatables.select.min.js?v=<?php echo $cacheVer; ?>"></script>
<script src="scripts/vendor/datatables.buttons.min.js?v=<?php echo $cacheVer; ?>"></script>
<script src="scripts/vendor/moment.min.js?v=<?php echo $cacheVer; ?>"></script>
<script src="scripts/vendor/Chart.min.js?v=<?php echo $cacheVer; ?>"></script>
<script src="scripts/vendor/chart.min.js?v=<?php echo $cacheVer; ?>"></script>
<script src="scripts/vendor/chartjs-adapter-moment.js?v=<?php echo $cacheVer; ?>"></script>
<?php } ?>
<script src="style/vendor/font-awesome/js/all.min.js?v=<?php echo $cacheVer; ?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?php echo $cacheVer; ?>"></script>

File diff suppressed because one or more lines are too long

13
scripts/vendor/chart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/*!
* chartjs-adapter-moment v1.0.0
* https://www.chartjs.org
* (c) 2021 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("moment"),require("chart.js")):"function"==typeof define&&define.amd?define(["moment","chart.js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).moment,e.Chart)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=n(e);const a={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};t._adapters._date.override("function"==typeof f.default?{_id:"moment",formats:function(){return a},parse:function(e,t){return"string"==typeof e&&"string"==typeof t?e=f.default(e,t):e instanceof f.default||(e=f.default(e)),e.isValid()?e.valueOf():null},format:function(e,t){return f.default(e).format(t)},add:function(e,t,n){return f.default(e).add(t,n).valueOf()},diff:function(e,t,n){return f.default(e).diff(f.default(t),n)},startOf:function(e,t,n){return e=f.default(e),"isoWeek"===t?(n=Math.trunc(Math.min(Math.max(0,n),6)),e.isoWeekday(n).startOf("day").valueOf()):e.startOf(t).valueOf()},endOf:function(e,t){return f.default(e).endOf(t).valueOf()}}:{})}));
//# sourceMappingURL=chartjs-adapter-moment.min.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"chartjs-adapter-moment.min.js","sources":["chartjs-adapter-moment.min"],"sourcesContent":["'use strict';\n\nimport moment from 'moment';\nimport {_adapters} from 'chart.js';\n\nconst FORMATS = {\n datetime: 'MMM D, YYYY, h:mm:ss a',\n millisecond: 'h:mm:ss.SSS a',\n second: 'h:mm:ss a',\n minute: 'h:mm a',\n hour: 'hA',\n day: 'MMM D',\n week: 'll',\n month: 'MMM YYYY',\n quarter: '[Q]Q - YYYY',\n year: 'YYYY'\n};\n\n_adapters._date.override(typeof moment === 'function' ? {\n _id: 'moment', // DEBUG ONLY\n\n formats: function() {\n return FORMATS;\n },\n\n parse: function(value, format) {\n if (typeof value === 'string' && typeof format === 'string') {\n value = moment(value, format);\n } else if (!(value instanceof moment)) {\n value = moment(value);\n }\n return value.isValid() ? value.valueOf() : null;\n },\n\n format: function(time, format) {\n return moment(time).format(format);\n },\n\n add: function(time, amount, unit) {\n return moment(time).add(amount, unit).valueOf();\n },\n\n diff: function(max, min, unit) {\n return moment(max).diff(moment(min), unit);\n },\n\n startOf: function(time, unit, weekday) {\n time = moment(time);\n if (unit === 'isoWeek') {\n weekday = Math.trunc(Math.min(Math.max(0, weekday), 6));\n return time.isoWeekday(weekday).startOf('day').valueOf();\n }\n return time.startOf(unit).valueOf();\n },\n\n endOf: function(time, unit) {\n return moment(time).endOf(unit).valueOf();\n }\n} : {});\n"],"names":["FORMATS","datetime","millisecond","second","minute","hour","day","week","month","quarter","year","_adapters","_date","override","moment","_id","formats","parse","value","format","isValid","valueOf","time","add","amount","unit","diff","max","min","startOf","weekday","Math","trunc","isoWeekday","endOf"],"mappings":";;;;;;gXAKA,MAAMA,EAAU,CACdC,SAAU,yBACVC,YAAa,gBACbC,OAAQ,YACRC,OAAQ,SACRC,KAAM,KACNC,IAAK,QACLC,KAAM,KACNC,MAAO,WACPC,QAAS,cACTC,KAAM,QAGRC,YAAUC,MAAMC,SAA2B,mBAAXC,UAAwB,CACtDC,IAAK,SAELC,QAAS,WACP,OAAOhB,GAGTiB,MAAO,SAASC,EAAOC,GAMrB,MALqB,iBAAVD,GAAwC,iBAAXC,EACtCD,EAAQJ,UAAOI,EAAOC,GACXD,aAAiBJ,YAC5BI,EAAQJ,UAAOI,IAEVA,EAAME,UAAYF,EAAMG,UAAY,MAG7CF,OAAQ,SAASG,EAAMH,GACrB,OAAOL,UAAOQ,GAAMH,OAAOA,IAG7BI,IAAK,SAASD,EAAME,EAAQC,GAC1B,OAAOX,UAAOQ,GAAMC,IAAIC,EAAQC,GAAMJ,WAGxCK,KAAM,SAASC,EAAKC,EAAKH,GACvB,OAAOX,UAAOa,GAAKD,KAAKZ,UAAOc,GAAMH,IAGvCI,QAAS,SAASP,EAAMG,EAAMK,GAE5B,OADAR,EAAOR,UAAOQ,GACD,YAATG,GACFK,EAAUC,KAAKC,MAAMD,KAAKH,IAAIG,KAAKJ,IAAI,EAAGG,GAAU,IAC7CR,EAAKW,WAAWH,GAASD,QAAQ,OAAOR,WAE1CC,EAAKO,QAAQJ,GAAMJ,WAG5Ba,MAAO,SAASZ,EAAMG,GACpB,OAAOX,UAAOQ,GAAMY,MAAMT,GAAMJ,YAEhC"}

View File

@@ -234,49 +234,37 @@ td.lookatme {
vertical-align: text-top;
}
.chart-legend {
overflow: auto;
#query-types-pie .box-body,
#forward-destinations-pie .box-body {
display: flex;
}
.chart-legend ul {
list-style-type: none;
padding-left: 45px;
.chart-legend {
overflow: auto;
display: flex;
justify-content: center;
align-self: center;
}
.chart-legend li {
cursor: pointer;
position: relative;
line-height: 1;
margin: 0 0 6px;
margin: 0 0 8px;
}
.chart-legend li span {
display: inline-block;
height: 12px;
}
.colorBoxWrapper {
font-size: 14px;
font-size: 15px;
}
.colorBoxWrapper:hover {
transform: scale(1.15);
}
.chart-legend li .colorBoxWrapper {
position: absolute;
width: 20px;
left: -20px;
top: 0;
line-height: 1;
}
.chart-legend li .legend-color-box {
display: none;
width: 12px;
margin-right: 5px;
}
.chart-legend li .legend-label-text {
line-height: 1;
word-break: break-word;