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
40 changed files with 2348 additions and 917 deletions
+3 -3
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
+1 -1
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
+1 -1
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
+1 -1
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
+1996 -600
View File
File diff suppressed because it is too large Load Diff
+24 -5
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"
}
}
+5 -2
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">
+1
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)
+5 -56
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() {
+49 -1
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
});
}
+15 -53
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() {
+5 -51
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) {
+18 -62
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() {
+32 -4
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() {
-5
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();
});
+56 -8
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");
+7 -7
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;
}
}
}
+1 -1
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
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
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>
+1 -1
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>
+1 -1
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>
+1 -1
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>
+1 -1
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>
+23 -23
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>
+52 -11
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;
}
+1
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 */
+1
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 */
+1
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 */
+2
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 */
+18 -1
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);
}
}
+1 -1
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)