diff --git a/index.lp b/index.lp
index 91cec9a7..712dd238 100644
--- a/index.lp
+++ b/index.lp
@@ -275,6 +275,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
+
mg.include('scripts/pi-hole/lua/footer.lp','r')?>
diff --git a/scripts/pi-hole/js/charts.js b/scripts/pi-hole/js/charts.js
new file mode 100644
index 00000000..634193c4
--- /dev/null
+++ b/scripts/pi-hole/js/charts.js
@@ -0,0 +1,343 @@
+/* Pi-hole: A black hole for Internet advertisements
+ * (c) 2023 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 querytypeids:false */
+
+// eslint-disable-next-line no-unused-vars
+var THEME_COLORS = [
+ "#f56954",
+ "#3c8dbc",
+ "#00a65a",
+ "#00c0ef",
+ "#f39c12",
+ "#0073b7",
+ "#001f3f",
+ "#39cccc",
+ "#3d9970",
+ "#01ff70",
+ "#ff851b",
+ "#f012be",
+ "#8e24aa",
+ "#d81b60",
+ "#222222",
+ "#d2d6de",
+];
+
+// eslint-disable-next-line no-unused-vars
+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
+ ? ''
+ : '';
+
+ 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();
+ });
+
+ const textLink = document.createElement("p");
+ if (
+ chart.canvas.id === "queryTypePieChart" ||
+ chart.canvas.id === "forwardDestinationPieChart"
+ ) {
+ // Text (link to query log page)
+ textLink.title = "List " + item.text + " queries";
+
+ textLink.addEventListener("click", () => {
+ if (chart.canvas.id === "queryTypePieChart") {
+ window.location.href = "queries.lp?querytype=" + querytypeids[item.index];
+ } else if (chart.canvas.id === "forwardDestinationPieChart") {
+ window.location.href = "queries.lp?forwarddest=" + encodeURIComponent(item.text);
+ }
+ });
+ }
+
+ 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);
+
+ li.append(boxSpan, textLink);
+ ul.append(li);
+ });
+ },
+};
+
+// eslint-disable-next-line no-unused-vars
+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
+ tooltipEl = document.createElement("div");
+ tooltipEl.id = this._chart.canvas.id + "-customTooltip";
+ tooltipEl.classList.add("chartjs-tooltip");
+ tooltipEl.innerHTML = "
";
+ // avoid browser's font-zoom since we know that 's
+ // 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.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);
+ }
+
+ // Hide if no tooltip
+ if (tooltip.opacity === 0) {
+ tooltipEl.style.opacity = 0;
+ return;
+ }
+
+ // Set caret position
+ tooltipEl.classList.remove("left", "right", "center", "top", "bottom");
+ tooltipEl.classList.add(tooltip.xAlign, tooltip.yAlign);
+
+ // Set Text
+ if (tooltip.body) {
+ var titleLines = tooltip.title || [];
+ var bodyLines = tooltip.body.map(function (bodyItem) {
+ return bodyItem.lines;
+ });
+ var innerHtml = "";
+
+ titleLines.forEach(function (title) {
+ innerHtml += "| " + title + " |
";
+ });
+ innerHtml += "";
+ var printed = 0;
+
+ var devicePixel = (1 / window.devicePixelRatio).toFixed(1);
+ bodyLines.forEach(function (body, i) {
+ var labelColors = tooltip.labelColors[i];
+ var style = "background-color: " + labelColors.backgroundColor;
+ style += "; outline: 1px solid " + labelColors.backgroundColor;
+ style += "; border: " + devicePixel + "px solid #fff";
+ var span = "";
+
+ var num = body[0].split(": ");
+ // do not display entries with value of 0 (in bar chart),
+ // but pass through entries with "0.0% (in pie charts)
+ if (num[1] !== "0") {
+ innerHtml += "| " + span + body + " |
";
+ printed++;
+ }
+ });
+ if (printed < 1) {
+ innerHtml += "| No activity recorded |
";
+ }
+
+ innerHtml += "";
+
+ var tableRoot = tooltipEl.querySelector("table");
+ tableRoot.innerHTML = innerHtml;
+ }
+
+ var canvasPos = this._chart.canvas.getBoundingClientRect();
+ var boxPos = tooltipEl.ancestor.getBoundingClientRect();
+ var offsetX = canvasPos.left - boxPos.left;
+ var offsetY = canvasPos.top - boxPos.top;
+ var tooltipWidth = tooltipEl.offsetWidth;
+ var tooltipHeight = tooltipEl.offsetHeight;
+ var caretX = tooltip.caretX;
+ var caretY = tooltip.caretY;
+ var caretPadding = tooltip.options.caretPadding;
+ var tooltipX, tooltipY, arrowX;
+ var arrowMinIndent = 2 * tooltip.options.cornerRadius;
+ var arrowSize = 5;
+
+ // Compute X position
+ if ($(document).width() > 2 * tooltip.width || tooltip.xAlign !== "center") {
+ // If the viewport is wide enough, let the tooltip follow the caret position
+ tooltipX = offsetX + caretX;
+ if (tooltip.yAlign === "top" || tooltip.yAlign === "bottom") {
+ switch (tooltip.xAlign) {
+ case "center":
+ // set a minimal X position to 5px to prevent
+ // the tooltip to stick out left of the viewport
+ var minX = 5;
+ if (2 * tooltipX < tooltipWidth + minX) {
+ arrowX = tooltipX - minX;
+ tooltipX = minX;
+ } else {
+ tooltipX -= tooltipWidth / 2;
+ }
+
+ break;
+ case "left":
+ tooltipX -= arrowMinIndent;
+ arrowX = arrowMinIndent;
+ break;
+ case "right":
+ tooltipX -= tooltipWidth - arrowMinIndent;
+ arrowX = tooltipWidth - arrowMinIndent;
+ break;
+ default:
+ break;
+ }
+ } else if (tooltip.yAlign === "center") {
+ switch (tooltip.xAlign) {
+ case "left":
+ tooltipX += caretPadding;
+ break;
+ case "right":
+ tooltipX -= tooltipWidth - caretPadding;
+ break;
+ case "center":
+ tooltipX -= tooltipWidth / 2;
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ // compute the tooltip's center inside ancestor element
+ tooltipX = (tooltipEl.ancestor.offsetWidth - tooltipWidth) / 2;
+ // move the tooltip if the arrow would stick out to the left
+ if (offsetX + caretX - arrowMinIndent < tooltipX) {
+ tooltipX = offsetX + caretX - arrowMinIndent;
+ }
+
+ // move the tooltip if the arrow would stick out to the right
+ if (offsetX + caretX - tooltipWidth + arrowMinIndent > tooltipX) {
+ tooltipX = offsetX + caretX - tooltipWidth + arrowMinIndent;
+ }
+
+ arrowX = offsetX + caretX - tooltipX;
+ }
+
+ // Compute Y position
+ switch (tooltip.yAlign) {
+ case "top":
+ tooltipY = offsetY + caretY + arrowSize + caretPadding;
+ break;
+ case "center":
+ tooltipY = offsetY + caretY - tooltipHeight / 2;
+ if (tooltip.xAlign === "left") {
+ tooltipX += arrowSize;
+ } else if (tooltip.xAlign === "right") {
+ tooltipX -= arrowSize;
+ }
+
+ break;
+ case "bottom":
+ tooltipY = offsetY + caretY - tooltipHeight - arrowSize - caretPadding;
+ break;
+ default:
+ break;
+ }
+
+ // Position tooltip and display
+ tooltipEl.style.top = tooltipY.toFixed(1) + "px";
+ tooltipEl.style.left = tooltipX.toFixed(1) + "px";
+ if (arrowX === undefined) {
+ tooltipEl.querySelector(".arrow").style.left = "";
+ } else {
+ // Calculate percentage X value depending on the tooltip's
+ // width to avoid hanging arrow out on tooltip width changes
+ var arrowXpercent = ((100 / tooltipWidth) * arrowX).toFixed(1);
+ tooltipEl.querySelector(".arrow").style.left = arrowXpercent + "%";
+ }
+
+ tooltipEl.style.opacity = 1;
+};
+
+// eslint-disable-next-line no-unused-vars
+function doughnutTooltip(tooltipLabel) {
+ var percentageTotalShown = tooltipLabel.chart._metasets[0].total.toFixed(1);
+ // tooltipLabel.chart._metasets[0].total returns the total percentage of the shown slices
+ // to compensate rounding errors we round to one decimal
+
+ var label = " " + tooltipLabel.label;
+ var itemPercentage;
+
+ // if we only show < 1% percent of all, show each item with two decimals
+ if (percentageTotalShown < 1) {
+ itemPercentage = tooltipLabel.parsed.toFixed(2);
+ } else {
+ // show with one decimal, but in case the item share is really small it could be rounded to 0.0
+ // we compensate for this
+ itemPercentage =
+ tooltipLabel.parsed.toFixed(1) === "0.0" ? "< 0.1" : tooltipLabel.parsed.toFixed(1);
+ }
+
+ // even if no doughnut slice is hidden, sometimes percentageTotalShown is slightly less then 100
+ // we therefore use 99.9 to decide if slices are hidden (we only show with 0.1 precision)
+ if (percentageTotalShown > 99.9) {
+ // All items shown
+ return label + ": " + itemPercentage + "%";
+ } else {
+ // set percentageTotalShown again without rounding to account
+ // for cases where the total shown percentage would be <0.1% of all
+ percentageTotalShown = tooltipLabel.chart._metasets[0].total;
+ return (
+ label +
+ ":
• " +
+ itemPercentage +
+ "% of all data
• " +
+ ((tooltipLabel.parsed * 100) / percentageTotalShown).toFixed(1) +
+ "% of shown items"
+ );
+ }
+}
+
+// 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;
+};
diff --git a/scripts/pi-hole/js/index.js b/scripts/pi-hole/js/index.js
index 98c7e9ee..57a0d63a 100644
--- a/scripts/pi-hole/js/index.js
+++ b/scripts/pi-hole/js/index.js
@@ -5,214 +5,12 @@
* 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, apiFailure:false */
+/* global utils:false, Chart:false, apiFailure:false, THEME_COLORS:false, customTooltips:false, htmlLegendPlugin:false,doughnutTooltip:false */
// Define global variables
var timeLineChart, clientsChart;
var queryTypePieChart, forwardDestinationPieChart;
-var THEME_COLORS = [
- "#f56954",
- "#3c8dbc",
- "#00a65a",
- "#00c0ef",
- "#f39c12",
- "#0073b7",
- "#001f3f",
- "#39cccc",
- "#3d9970",
- "#01ff70",
- "#ff851b",
- "#f012be",
- "#8e24aa",
- "#d81b60",
- "#222222",
- "#d2d6de",
-];
-
-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
- tooltipEl = document.createElement("div");
- tooltipEl.id = this._chart.canvas.id + "-customTooltip";
- tooltipEl.classList.add("chartjs-tooltip");
- tooltipEl.innerHTML = " ";
- // avoid browser's font-zoom since we know that 's
- // 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.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);
- }
-
- // Hide if no tooltip
- if (tooltip.opacity === 0) {
- tooltipEl.style.opacity = 0;
- return;
- }
-
- // Set caret position
- tooltipEl.classList.remove("left", "right", "center", "top", "bottom");
- tooltipEl.classList.add(tooltip.xAlign, tooltip.yAlign);
-
- // Set Text
- if (tooltip.body) {
- var titleLines = tooltip.title || [];
- var bodyLines = tooltip.body.map(function (bodyItem) {
- return bodyItem.lines;
- });
- var innerHtml = "";
-
- titleLines.forEach(function (title) {
- innerHtml += "| " + title + " |
";
- });
- innerHtml += "";
- var printed = 0;
-
- var devicePixel = (1 / window.devicePixelRatio).toFixed(1);
- bodyLines.forEach(function (body, i) {
- var labelColors = tooltip.labelColors[i];
- var style = "background-color: " + labelColors.backgroundColor;
- style += "; outline: 1px solid " + labelColors.backgroundColor;
- style += "; border: " + devicePixel + "px solid #fff";
- var span = "";
-
- var num = body[0].split(": ");
- // do not display entries with value of 0 (in bar chart),
- // but pass through entries with "0.0% (in pie charts)
- if (num[1] !== "0") {
- innerHtml += "| " + span + body + " |
";
- printed++;
- }
- });
- if (printed < 1) {
- innerHtml += "| No activity recorded |
";
- }
-
- innerHtml += "";
-
- var tableRoot = tooltipEl.querySelector("table");
- tableRoot.innerHTML = innerHtml;
- }
-
- var canvasPos = this._chart.canvas.getBoundingClientRect();
- var boxPos = tooltipEl.ancestor.getBoundingClientRect();
- var offsetX = canvasPos.left - boxPos.left;
- var offsetY = canvasPos.top - boxPos.top;
- var tooltipWidth = tooltipEl.offsetWidth;
- var tooltipHeight = tooltipEl.offsetHeight;
- var caretX = tooltip.caretX;
- var caretY = tooltip.caretY;
- var caretPadding = tooltip.options.caretPadding;
- var tooltipX, tooltipY, arrowX;
- var arrowMinIndent = 2 * tooltip.options.cornerRadius;
- var arrowSize = 5;
-
- // Compute X position
- if ($(document).width() > 2 * tooltip.width || tooltip.xAlign !== "center") {
- // If the viewport is wide enough, let the tooltip follow the caret position
- tooltipX = offsetX + caretX;
- if (tooltip.yAlign === "top" || tooltip.yAlign === "bottom") {
- switch (tooltip.xAlign) {
- case "center":
- // set a minimal X position to 5px to prevent
- // the tooltip to stick out left of the viewport
- var minX = 5;
- if (2 * tooltipX < tooltipWidth + minX) {
- arrowX = tooltipX - minX;
- tooltipX = minX;
- } else {
- tooltipX -= tooltipWidth / 2;
- }
-
- break;
- case "left":
- tooltipX -= arrowMinIndent;
- arrowX = arrowMinIndent;
- break;
- case "right":
- tooltipX -= tooltipWidth - arrowMinIndent;
- arrowX = tooltipWidth - arrowMinIndent;
- break;
- default:
- break;
- }
- } else if (tooltip.yAlign === "center") {
- switch (tooltip.xAlign) {
- case "left":
- tooltipX += caretPadding;
- break;
- case "right":
- tooltipX -= tooltipWidth - caretPadding;
- break;
- case "center":
- tooltipX -= tooltipWidth / 2;
- break;
- default:
- break;
- }
- }
- } else {
- // compute the tooltip's center inside ancestor element
- tooltipX = (tooltipEl.ancestor.offsetWidth - tooltipWidth) / 2;
- // move the tooltip if the arrow would stick out to the left
- if (offsetX + caretX - arrowMinIndent < tooltipX) {
- tooltipX = offsetX + caretX - arrowMinIndent;
- }
-
- // move the tooltip if the arrow would stick out to the right
- if (offsetX + caretX - tooltipWidth + arrowMinIndent > tooltipX) {
- tooltipX = offsetX + caretX - tooltipWidth + arrowMinIndent;
- }
-
- arrowX = offsetX + caretX - tooltipX;
- }
-
- // Compute Y position
- switch (tooltip.yAlign) {
- case "top":
- tooltipY = offsetY + caretY + arrowSize + caretPadding;
- break;
- case "center":
- tooltipY = offsetY + caretY - tooltipHeight / 2;
- if (tooltip.xAlign === "left") {
- tooltipX += arrowSize;
- } else if (tooltip.xAlign === "right") {
- tooltipX -= arrowSize;
- }
-
- break;
- case "bottom":
- tooltipY = offsetY + caretY - tooltipHeight - arrowSize - caretPadding;
- break;
- default:
- break;
- }
-
- // Position tooltip and display
- tooltipEl.style.top = tooltipY.toFixed(1) + "px";
- tooltipEl.style.left = tooltipX.toFixed(1) + "px";
- if (arrowX === undefined) {
- tooltipEl.querySelector(".arrow").style.left = "";
- } else {
- // Calculate percentage X value depending on the tooltip's
- // width to avoid hanging arrow out on tooltip width changes
- var arrowXpercent = ((100 / tooltipWidth) * arrowX).toFixed(1);
- tooltipEl.querySelector(".arrow").style.left = arrowXpercent + "%";
- }
-
- tooltipEl.style.opacity = 1;
-};
-
// Functions to update data in page
var failures = 0;
@@ -613,129 +411,6 @@ function updateSummaryData(runOnce) {
});
}
-function doughnutTooltip(tooltipLabel) {
- var percentageTotalShown = tooltipLabel.chart._metasets[0].total.toFixed(1);
- // tooltipLabel.chart._metasets[0].total returns the total percentage of the shown slices
- // to compensate rounding errors we round to one decimal
-
- var label = " " + tooltipLabel.label;
- var itemPercentage;
-
- // if we only show < 1% percent of all, show each item with two decimals
- if (percentageTotalShown < 1) {
- itemPercentage = tooltipLabel.parsed.toFixed(2);
- } else {
- // show with one decimal, but in case the item share is really small it could be rounded to 0.0
- // we compensate for this
- itemPercentage =
- tooltipLabel.parsed.toFixed(1) === "0.0" ? "< 0.1" : tooltipLabel.parsed.toFixed(1);
- }
-
- // even if no doughnut slice is hidden, sometimes percentageTotalShown is slightly less then 100
- // we therefore use 99.9 to decide if slices are hidden (we only show with 0.1 precision)
- if (percentageTotalShown > 99.9) {
- // All items shown
- return label + ": " + itemPercentage + "%";
- } else {
- // set percentageTotalShown again without rounding to account
- // for cases where the total shown percentage would be <0.1% of all
- percentageTotalShown = tooltipLabel.chart._metasets[0].total;
- return (
- label +
- ":
• " +
- itemPercentage +
- "% of all queries
• " +
- ((tooltipLabel.parsed * 100) / percentageTotalShown).toFixed(1) +
- "% of shown items"
- );
- }
-}
-
-// 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
- ? ''
- : '';
-
- 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.lp?querytype=" + querytypeids[item.index];
- } else if (chart.canvas.id === "forwardDestinationPieChart") {
- window.location.href = "queries.lp?forwarddest=" + encodeURIComponent(item.text);
- }
- });
-
- li.append(boxSpan, textLink);
- ul.append(li);
- });
- },
-};
-
$(function () {
// Pull in data via AJAX
updateSummaryData();
diff --git a/scripts/pi-hole/js/settings-system.js b/scripts/pi-hole/js/settings-system.js
index a0a7fe49..470089bc 100644
--- a/scripts/pi-hole/js/settings-system.js
+++ b/scripts/pi-hole/js/settings-system.js
@@ -5,9 +5,51 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global apiFailure:false */
+/* global apiFailure:false, Chart:false, THEME_COLORS:false, customTooltips:false, htmlLegendPlugin:false,doughnutTooltip:false */
var hostinfoTimer = null;
+var cachePieChart = null;
+var cacheSize = 0;
+
+var querytypeids = [];
+function updateCachePie(data) {
+ var v = [],
+ c = [],
+ k = [],
+ i = 0,
+ sum = 0;
+
+ // Compute total number of queries
+ Object.keys(data).forEach(function (item) {
+ sum += data[item];
+ });
+
+ // Add empty space to chart
+ data.empty = cacheSize - sum;
+ sum = cacheSize;
+
+ // Fill chart with data
+ querytypeids = [];
+ Object.keys(data).forEach(function (item) {
+ v.push((100 * data[item]) / sum);
+ c.push(THEME_COLORS[i % THEME_COLORS.length]);
+ k.push(item);
+ querytypeids.push(i + 1);
+
+ i++;
+ });
+
+ // Build a single dataset with the data to be pushed
+ var dd = { data: v, backgroundColor: c };
+ // and push it at once
+ cachePieChart.data.datasets[0] = dd;
+ cachePieChart.data.labels = k;
+ $("#cache-pie-chart .overlay").hide();
+ cachePieChart.update();
+
+ // Don't use rotation animation for further updates
+ cachePieChart.options.animation.duration = 0;
+}
function updateHostInfo() {
$.ajax({
@@ -45,16 +87,22 @@ function updateHostInfo() {
// Walk nested objects, create a dash-separated global key and assign the value
// to the corresponding element (add percentage for DNS replies)
function setMetrics(data, prefix) {
+ var cacheData = {};
for (const [key, val] of Object.entries(data)) {
if (prefix === "sysinfo-dns-cache-content-") {
// Create table row for each DNS cache entry
- const name =
- val.name !== "OTHER"
- ? "Valid " + (val.name !== null ? val.name : "TYPE " + val.type)
- : "Other valid";
- const tr = "| " + name + " records in cache: | " + val.count + " |
";
- // Append row to DNS cache table
- $("#dns-cache-table").append(tr);
+ // (if table exists)
+ if ($("#dns-cache-table").length > 0) {
+ const name =
+ val.name !== "OTHER"
+ ? "Valid " + (val.name !== null ? val.name : "TYPE " + val.type)
+ : "Other valid";
+ const tr = "| " + name + " records in cache: | " + val.count + " |
";
+ // Append row to DNS cache table
+ $("#dns-cache-table").append(tr);
+ }
+
+ cacheData[val.name] = val.count;
} else if (typeof val === "object") {
setMetrics(val, prefix + key + "-");
} else if (prefix === "sysinfo-dns-replies-") {
@@ -66,6 +114,11 @@ function setMetrics(data, prefix) {
$("#" + prefix + key).text(lval);
}
}
+
+ // Draw pie chart if data is available
+ if (Object.keys(cacheData).length > 0) {
+ updateCachePie(cacheData);
+ }
}
var metricsTimer = null;
@@ -77,6 +130,11 @@ function updateMetrics() {
.done(function (data) {
var metrics = data.metrics;
$("#dns-cache-table").empty();
+
+ // Set global cache size
+ cacheSize = metrics.dns.cache.size;
+
+ // Set metrics
setMetrics(metrics, "sysinfo-");
$("div[id^='sysinfo-metrics-overlay']").hide();
@@ -212,4 +270,45 @@ $(function () {
updateHostInfo();
updateMetrics();
showQueryLoggingButton();
+
+ var ctx = document.getElementById("cachePieChart").getContext("2d");
+ cachePieChart = new Chart(ctx, {
+ type: "doughnut",
+ data: {
+ labels: [],
+ datasets: [{ data: [], parsing: false }],
+ },
+ plugins: [htmlLegendPlugin],
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ elements: {
+ arc: {
+ borderColor: $(".box").css("background-color"),
+ },
+ },
+ plugins: {
+ htmlLegend: {
+ containerID: "cache-legend",
+ },
+ legend: {
+ display: false,
+ },
+ tooltip: {
+ // Disable the on-canvas tooltip
+ enabled: false,
+ external: customTooltips,
+ callbacks: {
+ title: function () {
+ return "Cache content";
+ },
+ label: doughnutTooltip,
+ },
+ },
+ },
+ animation: {
+ duration: 750,
+ },
+ },
+ });
});
diff --git a/settings-system.lp b/settings-system.lp
index 97d50bdb..023db375 100644
--- a/settings-system.lp
+++ b/settings-system.lp
@@ -159,7 +159,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
-
+
@@ -200,10 +200,17 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
-
+
+
+
@@ -297,6 +304,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
+