-
-
+
Log in (uses cookie)
diff --git a/package-lock.json b/package-lock.json
index 98d1743a..feb8a15e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,13 +37,13 @@
},
"devDependencies": {
"autoprefixer": "^10.4.21",
- "eslint": "^9.31.0",
+ "eslint": "^9.38.0",
"eslint-plugin-compat": "^6.0.2",
- "globals": "^16.3.0",
+ "globals": "^16.4.0",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"prettier": "^3.6.2",
- "xo": "^1.1.1"
+ "xo": "^1.2.3"
}
},
"node_modules/@babel/code-frame": {
@@ -126,9 +126,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
- "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -168,13 +168,13 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
- "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.6",
+ "@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -183,19 +183,22 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
- "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz",
+ "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==",
"dev": true,
"license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.16.0"
+ },
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
- "version": "0.15.1",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
- "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
+ "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -243,9 +246,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.31.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
- "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
+ "version": "9.38.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
+ "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -256,9 +259,9 @@
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
- "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -266,13 +269,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz",
- "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==",
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
+ "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.15.1",
+ "@eslint/core": "^0.16.0",
"levn": "^0.4.1"
},
"engines": {
@@ -563,17 +566,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
- "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
+ "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/type-utils": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.37.0",
+ "@typescript-eslint/type-utils": "8.37.0",
+ "@typescript-eslint/utils": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -587,7 +590,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.35.0",
+ "@typescript-eslint/parser": "^8.37.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -603,16 +606,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
- "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
+ "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/scope-manager": "8.37.0",
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4"
},
"engines": {
@@ -628,14 +631,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
- "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
+ "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.35.0",
- "@typescript-eslint/types": "^8.35.0",
+ "@typescript-eslint/tsconfig-utils": "^8.37.0",
+ "@typescript-eslint/types": "^8.37.0",
"debug": "^4.3.4"
},
"engines": {
@@ -650,14 +653,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
- "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
+ "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0"
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -668,9 +671,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
- "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
+ "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -685,14 +688,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
- "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
+ "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.35.0",
- "@typescript-eslint/utils": "8.35.0",
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.0",
+ "@typescript-eslint/utils": "8.37.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -709,9 +713,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
- "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
+ "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -723,16 +727,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
- "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
+ "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.35.0",
- "@typescript-eslint/tsconfig-utils": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/visitor-keys": "8.35.0",
+ "@typescript-eslint/project-service": "8.37.0",
+ "@typescript-eslint/tsconfig-utils": "8.37.0",
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -778,16 +782,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
- "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
+ "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.35.0",
- "@typescript-eslint/types": "8.35.0",
- "@typescript-eslint/typescript-estree": "8.35.0"
+ "@typescript-eslint/scope-manager": "8.37.0",
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -802,13 +806,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
- "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
+ "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.35.0",
+ "@typescript-eslint/types": "8.37.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -3161,25 +3165,24 @@
}
},
"node_modules/eslint": {
- "version": "9.31.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
- "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
+ "version": "9.38.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
+ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.0",
- "@eslint/config-helpers": "^0.3.0",
- "@eslint/core": "^0.15.0",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.1",
+ "@eslint/core": "^0.16.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.31.0",
- "@eslint/plugin-kit": "^0.3.1",
+ "@eslint/js": "9.38.0",
+ "@eslint/plugin-kit": "^0.4.0",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
@@ -3632,14 +3635,13 @@
}
},
"node_modules/eslint-plugin-n": {
- "version": "17.20.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.20.0.tgz",
- "integrity": "sha512-IRSoatgB/NQJZG5EeTbv/iAx1byOGdbbyhQrNvWdCfTnmPxUT0ao9/eGOeG7ljD8wJBsxwE8f6tES5Db0FRKEw==",
+ "version": "17.21.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.0.tgz",
+ "integrity": "sha512-1+iZ8We4ZlwVMtb/DcHG3y5/bZOdazIpa/4TySo22MLKdwrLcfrX0hbadnCvykSQCCmkAnWmIP8jZVb2AAq29A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.5.0",
- "@typescript-eslint/utils": "^8.26.1",
"enhanced-resolve": "^5.17.1",
"eslint-plugin-es-x": "^7.8.0",
"get-tsconfig": "^4.8.1",
@@ -4623,9 +4625,9 @@
}
},
"node_modules/globals": {
- "version": "16.3.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
- "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8315,9 +8317,9 @@
}
},
"node_modules/ts-declaration-location/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8466,15 +8468,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.35.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
- "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
+ "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.35.0",
- "@typescript-eslint/parser": "8.35.0",
- "@typescript-eslint/utils": "8.35.0"
+ "@typescript-eslint/eslint-plugin": "8.37.0",
+ "@typescript-eslint/parser": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.0",
+ "@typescript-eslint/utils": "8.37.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -8876,43 +8879,43 @@
"license": "ISC"
},
"node_modules/xo": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/xo/-/xo-1.1.1.tgz",
- "integrity": "sha512-CMuUby6vSP+OQyOQRbhqWWIfc1dg06M6MFdtIQh/EMNE+ONcBzDhzNnbtDt/qD9gGRdg5qZ9YK16BBGwOfA2BQ==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/xo/-/xo-1.2.3.tgz",
+ "integrity": "sha512-ykvWr88620CwealQwr7nWcPwolE6RMAVsCSBIdF3JnVdQUBAllnBJypSPsu0YYFzWTrJjQfNgH82lnWMPVTXnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
"@sindresorhus/tsconfig": "^7.0.0",
"@stylistic/eslint-plugin": "^4.2.0",
- "@typescript-eslint/parser": "^8.32.1",
+ "@typescript-eslint/parser": "^8.37.0",
"arrify": "^3.0.0",
"cosmiconfig": "^9.0.0",
"define-lazy-prop": "^3.0.0",
- "eslint": "^9.27.0",
+ "eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.5",
"eslint-config-xo-react": "^0.28.0",
"eslint-config-xo-typescript": "^7.0.0",
"eslint-formatter-pretty": "^6.0.1",
"eslint-plugin-ava": "^15.0.1",
- "eslint-plugin-import-x": "^4.12.2",
- "eslint-plugin-n": "^17.18.0",
+ "eslint-plugin-import-x": "^4.16.1",
+ "eslint-plugin-n": "^17.21.0",
"eslint-plugin-no-use-extend-native": "^0.7.2",
- "eslint-plugin-prettier": "^5.4.0",
+ "eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-unicorn": "^59.0.1",
"find-cache-directory": "^6.0.0",
"get-stdin": "^9.0.0",
"get-tsconfig": "^4.10.1",
- "globals": "^16.1.0",
+ "globals": "^16.3.0",
"globby": "^14.1.0",
"meow": "^13.2.0",
"micromatch": "^4.0.8",
"open-editor": "^5.1.0",
"path-exists": "^5.0.0",
- "prettier": "^3.5.3",
+ "prettier": "^3.6.2",
"type-fest": "^4.41.0",
- "typescript-eslint": "^8.32.1"
+ "typescript-eslint": "^8.37.0"
},
"bin": {
"xo": "dist/cli.js"
diff --git a/package.json b/package.json
index c71a4648..1c38c7a5 100644
--- a/package.json
+++ b/package.json
@@ -53,13 +53,13 @@
},
"devDependencies": {
"autoprefixer": "^10.4.21",
- "eslint": "^9.31.0",
+ "eslint": "^9.38.0",
"eslint-plugin-compat": "^6.0.2",
- "globals": "^16.3.0",
+ "globals": "^16.4.0",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"prettier": "^3.6.2",
- "xo": "^1.1.1"
+ "xo": "^1.2.3"
},
"browserslist": [
">= 0.5%",
diff --git a/queries.lp b/queries.lp
index f05f0ff3..df9a6cbb 100644
--- a/queries.lp
+++ b/queries.lp
@@ -138,7 +138,7 @@ mg.include('scripts/lua/header_authenticated.lp','r')
diff --git a/scripts/js/charts.js b/scripts/js/charts.js
index ecb99b61..613804c1 100644
--- a/scripts/js/charts.js
+++ b/scripts/js/charts.js
@@ -5,7 +5,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global upstreams:false */
+/* global upstreamIPs:false */
"use strict";
@@ -63,6 +63,23 @@ globalThis.htmlLegendPlugin = {
for (const item of items) {
const li = document.createElement("li");
+ // Select the corresponding "slice" of the chart when the mouse is over a legend item
+ li.addEventListener("mouseover", () => {
+ chart.setActiveElements([
+ {
+ datasetIndex: 0,
+ index: item.index,
+ },
+ ]);
+ chart.update();
+ });
+
+ // Deselect all "slices"
+ li.addEventListener("mouseout", () => {
+ chart.setActiveElements([]);
+ chart.update();
+ });
+
// Color checkbox (toggle visibility)
const boxSpan = document.createElement("span");
boxSpan.title = "Toggle visibility";
@@ -96,9 +113,19 @@ globalThis.htmlLegendPlugin = {
if (isQueryTypeChart) {
link.href = `queries?type=${item.text}`;
- } else if (isForwardDestinationChart) {
+ } else {
// Encode the forward destination as it may contain an "#" character
- link.href = `queries?upstream=${encodeURIComponent(upstreams[item.text])}`;
+ link.href = `queries?upstream=${encodeURIComponent(upstreamIPs[item.index])}`;
+
+ // If server name and IP are different:
+ if (item.text !== upstreamIPs[item.index]) {
+ // replace the title tooltip to include the upstream IP to the text ...
+ link.title = `List ${item.text} (${upstreamIPs[item.index]}) queries`;
+
+ // ... and include the server name (without port) to the querystring, to match
+ // the text used on the SELECT element (sent by suggestions API endpoint)
+ link.href += ` (${item.text.split("#")[0]})`;
+ }
}
} else {
// no clickable links in other charts
@@ -233,6 +260,10 @@ function positionTooltip(tooltipEl, tooltip, context) {
const arrowMinIndent = 2 * tooltip.options.cornerRadius;
const arrowSize = 5;
+ // Check if this is a queryOverTimeChart or clientsChart - these should stick to x-axis
+ const canvasId = context.chart.canvas.id;
+ const isTimelineChart = canvasId === "queryOverTimeChart" || canvasId === "clientsChart";
+
let tooltipX = offsetX + caretX;
let arrowX;
@@ -289,27 +320,37 @@ function positionTooltip(tooltipEl, tooltip, context) {
arrowX = offsetX + caretX - tooltipX;
}
- let tooltipY = offsetY + caretY;
+ let tooltipY;
- // Compute Y position
- switch (tooltip.yAlign) {
- case "top": {
- tooltipY += arrowSize + caretPadding;
- break;
- }
+ if (isTimelineChart) {
+ // For timeline charts, always position tooltip below the chart with caret pointing to x-axis
+ const chartArea = context.chart.chartArea;
+ const canvasBottom = chartArea.bottom;
+ tooltipY = offsetY + canvasBottom + arrowSize + caretPadding;
- case "center": {
- tooltipY -= tooltipHeight / 2;
- if (tooltip.xAlign === "left") tooltipX += arrowSize;
- if (tooltip.xAlign === "right") tooltipX -= arrowSize;
- break;
- }
+ // Ensure the arrow points to the correct X position
+ arrowX = tooltip.caretX - (tooltipX - offsetX);
+ } else {
+ tooltipY = offsetY + caretY;
+ switch (tooltip.yAlign) {
+ case "top": {
+ tooltipY += arrowSize + caretPadding;
+ break;
+ }
- case "bottom": {
- tooltipY -= tooltipHeight + arrowSize + caretPadding;
- break;
+ case "center": {
+ tooltipY -= tooltipHeight / 2;
+ if (tooltip.xAlign === "left") tooltipX += arrowSize;
+ if (tooltip.xAlign === "right") tooltipX -= arrowSize;
+ break;
+ }
+
+ case "bottom": {
+ tooltipY -= tooltipHeight + arrowSize + caretPadding;
+ break;
+ }
+ // No default
}
- // No default
}
// Position tooltip and display
diff --git a/scripts/js/footer.js b/scripts/js/footer.js
index 7856c137..a26b2740 100644
--- a/scripts/js/footer.js
+++ b/scripts/js/footer.js
@@ -241,28 +241,26 @@ function updateFtlInfo() {
$("#num_lists").text(intl.format(database.lists));
$("#num_gravity").text(intl.format(database.gravity));
$("#num_allowed")
- .text(intl.format(database.domains.allowed + database.regex.allowed))
+ .text(intl.format(database.domains.allowed.enabled + database.regex.allowed.enabled))
.attr(
"title",
"Allowed: " +
- intl.format(database.domains.allowed) +
+ intl.format(database.domains.allowed.enabled) +
" exact domains and " +
- intl.format(database.regex.allowed) +
+ intl.format(database.regex.allowed.enabled) +
" regex filters are enabled"
);
$("#num_denied")
- .text(intl.format(database.domains.denied + database.regex.denied))
+ .text(intl.format(database.domains.denied.enabled + database.regex.denied.enabled))
.attr(
"title",
"Denied: " +
- intl.format(database.domains.denied) +
+ intl.format(database.domains.denied.enabled) +
" exact domains and " +
- intl.format(database.regex.denied) +
+ intl.format(database.regex.denied.enabled) +
" regex filters are enabled"
);
updateQueryFrequency(intl, ftl.query_frequency);
- $("#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);
const startdate = moment()
.subtract(ftl.uptime, "milliseconds")
@@ -346,15 +344,17 @@ function updateSystemInfo() {
);
$("#cpu").prop(
"title",
- "Load averages for the past 1, 5, and 15 minutes\non a system with " +
+ "CPU usage: " +
+ system.cpu["%cpu"].toFixed(1) +
+ "%\nLoad averages for the past 1, 5, and 15 minutes\non a system with " +
system.cpu.nprocs +
" core" +
(system.cpu.nprocs > 1 ? "s" : "") +
" running " +
system.procs +
- " processes " +
+ " processes" +
(system.cpu.load.raw[0] > system.cpu.nprocs
- ? " (load is higher than the number of cores)"
+ ? "\n(load is higher than the number of cores)"
: "")
);
$("#sysinfo-cpu").text(
@@ -368,6 +368,9 @@ function updateSystemInfo() {
" processes"
);
+ $("#sysinfo-cpu-ftl").text("(" + system.ftl["%cpu"].toFixed(1) + "% used by FTL)");
+ $("#sysinfo-ram-ftl").text("(" + system.ftl["%mem"].toFixed(1) + "% used by FTL)");
+
const startdate = moment()
.subtract(system.uptime, "seconds")
.format("dddd, MMMM Do YYYY, HH:mm:ss");
diff --git a/scripts/js/gravity.js b/scripts/js/gravity.js
index 3fb960d8..425abdaa 100644
--- a/scripts/js/gravity.js
+++ b/scripts/js/gravity.js
@@ -5,7 +5,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global apiFailure:false */
+/* global apiFailure:false, utils:false */
"use strict";
@@ -89,9 +89,11 @@ function parseLines(outputElement, text) {
const lines = text.split(/(?=\r)/g);
for (let line of lines) {
+ // Escape HTML to prevent XSS attacks (both in adlist URL and non-domain entries)
+ line = utils.escapeHtml(line);
if (line[0] === "\r") {
// This line starts with the "OVER" sequence. Replace them with "\n" before print
- line = line.replaceAll("\r[K", "\n").replaceAll("\r", "\n");
+ line = line.replaceAll("\r\u001B[K", "\n").replaceAll("\r", "\n");
// Last line from the textarea will be overwritten, so we remove it
const lastLineIndex = outputElement.innerHTML.lastIndexOf("\n");
@@ -138,7 +140,7 @@ function parseLines(outputElement, text) {
});
// Append the new text to the end of the output
- outputElement.append(line);
+ outputElement.innerHTML += line;
}
}
diff --git a/scripts/js/groups-lists.js b/scripts/js/groups-lists.js
index 8d540c7f..47c3db92 100644
--- a/scripts/js/groups-lists.js
+++ b/scripts/js/groups-lists.js
@@ -234,23 +234,20 @@ function initTable() {
if (data.address.startsWith("file://")) {
// Local files cannot be downloaded from a distant client so don't show
// a link to such a list here
- $("td:eq(3)", row).html(
- '
' +
- utils.escapeHtml(data.address) +
- ""
- );
+ const codeElem = document.createElement("code");
+ codeElem.id = "address_" + dataId;
+ codeElem.className = "breakall";
+ codeElem.textContent = data.address;
+ $("td:eq(3)", row).empty().append(codeElem);
} else {
- $("td:eq(3)", row).html(
- '
' +
- utils.escapeHtml(data.address) +
- " "
- );
+ const aElem = document.createElement("a");
+ aElem.id = "address_" + dataId;
+ aElem.className = "breakall";
+ aElem.href = data.address;
+ aElem.target = "_blank";
+ aElem.rel = "noopener noreferrer";
+ aElem.textContent = data.address;
+ $("td:eq(3)", row).empty().append(aElem);
}
$("td:eq(4)", row).html(
@@ -518,12 +515,12 @@ function addList(event) {
}
$.ajax({
- url: document.body.dataset.apiurl + "/lists",
+ url: document.body.dataset.apiurl + "/lists?type=" + encodeURIComponent(type),
method: "post",
dataType: "json",
processData: false,
contentType: "application/json; charset=utf-8",
- data: JSON.stringify({ address: addresses, comment, type, groups: group }),
+ data: JSON.stringify({ address: addresses, comment, groups: group }),
success(data) {
utils.enableAll();
utils.listsAlert(type + "list", addresses, data);
diff --git a/scripts/js/index.js b/scripts/js/index.js
index 9bf49555..f79aa0df 100644
--- a/scripts/js/index.js
+++ b/scripts/js/index.js
@@ -226,7 +226,7 @@ function updateClientsOverTime() {
});
}
-const upstreams = {};
+const upstreamIPs = [];
function updateForwardDestinationsPie() {
$.getJSON(document.body.dataset.apiurl + "/stats/upstreams", data => {
const v = [];
@@ -248,11 +248,8 @@ function updateForwardDestinationsPie() {
label += "#" + item.port;
}
- // Store upstreams for generating links to the Query Log
- upstreams[label] = item.ip;
- if (item.port > 0) {
- upstreams[label] += "#" + item.port;
- }
+ // Store upstreams IPs for generating links to the Query Log
+ upstreamIPs.push(item.port > 0 ? item.ip + "#" + item.port : item.ip);
const percent = (100 * item.count) / sum;
values.push([label, percent, THEME_COLORS[i++ % THEME_COLORS.length]]);
@@ -521,8 +518,8 @@ function labelWithPercentage(tooltipLabel, skipZero = false) {
// Sum all queries for the current time by iterating over all keys in the
// current dataset
let sum = 0;
- for (const value of Object.values(tooltipLabel.parsed._stacks.y)) {
- if (value === undefined) continue;
+ for (const [key, value] of Object.entries(tooltipLabel.parsed._stacks.y)) {
+ if (key.startsWith("_") || value === undefined) continue;
const num = Number.parseInt(value, 10);
if (num) sum += num;
}
@@ -639,9 +636,11 @@ $(() => {
display: false,
},
tooltip: {
- enabled: true,
+ // Disable the on-canvas tooltip
+ enabled: false,
intersect: false,
- yAlign: "bottom",
+ external: customTooltips,
+ yAlign: "top",
itemSort(a, b) {
return b.datasetIndex - a.datasetIndex;
},
@@ -656,7 +655,7 @@ $(() => {
return "Queries from " + from + " to " + to;
},
label(tooltipLabel) {
- return labelWithPercentage(tooltipLabel);
+ return labelWithPercentage(tooltipLabel, true);
},
},
},
@@ -893,6 +892,8 @@ $(() => {
elements: {
arc: {
borderColor: $(".box").css("background-color"),
+ hoverBorderColor: $(".box").css("background-color"),
+ hoverOffset: 10,
},
},
plugins: {
@@ -917,6 +918,9 @@ $(() => {
animation: {
duration: 750,
},
+ layout: {
+ padding: 10,
+ },
},
});
@@ -939,6 +943,8 @@ $(() => {
elements: {
arc: {
borderColor: $(".box").css("background-color"),
+ hoverBorderColor: $(".box").css("background-color"),
+ hoverOffset: 10,
},
},
plugins: {
@@ -963,6 +969,9 @@ $(() => {
animation: {
duration: 750,
},
+ layout: {
+ padding: 10,
+ },
},
});
diff --git a/scripts/js/queries.js b/scripts/js/queries.js
index c0a3c2ba..41091a27 100644
--- a/scripts/js/queries.js
+++ b/scripts/js/queries.js
@@ -28,6 +28,14 @@ const filters = [
"reply",
"dnssec",
];
+let doDNSSEC = false;
+
+// Check if pihole is validiting DNSSEC
+function getDnssecConfig() {
+ $.getJSON(document.body.dataset.apiurl + "/config/dns/dnssec", data => {
+ doDNSSEC = data.config.dns.dnssec;
+ });
+}
function initDateRangePicker() {
$("#querytime").daterangepicker(
@@ -480,6 +488,9 @@ function liveUpdate() {
}
$(() => {
+ // Do we want to show DNSSEC icons?
+ getDnssecConfig();
+
// Do we want to filter queries?
const GETDict = utils.parseQueryString();
@@ -561,11 +572,13 @@ $(() => {
utils.stateSaveCallback("query_log_table", data);
},
stateLoadCallback() {
- return utils.stateLoadCallback("query_log_table");
+ const state = utils.stateLoadCallback("query_log_table");
+ // Default to 25 entries if "All" was previously selected
+ if (state) state.length = state.length === -1 ? 25 : state.length;
+ return state;
},
rowCallback(row, data) {
const querystatus = parseQueryStatus(data);
- const dnssec = parseDNSSEC(data);
if (querystatus.icon !== false) {
$("td:eq(1)", row).html(
@@ -589,14 +602,17 @@ $(() => {
// Prefix colored DNSSEC icon to domain text
let dnssecIcon = "";
- dnssecIcon =
- '
';
+ if (doDNSSEC === true) {
+ const dnssec = parseDNSSEC(data);
+ dnssecIcon =
+ '
';
+ }
// Escape HTML in domain
domain = dnssecIcon + utils.escapeHtml(domain);
diff --git a/scripts/js/settings-dhcp.js b/scripts/js/settings-dhcp.js
index 3074b97c..016d080d 100644
--- a/scripts/js/settings-dhcp.js
+++ b/scripts/js/settings-dhcp.js
@@ -361,7 +361,7 @@ $(document).on("focus input", "#StaticDHCPTable td[contenteditable]", function (
if (!row.next().hasClass("edit-hint-row")) {
row.next(".edit-hint-row").remove(); // Remove any existing hint
row.after(
- '
Please save this line before editing another or leaving the page, otherwise your changes will be lost. '
+ '
Please confirm changes using the green button, then click "Save & Apply" before leaving the page. '
);
}
});
@@ -398,10 +398,10 @@ function renderStaticDHCPTable() {
$("
")
.append(
$(
- '
'
+ '
'
)
.attr("data-row", idx)
- .attr("title", "Save changes to this line")
+ .attr("title", "Confirm changes to this line")
.attr("data-toggle", "tooltip")
)
.append(" ")
@@ -518,3 +518,17 @@ $(document).on("input blur paste", "#StaticDHCPTable td.static-ipaddr", function
$(this).attr("title", "");
}
});
+
+$(document).on("input blur paste", "#StaticDHCPTable td.static-hostname", function () {
+ const val = $(this).text().trim();
+ // Hostnames must not contain spaces, commas, or characters invalid in DNS names
+ const hostnameValidator = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/v;
+ if (val && !hostnameValidator.test(val)) {
+ $(this).addClass("table-danger");
+ $(this).removeClass("table-success");
+ $(this).attr("title", "Invalid hostname: only letters, digits, hyphens, and dots allowed");
+ } else {
+ $(this).removeClass("table-danger table-success");
+ $(this).attr("title", "");
+ }
+});
diff --git a/scripts/js/settings-dns-records.js b/scripts/js/settings-dns-records.js
index b9598455..0686cf32 100644
--- a/scripts/js/settings-dns-records.js
+++ b/scripts/js/settings-dns-records.js
@@ -215,7 +215,7 @@ $(() => {
$("#btnAdd-host").on("click", () => {
utils.disableAll();
- const elem = $("#Hip").val() + " " + $("#Hdomain").val();
+ const elem = $("#Hip").val().trim() + " " + $("#Hdomain").val().trim();
const url = document.body.dataset.apiurl + "/config/dns/hosts/" + encodeURIComponent(elem);
utils.showAlert("info", "", "Adding DNS record...", elem);
$.ajax({
@@ -239,7 +239,7 @@ $(() => {
$("#btnAdd-cname").on("click", () => {
utils.disableAll();
- let elem = $("#Cdomain").val() + "," + $("#Ctarget").val();
+ let elem = $("#Cdomain").val().trim() + "," + $("#Ctarget").val().trim();
const ttlVal = Number.parseInt($("#Cttl").val(), 10);
// TODO Fix eslint
// eslint-disable-next-line unicorn/prefer-number-properties
diff --git a/scripts/js/settings-system.js b/scripts/js/settings-system.js
index fc3f7beb..ae5ebb73 100644
--- a/scripts/js/settings-system.js
+++ b/scripts/js/settings-system.js
@@ -267,7 +267,7 @@ $(".confirm-flusharp").confirm({
title: "Confirmation required",
confirm() {
$.ajax({
- url: document.body.dataset.apiurl + "/action/flush/arp",
+ url: document.body.dataset.apiurl + "/action/flush/network",
type: "POST",
}).fail(data => {
apiFailure(data);
diff --git a/scripts/lua/header_authenticated.lp b/scripts/lua/header_authenticated.lp
index 609d7420..c544cf7a 100644
--- a/scripts/lua/header_authenticated.lp
+++ b/scripts/lua/header_authenticated.lp
@@ -24,7 +24,7 @@ mg.include('header.lp','r')
-
+
diff --git a/settings-api.lp b/settings-api.lp
index 2bdbe3e7..493c315d 100644
--- a/settings-api.lp
+++ b/settings-api.lp
@@ -180,7 +180,7 @@ mg.include('scripts/lua/settings_header.lp','r')
Enable two-factor authentication
-
Use a phone app like Google Authenticator, 2FA Authenticator, FreeOTP or BitWarden, etc. to get 2FA codes when prompted during login.
+
Use a phone app like Google Authenticator, 2FA Authenticator, FreeOTP or Bitwarden, etc. to get 2FA codes when prompted during login.
1. Scan the QR code below with your app or enter the secret manually.
@@ -192,11 +192,15 @@ mg.include('scripts/lua/settings_header.lp','r')
2. Enter the 2FA code from your app below to confirm that you have set up 2FA correctly.
-
diff --git a/settings-dns.lp b/settings-dns.lp
index e88c9db3..547fde8b 100644
--- a/settings-dns.lp
+++ b/settings-dns.lp
@@ -59,7 +59,7 @@ mg.include('scripts/lua/settings_header.lp','r')
@@ -68,7 +68,7 @@ mg.include('scripts/lua/settings_header.lp','r')
The DNS domains for your Pi-hole. This DNS domain is purely local. FTL may answer queries from its local cache and configuration but *never* forwards any requests upstream *unless* you have configured a dns.revServer exactly for this domain. If no domain is specified and you are using Pi-hole's DHCP server, then any hostnames with a domain part (i.e., with a period) will be disallowed. If a domain is specified, then hostnames with a domain parts matching the domain here are allowed. In addition, when a suffix is set then hostnames without a domain part have the suffix added as an optional domain part.
diff --git a/settings-system.lp b/settings-system.lp
index 9bde072a..171ad73a 100644
--- a/settings-system.lp
+++ b/settings-system.lp
@@ -44,7 +44,7 @@ mg.include('scripts/lua/settings_header.lp','r')
Kernel:
-
+
Uptime:
diff --git a/style/pi-hole.css b/style/pi-hole.css
index 6acd5c4b..97d93925 100644
--- a/style/pi-hole.css
+++ b/style/pi-hole.css
@@ -594,12 +594,6 @@ td.details-control {
border: none;
}
-.form-control-feedback {
- right: 0.5em;
- width: 16px;
- height: 100%;
-}
-
.loginpage-logo {
margin: 0 0 10px;
}
@@ -1650,3 +1644,13 @@ div.dhcp-hosts-wrapper {
background-color: #599900;
color: #fff;
}
+
+/* Used in query log page */
+td.dnssec {
+ padding-inline-start: 2.25em !important;
+ text-indent: -1.25em;
+}
+td.dnssec i {
+ text-indent: 0;
+ margin-left: -0.5rem;
+}