mirror of
https://github.com/pi-hole/web.git
synced 2025-12-20 02:38:28 +00:00
Modernize JS and tighten things (#3388)
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "Pi-hole web interface",
|
||||
"name": "pi-hole-web-interface",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Pi-hole web interface",
|
||||
"name": "pi-hole-web-interface",
|
||||
"version": "1.0.0",
|
||||
"license": "EUPL-1.2",
|
||||
"dependencies": {
|
||||
|
||||
37
package.json
37
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Pi-hole web interface",
|
||||
"name": "pi-hole-web-interface",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
@@ -75,6 +75,9 @@
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"xo": {
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
},
|
||||
"envs": [
|
||||
"browser",
|
||||
"jquery"
|
||||
@@ -82,10 +85,6 @@
|
||||
"extends": [
|
||||
"plugin:compat/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "script"
|
||||
},
|
||||
"prettier": true,
|
||||
"space": 2,
|
||||
"ignores": [
|
||||
@@ -107,37 +106,17 @@
|
||||
],
|
||||
"no-alert": "off",
|
||||
"no-console": "error",
|
||||
"no-else-return": "off",
|
||||
"no-negated-condition": "off",
|
||||
"no-var": "off",
|
||||
"object-shorthand": "off",
|
||||
"one-var": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-arrow-callback": "error",
|
||||
"spaced-comment": "off",
|
||||
"unicorn/explicit-length-check": [
|
||||
"error",
|
||||
{
|
||||
"non-zero": "greater-than"
|
||||
}
|
||||
],
|
||||
"unicorn/filename-case": "off",
|
||||
"strict": "error",
|
||||
"unicorn/no-anonymous-default-export": "off",
|
||||
"unicorn/no-array-for-each": "off",
|
||||
"unicorn/no-for-loop": "off",
|
||||
"unicorn/no-document-cookie": "off",
|
||||
"unicorn/numeric-separators-style": "off",
|
||||
"unicorn/prefer-includes": "off",
|
||||
"unicorn/no-negated-condition": "off",
|
||||
"unicorn/prefer-module": "off",
|
||||
"unicorn/prefer-node-append": "off",
|
||||
"unicorn/prefer-number-properties": "off",
|
||||
"unicorn/prefer-query-selector": "off",
|
||||
"unicorn/prefer-string-slice": "off",
|
||||
"unicorn/prevent-abbreviations": "off",
|
||||
"unicorn/prefer-logical-operator-over-ternary": "off",
|
||||
"unicorn/switch-case-braces": "off",
|
||||
"unicorn/no-negated-condition": "off",
|
||||
"logical-assignment-operators": "off",
|
||||
"prefer-object-has-own": "off"
|
||||
"unicorn/switch-case-braces": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
/* global upstreams:false */
|
||||
|
||||
"use strict";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var THEME_COLORS = [
|
||||
const THEME_COLORS = [
|
||||
"#f56954",
|
||||
"#3c8dbc",
|
||||
"#00a65a",
|
||||
@@ -57,7 +59,7 @@ const htmlLegendPlugin = {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
|
||||
items.forEach(item => {
|
||||
for (const item of items) {
|
||||
const li = document.createElement("li");
|
||||
li.style.alignItems = "center";
|
||||
li.style.cursor = "pointer";
|
||||
@@ -114,14 +116,14 @@ const htmlLegendPlugin = {
|
||||
|
||||
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");
|
||||
const customTooltips = function (context) {
|
||||
const tooltip = context.tooltip;
|
||||
let tooltipEl = document.getElementById(this.chart.canvas.id + "-customTooltip");
|
||||
if (!tooltipEl) {
|
||||
// Create Tooltip Element once per chart
|
||||
tooltipEl = document.createElement("div");
|
||||
@@ -130,7 +132,7 @@ var customTooltips = function (context) {
|
||||
tooltipEl.innerHTML = "<div class='arrow'></div> <table></table>";
|
||||
// avoid browser's font-zoom since we know that <body>'s
|
||||
// font-size was set to 14px by bootstrap's css
|
||||
var fontZoom = parseFloat($("body").css("font-size")) / 14;
|
||||
const fontZoom = Number.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";
|
||||
@@ -155,56 +157,58 @@ var customTooltips = function (context) {
|
||||
|
||||
// Set Text
|
||||
if (tooltip.body) {
|
||||
var titleLines = tooltip.title || [];
|
||||
var bodyLines = tooltip.body.map(function (bodyItem) {
|
||||
return bodyItem.lines;
|
||||
});
|
||||
var innerHtml = "<thead>";
|
||||
const titleLines = tooltip.title || [];
|
||||
const bodyLines = tooltip.body.map(bodyItem => bodyItem.lines);
|
||||
let innerHtml = "<thead>";
|
||||
|
||||
titleLines.forEach(function (title) {
|
||||
for (const title of titleLines) {
|
||||
innerHtml += "<tr><th>" + title + "</th></tr>";
|
||||
});
|
||||
innerHtml += "</thead><tbody>";
|
||||
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;
|
||||
innerHtml += "</thead><tbody>";
|
||||
let printed = 0;
|
||||
|
||||
const devicePixel = (1 / window.devicePixelRatio).toFixed(1);
|
||||
for (const [i, body] of bodyLines.entries()) {
|
||||
const labelColors = tooltip.labelColors[i];
|
||||
let style = "background-color: " + labelColors.backgroundColor;
|
||||
style += "; outline: 1px solid " + labelColors.backgroundColor;
|
||||
style += "; border: " + devicePixel + "px solid #fff";
|
||||
var span = "<span class='chartjs-tooltip-key' style='" + style + "'></span>";
|
||||
const span = "<span class='chartjs-tooltip-key' style='" + style + "'></span>";
|
||||
|
||||
var num = body[0].split(": ");
|
||||
const 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 += "<tr><td>" + span + body + "</td></tr>";
|
||||
printed++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (printed < 1) {
|
||||
innerHtml += "<tr><td>No activity recorded</td></tr>";
|
||||
}
|
||||
|
||||
innerHtml += "</tbody>";
|
||||
|
||||
var tableRoot = tooltipEl.querySelector("table");
|
||||
const 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;
|
||||
const canvasPos = this.chart.canvas.getBoundingClientRect();
|
||||
const boxPos = tooltipEl.ancestor.getBoundingClientRect();
|
||||
const offsetX = canvasPos.left - boxPos.left;
|
||||
const offsetY = canvasPos.top - boxPos.top;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
const tooltipHeight = tooltipEl.offsetHeight;
|
||||
const caretX = tooltip.caretX;
|
||||
const caretY = tooltip.caretY;
|
||||
const caretPadding = tooltip.options.caretPadding;
|
||||
let tooltipX;
|
||||
let tooltipY;
|
||||
let arrowX;
|
||||
const arrowMinIndent = 2 * tooltip.options.cornerRadius;
|
||||
const arrowSize = 5;
|
||||
|
||||
// Compute X position
|
||||
if ($(document).width() > 2 * tooltip.width || tooltip.xAlign !== "center") {
|
||||
@@ -212,10 +216,10 @@ var customTooltips = function (context) {
|
||||
tooltipX = offsetX + caretX;
|
||||
if (tooltip.yAlign === "top" || tooltip.yAlign === "bottom") {
|
||||
switch (tooltip.xAlign) {
|
||||
case "center":
|
||||
case "center": {
|
||||
// set a minimal X position to 5px to prevent
|
||||
// the tooltip to stick out left of the viewport
|
||||
var minX = 5;
|
||||
const minX = 5;
|
||||
if (2 * tooltipX < tooltipWidth + minX) {
|
||||
arrowX = tooltipX - minX;
|
||||
tooltipX = minX;
|
||||
@@ -224,6 +228,8 @@ var customTooltips = function (context) {
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "left":
|
||||
tooltipX -= arrowMinIndent;
|
||||
arrowX = arrowMinIndent;
|
||||
@@ -295,7 +301,7 @@ var customTooltips = function (context) {
|
||||
} 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);
|
||||
const arrowXpercent = ((100 / tooltipWidth) * arrowX).toFixed(1);
|
||||
tooltipEl.querySelector(".arrow").style.left = arrowXpercent + "%";
|
||||
}
|
||||
|
||||
@@ -304,12 +310,12 @@ var customTooltips = function (context) {
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function doughnutTooltip(tooltipLabel) {
|
||||
var percentageTotalShown = tooltipLabel.chart._metasets[0].total.toFixed(1);
|
||||
let 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;
|
||||
const label = " " + tooltipLabel.label;
|
||||
let itemPercentage;
|
||||
|
||||
// if we only show < 1% percent of all, show each item with two decimals
|
||||
if (percentageTotalShown < 1) {
|
||||
@@ -326,19 +332,19 @@ function doughnutTooltip(tooltipLabel) {
|
||||
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 +
|
||||
":<br>• " +
|
||||
itemPercentage +
|
||||
"% of all data<br>• " +
|
||||
((tooltipLabel.parsed * 100) / percentageTotalShown).toFixed(1) +
|
||||
"% of shown items"
|
||||
);
|
||||
}
|
||||
|
||||
// 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 +
|
||||
":<br>• " +
|
||||
itemPercentage +
|
||||
"% of all data<br>• " +
|
||||
((tooltipLabel.parsed * 100) / percentageTotalShown).toFixed(1) +
|
||||
"% of shown items"
|
||||
);
|
||||
}
|
||||
|
||||
// chartjs plugin used by the custom doughnut legend
|
||||
|
||||
@@ -7,30 +7,32 @@
|
||||
|
||||
/* global utils:false, moment:false */
|
||||
|
||||
var _isLoginPage = false;
|
||||
"use strict";
|
||||
|
||||
globalThis._isLoginPage = false;
|
||||
|
||||
const REFRESH_INTERVAL = {
|
||||
logs: 500, // 0.5 sec (logs page)
|
||||
summary: 1000, // 1 sec (dashboard)
|
||||
query_log: 2000, // 2 sec (Query Log)
|
||||
blocking: 10000, // 10 sec (all pages, sidebar)
|
||||
metrics: 10000, // 10 sec (settings page)
|
||||
system: 20000, // 20 sec (all pages, sidebar)
|
||||
query_types: 60000, // 1 min (dashboard)
|
||||
upstreams: 60000, // 1 min (dashboard)
|
||||
top_lists: 60000, // 1 min (dashboard)
|
||||
messages: 60000, // 1 min (all pages)
|
||||
version: 120000, // 2 min (all pages, footer)
|
||||
ftl: 120000, // 2 min (all pages, sidebar)
|
||||
hosts: 120000, // 2 min (settings page)
|
||||
history: 600000, // 10 min (dashboard)
|
||||
clients: 600000, // 10 min (dashboard)
|
||||
blocking: 10_000, // 10 sec (all pages, sidebar)
|
||||
metrics: 10_000, // 10 sec (settings page)
|
||||
system: 20_000, // 20 sec (all pages, sidebar)
|
||||
query_types: 60_000, // 1 min (dashboard)
|
||||
upstreams: 60_000, // 1 min (dashboard)
|
||||
top_lists: 60_000, // 1 min (dashboard)
|
||||
messages: 60_000, // 1 min (all pages)
|
||||
version: 120_000, // 2 min (all pages, footer)
|
||||
ftl: 120_000, // 2 min (all pages, sidebar)
|
||||
hosts: 120_000, // 2 min (settings page)
|
||||
history: 600_000, // 10 min (dashboard)
|
||||
clients: 600_000, // 10 min (dashboard)
|
||||
};
|
||||
|
||||
function secondsTimeSpanToHMS(s) {
|
||||
var h = Math.floor(s / 3600); //Get whole hours
|
||||
const h = Math.floor(s / 3600); //Get whole hours
|
||||
s -= h * 3600;
|
||||
var m = Math.floor(s / 60); //Get remaining minutes
|
||||
const m = Math.floor(s / 60); //Get remaining minutes
|
||||
s -= m * 60;
|
||||
return h + ":" + (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s); //zero padding on minutes and seconds
|
||||
}
|
||||
@@ -41,8 +43,8 @@ function piholeChanged(blocking, timer = null) {
|
||||
const dis = $("#pihole-disable");
|
||||
const enaT = $("#enableTimer");
|
||||
|
||||
if (timer !== null && parseFloat(timer) > 0) {
|
||||
enaT.html(Date.now() + parseFloat(timer) * 1000);
|
||||
if (timer !== null && Number.parseFloat(timer) > 0) {
|
||||
enaT.html(Date.now() + Number.parseFloat(timer) * 1000);
|
||||
setTimeout(countDown, 100);
|
||||
}
|
||||
|
||||
@@ -83,10 +85,10 @@ function piholeChanged(blocking, timer = null) {
|
||||
}
|
||||
|
||||
function countDown() {
|
||||
var ena = $("#enableLabel");
|
||||
var enaT = $("#enableTimer");
|
||||
var target = new Date(parseInt(enaT.text(), 10));
|
||||
var seconds = Math.round((target.getTime() - Date.now()) / 1000);
|
||||
const ena = $("#enableLabel");
|
||||
const enaT = $("#enableTimer");
|
||||
const target = new Date(Number.parseInt(enaT.text(), 10));
|
||||
const seconds = Math.round((target.getTime() - Date.now()) / 1000);
|
||||
|
||||
//Stop and remove timer when user enabled early
|
||||
if ($("#pihole-enable").is(":hidden")) {
|
||||
@@ -117,11 +119,11 @@ function checkBlocking() {
|
||||
url: document.body.dataset.apiurl + "/dns/blocking",
|
||||
method: "GET",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
piholeChanged(data.blocking, data.timer);
|
||||
utils.setTimer(checkBlocking, REFRESH_INTERVAL.blocking);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
utils.setTimer(checkBlocking, 3 * REFRESH_INTERVAL.blocking);
|
||||
});
|
||||
@@ -150,16 +152,16 @@ function piholeChange(action, duration) {
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
blocking: action === "enable",
|
||||
timer: parseInt(duration, 10) > 0 ? parseInt(duration, 10) : null,
|
||||
timer: Number.parseInt(duration, 10) > 0 ? Number.parseInt(duration, 10) : null,
|
||||
}),
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
if (data.blocking === action + "d") {
|
||||
btnStatus.html("");
|
||||
piholeChanged(data.blocking, data.timer);
|
||||
}
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -171,7 +173,7 @@ function testCookies() {
|
||||
|
||||
// set and read cookie
|
||||
document.cookie = "cookietest=1";
|
||||
var ret = document.cookie.indexOf("cookietest=") !== -1;
|
||||
const ret = document.cookie.includes("cookietest=");
|
||||
|
||||
// delete cookie
|
||||
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
|
||||
@@ -182,7 +184,7 @@ function testCookies() {
|
||||
function applyCheckboxRadioStyle() {
|
||||
// Get all radio/checkboxes for theming, with the exception of the two radio buttons on the custom disable timer,
|
||||
// as well as every element with an id that starts with "status_"
|
||||
var sel = $("input[type='radio'],input[type='checkbox']")
|
||||
const sel = $("input[type='radio'],input[type='checkbox']")
|
||||
.not("#selSec")
|
||||
.not("#selMin")
|
||||
.not("#expert-settings")
|
||||
@@ -192,7 +194,8 @@ function applyCheckboxRadioStyle() {
|
||||
sel.parent().addClass("icheck-primary");
|
||||
}
|
||||
|
||||
var systemTimer, versionTimer;
|
||||
let systemTimer;
|
||||
let versionTimer;
|
||||
function updateInfo() {
|
||||
updateSystemInfo();
|
||||
updateVersionInfo();
|
||||
@@ -201,7 +204,7 @@ function updateInfo() {
|
||||
}
|
||||
|
||||
function updateQueryFrequency(intl, frequency) {
|
||||
let freq = parseFloat(frequency) * 60;
|
||||
let freq = Number.parseFloat(frequency) * 60;
|
||||
let unit = "q/min";
|
||||
let title = "Queries per minute";
|
||||
if (freq > 100) {
|
||||
@@ -231,15 +234,15 @@ function updateQueryFrequency(intl, frequency) {
|
||||
.attr("title", title);
|
||||
}
|
||||
|
||||
var ftlinfoTimer = null;
|
||||
let ftlinfoTimer = null;
|
||||
function updateFtlInfo() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/info/ftl",
|
||||
})
|
||||
.done(function (data) {
|
||||
var ftl = data.ftl;
|
||||
var database = ftl.database;
|
||||
var intl = new Intl.NumberFormat();
|
||||
.done(data => {
|
||||
const ftl = data.ftl;
|
||||
const database = ftl.database;
|
||||
const intl = new Intl.NumberFormat();
|
||||
$("#num_groups").text(intl.format(database.groups));
|
||||
$("#num_clients").text(intl.format(database.clients));
|
||||
$("#num_lists").text(intl.format(database.lists));
|
||||
@@ -268,7 +271,7 @@ function updateFtlInfo() {
|
||||
$("#sysinfo-cpu-ftl").text("(" + ftl["%cpu"].toFixed(1) + "% used by FTL)");
|
||||
$("#sysinfo-ram-ftl").text("(" + ftl["%mem"].toFixed(1) + "% used by FTL)");
|
||||
$("#sysinfo-pid-ftl").text(ftl.pid);
|
||||
var startdate = moment()
|
||||
const startdate = moment()
|
||||
.subtract(ftl.uptime, "milliseconds")
|
||||
.format("dddd, MMMM Do YYYY, HH:mm:ss");
|
||||
$("#sysinfo-uptime-ftl").text(startdate);
|
||||
@@ -284,7 +287,7 @@ function updateFtlInfo() {
|
||||
clearTimeout(ftlinfoTimer);
|
||||
ftlinfoTimer = utils.setTimer(updateFtlInfo, REFRESH_INTERVAL.ftl);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -293,10 +296,10 @@ function updateSystemInfo() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/info/system",
|
||||
})
|
||||
.done(function (data) {
|
||||
var system = data.system;
|
||||
var percentRAM = system.memory.ram["%used"];
|
||||
var percentSwap = system.memory.swap["%used"];
|
||||
.done(data => {
|
||||
const system = data.system;
|
||||
const percentRAM = system.memory.ram["%used"];
|
||||
const percentSwap = system.memory.swap["%used"];
|
||||
let totalRAM = system.memory.ram.total / 1024;
|
||||
let totalRAMUnit = "MB";
|
||||
if (totalRAM > 1024) {
|
||||
@@ -311,11 +314,11 @@ function updateSystemInfo() {
|
||||
totalSwapUnit = "GB";
|
||||
}
|
||||
|
||||
var swap =
|
||||
const swap =
|
||||
system.memory.swap.total > 0
|
||||
? ((1e2 * system.memory.swap.used) / system.memory.swap.total).toFixed(1) + " %"
|
||||
: "N/A";
|
||||
var color;
|
||||
let color;
|
||||
color = percentRAM > 75 ? "text-red" : "text-green-light";
|
||||
$("#memory").html(
|
||||
'<i class="fa fa-fw fa-memory ' +
|
||||
@@ -374,7 +377,7 @@ function updateSystemInfo() {
|
||||
" processes"
|
||||
);
|
||||
|
||||
var startdate = moment()
|
||||
const startdate = moment()
|
||||
.subtract(system.uptime, "seconds")
|
||||
.format("dddd, MMMM Do YYYY, HH:mm:ss");
|
||||
$("#status").prop(
|
||||
@@ -393,7 +396,7 @@ function updateSystemInfo() {
|
||||
clearTimeout(systemTimer);
|
||||
systemTimer = utils.setTimer(updateSystemInfo, REFRESH_INTERVAL.system);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -410,8 +413,8 @@ function apiFailure(data) {
|
||||
// Credits: https://www.geeksforgeeks.org/compare-two-version-numbers/
|
||||
function versionCompare(v1, v2) {
|
||||
// vnum stores each numeric part of version
|
||||
var vnum1 = 0,
|
||||
vnum2 = 0;
|
||||
let vnum1 = 0;
|
||||
let vnum2 = 0;
|
||||
|
||||
// Remove possible leading "v" in v1 and v2
|
||||
if (v1[0] === "v") {
|
||||
@@ -423,7 +426,7 @@ function versionCompare(v1, v2) {
|
||||
}
|
||||
|
||||
// loop until both string are processed
|
||||
for (var i = 0, j = 0; i < v1.length || j < v2.length; ) {
|
||||
for (let i = 0, j = 0; i < v1.length || j < v2.length; ) {
|
||||
// storing numeric part of version 1 in vnum1
|
||||
while (i < v1.length && v1[i] !== ".") {
|
||||
vnum1 = vnum1 * 10 + (v1[i] - "0");
|
||||
@@ -452,14 +455,14 @@ function versionCompare(v1, v2) {
|
||||
function updateVersionInfo() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/info/version",
|
||||
}).done(function (data) {
|
||||
var version = data.version;
|
||||
var updateAvailable = false;
|
||||
var dockerUpdate = false;
|
||||
var isDocker = false;
|
||||
}).done(data => {
|
||||
const version = data.version;
|
||||
let updateAvailable = false;
|
||||
let dockerUpdate = false;
|
||||
let isDocker = false;
|
||||
$("#versions").empty();
|
||||
|
||||
var versions = [
|
||||
const versions = [
|
||||
{
|
||||
name: "Docker Tag",
|
||||
local: version.docker.local,
|
||||
@@ -502,11 +505,11 @@ function updateVersionInfo() {
|
||||
isDocker = true;
|
||||
}
|
||||
|
||||
versions.forEach(function (v) {
|
||||
for (const v of versions) {
|
||||
if (v.local !== null) {
|
||||
// reset update status for each component
|
||||
var updateComponentAvailable = false;
|
||||
var localVersion = v.local;
|
||||
let updateComponentAvailable = false;
|
||||
let localVersion = v.local;
|
||||
if (v.branch !== null && v.hash !== null) {
|
||||
if (v.branch === "master") {
|
||||
localVersion = v.local.split("-")[0];
|
||||
@@ -569,7 +572,7 @@ function updateVersionInfo() {
|
||||
$("#versions").append("<li><strong>" + v.name + "</strong> " + localVersion + "</li>");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (dockerUpdate)
|
||||
$("#update-hint").html(
|
||||
@@ -585,11 +588,11 @@ function updateVersionInfo() {
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
if (!_isLoginPage) updateInfo();
|
||||
var enaT = $("#enableTimer");
|
||||
var target = new Date(parseInt(enaT.html(), 10));
|
||||
var seconds = Math.round((target.getTime() - Date.now()) / 1000);
|
||||
$(() => {
|
||||
if (!globalThis._isLoginPage) updateInfo();
|
||||
const enaT = $("#enableTimer");
|
||||
const target = new Date(Number.parseInt(enaT.html(), 10));
|
||||
const seconds = Math.round((target.getTime() - Date.now()) / 1000);
|
||||
if (seconds > 0) {
|
||||
setTimeout(countDown, 100);
|
||||
}
|
||||
@@ -601,7 +604,7 @@ $(function () {
|
||||
// Apply icheckbox/iradio style
|
||||
applyCheckboxRadioStyle();
|
||||
|
||||
if (!_isLoginPage) {
|
||||
if (!globalThis._isLoginPage) {
|
||||
// Run check immediately after page loading ...
|
||||
utils.checkMessages();
|
||||
// ... and then periodically
|
||||
@@ -610,30 +613,30 @@ $(function () {
|
||||
});
|
||||
|
||||
// Handle Enable/Disable
|
||||
$("#pihole-enable").on("click", function (e) {
|
||||
$("#pihole-enable").on("click", e => {
|
||||
e.preventDefault();
|
||||
localStorage.removeItem("countDownTarget");
|
||||
piholeChange("enable", "");
|
||||
});
|
||||
$("#pihole-disable-indefinitely").on("click", function (e) {
|
||||
$("#pihole-disable-indefinitely").on("click", e => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable", "0");
|
||||
});
|
||||
$("#pihole-disable-10s").on("click", function (e) {
|
||||
$("#pihole-disable-10s").on("click", e => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable", "10");
|
||||
});
|
||||
$("#pihole-disable-30s").on("click", function (e) {
|
||||
$("#pihole-disable-30s").on("click", e => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable", "30");
|
||||
});
|
||||
$("#pihole-disable-5m").on("click", function (e) {
|
||||
$("#pihole-disable-5m").on("click", e => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable", "300");
|
||||
});
|
||||
$("#pihole-disable-custom").on("click", function (e) {
|
||||
$("#pihole-disable-custom").on("click", e => {
|
||||
e.preventDefault();
|
||||
var custVal = $("#customTimeout").val();
|
||||
let custVal = $("#customTimeout").val();
|
||||
custVal = $("#btnMins").hasClass("active") ? custVal * 60 : custVal;
|
||||
piholeChange("disable", custVal);
|
||||
});
|
||||
@@ -706,9 +709,9 @@ function addAdvancedInfo() {
|
||||
const advancedInfoTarget = $("#advanced-info");
|
||||
const isTLS = location.protocol === "https:";
|
||||
const clientIP = advancedInfoSource.data("client-ip");
|
||||
const XForwardedFor = globalThis.atob(advancedInfoSource.data("xff") ?? "");
|
||||
const starttime = parseFloat(advancedInfoSource.data("starttime"));
|
||||
const endtime = parseFloat(advancedInfoSource.data("endtime"));
|
||||
const XForwardedFor = globalThis.atob(advancedInfoSource.data("xff") || "") || null;
|
||||
const starttime = Number.parseFloat(advancedInfoSource.data("starttime"));
|
||||
const endtime = Number.parseFloat(advancedInfoSource.data("endtime"));
|
||||
const totaltime = 1e3 * (endtime - starttime);
|
||||
|
||||
// Show advanced info
|
||||
@@ -724,7 +727,7 @@ function addAdvancedInfo() {
|
||||
);
|
||||
|
||||
// Add client IP info
|
||||
$("#client-id").text(XForwardedFor ? XForwardedFor : clientIP);
|
||||
$("#client-id").text(XForwardedFor ?? clientIP);
|
||||
if (XForwardedFor) {
|
||||
// If X-Forwarded-For is set, show the X-Forwarded-For in italics and add
|
||||
// the real client IP as tooltip
|
||||
@@ -741,7 +744,7 @@ function addAdvancedInfo() {
|
||||
advancedInfoTarget.show();
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
initSettingsLevel();
|
||||
addAdvancedInfo();
|
||||
});
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
"use strict";
|
||||
|
||||
function eventsource() {
|
||||
var alInfo = $("#alInfo");
|
||||
var alSuccess = $("#alSuccess");
|
||||
var ta = $("#output");
|
||||
const alInfo = $("#alInfo");
|
||||
const alSuccess = $("#alSuccess");
|
||||
const ta = $("#output");
|
||||
|
||||
ta.html("");
|
||||
ta.show();
|
||||
@@ -37,10 +39,10 @@ function eventsource() {
|
||||
|
||||
// Enqueue the next data chunk into our target stream
|
||||
controller.enqueue(value);
|
||||
var string = new TextDecoder().decode(value);
|
||||
const string = new TextDecoder().decode(value);
|
||||
parseLines(ta, string);
|
||||
|
||||
if (string.indexOf("Done.") !== -1) {
|
||||
if (string.includes("Done.")) {
|
||||
alSuccess.show();
|
||||
}
|
||||
|
||||
@@ -53,13 +55,13 @@ function eventsource() {
|
||||
.catch(error => console.error(error)); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
$("#gravityBtn").on("click", function () {
|
||||
$("#gravityBtn").on("click", () => {
|
||||
$("#gravityBtn").prop("disabled", true);
|
||||
eventsource();
|
||||
});
|
||||
|
||||
// Handle hiding of alerts
|
||||
$(function () {
|
||||
$(() => {
|
||||
$("[data-hide]").on("click", function () {
|
||||
$(this)
|
||||
.closest("." + $(this).attr("data-hide"))
|
||||
@@ -68,8 +70,8 @@ $(function () {
|
||||
|
||||
// Do we want to start updating immediately?
|
||||
// gravity?go
|
||||
var searchString = globalThis.location.search.substring(1);
|
||||
if (searchString.indexOf("go") !== -1) {
|
||||
const searchString = globalThis.location.search.substring(1);
|
||||
if (searchString.includes("go")) {
|
||||
$("#gravityBtn").prop("disabled", true);
|
||||
eventsource();
|
||||
}
|
||||
@@ -80,7 +82,7 @@ function parseLines(ta, str) {
|
||||
// We want to split the text before an "OVER" escape sequence to allow overwriting previous line when needed
|
||||
|
||||
// Splitting the text on "\r"
|
||||
var lines = str.split(/(?=\r)/g);
|
||||
const lines = str.split(/(?=\r)/g);
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i][0] === "\r") {
|
||||
|
||||
@@ -8,15 +8,17 @@
|
||||
/* global utils:false, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
"use strict";
|
||||
|
||||
let table;
|
||||
|
||||
function reloadClientSuggestions() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/clients/_suggestions",
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
var sel = $("#select");
|
||||
success(data) {
|
||||
const sel = $("#select");
|
||||
sel.empty();
|
||||
|
||||
// In order for the placeholder value to appear, we have to have a blank
|
||||
@@ -27,11 +29,11 @@ function reloadClientSuggestions() {
|
||||
sel.append($("<option />"));
|
||||
|
||||
// Add data obtained from API
|
||||
for (var i = 0; i < data.clients.length; i++) {
|
||||
for (let i = 0; i < data.clients.length; i++) {
|
||||
const client = data.clients[i];
|
||||
let mockDevice = false;
|
||||
var text = client.hwaddr.toUpperCase();
|
||||
var key = text;
|
||||
let text = client.hwaddr.toUpperCase();
|
||||
let key = text;
|
||||
if (key.startsWith("IP-")) {
|
||||
// Mock MAC address for address-only devices
|
||||
mockDevice = true;
|
||||
@@ -40,10 +42,10 @@ function reloadClientSuggestions() {
|
||||
}
|
||||
|
||||
// Append additional infos if available
|
||||
var extraInfo = "";
|
||||
let extraInfo = "";
|
||||
if (client.names !== null && client.names.length > 0) {
|
||||
// Count number of "," in client.names to determine number of hostnames
|
||||
var numHostnames = client.names.split(",").length;
|
||||
const numHostnames = client.names.split(",").length;
|
||||
const pluralHostnames = numHostnames > 1 ? "s" : "";
|
||||
extraInfo =
|
||||
numHostnames + " hostname" + pluralHostnames + ": " + utils.escapeHtml(client.names);
|
||||
@@ -59,7 +61,7 @@ function reloadClientSuggestions() {
|
||||
if (client.addresses !== null && client.addresses.length > 0 && !mockDevice) {
|
||||
if (extraInfo.length > 0) extraInfo += "; ";
|
||||
// Count number of "," in client.addresses to determine number of addresses
|
||||
var numAddresses = client.addresses.split(",").length;
|
||||
const numAddresses = client.addresses.split(",").length;
|
||||
const pluralAddresses = numAddresses > 1 ? "es" : "";
|
||||
extraInfo +=
|
||||
numAddresses + " address" + pluralAddresses + ": " + utils.escapeHtml(client.addresses);
|
||||
@@ -73,7 +75,7 @@ function reloadClientSuggestions() {
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
$("#btnAdd").on("click", addClient);
|
||||
$("#select").select2({
|
||||
tags: true,
|
||||
@@ -85,7 +87,7 @@ $(function () {
|
||||
utils.setBsSelectDefaults();
|
||||
getGroups();
|
||||
|
||||
$("#select").on("change", function () {
|
||||
$("#select").on("change", () => {
|
||||
$("#ip-custom").val("");
|
||||
$("#ip-custom").prop("disabled", $("#select option:selected").val() !== "custom");
|
||||
});
|
||||
@@ -113,7 +115,7 @@ function initTable() {
|
||||
{
|
||||
targets: 1,
|
||||
className: "select-checkbox",
|
||||
render: function () {
|
||||
render() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
@@ -122,26 +124,26 @@ function initTable() {
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
// Hide buttons if all clients were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
const hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
$('button[id^="deleteClient_"]').on("click", deleteClient);
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
var dataId = utils.hexEncode(data.client);
|
||||
rowCallback(row, data) {
|
||||
const dataId = utils.hexEncode(data.client);
|
||||
$(row).attr("data-id", dataId);
|
||||
var tooltip =
|
||||
const tooltip =
|
||||
"Added: " +
|
||||
utils.datetime(data.date_added, false) +
|
||||
"\nLast modified: " +
|
||||
utils.datetime(data.date_modified, false) +
|
||||
"\nDatabase ID: " +
|
||||
data.id;
|
||||
var ipName =
|
||||
let ipName =
|
||||
'<code id="ip_' +
|
||||
dataId +
|
||||
'" title="' +
|
||||
@@ -161,7 +163,7 @@ function initTable() {
|
||||
$("td:eq(1)", row).html(ipName);
|
||||
|
||||
$("td:eq(2)", row).html('<input id="comment_' + dataId + '" class="form-control">');
|
||||
var commentEl = $("#comment_" + dataId, row);
|
||||
const commentEl = $("#comment_" + dataId, row);
|
||||
commentEl.val(data.comment);
|
||||
commentEl.on("change", editClient);
|
||||
|
||||
@@ -169,45 +171,44 @@ function initTable() {
|
||||
$("td:eq(3)", row).append(
|
||||
'<select class="selectpicker" id="multiselect_' + dataId + '" multiple></select>'
|
||||
);
|
||||
var selectEl = $("#multiselect_" + dataId, row);
|
||||
const selectEl = $("#multiselect_" + dataId, row);
|
||||
// Add all known groups
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var dataSub = "";
|
||||
if (!groups[i].enabled) {
|
||||
dataSub = 'data-subtext="(disabled)"';
|
||||
}
|
||||
for (const group of groups) {
|
||||
const dataSub = group.enabled ? "" : 'data-subtext="(disabled)"';
|
||||
|
||||
selectEl.append(
|
||||
$("<option " + dataSub + "/>")
|
||||
.val(groups[i].id)
|
||||
.text(groups[i].name)
|
||||
.val(group.id)
|
||||
.text(group.name)
|
||||
);
|
||||
}
|
||||
|
||||
const applyBtn = "#btn_apply_" + dataId;
|
||||
|
||||
// Select assigned groups
|
||||
selectEl.val(data.groups);
|
||||
// Initialize bootstrap-select
|
||||
selectEl
|
||||
// fix dropdown if it would stick out right of the viewport
|
||||
.on("show.bs.select", function () {
|
||||
var winWidth = $(globalThis).width();
|
||||
var dropdownEl = $("body > .bootstrap-select.dropdown");
|
||||
.on("show.bs.select", () => {
|
||||
const winWidth = $(globalThis).width();
|
||||
const dropdownEl = $("body > .bootstrap-select.dropdown");
|
||||
if (dropdownEl.length > 0) {
|
||||
dropdownEl.removeClass("align-right");
|
||||
var width = dropdownEl.width();
|
||||
var left = dropdownEl.offset().left;
|
||||
const width = dropdownEl.width();
|
||||
const left = dropdownEl.offset().left;
|
||||
if (left + width > winWidth) {
|
||||
dropdownEl.addClass("align-right");
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("changed.bs.select", function () {
|
||||
.on("changed.bs.select", () => {
|
||||
// enable Apply button
|
||||
if ($(applyBtn).prop("disabled")) {
|
||||
$(applyBtn)
|
||||
.addClass("btn-success")
|
||||
.prop("disabled", false)
|
||||
.on("click", function () {
|
||||
.on("click", () => {
|
||||
editClient.call(selectEl);
|
||||
});
|
||||
}
|
||||
@@ -228,9 +229,7 @@ function initTable() {
|
||||
' class="btn btn-block btn-sm" disabled>Apply</button>'
|
||||
);
|
||||
|
||||
var applyBtn = "#btn_apply_" + dataId;
|
||||
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteClient_' +
|
||||
dataId +
|
||||
'" data-id="' +
|
||||
@@ -250,7 +249,7 @@ function initTable() {
|
||||
text: '<span class="far fa-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectAll",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -258,7 +257,7 @@ function initTable() {
|
||||
text: '<span class="far fa-plus-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectMore",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -272,9 +271,9 @@ function initTable() {
|
||||
text: '<span class="far fa-trash-alt"></span>',
|
||||
titleAttr: "Delete Selected",
|
||||
className: "btn-sm datatable-bt deleteSelected",
|
||||
action: function () {
|
||||
action() {
|
||||
// For each ".selected" row ...
|
||||
var ids = [];
|
||||
const ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push({ item: $(this).attr("data-id") });
|
||||
@@ -296,11 +295,11 @@ function initTable() {
|
||||
],
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("groups-clients-table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback("groups-clients-table");
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback("groups-clients-table");
|
||||
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
@@ -315,7 +314,7 @@ function initTable() {
|
||||
});
|
||||
|
||||
// Disable autocorrect in the search box
|
||||
var input = document.querySelector("input[type=search]");
|
||||
const input = document.querySelector("input[type=search]");
|
||||
if (input !== null) {
|
||||
input.setAttribute("autocomplete", "off");
|
||||
input.setAttribute("autocorrect", "off");
|
||||
@@ -323,12 +322,12 @@ function initTable() {
|
||||
input.setAttribute("spellcheck", false);
|
||||
}
|
||||
|
||||
table.on("init select deselect", function () {
|
||||
table.on("init select deselect", () => {
|
||||
utils.changeBulkDeleteStates(table);
|
||||
});
|
||||
|
||||
table.on("order.dt", function () {
|
||||
var order = table.order();
|
||||
table.on("order.dt", () => {
|
||||
const order = table.order();
|
||||
if (order[0][0] !== 0 || order[0][1] !== "asc") {
|
||||
$("#resetButton").removeClass("hidden");
|
||||
} else {
|
||||
@@ -336,7 +335,7 @@ function initTable() {
|
||||
}
|
||||
});
|
||||
|
||||
$("#resetButton").on("click", function () {
|
||||
$("#resetButton").on("click", () => {
|
||||
table.order([[0, "asc"]]).draw();
|
||||
$("#resetButton").addClass("hidden");
|
||||
});
|
||||
@@ -358,14 +357,12 @@ function addClient() {
|
||||
|
||||
// Check if the user wants to add multiple IPs (space or newline separated)
|
||||
// If so, split the input and store it in an array
|
||||
var ips = $("#select")
|
||||
const ips = $("#select")
|
||||
.val()
|
||||
.trim()
|
||||
.split(/[\s,]+/);
|
||||
// Remove empty elements
|
||||
ips = ips.filter(function (el) {
|
||||
return el !== "";
|
||||
});
|
||||
.split(/[\s,]+/)
|
||||
// Remove empty elements
|
||||
.filter(el => el !== "");
|
||||
const ipStr = JSON.stringify(ips);
|
||||
|
||||
// Validate input, can be:
|
||||
@@ -373,7 +370,7 @@ function addClient() {
|
||||
// - IPv6 address (with and without CIDR)
|
||||
// - MAC address (in the form AA:BB:CC:DD:EE:FF)
|
||||
// - host name (arbitrary form, we're only checking against some reserved characters)
|
||||
for (var i = 0; i < ips.length; i++) {
|
||||
for (let i = 0; i < ips.length; i++) {
|
||||
if (
|
||||
utils.validateIPv4CIDR(ips[i]) ||
|
||||
utils.validateIPv6CIDR(ips[i]) ||
|
||||
@@ -407,8 +404,8 @@ function addClient() {
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({ client: ips, comment: comment, groups: group }),
|
||||
success: function (data) {
|
||||
data: JSON.stringify({ client: ips, comment, groups: group }),
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
utils.listsAlert("client", ips, data);
|
||||
reloadClientSuggestions();
|
||||
@@ -419,7 +416,7 @@ function addClient() {
|
||||
// Update number of groups in the sidebar
|
||||
updateFtlInfo();
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while adding new client", data.responseText);
|
||||
@@ -439,8 +436,8 @@ function editClient() {
|
||||
.map(Number);
|
||||
const comment = tr.find("#comment_" + client).val();
|
||||
|
||||
var done = "edited";
|
||||
var notDone = "editing";
|
||||
let done = "edited";
|
||||
let notDone = "editing";
|
||||
switch (elem) {
|
||||
case "multiselect_" + client:
|
||||
done = "edited groups of";
|
||||
@@ -465,15 +462,15 @@ function editClient() {
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
groups: groups,
|
||||
comment: comment,
|
||||
groups,
|
||||
comment,
|
||||
}),
|
||||
success: function (data) {
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
processGroupResult(data, "client", done, notDone);
|
||||
table.ajax.reload(null, false);
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
/* global apiFailure:false, utils:false, initTable:false, updateFtlInfo:false */
|
||||
|
||||
var groups = [];
|
||||
"use strict";
|
||||
|
||||
let groups = [];
|
||||
|
||||
function populateGroupSelect(selectEl) {
|
||||
if (selectEl.length === 0) {
|
||||
@@ -16,16 +18,13 @@ function populateGroupSelect(selectEl) {
|
||||
}
|
||||
|
||||
// Add all known groups
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var dataSub = "";
|
||||
if (!groups[i].enabled) {
|
||||
dataSub = 'data-subtext="(disabled)"';
|
||||
}
|
||||
for (const group of groups) {
|
||||
const dataSub = group.enabled ? "" : 'data-subtext="(disabled)"';
|
||||
|
||||
selectEl.append(
|
||||
$("<option " + dataSub + "/>")
|
||||
.val(groups[i].id)
|
||||
.text(groups[i].name)
|
||||
.val(group.id)
|
||||
.text(group.name)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,20 +41,20 @@ function getGroups(groupSelector) {
|
||||
url: document.body.dataset.apiurl + "/groups",
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
success(data) {
|
||||
groups = data.groups;
|
||||
|
||||
// Get all all <select> elements with the class "selectpicker"
|
||||
var groupSelector = $(".selectpicker");
|
||||
const groupSelector = $(".selectpicker");
|
||||
// Populate the groupSelector with the groups
|
||||
for (var i = 0; i < groupSelector.length; i++) {
|
||||
populateGroupSelect($(groupSelector[i]));
|
||||
for (const element of groupSelector) {
|
||||
populateGroupSelect($(element));
|
||||
}
|
||||
|
||||
// Actually load table contents
|
||||
initTable();
|
||||
},
|
||||
error: function (data) {
|
||||
error(data) {
|
||||
apiFailure(data);
|
||||
},
|
||||
});
|
||||
@@ -64,14 +63,15 @@ function getGroups(groupSelector) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function processGroupResult(data, type, done, notDone) {
|
||||
// Loop over data.processed.success and show toasts
|
||||
data.processed.success.forEach(function (item) {
|
||||
for (const item of data.processed.success) {
|
||||
utils.showAlert("success", "fas fa-pencil-alt", `Successfully ${done} ${type}`, item);
|
||||
});
|
||||
}
|
||||
|
||||
// Loop over errors and display them
|
||||
data.processed.errors.forEach(function (error) {
|
||||
for (const error of data.processed.errors) {
|
||||
console.log(error); // eslint-disable-line no-console
|
||||
utils.showAlert("error", "", `Error while ${notDone} ${type} ${error.item}`, error.error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
@@ -83,9 +83,9 @@ function delGroupItems(type, ids, table, listType = undefined) {
|
||||
|
||||
// use utils.hexDecode() to decode all clients
|
||||
let idstring = "";
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
ids[i].item = utils.hexDecode(ids[i].item);
|
||||
idstring += ids[i].item + ", ";
|
||||
for (const id of ids) {
|
||||
id.item = utils.hexDecode(id.item);
|
||||
idstring += id.item + ", ";
|
||||
}
|
||||
|
||||
// Remove last comma and space from idstring
|
||||
@@ -103,12 +103,12 @@ function delGroupItems(type, ids, table, listType = undefined) {
|
||||
utils.showAlert("info", "", "Deleting " + ids.length + " " + type + "...", idstring);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
url,
|
||||
data: JSON.stringify(ids),
|
||||
contentType: "application/json",
|
||||
method: "POST",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted " + type, idstring);
|
||||
table.ajax.reload(null, false);
|
||||
@@ -120,7 +120,7 @@ function delGroupItems(type, ids, table, listType = undefined) {
|
||||
// Update number of <type> items in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
.fail((data, exception) => {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while deleting " + type, data.responseText);
|
||||
|
||||
@@ -8,17 +8,19 @@
|
||||
/* global utils:false, groups:false, getGroups:false, updateFtlInfo:false, apiFailure:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
var GETDict = {};
|
||||
"use strict";
|
||||
|
||||
$(function () {
|
||||
let table;
|
||||
let GETDict = {};
|
||||
|
||||
$(() => {
|
||||
GETDict = utils.parseQueryString();
|
||||
|
||||
// Tabs: Domain/Regex handling
|
||||
// sync description fields, reset inactive inputs on tab change
|
||||
$('a[data-toggle="tab"]').on("shown.bs.tab", function () {
|
||||
var tabHref = $(this).attr("href");
|
||||
var val;
|
||||
const tabHref = $(this).attr("href");
|
||||
let val;
|
||||
if (tabHref === "#tab_domain") {
|
||||
val = $("#new_regex_comment").val();
|
||||
$("#new_domain_comment").val(val);
|
||||
@@ -37,8 +39,8 @@ $(function () {
|
||||
$("#add_deny, #add_allow").on("click", addDomain);
|
||||
|
||||
// Domain suggestion handling
|
||||
var suggestTimeout;
|
||||
$("#new_domain").on("input", function (e) {
|
||||
let suggestTimeout;
|
||||
$("#new_domain").on("input", e => {
|
||||
hideSuggestDomains();
|
||||
clearTimeout(suggestTimeout);
|
||||
suggestTimeout = setTimeout(showSuggestDomains, 1000, e.target.value);
|
||||
@@ -50,25 +52,25 @@ $(function () {
|
||||
|
||||
// Show a list of suggested domains based on the user's input
|
||||
function showSuggestDomains(value) {
|
||||
const newDomainEl = $("#new_domain");
|
||||
const suggestDomainEl = $("#suggest_domains");
|
||||
|
||||
function createButton(hostname) {
|
||||
// Purposefully omit 'btn' class to save space on padding
|
||||
return $('<button type="button" class="btn-link btn-block text-right">')
|
||||
.append($("<em>").text(hostname))
|
||||
.on("click", function () {
|
||||
.on("click", () => {
|
||||
hideSuggestDomains();
|
||||
newDomainEl.val(hostname);
|
||||
});
|
||||
}
|
||||
|
||||
var newDomainEl = $("#new_domain");
|
||||
var suggestDomainEl = $("#suggest_domains");
|
||||
|
||||
try {
|
||||
var parts = new URL(value).hostname.split(".");
|
||||
var table = $("<table>");
|
||||
const parts = new URL(value).hostname.split(".");
|
||||
const table = $("<table>");
|
||||
|
||||
for (var i = 0; i < parts.length - 1; ++i) {
|
||||
var hostname = parts.slice(i).join(".");
|
||||
for (let i = 0; i < parts.length - 1; ++i) {
|
||||
const hostname = parts.slice(i).join(".");
|
||||
|
||||
table.append(
|
||||
$("<tr>")
|
||||
@@ -77,7 +79,7 @@ function showSuggestDomains(value) {
|
||||
);
|
||||
}
|
||||
|
||||
suggestDomainEl.slideUp("fast", function () {
|
||||
suggestDomainEl.slideUp("fast", () => {
|
||||
suggestDomainEl.html(table);
|
||||
suggestDomainEl.slideDown("fast");
|
||||
});
|
||||
@@ -121,13 +123,13 @@ function initTable() {
|
||||
{
|
||||
targets: 1,
|
||||
className: "select-checkbox",
|
||||
render: function () {
|
||||
render() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: 3,
|
||||
render: function (data) {
|
||||
render(data) {
|
||||
return data.kind + "_" + data.type;
|
||||
},
|
||||
},
|
||||
@@ -136,20 +138,20 @@ function initTable() {
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
// Hide buttons if all domains were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
const hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
$('button[id^="deleteDomain_"]').on("click", deleteDomain);
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
var dataId = utils.hexEncode(data.domain) + "_" + data.type + "_" + data.kind;
|
||||
rowCallback(row, data) {
|
||||
const dataId = utils.hexEncode(data.domain) + "_" + data.type + "_" + data.kind;
|
||||
$(row).attr("data-id", dataId);
|
||||
// Tooltip for domain
|
||||
var tooltip =
|
||||
const tooltip =
|
||||
"Added: " +
|
||||
utils.datetime(data.date_added, false) +
|
||||
"\nLast modified: " +
|
||||
@@ -193,7 +195,7 @@ function initTable() {
|
||||
data.kind +
|
||||
"'>"
|
||||
);
|
||||
var typeEl = $("#type_" + dataId, row);
|
||||
const typeEl = $("#type_" + dataId, row);
|
||||
typeEl.on("change", editDomain);
|
||||
|
||||
// Initialize bootstrap-toggle for status field (enabled/disabled)
|
||||
@@ -204,7 +206,7 @@ function initTable() {
|
||||
(data.enabled ? " checked" : "") +
|
||||
">"
|
||||
);
|
||||
var statusEl = $("#enabled_" + dataId, row);
|
||||
const statusEl = $("#enabled_" + dataId, row);
|
||||
statusEl.bootstrapToggle({
|
||||
on: "Enabled",
|
||||
off: "Disabled",
|
||||
@@ -216,7 +218,7 @@ function initTable() {
|
||||
|
||||
// Comment field
|
||||
$("td:eq(4)", row).html('<input id="comment_' + dataId + '" class="form-control">');
|
||||
var commentEl = $("#comment_" + dataId, row);
|
||||
const commentEl = $("#comment_" + dataId, row);
|
||||
commentEl.val(data.comment);
|
||||
commentEl.on("change", editDomain);
|
||||
|
||||
@@ -225,18 +227,15 @@ function initTable() {
|
||||
$("td:eq(5)", row).append(
|
||||
'<select class="selectpicker" id="multiselect_' + dataId + '" multiple></select>'
|
||||
);
|
||||
var selectEl = $("#multiselect_" + dataId, row);
|
||||
const selectEl = $("#multiselect_" + dataId, row);
|
||||
// Add all known groups
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var dataSub = "";
|
||||
if (!groups[i].enabled) {
|
||||
dataSub = 'data-subtext="(disabled)"';
|
||||
}
|
||||
for (const group of groups) {
|
||||
const dataSub = group.enabled ? "" : 'data-subtext="(disabled)"';
|
||||
|
||||
selectEl.append(
|
||||
$("<option " + dataSub + "/>")
|
||||
.val(groups[i].id)
|
||||
.text(groups[i].name)
|
||||
.val(group.id)
|
||||
.text(group.name)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -246,26 +245,26 @@ function initTable() {
|
||||
const applyBtn = "#btn_apply_" + dataId;
|
||||
selectEl
|
||||
// fix dropdown if it would stick out right of the viewport
|
||||
.on("show.bs.select", function () {
|
||||
var winWidth = $(globalThis).width();
|
||||
var dropdownEl = $("body > .bootstrap-select.dropdown");
|
||||
.on("show.bs.select", () => {
|
||||
const winWidth = $(globalThis).width();
|
||||
const dropdownEl = $("body > .bootstrap-select.dropdown");
|
||||
if (dropdownEl.length > 0) {
|
||||
dropdownEl.removeClass("align-right");
|
||||
var width = dropdownEl.width();
|
||||
var left = dropdownEl.offset().left;
|
||||
const width = dropdownEl.width();
|
||||
const left = dropdownEl.offset().left;
|
||||
if (left + width > winWidth) {
|
||||
dropdownEl.addClass("align-right");
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("changed.bs.select", function () {
|
||||
.on("changed.bs.select", () => {
|
||||
// enable Apply button if changes were made to the drop-down menu
|
||||
// and have it call editDomain() on click
|
||||
if ($(applyBtn).prop("disabled")) {
|
||||
$(applyBtn)
|
||||
.addClass("btn-success")
|
||||
.prop("disabled", false)
|
||||
.on("click", function () {
|
||||
.on("click", () => {
|
||||
editDomain.call(selectEl);
|
||||
});
|
||||
}
|
||||
@@ -289,12 +288,12 @@ function initTable() {
|
||||
);
|
||||
|
||||
// Highlight row (if url parameter "domainid=" is used)
|
||||
if ("domainid" in GETDict && data.id === parseInt(GETDict.domainid, 10)) {
|
||||
if ("domainid" in GETDict && data.id === Number.parseInt(GETDict.domainid, 10)) {
|
||||
$(row).find("td").addClass("highlight");
|
||||
}
|
||||
|
||||
// Add delete domain button
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteDomain_' +
|
||||
dataId +
|
||||
'" data-id="' +
|
||||
@@ -314,7 +313,7 @@ function initTable() {
|
||||
text: '<span class="far fa-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectAll",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -322,7 +321,7 @@ function initTable() {
|
||||
text: '<span class="far fa-plus-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectMore",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -336,9 +335,9 @@ function initTable() {
|
||||
text: '<span class="far fa-trash-alt"></span>',
|
||||
titleAttr: "Delete Selected",
|
||||
className: "btn-sm datatable-bt deleteSelected",
|
||||
action: function () {
|
||||
action() {
|
||||
// For each ".selected" row ...
|
||||
var ids = [];
|
||||
const ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push($(this).attr("data-id"));
|
||||
@@ -360,11 +359,11 @@ function initTable() {
|
||||
],
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("groups-domains-table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback("groups-domains-table");
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback("groups-domains-table");
|
||||
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
@@ -376,21 +375,21 @@ function initTable() {
|
||||
// Apply loaded state to table
|
||||
return data;
|
||||
},
|
||||
initComplete: function () {
|
||||
initComplete() {
|
||||
if ("domainid" in GETDict) {
|
||||
var pos = table
|
||||
const pos = table
|
||||
.column(0, { order: "current" })
|
||||
.data()
|
||||
.indexOf(parseInt(GETDict.domainid, 10));
|
||||
if (pos >= 0) {
|
||||
var page = Math.floor(pos / table.page.info().length);
|
||||
.indexOf(Number.parseInt(GETDict.domainid, 10));
|
||||
if (pos !== -1) {
|
||||
const page = Math.floor(pos / table.page.info().length);
|
||||
table.page(page).draw(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
// Disable autocorrect in the search box
|
||||
var input = document.querySelector("input[type=search]");
|
||||
const input = document.querySelector("input[type=search]");
|
||||
if (input !== null) {
|
||||
input.setAttribute("autocomplete", "off");
|
||||
input.setAttribute("autocorrect", "off");
|
||||
@@ -398,12 +397,12 @@ function initTable() {
|
||||
input.setAttribute("spellcheck", false);
|
||||
}
|
||||
|
||||
table.on("init select deselect", function () {
|
||||
table.on("init select deselect", () => {
|
||||
utils.changeBulkDeleteStates(table);
|
||||
});
|
||||
|
||||
table.on("order.dt", function () {
|
||||
var order = table.order();
|
||||
table.on("order.dt", () => {
|
||||
const order = table.order();
|
||||
if (order[0][0] !== 0 || order[0][1] !== "asc") {
|
||||
$("#resetButton").removeClass("hidden");
|
||||
} else {
|
||||
@@ -411,28 +410,28 @@ function initTable() {
|
||||
}
|
||||
});
|
||||
|
||||
$("#resetButton").on("click", function () {
|
||||
$("#resetButton").on("click", () => {
|
||||
table.order([[0, "asc"]]).draw();
|
||||
$("#resetButton").addClass("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
// Enable "filter by type" functionality, using checkboxes
|
||||
$.fn.dataTable.ext.search.push(function (settings, searchData, index, rowData) {
|
||||
var types = $(".filter_types input:checkbox:checked")
|
||||
$.fn.dataTable.ext.search.push((settings, searchData, index, rowData) => {
|
||||
const types = $(".filter_types input:checkbox:checked")
|
||||
.map(function () {
|
||||
return this.value;
|
||||
})
|
||||
.get();
|
||||
|
||||
const typeStr = rowData.type + "/" + rowData.kind;
|
||||
if (types.indexOf(typeStr) !== -1) {
|
||||
if (types.includes(typeStr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
$(".filter_types input:checkbox").on("change", function () {
|
||||
$(".filter_types input:checkbox").on("change", () => {
|
||||
table.draw();
|
||||
});
|
||||
|
||||
@@ -446,9 +445,9 @@ function deleteDomain() {
|
||||
|
||||
function deleteDomains(encodedIds) {
|
||||
const decodedIds = [];
|
||||
for (let i = 0; i < encodedIds.length; i++) {
|
||||
for (const [i, encodedId] of encodedIds.entries()) {
|
||||
// Decode domain, type, and kind and add to array
|
||||
const parts = encodedIds[i].split("_");
|
||||
const parts = encodedId.split("_");
|
||||
decodedIds[i] = {
|
||||
item: parts[0],
|
||||
type: parts[1],
|
||||
@@ -466,7 +465,10 @@ function addDomain() {
|
||||
const wildcardChecked = wildcardEl.prop("checked");
|
||||
|
||||
// current tab's inputs
|
||||
var kind, domainEl, commentEl, groupEl;
|
||||
let kind;
|
||||
let domainEl;
|
||||
let commentEl;
|
||||
let groupEl;
|
||||
if (tabHref === "#tab_domain") {
|
||||
kind = "exact";
|
||||
domainEl = $("#new_domain");
|
||||
@@ -485,11 +487,9 @@ function addDomain() {
|
||||
|
||||
// Check if the user wants to add multiple domains (space or newline separated)
|
||||
// If so, split the input and store it in an array
|
||||
var domains = domainEl.val().split(/\s+/);
|
||||
let domains = domainEl.val().split(/\s+/);
|
||||
// Remove empty elements
|
||||
domains = domains.filter(function (el) {
|
||||
return el !== "";
|
||||
});
|
||||
domains = domains.filter(el => el !== "");
|
||||
const domainStr = JSON.stringify(domains);
|
||||
|
||||
utils.disableAll();
|
||||
@@ -503,7 +503,7 @@ function addDomain() {
|
||||
|
||||
// Check if the wildcard checkbox was marked and transform the domains into regex
|
||||
if (kind === "exact" && wildcardChecked) {
|
||||
for (var i = 0; i < domains.length; i++) {
|
||||
for (let i = 0; i < domains.length; i++) {
|
||||
// Strip leading "*." if specified by user in wildcard mode
|
||||
if (domains[i].startsWith("*.")) domains[i] = domains[i].substr(2);
|
||||
|
||||
@@ -525,12 +525,12 @@ function addDomain() {
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
domain: domains,
|
||||
comment: comment,
|
||||
type: type,
|
||||
kind: kind,
|
||||
comment,
|
||||
type,
|
||||
kind,
|
||||
groups: group,
|
||||
}),
|
||||
success: function (data) {
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
utils.listsAlert("domain", domains, data);
|
||||
$("#new_domain").val("");
|
||||
@@ -543,7 +543,7 @@ function addDomain() {
|
||||
// Update number of groups in the sidebar
|
||||
updateFtlInfo();
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while adding new domain", data.responseText);
|
||||
@@ -569,8 +569,8 @@ function editDomain() {
|
||||
const oldType = oldTypeStr.split("/")[0];
|
||||
const oldKind = oldTypeStr.split("/")[1];
|
||||
|
||||
var done = "edited";
|
||||
var notDone = "editing";
|
||||
let done = "edited";
|
||||
let notDone = "editing";
|
||||
switch (elem) {
|
||||
case "enabled_" + domain:
|
||||
if (!enabled) {
|
||||
@@ -618,18 +618,18 @@ function editDomain() {
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
groups: groups,
|
||||
comment: comment,
|
||||
enabled: enabled,
|
||||
groups,
|
||||
comment,
|
||||
enabled,
|
||||
type: oldType,
|
||||
kind: oldKind,
|
||||
}),
|
||||
success: function (data) {
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
processGroupResult(data, "domain", done, notDone);
|
||||
table.ajax.reload(null, false);
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
/* global utils:false, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
var GETDict = {};
|
||||
"use strict";
|
||||
|
||||
$(function () {
|
||||
let table;
|
||||
let GETDict = {};
|
||||
|
||||
$(() => {
|
||||
GETDict = utils.parseQueryString();
|
||||
|
||||
$("#btnAddAllow").on("click", { type: "allow" }, addList);
|
||||
@@ -23,33 +25,35 @@ $(function () {
|
||||
|
||||
function format(data) {
|
||||
// Generate human-friendly status string
|
||||
var statusText = setStatusText(data, true);
|
||||
var numbers = true;
|
||||
const statusText = setStatusText(data, true);
|
||||
let numbers = true;
|
||||
if (data.status === 0 || data.status === 4) {
|
||||
numbers = false;
|
||||
}
|
||||
|
||||
// Compile extra info for displaying
|
||||
var dateAddedISO = utils.datetime(data.date_added, false),
|
||||
dateModifiedISO = utils.datetime(data.date_modified, false),
|
||||
dateUpdated =
|
||||
data.date_updated > 0
|
||||
? utils.datetimeRelative(data.date_updated) +
|
||||
" (" +
|
||||
utils.datetime(data.date_updated, false) +
|
||||
")"
|
||||
: "N/A",
|
||||
numberOfEntries =
|
||||
(data.number !== null && numbers === true
|
||||
? parseInt(data.number, 10).toLocaleString()
|
||||
: "N/A") +
|
||||
(data.abp_entries !== null && parseInt(data.abp_entries, 10) > 0 && numbers === true
|
||||
? " (out of which " + parseInt(data.abp_entries, 10).toLocaleString() + " are in ABP-style)"
|
||||
: ""),
|
||||
nonDomains =
|
||||
data.invalid_domains !== null && numbers === true
|
||||
? parseInt(data.invalid_domains, 10).toLocaleString()
|
||||
: "N/A";
|
||||
const dateAddedISO = utils.datetime(data.date_added, false);
|
||||
const dateModifiedISO = utils.datetime(data.date_modified, false);
|
||||
const dateUpdated =
|
||||
data.date_updated > 0
|
||||
? utils.datetimeRelative(data.date_updated) +
|
||||
" (" +
|
||||
utils.datetime(data.date_updated, false) +
|
||||
")"
|
||||
: "N/A";
|
||||
const numberOfEntries =
|
||||
(data.number !== null && numbers === true
|
||||
? Number.parseInt(data.number, 10).toLocaleString()
|
||||
: "N/A") +
|
||||
(data.abp_entries !== null && Number.parseInt(data.abp_entries, 10) > 0 && numbers === true
|
||||
? " (out of which " +
|
||||
Number.parseInt(data.abp_entries, 10).toLocaleString() +
|
||||
" are in ABP-style)"
|
||||
: "");
|
||||
const nonDomains =
|
||||
data.invalid_domains !== null && numbers === true
|
||||
? Number.parseInt(data.invalid_domains, 10).toLocaleString()
|
||||
: "N/A";
|
||||
|
||||
return `<table>
|
||||
<tr class="dataTables-child">
|
||||
@@ -83,9 +87,9 @@ function format(data) {
|
||||
|
||||
// Define the status icon element
|
||||
function setStatusIcon(data) {
|
||||
var statusCode = parseInt(data.status, 10),
|
||||
statusTitle = setStatusText(data) + "\nClick for details about this list",
|
||||
statusIcon;
|
||||
const statusCode = Number.parseInt(data.status, 10);
|
||||
const statusTitle = setStatusText(data) + "\nClick for details about this list";
|
||||
let statusIcon;
|
||||
|
||||
switch (statusCode) {
|
||||
case 1:
|
||||
@@ -110,10 +114,10 @@ function setStatusIcon(data) {
|
||||
|
||||
// Define human-friendly status string
|
||||
function setStatusText(data, showdetails = false) {
|
||||
var statusText = "Unknown",
|
||||
statusDetails = "";
|
||||
let statusText = "Unknown";
|
||||
let statusDetails = "";
|
||||
if (data.status !== null) {
|
||||
switch (parseInt(data.status, 10)) {
|
||||
switch (Number.parseInt(data.status, 10)) {
|
||||
case 0:
|
||||
statusText =
|
||||
data.enabled === 0
|
||||
@@ -140,7 +144,8 @@ function setStatusText(data, showdetails = false) {
|
||||
|
||||
default:
|
||||
statusText = "Unknown";
|
||||
statusDetails = ' (<span class="list-status-0">' + parseInt(data.status, 10) + "</span>)";
|
||||
statusDetails =
|
||||
' (<span class="list-status-0">' + Number.parseInt(data.status, 10) + "</span>)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -152,8 +157,8 @@ function setStatusText(data, showdetails = false) {
|
||||
function setTypeIcon(type) {
|
||||
//Add red ban icon if data["type"] is "block"
|
||||
//Add green check icon if data["type"] is "allow"
|
||||
let iconClass = "fa-question text-orange",
|
||||
title = "This list is of unknown type";
|
||||
let iconClass = "fa-question text-orange";
|
||||
let title = "This list is of unknown type";
|
||||
if (type === "block") {
|
||||
iconClass = "fa-ban text-red";
|
||||
title = "This is a blocklist";
|
||||
@@ -190,7 +195,7 @@ function initTable() {
|
||||
{
|
||||
targets: 1,
|
||||
className: "select-checkbox",
|
||||
render: function () {
|
||||
render() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
@@ -199,26 +204,26 @@ function initTable() {
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
// Hide buttons if all lists were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
const hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
$('button[id^="deleteList_"]').on("click", deleteList);
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
var dataId = utils.hexEncode(data.address + "_" + data.type);
|
||||
rowCallback(row, data) {
|
||||
const dataId = utils.hexEncode(data.address + "_" + data.type);
|
||||
$(row).attr("data-id", dataId);
|
||||
$(row).attr("data-address", utils.hexEncode(data.address));
|
||||
$(row).attr("data-type", data.type);
|
||||
|
||||
var statusCode = 0;
|
||||
let statusCode = 0;
|
||||
// If there is no status or the list is disabled, we keep
|
||||
// status 0 (== unknown)
|
||||
if (data.status !== null && data.enabled) {
|
||||
statusCode = parseInt(data.status, 10);
|
||||
statusCode = Number.parseInt(data.status, 10);
|
||||
}
|
||||
|
||||
$("td:eq(1)", row).addClass("list-status-" + statusCode);
|
||||
@@ -256,7 +261,7 @@ function initTable() {
|
||||
(data.enabled ? " checked" : "") +
|
||||
">"
|
||||
);
|
||||
var statusEl = $("#enabled_" + dataId, row);
|
||||
const statusEl = $("#enabled_" + dataId, row);
|
||||
statusEl.bootstrapToggle({
|
||||
on: "Enabled",
|
||||
off: "Disabled",
|
||||
@@ -267,7 +272,7 @@ function initTable() {
|
||||
statusEl.on("change", editList);
|
||||
|
||||
$("td:eq(5)", row).html('<input id="comment_' + dataId + '" class="form-control">');
|
||||
var commentEl = $("#comment_" + dataId, row);
|
||||
const commentEl = $("#comment_" + dataId, row);
|
||||
commentEl.val(data.comment);
|
||||
commentEl.on("change", editList);
|
||||
|
||||
@@ -275,47 +280,44 @@ function initTable() {
|
||||
$("td:eq(6)", row).append(
|
||||
'<select class="selectpicker" id="multiselect_' + dataId + '" multiple></select>'
|
||||
);
|
||||
var selectEl = $("#multiselect_" + dataId, row);
|
||||
const selectEl = $("#multiselect_" + dataId, row);
|
||||
// Add all known groups
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var dataSub = "";
|
||||
if (!groups[i].enabled) {
|
||||
dataSub = 'data-subtext="(disabled)"';
|
||||
}
|
||||
for (const group of groups) {
|
||||
const dataSub = group.enabled ? "" : 'data-subtext="(disabled)"';
|
||||
|
||||
selectEl.append(
|
||||
$("<option " + dataSub + "/>")
|
||||
.val(groups[i].id)
|
||||
.text(groups[i].name)
|
||||
.val(group.id)
|
||||
.text(group.name)
|
||||
);
|
||||
}
|
||||
|
||||
var applyBtn = "#btn_apply_" + dataId;
|
||||
const applyBtn = "#btn_apply_" + dataId;
|
||||
|
||||
// Select assigned groups
|
||||
selectEl.val(data.groups);
|
||||
// Initialize bootstrap-select
|
||||
selectEl
|
||||
// fix dropdown if it would stick out right of the viewport
|
||||
.on("show.bs.select", function () {
|
||||
var winWidth = $(globalThis).width();
|
||||
var dropdownEl = $("body > .bootstrap-select.dropdown");
|
||||
.on("show.bs.select", () => {
|
||||
const winWidth = $(globalThis).width();
|
||||
const dropdownEl = $("body > .bootstrap-select.dropdown");
|
||||
if (dropdownEl.length > 0) {
|
||||
dropdownEl.removeClass("align-right");
|
||||
var width = dropdownEl.width();
|
||||
var left = dropdownEl.offset().left;
|
||||
const width = dropdownEl.width();
|
||||
const left = dropdownEl.offset().left;
|
||||
if (left + width > winWidth) {
|
||||
dropdownEl.addClass("align-right");
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("changed.bs.select", function () {
|
||||
.on("changed.bs.select", () => {
|
||||
// enable Apply button
|
||||
if ($(applyBtn).prop("disabled")) {
|
||||
$(applyBtn)
|
||||
.addClass("btn-success")
|
||||
.prop("disabled", false)
|
||||
.on("click", function () {
|
||||
.on("click", () => {
|
||||
editList.call(selectEl);
|
||||
});
|
||||
}
|
||||
@@ -337,11 +339,11 @@ function initTable() {
|
||||
);
|
||||
|
||||
// Highlight row (if url parameter "listid=" is used)
|
||||
if ("listid" in GETDict && data.id === parseInt(GETDict.listid, 10)) {
|
||||
if ("listid" in GETDict && data.id === Number.parseInt(GETDict.listid, 10)) {
|
||||
$(row).find("td").addClass("highlight");
|
||||
}
|
||||
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteList_' +
|
||||
dataId +
|
||||
'" data-id="' +
|
||||
@@ -371,7 +373,7 @@ function initTable() {
|
||||
text: '<span class="far fa-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectAll",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -379,7 +381,7 @@ function initTable() {
|
||||
text: '<span class="far fa-plus-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectMore",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -393,9 +395,9 @@ function initTable() {
|
||||
text: '<span class="far fa-trash-alt"></span>',
|
||||
titleAttr: "Delete Selected",
|
||||
className: "btn-sm datatable-bt deleteSelected",
|
||||
action: function () {
|
||||
action() {
|
||||
// For each ".selected" row ...
|
||||
var ids = [];
|
||||
const ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push({ item: $(this).attr("data-address"), type: $(this).attr("data-type") });
|
||||
@@ -407,11 +409,11 @@ function initTable() {
|
||||
],
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("groups-lists-table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback("groups-lists-table");
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback("groups-lists-table");
|
||||
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
@@ -423,26 +425,26 @@ function initTable() {
|
||||
// Apply loaded state to table
|
||||
return data;
|
||||
},
|
||||
initComplete: function () {
|
||||
initComplete() {
|
||||
if ("listid" in GETDict) {
|
||||
var pos = table
|
||||
const pos = table
|
||||
.column(0, { order: "current" })
|
||||
.data()
|
||||
.indexOf(parseInt(GETDict.listid, 10));
|
||||
if (pos >= 0) {
|
||||
var page = Math.floor(pos / table.page.info().length);
|
||||
.indexOf(Number.parseInt(GETDict.listid, 10));
|
||||
if (pos !== -1) {
|
||||
const page = Math.floor(pos / table.page.info().length);
|
||||
table.page(page).draw(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
table.on("init select deselect", function () {
|
||||
table.on("init select deselect", () => {
|
||||
utils.changeBulkDeleteStates(table);
|
||||
});
|
||||
|
||||
table.on("order.dt", function () {
|
||||
var order = table.order();
|
||||
table.on("order.dt", () => {
|
||||
const order = table.order();
|
||||
if (order[0][0] !== 0 || order[0][1] !== "asc") {
|
||||
$("#resetButton").removeClass("hidden");
|
||||
} else {
|
||||
@@ -450,15 +452,15 @@ function initTable() {
|
||||
}
|
||||
});
|
||||
|
||||
$("#resetButton").on("click", function () {
|
||||
$("#resetButton").on("click", () => {
|
||||
table.order([[0, "asc"]]).draw();
|
||||
$("#resetButton").addClass("hidden");
|
||||
});
|
||||
|
||||
// Add event listener for opening and closing details
|
||||
$("#listsTable tbody").on("click", "td.details-control", function () {
|
||||
var tr = $(this).closest("tr");
|
||||
var row = table.row(tr);
|
||||
const tr = $(this).closest("tr");
|
||||
const row = table.row(tr);
|
||||
|
||||
if (row.child.isShown()) {
|
||||
// This row is already open - close it
|
||||
@@ -472,7 +474,7 @@ function initTable() {
|
||||
});
|
||||
|
||||
// Disable autocorrect in the search box
|
||||
var input = document.querySelector("input[type=search]");
|
||||
const input = document.querySelector("input[type=search]");
|
||||
if (input !== null) {
|
||||
input.setAttribute("autocomplete", "off");
|
||||
input.setAttribute("autocorrect", "off");
|
||||
@@ -499,13 +501,11 @@ function addList(event) {
|
||||
|
||||
// Check if the user wants to add multiple domains (space or newline separated)
|
||||
// If so, split the input and store it in an array
|
||||
var addresses = $("#new_address")
|
||||
let addresses = $("#new_address")
|
||||
.val()
|
||||
.split(/[\s,]+/);
|
||||
// Remove empty elements
|
||||
addresses = addresses.filter(function (el) {
|
||||
return el !== "";
|
||||
});
|
||||
addresses = addresses.filter(el => el !== "");
|
||||
const addressestr = JSON.stringify(addresses);
|
||||
|
||||
utils.disableAll();
|
||||
@@ -524,8 +524,8 @@ function addList(event) {
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({ address: addresses, comment: comment, type: type, groups: group }),
|
||||
success: function (data) {
|
||||
data: JSON.stringify({ address: addresses, comment, type, groups: group }),
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
utils.listsAlert(type + "list", addresses, data);
|
||||
$("#new_address").val("");
|
||||
@@ -536,7 +536,7 @@ function addList(event) {
|
||||
// Update number of groups in the sidebar
|
||||
updateFtlInfo();
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while adding new " + type + "list", data.responseText);
|
||||
@@ -559,8 +559,8 @@ function editList() {
|
||||
.val()
|
||||
.map(Number);
|
||||
|
||||
var done = "edited";
|
||||
var notDone = "editing";
|
||||
let done = "edited";
|
||||
let notDone = "editing";
|
||||
switch (elem) {
|
||||
case "enabled_" + dataId:
|
||||
if (!enabled) {
|
||||
@@ -594,17 +594,17 @@ function editList() {
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
groups: groups,
|
||||
comment: comment,
|
||||
enabled: enabled,
|
||||
type: type,
|
||||
groups,
|
||||
comment,
|
||||
enabled,
|
||||
type,
|
||||
}),
|
||||
success: function (data) {
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
processGroupResult(data, type + "list", done, notDone);
|
||||
table.ajax.reload(null, false);
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
/* global utils:false, apiFailure:false, updateFtlInfo:false, processGroupResult:false, delGroupItems:false */
|
||||
|
||||
var table;
|
||||
"use strict";
|
||||
|
||||
let table;
|
||||
|
||||
function handleAjaxError(xhr, textStatus) {
|
||||
if (textStatus === "timeout") {
|
||||
@@ -20,7 +22,7 @@ function handleAjaxError(xhr, textStatus) {
|
||||
table.draw();
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
$("#btnAdd").on("click", addGroup);
|
||||
|
||||
table = $("#groupsTable").DataTable({
|
||||
@@ -44,7 +46,7 @@ $(function () {
|
||||
{
|
||||
targets: 1,
|
||||
className: "select-checkbox",
|
||||
render: function () {
|
||||
render() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
@@ -53,18 +55,18 @@ $(function () {
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
// Hide buttons if all groups were deleted
|
||||
// if there is one row, it's the default group
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 1;
|
||||
const hasRows = this.api().rows({ filter: "applied" }).data().length > 1;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
$('button[id^="deleteGroup_"]').on("click", deleteGroup);
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
var dataId = utils.hexEncode(data.name);
|
||||
rowCallback(row, data) {
|
||||
const dataId = utils.hexEncode(data.name);
|
||||
$(row).attr("data-id", dataId);
|
||||
var tooltip =
|
||||
const tooltip =
|
||||
"Added: " +
|
||||
utils.datetime(data.date_added, false) +
|
||||
"\nLast modified: " +
|
||||
@@ -74,7 +76,7 @@ $(function () {
|
||||
$("td:eq(1)", row).html(
|
||||
'<input id="name_' + dataId + '" title="' + tooltip + '" class="form-control">'
|
||||
);
|
||||
var nameEl = $("#name_" + dataId, row);
|
||||
const nameEl = $("#name_" + dataId, row);
|
||||
nameEl.val(data.name);
|
||||
nameEl.on("change", editGroup);
|
||||
|
||||
@@ -85,7 +87,7 @@ $(function () {
|
||||
(data.enabled ? " checked" : "") +
|
||||
">"
|
||||
);
|
||||
var enabledEl = $("#enabled_" + dataId, row);
|
||||
const enabledEl = $("#enabled_" + dataId, row);
|
||||
enabledEl.bootstrapToggle({
|
||||
on: "Enabled",
|
||||
off: "Disabled",
|
||||
@@ -96,15 +98,15 @@ $(function () {
|
||||
enabledEl.on("change", editGroup);
|
||||
|
||||
$("td:eq(3)", row).html('<input id="comment_' + dataId + '" class="form-control">');
|
||||
var comment = data.comment !== null ? data.comment : "";
|
||||
var commentEl = $("#comment_" + dataId, row);
|
||||
const comment = data.comment !== null ? data.comment : "";
|
||||
const commentEl = $("#comment_" + dataId, row);
|
||||
commentEl.val(comment);
|
||||
commentEl.on("change", editGroup);
|
||||
|
||||
$("td:eq(4)", row).empty();
|
||||
// Show delete button for all but the default group
|
||||
if (data.id !== 0) {
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteGroup_' +
|
||||
dataId +
|
||||
'" data-id="' +
|
||||
@@ -125,7 +127,7 @@ $(function () {
|
||||
text: '<span class="far fa-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectAll",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -133,7 +135,7 @@ $(function () {
|
||||
text: '<span class="far fa-plus-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectMore",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -147,9 +149,9 @@ $(function () {
|
||||
text: '<span class="far fa-trash-alt"></span>',
|
||||
titleAttr: "Delete Selected",
|
||||
className: "btn-sm datatable-bt deleteSelected",
|
||||
action: function () {
|
||||
action() {
|
||||
// For each ".selected" row ...
|
||||
var ids = [];
|
||||
const ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push({ item: $(this).attr("data-id") });
|
||||
@@ -171,11 +173,11 @@ $(function () {
|
||||
],
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("groups-table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback("groups-table");
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback("groups-table");
|
||||
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
@@ -190,7 +192,7 @@ $(function () {
|
||||
});
|
||||
|
||||
// Disable autocorrect in the search box
|
||||
var input = document.querySelector("input[type=search]");
|
||||
const input = document.querySelector("input[type=search]");
|
||||
if (input !== null) {
|
||||
input.setAttribute("autocomplete", "off");
|
||||
input.setAttribute("autocorrect", "off");
|
||||
@@ -198,8 +200,9 @@ $(function () {
|
||||
input.setAttribute("spellcheck", false);
|
||||
}
|
||||
|
||||
table.on("init select deselect", function () {
|
||||
table.on("init select deselect", () => {
|
||||
// if the Default group is selected, undo the selection of it
|
||||
// eslint-disable-next-line unicorn/prefer-includes
|
||||
if (table.rows({ selected: true }).data().pluck("id").indexOf(0) !== -1) {
|
||||
table.rows(0).deselect();
|
||||
}
|
||||
@@ -207,8 +210,8 @@ $(function () {
|
||||
utils.changeBulkDeleteStates(table);
|
||||
});
|
||||
|
||||
table.on("order.dt", function () {
|
||||
var order = table.order();
|
||||
table.on("order.dt", () => {
|
||||
const order = table.order();
|
||||
if (order[0][0] !== 0 || order[0][1] !== "asc") {
|
||||
$("#resetButton").removeClass("hidden");
|
||||
} else {
|
||||
@@ -216,7 +219,7 @@ $(function () {
|
||||
}
|
||||
});
|
||||
|
||||
$("#resetButton").on("click", function () {
|
||||
$("#resetButton").on("click", () => {
|
||||
table.order([[0, "asc"]]).draw();
|
||||
$("#resetButton").addClass("hidden");
|
||||
});
|
||||
@@ -237,17 +240,13 @@ function addGroup() {
|
||||
// Check if the user wants to add multiple groups (space or newline separated)
|
||||
// If so, split the input and store it in an array, however, do not split
|
||||
// group names enclosed in quotes
|
||||
var names = utils
|
||||
let names = utils
|
||||
.escapeHtml($("#new_name"))
|
||||
.val()
|
||||
.match(/(?:[^\s"]+|"[^"]*")+/g)
|
||||
.map(function (name) {
|
||||
return name.replaceAll(/(^"|"$)/g, "");
|
||||
});
|
||||
.map(name => name.replaceAll(/(^"|"$)/g, ""));
|
||||
// Remove empty elements
|
||||
names = names.filter(function (el) {
|
||||
return el !== "";
|
||||
});
|
||||
names = names.filter(el => el !== "");
|
||||
const groupStr = JSON.stringify(names);
|
||||
|
||||
utils.disableAll();
|
||||
@@ -268,10 +267,10 @@ function addGroup() {
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
name: names,
|
||||
comment: comment,
|
||||
comment,
|
||||
enabled: true,
|
||||
}),
|
||||
success: function (data) {
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
utils.listsAlert("group", names, data);
|
||||
$("#new_name").val("");
|
||||
@@ -282,7 +281,7 @@ function addGroup() {
|
||||
// Update number of groups in the sidebar
|
||||
updateFtlInfo();
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while adding new group", data.responseText);
|
||||
@@ -300,8 +299,8 @@ function editGroup() {
|
||||
const enabled = tr.find("#enabled_" + id).is(":checked");
|
||||
const comment = tr.find("#comment_" + id).val();
|
||||
|
||||
var done = "edited";
|
||||
var notDone = "editing";
|
||||
let done = "edited";
|
||||
let notDone = "editing";
|
||||
switch (elem) {
|
||||
case "enabled_" + id:
|
||||
if (!enabled) {
|
||||
@@ -335,16 +334,16 @@ function editGroup() {
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
name: name,
|
||||
comment: comment,
|
||||
enabled: enabled,
|
||||
name,
|
||||
comment,
|
||||
enabled,
|
||||
}),
|
||||
success: function (data) {
|
||||
success(data) {
|
||||
utils.enableAll();
|
||||
processGroupResult(data, "group", done, notDone);
|
||||
table.ajax.reload(null, false);
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
|
||||
/* global utils:false, Chart:false, apiFailure:false, THEME_COLORS:false, customTooltips:false, htmlLegendPlugin:false,doughnutTooltip:false, ChartDeferred:false, REFRESH_INTERVAL: false, updateQueryFrequency: false */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Define global variables
|
||||
var timeLineChart, clientsChart;
|
||||
var queryTypePieChart, forwardDestinationPieChart;
|
||||
let timeLineChart;
|
||||
let clientsChart;
|
||||
let queryTypePieChart;
|
||||
let forwardDestinationPieChart;
|
||||
|
||||
// Register the ChartDeferred plugin to all charts:
|
||||
Chart.register(ChartDeferred);
|
||||
@@ -20,9 +24,9 @@ Chart.defaults.set("plugins.deferred", {
|
||||
|
||||
// Functions to update data in page
|
||||
|
||||
var failures = 0;
|
||||
let failures = 0;
|
||||
function updateQueriesOverTime() {
|
||||
$.getJSON(document.body.dataset.apiurl + "/history", function (data) {
|
||||
$.getJSON(document.body.dataset.apiurl + "/history", data => {
|
||||
// Remove graph if there are no results (e.g. new
|
||||
// installation or privacy mode enabled)
|
||||
if (jQuery.isEmptyObject(data.history)) {
|
||||
@@ -34,20 +38,20 @@ function updateQueriesOverTime() {
|
||||
timeLineChart.data.labels = [];
|
||||
timeLineChart.data.datasets = [];
|
||||
|
||||
var labels = [
|
||||
const labels = [
|
||||
"Other DNS Queries",
|
||||
"Blocked DNS Queries",
|
||||
"Cached DNS Queries",
|
||||
"Forwarded DNS Queries",
|
||||
];
|
||||
var cachedColor = utils.getCSSval("queries-cached", "background-color");
|
||||
var blockedColor = utils.getCSSval("queries-blocked", "background-color");
|
||||
var permittedColor = utils.getCSSval("queries-permitted", "background-color");
|
||||
var otherColor = utils.getCSSval("queries-other", "background-color");
|
||||
var colors = [otherColor, blockedColor, cachedColor, permittedColor];
|
||||
const cachedColor = utils.getCSSval("queries-cached", "background-color");
|
||||
const blockedColor = utils.getCSSval("queries-blocked", "background-color");
|
||||
const permittedColor = utils.getCSSval("queries-permitted", "background-color");
|
||||
const otherColor = utils.getCSSval("queries-other", "background-color");
|
||||
const colors = [otherColor, blockedColor, cachedColor, permittedColor];
|
||||
|
||||
// Collect values and colors, and labels
|
||||
for (var i = 0; i < labels.length; i++) {
|
||||
for (const [i, label] of labels.entries()) {
|
||||
timeLineChart.data.datasets.push({
|
||||
data: [],
|
||||
// If we ran out of colors, make a random one
|
||||
@@ -55,57 +59,57 @@ function updateQueriesOverTime() {
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
label: labels[i],
|
||||
label,
|
||||
cubicInterpolationMode: "monotone",
|
||||
});
|
||||
}
|
||||
|
||||
// Add data for each dataset that is available
|
||||
data.history.forEach(function (item) {
|
||||
var timestamp = new Date(1000 * parseInt(item.timestamp, 10));
|
||||
for (const item of data.history) {
|
||||
const timestamp = new Date(1000 * Number.parseInt(item.timestamp, 10));
|
||||
|
||||
timeLineChart.data.labels.push(timestamp);
|
||||
var other = item.total - (item.blocked + item.cached + item.forwarded);
|
||||
const other = item.total - (item.blocked + item.cached + item.forwarded);
|
||||
timeLineChart.data.datasets[0].data.push(other);
|
||||
timeLineChart.data.datasets[1].data.push(item.blocked);
|
||||
timeLineChart.data.datasets[2].data.push(item.cached);
|
||||
timeLineChart.data.datasets[3].data.push(item.forwarded);
|
||||
});
|
||||
}
|
||||
|
||||
$("#queries-over-time .overlay").hide();
|
||||
timeLineChart.update();
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
failures = 0;
|
||||
utils.setTimer(updateQueriesOverTime, REFRESH_INTERVAL.history);
|
||||
})
|
||||
.fail(function () {
|
||||
.fail(() => {
|
||||
failures++;
|
||||
if (failures < 5) {
|
||||
// Try again ´only if this has not failed more than five times in a row
|
||||
utils.setTimer(updateQueriesOverTime, 0.1 * REFRESH_INTERVAL.history);
|
||||
}
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
function updateQueryTypesPie() {
|
||||
$.getJSON(document.body.dataset.apiurl + "/stats/query_types", function (data) {
|
||||
var v = [],
|
||||
c = [],
|
||||
k = [],
|
||||
i = 0,
|
||||
sum = 0;
|
||||
$.getJSON(document.body.dataset.apiurl + "/stats/query_types", data => {
|
||||
const v = [];
|
||||
const c = [];
|
||||
const k = [];
|
||||
let i = 0;
|
||||
let sum = 0;
|
||||
|
||||
// Compute total number of queries
|
||||
Object.keys(data.types).forEach(function (item) {
|
||||
for (const item of Object.keys(data.types)) {
|
||||
sum += data.types[item];
|
||||
});
|
||||
}
|
||||
|
||||
// Fill chart with data (only include query types which appeared recently)
|
||||
Object.keys(data.types).forEach(function (item) {
|
||||
for (const item of Object.keys(data.types)) {
|
||||
if (data.types[item] > 0) {
|
||||
v.push((100 * data.types[item]) / sum);
|
||||
c.push(THEME_COLORS[i % THEME_COLORS.length]);
|
||||
@@ -113,10 +117,10 @@ function updateQueryTypesPie() {
|
||||
}
|
||||
|
||||
i++;
|
||||
});
|
||||
}
|
||||
|
||||
// Build a single dataset with the data to be pushed
|
||||
var dd = { data: v, backgroundColor: c };
|
||||
const dd = { data: v, backgroundColor: c };
|
||||
// and push it at once
|
||||
queryTypePieChart.data.datasets[0] = dd;
|
||||
queryTypePieChart.data.labels = k;
|
||||
@@ -125,16 +129,16 @@ function updateQueryTypesPie() {
|
||||
//https://www.chartjs.org/docs/latest/developers/updates.html#preventing-animations
|
||||
queryTypePieChart.update("none");
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.setTimer(updateQueryTypesPie, REFRESH_INTERVAL.query_types);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
function updateClientsOverTime() {
|
||||
$.getJSON(document.body.dataset.apiurl + "/history/clients", function (data) {
|
||||
$.getJSON(document.body.dataset.apiurl + "/history/clients", data => {
|
||||
// Remove graph if there are no results (e.g. new
|
||||
// installation or privacy mode enabled)
|
||||
if (jQuery.isEmptyObject(data.history)) {
|
||||
@@ -143,12 +147,12 @@ function updateClientsOverTime() {
|
||||
}
|
||||
|
||||
let numClients = 0;
|
||||
const labels = [],
|
||||
clients = {};
|
||||
Object.keys(data.clients).forEach(function (ip) {
|
||||
const labels = [];
|
||||
const clients = {};
|
||||
for (const ip of Object.keys(data.clients)) {
|
||||
clients[ip] = numClients++;
|
||||
labels.push(data.clients[ip].name !== null ? data.clients[ip].name : ip);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove possibly already existing data
|
||||
clientsChart.data.labels = [];
|
||||
@@ -161,7 +165,7 @@ function updateClientsOverTime() {
|
||||
backgroundColor:
|
||||
i < THEME_COLORS.length
|
||||
? THEME_COLORS[i]
|
||||
: "#" + (0x1000000 + Math.random() * 0xffffff).toString(16).substr(1, 6),
|
||||
: "#" + (0x1_00_00_00 + Math.random() * 0xff_ff_ff).toString(16).substr(1, 6),
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
@@ -172,8 +176,8 @@ function updateClientsOverTime() {
|
||||
|
||||
// Add data for each dataset that is available
|
||||
// We need to iterate over all time slots and fill in the data for each client
|
||||
Object.keys(data.history).forEach(function (item) {
|
||||
Object.keys(clients).forEach(function (client) {
|
||||
for (const item of Object.keys(data.history)) {
|
||||
for (const client of Object.keys(clients)) {
|
||||
if (data.history[item].data[client] === undefined) {
|
||||
// If there is no data for this client in this timeslot, we push 0
|
||||
clientsChart.data.datasets[clients[client]].data.push(0);
|
||||
@@ -181,53 +185,53 @@ function updateClientsOverTime() {
|
||||
// Otherwise, we push the data
|
||||
clientsChart.data.datasets[clients[client]].data.push(data.history[item].data[client]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Extract data timestamps
|
||||
data.history.forEach(function (item) {
|
||||
var d = new Date(1000 * parseInt(item.timestamp, 10));
|
||||
for (const item of data.history) {
|
||||
const d = new Date(1000 * Number.parseInt(item.timestamp, 10));
|
||||
clientsChart.data.labels.push(d);
|
||||
});
|
||||
}
|
||||
|
||||
$("#clients .overlay").hide();
|
||||
clientsChart.update();
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
// Reload graph after 10 minutes
|
||||
failures = 0;
|
||||
utils.setTimer(updateClientsOverTime, REFRESH_INTERVAL.clients);
|
||||
})
|
||||
.fail(function () {
|
||||
.fail(() => {
|
||||
failures++;
|
||||
if (failures < 5) {
|
||||
// Try again only if this has not failed more than five times in a row
|
||||
utils.setTimer(updateClientsOverTime, 0.1 * REFRESH_INTERVAL.clients);
|
||||
}
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
var upstreams = {};
|
||||
const upstreams = {};
|
||||
function updateForwardDestinationsPie() {
|
||||
$.getJSON(document.body.dataset.apiurl + "/stats/upstreams", function (data) {
|
||||
var v = [],
|
||||
c = [],
|
||||
k = [],
|
||||
i = 0,
|
||||
sum = 0,
|
||||
values = [];
|
||||
$.getJSON(document.body.dataset.apiurl + "/stats/upstreams", data => {
|
||||
const v = [];
|
||||
const c = [];
|
||||
const k = [];
|
||||
let i = 0;
|
||||
let sum = 0;
|
||||
const values = [];
|
||||
|
||||
// Compute total number of queries
|
||||
data.upstreams.forEach(function (item) {
|
||||
for (const item of data.upstreams) {
|
||||
sum += item.count;
|
||||
});
|
||||
}
|
||||
|
||||
// Collect values and colors
|
||||
data.upstreams.forEach(function (item) {
|
||||
var label = item.name !== null && item.name.length > 0 ? item.name : item.ip;
|
||||
for (const item of data.upstreams) {
|
||||
let label = item.name !== null && item.name.length > 0 ? item.name : item.ip;
|
||||
if (item.port > 0) {
|
||||
label += "#" + item.port;
|
||||
}
|
||||
@@ -238,19 +242,19 @@ function updateForwardDestinationsPie() {
|
||||
upstreams[label] += "#" + item.port;
|
||||
}
|
||||
|
||||
var percent = (100 * item.count) / sum;
|
||||
const percent = (100 * item.count) / sum;
|
||||
values.push([label, percent, THEME_COLORS[i++ % THEME_COLORS.length]]);
|
||||
});
|
||||
}
|
||||
|
||||
// Split data into individual arrays for the graphs
|
||||
values.forEach(function (value) {
|
||||
for (const value of values) {
|
||||
k.push(value[0]);
|
||||
v.push(value[1]);
|
||||
c.push(value[2]);
|
||||
});
|
||||
}
|
||||
|
||||
// Build a single dataset with the data to be pushed
|
||||
var dd = { data: v, backgroundColor: c };
|
||||
const dd = { data: v, backgroundColor: c };
|
||||
// and push it at once
|
||||
forwardDestinationPieChart.data.labels = k;
|
||||
forwardDestinationPieChart.data.datasets[0] = dd;
|
||||
@@ -262,16 +266,20 @@ function updateForwardDestinationsPie() {
|
||||
queryTypePieChart.update("none");
|
||||
forwardDestinationPieChart.update("none");
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.setTimer(updateForwardDestinationsPie, REFRESH_INTERVAL.upstreams);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
function updateTopClientsTable(blocked) {
|
||||
let api, style, tablecontent, overlay, clienttable;
|
||||
let api;
|
||||
let style;
|
||||
let tablecontent;
|
||||
let overlay;
|
||||
let clienttable;
|
||||
if (blocked) {
|
||||
api = document.body.dataset.apiurl + "/stats/top_clients?blocked=true";
|
||||
style = "queries-blocked";
|
||||
@@ -286,10 +294,11 @@ function updateTopClientsTable(blocked) {
|
||||
clienttable = $("#client-frequency").find("tbody:last");
|
||||
}
|
||||
|
||||
$.getJSON(api, function (data) {
|
||||
$.getJSON(api, data => {
|
||||
// Clear tables before filling them with data
|
||||
tablecontent.remove();
|
||||
let url, percentage;
|
||||
let url;
|
||||
let percentage;
|
||||
const sum = blocked ? data.blocked_queries : data.total_queries;
|
||||
|
||||
// Add note if there are no results (e.g. privacy mode enabled)
|
||||
@@ -300,7 +309,7 @@ function updateTopClientsTable(blocked) {
|
||||
}
|
||||
|
||||
// Populate table with content
|
||||
data.clients.forEach(function (client) {
|
||||
for (const client of data.clients) {
|
||||
// Sanitize client
|
||||
let clientname = client.name;
|
||||
if (clientname.length === 0) clientname = client.ip;
|
||||
@@ -321,17 +330,21 @@ function updateTopClientsTable(blocked) {
|
||||
utils.addTD(utils.colorBar(percentage, sum, style)) +
|
||||
"</tr> "
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Hide overlay
|
||||
overlay.hide();
|
||||
}).fail(function (data) {
|
||||
}).fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
function updateTopDomainsTable(blocked) {
|
||||
let api, style, tablecontent, overlay, domaintable;
|
||||
let api;
|
||||
let style;
|
||||
let tablecontent;
|
||||
let overlay;
|
||||
let domaintable;
|
||||
if (blocked) {
|
||||
api = document.body.dataset.apiurl + "/stats/top_domains?blocked=true";
|
||||
style = "queries-blocked";
|
||||
@@ -346,10 +359,13 @@ function updateTopDomainsTable(blocked) {
|
||||
domaintable = $("#domain-frequency").find("tbody:last");
|
||||
}
|
||||
|
||||
$.getJSON(api, function (data) {
|
||||
$.getJSON(api, data => {
|
||||
// Clear tables before filling them with data
|
||||
tablecontent.remove();
|
||||
let url, domain, percentage, urlText;
|
||||
let url;
|
||||
let domain;
|
||||
let percentage;
|
||||
let urlText;
|
||||
const sum = blocked ? data.blocked_queries : data.total_queries;
|
||||
|
||||
// Add note if there are no results (e.g. privacy mode enabled)
|
||||
@@ -360,7 +376,7 @@ function updateTopDomainsTable(blocked) {
|
||||
}
|
||||
|
||||
// Populate table with content
|
||||
data.domains.forEach(function (item) {
|
||||
for (const item of data.domains) {
|
||||
// Sanitize domain
|
||||
domain = encodeURIComponent(item.domain);
|
||||
// Substitute "." for empty domain lookups
|
||||
@@ -380,10 +396,10 @@ function updateTopDomainsTable(blocked) {
|
||||
utils.addTD(utils.colorBar(percentage, sum, style)) +
|
||||
"</tr> "
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
overlay.hide();
|
||||
}).fail(function (data) {
|
||||
}).fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -405,26 +421,26 @@ function updateTopLists() {
|
||||
utils.setTimer(updateTopLists, REFRESH_INTERVAL.top_lists);
|
||||
}
|
||||
|
||||
var previousCount = 0;
|
||||
var firstSummaryUpdate = true;
|
||||
let previousCount = 0;
|
||||
let firstSummaryUpdate = true;
|
||||
function updateSummaryData(runOnce = false) {
|
||||
$.getJSON(document.body.dataset.apiurl + "/stats/summary", function (data) {
|
||||
var intl = new Intl.NumberFormat();
|
||||
const newCount = parseInt(data.queries.total, 10);
|
||||
$.getJSON(document.body.dataset.apiurl + "/stats/summary", data => {
|
||||
const intl = new Intl.NumberFormat();
|
||||
const newCount = Number.parseInt(data.queries.total, 10);
|
||||
|
||||
$("span#dns_queries").text(intl.format(newCount));
|
||||
$("span#active_clients").text(intl.format(parseInt(data.clients.active, 10)));
|
||||
$("span#active_clients").text(intl.format(Number.parseInt(data.clients.active, 10)));
|
||||
$("a#total_clients").attr(
|
||||
"title",
|
||||
intl.format(parseInt(data.clients.total, 10)) + " total clients"
|
||||
intl.format(Number.parseInt(data.clients.total, 10)) + " total clients"
|
||||
);
|
||||
$("span#blocked_queries").text(intl.format(parseFloat(data.queries.blocked)));
|
||||
var formattedPercentage = utils.toPercent(data.queries.percent_blocked, 1);
|
||||
$("span#blocked_queries").text(intl.format(Number.parseFloat(data.queries.blocked)));
|
||||
const formattedPercentage = utils.toPercent(data.queries.percent_blocked, 1);
|
||||
$("span#percent_blocked").text(formattedPercentage);
|
||||
updateQueryFrequency(intl, data.queries.frequency);
|
||||
|
||||
const lastupdate = parseInt(data.gravity.last_update, 10);
|
||||
var updatetxt = "Lists were never updated";
|
||||
const lastupdate = Number.parseInt(data.gravity.last_update, 10);
|
||||
let updatetxt = "Lists were never updated";
|
||||
if (lastupdate > 0) {
|
||||
updatetxt =
|
||||
"Lists updated " +
|
||||
@@ -434,7 +450,7 @@ function updateSummaryData(runOnce = false) {
|
||||
")";
|
||||
}
|
||||
|
||||
const gravityCount = parseInt(data.gravity.domains_being_blocked, 10);
|
||||
const gravityCount = Number.parseInt(data.gravity.domains_being_blocked, 10);
|
||||
if (gravityCount < 0) {
|
||||
// Error. Change the title text and show the error code in parentheses
|
||||
updatetxt = "Error! Update gravity to reset this value.";
|
||||
@@ -460,10 +476,10 @@ function updateSummaryData(runOnce = false) {
|
||||
previousCount = newCount;
|
||||
firstSummaryUpdate = false;
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
if (!runOnce) utils.setTimer(updateSummaryData, REFRESH_INTERVAL.summary);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
utils.setTimer(updateSummaryData, 3 * REFRESH_INTERVAL.summary);
|
||||
apiFailure(data);
|
||||
});
|
||||
@@ -476,11 +492,11 @@ function labelWithPercentage(tooltipLabel, skipZero = false) {
|
||||
const keys = Object.keys(tooltipLabel.parsed._stacks.y);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (tooltipLabel.parsed._stacks.y[i] === undefined) continue;
|
||||
sum += parseInt(tooltipLabel.parsed._stacks.y[i], 10);
|
||||
sum += Number.parseInt(tooltipLabel.parsed._stacks.y[i], 10);
|
||||
}
|
||||
|
||||
let percentage = 0;
|
||||
const data = parseInt(tooltipLabel.parsed._stacks.y[tooltipLabel.datasetIndex], 10);
|
||||
const data = Number.parseInt(tooltipLabel.parsed._stacks.y[tooltipLabel.datasetIndex], 10);
|
||||
if (sum > 0) {
|
||||
percentage = (100 * data) / sum;
|
||||
}
|
||||
@@ -496,7 +512,7 @@ function labelWithPercentage(tooltipLabel, skipZero = false) {
|
||||
);
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
// Pull in data via AJAX
|
||||
updateSummaryData();
|
||||
|
||||
@@ -522,14 +538,14 @@ $(function () {
|
||||
enabled: true,
|
||||
},
|
||||
mode: "y",
|
||||
onZoom: function ({ chart, trigger }) {
|
||||
onZoom({ chart, trigger }) {
|
||||
if (trigger === "api") {
|
||||
// Ignore onZoom triggered by the chart.zoomScale api call below
|
||||
return;
|
||||
}
|
||||
|
||||
// The first time the chart is zoomed, save the maximum initial scale bound
|
||||
if (!chart.absMax) chart.absMax = chart.getInitialScaleBounds().y.max;
|
||||
chart.absMax ||= chart.getInitialScaleBounds().y.max;
|
||||
// Calculate the maximum value to be shown for the current zoom level
|
||||
const zoomMax = chart.absMax / chart.getZoomLevel();
|
||||
// Update the y axis scale
|
||||
@@ -570,9 +586,9 @@ $(function () {
|
||||
},
|
||||
};
|
||||
|
||||
var gridColor = utils.getCSSval("graphs-grid", "background-color");
|
||||
var ticksColor = utils.getCSSval("graphs-ticks", "color");
|
||||
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
|
||||
const gridColor = utils.getCSSval("graphs-grid", "background-color");
|
||||
const ticksColor = utils.getCSSval("graphs-ticks", "color");
|
||||
let ctx = document.getElementById("queryOverTimeChart").getContext("2d");
|
||||
timeLineChart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: {
|
||||
@@ -594,20 +610,20 @@ $(function () {
|
||||
enabled: true,
|
||||
intersect: false,
|
||||
yAlign: "bottom",
|
||||
itemSort: function (a, b) {
|
||||
itemSort(a, b) {
|
||||
return b.datasetIndex - a.datasetIndex;
|
||||
},
|
||||
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";
|
||||
title(tooltipTitle) {
|
||||
const label = tooltipTitle[0].label;
|
||||
const time = label.match(/(\d?\d):?(\d?\d?)/);
|
||||
const h = Number.parseInt(time[1], 10);
|
||||
const m = Number.parseInt(time[2], 10) || 0;
|
||||
const from = utils.padNumber(h) + ":" + utils.padNumber(m - 5) + ":00";
|
||||
const to = utils.padNumber(h) + ":" + utils.padNumber(m + 4) + ":59";
|
||||
return "Queries from " + from + " to " + to;
|
||||
},
|
||||
label: function (tooltipLabel) {
|
||||
label(tooltipLabel) {
|
||||
return labelWithPercentage(tooltipLabel);
|
||||
},
|
||||
},
|
||||
@@ -672,7 +688,7 @@ $(function () {
|
||||
updateQueriesOverTime();
|
||||
|
||||
// Create / load "Top Clients over Time" only if authorized
|
||||
var clientsChartEl = document.getElementById("clientsChart");
|
||||
const clientsChartEl = document.getElementById("clientsChart");
|
||||
if (clientsChartEl) {
|
||||
ctx = clientsChartEl.getContext("2d");
|
||||
clientsChart = new Chart(ctx, {
|
||||
@@ -698,20 +714,20 @@ $(function () {
|
||||
intersect: false,
|
||||
external: customTooltips,
|
||||
yAlign: "top",
|
||||
itemSort: function (a, b) {
|
||||
itemSort(a, b) {
|
||||
return b.raw - a.raw;
|
||||
},
|
||||
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";
|
||||
title(tooltipTitle) {
|
||||
const label = tooltipTitle[0].label;
|
||||
const time = label.match(/(\d?\d):?(\d?\d?)/);
|
||||
const h = Number.parseInt(time[1], 10);
|
||||
const m = Number.parseInt(time[2], 10) || 0;
|
||||
const from = utils.padNumber(h) + ":" + utils.padNumber(m - 5) + ":00";
|
||||
const to = utils.padNumber(h) + ":" + utils.padNumber(m + 4) + ":59";
|
||||
return "Client activity from " + from + " to " + to;
|
||||
},
|
||||
label: function (tooltipLabel) {
|
||||
label(tooltipLabel) {
|
||||
return labelWithPercentage(tooltipLabel, true);
|
||||
},
|
||||
},
|
||||
@@ -781,8 +797,8 @@ $(function () {
|
||||
|
||||
updateTopLists();
|
||||
|
||||
$("#queryOverTimeChart").on("click", function (evt) {
|
||||
var activePoints = timeLineChart.getElementsAtEventForMode(
|
||||
$("#queryOverTimeChart").on("click", evt => {
|
||||
const activePoints = timeLineChart.getElementsAtEventForMode(
|
||||
evt,
|
||||
"nearest",
|
||||
{ intersect: true },
|
||||
@@ -790,21 +806,21 @@ $(function () {
|
||||
);
|
||||
if (activePoints.length > 0) {
|
||||
//get the internal index
|
||||
var clickedElementindex = activePoints[0].index;
|
||||
const clickedElementindex = activePoints[0].index;
|
||||
//get specific label by index
|
||||
var label = timeLineChart.data.labels[clickedElementindex];
|
||||
const label = timeLineChart.data.labels[clickedElementindex];
|
||||
|
||||
//get value by index
|
||||
var from = label / 1000 - 300;
|
||||
var until = label / 1000 + 300;
|
||||
const from = label / 1000 - 300;
|
||||
const until = label / 1000 + 300;
|
||||
globalThis.location.href = "queries?from=" + from + "&until=" + until;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#clientsChart").on("click", function (evt) {
|
||||
var activePoints = clientsChart.getElementsAtEventForMode(
|
||||
$("#clientsChart").on("click", evt => {
|
||||
const activePoints = clientsChart.getElementsAtEventForMode(
|
||||
evt,
|
||||
"nearest",
|
||||
{ intersect: true },
|
||||
@@ -812,14 +828,14 @@ $(function () {
|
||||
);
|
||||
if (activePoints.length > 0) {
|
||||
//get the internal index
|
||||
var clickedElementindex = activePoints[0].index;
|
||||
const clickedElementindex = activePoints[0].index;
|
||||
|
||||
//get specific label by index
|
||||
var label = clientsChart.data.labels[clickedElementindex];
|
||||
const label = clientsChart.data.labels[clickedElementindex];
|
||||
|
||||
//get value by index
|
||||
var from = label / 1000 - 300;
|
||||
var until = label / 1000 + 300;
|
||||
const from = label / 1000 - 300;
|
||||
const until = label / 1000 + 300;
|
||||
globalThis.location.href = "queries?from=" + from + "&until=" + until;
|
||||
}
|
||||
|
||||
@@ -855,7 +871,7 @@ $(function () {
|
||||
enabled: false,
|
||||
external: customTooltips,
|
||||
callbacks: {
|
||||
title: function () {
|
||||
title() {
|
||||
return "Query type";
|
||||
},
|
||||
label: doughnutTooltip,
|
||||
@@ -901,7 +917,7 @@ $(function () {
|
||||
enabled: false,
|
||||
external: customTooltips,
|
||||
callbacks: {
|
||||
title: function () {
|
||||
title() {
|
||||
return "Upstream server";
|
||||
},
|
||||
label: doughnutTooltip,
|
||||
@@ -920,11 +936,11 @@ $(function () {
|
||||
});
|
||||
|
||||
//destroy all chartjs customTooltips on window resize
|
||||
window.addEventListener("resize", function () {
|
||||
window.addEventListener("resize", () => {
|
||||
$(".chartjs-tooltip").remove();
|
||||
});
|
||||
|
||||
// Tooltips
|
||||
$(function () {
|
||||
$(() => {
|
||||
$('[data-toggle="tooltip"]').tooltip({ html: true, container: "body" });
|
||||
});
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
|
||||
/* global utils: false */
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
$(() => {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/network/gateway",
|
||||
data: { detailed: true },
|
||||
}).done(function (data) {
|
||||
var intl = new Intl.NumberFormat();
|
||||
}).done(data => {
|
||||
const intl = new Intl.NumberFormat();
|
||||
const gateway = data.gateway;
|
||||
// Get all objects in gateway that has family == "inet"
|
||||
const inet = gateway.find(obj => obj.family === "inet");
|
||||
@@ -28,18 +30,18 @@ $(function () {
|
||||
gateways.add(inet6.gateway);
|
||||
}
|
||||
|
||||
var interfaces = {};
|
||||
var masterInterfaces = {};
|
||||
const interfaces = {};
|
||||
const masterInterfaces = {};
|
||||
|
||||
// For each interface in data.interface, create a new object and push it to json
|
||||
data.interfaces.forEach(function (interface) {
|
||||
const carrierColor = interface.carrier ? "text-green" : "text-red";
|
||||
let stateText = interface.state.toUpperCase();
|
||||
if (stateText === "UNKNOWN" && interface.flags !== undefined && interface.flags.length > 0) {
|
||||
if (interface.flags.includes("pointopoint")) {
|
||||
for (const iface of data.interfaces) {
|
||||
const carrierColor = iface.carrier ? "text-green" : "text-red";
|
||||
let stateText = iface.state.toUpperCase();
|
||||
if (stateText === "UNKNOWN" && iface.flags !== undefined && iface.flags.length > 0) {
|
||||
if (iface.flags.includes("pointopoint")) {
|
||||
// WireGuards, etc. -> the typo is intentional
|
||||
stateText = "P2P";
|
||||
} else if (interface.flags.includes("loopback")) {
|
||||
} else if (iface.flags.includes("loopback")) {
|
||||
// Loopback interfaces
|
||||
stateText = "LOOPBACK";
|
||||
}
|
||||
@@ -48,18 +50,18 @@ $(function () {
|
||||
const status = `<span class="${carrierColor}">${stateText}</span>`;
|
||||
|
||||
let master = null;
|
||||
if (interface.master !== undefined) {
|
||||
if (iface.master !== undefined) {
|
||||
// Find interface.master in data.interfaces
|
||||
master = data.interfaces.find(obj => obj.index === interface.master).name;
|
||||
master = data.interfaces.find(obj => obj.index === iface.master).name;
|
||||
}
|
||||
|
||||
// Show an icon for indenting slave interfaces
|
||||
const indentIcon =
|
||||
master === null ? "" : "<span class='child-interface-icon'> ⤷</span> ";
|
||||
|
||||
var obj = {
|
||||
text: indentIcon + interface.name + " - " + status,
|
||||
class: gateways.has(interface.name) ? "text-bold" : null,
|
||||
const obj = {
|
||||
text: indentIcon + iface.name + " - " + status,
|
||||
class: gateways.has(iface.name) ? "text-bold" : null,
|
||||
icon: master === null ? "fa fa-network-wired fa-fw" : "",
|
||||
nodes: [],
|
||||
};
|
||||
@@ -71,57 +73,56 @@ $(function () {
|
||||
});
|
||||
|
||||
if (master in masterInterfaces) {
|
||||
masterInterfaces[master].push(interface.name);
|
||||
masterInterfaces[master].push(iface.name);
|
||||
} else {
|
||||
masterInterfaces[master] = [interface.name];
|
||||
masterInterfaces[master] = [iface.name];
|
||||
}
|
||||
}
|
||||
|
||||
if (interface.speed) {
|
||||
if (iface.speed) {
|
||||
obj.nodes.push({
|
||||
text: "Speed: " + intl.format(interface.speed) + " Mbit/s",
|
||||
text: "Speed: " + intl.format(iface.speed) + " Mbit/s",
|
||||
icon: "fa fa-tachometer-alt fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.type !== undefined) {
|
||||
if (iface.type !== undefined) {
|
||||
obj.nodes.push({
|
||||
text: "Type: " + utils.escapeHtml(interface.type),
|
||||
text: "Type: " + utils.escapeHtml(iface.type),
|
||||
icon: "fa fa-network-wired fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.flags !== undefined && interface.flags.length > 0) {
|
||||
if (iface.flags !== undefined && iface.flags.length > 0) {
|
||||
obj.nodes.push({
|
||||
text: "Flags: " + utils.escapeHtml(interface.flags.join(", ")),
|
||||
text: "Flags: " + utils.escapeHtml(iface.flags.join(", ")),
|
||||
icon: "fa fa-flag fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.address !== undefined) {
|
||||
if (iface.address !== undefined) {
|
||||
let extra = "";
|
||||
if (interface.perm_address !== undefined && interface.perm_address !== interface.address) {
|
||||
extra = " (permanent: <code>" + utils.escapeHtml(interface.perm_address) + "</code>)";
|
||||
if (iface.perm_address !== undefined && iface.perm_address !== iface.address) {
|
||||
extra = " (permanent: <code>" + utils.escapeHtml(iface.perm_address) + "</code>)";
|
||||
}
|
||||
|
||||
obj.nodes.push({
|
||||
text:
|
||||
"Hardware address: <code>" + utils.escapeHtml(interface.address) + "</code>" + extra,
|
||||
text: "Hardware address: <code>" + utils.escapeHtml(iface.address) + "</code>" + extra,
|
||||
icon: "fa fa-map-marker-alt fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.addresses !== undefined) {
|
||||
if (iface.addresses !== undefined) {
|
||||
const addrs = {
|
||||
text:
|
||||
interface.addresses.length +
|
||||
(interface.addresses.length === 1 ? " address" : " addresses") +
|
||||
iface.addresses.length +
|
||||
(iface.addresses.length === 1 ? " address" : " addresses") +
|
||||
" connected to interface",
|
||||
icon: "fa fa-map-marker-alt fa-fw",
|
||||
nodes: [],
|
||||
};
|
||||
|
||||
for (const addr of interface.addresses) {
|
||||
for (const addr of iface.addresses) {
|
||||
let extraaddr = "";
|
||||
if (addr.prefixlen !== undefined) {
|
||||
extraaddr += " / <code>" + addr.prefixlen + "</code>";
|
||||
@@ -179,7 +180,7 @@ $(function () {
|
||||
|
||||
if (addr.prefered !== undefined) {
|
||||
const pref =
|
||||
addr.prefered === 4294967295 ? "forever" : intl.format(addr.prefered) + " s";
|
||||
addr.prefered === 4_294_967_295 ? "forever" : intl.format(addr.prefered) + " s";
|
||||
jaddr.nodes.push({
|
||||
text: "Preferred lifetime: " + pref,
|
||||
icon: "fa fa-clock fa-fw",
|
||||
@@ -187,7 +188,7 @@ $(function () {
|
||||
}
|
||||
|
||||
if (addr.valid !== undefined) {
|
||||
const valid = addr.valid === 4294967295 ? "forever" : intl.format(addr.valid) + " s";
|
||||
const valid = addr.valid === 4_294_967_295 ? "forever" : intl.format(addr.valid) + " s";
|
||||
jaddr.nodes.push({
|
||||
text: "Valid lifetime: " + valid,
|
||||
icon: "fa fa-clock fa-fw",
|
||||
@@ -214,107 +215,107 @@ $(function () {
|
||||
obj.nodes.push(addrs);
|
||||
}
|
||||
|
||||
if (interface.stats !== undefined) {
|
||||
if (iface.stats !== undefined) {
|
||||
const stats = {
|
||||
text: "Statistics",
|
||||
icon: "fa fa-chart-line fa-fw",
|
||||
expanded: false,
|
||||
nodes: [],
|
||||
};
|
||||
if (interface.stats.rx_bytes !== undefined) {
|
||||
if (iface.stats.rx_bytes !== undefined) {
|
||||
stats.nodes.push({
|
||||
text:
|
||||
"RX bytes: " +
|
||||
intl.format(interface.stats.rx_bytes.value) +
|
||||
intl.format(iface.stats.rx_bytes.value) +
|
||||
" " +
|
||||
interface.stats.rx_bytes.unit,
|
||||
iface.stats.rx_bytes.unit,
|
||||
icon: "fa fa-download fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.tx_bytes !== undefined) {
|
||||
if (iface.stats.tx_bytes !== undefined) {
|
||||
stats.nodes.push({
|
||||
text:
|
||||
"TX bytes: " +
|
||||
intl.format(interface.stats.tx_bytes.value) +
|
||||
intl.format(iface.stats.tx_bytes.value) +
|
||||
" " +
|
||||
interface.stats.tx_bytes.unit,
|
||||
iface.stats.tx_bytes.unit,
|
||||
icon: "fa fa-upload fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.rx_packets !== undefined) {
|
||||
if (iface.stats.rx_packets !== undefined) {
|
||||
stats.nodes.push({
|
||||
text: "RX packets: " + intl.format(interface.stats.rx_packets),
|
||||
text: "RX packets: " + intl.format(iface.stats.rx_packets),
|
||||
icon: "fa fa-download fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.rx_errors !== undefined) {
|
||||
if (iface.stats.rx_errors !== undefined) {
|
||||
stats.nodes.push({
|
||||
text:
|
||||
"RX errors: " +
|
||||
intl.format(interface.stats.rx_errors) +
|
||||
intl.format(iface.stats.rx_errors) +
|
||||
" (" +
|
||||
((interface.stats.rx_errors / interface.stats.rx_packets) * 100).toFixed(1) +
|
||||
((iface.stats.rx_errors / iface.stats.rx_packets) * 100).toFixed(1) +
|
||||
"%)",
|
||||
icon: "fa fa-download fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.rx_dropped !== undefined) {
|
||||
if (iface.stats.rx_dropped !== undefined) {
|
||||
stats.nodes.push({
|
||||
text:
|
||||
"RX dropped: " +
|
||||
intl.format(interface.stats.rx_dropped) +
|
||||
intl.format(iface.stats.rx_dropped) +
|
||||
" (" +
|
||||
((interface.stats.rx_dropped / interface.stats.rx_packets) * 100).toFixed(1) +
|
||||
((iface.stats.rx_dropped / iface.stats.rx_packets) * 100).toFixed(1) +
|
||||
"%)",
|
||||
icon: "fa fa-download fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.tx_packets !== undefined) {
|
||||
if (iface.stats.tx_packets !== undefined) {
|
||||
stats.nodes.push({
|
||||
text: "TX packets: " + intl.format(interface.stats.tx_packets),
|
||||
text: "TX packets: " + intl.format(iface.stats.tx_packets),
|
||||
icon: "fa fa-upload fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.tx_errors !== undefined) {
|
||||
if (iface.stats.tx_errors !== undefined) {
|
||||
stats.nodes.push({
|
||||
text:
|
||||
"TX errors: " +
|
||||
intl.format(interface.stats.tx_errors) +
|
||||
intl.format(iface.stats.tx_errors) +
|
||||
" (" +
|
||||
((interface.stats.tx_errors / interface.stats.tx_packets) * 100).toFixed(1) +
|
||||
((iface.stats.tx_errors / iface.stats.tx_packets) * 100).toFixed(1) +
|
||||
"%)",
|
||||
icon: "fa fa-upload fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.tx_dropped !== undefined) {
|
||||
if (iface.stats.tx_dropped !== undefined) {
|
||||
stats.nodes.push({
|
||||
text:
|
||||
"TX dropped: " +
|
||||
intl.format(interface.stats.tx_dropped) +
|
||||
intl.format(iface.stats.tx_dropped) +
|
||||
" (" +
|
||||
((interface.stats.tx_dropped / interface.stats.tx_packets) * 100).toFixed(1) +
|
||||
((iface.stats.tx_dropped / iface.stats.tx_packets) * 100).toFixed(1) +
|
||||
"%)",
|
||||
icon: "fa fa-upload fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.multicast !== undefined) {
|
||||
if (iface.stats.multicast !== undefined) {
|
||||
stats.nodes.push({
|
||||
text: "Multicast: " + intl.format(interface.stats.multicast),
|
||||
text: "Multicast: " + intl.format(iface.stats.multicast),
|
||||
icon: "fa fa-broadcast-tower fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.stats.collisions !== undefined) {
|
||||
if (iface.stats.collisions !== undefined) {
|
||||
stats.nodes.push({
|
||||
text: "Collisions: " + intl.format(interface.stats.collisions),
|
||||
text: "Collisions: " + intl.format(iface.stats.collisions),
|
||||
icon: "fa fa-exchange-alt fa-fw",
|
||||
});
|
||||
}
|
||||
@@ -333,81 +334,78 @@ $(function () {
|
||||
{
|
||||
text:
|
||||
"Carrier: " +
|
||||
(interface.carrier
|
||||
(iface.carrier
|
||||
? "<span class='text-green'>Connected</span>"
|
||||
: "<span class='text-red'>Disconnected</span>"),
|
||||
icon: "fa fa-link fa-fw",
|
||||
},
|
||||
{
|
||||
text: "State: " + utils.escapeHtml(interface.state.toUpperCase()),
|
||||
text: "State: " + utils.escapeHtml(iface.state.toUpperCase()),
|
||||
icon: "fa fa-server fa-fw",
|
||||
}
|
||||
);
|
||||
|
||||
if (interface.parent_dev_name !== undefined) {
|
||||
if (iface.parent_dev_name !== undefined) {
|
||||
let extra = "";
|
||||
if (interface.parent_dev_bus_name !== undefined) {
|
||||
extra = " @ " + utils.escapeHtml(interface.parent_dev_bus_name);
|
||||
if (iface.parent_dev_bus_name !== undefined) {
|
||||
extra = " @ " + utils.escapeHtml(iface.parent_dev_bus_name);
|
||||
}
|
||||
|
||||
furtherDetails.nodes.push({
|
||||
text:
|
||||
"Parent device: <code>" +
|
||||
utils.escapeHtml(interface.parent_dev_name) +
|
||||
extra +
|
||||
"</code>",
|
||||
"Parent device: <code>" + utils.escapeHtml(iface.parent_dev_name) + extra + "</code>",
|
||||
icon: "fa fa-network-wired fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.carrier_changes !== undefined) {
|
||||
if (iface.carrier_changes !== undefined) {
|
||||
furtherDetails.nodes.push({
|
||||
text: "Carrier changes: " + intl.format(interface.carrier_changes),
|
||||
text: "Carrier changes: " + intl.format(iface.carrier_changes),
|
||||
icon: "fa fa-exchange-alt fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.broadcast) {
|
||||
if (iface.broadcast) {
|
||||
furtherDetails.nodes.push({
|
||||
text: "Broadcast: <code>" + utils.escapeHtml(interface.broadcast) + "</code>",
|
||||
text: "Broadcast: <code>" + utils.escapeHtml(iface.broadcast) + "</code>",
|
||||
icon: "fa fa-broadcast-tower fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.mtu) {
|
||||
if (iface.mtu) {
|
||||
let extra = "";
|
||||
if (interface.min_mtu !== undefined && interface.max_mtu !== undefined) {
|
||||
if (iface.min_mtu !== undefined && iface.max_mtu !== undefined) {
|
||||
extra +=
|
||||
" (min: " +
|
||||
intl.format(interface.min_mtu) +
|
||||
intl.format(iface.min_mtu) +
|
||||
" bytes, max: " +
|
||||
intl.format(interface.max_mtu) +
|
||||
intl.format(iface.max_mtu) +
|
||||
" bytes)";
|
||||
}
|
||||
|
||||
furtherDetails.nodes.push({
|
||||
text: "MTU: " + intl.format(interface.mtu) + " bytes" + extra,
|
||||
text: "MTU: " + intl.format(iface.mtu) + " bytes" + extra,
|
||||
icon: "fa fa-arrows-alt-h fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.txqlen) {
|
||||
if (iface.txqlen) {
|
||||
furtherDetails.nodes.push({
|
||||
text: "TX queue length: " + intl.format(interface.txqlen),
|
||||
text: "TX queue length: " + intl.format(iface.txqlen),
|
||||
icon: "fa fa-file-upload fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.promiscuity !== undefined) {
|
||||
if (iface.promiscuity !== undefined) {
|
||||
furtherDetails.nodes.push({
|
||||
text: "Promiscuity mode: " + (interface.promiscuity ? "Yes" : "No"),
|
||||
text: "Promiscuity mode: " + (iface.promiscuity ? "Yes" : "No"),
|
||||
icon: "fa fa-eye fa-fw",
|
||||
});
|
||||
}
|
||||
|
||||
if (interface.qdisc !== undefined) {
|
||||
if (iface.qdisc !== undefined) {
|
||||
furtherDetails.nodes.push({
|
||||
text: "Scheduler: " + utils.escapeHtml(interface.qdisc),
|
||||
text: "Scheduler: " + utils.escapeHtml(iface.qdisc),
|
||||
icon: "fa fa-network-wired fa-fw",
|
||||
});
|
||||
}
|
||||
@@ -416,8 +414,8 @@ $(function () {
|
||||
obj.nodes.push(furtherDetails);
|
||||
}
|
||||
|
||||
interfaces[interface.name] = obj;
|
||||
});
|
||||
interfaces[iface.name] = obj;
|
||||
}
|
||||
|
||||
// Sort interfaces based on masterInterfaces. If an item is found in
|
||||
// masterInterfaces, it should be placed after the master interface
|
||||
|
||||
@@ -5,31 +5,34 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// This code has been taken from
|
||||
// https://datatables.net/plug-ins/sorting/ip-address
|
||||
// and was modified by the Pi-hole team to support
|
||||
// CIDR notation and be more robust against invalid
|
||||
// input data (like empty IP addresses)
|
||||
$.extend($.fn.dataTableExt.oSort, {
|
||||
"ip-address-pre": function (a) {
|
||||
"ip-address-pre"(a) {
|
||||
// Skip empty fields (IP address might have expired or
|
||||
// reassigned to a different device)
|
||||
if (!a || a.length === 0) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
var i, item;
|
||||
let i;
|
||||
let item;
|
||||
// Use the first IP in case there is a list of IPs
|
||||
// for a given device
|
||||
if (Array.isArray(a)) {
|
||||
a = a[0];
|
||||
}
|
||||
|
||||
var m = a.split("."),
|
||||
n = a.split(":"),
|
||||
x = "",
|
||||
xa = "",
|
||||
cidr = [];
|
||||
let m = a.split(".");
|
||||
let n = a.split(":");
|
||||
let x = "";
|
||||
let xa = "";
|
||||
let cidr = [];
|
||||
if (m.length === 4) {
|
||||
// IPV4 (possibly with CIDR)
|
||||
cidr = m[3].split("/");
|
||||
@@ -51,7 +54,7 @@ $.extend($.fn.dataTableExt.oSort, {
|
||||
}
|
||||
} else if (n.length > 0) {
|
||||
// IPV6 (possibly with CIDR)
|
||||
var count = 0;
|
||||
let count = 0;
|
||||
for (i = 0; i < n.length; i++) {
|
||||
item = n[i];
|
||||
|
||||
@@ -96,12 +99,12 @@ $.extend($.fn.dataTableExt.oSort, {
|
||||
|
||||
// Padding the ::
|
||||
n = xa.split(":");
|
||||
var paddDone = 0;
|
||||
let paddDone = 0;
|
||||
|
||||
for (i = 0; i < n.length; i++) {
|
||||
item = n[i];
|
||||
if (item.length === 0 && paddDone === 0) {
|
||||
for (var padding = 0; padding < 32 - count; padding++) {
|
||||
for (let padding = 0; padding < 32 - count; padding++) {
|
||||
x += "0";
|
||||
paddDone = 1;
|
||||
}
|
||||
@@ -127,11 +130,11 @@ $.extend($.fn.dataTableExt.oSort, {
|
||||
return x;
|
||||
},
|
||||
|
||||
"ip-address-asc": function (a, b) {
|
||||
"ip-address-asc"(a, b) {
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
},
|
||||
|
||||
"ip-address-desc": function (a, b) {
|
||||
"ip-address-desc"(a, b) {
|
||||
return a < b ? 1 : a > b ? -1 : 0;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
|
||||
/* global utils:false, NProgress:false */
|
||||
|
||||
var _isLoginPage = true;
|
||||
"use strict";
|
||||
|
||||
globalThis._isLoginPage = true;
|
||||
|
||||
function redirect() {
|
||||
// Login succeeded or not needed (empty password)
|
||||
// Default: Send back to dashboard
|
||||
var target = ".";
|
||||
let target = ".";
|
||||
|
||||
// If DNS failure: send to Pi-hole diagnosis messages page
|
||||
if ($("#dns-failure-label").is(":visible")) {
|
||||
@@ -25,8 +27,8 @@ function redirect() {
|
||||
|
||||
function wrongPassword(isError = false, isSuccess = false, data = null) {
|
||||
if (isError) {
|
||||
let isErrorResponse = false,
|
||||
isInvalidTOTP = false;
|
||||
let isErrorResponse = false;
|
||||
let isInvalidTOTP = false;
|
||||
|
||||
// Reset hint and error message
|
||||
$("#error-message").text("");
|
||||
@@ -71,7 +73,9 @@ function wrongPassword(isError = false, isSuccess = false, data = null) {
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (isSuccess) {
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
$("#pw-field").addClass("has-success");
|
||||
$("#totp_input").addClass("has-success");
|
||||
} else {
|
||||
@@ -96,21 +100,21 @@ function doLogin(password) {
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({ password: password, totp: parseInt($("#totp").val(), 10) }),
|
||||
data: JSON.stringify({ password, totp: Number.parseInt($("#totp").val(), 10) }),
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
wrongPassword(false, true, data);
|
||||
NProgress.done();
|
||||
redirect();
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
wrongPassword(true, false, data);
|
||||
NProgress.done();
|
||||
utils.enableAll();
|
||||
});
|
||||
}
|
||||
|
||||
$("#loginform").on("submit", e => {
|
||||
$("#loginform").submit(e => {
|
||||
// Cancel the native submit event (prevent the form from being
|
||||
// submitted) because we want to do a two-step challenge-response login
|
||||
e.preventDefault();
|
||||
@@ -139,7 +143,7 @@ $("#toggle-password").on("click", function () {
|
||||
$(".field-icon", this).toggleClass("fa-eye fa-eye-slash");
|
||||
|
||||
// Password field
|
||||
var $pwd = $("#current-password");
|
||||
const $pwd = $("#current-password");
|
||||
if ($pwd.attr("type") === "password") {
|
||||
$pwd.attr("type", "text");
|
||||
$pwd.attr("title", "Hide password");
|
||||
@@ -160,16 +164,16 @@ function showDNSfailure() {
|
||||
$("#login-box").addClass("error-box");
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
// Check if we need to login at all
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/auth",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
// If we are already logged in, redirect to dashboard
|
||||
if (data.session.valid === true) redirect();
|
||||
})
|
||||
.fail(function (xhr) {
|
||||
.fail(xhr => {
|
||||
const session = xhr.responseJSON.session;
|
||||
// If TOPT is enabled, show the input field and add the required attribute
|
||||
if (session.totp === true) {
|
||||
@@ -183,7 +187,7 @@ $(function () {
|
||||
// Get information about HTTPS port and DNS status
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/info/login",
|
||||
}).done(function (data) {
|
||||
}).done(data => {
|
||||
if (data.dns === false) showDNSfailure();
|
||||
|
||||
// Generate HTTPS redirection link (only used if not already HTTPS)
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
/* global utils:false */
|
||||
|
||||
"use strict";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const logoutButton = document.getElementById("logout-button");
|
||||
const logoutUrl = document.body.dataset.webhome + "login";
|
||||
|
||||
@@ -6,20 +6,23 @@
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/* global utils: false */
|
||||
var table,
|
||||
toasts = {};
|
||||
|
||||
$(function () {
|
||||
var ignoreNonfatal = localStorage
|
||||
"use strict";
|
||||
|
||||
let table;
|
||||
const toasts = {};
|
||||
|
||||
$(() => {
|
||||
const ignoreNonfatal = localStorage
|
||||
? localStorage.getItem("hideNonfatalDnsmasqWarnings_chkbox") === "true"
|
||||
: false;
|
||||
var url =
|
||||
const url =
|
||||
document.body.dataset.apiurl +
|
||||
"/info/messages" +
|
||||
(ignoreNonfatal ? "?filter_dnsmasq_warnings=true" : "");
|
||||
table = $("#messagesTable").DataTable({
|
||||
ajax: {
|
||||
url: url,
|
||||
url,
|
||||
type: "GET",
|
||||
dataSrc: "messages",
|
||||
},
|
||||
@@ -37,7 +40,7 @@ $(function () {
|
||||
targets: 1,
|
||||
orderable: false,
|
||||
className: "select-checkbox",
|
||||
render: function () {
|
||||
render() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
@@ -46,19 +49,19 @@ $(function () {
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
$('button[id^="deleteMessage_"]').on("click", deleteMessage);
|
||||
|
||||
// Hide buttons if all messages were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
const hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
rowCallback(row, data) {
|
||||
$(row).attr("data-id", data.id);
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteMessage_' +
|
||||
data.id +
|
||||
'" data-del-id="' +
|
||||
@@ -78,7 +81,7 @@ $(function () {
|
||||
text: '<span class="far fa-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectAll",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -86,7 +89,7 @@ $(function () {
|
||||
text: '<span class="far fa-plus-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectMore",
|
||||
action: function () {
|
||||
action() {
|
||||
table.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -100,7 +103,7 @@ $(function () {
|
||||
text: '<span class="far fa-trash-alt"></span>',
|
||||
titleAttr: "Delete Selected",
|
||||
className: "btn-sm datatable-bt deleteSelected",
|
||||
action: function () {
|
||||
action() {
|
||||
// For each ".selected" row ...
|
||||
$("tr.selected").each(function () {
|
||||
// ... delete the row identified by "data-id".
|
||||
@@ -124,11 +127,11 @@ $(function () {
|
||||
},
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("messages-table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback("messages-table");
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback("messages-table");
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
return null;
|
||||
@@ -141,7 +144,7 @@ $(function () {
|
||||
return data;
|
||||
},
|
||||
});
|
||||
table.on("init select deselect", function () {
|
||||
table.on("init select deselect", () => {
|
||||
utils.changeTableButtonStates(table);
|
||||
});
|
||||
});
|
||||
@@ -155,7 +158,7 @@ function deleteMessage() {
|
||||
}
|
||||
|
||||
function delMsg(id) {
|
||||
id = parseInt(id, 10);
|
||||
id = Number.parseInt(id, 10);
|
||||
utils.disableAll();
|
||||
toasts[id] = utils.showAlert("info", "", "Deleting message...", "ID: " + id, null);
|
||||
|
||||
@@ -163,7 +166,7 @@ function delMsg(id) {
|
||||
url: document.body.dataset.apiurl + "/info/messages/" + id,
|
||||
method: "DELETE",
|
||||
})
|
||||
.done(function (response) {
|
||||
.done(response => {
|
||||
utils.enableAll();
|
||||
if (response === undefined) {
|
||||
utils.showAlert(
|
||||
@@ -193,7 +196,7 @@ function delMsg(id) {
|
||||
.done(
|
||||
utils.checkMessages // Update icon warnings count
|
||||
)
|
||||
.fail(function (jqXHR, exception) {
|
||||
.fail((jqXHR, exception) => {
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"error",
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
/* global utils:false, apiFailure:false */
|
||||
|
||||
var tableApi;
|
||||
"use strict";
|
||||
|
||||
let tableApi;
|
||||
|
||||
// How many IPs do we show at most per device?
|
||||
const MAXIPDISPLAY = 3;
|
||||
@@ -16,7 +18,7 @@ const DAY_IN_SECONDS = 24 * 60 * 60;
|
||||
function handleAjaxError(xhr, textStatus) {
|
||||
if (textStatus === "timeout") {
|
||||
alert("The server took too long to send the data.");
|
||||
} else if (xhr.responseText.indexOf("Connection refused") === -1) {
|
||||
} else if (!xhr.responseText.includes("Connection refused")) {
|
||||
alert("An unknown error occurred while loading the data.\n" + xhr.responseText);
|
||||
} else {
|
||||
alert("An error occurred while loading the data: Connection refused. Is FTL running?");
|
||||
@@ -32,7 +34,7 @@ function getTimestamp() {
|
||||
}
|
||||
|
||||
function valueToHex(c) {
|
||||
var hex = Math.round(c).toString(16);
|
||||
const hex = Math.round(c).toString(16);
|
||||
return hex.length === 1 ? "0" + hex : hex;
|
||||
}
|
||||
|
||||
@@ -49,7 +51,7 @@ function mixColors(ratio, rgb1, rgb2) {
|
||||
}
|
||||
|
||||
function parseColor(input) {
|
||||
var match = input.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
|
||||
const match = input.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
|
||||
|
||||
if (match) {
|
||||
return [match[1], match[2], match[3]];
|
||||
@@ -66,7 +68,7 @@ function deleteNetworkEntry() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/network/devices/" + id,
|
||||
method: "DELETE",
|
||||
success: function () {
|
||||
success() {
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"success",
|
||||
@@ -76,7 +78,7 @@ function deleteNetworkEntry() {
|
||||
);
|
||||
tableApi.row(tr).remove().draw(false).ajax.reload(null, false);
|
||||
},
|
||||
error: function (data, exception) {
|
||||
error(data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
@@ -90,24 +92,24 @@ function deleteNetworkEntry() {
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
tableApi = $("#network-entries").DataTable({
|
||||
rowCallback: function (row, data) {
|
||||
var color;
|
||||
var index;
|
||||
var iconClasses;
|
||||
var lastQuery = parseInt(data.lastQuery, 10);
|
||||
var diff = getTimestamp() - lastQuery;
|
||||
var networkRecent = $(".network-recent").css("background-color");
|
||||
var networkOld = $(".network-old").css("background-color");
|
||||
var networkOlder = $(".network-older").css("background-color");
|
||||
var networkNever = $(".network-never").css("background-color");
|
||||
rowCallback(row, data) {
|
||||
let color;
|
||||
let index;
|
||||
let iconClasses;
|
||||
const lastQuery = Number.parseInt(data.lastQuery, 10);
|
||||
const diff = getTimestamp() - lastQuery;
|
||||
const networkRecent = $(".network-recent").css("background-color");
|
||||
const networkOld = $(".network-old").css("background-color");
|
||||
const networkOlder = $(".network-older").css("background-color");
|
||||
const networkNever = $(".network-never").css("background-color");
|
||||
|
||||
if (lastQuery > 0) {
|
||||
if (diff <= DAY_IN_SECONDS) {
|
||||
// Last query came in within the last 24 hours
|
||||
// Color: light-green to light-yellow
|
||||
var ratio = Number(diff) / DAY_IN_SECONDS;
|
||||
const ratio = Number(diff) / DAY_IN_SECONDS;
|
||||
color = rgbToHex(mixColors(ratio, parseColor(networkRecent), parseColor(networkOld)));
|
||||
iconClasses = "fas fa-check";
|
||||
} else {
|
||||
@@ -135,11 +137,11 @@ $(function () {
|
||||
// Set number of queries to localized string (add thousand separators)
|
||||
$("td:eq(5)", row).html(data.numQueries.toLocaleString());
|
||||
|
||||
var ips = [],
|
||||
iptitles = [];
|
||||
const ips = [];
|
||||
const iptitles = [];
|
||||
|
||||
// Sort IPs, IPv4 before IPv6, then alphabetically
|
||||
data.ips.sort(function (a, b) {
|
||||
data.ips.sort((a, b) => {
|
||||
if (a.ip.includes(":") && !b.ip.includes(":")) {
|
||||
return 1;
|
||||
}
|
||||
@@ -152,8 +154,8 @@ $(function () {
|
||||
});
|
||||
|
||||
for (index = 0; index < data.ips.length; index++) {
|
||||
var ip = data.ips[index],
|
||||
iptext = ip.ip;
|
||||
const ip = data.ips[index];
|
||||
let iptext = ip.ip;
|
||||
|
||||
if (ip.name !== null && ip.name.length > 0) {
|
||||
iptext = iptext + " (" + ip.name + ")";
|
||||
@@ -194,7 +196,7 @@ $(function () {
|
||||
// Add delete button
|
||||
$(row).attr("data-id", data.id);
|
||||
$(row).attr("data-hwaddr", data.hwaddr);
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteNetworkEntry_' +
|
||||
data.id +
|
||||
'">' +
|
||||
@@ -229,7 +231,7 @@ $(function () {
|
||||
{
|
||||
data: "firstSeen",
|
||||
width: "8%",
|
||||
render: function (data, type) {
|
||||
render(data, type) {
|
||||
if (type === "display") {
|
||||
return utils.datetime(data);
|
||||
}
|
||||
@@ -240,7 +242,7 @@ $(function () {
|
||||
{
|
||||
data: "lastQuery",
|
||||
width: "8%",
|
||||
render: function (data, type) {
|
||||
render(data, type) {
|
||||
if (type === "display") {
|
||||
return utils.datetime(data);
|
||||
}
|
||||
@@ -254,7 +256,7 @@ $(function () {
|
||||
{ data: "ips[].name", visible: false, class: "hide" },
|
||||
],
|
||||
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
$('button[id^="deleteNetworkEntry_"]').on("click", deleteNetworkEntry);
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
@@ -265,10 +267,10 @@ $(function () {
|
||||
],
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("network_table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
stateLoadCallback() {
|
||||
return utils.stateLoadCallback("network_table");
|
||||
},
|
||||
columnDefs: [
|
||||
@@ -284,7 +286,7 @@ $(function () {
|
||||
],
|
||||
});
|
||||
// Disable autocorrect in the search box
|
||||
var input = document.querySelector("input[type=search]");
|
||||
const input = document.querySelector("input[type=search]");
|
||||
input.setAttribute("autocomplete", "off");
|
||||
input.setAttribute("autocorrect", "off");
|
||||
input.setAttribute("autocapitalize", "off");
|
||||
|
||||
@@ -7,16 +7,18 @@
|
||||
|
||||
/* global moment:false, utils:false, REFRESH_INTERVAL:false */
|
||||
|
||||
const beginningOfTime = 1262304000; // Jan 01 2010, 00:00 in seconds
|
||||
const endOfTime = 2147483647; // Jan 19, 2038, 03:14 in seconds
|
||||
var from = beginningOfTime;
|
||||
var until = endOfTime;
|
||||
"use strict";
|
||||
|
||||
var dateformat = "MMM Do YYYY, HH:mm";
|
||||
const beginningOfTime = 1_262_304_000; // Jan 01 2010, 00:00 in seconds
|
||||
const endOfTime = 2_147_483_647; // Jan 19, 2038, 03:14 in seconds
|
||||
let from = beginningOfTime;
|
||||
let until = endOfTime;
|
||||
|
||||
var table = null;
|
||||
var cursor = null;
|
||||
var filters = [
|
||||
const dateformat = "MMM Do YYYY, HH:mm";
|
||||
|
||||
let table = null;
|
||||
let cursor = null;
|
||||
const filters = [
|
||||
"client_ip",
|
||||
"client_name",
|
||||
"domain",
|
||||
@@ -58,7 +60,7 @@ function initDateRangePicker() {
|
||||
showDropdowns: true,
|
||||
autoUpdateInput: true,
|
||||
},
|
||||
function (startt, endt) {
|
||||
(startt, endt) => {
|
||||
// Update global variables
|
||||
// Convert milliseconds (JS) to seconds (API)
|
||||
from = moment(startt).utc().valueOf() / 1000;
|
||||
@@ -81,12 +83,12 @@ function handleAjaxError(xhr, textStatus) {
|
||||
|
||||
function parseQueryStatus(data) {
|
||||
// Parse query status
|
||||
var fieldtext,
|
||||
buttontext,
|
||||
icon = null,
|
||||
colorClass = false,
|
||||
blocked = false,
|
||||
isCNAME = false;
|
||||
let fieldtext;
|
||||
let buttontext;
|
||||
let icon = null;
|
||||
let colorClass = false;
|
||||
let blocked = false;
|
||||
let isCNAME = false;
|
||||
switch (data.status) {
|
||||
case "GRAVITY":
|
||||
colorClass = "text-red";
|
||||
@@ -223,17 +225,17 @@ function parseQueryStatus(data) {
|
||||
buttontext = "";
|
||||
}
|
||||
|
||||
var matchText =
|
||||
const matchText =
|
||||
colorClass === "text-green" ? "allowed" : colorClass === "text-red" ? "blocked" : "matched";
|
||||
|
||||
return {
|
||||
fieldtext: fieldtext,
|
||||
buttontext: buttontext,
|
||||
colorClass: colorClass,
|
||||
icon: icon,
|
||||
isCNAME: isCNAME,
|
||||
matchText: matchText,
|
||||
blocked: blocked,
|
||||
fieldtext,
|
||||
buttontext,
|
||||
colorClass,
|
||||
icon,
|
||||
isCNAME,
|
||||
matchText,
|
||||
blocked,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -256,9 +258,9 @@ function formatReplyTime(replyTime, type) {
|
||||
|
||||
// Parse DNSSEC status
|
||||
function parseDNSSEC(data) {
|
||||
var icon = "", // Icon to display
|
||||
color = "", // Class to apply to text
|
||||
text = data.dnssec; // Text to display
|
||||
let icon = ""; // Icon to display
|
||||
let color = ""; // Class to apply to text
|
||||
let text = data.dnssec; // Text to display
|
||||
switch (text) {
|
||||
case "SECURE":
|
||||
icon = "fa-solid fa-lock";
|
||||
@@ -283,15 +285,15 @@ function parseDNSSEC(data) {
|
||||
icon = "";
|
||||
}
|
||||
|
||||
return { text: text, icon: icon, color: color };
|
||||
return { text, icon, color };
|
||||
}
|
||||
|
||||
function formatInfo(data) {
|
||||
// Parse Query Status
|
||||
var dnssec = parseDNSSEC(data);
|
||||
var queryStatus = parseQueryStatus(data);
|
||||
var divStart = '<div class="col-xl-2 col-lg-4 col-md-6 col-12 overflow-wrap">';
|
||||
var statusInfo = "";
|
||||
const dnssec = parseDNSSEC(data);
|
||||
const queryStatus = parseQueryStatus(data);
|
||||
const divStart = '<div class="col-xl-2 col-lg-4 col-md-6 col-12 overflow-wrap">';
|
||||
let statusInfo = "";
|
||||
if (queryStatus.colorClass !== false) {
|
||||
statusInfo =
|
||||
divStart +
|
||||
@@ -303,12 +305,12 @@ function formatInfo(data) {
|
||||
"</span></strong></div>";
|
||||
}
|
||||
|
||||
var listInfo = "",
|
||||
cnameInfo = "";
|
||||
let listInfo = "";
|
||||
let cnameInfo = "";
|
||||
if (data.list_id !== null && data.list_id !== -1) {
|
||||
// Some list matched - add link to search page
|
||||
|
||||
var listLink =
|
||||
const listLink =
|
||||
'<a href="search?domain=' +
|
||||
encodeURIComponent(data.domain) +
|
||||
'" target="_blank">search lists</a>';
|
||||
@@ -321,7 +323,7 @@ function formatInfo(data) {
|
||||
}
|
||||
|
||||
// Show TTL if applicable
|
||||
var ttlInfo = "";
|
||||
let ttlInfo = "";
|
||||
if (data.ttl > 0) {
|
||||
ttlInfo =
|
||||
divStart +
|
||||
@@ -333,14 +335,14 @@ function formatInfo(data) {
|
||||
}
|
||||
|
||||
// Show client information, show hostname only if available
|
||||
var ipInfo =
|
||||
const ipInfo =
|
||||
data.client.name !== null && data.client.name.length > 0
|
||||
? utils.escapeHtml(data.client.name) + " (" + data.client.ip + ")"
|
||||
: data.client.ip;
|
||||
var clientInfo = divStart + "Client: <strong>" + ipInfo + "</strong></div>";
|
||||
const clientInfo = divStart + "Client: <strong>" + ipInfo + "</strong></div>";
|
||||
|
||||
// Show DNSSEC status if applicable
|
||||
var dnssecInfo = "";
|
||||
let dnssecInfo = "";
|
||||
if (dnssec.color !== "") {
|
||||
dnssecInfo =
|
||||
divStart +
|
||||
@@ -352,20 +354,20 @@ function formatInfo(data) {
|
||||
}
|
||||
|
||||
// Show long-term database information if applicable
|
||||
var dbInfo = "";
|
||||
let dbInfo = "";
|
||||
if (data.dbid !== false) {
|
||||
dbInfo = divStart + "Database ID: " + data.id + "</div>";
|
||||
}
|
||||
|
||||
// Always show reply info, add reply delay if applicable
|
||||
var replyInfo = "";
|
||||
let replyInfo = "";
|
||||
replyInfo =
|
||||
data.reply.type !== "UNKNOWN"
|
||||
? divStart + "Reply:  " + data.reply.type + "</div>"
|
||||
: divStart + "Reply: No reply received</div>";
|
||||
|
||||
// Show extended DNS error if applicable
|
||||
var edeInfo = "";
|
||||
let edeInfo = "";
|
||||
if (data.ede !== null && data.ede.text !== null) {
|
||||
edeInfo = divStart + "Extended DNS error: <strong";
|
||||
if (dnssec.color !== "") {
|
||||
@@ -396,8 +398,8 @@ function formatInfo(data) {
|
||||
}
|
||||
|
||||
function addSelectSuggestion(name, dict, data) {
|
||||
var obj = $("#" + name + "_filter"),
|
||||
value = "";
|
||||
const obj = $("#" + name + "_filter");
|
||||
let value = "";
|
||||
obj.empty();
|
||||
|
||||
// In order for the placeholder value to appear, we have to have a blank
|
||||
@@ -414,12 +416,12 @@ function addSelectSuggestion(name, dict, data) {
|
||||
}
|
||||
|
||||
// Add data obtained from API
|
||||
for (var key in data) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
for (const key in data) {
|
||||
if (!Object.hasOwn(data, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = data[key];
|
||||
const text = data[key];
|
||||
obj.append($("<option />").val(text).text(text));
|
||||
}
|
||||
|
||||
@@ -432,10 +434,10 @@ function addSelectSuggestion(name, dict, data) {
|
||||
function getSuggestions(dict) {
|
||||
$.get(
|
||||
document.body.dataset.apiurl + "/queries/suggestions",
|
||||
function (data) {
|
||||
for (var key in filters) {
|
||||
if (Object.hasOwnProperty.call(filters, key)) {
|
||||
var f = filters[key];
|
||||
data => {
|
||||
for (const key in filters) {
|
||||
if (Object.hasOwn(filters, key)) {
|
||||
const f = filters[key];
|
||||
addSelectSuggestion(f, dict, data.suggestions[f]);
|
||||
}
|
||||
}
|
||||
@@ -445,10 +447,10 @@ function getSuggestions(dict) {
|
||||
}
|
||||
|
||||
function parseFilters() {
|
||||
var filter = {};
|
||||
for (var key in filters) {
|
||||
if (Object.hasOwnProperty.call(filters, key)) {
|
||||
var f = filters[key];
|
||||
const filter = {};
|
||||
for (const key in filters) {
|
||||
if (Object.hasOwn(filters, key)) {
|
||||
const f = filters[key];
|
||||
filter[f] = $("#" + f + "_filter").val();
|
||||
}
|
||||
}
|
||||
@@ -462,10 +464,10 @@ function filterOn(param, dict) {
|
||||
}
|
||||
|
||||
function getAPIURL(filters) {
|
||||
var apiurl = document.body.dataset.apiurl + "/queries?";
|
||||
for (var key in filters) {
|
||||
if (Object.hasOwnProperty.call(filters, key)) {
|
||||
var filter = filters[key];
|
||||
let apiurl = document.body.dataset.apiurl + "/queries?";
|
||||
for (const key in filters) {
|
||||
if (Object.hasOwn(filters, key)) {
|
||||
const filter = filters[key];
|
||||
if (filterOn(key, filters)) {
|
||||
if (!apiurl.endsWith("?")) apiurl += "&";
|
||||
apiurl += key + "=" + encodeURIComponent(filter);
|
||||
@@ -484,7 +486,7 @@ function getAPIURL(filters) {
|
||||
return encodeURI(apiurl);
|
||||
}
|
||||
|
||||
var liveMode = false;
|
||||
let liveMode = false;
|
||||
$("#live").prop("checked", liveMode);
|
||||
$("#live").on("click", function () {
|
||||
liveMode = $(this).prop("checked");
|
||||
@@ -497,13 +499,13 @@ function liveUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
// Do we want to filter queries?
|
||||
var GETDict = utils.parseQueryString();
|
||||
const GETDict = utils.parseQueryString();
|
||||
|
||||
for (var sel in filters) {
|
||||
if (Object.hasOwnProperty.call(filters, sel)) {
|
||||
var element = filters[sel];
|
||||
for (const sel in filters) {
|
||||
if (Object.hasOwn(filters, sel)) {
|
||||
const element = filters[sel];
|
||||
$("#" + element + "_filter").select2({
|
||||
width: "100%",
|
||||
tags: sel < 4, // Only the first four (client(IP/name), domain, upstream) are allowed to freely specify input
|
||||
@@ -514,7 +516,7 @@ $(function () {
|
||||
}
|
||||
|
||||
getSuggestions(GETDict);
|
||||
var apiURL = getAPIURL(GETDict);
|
||||
const apiURL = getAPIURL(GETDict);
|
||||
|
||||
if ("from" in GETDict) {
|
||||
from = GETDict.from;
|
||||
@@ -533,11 +535,11 @@ $(function () {
|
||||
url: apiURL,
|
||||
error: handleAjaxError,
|
||||
dataSrc: "queries",
|
||||
data: function (d) {
|
||||
data(d) {
|
||||
if (cursor !== null) d.cursor = cursor;
|
||||
},
|
||||
dataFilter: function (d) {
|
||||
var json = jQuery.parseJSON(d);
|
||||
dataFilter(d) {
|
||||
const json = jQuery.parseJSON(d);
|
||||
cursor = json.cursor; // Extract cursor from original data
|
||||
if (liveMode) {
|
||||
utils.setTimer(liveUpdate, REFRESH_INTERVAL.query_log);
|
||||
@@ -558,7 +560,7 @@ $(function () {
|
||||
{
|
||||
data: "time",
|
||||
width: "10%",
|
||||
render: function (data, type) {
|
||||
render(data, type) {
|
||||
if (type === "display") {
|
||||
return moment.unix(data).format("Y-MM-DD [<br class='hidden-lg'>]HH:mm:ss z");
|
||||
}
|
||||
@@ -580,14 +582,14 @@ $(function () {
|
||||
],
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("query_log_table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
stateLoadCallback() {
|
||||
return utils.stateLoadCallback("query_log_table");
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
var querystatus = parseQueryStatus(data);
|
||||
rowCallback(row, data) {
|
||||
const querystatus = parseQueryStatus(data);
|
||||
const dnssec = parseDNSSEC(data);
|
||||
|
||||
if (querystatus.icon !== false) {
|
||||
@@ -608,10 +610,10 @@ $(function () {
|
||||
$(row).addClass(querystatus.blocked === true ? "blocked-row" : "allowed-row");
|
||||
|
||||
// Substitute domain by "." if empty
|
||||
var domain = data.domain === 0 ? "." : data.domain;
|
||||
let domain = data.domain === 0 ? "." : data.domain;
|
||||
|
||||
// Prefix colored DNSSEC icon to domain text
|
||||
var dnssecIcon = "";
|
||||
let dnssecIcon = "";
|
||||
dnssecIcon =
|
||||
'<i class="mr-2 fa fa-fw ' +
|
||||
dnssec.icon +
|
||||
@@ -648,7 +650,7 @@ $(function () {
|
||||
$("td:eq(6)", row).html(querystatus.buttontext);
|
||||
}
|
||||
},
|
||||
initComplete: function () {
|
||||
initComplete() {
|
||||
this.api()
|
||||
.columns()
|
||||
.every(function () {
|
||||
@@ -678,11 +680,11 @@ $(function () {
|
||||
|
||||
// Add event listener for adding domains to the allow-/blocklist
|
||||
$("#all-queries tbody").on("click", "button", function (event) {
|
||||
var button = $(this);
|
||||
var tr = button.parents("tr");
|
||||
var allowButton = button[0].classList.contains("text-green");
|
||||
var denyButton = button[0].classList.contains("text-red");
|
||||
var data = table.row(tr).data();
|
||||
const button = $(this);
|
||||
const tr = button.parents("tr");
|
||||
const allowButton = button[0].classList.contains("text-green");
|
||||
const denyButton = button[0].classList.contains("text-red");
|
||||
const data = table.row(tr).data();
|
||||
if (denyButton) {
|
||||
utils.addFromQueryLog(data.domain, "deny");
|
||||
} else if (allowButton) {
|
||||
@@ -696,8 +698,8 @@ $(function () {
|
||||
|
||||
// Add event listener for opening and closing details, except on rows with "details-row" class
|
||||
$("#all-queries tbody").on("click", "tr:not(.details-row)", function () {
|
||||
var tr = $(this);
|
||||
var row = table.row(tr);
|
||||
const tr = $(this);
|
||||
const row = table.row(tr);
|
||||
|
||||
if (globalThis.getSelection().toString().length > 0) {
|
||||
// This event was triggered by a selection, so don't open the row
|
||||
@@ -737,9 +739,9 @@ function refreshTable() {
|
||||
table.clear();
|
||||
|
||||
// Source data from API
|
||||
var filters = parseFilters();
|
||||
const filters = parseFilters();
|
||||
filters.from = from;
|
||||
filters.until = until;
|
||||
var apiUrl = getAPIURL(filters);
|
||||
const apiUrl = getAPIURL(filters);
|
||||
table.ajax.url(apiUrl).draw();
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/* global utils:false, apiFailure:false */
|
||||
var GETDict = {};
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
let GETDict = {};
|
||||
|
||||
$(() => {
|
||||
GETDict = utils.parseQueryString();
|
||||
if (GETDict.domain !== undefined) {
|
||||
$("input[id^='domain']").val(GETDict.domain);
|
||||
@@ -31,23 +34,23 @@ function doSearch() {
|
||||
return;
|
||||
}
|
||||
|
||||
var verb = partial ? "partially" : "exactly";
|
||||
const verb = partial ? "partially" : "exactly";
|
||||
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: document.body.dataset.apiurl + "/search/" + encodeURIComponent(q),
|
||||
async: false,
|
||||
data: {
|
||||
partial: partial,
|
||||
N: N,
|
||||
partial,
|
||||
N,
|
||||
},
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
ta.empty();
|
||||
ta.show();
|
||||
|
||||
const res = data.search;
|
||||
var result = "";
|
||||
let result = "";
|
||||
const numDomains = res.domains.length;
|
||||
result =
|
||||
"Found " +
|
||||
@@ -91,7 +94,7 @@ function doSearch() {
|
||||
}
|
||||
|
||||
// Group results in res.gravity by res.gravity[].address
|
||||
var grouped = {};
|
||||
const grouped = {};
|
||||
for (const list of res.gravity) {
|
||||
if (grouped[list.address + "_" + list.type] === undefined) {
|
||||
grouped[list.address + "_" + list.type] = [];
|
||||
@@ -190,13 +193,13 @@ function doSearch() {
|
||||
|
||||
ta.append(result);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle enter key
|
||||
$("#domain").on("keypress", function (e) {
|
||||
$("#domain").on("keypress", e => {
|
||||
if (e.which === 13) {
|
||||
// Enter was pressed, and the input has focus
|
||||
doSearch();
|
||||
@@ -204,6 +207,6 @@ $("#domain").on("keypress", function (e) {
|
||||
});
|
||||
|
||||
// Handle search buttons
|
||||
$("button[id='btnSearch']").on("click", function () {
|
||||
$("button[id='btnSearch']").on("click", () => {
|
||||
doSearch();
|
||||
});
|
||||
|
||||
@@ -8,18 +8,20 @@
|
||||
/* global utils:false, apiFailure: false, applyCheckboxRadioStyle: false, saveSettings:false */
|
||||
/* exported createDynamicConfigTabs */
|
||||
|
||||
"use strict";
|
||||
|
||||
function addAllowedValues(allowed) {
|
||||
if (typeof allowed === "object") {
|
||||
return (
|
||||
"<p>Available options:</p><ul><li>" +
|
||||
allowed
|
||||
.map(function (option) {
|
||||
return "<code>" + option.item + "</code>: " + utils.escapeHtml(option.description);
|
||||
})
|
||||
.map(option => "<code>" + option.item + "</code>: " + utils.escapeHtml(option.description))
|
||||
.join("</li><li>") +
|
||||
"</li></ul>"
|
||||
);
|
||||
} else if (typeof allowed === "string") {
|
||||
}
|
||||
|
||||
if (typeof allowed === "string") {
|
||||
return "<p><small>Allowed value: " + utils.escapeHtml(allowed) + "</small></p>";
|
||||
}
|
||||
}
|
||||
@@ -212,7 +214,7 @@ function valueDetails(key, value) {
|
||||
case "enum (unsigned integer)": // fallthrough
|
||||
case "enum (string)": {
|
||||
content += '<div class="col-sm-12">';
|
||||
value.allowed.forEach(function (option, i) {
|
||||
for (const [i, option] of value.allowed.entries()) {
|
||||
content +=
|
||||
"<div>" +
|
||||
// Radio button
|
||||
@@ -227,7 +229,8 @@ function valueDetails(key, value) {
|
||||
// Paragraph with description
|
||||
`<p class="help-block">${option.description}</p>` +
|
||||
"</div>";
|
||||
});
|
||||
}
|
||||
|
||||
content += "</div>";
|
||||
|
||||
break;
|
||||
@@ -262,15 +265,16 @@ function valueDetails(key, value) {
|
||||
function generateRow(topic, key, value) {
|
||||
// If the value is an object, we need to recurse
|
||||
if (!("description" in value)) {
|
||||
Object.keys(value).forEach(function (subkey) {
|
||||
var subvalue = value[subkey];
|
||||
for (const subkey of Object.keys(value)) {
|
||||
const subvalue = value[subkey];
|
||||
generateRow(topic, key + "." + subkey, subvalue);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// else: we have a setting we can display
|
||||
var box =
|
||||
const box =
|
||||
'<div class="box settings-box">' +
|
||||
'<div class="box-header with-border">' +
|
||||
'<h3 class="box-title" data-key="' +
|
||||
@@ -289,8 +293,8 @@ function generateRow(topic, key, value) {
|
||||
valueDetails(key, value) +
|
||||
"</div></div> ";
|
||||
|
||||
var topKey = key.split(".")[0];
|
||||
var elem = $("#advanced-content-" + topKey + "-flex");
|
||||
const topKey = key.split(".")[0];
|
||||
const elem = $("#advanced-content-" + topKey + "-flex");
|
||||
elem.append(box);
|
||||
}
|
||||
|
||||
@@ -298,10 +302,10 @@ function createDynamicConfigTabs() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/config?detailed=true",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
// Create the tabs for the advanced dynamic config topics
|
||||
Object.keys(data.topics).forEach(function (n) {
|
||||
var topic = data.topics[n];
|
||||
for (const n of Object.keys(data.topics)) {
|
||||
const topic = data.topics[n];
|
||||
|
||||
$("#advanced-settings-tabs").append(`
|
||||
<div id="advanced-content-${topic.name}" role="tabpanel" class="tab-pane fade">
|
||||
@@ -318,27 +322,28 @@ function createDynamicConfigTabs() {
|
||||
<a href="#advanced-content-${topic.name}" class="btn btn-default" aria-controls="advanced-content-${topic.name}" role="pill" data-toggle="pill">${topic.description.replace(" settings", "")}</a>
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
// Dynamically fill the tabs with config topics
|
||||
Object.keys(data.config).forEach(function (topic) {
|
||||
var value = data.config[topic];
|
||||
for (const topic of Object.keys(data.config)) {
|
||||
const value = data.config[topic];
|
||||
generateRow(topic, topic, value);
|
||||
});
|
||||
}
|
||||
|
||||
$("#advanced-overlay").hide();
|
||||
|
||||
// Select the first tab and show the content
|
||||
$("#advanced-settings-menu ul li:first-child").addClass("active");
|
||||
$("#advanced-settings-tabs > div:first-child").addClass("active in");
|
||||
|
||||
$("button[id='save']").on("click", function () {
|
||||
$("button[id='save']").on("click", () => {
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
applyCheckboxRadioStyle();
|
||||
applyOnlyChanged();
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -401,7 +406,7 @@ function applyOnlyChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
createDynamicConfigTabs();
|
||||
initOnlyChanged();
|
||||
});
|
||||
|
||||
@@ -7,16 +7,18 @@
|
||||
|
||||
/* global utils:false, setConfigValues: false, apiFailure: false, QRious: false */
|
||||
|
||||
var apiSessionsTable = null;
|
||||
var ownSessionID = null;
|
||||
var deleted = 0;
|
||||
var TOTPdata = null;
|
||||
var apppwSet = false;
|
||||
"use strict";
|
||||
|
||||
let apiSessionsTable = null;
|
||||
let ownSessionID = null;
|
||||
let deleted = 0;
|
||||
let TOTPdata = null;
|
||||
let apppwSet = false;
|
||||
|
||||
function renderBool(data, type) {
|
||||
// Display and search content
|
||||
if (type === "display" || type === "filter") {
|
||||
var icon = "fa-xmark text-danger";
|
||||
let icon = "fa-xmark text-danger";
|
||||
if (data === true) {
|
||||
icon = "fa-check text-success";
|
||||
}
|
||||
@@ -28,7 +30,7 @@ function renderBool(data, type) {
|
||||
return data;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
apiSessionsTable = $("#APISessionsTable").DataTable({
|
||||
ajax: {
|
||||
url: document.body.dataset.apiurl + "/auth/sessions",
|
||||
@@ -54,7 +56,7 @@ $(function () {
|
||||
targets: 0,
|
||||
orderable: false,
|
||||
className: "select-checkbox",
|
||||
render: function () {
|
||||
render() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
@@ -63,19 +65,19 @@ $(function () {
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
$('button[id^="deleteSession_"]').on("click", deleteThisSession);
|
||||
|
||||
// Hide buttons if all messages were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
const hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
rowCallback(row, data) {
|
||||
$(row).attr("data-id", data.id);
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteSession_' +
|
||||
data.id +
|
||||
'" data-del-id="' +
|
||||
@@ -127,7 +129,7 @@ $(function () {
|
||||
text: '<span class="far fa-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectAll",
|
||||
action: function () {
|
||||
action() {
|
||||
apiSessionsTable.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -135,7 +137,7 @@ $(function () {
|
||||
text: '<span class="far fa-plus-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectMore",
|
||||
action: function () {
|
||||
action() {
|
||||
apiSessionsTable.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -149,12 +151,12 @@ $(function () {
|
||||
text: '<span class="far fa-trash-alt"></span>',
|
||||
titleAttr: "Delete Selected",
|
||||
className: "btn-sm datatable-bt deleteSelected",
|
||||
action: function () {
|
||||
action() {
|
||||
// For each ".selected" row ...
|
||||
var ids = [];
|
||||
const ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push(parseInt($(this).attr("data-id"), 10));
|
||||
ids.push(Number.parseInt($(this).attr("data-id"), 10));
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
deleteMultipleSessions(ids);
|
||||
@@ -176,11 +178,11 @@ $(function () {
|
||||
},
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("api-sessions-table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback("api-sessions-table");
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback("api-sessions-table");
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
return null;
|
||||
@@ -190,7 +192,7 @@ $(function () {
|
||||
return data;
|
||||
},
|
||||
});
|
||||
apiSessionsTable.on("init select deselect", function () {
|
||||
apiSessionsTable.on("init select deselect", () => {
|
||||
utils.changeTableButtonStates(apiSessionsTable);
|
||||
});
|
||||
});
|
||||
@@ -198,7 +200,7 @@ $(function () {
|
||||
function deleteThisSession() {
|
||||
// This function is called when a red trash button is clicked
|
||||
// We get the ID of the current item from the data-del-id attribute
|
||||
const thisID = parseInt($(this).attr("data-del-id"), 10);
|
||||
const thisID = Number.parseInt($(this).attr("data-del-id"), 10);
|
||||
deleted = 0;
|
||||
deleteOneSession(thisID, 1, false);
|
||||
}
|
||||
@@ -212,13 +214,13 @@ function deleteMultipleSessions(ids) {
|
||||
|
||||
// Exploit prevention: Return early for non-numeric IDs
|
||||
for (const id of ids) {
|
||||
if (Object.hasOwnProperty.call(ids, id) && isNaN(ids[id])) return;
|
||||
// TODO Fix eslint
|
||||
// eslint-disable-next-line unicorn/prefer-number-properties
|
||||
if (Object.hasOwn(ids, id) && isNaN(ids[id])) return;
|
||||
}
|
||||
|
||||
// Convert all ids to integers
|
||||
ids = ids.map(function (value) {
|
||||
return parseInt(value, 10);
|
||||
});
|
||||
ids = ids.map(value => Number.parseInt(value, 10));
|
||||
|
||||
// Check if own session is selected and remove it when deleting multiple
|
||||
// We need this only when multiple sessions are removed to ensure we do not
|
||||
@@ -228,9 +230,7 @@ function deleteMultipleSessions(ids) {
|
||||
if (ids.includes(ownSessionID) && ids.length > 1) {
|
||||
ownSessionDelete = true;
|
||||
// Strip own session ID from array
|
||||
ids = ids.filter(function (value) {
|
||||
return value !== ownSessionID;
|
||||
});
|
||||
ids = ids.filter(value => value !== ownSessionID);
|
||||
}
|
||||
|
||||
// Loop through IDs and delete them
|
||||
@@ -250,7 +250,7 @@ function deleteOneSession(id, len, ownSessionDelete) {
|
||||
url: document.body.dataset.apiurl + "/auth/session/" + id,
|
||||
method: "DELETE",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
// Do not reload page when deleting multiple sessions
|
||||
if (++deleted < len) return;
|
||||
|
||||
@@ -265,7 +265,7 @@ function deleteOneSession(id, len, ownSessionDelete) {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -274,7 +274,7 @@ function processWebServerConfig() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/config/webserver?detailed=true",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
setConfigValues("webserver", "webserver", data.config.webserver);
|
||||
if (data.config.webserver.api.app_pwhash.value.length > 0) {
|
||||
apppwSet = true;
|
||||
@@ -284,19 +284,19 @@ function processWebServerConfig() {
|
||||
$("#apppw_submit").addClass("btn-warning");
|
||||
} else $("#apppw_clear").hide();
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
$("#modal-totp").on("shown.bs.modal", function () {
|
||||
$("#modal-totp").on("shown.bs.modal", () => {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/auth/totp",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
TOTPdata = data.totp;
|
||||
$("#totp_secret").text(data.totp.secret);
|
||||
var qrlink =
|
||||
const qrlink =
|
||||
"otpauth://totp/" +
|
||||
data.totp.issuer +
|
||||
":" +
|
||||
@@ -321,28 +321,28 @@ $("#modal-totp").on("shown.bs.modal", function () {
|
||||
$("#qrcode-spinner").hide();
|
||||
$("#qrcode").show();
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
});
|
||||
|
||||
var apppwhash = null;
|
||||
$("#modal-apppw").on("shown.bs.modal", function () {
|
||||
let apppwhash = null;
|
||||
$("#modal-apppw").on("shown.bs.modal", () => {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/auth/app",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
apppwhash = data.app.hash;
|
||||
$("#password_code").text(data.app.password);
|
||||
$("#password_display").removeClass("hidden");
|
||||
$("#password-spinner").hide();
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
});
|
||||
|
||||
$("#apppw_submit").on("click", function () {
|
||||
$("#apppw_submit").on("click", () => {
|
||||
// Enable app password
|
||||
if (!apppwSet) {
|
||||
return setAppPassword();
|
||||
@@ -353,7 +353,7 @@ $("#apppw_submit").on("click", function () {
|
||||
text: "Are you sure you want to replace your previous app password? You will need to re-login to continue using the web interface.",
|
||||
title: "Confirmation required",
|
||||
confirm: setAppPassword,
|
||||
cancel: function () {
|
||||
cancel() {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, replace password",
|
||||
@@ -365,7 +365,7 @@ $("#apppw_submit").on("click", function () {
|
||||
});
|
||||
});
|
||||
|
||||
$("#apppw_clear").on("click", function () {
|
||||
$("#apppw_clear").on("click", () => {
|
||||
// Disable app password
|
||||
apppwhash = "";
|
||||
setAppPassword();
|
||||
@@ -380,7 +380,7 @@ function setAppPassword() {
|
||||
data: JSON.stringify({ config: { webserver: { api: { app_pwhash: apppwhash } } } }),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
$("#modal-apppw").modal("hide");
|
||||
const verb = apppwhash.length > 0 ? "enabled" : "disabled";
|
||||
const verb2 = apppwhash.length > 0 ? "will" : "may";
|
||||
@@ -393,7 +393,7 @@ function setAppPassword() {
|
||||
);
|
||||
location.reload();
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -408,12 +408,12 @@ $("#password_code").on("mouseout blur", function () {
|
||||
});
|
||||
|
||||
// Trigger keyup event when pasting into the TOTP code input field
|
||||
$("#totp_code").on("paste", function (e) {
|
||||
$("#totp_code").on("paste", e => {
|
||||
$(e.target).keyup();
|
||||
});
|
||||
|
||||
$("#totp_code").on("keyup", function () {
|
||||
var code = parseInt($(this).val(), 10);
|
||||
const code = Number.parseInt($(this).val(), 10);
|
||||
if (TOTPdata.codes.includes(code)) {
|
||||
$("#totp_div").removeClass("has-error");
|
||||
$("#totp_div").addClass("has-success");
|
||||
@@ -433,12 +433,12 @@ function setTOTPSecret(secret) {
|
||||
data: JSON.stringify({ config: { webserver: { api: { totp_secret: secret } } } }),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
$("#button-enable-totp").addClass("hidden");
|
||||
$("#button-disable-totp").removeClass("hidden");
|
||||
$("#totp_code").val("");
|
||||
$("#modal-totp").modal("hide");
|
||||
var verb = secret.length > 0 ? "enabled" : "disabled";
|
||||
const verb = secret.length > 0 ? "enabled" : "disabled";
|
||||
alert(
|
||||
"Two-factor authentication has been " +
|
||||
verb +
|
||||
@@ -446,12 +446,12 @@ function setTOTPSecret(secret) {
|
||||
);
|
||||
location.reload();
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
$("#totp_submit").on("click", function () {
|
||||
$("#totp_submit").on("click", () => {
|
||||
// Enable TOTP
|
||||
setTOTPSecret(TOTPdata.secret);
|
||||
});
|
||||
@@ -459,11 +459,11 @@ $("#totp_submit").on("click", function () {
|
||||
$("#button-disable-totp").confirm({
|
||||
text: "Are you sure you want to disable 2FA authentication on your Pi-hole?",
|
||||
title: "Confirmation required",
|
||||
confirm: function () {
|
||||
confirm() {
|
||||
// Disable TOTP
|
||||
setTOTPSecret("");
|
||||
},
|
||||
cancel: function () {
|
||||
cancel() {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, disable 2FA",
|
||||
@@ -474,12 +474,12 @@ $("#button-disable-totp").confirm({
|
||||
dialogClass: "modal-dialog",
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
processWebServerConfig();
|
||||
// Check if TOTP is enabled
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/auth",
|
||||
}).done(function (data) {
|
||||
}).done(data => {
|
||||
if (data.session.totp === false) $("#button-enable-totp").removeClass("hidden");
|
||||
else $("#button-disable-totp").removeClass("hidden");
|
||||
});
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
|
||||
/* global utils:false, setConfigValues: false, apiFailure: false */
|
||||
|
||||
var dhcpLeaesTable = null,
|
||||
toasts = {};
|
||||
"use strict";
|
||||
|
||||
let dhcpLeaesTable = null;
|
||||
const toasts = {};
|
||||
|
||||
// DHCP leases tooltips
|
||||
$(function () {
|
||||
$(() => {
|
||||
$('[data-toggle="tooltip"]').tooltip({ html: true, container: "body" });
|
||||
});
|
||||
|
||||
@@ -29,7 +31,7 @@ function renderHostnameCLID(data, type) {
|
||||
return data;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
dhcpLeaesTable = $("#DHCPLeasesTable").DataTable({
|
||||
ajax: {
|
||||
url: document.body.dataset.apiurl + "/dhcp/leases",
|
||||
@@ -51,7 +53,7 @@ $(function () {
|
||||
targets: 0,
|
||||
orderable: false,
|
||||
className: "select-checkbox",
|
||||
render: function () {
|
||||
render() {
|
||||
return "";
|
||||
},
|
||||
},
|
||||
@@ -60,19 +62,19 @@ $(function () {
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
$('button[id^="deleteLease_"]').on("click", deleteLease);
|
||||
|
||||
// Hide buttons if all messages were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
const hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
rowCallback(row, data) {
|
||||
$(row).attr("data-id", data.ip);
|
||||
var button =
|
||||
const button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteLease_' +
|
||||
data.ip +
|
||||
'" data-del-ip="' +
|
||||
@@ -92,7 +94,7 @@ $(function () {
|
||||
text: '<span class="far fa-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectAll",
|
||||
action: function () {
|
||||
action() {
|
||||
dhcpLeaesTable.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -100,7 +102,7 @@ $(function () {
|
||||
text: '<span class="far fa-plus-square"></span>',
|
||||
titleAttr: "Select All",
|
||||
className: "btn-sm datatable-bt selectMore",
|
||||
action: function () {
|
||||
action() {
|
||||
dhcpLeaesTable.rows({ page: "current" }).select();
|
||||
},
|
||||
},
|
||||
@@ -114,7 +116,7 @@ $(function () {
|
||||
text: '<span class="far fa-trash-alt"></span>',
|
||||
titleAttr: "Delete Selected",
|
||||
className: "btn-sm datatable-bt deleteSelected",
|
||||
action: function () {
|
||||
action() {
|
||||
// For each ".selected" row ...
|
||||
$("tr.selected").each(function () {
|
||||
// ... delete the row identified by "data-id".
|
||||
@@ -138,11 +140,11 @@ $(function () {
|
||||
},
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback("dhcp-leases-table", data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback("dhcp-leases-table");
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback("dhcp-leases-table");
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
return null;
|
||||
@@ -152,7 +154,7 @@ $(function () {
|
||||
return data;
|
||||
},
|
||||
});
|
||||
dhcpLeaesTable.on("init select deselect", function () {
|
||||
dhcpLeaesTable.on("init select deselect", () => {
|
||||
utils.changeTableButtonStates(dhcpLeaesTable);
|
||||
});
|
||||
});
|
||||
@@ -170,7 +172,7 @@ function delLease(ip) {
|
||||
url: document.body.dataset.apiurl + "/dhcp/leases/" + encodeURIComponent(ip),
|
||||
method: "DELETE",
|
||||
})
|
||||
.done(function (response) {
|
||||
.done(response => {
|
||||
utils.enableAll();
|
||||
if (response === undefined) {
|
||||
utils.showAlert(
|
||||
@@ -195,7 +197,7 @@ function delLease(ip) {
|
||||
dhcpLeaesTable.rows().deselect();
|
||||
utils.changeTableButtonStates(dhcpLeaesTable);
|
||||
})
|
||||
.fail(function (jqXHR, exception) {
|
||||
.fail((jqXHR, exception) => {
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"error",
|
||||
@@ -216,15 +218,15 @@ function processDHCPConfig() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/config/dhcp?detailed=true",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
fillDHCPhosts(data.config.dhcp.hosts);
|
||||
setConfigValues("dhcp", "dhcp", data.config.dhcp);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
processDHCPConfig();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
/* global utils: false, apiFailure:false, setConfigValues: false */
|
||||
|
||||
"use strict";
|
||||
|
||||
function hostsDomain(data) {
|
||||
// Split record in format IP NAME1 [NAME2 [NAME3 [NAME...]]]
|
||||
// We split both on spaces and tabs to support both formats
|
||||
@@ -46,7 +48,7 @@ function CNAMEttl(data) {
|
||||
}
|
||||
|
||||
function populateDataTable(endpoint) {
|
||||
var columns = "";
|
||||
let columns = "";
|
||||
if (endpoint === "hosts") {
|
||||
columns = [
|
||||
{ data: null, render: hostsDomain },
|
||||
@@ -62,10 +64,10 @@ function populateDataTable(endpoint) {
|
||||
];
|
||||
}
|
||||
|
||||
var setByEnv = false;
|
||||
const setByEnv = false;
|
||||
$.ajax({
|
||||
url: `/api/config/dns/${endpoint}?detailed=true`,
|
||||
}).done(function (data) {
|
||||
}).done(data => {
|
||||
// Set the title icons if needed
|
||||
setConfigValues("dns", "dns", data.config.dns);
|
||||
|
||||
@@ -82,22 +84,22 @@ function populateDataTable(endpoint) {
|
||||
dataSrc: `config.dns.${endpoint}`,
|
||||
},
|
||||
autoWidth: false,
|
||||
columns: columns,
|
||||
columns,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: "_all",
|
||||
render: $.fn.dataTable.render.text(),
|
||||
},
|
||||
],
|
||||
drawCallback: function () {
|
||||
drawCallback() {
|
||||
$(`button[id^="delete${endpoint}"]`).on("click", deleteRecord);
|
||||
|
||||
// Remove visible dropdown to prevent orphaning
|
||||
$("body > .bootstrap-select.dropdown").remove();
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
rowCallback(row, data) {
|
||||
$(row).attr("data-id", data);
|
||||
var button = `<button type="button"
|
||||
const button = `<button type="button"
|
||||
class="btn btn-danger btn-xs"
|
||||
id="delete${endpoint}${utils.hexEncode(data)}"
|
||||
data-tag="${data}"
|
||||
@@ -118,7 +120,7 @@ function populateDataTable(endpoint) {
|
||||
[10, 25, 50, 100, "All"],
|
||||
],
|
||||
language: {
|
||||
emptyTable: function () {
|
||||
emptyTable() {
|
||||
return endpoint === "hosts"
|
||||
? "No local DNS records defined."
|
||||
: "No local CNAME records defined.";
|
||||
@@ -127,11 +129,11 @@ function populateDataTable(endpoint) {
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
processing: true,
|
||||
stateSaveCallback: function (settings, data) {
|
||||
stateSaveCallback(settings, data) {
|
||||
utils.stateSaveCallback(`${endpoint}-records-table`, data);
|
||||
},
|
||||
stateLoadCallback: function () {
|
||||
var data = utils.stateLoadCallback(`${endpoint}-records-table`);
|
||||
stateLoadCallback() {
|
||||
const data = utils.stateLoadCallback(`${endpoint}-records-table`);
|
||||
// Return if not available
|
||||
if (data === null) {
|
||||
return null;
|
||||
@@ -143,7 +145,7 @@ function populateDataTable(endpoint) {
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
populateDataTable("hosts");
|
||||
populateDataTable("cnameRecords");
|
||||
});
|
||||
@@ -159,15 +161,15 @@ function delHosts(elem) {
|
||||
const url = document.body.dataset.apiurl + "/config/dns/hosts/" + encodeURIComponent(elem);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
url,
|
||||
method: "DELETE",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "fas fa-trash-alt", "Successfully deleted DNS record", elem);
|
||||
$("#hosts-Table").DataTable().ajax.reload(null, false);
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
.fail((data, exception) => {
|
||||
utils.enableAll();
|
||||
apiFailure(data);
|
||||
utils.showAlert(
|
||||
@@ -186,10 +188,10 @@ function delCNAME(elem) {
|
||||
const url = document.body.dataset.apiurl + "/config/dns/cnameRecords/" + encodeURIComponent(elem);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
url,
|
||||
method: "DELETE",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"success",
|
||||
@@ -199,7 +201,7 @@ function delCNAME(elem) {
|
||||
);
|
||||
$("#cnameRecords-Table").DataTable().ajax.reload(null, false);
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
.fail((data, exception) => {
|
||||
utils.enableAll();
|
||||
apiFailure(data);
|
||||
utils.showAlert(
|
||||
@@ -212,24 +214,24 @@ function delCNAME(elem) {
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$("#btnAdd-host").on("click", function () {
|
||||
$(document).ready(() => {
|
||||
$("#btnAdd-host").on("click", () => {
|
||||
utils.disableAll();
|
||||
const elem = $("#Hip").val() + " " + $("#Hdomain").val();
|
||||
const url = document.body.dataset.apiurl + "/config/dns/hosts/" + encodeURIComponent(elem);
|
||||
utils.showAlert("info", "", "Adding DNS record...", elem);
|
||||
$.ajax({
|
||||
url: url,
|
||||
url,
|
||||
method: "PUT",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "fas fa-plus", "Successfully added DNS record", elem);
|
||||
$("#Hdomain").val("");
|
||||
$("#Hip").val("");
|
||||
$("#hosts-Table").DataTable().ajax.reload(null, false);
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
.fail((data, exception) => {
|
||||
utils.enableAll();
|
||||
apiFailure(data);
|
||||
utils.showAlert("error", "", "Error while deleting DNS record", data.responseText);
|
||||
@@ -237,26 +239,28 @@ $(document).ready(function () {
|
||||
});
|
||||
});
|
||||
|
||||
$("#btnAdd-cname").on("click", function () {
|
||||
$("#btnAdd-cname").on("click", () => {
|
||||
utils.disableAll();
|
||||
var elem = $("#Cdomain").val() + "," + $("#Ctarget").val();
|
||||
var ttlVal = parseInt($("#Cttl").val(), 10);
|
||||
let elem = $("#Cdomain").val() + "," + $("#Ctarget").val();
|
||||
const ttlVal = Number.parseInt($("#Cttl").val(), 10);
|
||||
// TODO Fix eslint
|
||||
// eslint-disable-next-line unicorn/prefer-number-properties
|
||||
if (isFinite(ttlVal) && ttlVal >= 0) elem += "," + ttlVal;
|
||||
const url =
|
||||
document.body.dataset.apiurl + "/config/dns/cnameRecords/" + encodeURIComponent(elem);
|
||||
utils.showAlert("info", "", "Adding DNS record...", elem);
|
||||
$.ajax({
|
||||
url: url,
|
||||
url,
|
||||
method: "PUT",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "fas fa-plus", "Successfully added CNAME record", elem);
|
||||
$("#Cdomain").val("");
|
||||
$("#Ctarget").val("");
|
||||
$("#cnameRecords-Table").DataTable().ajax.reload(null, false);
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
.fail((data, exception) => {
|
||||
utils.enableAll();
|
||||
apiFailure(data);
|
||||
utils.showAlert("error", "", "Error while deleting CNAME record", data.responseText);
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
|
||||
/* global applyCheckboxRadioStyle:false, setConfigValues: false, apiFailure: false */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Remove an element from an array (inline)
|
||||
function removeFromArray(arr, what) {
|
||||
var found = arr.indexOf(what);
|
||||
let found = arr.indexOf(what);
|
||||
|
||||
while (found !== -1) {
|
||||
arr.splice(found, 1);
|
||||
@@ -18,16 +20,16 @@ function removeFromArray(arr, what) {
|
||||
}
|
||||
|
||||
function fillDNSupstreams(value, servers) {
|
||||
var disabledStr = "";
|
||||
let disabledStr = "";
|
||||
if (value.flags.env_var === true) {
|
||||
$("#DNSupstreamsTextfield").prop("disabled", true);
|
||||
disabledStr = 'disabled="Disabled"';
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
var customServers = value.value.length;
|
||||
servers.forEach(element => {
|
||||
var row = "<tr>";
|
||||
let i = 0;
|
||||
let customServers = value.value.length;
|
||||
for (const element of servers) {
|
||||
let row = "<tr>";
|
||||
// Build checkboxes for IPv4 and IPv6
|
||||
const addresses = [element.v4, element.v6];
|
||||
// Loop over address types (IPv4, IPv6)
|
||||
@@ -36,7 +38,7 @@ function fillDNSupstreams(value, servers) {
|
||||
// Loop over available addresses (up to 2)
|
||||
for (let index = 0; index < 2; index++) {
|
||||
if (address.length > index) {
|
||||
var checkedStr = "";
|
||||
let checkedStr = "";
|
||||
if (
|
||||
value.value.includes(address[index]) ||
|
||||
value.value.includes(address[index] + "#53")
|
||||
@@ -65,14 +67,14 @@ function fillDNSupstreams(value, servers) {
|
||||
|
||||
// Add row to table
|
||||
$("#DNSupstreamsTable").append(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Add event listener to checkboxes
|
||||
$("input[id^='DNSupstreams-']").on("change", function () {
|
||||
var upstreams = $("#DNSupstreamsTextfield").val().split("\n");
|
||||
var customServers = 0;
|
||||
$("input[id^='DNSupstreams-']").on("change", () => {
|
||||
const upstreams = $("#DNSupstreamsTextfield").val().split("\n");
|
||||
let customServers = 0;
|
||||
$("#DNSupstreamsTable input").each(function () {
|
||||
var title = $(this).closest("td").attr("title");
|
||||
const title = $(this).closest("td").attr("title");
|
||||
if (this.checked && !upstreams.includes(title)) {
|
||||
// Add server to array
|
||||
upstreams.push(title);
|
||||
@@ -99,9 +101,9 @@ function fillDNSupstreams(value, servers) {
|
||||
applyCheckboxRadioStyle();
|
||||
}
|
||||
|
||||
function setInterfaceName(interface) {
|
||||
$("#interface-name-1").text(interface);
|
||||
$("#interface-name-2").text(interface);
|
||||
function setInterfaceName(name) {
|
||||
$("#interface-name-1").text(name);
|
||||
$("#interface-name-2").text(name);
|
||||
}
|
||||
|
||||
// Update the textfield with all (incl. custom) upstream servers
|
||||
@@ -116,17 +118,17 @@ function processDNSConfig() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/config/dns?detailed=true", // We need the detailed output to get the DNS server list
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
// Initialize the DNS upstreams
|
||||
fillDNSupstreams(data.config.dns.upstreams, data.dns_servers);
|
||||
setInterfaceName(data.config.dns.interface.value);
|
||||
setConfigValues("dns", "dns", data.config.dns);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
processDNSConfig();
|
||||
});
|
||||
|
||||
@@ -7,18 +7,20 @@
|
||||
|
||||
/* global setConfigValues: false, apiFailure: false */
|
||||
|
||||
"use strict";
|
||||
|
||||
function getConfig() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/config/?detailed=true",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
setConfigValues("", "", data.config);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).ready(() => {
|
||||
getConfig();
|
||||
});
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
|
||||
/* global apiFailure:false, Chart:false, THEME_COLORS:false, customTooltips:false, htmlLegendPlugin:false,doughnutTooltip:false, ChartDeferred:false, REFRESH_INTERVAL: false, utils: false */
|
||||
|
||||
var hostinfoTimer = null;
|
||||
var cachePieChart = null;
|
||||
var cacheSize = 0,
|
||||
cacheEntries = 0;
|
||||
"use strict";
|
||||
|
||||
let hostinfoTimer = null;
|
||||
let cachePieChart = null;
|
||||
let cacheSize = 0;
|
||||
let cacheEntries = 0;
|
||||
|
||||
// Register the ChartDeferred plugin to all charts:
|
||||
Chart.register(ChartDeferred);
|
||||
@@ -20,34 +22,37 @@ Chart.defaults.set("plugins.deferred", {
|
||||
});
|
||||
|
||||
function updateCachePie(data) {
|
||||
var v = [],
|
||||
c = [],
|
||||
k = [],
|
||||
i = 0;
|
||||
const v = [];
|
||||
const c = [];
|
||||
const k = [];
|
||||
let i = 0;
|
||||
|
||||
// Compute total number of cache entries
|
||||
cacheEntries = 0;
|
||||
Object.keys(data).forEach(function (item) {
|
||||
for (const item of Object.keys(data)) {
|
||||
cacheEntries += data[item].valid;
|
||||
cacheEntries += data[item].stale;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort data by value, put OTHER always as last
|
||||
var sorted = Object.keys(data).sort(function (a, b) {
|
||||
const sorted = Object.keys(data).sort((a, b) => {
|
||||
if (a === "OTHER") {
|
||||
return 1;
|
||||
} else if (b === "OTHER") {
|
||||
return -1;
|
||||
} else {
|
||||
return data[b].valid + data[b].stale - (data[a].valid + data[a].stale);
|
||||
}
|
||||
|
||||
if (b === "OTHER") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return data[b].valid + data[b].stale - (data[a].valid + data[a].stale);
|
||||
});
|
||||
|
||||
// Rebuild data object
|
||||
var tmp = {};
|
||||
sorted.forEach(function (item) {
|
||||
const tmp = {};
|
||||
for (const item of sorted) {
|
||||
tmp[item] = data[item];
|
||||
});
|
||||
}
|
||||
|
||||
data = tmp;
|
||||
|
||||
// Add empty space to chart
|
||||
@@ -55,7 +60,7 @@ function updateCachePie(data) {
|
||||
data.empty.valid = cacheSize - cacheEntries;
|
||||
|
||||
// Fill chart with data
|
||||
Object.keys(data).forEach(function (item) {
|
||||
for (const item of Object.keys(data)) {
|
||||
if (data[item].valid > 0) {
|
||||
v.push((100 * data[item].valid) / cacheSize);
|
||||
c.push(item !== "empty" ? THEME_COLORS[i++ % THEME_COLORS.length] : "#80808040");
|
||||
@@ -68,10 +73,10 @@ function updateCachePie(data) {
|
||||
c.push(THEME_COLORS[i++ % THEME_COLORS.length]);
|
||||
k.push(item + " (stale)");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Build a single dataset with the data to be pushed
|
||||
var dd = { data: v, backgroundColor: c };
|
||||
const dd = { data: v, backgroundColor: c };
|
||||
// and push it at once
|
||||
cachePieChart.data.datasets[0] = dd;
|
||||
cachePieChart.data.labels = k;
|
||||
@@ -85,9 +90,9 @@ function updateHostInfo() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/info/host",
|
||||
})
|
||||
.done(function (data) {
|
||||
var host = data.host;
|
||||
var uname = host.uname;
|
||||
.done(data => {
|
||||
const host = data.host;
|
||||
const uname = host.uname;
|
||||
if (uname.domainname !== "(none)") {
|
||||
$("#sysinfo-hostname").text(uname.nodename + "." + uname.domainname);
|
||||
} else {
|
||||
@@ -108,7 +113,7 @@ function updateHostInfo() {
|
||||
clearTimeout(hostinfoTimer);
|
||||
hostinfoTimer = utils.setTimer(updateHostInfo, REFRESH_INTERVAL.hosts);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -116,7 +121,7 @@ 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 = {};
|
||||
const cacheData = {};
|
||||
for (const [key, val] of Object.entries(data)) {
|
||||
if (prefix === "sysinfo-dns-cache-content-") {
|
||||
// Create table row for each DNS cache entry
|
||||
@@ -151,14 +156,14 @@ function setMetrics(data, prefix) {
|
||||
}
|
||||
}
|
||||
|
||||
var metricsTimer = null;
|
||||
let metricsTimer = null;
|
||||
|
||||
function updateMetrics() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/info/metrics",
|
||||
})
|
||||
.done(function (data) {
|
||||
var metrics = data.metrics;
|
||||
.done(data => {
|
||||
const metrics = data.metrics;
|
||||
$("#dns-cache-table").empty();
|
||||
|
||||
// Set global cache size
|
||||
@@ -175,7 +180,7 @@ function updateMetrics() {
|
||||
clearTimeout(metricsTimer);
|
||||
metricsTimer = utils.setTimer(updateMetrics, REFRESH_INTERVAL.metrics);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -198,10 +203,10 @@ function getLoggingButton() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/config/dns/queryLogging",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
showQueryLoggingButton(data.config.dns.queryLogging);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
}
|
||||
@@ -212,15 +217,15 @@ $(".confirm-restartdns").confirm({
|
||||
"This will clear the DNS cache and may temporarily interrupt your internet connection.<br>" +
|
||||
"Furthermore, you will be logged out of the web interface as consequence of this action.",
|
||||
title: "Confirmation required",
|
||||
confirm: function () {
|
||||
confirm() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/action/restartdns",
|
||||
type: "POST",
|
||||
}).fail(function (data) {
|
||||
}).fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
},
|
||||
cancel: function () {
|
||||
cancel() {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, restart DNS server",
|
||||
@@ -236,15 +241,15 @@ $(".confirm-flushlogs").confirm({
|
||||
"Are you sure you want to flush your logs?<br><br>" +
|
||||
"<strong>This will clear all logs and cannot be undone.</strong>",
|
||||
title: "Confirmation required",
|
||||
confirm: function () {
|
||||
confirm() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/action/flush/logs",
|
||||
type: "POST",
|
||||
}).fail(function (data) {
|
||||
}).fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
},
|
||||
cancel: function () {
|
||||
cancel() {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, flush logs",
|
||||
@@ -260,15 +265,15 @@ $(".confirm-flusharp").confirm({
|
||||
"Are you sure you want to flush your network table?<br><br>" +
|
||||
"<strong>This will clear all entries and cannot be undone.</strong>",
|
||||
title: "Confirmation required",
|
||||
confirm: function () {
|
||||
confirm() {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/action/flush/arp",
|
||||
type: "POST",
|
||||
}).fail(function (data) {
|
||||
}).fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
},
|
||||
cancel: function () {
|
||||
cancel() {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, flush my network table",
|
||||
@@ -286,7 +291,7 @@ $("#loggingButton").confirm({
|
||||
"As consequence of this action, your DNS cache will be cleared and you may temporarily loose your internet connection.<br>" +
|
||||
"Furthermore, you will be logged out of the web interface.",
|
||||
title: "Confirmation required",
|
||||
confirm: function () {
|
||||
confirm() {
|
||||
const data = {};
|
||||
data.config = {};
|
||||
data.config.dns = {};
|
||||
@@ -299,14 +304,14 @@ $("#loggingButton").confirm({
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify(data),
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
showQueryLoggingButton(data.config.dns.queryLogging);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
},
|
||||
cancel: function () {
|
||||
cancel() {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, change query logging",
|
||||
@@ -317,12 +322,12 @@ $("#loggingButton").confirm({
|
||||
dialogClass: "modal-dialog",
|
||||
});
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
updateHostInfo();
|
||||
updateMetrics();
|
||||
getLoggingButton();
|
||||
|
||||
var ctx = document.getElementById("cachePieChart").getContext("2d");
|
||||
const ctx = document.getElementById("cachePieChart").getContext("2d");
|
||||
cachePieChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
@@ -350,7 +355,7 @@ $(function () {
|
||||
enabled: false,
|
||||
external: customTooltips,
|
||||
callbacks: {
|
||||
title: function () {
|
||||
title() {
|
||||
return "Cache content";
|
||||
},
|
||||
label: doughnutTooltip,
|
||||
@@ -366,7 +371,7 @@ $(function () {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/network/gateway",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
const gateway = data.gateway;
|
||||
// Get first object in gateway that has family == "inet"
|
||||
const inet = gateway.find(obj => obj.family === "inet");
|
||||
@@ -378,7 +383,7 @@ $(function () {
|
||||
$("#sysinfo-gw-v6-addr").text(inet6 ? inet6.local.join("\n") : "N/A");
|
||||
$("#sysinfo-gw-v6-iface").text(inet6 ? inet6.interface : "N/A");
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,22 +7,24 @@
|
||||
|
||||
/* global utils:false */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Add event listener to import button
|
||||
document.getElementById("submit-import").addEventListener("click", function () {
|
||||
document.getElementById("submit-import").addEventListener("click", () => {
|
||||
importZIP();
|
||||
});
|
||||
|
||||
// Upload file to Pi-hole
|
||||
function importZIP() {
|
||||
var file = document.getElementById("file").files[0];
|
||||
const file = document.getElementById("file").files[0];
|
||||
if (file === undefined) {
|
||||
alert("Please select a file to import.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the selected import options
|
||||
const imports = {},
|
||||
gravity = {};
|
||||
const imports = {};
|
||||
const gravity = {};
|
||||
imports.config = document.getElementById("import.config").checked;
|
||||
imports.dhcp_leases = document.getElementById("import.dhcp_leases").checked;
|
||||
gravity.group = document.getElementById("import.gravity.group").checked;
|
||||
@@ -56,8 +58,8 @@ function importZIP() {
|
||||
} else if ("files" in data) {
|
||||
$("#modal-import-success").show();
|
||||
$("#modal-import-success-title").text("Import successful");
|
||||
var text = "<p>Processed files:</p><ul>";
|
||||
for (var i = 0; i < data.files.length; i++) {
|
||||
let text = "<p>Processed files:</p><ul>";
|
||||
for (let i = 0; i < data.files.length; i++) {
|
||||
text += "<li>" + utils.escapeHtml(data.files[i]) + "</li>";
|
||||
}
|
||||
|
||||
@@ -75,7 +77,7 @@ function importZIP() {
|
||||
}
|
||||
|
||||
// Inspired by https://stackoverflow.com/a/59576416/2087442
|
||||
$("#GETTeleporter").on("click", function () {
|
||||
$("#GETTeleporter").on("click", () => {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/teleporter",
|
||||
headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") },
|
||||
@@ -83,9 +85,9 @@ $("#GETTeleporter").on("click", function () {
|
||||
xhrFields: {
|
||||
responseType: "blob",
|
||||
},
|
||||
success: function (data, status, xhr) {
|
||||
var a = document.createElement("a");
|
||||
var url = globalThis.URL.createObjectURL(data);
|
||||
success(data, status, xhr) {
|
||||
const a = document.createElement("a");
|
||||
const url = globalThis.URL.createObjectURL(data);
|
||||
|
||||
a.href = url;
|
||||
a.download = xhr.getResponseHeader("Content-Disposition").match(/filename="([^"]*)"/)[1];
|
||||
@@ -98,7 +100,7 @@ $("#GETTeleporter").on("click", function () {
|
||||
});
|
||||
});
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
// Show warning if not accessed over HTTPS
|
||||
if (location.protocol !== "https:") {
|
||||
$("#encryption-warning").show();
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
/* global utils:false, apiFailure:false*/
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
$(() => {
|
||||
// Handle hiding of alerts
|
||||
$("[data-hide]").on("click", function () {
|
||||
$(this)
|
||||
@@ -16,7 +18,7 @@ $(function () {
|
||||
});
|
||||
|
||||
// Handle saving of settings
|
||||
$(".save-button").on("click", function () {
|
||||
$(".save-button").on("click", () => {
|
||||
saveSettings();
|
||||
});
|
||||
});
|
||||
@@ -26,18 +28,19 @@ $(function () {
|
||||
function setConfigValues(topic, key, value) {
|
||||
// If the value is an object, we need to recurse
|
||||
if (!("description" in value)) {
|
||||
Object.keys(value).forEach(function (subkey) {
|
||||
var subvalue = value[subkey];
|
||||
for (const subkey of Object.keys(value)) {
|
||||
const subvalue = value[subkey];
|
||||
// If the key is empty, we are at the top level
|
||||
var newKey = key === "" ? subkey : key + "." + subkey;
|
||||
const newKey = key === "" ? subkey : key + "." + subkey;
|
||||
setConfigValues(topic, newKey, subvalue);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// else: we have a setting we can set
|
||||
var escapedKey = key.replaceAll(".", "\\.");
|
||||
var envTitle = $(`[data-configkeys~='${key}']`);
|
||||
const escapedKey = key.replaceAll(".", "\\.");
|
||||
const envTitle = $(`[data-configkeys~='${key}']`);
|
||||
|
||||
if (
|
||||
envTitle.parents().parents().hasClass("settings-level-expert") &&
|
||||
@@ -71,13 +74,14 @@ function setConfigValues(topic, key, value) {
|
||||
// Remove all options from select
|
||||
$("#" + escapedKey + " option").remove();
|
||||
// Add allowed select items (if available)
|
||||
value.allowed.forEach(function (allowedValue) {
|
||||
for (const allowedValue of value.allowed) {
|
||||
$("#" + escapedKey + "-" + allowedValue.item).prop("disabled", value.flags.env_var);
|
||||
var newopt = $("<option></option>")
|
||||
const newopt = $("<option></option>")
|
||||
.attr("value", allowedValue.item)
|
||||
.text(allowedValue.description);
|
||||
$("#" + escapedKey).append(newopt);
|
||||
});
|
||||
}
|
||||
|
||||
// Select the current value
|
||||
$("#" + escapedKey)
|
||||
.val(value.value)
|
||||
@@ -116,11 +120,11 @@ function setConfigValues(topic, key, value) {
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var settings = {};
|
||||
const settings = {};
|
||||
utils.disableAll();
|
||||
$("[data-key]").each(function () {
|
||||
var key = $(this).data("key");
|
||||
var value = $(this).val();
|
||||
const key = $(this).data("key");
|
||||
let value = $(this).val();
|
||||
|
||||
// If this is a checkbox, use the checked state
|
||||
if ($(this).is(":checkbox")) {
|
||||
@@ -138,20 +142,20 @@ function saveSettings() {
|
||||
|
||||
// If this is an integer number, parse it accordingly
|
||||
if ($(this).data("type") === "integer") {
|
||||
value = parseInt(value, 10);
|
||||
value = Number.parseInt(value, 10);
|
||||
}
|
||||
|
||||
// If this is a floating point value, parse it accordingly
|
||||
if ($(this).data("type") === "float") {
|
||||
value = parseFloat(value);
|
||||
value = Number.parseFloat(value);
|
||||
}
|
||||
|
||||
// Build deep object
|
||||
// Transform "foo.bar.baz" into {foo: {bar: {baz: value}}}
|
||||
var parts = key.split(".");
|
||||
var obj = {};
|
||||
var tmp = obj;
|
||||
for (var i = 0; i < parts.length - 1; i++) {
|
||||
const parts = key.split(".");
|
||||
const obj = {};
|
||||
let tmp = obj;
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
tmp[parts[i]] = {};
|
||||
tmp = tmp[parts[i]];
|
||||
}
|
||||
@@ -171,7 +175,7 @@ function saveSettings() {
|
||||
data: JSON.stringify({ config: settings }),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
utils.enableAll();
|
||||
// Success
|
||||
utils.showAlert(
|
||||
@@ -183,7 +187,7 @@ function saveSettings() {
|
||||
// Show loading overlay
|
||||
utils.loadingOverlay(true);
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
.fail((data, exception) => {
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while applying settings", data.responseText);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
/* global moment: false, apiFailure: false, utils: false, REFRESH_INTERVAL: false */
|
||||
|
||||
var nextID = 0;
|
||||
var lastPID = -1;
|
||||
"use strict";
|
||||
|
||||
let nextID = 0;
|
||||
let lastPID = -1;
|
||||
|
||||
// Maximum number of lines to display
|
||||
const maxlines = 5000;
|
||||
@@ -69,6 +71,8 @@ function formatFTL(line, prio) {
|
||||
return `<span class="${prioClass}">${utils.escapeHtml(prio)}</span> ${line}`;
|
||||
}
|
||||
|
||||
let gAutoScrolling;
|
||||
|
||||
// Function that asks the API for new data
|
||||
function getData() {
|
||||
// Only update when spinner is spinning
|
||||
@@ -77,7 +81,7 @@ function getData() {
|
||||
return;
|
||||
}
|
||||
|
||||
var GETDict = utils.parseQueryString();
|
||||
const GETDict = utils.parseQueryString();
|
||||
if (!("file" in GETDict)) {
|
||||
globalThis.location.href += "?file=dnsmasq";
|
||||
return;
|
||||
@@ -88,7 +92,7 @@ function getData() {
|
||||
timeout: 5000,
|
||||
method: "GET",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
// Check if we have a new PID -> FTL was restarted
|
||||
if (lastPID !== data.pid) {
|
||||
if (lastPID !== -1) {
|
||||
@@ -120,7 +124,7 @@ function getData() {
|
||||
$("#output").append('<hr class="hr-small">').children(":last").fadeOut(2000);
|
||||
}
|
||||
|
||||
data.log.forEach(function (line) {
|
||||
for (const line of data.log) {
|
||||
// Escape HTML
|
||||
line.message = utils.escapeHtml(line.message);
|
||||
// Format line if applicable
|
||||
@@ -139,10 +143,10 @@ function getData() {
|
||||
//$(".left-line:last").fadeOut(2000);
|
||||
$("#output").children(":last").hide().fadeIn("fast");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Limit output to <maxlines> lines
|
||||
var lines = $("#output").val().split("\n");
|
||||
const lines = $("#output").val().split("\n");
|
||||
if (lines.length > maxlines) {
|
||||
lines.splice(0, lines.length - maxlines);
|
||||
$("#output").val(lines.join("\n"));
|
||||
@@ -162,14 +166,14 @@ function getData() {
|
||||
|
||||
utils.setTimer(getData, REFRESH_INTERVAL.logs);
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
apiFailure(data);
|
||||
utils.setTimer(getData, 5 * REFRESH_INTERVAL.logs);
|
||||
});
|
||||
}
|
||||
|
||||
var gAutoScrolling = true;
|
||||
$("#output").on("scroll", function () {
|
||||
gAutoScrolling = true;
|
||||
$("#output").on("scroll", () => {
|
||||
// Check if we are at the bottom of the output
|
||||
//
|
||||
// - $("#output")[0].scrollHeight: This gets the entire height of the content
|
||||
@@ -185,7 +189,7 @@ $("#output").on("scroll", function () {
|
||||
const bottom =
|
||||
$("#output")[0].scrollHeight - $("#output").innerHeight() - $("#output").scrollTop();
|
||||
// Add a tolerance of four line heights
|
||||
const tolerance = 4 * parseFloat($("#output").css("line-height"));
|
||||
const tolerance = 4 * Number.parseFloat($("#output").css("line-height"));
|
||||
if (bottom <= tolerance) {
|
||||
// Auto-scrolling is enabled
|
||||
gAutoScrolling = true;
|
||||
@@ -199,7 +203,7 @@ $("#output").on("scroll", function () {
|
||||
}
|
||||
});
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
getData();
|
||||
|
||||
// Clicking on the element with class "fa-spinner" will toggle the play/pause state
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
/* global moment:false, apiFailure: false, updateFtlInfo: false, NProgress:false, WaitMe:false */
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
$(() => {
|
||||
// CSRF protection for AJAX requests, this has to be configured globally
|
||||
// because we are using the jQuery $.ajax() function directly in some cases
|
||||
// Furthermore, has this to be done before any AJAX request is made so that
|
||||
@@ -19,7 +21,7 @@ $(function () {
|
||||
|
||||
// Credit: https://stackoverflow.com/a/4835406
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
@@ -30,13 +32,11 @@ function escapeHtml(text) {
|
||||
// Return early when text is not a string
|
||||
if (typeof text !== "string") return text;
|
||||
|
||||
return text.replaceAll(/[&<>"']/g, function (m) {
|
||||
return map[m];
|
||||
});
|
||||
return text.replaceAll(/[&<>"']/g, m => map[m]);
|
||||
}
|
||||
|
||||
function unescapeHtml(text) {
|
||||
var map = {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
@@ -55,25 +55,19 @@ function unescapeHtml(text) {
|
||||
|
||||
return text.replaceAll(
|
||||
/&(?:amp|lt|gt|quot|#039|Uuml|uuml|Auml|auml|Ouml|ouml|szlig);/g,
|
||||
function (m) {
|
||||
return map[m];
|
||||
}
|
||||
m => map[m]
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function for converting Objects to Arrays after sorting the keys
|
||||
function objectToArray(obj) {
|
||||
var arr = [];
|
||||
var idx = [];
|
||||
var keys = Object.keys(obj);
|
||||
const arr = [];
|
||||
const idx = [];
|
||||
const sortedKeys = Object.keys(obj).sort((a, b) => a - b);
|
||||
|
||||
keys.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
arr.push(obj[keys[i]]);
|
||||
idx.push(keys[i]);
|
||||
for (const key of sortedKeys) {
|
||||
arr.push(obj[key]);
|
||||
idx.push(key);
|
||||
}
|
||||
|
||||
return [idx, arr];
|
||||
@@ -83,22 +77,22 @@ function padNumber(num) {
|
||||
return ("00" + num).substr(-2, 2);
|
||||
}
|
||||
|
||||
var showAlertBox = null;
|
||||
let showAlertBox = null;
|
||||
function showAlert(type, icon, title, message, toast) {
|
||||
const options = {
|
||||
title: " <strong>" + escapeHtml(title) + "</strong><br>",
|
||||
message: escapeHtml(message),
|
||||
icon: icon,
|
||||
title: " <strong>" + escapeHtml(title) + "</strong><br>",
|
||||
message: escapeHtml(message),
|
||||
icon,
|
||||
};
|
||||
const settings = {
|
||||
type,
|
||||
delay: 5000, // default value
|
||||
mouse_over: "pause",
|
||||
animate: {
|
||||
enter: "animate__animated animate__fadeInDown",
|
||||
exit: "animate__animated animate__fadeOutUp",
|
||||
},
|
||||
settings = {
|
||||
type: type,
|
||||
delay: 5000, // default value
|
||||
mouse_over: "pause",
|
||||
animate: {
|
||||
enter: "animate__animated animate__fadeInDown",
|
||||
exit: "animate__animated animate__fadeOutUp",
|
||||
},
|
||||
};
|
||||
};
|
||||
switch (type) {
|
||||
case "info":
|
||||
options.icon = icon !== null && icon.length > 0 ? icon : "fas fa-clock";
|
||||
@@ -120,7 +114,7 @@ function showAlert(type, icon, title, message, toast) {
|
||||
// If the message is an API object, nicely format the error message
|
||||
// Try to parse message as JSON
|
||||
try {
|
||||
var data = JSON.parse(message);
|
||||
const data = JSON.parse(message);
|
||||
console.log(data); // eslint-disable-line no-console
|
||||
if (data.error !== undefined) {
|
||||
options.title = " <strong>" + escapeHtml(data.error.message) + "</strong><br>";
|
||||
@@ -143,24 +137,28 @@ function showAlert(type, icon, title, message, toast) {
|
||||
// Create a new notification for info boxes
|
||||
showAlertBox = $.notify(options, settings);
|
||||
return showAlertBox;
|
||||
} else if (showAlertBox !== null) {
|
||||
}
|
||||
|
||||
if (showAlertBox !== null) {
|
||||
// Update existing notification for other boxes (if available)
|
||||
showAlertBox.update(options);
|
||||
showAlertBox.update(settings);
|
||||
return showAlertBox;
|
||||
} else {
|
||||
// Create a new notification for other boxes if no previous info box exists
|
||||
return $.notify(options, settings);
|
||||
}
|
||||
} else if (toast === null) {
|
||||
|
||||
// Create a new notification for other boxes if no previous info box exists
|
||||
return $.notify(options, settings);
|
||||
}
|
||||
|
||||
if (toast === null) {
|
||||
// Always create a new toast
|
||||
return $.notify(options, settings);
|
||||
} else {
|
||||
// Update existing toast
|
||||
toast.update(options);
|
||||
toast.update(settings);
|
||||
return toast;
|
||||
}
|
||||
|
||||
// Update existing toast
|
||||
toast.update(options);
|
||||
toast.update(settings);
|
||||
return toast;
|
||||
}
|
||||
|
||||
function datetime(date, html, humanReadable) {
|
||||
@@ -168,8 +166,9 @@ function datetime(date, html, humanReadable) {
|
||||
return "Never";
|
||||
}
|
||||
|
||||
var format = html === false ? "Y-MM-DD HH:mm:ss z" : "Y-MM-DD [<br class='hidden-lg'>]HH:mm:ss z";
|
||||
var timestr = moment.unix(Math.floor(date)).format(format).trim();
|
||||
const format =
|
||||
html === false ? "Y-MM-DD HH:mm:ss z" : "Y-MM-DD [<br class='hidden-lg'>]HH:mm:ss z";
|
||||
const timestr = moment.unix(Math.floor(date)).format(format).trim();
|
||||
return humanReadable
|
||||
? '<span title="' + timestr + '">' + moment.unix(Math.floor(date)).fromNow() + "</span>"
|
||||
: timestr;
|
||||
@@ -193,7 +192,7 @@ function enableAll() {
|
||||
$("textarea").prop("disabled", false);
|
||||
|
||||
// Enable custom input field only if applicable
|
||||
var ip = $("#select") ? $("#select").val() : null;
|
||||
const ip = $("#select") ? $("#select").val() : null;
|
||||
if (ip !== null && ip !== "custom") {
|
||||
$("#ip-custom").prop("disabled", true);
|
||||
}
|
||||
@@ -202,10 +201,10 @@ function enableAll() {
|
||||
// Pi-hole IPv4/CIDR validator by DL6ER, see regexr.com/50csh
|
||||
function validateIPv4CIDR(ip) {
|
||||
// One IPv4 element is 8bit: 0 - 256
|
||||
var ipv4elem = "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)";
|
||||
const ipv4elem = "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)";
|
||||
// CIDR for IPv4 is 1 - 32 bit
|
||||
var v4cidr = "(\\/([1-9]|[1-2][0-9]|3[0-2])){0,1}";
|
||||
var ipv4validator = new RegExp(
|
||||
const v4cidr = "(\\/([1-9]|[1-2][0-9]|3[0-2])){0,1}";
|
||||
const ipv4validator = new RegExp(
|
||||
"^" + ipv4elem + "\\." + ipv4elem + "\\." + ipv4elem + "\\." + ipv4elem + v4cidr + "$"
|
||||
);
|
||||
return ipv4validator.test(ip);
|
||||
@@ -214,10 +213,10 @@ function validateIPv4CIDR(ip) {
|
||||
// Pi-hole IPv6/CIDR validator by DL6ER, see regexr.com/50csn
|
||||
function validateIPv6CIDR(ip) {
|
||||
// One IPv6 element is 16bit: 0000 - FFFF
|
||||
var ipv6elem = "[0-9A-Fa-f]{1,4}";
|
||||
const ipv6elem = "[0-9A-Fa-f]{1,4}";
|
||||
// CIDR for IPv6 is 1- 128 bit
|
||||
var v6cidr = "(\\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}";
|
||||
var ipv6validator = new RegExp(
|
||||
const v6cidr = "(\\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}";
|
||||
const ipv6validator = new RegExp(
|
||||
"^(((?:" +
|
||||
ipv6elem +
|
||||
"))*((?::" +
|
||||
@@ -238,18 +237,18 @@ function validateIPv6CIDR(ip) {
|
||||
}
|
||||
|
||||
function validateMAC(mac) {
|
||||
var macvalidator = /^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$/;
|
||||
const macvalidator = /^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$/;
|
||||
return macvalidator.test(mac);
|
||||
}
|
||||
|
||||
function validateHostname(name) {
|
||||
var namevalidator = /[^<>;"]/;
|
||||
const namevalidator = /[^<>;"]/;
|
||||
return namevalidator.test(name);
|
||||
}
|
||||
|
||||
// set bootstrap-select defaults
|
||||
function setBsSelectDefaults() {
|
||||
var bsSelectDefaults = $.fn.selectpicker.Constructor.DEFAULTS;
|
||||
const bsSelectDefaults = $.fn.selectpicker.Constructor.DEFAULTS;
|
||||
bsSelectDefaults.noneSelectedText = "none selected";
|
||||
bsSelectDefaults.selectedTextFormat = "count > 1";
|
||||
bsSelectDefaults.actionsBox = true;
|
||||
@@ -267,7 +266,7 @@ function setBsSelectDefaults() {
|
||||
};
|
||||
}
|
||||
|
||||
var backupStorage = {};
|
||||
const backupStorage = {};
|
||||
function stateSaveCallback(itemName, data) {
|
||||
if (localStorage === null) {
|
||||
backupStorage[itemName] = JSON.stringify(data);
|
||||
@@ -277,10 +276,10 @@ function stateSaveCallback(itemName, data) {
|
||||
}
|
||||
|
||||
function stateLoadCallback(itemName) {
|
||||
var data;
|
||||
let data;
|
||||
// Receive previous state from client's local storage area
|
||||
if (localStorage === null) {
|
||||
var item = backupStorage[itemName];
|
||||
const item = backupStorage[itemName];
|
||||
data = item === "undefined" ? null : item;
|
||||
} else {
|
||||
data = localStorage.getItem(itemName);
|
||||
@@ -295,9 +294,10 @@ function stateLoadCallback(itemName) {
|
||||
data = JSON.parse(data);
|
||||
|
||||
// Clear possible filtering settings
|
||||
data.columns.forEach(function (value, index) {
|
||||
// TODO Maybe Object.values() is meant to be used here?
|
||||
for (const [index, _value] of data.columns.entries()) {
|
||||
data.columns[index].search.search = "";
|
||||
});
|
||||
}
|
||||
|
||||
// Always start on the first page to show most recent queries
|
||||
data.start = 0;
|
||||
@@ -308,28 +308,28 @@ function stateLoadCallback(itemName) {
|
||||
}
|
||||
|
||||
function addFromQueryLog(domain, list) {
|
||||
var alertModal = $("#alertModal");
|
||||
var alProcessing = alertModal.find(".alProcessing");
|
||||
var alSuccess = alertModal.find(".alSuccess");
|
||||
var alFailure = alertModal.find(".alFailure");
|
||||
var alNetworkErr = alertModal.find(".alFailure #alNetErr");
|
||||
var alCustomErr = alertModal.find(".alFailure #alCustomErr");
|
||||
var alList = "#alList";
|
||||
var alDomain = "#alDomain";
|
||||
const alertModal = $("#alertModal");
|
||||
const alProcessing = alertModal.find(".alProcessing");
|
||||
const alSuccess = alertModal.find(".alSuccess");
|
||||
const alFailure = alertModal.find(".alFailure");
|
||||
const alNetworkErr = alertModal.find(".alFailure #alNetErr");
|
||||
const alCustomErr = alertModal.find(".alFailure #alCustomErr");
|
||||
const alList = "#alList";
|
||||
const alDomain = "#alDomain";
|
||||
|
||||
// Exit the function here if the Modal is already shown (multiple running interlock)
|
||||
if (alertModal.css("display") !== "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
var listtype = list === "allow" ? "Allowlist" : "Denylist";
|
||||
const listtype = list === "allow" ? "Allowlist" : "Denylist";
|
||||
|
||||
alProcessing.children(alDomain).text(domain);
|
||||
alProcessing.children(alList).text(listtype);
|
||||
alertModal.modal("show");
|
||||
|
||||
// add Domain to List after Modal has faded in
|
||||
alertModal.one("shown.bs.modal", function () {
|
||||
alertModal.one("shown.bs.modal", () => {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/domains/" + list + "/exact",
|
||||
method: "post",
|
||||
@@ -337,12 +337,12 @@ function addFromQueryLog(domain, list) {
|
||||
processData: false,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({
|
||||
domain: domain,
|
||||
domain,
|
||||
comment: "Added from Query Log",
|
||||
type: list,
|
||||
kind: "exact",
|
||||
}),
|
||||
success: function (response) {
|
||||
success(response) {
|
||||
alProcessing.hide();
|
||||
if ("domains" in response && response.domains.length > 0) {
|
||||
// Success
|
||||
@@ -351,7 +351,7 @@ function addFromQueryLog(domain, list) {
|
||||
alSuccess.fadeIn(1000);
|
||||
// Update domains counter in the menu
|
||||
updateFtlInfo();
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
alertModal.modal("hide");
|
||||
}, 2000);
|
||||
} else {
|
||||
@@ -359,17 +359,17 @@ function addFromQueryLog(domain, list) {
|
||||
alNetworkErr.hide();
|
||||
alCustomErr.html(response.message);
|
||||
alFailure.fadeIn(1000);
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
alertModal.modal("hide");
|
||||
}, 10000);
|
||||
}, 10_000);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
error() {
|
||||
// Network Error
|
||||
alProcessing.hide();
|
||||
alNetworkErr.show();
|
||||
alFailure.fadeIn(1000);
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
alertModal.modal("hide");
|
||||
}, 8000);
|
||||
},
|
||||
@@ -377,7 +377,7 @@ function addFromQueryLog(domain, list) {
|
||||
});
|
||||
|
||||
// Reset Modal after it has faded out
|
||||
alertModal.one("hidden.bs.modal", function () {
|
||||
alertModal.one("hidden.bs.modal", () => {
|
||||
alProcessing.show();
|
||||
alSuccess.add(alFailure).hide();
|
||||
alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html("");
|
||||
@@ -407,7 +407,7 @@ function colorBar(percentage, total, cssClass) {
|
||||
}
|
||||
|
||||
function checkMessages() {
|
||||
var ignoreNonfatal = localStorage
|
||||
const ignoreNonfatal = localStorage
|
||||
? localStorage.getItem("hideNonfatalDnsmasqWarnings_chkbox") === "true"
|
||||
: false;
|
||||
$.ajax({
|
||||
@@ -418,10 +418,10 @@ function checkMessages() {
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
})
|
||||
.done(function (data) {
|
||||
.done(data => {
|
||||
if (data.count > 0) {
|
||||
var more = '\nAccess "Tools/Pi-hole diagnosis" for further details.';
|
||||
var title =
|
||||
const more = '\nAccess "Tools/Pi-hole diagnosis" for further details.';
|
||||
const title =
|
||||
data.count > 1
|
||||
? "There are " + data.count + " warnings." + more
|
||||
: "There is one warning." + more;
|
||||
@@ -433,7 +433,7 @@ function checkMessages() {
|
||||
$(".warning-count").addClass("hidden");
|
||||
}
|
||||
})
|
||||
.fail(function (data) {
|
||||
.fail(data => {
|
||||
$(".warning-count").addClass("hidden");
|
||||
apiFailure(data);
|
||||
});
|
||||
@@ -441,9 +441,9 @@ function checkMessages() {
|
||||
|
||||
// Show only the appropriate delete buttons in datatables
|
||||
function changeBulkDeleteStates(table) {
|
||||
var allRows = table.rows({ filter: "applied" }).data().length;
|
||||
var pageLength = table.page.len();
|
||||
var selectedRows = table.rows(".selected").data().length;
|
||||
const allRows = table.rows({ filter: "applied" }).data().length;
|
||||
const pageLength = table.page.len();
|
||||
const selectedRows = table.rows(".selected").data().length;
|
||||
|
||||
if (selectedRows === 0) {
|
||||
// Nothing selected
|
||||
@@ -470,7 +470,7 @@ function doLogout(url) {
|
||||
$.ajax({
|
||||
url: document.body.dataset.apiurl + "/auth",
|
||||
method: "DELETE",
|
||||
}).always(function () {
|
||||
}).always(() => {
|
||||
globalThis.location = url;
|
||||
});
|
||||
}
|
||||
@@ -501,9 +501,9 @@ function htmlPass(data, _type) {
|
||||
|
||||
// Show only the appropriate buttons
|
||||
function changeTableButtonStates(table) {
|
||||
var allRows = table.rows({ filter: "applied" }).data().length;
|
||||
var pageLength = table.page.len();
|
||||
var selectedRows = table.rows(".selected").data().length;
|
||||
const allRows = table.rows({ filter: "applied" }).data().length;
|
||||
const pageLength = table.page.len();
|
||||
const selectedRows = table.rows(".selected").data().length;
|
||||
|
||||
if (selectedRows === 0) {
|
||||
// Nothing selected
|
||||
@@ -527,8 +527,8 @@ function changeTableButtonStates(table) {
|
||||
}
|
||||
|
||||
function getCSSval(cssclass, cssproperty) {
|
||||
var elem = $("<div class='" + cssclass + "'></div>"),
|
||||
val = elem.appendTo("body").css(cssproperty);
|
||||
const elem = $("<div class='" + cssclass + "'></div>");
|
||||
const val = elem.appendTo("body").css(cssproperty);
|
||||
elem.remove();
|
||||
return val;
|
||||
}
|
||||
@@ -540,9 +540,10 @@ function parseQueryString() {
|
||||
|
||||
// https://stackoverflow.com/q/21647928
|
||||
function hexEncode(string) {
|
||||
var hex, i;
|
||||
let hex;
|
||||
let i;
|
||||
|
||||
var result = "";
|
||||
let result = "";
|
||||
for (i = 0; i < string.length; i++) {
|
||||
hex = string.codePointAt(i).toString(16);
|
||||
result += ("000" + hex).slice(-4);
|
||||
@@ -553,17 +554,17 @@ function hexEncode(string) {
|
||||
|
||||
// https://stackoverflow.com/q/21647928
|
||||
function hexDecode(string) {
|
||||
var j;
|
||||
var hexes = string.match(/.{1,4}/g) || [];
|
||||
var back = "";
|
||||
let j;
|
||||
const hexes = string.match(/.{1,4}/g) || [];
|
||||
let back = "";
|
||||
for (j = 0; j < hexes.length; j++) {
|
||||
back += String.fromCodePoint(parseInt(hexes[j], 16));
|
||||
back += String.fromCodePoint(Number.parseInt(hexes[j], 16));
|
||||
}
|
||||
|
||||
return back;
|
||||
}
|
||||
|
||||
function listAlert(type, items, data) {
|
||||
function listsAlert(type, items, data) {
|
||||
// Show simple success message if there is no "processed" object in "data" or
|
||||
// if all items were processed successfully
|
||||
if (data.processed === undefined || data.processed.success.length === items.length) {
|
||||
@@ -592,7 +593,7 @@ function listAlert(type, items, data) {
|
||||
|
||||
// Loop over data.processed.success and print "item"
|
||||
for (const item in data.processed.success) {
|
||||
if (Object.prototype.hasOwnProperty.call(data.processed.success, item)) {
|
||||
if (Object.hasOwn(data.processed.success, item)) {
|
||||
message += "\n- " + data.processed.success[item].item;
|
||||
}
|
||||
}
|
||||
@@ -615,10 +616,10 @@ function listAlert(type, items, data) {
|
||||
|
||||
// Loop over data.processed.errors and print "item: error"
|
||||
for (const item in data.processed.errors) {
|
||||
if (Object.prototype.hasOwnProperty.call(data.processed.errors, item)) {
|
||||
if (Object.hasOwn(data.processed.errors, item)) {
|
||||
let error = data.processed.errors[item].error;
|
||||
// Replace some error messages with a more user-friendly text
|
||||
if (error.indexOf("UNIQUE constraint failed") > -1) {
|
||||
if (error.includes("UNIQUE constraint failed")) {
|
||||
error = "Already present";
|
||||
}
|
||||
|
||||
@@ -648,7 +649,7 @@ function loadingOverlayTimeoutCallback(reloadAfterTimeout) {
|
||||
cache: false,
|
||||
dataType: "json",
|
||||
})
|
||||
.done(function () {
|
||||
.done(() => {
|
||||
// FTL is running again, hide loading overlay
|
||||
NProgress.done();
|
||||
if (reloadAfterTimeout) {
|
||||
@@ -657,7 +658,7 @@ function loadingOverlayTimeoutCallback(reloadAfterTimeout) {
|
||||
waitMe.hideAll();
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
.fail(() => {
|
||||
// FTL is not running yet, try again in 500ms
|
||||
setTimeout(loadingOverlayTimeoutCallback, 500, reloadAfterTimeout);
|
||||
});
|
||||
@@ -717,40 +718,40 @@ function setInter(func, interval) {
|
||||
|
||||
globalThis.utils = (function () {
|
||||
return {
|
||||
escapeHtml: escapeHtml,
|
||||
unescapeHtml: unescapeHtml,
|
||||
objectToArray: objectToArray,
|
||||
padNumber: padNumber,
|
||||
showAlert: showAlert,
|
||||
datetime: datetime,
|
||||
datetimeRelative: datetimeRelative,
|
||||
disableAll: disableAll,
|
||||
enableAll: enableAll,
|
||||
validateIPv4CIDR: validateIPv4CIDR,
|
||||
validateIPv6CIDR: validateIPv6CIDR,
|
||||
setBsSelectDefaults: setBsSelectDefaults,
|
||||
stateSaveCallback: stateSaveCallback,
|
||||
stateLoadCallback: stateLoadCallback,
|
||||
validateMAC: validateMAC,
|
||||
validateHostname: validateHostname,
|
||||
addFromQueryLog: addFromQueryLog,
|
||||
addTD: addTD,
|
||||
toPercent: toPercent,
|
||||
colorBar: colorBar,
|
||||
checkMessages: checkMessages,
|
||||
changeBulkDeleteStates: changeBulkDeleteStates,
|
||||
doLogout: doLogout,
|
||||
renderTimestamp: renderTimestamp,
|
||||
renderTimespan: renderTimespan,
|
||||
htmlPass: htmlPass,
|
||||
changeTableButtonStates: changeTableButtonStates,
|
||||
getCSSval: getCSSval,
|
||||
parseQueryString: parseQueryString,
|
||||
hexEncode: hexEncode,
|
||||
hexDecode: hexDecode,
|
||||
listsAlert: listAlert,
|
||||
loadingOverlay: loadingOverlay,
|
||||
setTimer: setTimer,
|
||||
setInter: setInter,
|
||||
escapeHtml,
|
||||
unescapeHtml,
|
||||
objectToArray,
|
||||
padNumber,
|
||||
showAlert,
|
||||
datetime,
|
||||
datetimeRelative,
|
||||
disableAll,
|
||||
enableAll,
|
||||
validateIPv4CIDR,
|
||||
validateIPv6CIDR,
|
||||
setBsSelectDefaults,
|
||||
stateSaveCallback,
|
||||
stateLoadCallback,
|
||||
validateMAC,
|
||||
validateHostname,
|
||||
addFromQueryLog,
|
||||
addTD,
|
||||
toPercent,
|
||||
colorBar,
|
||||
checkMessages,
|
||||
changeBulkDeleteStates,
|
||||
doLogout,
|
||||
renderTimestamp,
|
||||
renderTimespan,
|
||||
htmlPass,
|
||||
changeTableButtonStates,
|
||||
getCSSval,
|
||||
parseQueryString,
|
||||
hexEncode,
|
||||
hexDecode,
|
||||
listsAlert,
|
||||
loadingOverlay,
|
||||
setTimer,
|
||||
setInter,
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user