Merge branch 'development-v6' into tweak/settings_changed_only

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER 2024-01-07 14:03:56 +01:00
commit 52991f495c
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
40 changed files with 2348 additions and 917 deletions

View File

@ -24,12 +24,12 @@ jobs:
uses: actions/checkout@v4.1.1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: "javascript"
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@ -13,7 +13,7 @@ jobs:
issues: write
steps:
- uses: actions/stale@v8.0.0
- uses: actions/stale@v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30

View File

@ -17,7 +17,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/stale@v8.0.0
- uses: actions/stale@v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Do not automatically mark PR/issue as stale

View File

@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@v4.1.1
- name: Set up Node.js
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version: "20.x"
cache: npm

2596
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{
"name": "AdminLTE",
"name": "Pi-hole web interface",
"version": "1.0.0",
"private": true,
"description": "",
@ -27,9 +27,9 @@
"devDependencies": {
"autoprefixer": "^10.4.16",
"eslint-plugin-compat": "^4.2.0",
"postcss": "^8.4.31",
"postcss-cli": "^10.1.0",
"prettier": "^3.1.0",
"postcss": "^8.4.33",
"postcss-cli": "^11.0.0",
"prettier": "^3.1.1",
"xo": "^0.56.0"
},
"browserslist": [
@ -106,5 +106,24 @@
"unicorn/no-negated-condition": "off"
}
},
"dependencies": {}
"dependencies": {
"admin-lte": "2.4.18",
"bootstrap-notify": "3.1.3",
"bootstrap-select": "1.13.18",
"bootstrap-toggle": "2.2.2",
"chart.js": "4.4.1",
"chartjs-adapter-moment": "1.0.1",
"chartjs-plugin-deferred": "2.0.0",
"chartjs-plugin-zoom": "2.0.1",
"datatables.net-bs": "1.10.21",
"datatables.net-buttons-bs": "1.7.1",
"datatables.net-select-bs": "1.3.1",
"daterangepicker": "3.1.0",
"hammerjs": "2.0.8",
"jquery": "3.7.1",
"moment": "2.30.1",
"myclabs.jquery.confirm": "2.7.0",
"qrious": "4.0.2",
"select2": "4.0.13"
}
}

View File

@ -66,7 +66,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
</div>
</div>
<div class="form-group col-md-6">
<div><input type="checkbox" id="disk"><label for="disk">Query on-disk data. This is <em>a lot</em> slower but necessary if you want to obtain queries older than 24 hours.</label></div>
<div><input type="checkbox" id="disk"><label for="disk">Query on-disk data. This is <em>a lot</em> slower but necessary if you want to obtain queries older than 24 hours. This option disables live update.</label></div>
</div>
</div>
<div class="row">
@ -152,7 +152,10 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
<div class="box" id="recent-queries">
<div class="box-header with-border">
<h3 class="box-title">Recent Queries</h3>
<a id="refresh" href="#" class="btn btn-sm btn-info btn-flat pull-right">Refresh</a>
<div class="pull-right align-click-options">
<span><input type="checkbox" id="live"><label for="live">Live update</label></span>
<a id="refresh" href="#" class="btn btn-sm btn-info btn-flat">Refresh</a>
</div>
</div>
<!-- /.box-header -->
<div class="box-body">

View File

@ -13,6 +13,7 @@
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)

View File

@ -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 utils:false, groups:false,, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false */
/* global utils:false, groups:false,, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
/* exported initTable */
var table;
@ -277,10 +277,10 @@ function initTable() {
var ids = [];
$("tr.selected").each(function () {
// ... add the row identified by "data-id".
ids.push($(this).attr("data-id"));
ids.push({ item: $(this).attr("data-id") });
});
// Delete all selected rows at once
delItems(ids);
delGroupItems("client", ids, table);
},
},
],
@ -347,59 +347,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteClient() {
// Passes the button data-id attribute as ID
const ids = [$(this).attr("data-id")];
delItems(ids);
}
function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
// Get first element from array
const clientRaw = ids[0];
const client = utils.hexDecode(clientRaw);
// Remove first element from array
ids.shift();
utils.disableAll();
const idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting client...", client);
$.ajax({
url: "/api/clients/" + encodeURIComponent(client),
method: "delete",
})
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted client: ", client);
table.row(clientRaw).remove().draw(false);
if (ids.length > 0) {
// Recursively delete all remaining items
delItems(ids);
return;
}
table.ajax.reload(null, false);
// Clear selection after deletion
table.rows().deselect();
utils.changeBulkDeleteStates(table);
// Update number of clients in the sidebar
updateFtlInfo();
})
.fail(function (data, exception) {
apiFailure(data);
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while deleting client(s): " + idstring,
data.responseText
);
console.log(exception); // eslint-disable-line no-console
});
const ids = [{ item: $(this).attr("data-id") }];
delGroupItems("client", ids, table);
}
function addClient() {

View File

@ -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, utils:false, initTable:false */
/* global apiFailure:false, utils:false, initTable:false, updateFtlInfo:false */
// eslint-disable-next-line no-unused-vars
var groups = [];
@ -39,3 +39,51 @@ function processGroupResult(data, type, done, notDone) {
utils.showAlert("error", "", `Error while ${notDone} ${type} ${error.item}`, error.error);
});
}
// eslint-disable-next-line no-unused-vars
function delGroupItems(type, ids, table) {
// Check input validity
if (!Array.isArray(ids)) return;
const url = "/api/" + type + "s:batchDelete";
// 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 + ", ";
}
// Remove last comma and space from idstring
idstring = idstring.substring(0, idstring.length - 2);
// Append "s" to type if more than one item is deleted
type += ids.length > 1 ? "s" : "";
utils.disableAll();
utils.showAlert("info", "", "Deleting " + ids.length + " " + type + "...", idstring);
$.ajax({
url: url,
data: JSON.stringify(ids),
method: "POST",
})
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted " + type, idstring);
table.ajax.reload(null, false);
// Clear selection after deletion
table.rows().deselect();
utils.changeBulkDeleteStates(table);
// Update number of <type> items in the sidebar
updateFtlInfo();
})
.fail(function (data, exception) {
apiFailure(data);
utils.enableAll();
utils.showAlert("error", "", "Error while deleting " + type, data.responseText);
console.log(exception); // eslint-disable-line no-console
});
}

View File

@ -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 utils:false, groups:false,, getGroups:false, updateFtlInfo:false, apiFailure:false, processGroupResult:false */
/* global utils:false, groups:false,, getGroups:false, updateFtlInfo:false, apiFailure:false, processGroupResult:false, delGroupItems:false */
/* exported initTable */
var table;
@ -333,7 +333,7 @@ function initTable() {
ids.push($(this).attr("data-id"));
});
// Delete all selected rows at once
delItems(ids);
deleteDomains(ids);
},
},
],
@ -430,60 +430,22 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteDomain() {
// Passes the button data-id attribute as ID
const ids = [$(this).attr("data-id")];
delItems(ids);
deleteDomains([$(this).attr("data-id")]);
}
function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
function deleteDomains(encodedIds) {
const decodedIds = [];
for (let i = 0; i < encodedIds.length; i++) {
// Decode domain, type, and kind and add to array
const parts = encodedIds[i].split("_");
decodedIds[i] = {
item: parts[0],
type: parts[1],
kind: parts[2],
};
}
// Get first element from array
const domainRaw = ids[0];
const domain = utils.hexDecode(domainRaw.split("_")[0]);
const typestr = $("#old_type_" + domainRaw).val();
// Remove first element from array
ids.shift();
utils.disableAll();
const idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting domain...", domain);
$.ajax({
url: "/api/domains/" + typestr + "/" + encodeURIComponent(domain),
method: "delete",
})
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted domain: ", domain);
table.row(domainRaw).remove().draw(false);
if (ids.length > 0) {
// Recursively delete all remaining items
delItems(ids);
return;
}
table.ajax.reload(null, false);
// Clear selection after deletion
table.rows().deselect();
utils.changeBulkDeleteStates(table);
// Update number of lists in the sidebar
updateFtlInfo();
})
.fail(function (data, exception) {
apiFailure(data);
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while deleting domain(s): " + idstring,
data.responseText
);
console.log(exception); // eslint-disable-line no-console
});
delGroupItems("domain", decodedIds, table);
}
function addDomain() {

View File

@ -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 utils:false, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false */
/* global utils:false, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
/* exported initTable */
var table;
@ -397,10 +397,10 @@ function initTable() {
var ids = [];
$("tr.selected").each(function () {
// ... add the row identified by "data-id".
ids.push($(this).attr("data-id"));
ids.push({ item: $(this).attr("data-id") });
});
// Delete all selected rows at once
delItems(ids);
delGroupItems("list", ids, table);
},
},
],
@ -485,54 +485,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteList() {
// Passes the button data-id attribute as ID
const ids = [$(this).attr("data-id")];
delItems(ids);
}
function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
// Get first element from array
const addressRaw = ids[0];
const address = utils.hexDecode(addressRaw);
// Remove first element from array
ids.shift();
utils.disableAll();
const idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting list(s) ...", address);
$.ajax({
url: "/api/lists/" + encodeURIComponent(address),
method: "delete",
})
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted list: ", address);
table.row(addressRaw).remove().draw(false);
if (ids.length > 0) {
// Recursively delete all remaining items
delItems(ids);
return;
}
table.ajax.reload(null, false);
// Clear selection after deletion
table.rows().deselect();
utils.changeBulkDeleteStates(table);
// Update number of lists in the sidebar
updateFtlInfo();
})
.fail(function (data, exception) {
apiFailure(data);
utils.enableAll();
utils.showAlert("error", "", "Error while deleting list(s): " + idstring, data.responseText);
console.log(exception); // eslint-disable-line no-console
});
const ids = [{ item: $(this).attr("data-id") }];
delGroupItems("list", ids, table);
}
function addList(event) {

View File

@ -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 utils:false, apiFailure:false, updateFtlInfo:false, processGroupResult:false */
/* global utils:false, apiFailure:false, updateFtlInfo:false, processGroupResult:false, delGroupItems:false */
var table,
idNames = {};
@ -56,14 +56,15 @@ $(function () {
],
drawCallback: function () {
// Hide buttons if all groups were deleted
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
// if there is one row, it's the default group
var 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) {
idNames[data.id] = data.name;
$(row).attr("data-id", data.id);
var dataId = utils.hexEncode(data.name);
$(row).attr("data-id", dataId);
var tooltip =
"Added: " +
utils.datetime(data.date_added, false) +
@ -102,12 +103,13 @@ $(function () {
commentEl.on("change", editGroup);
$("td:eq(4)", row).empty();
// Show delete button for all but the default group
if (data.id !== 0) {
var button =
'<button type="button" class="btn btn-danger btn-xs" id="deleteGroup_' +
data.id +
'" data-del-id="' +
data.id +
dataId +
'" data-id="' +
dataId +
'">' +
'<span class="far fa-trash-alt"></span>' +
"</button>";
@ -151,10 +153,10 @@ $(function () {
var ids = [];
$("tr.selected").each(function () {
// ... add the row identified by "data-id".
ids.push(parseInt($(this).attr("data-id"), 10));
ids.push({ item: $(this).attr("data-id") });
});
// Delete all selected rows at once
delItems(ids);
delGroupItems("group", ids, table);
},
},
],
@ -198,6 +200,11 @@ $(function () {
}
table.on("init select deselect", function () {
// if the Default group is selected, undo the selection of it
if (table.rows({ selected: true }).data().pluck("id").indexOf(0) !== -1) {
table.rows(0).deselect();
}
utils.changeBulkDeleteStates(table);
});
@ -221,59 +228,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteGroup() {
// Passes the button data-del-id attribute as ID
const ids = [parseInt($(this).attr("data-del-id"), 10)];
delItems(ids);
}
function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
for (const id of ids) {
// Exploit prevention: Return early for non-numeric IDs
if (typeof id !== "number") return;
}
// Get first element from array
const id = ids[0];
const name = idNames[id];
// Remove first element from array
ids.shift();
utils.disableAll();
const idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting group...", name);
$.ajax({
url: "/api/groups/" + name,
method: "delete",
})
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted group: ", name);
table.row(id).remove().draw(false);
if (ids.length > 0) {
// Recursively delete all remaining items
delItems(ids);
return;
}
table.ajax.reload(null, false);
// Clear selection after deletion
table.rows().deselect();
utils.changeBulkDeleteStates(table);
// Update number of groups in the sidebar
updateFtlInfo();
})
.fail(function (data, exception) {
apiFailure(data);
utils.enableAll();
utils.showAlert("error", "", "Error while deleting group(s): " + idstring, data.responseText);
console.log(exception); // eslint-disable-line no-console
});
const ids = [{ item: $(this).attr("data-id") }];
delGroupItems("group", ids, table);
}
function addGroup() {

View File

@ -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 moment:false, utils:false */
/* 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
@ -263,7 +263,7 @@ function formatInfo(data) {
// Parse Query Status
var queryStatus = parseQueryStatus(data);
var divStart = '<div class="col-xl-2 col-lg-4 col-md-6 col-12">';
var divStart = '<div class="col-xl-2 col-lg-4 col-md-6 col-12 overflow-wrap">';
var statusInfo = "";
if (queryStatus.colorClass !== false) {
statusInfo =
@ -282,7 +282,7 @@ function formatInfo(data) {
var regexLink =
'<a href="groups/domains?domainid=' +
data.regex_id +
'" target="_blank">regex ID ' +
'" target="_blank">entry with ID ' +
data.regex_id +
"</a>";
regexInfo =
@ -470,6 +470,19 @@ function getAPIURL(filters) {
return encodeURI(apiurl);
}
var liveMode = false;
$("#live").prop("checked", liveMode);
$("#live").on("click", function () {
liveMode = $(this).prop("checked");
liveUpdate();
});
function liveUpdate() {
if (liveMode) {
refreshTable();
}
}
$(function () {
// Do we want to filter queries?
var GETDict = utils.parseQueryString();
@ -512,6 +525,10 @@ $(function () {
dataFilter: function (d) {
var json = jQuery.parseJSON(d);
cursor = json.cursor; // Extract cursor from original data
if (liveMode) {
utils.setTimer(liveUpdate, REFRESH_INTERVAL.query_log);
}
return d;
},
},
@ -521,7 +538,7 @@ $(function () {
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
"<'row'<'col-sm-5'i><'col-sm-7'p>>",
autoWidth: false,
processing: true,
processing: false,
order: [[0, "desc"]],
columns: [
{
@ -639,6 +656,17 @@ $(function () {
});
$("#refresh").on("click", refreshTable);
// Disable live mode when #disk is checked
$("#disk").on("click", function () {
if ($(this).prop("checked")) {
$("#live").prop("checked", false);
$("#live").prop("disabled", true);
liveMode = false;
} else {
$("#live").prop("disabled", false);
}
});
});
function refreshTable() {

View File

@ -326,11 +326,6 @@ function createDynamicConfigTabs() {
});
$("#advanced-overlay").hide();
$("#advanced-content").append(
'<div class="col-lg-12 settings-level-expert">' +
'<button type="button" class="btn btn-primary save-button" id="save"><i class="fa-solid fa-fw fa-floppy-disk"></i>&nbsp;Save & Apply</button>' +
"</div>"
);
$("button[id='save']").on("click", function () {
saveSettings();
});

View File

@ -20,7 +20,7 @@ const fadeIn = true;
const markUpdates = true;
// Format a line of the dnsmasq log
function formatLine(line) {
function formatDnsmasq(line) {
// Remove dnsmasq + PID
let txt = line.replaceAll(/ dnsmasq\[\d*]/g, "");
@ -38,6 +38,36 @@ function formatLine(line) {
return txt;
}
function formatFTL(line, prio) {
// Colorize priority
let prioClass = "";
switch (prio) {
case "INFO": {
prioClass = "text-success";
break;
}
case "WARNING": {
prioClass = "text-warning";
break;
}
case "ERR":
case "EMERG":
case "ALERT":
case "CRIT": {
prioClass = "text-danger";
break;
}
default:
prioClass = prio.startsWith("DEBUG") ? "text-info" : "text-muted";
}
// Return formatted line
return `<span class="${prioClass}">${utils.escapeHtml(prio)}</span> ${line}`;
}
// Function that asks the API for new data
function getData() {
// Only update when spinner is spinning
@ -61,7 +91,7 @@ function getData() {
// Check if we have a new PID -> FTL was restarted
if (lastPID !== data.pid) {
if (lastPID !== -1) {
$("#output").append("<i class='text-danger'>*** FTL restarted ***</i><br>");
$("#output").append("<div><i class='text-danger'>*** FTL restarted ***</i></div>");
}
// Remember PID
@ -76,7 +106,7 @@ function getData() {
// Set placeholder text if log file is empty and we have no new lines
if (data.log.length === 0) {
if (nextID === 0) {
$("#output").html("<i>*** Log file is empty ***</i>");
$("#output").html("<div><i>*** Log file is empty ***</i></div>");
}
utils.setTimer(getData, REFRESH_INTERVAL.logs);
@ -90,16 +120,19 @@ function getData() {
}
data.log.forEach(function (line) {
// Format line if this is the dnsmasq log
if (GETDict.file === "dnsmasq") line.message = formatLine(line.message);
// Escape HTML
line.message = utils.escapeHtml(line.message);
// Format line if applicable
if (GETDict.file === "dnsmasq") line.message = formatDnsmasq(line.message);
else if (GETDict.file === "ftl") line.message = formatFTL(line.message, line.prio);
// Add new line to output
$("#output").append(
'<span class="log-entry"><span class="text-muted">' +
'<div class="log-entry"><span class="text-muted">' +
moment(1000 * line.timestamp).format("YYYY-MM-DD HH:mm:ss.SSS") +
"</span> " +
line.message +
"<br></span>"
"</div>"
);
if (fadeIn) {
//$(".left-line:last").fadeOut(2000);
@ -137,7 +170,22 @@ function getData() {
var gAutoScrolling = true;
$("#output").on("scroll", function () {
// Check if we are at the bottom of the output
if ($("#output").scrollTop() + $("#output").innerHeight() >= $("#output")[0].scrollHeight) {
//
// - $("#output")[0].scrollHeight: This gets the entire height of the content
// of the "output" element, including the part that is not visible due to
// scrolling.
// - $("#output").innerHeight(): This gets the inner height of the "output"
// element, which is the visible part of the content.
// - $("#output").scrollTop(): This gets the number of pixels that the content
// of the "output" element is scrolled vertically from the top.
//
// By subtracting the inner height and the scroll top from the scroll height,
// you get the distance from the bottom of the scrollable area.
const bottom =
$("#output")[0].scrollHeight - $("#output").innerHeight() - $("#output").scrollTop();
// Add a tolerance of four line heights
const tolerance = 4 * parseFloat($("#output").css("line-height"));
if (bottom <= tolerance) {
// Auto-scrolling is enabled
gAutoScrolling = true;
$("#autoscrolling").addClass("fa-check");

View File

@ -561,35 +561,35 @@ function listAlert(type, items, data) {
// Show a list of successful items if there are any
if (data.processed.success.length > 0) {
message +=
"<strong>Successfully added " +
"Successfully added " +
data.processed.success.length +
" " +
type +
(data.processed.success.length !== 1 ? "s" : "") +
":</strong>";
":";
// Loop over data.processed.success and print "item"
for (const item in data.processed.success) {
if (Object.prototype.hasOwnProperty.call(data.processed.success, item)) {
message += "<br>- <strong>" + data.processed.success[item].item + "</strong>";
message += "\n- " + data.processed.success[item].item;
}
}
}
// Add a line break if there are both successful and failed items
if (data.processed.success.length > 0 && data.processed.errors.length > 0) {
message += "<br><br>";
message += "\n\n";
}
// Show a list of failed items if there are any
if (data.processed.errors.length > 0) {
message +=
"<strong>Failed to add " +
"Failed to add " +
data.processed.errors.length +
" " +
type +
(data.processed.errors.length !== 1 ? "s" : "") +
":</strong>\n";
":\n";
// Loop over data.processed.errors and print "item: error"
for (const item in data.processed.errors) {
@ -600,7 +600,7 @@ function listAlert(type, items, data) {
error = "Already present";
}
message += "<br>- <strong>" + data.processed.errors[item].item + "</strong>: " + error;
message += "\n- " + data.processed.errors[item].item + ": " + error;
}
}
}

View File

@ -13,7 +13,7 @@ mg.include('header.lp','r')
<script src="<?=pihole.fileversion('scripts/vendor/datatables.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/datatables.select.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/datatables.buttons.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/chart.umd.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/chart.umd.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/chartjs-plugin-deferred.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/moment.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/chartjs-adapter-moment.js')?>"></script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

14
scripts/vendor/chart.umd.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
scripts/vendor/moment.min.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="overlay" id="advanced-overlay">
<i class="fa fa-sync fa-spin"></i>
</div>
<div class="col-lg-12 settings-level-expert">
<div class="col-lg-12 settings-level-expert save-button-container">
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i>&nbsp;Save & Apply</button>
</div>
</div>

View File

@ -177,7 +177,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
</div>
</div>
<div class="col-lg-12 settings-level-basic">
<div class="col-lg-12 settings-level-basic save-button-container">
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i>&nbsp;Save & Apply</button>
</div>
</div>

View File

@ -200,7 +200,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
</div>
</div>
<div class="col-lg-12 settings-level-basic">
<div class="col-lg-12 settings-level-basic save-button-container">
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i>&nbsp;Save & Apply</button>
</div>
</div>

View File

@ -258,7 +258,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
</div>
</div>
<div class="col-lg-12 settings-level-basic">
<div class="col-lg-12 settings-level-basic save-button-container">
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i>&nbsp;Save & Apply</button>
</div>
</div>

View File

@ -103,7 +103,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
</div>
</div>
<div class="col-lg-12 settings-level-basic">
<div class="col-lg-12 settings-level-basic save-button-container">
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i>&nbsp;Save & Apply</button>
</div>
</div>

View File

@ -70,67 +70,67 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-lg-12">
<table class="table table-striped table-bordered nowrap">
<tbody>
<tr>
<tr title="Client broadcast to locate available servers">
<th scope="row">
<span>DHCPDISCOVER:</span>
</th>
<td id="sysinfo-dhcp-discover">&nbsp;</td>
</tr>
<tr>
<tr title="Server to client in response to DHCPDISCOVER with offer of configuration parameters">
<th scope="row">
<span>DHCPOFFER:</span>
</th>
<td id="sysinfo-dhcp-offer">&nbsp;</td>
</tr>
<tr>
<tr title="Client message to servers either &#010; (a) requesting offered parameters from one server and implicitly declining offers from all others, &#010; (b) confirming correctness of previously allocated address after, e.g., system reboot, or &#010; (c) extending the lease on a particular network address">
<th scope="row">
<span>DHCPREQUEST:</span>
</th>
<td id="sysinfo-dhcp-request">&nbsp;</td>
</tr>
<tr>
<tr title="Server to client with configuration parameters, including committed network address">
<th scope="row">
<span>DHCPACK:</span>
</th>
<td id="sysinfo-dhcp-ack">&nbsp;</td>
</tr>
<tr>
<tr title="Server to client indicating client's notion of network address is incorrect (e.g., client has moved to new subnet) or client's lease as expired">
<th scope="row">
<span>DHCPNAK:</span>
</th>
<td id="sysinfo-dhcp-nak">&nbsp;</td>
</tr>
<tr>
<tr title="Client to server indicating network address is already in use">
<th scope="row">
<span>DHCPDECLINE:</span>
</th>
<td id="sysinfo-dhcp-decline">&nbsp;</td>
</tr>
<tr>
<tr title="Client to server, asking only for local configuration parameters; client already has externally configured network address">
<th scope="row">
<span>DHCPINFORM:</span>
</th>
<td id="sysinfo-dhcp-inform">&nbsp;</td>
</tr>
<tr>
<tr title="Client to server relinquishing network address and cancelling remaining lease">
<th scope="row">
<span>DHCPRELEASE:</span>
</th>
<td id="sysinfo-dhcp-release">&nbsp;</td>
</tr>
<tr>
<tr title="Could not provide an answer, e.g., because there are no leases left, the client wants to renew a lease that is outside of our range, or the explicitly requested address is already in use">
<th scope="row">
<span>DHCPNOANSWER:</span>
</th>
<td id="sysinfo-dhcp-noanswer">&nbsp;</td>
</tr>
<tr>
<tr title="Processed BOOTP packets">
<th scope="row">
<span>BOOTP:</span>
</th>
<td id="sysinfo-dhcp-bootp">&nbsp;</td>
</tr>
<tr>
<tr title="Processed PXE packets">
<th scope="row">
<span>PXE:</span>
</th>
@ -172,39 +172,39 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-lg-12">
<table class="table table-striped table-bordered nowrap">
<tbody>
<tr>
<tr title="Total size of the DNS cache">
<th scope="row">
<span title="Total size of the DNS cache">DNS cache size:</span>
<span>DNS cache size:</span>
</th>
<td id="sysinfo-dns-cache-size">&nbsp;</td>
</tr>
<tr>
<tr title="Share of the allocated cache that is currently in use (this includes stale/expired entries). If the percentage value is very low, it is advisable to reduce the DNS cache size to optimize performance">
<th scope="row">
<span title="Share of the allocated cache that is currently in use (this includes stale/expired entries). If the percentage value is very low, it is advisable to reduce the DNS cache size to optimize performance">Active cache records:</span>
<span>Active cache records:</span>
</th>
<td id="cache-utilization">&nbsp;</td>
</tr>
<tr>
<tr title="Number of cache insertions, this number will grow continuously as expiring records naturally make space for new records">
<th scope="row">
<span title="Number of cache insertions, this number will grow continuously as expiring records naturally make space for new records">Total cache insertions:</span>
<span>Total cache insertions:</span>
</th>
<td id="sysinfo-dns-cache-inserted">&nbsp;</td>
</tr>
<tr>
<tr title="Number of cache entries that had to be removed although they are not expired. When this number if larger than zero, you should consider increasing your total cache size" lookatme-text="DNS cache evictions:">
<th scope="row">
<span title="Number of cache entries that had to be removed although they are not expired. When this number if larger than zero, you should consider increasing your total cache size" lookatme-text="DNS cache evictions:">DNS cache evictions:</span>
<span>DNS cache evictions:</span>
</th>
<td id="sysinfo-dns-cache-evicted">&nbsp;</td>
</tr>
<tr>
<tr title="Number of expired cache entries (they can still be used by the cache optimizer). These entries will only be freed when space is really needed (for efficiency reasons)">
<th scope="row">
<span title="Number of expired cache entries (they can still be used by the cache optimizer). These entries will only be freed when space is really needed (for efficiency reasons)">Expired DNS cache entries:</span>
<span>Expired DNS cache entries:</span>
</th>
<td id="sysinfo-dns-cache-expired">&nbsp;</td>
</tr>
<tr>
<tr title="Number of immortal cache entries that will never expire (e.g. from /etc/hosts or local configuration)">
<th scope="row">
<span title="Number of immortal cache entries that will never expire (e.g. from /etc/hosts or local configuration)">Immortal DNS cache entries:</span>
<span>Immortal DNS cache entries:</span>
</th>
<td id="sysinfo-dns-cache-immortal">&nbsp;</td>
</tr>

View File

@ -5,6 +5,14 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
:root {
/* Datatables Select - bgcolor */
--datatable-bgcolor: #e0e8ee;
/* Used in settings save button */
--overlay-bgcolor: rgba(212, 220, 232, 0.55);
}
.layout-boxed {
background: url("../img/boxed-bg.png") repeat fixed;
}
@ -1005,11 +1013,6 @@ body:not(.lcars) .filter_types [class*="icheck-"] > input:first-child:checked +
margin-right: 0.5em;
}
/* Datatables Select - bgcolor */
:root {
--datatable-bgcolor: #e0e8ee;
}
div.dt-buttons {
margin: 2px 0 5px;
}
@ -1065,6 +1068,7 @@ table.dataTable tbody > tr > .selected {
#output {
position: relative;
display: grid;
margin: 5px 0;
min-height: 36px;
padding: 4px 8px;
@ -1115,13 +1119,26 @@ table.dataTable tbody > tr > .selected {
}
.pre-scrollable {
max-height: calc(100vh - 300px);
/* use dynamic unit to account for mobile browser interface height */
max-height: max(195px, 100dvh - 170px);
overflow-y: scroll;
}
@media screen and (max-width: 767px) {
.pre-scrollable {
/* reduce max-height on mobile (higher header) */
max-height: max(195px, 100dvh - 220px);
}
}
@media screen and (max-width: 546px) {
.pre-scrollable {
/* reduce max-height even more (box-header text wraps, increasing the height) */
max-height: max(195px, 100dvh - 240px);
}
}
.hr-small {
margin-top: 0px;
margin-bottom: 0px;
width: 100%;
margin: 0 0 -1px;
border-top: 1px solid #ff0000aa;
}
@ -1141,11 +1158,23 @@ table.dataTable tbody > tr > .selected {
opacity: 0.1;
}
.save-button {
.save-button-container {
position: fixed;
bottom: 2%;
right: 2%;
bottom: 1.2em;
right: 0.6em;
z-index: 10;
background: var(--overlay-bgcolor);
width: fit-content;
border-radius: 0.5em;
padding: 0;
}
.save-button {
margin: 1.2em;
}
@media (min-width: 1250px) {
.layout-boxed .save-button-container {
right: calc((100% - 1250px) / 2 + 1.2em);
}
}
.settings-level-basic {
@ -1423,3 +1452,15 @@ table.dataTable tbody > tr > .selected {
.log-entry {
padding-left: 0.5em;
}
.align-click-options {
align-items: center;
display: grid;
grid-auto-flow: column;
grid-column-gap: 15px;
}
.overflow-wrap {
overflow-wrap: break-word;
inline-size: auto;
}

View File

@ -10,6 +10,7 @@
:root {
--datatable-bgcolor: rgba(64, 76, 88, 0.8);
--overlay-bgcolor: var(--datatable-bgcolor);
}
/* fix #2554: browser not detecting dark mode */

View File

@ -109,6 +109,7 @@ select:-webkit-autofill {
--darkreader-selection-background: #004daa;
--darkreader-selection-text: #e8e6e3;
--datatable-bgcolor: rgb(35, 47, 52);
--overlay-bgcolor: rgba(35, 47, 52, 0.7);
}
/* Modified CSS */

View File

@ -29,6 +29,7 @@
--blocked-color: var(--danger-color);
--datatable-bgcolor: rgba(18, 44, 68, 0.3);
--overlay-bgcolor: rgba(24, 28, 32, 0.8);
}
/* removing transition for every element, to avoid unnecessary movement */

View File

@ -25,6 +25,8 @@
--allowed-color: #3c7;
--blocked-color: var(--danger-color);
--overlay-bgcolor: rgba(255, 255, 255, 0.8);
}
/* removing transition for every element, to avoid unnecessary movement */

View File

@ -2123,8 +2123,25 @@ td.highlight {
}
/*** add style to the information/about dropdown ***/
.navbar-nav > .user-menu > .dropdown-menu > .user-header,
.navbar-nav > .user-menu > .dropdown-menu > .user-footer {
background-color: #111111;
}
.save-button-container {
border-radius: 50px;
background: none;
box-shadow:
0 0 10px -1px #000,
0 0 20px 2px #000;
bottom: 2em;
right: 1.7em;
}
.save-button {
margin: 0;
}
@media (min-width: 1320px) {
.layout-boxed .save-button-container {
right: calc((100% - 1320px) / 2 + 0.85em);
}
}

View File

@ -1,5 +1,5 @@
/*!
* Bootstrap-select v1.13.17 (https://developer.snapappointments.com/bootstrap-select)
* Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)