Modernize JS and tighten things (#3388)

This commit is contained in:
Adam Warner
2025-04-24 18:17:04 +01:00
committed by GitHub
30 changed files with 1310 additions and 1260 deletions

4
package-lock.json generated
View File

@@ -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": {

View File

@@ -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"
}
}
}

View File

@@ -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,7 +332,8 @@ 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;
@@ -338,7 +345,6 @@ function doughnutTooltip(tooltipLabel) {
((tooltipLabel.parsed * 100) / percentageTotalShown).toFixed(1) +
"% of shown items"
);
}
}
// chartjs plugin used by the custom doughnut legend

View File

@@ -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();
});

View File

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

View File

@@ -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,]+/);
.split(/[\s,]+/)
// Remove empty elements
ips = ips.filter(function (el) {
return el !== "";
});
.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(

View File

@@ -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);

View File

@@ -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(

View File

@@ -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,32 +25,34 @@ $(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 =
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) +
"&nbsp;(" +
utils.datetime(data.date_updated, false) +
")"
: "N/A",
numberOfEntries =
: "N/A";
const numberOfEntries =
(data.number !== null && numbers === true
? parseInt(data.number, 10).toLocaleString()
? Number.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.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
? parseInt(data.invalid_domains, 10).toLocaleString()
? Number.parseInt(data.invalid_domains, 10).toLocaleString()
: "N/A";
return `<table>
@@ -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(

View File

@@ -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(

View File

@@ -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" });
});

View File

@@ -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'>&nbsp;&rdca;</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

View File

@@ -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;
},
});

View File

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

View File

@@ -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";

View File

@@ -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",

View File

@@ -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");

View File

@@ -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:&nbsp;&nbsp;<strong>" + ipInfo + "</strong></div>";
const clientInfo = divStart + "Client:&nbsp;&nbsp;<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:&nbsp;&nbsp;" + data.id + "</div>";
}
// Always show reply info, add reply delay if applicable
var replyInfo = "";
let replyInfo = "";
replyInfo =
data.reply.type !== "UNKNOWN"
? divStart + "Reply:&nbsp&nbsp;" + data.reply.type + "</div>"
: divStart + "Reply:&nbsp;&nbsp;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:&nbsp;&nbsp;<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();
}

View File

@@ -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();
});

View File

@@ -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();
});

View File

@@ -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");
});

View File

@@ -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();
});

View File

@@ -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);

View File

@@ -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();
});

View File

@@ -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();
});

View File

@@ -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);
});
});

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
@@ -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 = {
"&amp;": "&",
"&lt;": "<",
"&gt;": ">",
@@ -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,15 +77,15 @@ 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: "&nbsp;<strong>" + escapeHtml(title) + "</strong><br>",
message: escapeHtml(message),
icon: icon,
},
settings = {
type: type,
icon,
};
const settings = {
type,
delay: 5000, // default value
mouse_over: "pause",
animate: {
@@ -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 = "&nbsp;<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) {
if (toast === null) {
// Always create a new toast
return $.notify(options, settings);
} else {
}
// 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,
};
})();