Merge branch 'development-v6' into new/error_pages

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER
2023-10-29 20:58:16 +01:00
40 changed files with 986 additions and 416 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
-
name: Spell-Checking
uses: codespell-project/actions-codespell@master
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
name: editorconfig-checker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.1.1
- uses: editorconfig-checker/action-editorconfig-checker@main
- run: editorconfig-checker
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
name: Syncing branches
steps:
- name: Checkout
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Opening pull request
run: gh pr create -B devel -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal'
env:
+2 -2
View File
@@ -19,10 +19,10 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Set up Node.js
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v4.0.0
with:
node-version: "16.x"
cache: npm
+2 -2
View File
@@ -22,8 +22,8 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
<div class="icon">
<i class="fas fa-globe-americas"></i>
</div>
<a href="network.lp" class="small-box-footer" title="">
<span id="total_clients">-</span> active clients <i class="fa fa-arrow-circle-right"></i>
<a href="network.lp" class="small-box-footer" title="" id="total_clients">
<span id="active_clients">-</span> active clients <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div>
+2 -1
View File
@@ -165,6 +165,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th><i class="fas fa-stopwatch" title="Query reply time"></i></th>
<th></th>
</tr>
</thead>
@@ -175,6 +176,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th><i class="fas fa-stopwatch" title="Query reply time"></i></th>
<th></th>
</tr>
</tfoot>
@@ -192,7 +194,6 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
<script src="<?=pihole.fileversion('scripts/vendor/bootstrap-select.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/pi-hole/js/ip-address-sorting.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/daterangepicker.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/pi-hole/js/utils.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/pi-hole/js/queries.js')?>"></script>
<? mg.include('scripts/pi-hole/lua/footer.lp','r')?>
+4 -5
View File
@@ -87,7 +87,6 @@ const htmlLegendPlugin = {
});
}
textLink.style.color = item.fontColor;
textLink.style.margin = 0;
textLink.style.padding = 0;
textLink.style.textDecoration = item.hidden ? "line-through" : "";
@@ -103,11 +102,11 @@ const htmlLegendPlugin = {
// eslint-disable-next-line no-unused-vars
var customTooltips = function (context) {
var tooltip = context.tooltip;
var tooltipEl = document.getElementById(this._chart.canvas.id + "-customTooltip");
var tooltipEl = document.getElementById(this.chart.canvas.id + "-customTooltip");
if (!tooltipEl) {
// Create Tooltip Element once per chart
tooltipEl = document.createElement("div");
tooltipEl.id = this._chart.canvas.id + "-customTooltip";
tooltipEl.id = this.chart.canvas.id + "-customTooltip";
tooltipEl.classList.add("chartjs-tooltip");
tooltipEl.innerHTML = "<div class='arrow'></div> <table></table>";
// avoid browser's font-zoom since we know that <body>'s
@@ -121,7 +120,7 @@ var customTooltips = function (context) {
tooltipEl.style.fontSize = tooltip.options.bodyFont.size / fontZoom + "px";
tooltipEl.style.fontStyle = tooltip.options.bodyFont.style;
// append Tooltip next to canvas-containing box
tooltipEl.ancestor = this._chart.canvas.closest(".box[id]").parentNode;
tooltipEl.ancestor = this.chart.canvas.closest(".box[id]").parentNode;
tooltipEl.ancestor.append(tooltipEl);
}
@@ -175,7 +174,7 @@ var customTooltips = function (context) {
tableRoot.innerHTML = innerHtml;
}
var canvasPos = this._chart.canvas.getBoundingClientRect();
var canvasPos = this.chart.canvas.getBoundingClientRect();
var boxPos = tooltipEl.ancestor.getBoundingClientRect();
var offsetX = canvasPos.left - boxPos.left;
var offsetY = canvasPos.top - boxPos.top;
+16 -9
View File
@@ -501,8 +501,10 @@ function updateVersionInfo() {
versions.forEach(function (v) {
if (v.local !== null) {
// reset update status for each component
var updateComponentAvailable = false;
var localVersion = v.local;
if (v.branch !== null && v.hash !== null)
if (v.branch !== null && v.hash !== null) {
if (v.branch === "master") {
localVersion = v.local.split("-")[0];
localVersion =
@@ -515,32 +517,37 @@ function updateVersionInfo() {
"</a>";
if (versionCompare(v.local, v.remote) === -1) {
// Update available
updateAvailable = true;
updateComponentAvailable = true;
}
} else {
// non-master branch
localVersion = "vDev (" + v.branch + ", " + v.hash + ")";
if (v.hash !== v.hash_remote) {
if (v.hash_remote && v.hash !== v.hash_remote) {
// hash differ > Update available
updateAvailable = true;
updateComponentAvailable = true;
// link to the commit history instead of release page
v.url = v.url.replace("releases", "commits/" + v.branch);
}
}
}
if (v.name === "Docker Tag" && versionCompare(v.local, v.remote) === -1) {
updateAvailable = true;
updateComponentAvailable = true;
dockerUpdate = true;
}
if (updateAvailable) {
if (updateComponentAvailable) {
$("#versions").append(
"<li><strong>" +
v.name +
"</strong> " +
localVersion +
'&middot; <a class="lookatme" lookatme-text="Update available!" href="' +
'&nbsp&middot; <a class="lookatme" lookatme-text="Update available!" href="' +
v.url +
'" rel="noopener" target="_blank">Update available!</a></li>'
);
// if at least one component can be updated, display the update-hint footer
updateAvailable = true;
} else {
$("#versions").append("<li><strong>" + v.name + "</strong> " + localVersion + "</li>");
}
@@ -548,11 +555,11 @@ function updateVersionInfo() {
});
if (dockerUpdate)
$("update-hint").html(
$("#update-hint").html(
'To install updates, <a href="https://github.com/pi-hole/docker-pi-hole#upgrading-persistence-and-customizations" rel="noopener" target="_blank">replace this old container with a fresh upgraded image</a>.'
);
else if (updateAvailable)
$("update-hint").html(
$("#update-hint").html(
'To install updates, run <code><a href="https://docs.pi-hole.net/main/update/" rel="noopener" target="_blank">pihole -up</a></code>.'
);
+1 -2
View File
@@ -38,7 +38,6 @@ function eventsource() {
return reader.read().then(({ done, value }) => {
// When no more data needs to be consumed, close the stream
if (done) {
ta.append("Done.");
controller.close();
alInfo.hide();
$("#gravityBtn").prop("disabled", false);
@@ -50,7 +49,7 @@ function eventsource() {
var string = new TextDecoder().decode(value);
parseLines(ta, string);
if (string.indexOf("Pi-hole blocking is") !== -1) {
if (string.indexOf("Done.") !== -1) {
alSuccess.show();
}
+17 -9
View File
@@ -85,7 +85,7 @@ function format(data) {
: "N/A") +
(data.abp_entries !== null && parseInt(data.abp_entries, 10) > 0 && numbers === true
? " (out of which " + parseInt(data.abp_entries, 10).toLocaleString() + " are in ABP-style)"
: "N/A") +
: "") +
'</td></tr><tr class="dataTables-child"' +
"><td>Number of non-domains on this list:&nbsp;&nbsp;</td>" +
"<td>" +
@@ -359,7 +359,7 @@ function initTable() {
var ids = [];
$("tr.selected").each(function () {
// ... add the row identified by "data-id".
ids.push($(this).attr("data-id"), 10);
ids.push($(this).attr("data-id"));
});
// Delete all selected rows at once
delItems(ids);
@@ -499,13 +499,21 @@ function delItems(ids) {
function addAdlist(event) {
const type = event.data.type;
const address = utils.escapeHtml($("#new_address").val());
const comment = utils.escapeHtml($("#new_comment").val());
utils.disableAll();
utils.showAlert("info", "", "Adding subscribed " + type + "list...", address);
// Check if the user wants to add multiple domains (space or newline separated)
// If so, split the input and store it in an array
var addresses = utils.escapeHtml($("#new_address").val()).split(/[\s,]+/);
// Remove empty elements
addresses = addresses.filter(function (el) {
return el !== "";
});
const addressestr = JSON.stringify(addresses);
if (address.length === 0) {
utils.disableAll();
utils.showAlert("info", "", "Adding subscribed " + type + "list(s)...", addressestr);
if (addresses.length === 0) {
// enable the ui elements again
utils.enableAll();
utils.showAlert("warning", "", "Warning", "Please specify " + type + "list address");
@@ -517,10 +525,10 @@ function addAdlist(event) {
method: "post",
dataType: "json",
processData: false,
data: JSON.stringify({ address: address, comment: comment, type: type }),
success: function () {
data: JSON.stringify({ address: addresses, comment: comment, type: type }),
success: function (data) {
utils.enableAll();
utils.showAlert("success", "fas fa-plus", "Successfully added " + type + "list", address);
utils.listsAlert("list", addresses, data);
table.ajax.reload(null, false);
table.rows().deselect();
+35 -22
View File
@@ -405,34 +405,47 @@ function delItems(ids) {
}
function addClient() {
var ip = utils.escapeHtml($("#select").val().trim());
const comment = utils.escapeHtml($("#new_comment").val());
utils.disableAll();
utils.showAlert("info", "", "Adding client...", ip);
if (ip.length === 0) {
utils.enableAll();
utils.showAlert("warning", "", "Warning", "Please specify a client IP or MAC address");
return;
}
// Check if the user wants to add multiple IPs (space or newline separated)
// If so, split the input and store it in an array
var ips = utils.escapeHtml($("#select").val().trim()).split(/[\s,]+/);
// Remove empty elements
ips = ips.filter(function (el) {
return el !== "";
});
const ipStr = JSON.stringify(ips);
// Validate input, can be:
// - IPv4 address (with and without CIDR)
// - IPv6 address (with and without CIDR)
// - MAC address (in the form AA:BB:CC:DD:EE:FF)
// - host name (arbitrary form, we're only checking against some reserved characters)
if (utils.validateIPv4CIDR(ip) || utils.validateIPv6CIDR(ip) || utils.validateMAC(ip)) {
// Convert input to upper case (important for MAC addresses)
ip = ip.toUpperCase();
} else if (!utils.validateHostname(ip)) {
for (var i = 0; i < ips.length; i++) {
if (
utils.validateIPv4CIDR(ips[i]) ||
utils.validateIPv6CIDR(ips[i]) ||
utils.validateMAC(ips[i])
) {
// Convert input to upper case (important for MAC addresses)
ips[i] = ips[i].toUpperCase();
} else if (!utils.validateHostname(ips[i])) {
utils.showAlert(
"warning",
"",
"Warning",
"Input is neither a valid IP or MAC address nor a valid host name!"
);
return;
}
}
utils.disableAll();
utils.showAlert("info", "", "Adding client(s)...", ipStr);
if (ips.length === 0) {
utils.enableAll();
utils.showAlert(
"warning",
"",
"Warning",
"Input is neither a valid IP or MAC address nor a valid host name!"
);
utils.showAlert("warning", "", "Warning", "Please specify a client IP or MAC address");
return;
}
@@ -441,10 +454,10 @@ function addClient() {
method: "post",
dataType: "json",
processData: false,
data: JSON.stringify({ client: ip, comment: comment }),
success: function () {
data: JSON.stringify({ client: ips, comment: comment }),
success: function (data) {
utils.enableAll();
utils.showAlert("success", "fas fa-plus", "Successfully added client", ip);
utils.listsAlert("client", ips, data);
reloadClientSuggestions();
table.ajax.reload(null, false);
table.rows().deselect();
+25 -17
View File
@@ -497,46 +497,54 @@ function addDomain() {
commentEl = $("#new_regex_comment");
}
var domain = utils.escapeHtml(domainEl.val());
const comment = utils.escapeHtml(commentEl.val());
utils.disableAll();
utils.showAlert("info", "", "Adding domain...", domain);
// Check if the user wants to add multiple domains (space or newline separated)
// If so, split the input and store it in an array
var domains = utils.escapeHtml(domainEl.val()).split(/[\s,]+/);
// Remove empty elements
domains = domains.filter(function (el) {
return el !== "";
});
const domainStr = JSON.stringify(domains);
if (domain.length < 2) {
utils.disableAll();
utils.showAlert("info", "", "Adding domain(s)...", domainStr);
if (domains.length === 0) {
utils.enableAll();
utils.showAlert("warning", "", "Warning", "Please specify a domain");
utils.showAlert("warning", "", "Warning", "Please specify at least one domain");
return;
}
// strip "*." if specified by user in wildcard mode
if (kind === "exact" && wildcardChecked && domain.startsWith("*.")) {
domain = domain.substr(2);
for (var i = 0; i < domains.length; i++) {
if (kind === "exact" && wildcardChecked) {
// Transform domain to wildcard if specified by user
domains[i] = "(\\.|^)" + domains[i].replaceAll(".", "\\.") + "$";
kind = "regex";
// strip leading "*." if specified by user in wildcard mode
if (domains[i].startsWith("*.")) domains[i] = domains[i].substr(2);
}
}
// determine list type
const type = action === "add_deny" ? "deny" : "allow";
// Transform domain to wildcard if specified by user
if (kind === "exact" && wildcardChecked) {
domain = "(\\.|^)" + domain.replaceAll(".", "\\.") + "$";
kind = "regex";
}
$.ajax({
url: "/api/domains/" + type + "/" + kind,
method: "post",
dataType: "json",
processData: false,
data: JSON.stringify({
domain: domain,
domain: domains,
comment: comment,
type: type,
kind: kind,
}),
success: function () {
success: function (data) {
utils.enableAll();
utils.showAlert("success", "fas fa-plus", "Successfully added domain", domain);
utils.listsAlert("domain", domains, data);
table.ajax.reload(null, false);
table.rows().deselect();
+18 -7
View File
@@ -277,13 +277,24 @@ function delItems(ids) {
}
function addGroup() {
const name = utils.escapeHtml($("#new_name").val());
const comment = utils.escapeHtml($("#new_comment").val());
utils.disableAll();
utils.showAlert("info", "", "Adding group...", name);
// Check if the user wants to add multiple groups (space or newline separated)
// If so, split the input and store it in an array
var names = utils
.escapeHtml($("#new_name"))
.val()
.split(/[\s,]+/);
// Remove empty elements
names = names.filter(function (el) {
return el !== "";
});
const groupStr = JSON.stringify(names);
if (name.length === 0) {
utils.disableAll();
utils.showAlert("info", "", "Adding group(s)...", groupStr);
if (names.length === 0) {
// enable the ui elements again
utils.enableAll();
utils.showAlert("warning", "", "Warning", "Please specify a group name");
@@ -296,13 +307,13 @@ function addGroup() {
dataType: "json",
processData: false,
data: JSON.stringify({
name: name,
name: names,
comment: comment,
enabled: true,
}),
success: function () {
success: function (data) {
utils.enableAll();
utils.showAlert("success", "fas fa-plus", "Successfully added group", name);
utils.listsAlert("group", names, data);
$("#new_name").val("");
$("#new_comment").val("");
table.ajax.reload();
+19 -11
View File
@@ -16,6 +16,13 @@ var queryTypePieChart, forwardDestinationPieChart;
var failures = 0;
function updateQueriesOverTime() {
$.getJSON("/api/history", function (data) {
// Remove graph if there are no results (e.g. new
// installation or privacy mode enabled)
if (jQuery.isEmptyObject(data.history)) {
$("#queries-over-time").remove();
return;
}
// Remove possibly already existing data
timeLineChart.data.labels = [];
timeLineChart.data.datasets = [];
@@ -123,13 +130,10 @@ function updateClientsOverTime() {
// Remove graph if there are no results (e.g. new
// installation or privacy mode enabled)
if (jQuery.isEmptyObject(data.history)) {
$("#clients-over-time").parent().remove();
$("#clients").remove();
return;
}
// remove last data point for line charts as it is not representative there
if (utils.getGraphType() === "line") data.history.splice(-1, 1);
var i,
labels = [];
data.clients.forEach(function (client) {
@@ -384,7 +388,11 @@ function updateSummaryData(runOnce) {
$.getJSON("/api/stats/summary", function (data) {
var intl = new Intl.NumberFormat();
glowIfChanged($("span#dns_queries"), intl.format(parseInt(data.queries.total, 10)));
glowIfChanged($("span#total_clients"), intl.format(parseInt(data.clients.total, 10)));
glowIfChanged($("span#active_clients"), intl.format(parseInt(data.clients.active, 10)));
$("a#total_clients").attr(
"title",
intl.format(parseInt(data.clients.total, 10)) + " total clients"
);
glowIfChanged($("span#blocked_queries"), intl.format(parseFloat(data.queries.blocked)));
glowIfChanged(
$("span#percent_blocked"),
@@ -416,7 +424,7 @@ $(function () {
var ticksColor = utils.getCSSval("graphs-ticks", "color");
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
timeLineChart = new Chart(ctx, {
type: utils.getGraphType(),
type: "bar",
data: {
labels: [],
datasets: [{ data: [], parsing: false }],
@@ -471,7 +479,7 @@ $(function () {
},
},
scales: {
xAxes: {
x: {
type: "time",
stacked: true,
offset: false,
@@ -491,7 +499,7 @@ $(function () {
color: ticksColor,
},
},
yAxes: {
y: {
stacked: true,
beginAtZero: true,
ticks: {
@@ -527,7 +535,7 @@ $(function () {
if (clientsChartEl) {
ctx = clientsChartEl.getContext("2d");
clientsChart = new Chart(ctx, {
type: utils.getGraphType(),
type: "bar",
data: {
labels: [],
datasets: [{ data: [], parsing: false }],
@@ -566,7 +574,7 @@ $(function () {
},
},
scales: {
xAxes: {
x: {
type: "time",
stacked: true,
offset: false,
@@ -586,7 +594,7 @@ $(function () {
color: ticksColor,
},
},
yAxes: {
y: {
beginAtZero: true,
ticks: {
color: ticksColor,
+25 -17
View File
@@ -208,6 +208,23 @@ function parseQueryStatus(data) {
};
}
function formatReplyTime(replyTime, type) {
if (type === "display") {
// Units:
// - seconds if replytime >= 1 second
// - milliseconds if reply time >= 100 µs
// - microseconds otherwise
return replyTime < 1e-4
? (1e6 * replyTime).toFixed(1) + " µs"
: replyTime < 1
? (1e3 * replyTime).toFixed(1) + " ms"
: replyTime.toFixed(1) + " s";
}
// else: return the number itself (for sorting and searching)
return replyTime;
}
function formatInfo(data) {
// DNSSEC status
var dnssecStatus = data.dnssec,
@@ -303,20 +320,10 @@ function formatInfo(data) {
// Always show reply info, add reply delay if applicable
var replyInfo = "";
if (data.reply.type !== "UNKNOWN") {
replyInfo = divStart + "Reply:&nbsp&nbsp;" + data.reply.type;
if (data.reply.time >= 0 && data.reply.type !== "UNKNOWN") {
replyInfo +=
" (" +
(data.reply.time < 1
? (1e3 * data.reply.time).toFixed(1) + " ms)"
: data.reply.time.toFixed(1) + " s)");
}
replyInfo += "</div>";
} else {
replyInfo = divStart + "Reply:&nbsp;&nbsp;No reply received</div>";
}
replyInfo =
data.reply.type !== "UNKNOWN"
? divStart + "Reply:&nbsp&nbsp;" + data.reply.type + "</div>"
: divStart + "Reply:&nbsp;&nbsp;No reply received</div>";
// Compile extra info for displaying
return (
@@ -515,9 +522,10 @@ $(function () {
},
},
{ data: "status", width: "1%" },
{ data: "type", width: "5%" },
{ data: "type", width: "1%" },
{ data: "domain", width: "45%" },
{ data: "client.ip", width: "29%", type: "ip-address", render: $.fn.dataTable.render.text() },
{ data: "client.ip", width: "29%", type: "ip-address" },
{ data: "reply.time", width: "4%", render: formatReplyTime },
{ data: null, width: "10%", sortable: false, searchable: false },
],
lengthMenu: [
@@ -569,7 +577,7 @@ $(function () {
}
if (querystatus.buttontext !== false) {
$("td:eq(5)", row).html(querystatus.buttontext);
$("td:eq(6)", row).html(querystatus.buttontext);
}
},
});
+51 -10
View File
@@ -48,16 +48,19 @@ function eventsource(partial) {
(numDomains > 0 ? ":" : ".") +
"<br><br>";
for (const domain of res.domains) {
const color = domain.type === "deny" ? "red" : "green";
result +=
" - <a href='groups-domains.lp?domainid=" +
domain.id +
"' target='_blank'><strong class='text-green'>" +
"' target='_blank'><strong>" +
utils.escapeHtml(domain.domain) +
"</strong></a><br> " +
"</strong></a><br> <strong class='text-" +
color +
"'>" +
domain.kind +
" " +
domain.type +
" domain<br> added: " +
" domain</strong><br> added: " +
utils.renderTimestamp(domain.date_added, "display") +
"<br> last modified: " +
utils.renderTimestamp(domain.date_modified, "display") +
@@ -88,7 +91,7 @@ function eventsource(partial) {
result +=
"Found " +
numAdlists +
" adlist" +
" list" +
(numAdlists !== 1 ? "s" : "") +
" <em>" +
verb +
@@ -99,12 +102,18 @@ function eventsource(partial) {
"<br><br>";
for (const address of Object.keys(grouped)) {
const adlist = grouped[address][0];
const color = adlist.type === "block" ? "red" : "green";
result +=
" - <a href='groups-adlists.lp?adlistid=" +
adlist.id +
"' target='_blank'>" +
utils.escapeHtml(address) +
"</a><br> added: " +
"</a><br> <strong class='text-" +
color +
"'>" +
adlist.type +
" list</strong>" +
"<br> added: " +
utils.renderTimestamp(adlist.date_added, "display") +
"<br> last modified: " +
utils.renderTimestamp(adlist.date_modified, "display") +
@@ -122,18 +131,50 @@ function eventsource(partial) {
(adlist.comment !== null && adlist.comment.length > 0
? '<br> comment: "' + utils.escapeHtml(adlist.comment) + '"'
: "<br> no comment") +
"<br>";
"<br> matching entries:<br>";
for (const adlists of grouped[address]) {
result +=
" - <strong class='text-green'>" +
utils.escapeHtml(adlists.domain) +
"</strong><br>";
" - <strong class='text-blue'>" + utils.escapeHtml(adlists.domain) + "</strong><br>";
}
result += "<br>";
}
result += "Search took " + (1000 * data.took).toFixed(1) + " ms";
result += "Number of results per type:<br>";
result +=
" - <strong class='text-blue'>" +
data.search.results.domains.exact +
"</strong> exact domain matches<br>";
result +=
" - <strong class='text-blue'>" +
data.search.results.domains.regex +
"</strong> regex domain matches<br>";
result +=
" - <strong class='text-blue'>" +
data.search.results.gravity.allow +
"</strong> allowlist (antigravity) matches<br>";
result +=
" - <strong class='text-blue'>" +
data.search.results.gravity.block +
"</strong> blocklist (gravity) matches<br>";
if (
data.search.results.gravity.allow > data.search.parameters.N ||
data.search.results.gravity.block > data.search.parameters.N ||
data.search.results.domains.exact > data.search.parameters.N ||
data.search.results.domains.regex > data.search.parameters.N
) {
result +=
"<br><br><strong class='text-green'>Note:</strong> " +
"The number of results to return per type is limited to " +
data.search.parameters.N +
" entries.<br> There are " +
data.search.results.total +
" matching entries in total.<br> Consider " +
"using a more specific search term or increase N.<br>";
}
result += "<br>Search took " + (1000 * data.took).toFixed(1) + " ms";
ta.append(result);
})
+39 -10
View File
@@ -41,11 +41,17 @@ function generateRow(topic, key, value) {
'<h3 class="box-title">' +
key +
(value.modified
? '&nbsp;&nbsp;<i class="far fa-edit text-light-blue" title="Modified"></i>'
? '&nbsp;&nbsp;<i class="far fa-edit text-light-blue" title="Modified from default"></i>'
: "") +
(value.flags.advanced
? '&nbsp;&nbsp;<i class="fas fa-cogs text-yellow" title="Expert-level setting"></i>'
: "") +
(value.flags.restart_dnsmasq
? '&nbsp;&nbsp;<i class="fas fa-redo text-orange" title="Setting requires FTL restart on change"></i>'
: "") +
(value.flags.env_var
? '&nbsp;&nbsp;<i class="fas fa-lock text-orange" title="Settings overwritten by an environmental variable are read-only"></i>'
: "") +
"</h3>" +
"<p>" +
utils.escapeHtml(value.description).replace("\n", "<br>") +
@@ -84,6 +90,11 @@ function generateRow(topic, key, value) {
}
}
let extraAttributes = "";
if (value.flags.env_var) {
extraAttributes = " disabled";
}
switch (value.type) {
case "IPv4 address":
case "IPv6 address":
@@ -95,7 +106,9 @@ function generateRow(topic, key, value) {
value.value +
'" data-key="' +
key +
'"> ' +
'"' +
extraAttributes +
"> " +
defaultValueHint +
addAllowedValues(value.allowed) +
"</div>";
@@ -111,7 +124,9 @@ function generateRow(topic, key, value) {
key +
'-checkbox" data-key="' +
key +
'"><label for="' +
'"' +
extraAttributes +
'><label for="' +
key +
'-checkbox">Enabled ' +
defaultValueHint +
@@ -129,7 +144,9 @@ function generateRow(topic, key, value) {
value.value +
'" data-key="' +
key +
'" data-type="float"> ' +
'" data-type="float"' +
extraAttributes +
"> " +
defaultValueHint +
"</div>";
@@ -144,7 +161,9 @@ function generateRow(topic, key, value) {
value.value +
'" data-key="' +
key +
'" data-type="integer"> ' +
'" data-type="integer"' +
extraAttributes +
"> " +
defaultValueHint +
"</div>";
@@ -159,7 +178,9 @@ function generateRow(topic, key, value) {
value.value +
'" data-key="' +
key +
'" data-type="integer"> ' +
'" data-type="integer"' +
extraAttributes +
"> " +
defaultValueHint +
"</div>";
@@ -174,7 +195,9 @@ function generateRow(topic, key, value) {
value.value +
'" data-key="' +
key +
'" data-type="integer"> ' +
'" data-type="integer"' +
extraAttributes +
"> " +
defaultValueHint +
"</div>";
@@ -187,7 +210,9 @@ function generateRow(topic, key, value) {
'<div class="col-sm-7">' +
'<textarea class="form-control" data-key="' +
key +
'">' +
'"' +
extraAttributes +
">" +
value.value.join("\n") +
"</textarea> " +
defaultValueHint +
@@ -204,7 +229,9 @@ function generateRow(topic, key, value) {
'<div class="col-sm-8">' +
'<select class="form-control" data-key="' +
key +
'">';
'"' +
extraAttributes +
">";
value.allowed.forEach(function (option) {
box +=
'<option value="' +
@@ -234,7 +261,9 @@ function generateRow(topic, key, value) {
value.value +
'" data-key="' +
key +
'"> ' +
'"' +
extraAttributes +
"> " +
defaultValueHint +
addAllowedValues(value.allowed) +
"</div>";
+99 -21
View File
@@ -9,6 +9,7 @@
var apiSessionsTable = null;
var ownSessionID = null;
var deleted = 0;
var TOTPdata = null;
function renderBool(data, type) {
@@ -39,6 +40,7 @@ $(function () {
{ data: "id" },
{ data: "valid", render: renderBool },
{ data: null },
{ data: "app", render: renderBool },
{ data: "login_at", render: utils.renderTimestamp },
{ data: "valid_until", render: utils.renderTimestamp },
{ data: "remote_addr", type: "ip-address" },
@@ -60,7 +62,7 @@ $(function () {
},
],
drawCallback: function () {
$('button[id^="deleteSession_"]').on("click", deleteSession);
$('button[id^="deleteSession_"]').on("click", deleteThisSession);
// Hide buttons if all messages were deleted
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
@@ -70,7 +72,7 @@ $(function () {
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
$(row).attr("data-id", data.ip);
$(row).attr("data-id", data.id);
var button =
'<button type="button" class="btn btn-danger btn-xs" id="deleteSession_' +
data.id +
@@ -83,10 +85,10 @@ $(function () {
'">' +
'<span class="far fa-trash-alt"></span>' +
"</button>";
$("td:eq(8)", row).html(button);
$("td:eq(9)", row).html(button);
if (data.current_session) {
ownSessionID = data.id;
$("td:eq(6)", row).html(
$("td:eq(7)", row).html(
'<strong title="This is the session you are currently using for the web interface">' +
data.remote_addr +
"</strong>"
@@ -148,7 +150,7 @@ $(function () {
ids.push(parseInt($(this).attr("data-id"), 10));
});
// Delete all selected rows at once
delSession(ids);
deleteMultipleSessions(ids);
},
},
],
@@ -186,13 +188,18 @@ $(function () {
});
});
function deleteSession() {
// Passes the button data-del-id attribute as ID
var ids = [$(this).attr("data-del-id")];
delSessions(ids);
function deleteThisSession() {
// This function is called when a red trash button is clicked
// We get the ID of the current item from the data-del-id attribute
const thisID = parseInt(this.attr("data-del-id"), 10);
deleted = 0;
deleteOneSession(thisID, 1, false);
}
function delSessions(ids) {
function deleteMultipleSessions(ids) {
// This function is called when multiple sessions are selected and the gray
// trash button is clicked
// Check input validity
if (!Array.isArray(ids)) return;
@@ -206,9 +213,12 @@ function delSessions(ids) {
return parseInt(value, 10);
});
// Check if own session is selected
// Check if own session is selected and remove it when deleting multiple
// We need this only when multiple sessions are removed to ensure we do not
// accidentally remove our own session and thus log us out *before* we can
// remove the other sessions
let ownSessionDelete = false;
if (ids.includes(ownSessionID)) {
if (ids.includes(ownSessionID) && ids.length > 1) {
ownSessionDelete = true;
// Strip own session ID from array
ids = ids.filter(function (value) {
@@ -217,24 +227,29 @@ function delSessions(ids) {
}
// Loop through IDs and delete them
deleted = 0;
for (const id of ids) {
if (Object.hasOwnProperty.call(ids, id)) {
delSession(ids[id]);
}
}
// Delete own session last (if selected)
if (ownSessionDelete) {
delSession(ownSessionID);
deleteOneSession(id, ids.length, ownSessionDelete);
}
}
function delSession(id) {
function deleteOneSession(id, len, ownSessionDelete) {
// This function is called to delete a single session
// If we are batch deleting, we ensure that we do not delete our own session
// before having successfully deleted all other sessions, the deletion of
// our own session is then triggered by the last successful deletion of
// another session (ownSessionDelete == true, len == global deleted)
$.ajax({
url: "/api/auth/session/" + id,
method: "DELETE",
})
.done(function () {
// Do not reload page when deleting multiple sessions
if (++deleted < len) return;
// All other sessions have been deleted, now delete own session
if (ownSessionDelete) deleteOneSession(ownSessionID, 1, false);
if (id !== ownSessionID) {
// Reload table to remove session
apiSessionsTable.ajax.reload();
@@ -297,6 +312,69 @@ $("#modal-totp").on("shown.bs.modal", function () {
});
});
var apppwhash = null;
$("#modal-apppw").on("shown.bs.modal", function () {
$.ajax({
url: "/api/auth/app",
})
.done(function (data) {
apppwhash = data.app.hash;
$("#password_code").text(data.app.password);
$("#password_display").removeClass("hidden");
$("#password-spinner").hide();
})
.fail(function (data) {
apiFailure(data);
});
});
$("#apppw_submit").on("click", function () {
// Enable app password
setAppPassword();
});
$("#apppw_clear").on("click", function () {
// Disable app password
apppwhash = "";
setAppPassword();
});
function setAppPassword() {
$.ajax({
url: "/api/config",
type: "PATCH",
dataType: "json",
processData: false,
data: JSON.stringify({ config: { webserver: { api: { app_pwhash: apppwhash } } } }),
contentType: "application/json",
})
.done(function () {
$("#modal-apppw").modal("hide");
const verb = apppwhash.length > 0 ? "enabled" : "disabled";
const verb2 = apppwhash.length > 0 ? "will" : "may";
alert(
"App password has been " +
verb +
", you " +
verb2 +
" need to re-login to continue using the web interface."
);
location.reload();
})
.fail(function (data) {
apiFailure(data);
});
}
// Remove class "password_background" from the password input field with ID
// password_code when the user hovers over it or if it is focused
$("#password_code").on("mouseover focus", function () {
$(this).removeClass("password_background");
});
$("#password_code").on("mouseout blur", function () {
$(this).addClass("password_background");
});
// Trigger keyup event when pasting into the TOTP code input field
$("#totp_code").on("paste", function (e) {
$(e.target).keyup();
+135 -151
View File
@@ -7,161 +7,141 @@
/* global utils: false, apiFailure:false */
var dnsRecordsTable = null;
var customCNAMETable = null;
function hostsDomain(data) {
// Split record in format IP NAME1 [NAME2 [NAME3 [NAME...]]]
const name = data.substring(data.indexOf(" ") + 1);
return name;
}
function hostsIP(data) {
// Split record in format IP NAME1 [NAME2 [NAME3 [NAME...]]]
const ip = data.substring(0, data.indexOf(" "));
return ip;
}
function CNAMEdomain(data) {
// Split record in format <cname>,<target>[,<TTL>]
const CNAMEarr = data.split(",");
return CNAMEarr[0];
}
function CNAMEtarget(data) {
// Split record in format <cname>,<target>[,<TTL>]
const CNAMEarr = data.split(",");
return CNAMEarr[1];
}
function CNAMEttl(data) {
// Split record in format <cname>,<target>[,<TTL>]
const CNAMEarr = data.split(",");
return CNAMEarr.length > 2 ? CNAMEarr[2] : "-";
}
function populateDataTable(endpoint) {
var columns = "";
if (endpoint === "hosts") {
columns = [
{ data: null, render: hostsDomain },
{ data: null, type: "ip-address", render: hostsIP },
{ data: null, width: "22px", orderable: false },
];
} else {
columns = [
{ data: null, render: CNAMEdomain },
{ data: null, render: CNAMEtarget },
{ data: null, render: CNAMEttl },
{ data: null, width: "22px", orderable: false },
];
}
var setByEnv = false;
$.ajax({
url: `/api/config/dns/${endpoint}?detailed=true`,
}).done(function (data) {
setByEnv = data.config.dns[endpoint].flags.env_var;
if (setByEnv) {
$(`#title-${endpoint}`).append(
`<span class="env-warning">&nbsp;&nbsp;<i class="fas fa-lock text-orange env-warning" title="Settings overwritten by an environmental variable are read-only"></i></span>`
);
$(`.${endpoint}`).prop("disabled", true);
}
});
$(`#${endpoint}-Table`).DataTable({
ajax: {
url: `/api/config/dns/${endpoint}`,
type: "GET",
dataSrc: `config.dns.${endpoint}`,
},
columns: columns,
columnDefs: [
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
drawCallback: function () {
$(`button[id^="delete${endpoint}"]`).on("click", deleteRecord);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
$(row).attr("data-id", data);
var button = `<button type="button"
class="btn btn-danger btn-xs"
id="delete${endpoint}${utils.hexEncode(data)}"
data-tag="${data}"
data-type="${endpoint}"
${setByEnv ? "disabled" : ""}>
<span class="far fa-trash-alt"></span>
</button>`;
$(`td:eq(${endpoint === "hosts" ? 2 : 3})`, row).html(button);
},
dom:
"<'row'<'col-sm-6'l><'col-sm-6'f>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
"<'row'<'col-sm-12'i>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"],
],
language: {
emptyTable: function () {
return endpoint === "hosts"
? "No local DNS records defined."
: "No local CNAME records defined.";
},
},
stateSave: true,
stateDuration: 0,
processing: true,
stateSaveCallback: function (settings, data) {
utils.stateSaveCallback(`${endpoint}-records-table`, data);
},
stateLoadCallback: function () {
var data = utils.stateLoadCallback(`${endpoint}-records-table`);
// Return if not available
if (data === null) {
return null;
}
// Apply loaded state to table
return data;
},
});
}
$(function () {
dnsRecordsTable = $("#dnsRecordsTable").DataTable({
ajax: {
url: "/api/config/dns/hosts",
type: "GET",
dataSrc: "config.dns.hosts",
},
order: [[0, "asc"]],
columns: [
{ data: null },
{ data: null, type: "ip-address" },
{ data: null, width: "22px", orderable: false },
],
columnDefs: [
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
drawCallback: function () {
$('button[id^="deleteRecord"]').on("click", deleteRecord);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
// Split record in format IP NAME1 [NAME2 [NAME3 [NAME...]]]
var ip = data.substring(0, data.indexOf(" "));
// The name can be multiple domains separated by spaces
var name = data.substring(data.indexOf(" ") + 1);
$(row).attr("data-id", data);
var button =
'<button type="button" class="btn btn-danger btn-xs" id="deleteRecord' +
utils.hexEncode(data) +
'" data-tag="' +
data +
'" data-type="hosts">' +
'<span class="far fa-trash-alt"></span>' +
"</button>";
$("td:eq(2)", row).html(button);
$("td:eq(0)", row).text(name);
$("td:eq(1)", row).text(ip);
},
dom:
"<'row'<'col-sm-6'l><'col-sm-6'f>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
"<'row'<'col-sm-12'i>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"],
],
language: {
emptyTable: "No local DNS records defined.",
},
stateSave: true,
stateDuration: 0,
processing: true,
stateSaveCallback: function (settings, data) {
utils.stateSaveCallback("dns-records-table", data);
},
stateLoadCallback: function () {
var data = utils.stateLoadCallback("dns-records-table");
// Return if not available
if (data === null) {
return null;
}
// Apply loaded state to table
return data;
},
});
dnsRecordsTable = $("#customCNAMETable").DataTable({
ajax: {
url: "/api/config/dns/cnameRecords",
type: "GET",
dataSrc: "config.dns.cnameRecords",
},
order: [[0, "asc"]],
columns: [
{ data: null },
{ data: null },
{ data: null },
{ data: null, width: "22px", orderable: false },
],
columnDefs: [
{
targets: "_all",
render: $.fn.dataTable.render.text(),
},
],
drawCallback: function () {
$('button[id^="deleteCNAME"]').on("click", deleteRecord);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
// Split record in format <cname>,<target>[,<TTL>]
var CNAMEarr = data.split(",");
$(row).attr("data-id", data);
var button =
'<button type="button" class="btn btn-danger btn-xs" id="deleteCNAME' +
utils.hexEncode(data) +
'" data-tag="' +
data +
'" data-type="cname">' +
'<span class="far fa-trash-alt"></span>' +
"</button>";
$("td:eq(3)", row).html(button);
$("td:eq(0)", row).text(CNAMEarr[0]);
$("td:eq(1)", row).text(CNAMEarr[1]);
if (CNAMEarr.length > 2) $("td:eq(2)", row).text(CNAMEarr[2]);
else $("td:eq(2)", row).text("-");
},
dom:
"<'row'<'col-sm-6'l><'col-sm-6'f>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
"<'row'<'col-sm-12'i>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"],
],
language: {
emptyTable: "No local CNAME records defined.",
},
stateSave: true,
stateDuration: 0,
processing: true,
stateSaveCallback: function (settings, data) {
utils.stateSaveCallback("dns-cname-records-table", data);
},
stateLoadCallback: function () {
var data = utils.stateLoadCallback("dns-cname-records-table");
// Return if not available
if (data === null) {
return null;
}
// Apply loaded state to table
return data;
},
});
populateDataTable("hosts");
populateDataTable("cnameRecords");
});
function deleteRecord() {
// Get the tags
var tags = [$(this).attr("data-tag")];
var types = [$(this).attr("data-type")];
// Check input validity
if (!Array.isArray(tags)) return;
@@ -186,7 +166,7 @@ function delHosts(elem) {
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted DNS record", "");
dnsRecordsTable.ajax.reload(null, false);
$("#hosts-Table").DataTable().ajax.reload(null, false);
})
.fail(function (data, exception) {
utils.enableAll();
@@ -213,7 +193,7 @@ function delCNAME(elem) {
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted local CNAME record", "");
customCNAMETable.ajax.reload(null, false);
$("#cnameRecords-Table").DataTable().ajax.reload(null, false);
})
.fail(function (data, exception) {
utils.enableAll();
@@ -230,8 +210,10 @@ function delCNAME(elem) {
$(document).ready(function () {
$("#btnAdd-host").on("click", function () {
utils.disableAll();
const elem = $("#Hip").val() + " " + $("#Hdomain").val();
const url = "/api/config/dns/hosts/" + encodeURIComponent(elem);
utils.showAlert("info", "", "Adding DNS record...", elem);
$.ajax({
url: url,
method: "PUT",
@@ -239,7 +221,7 @@ $(document).ready(function () {
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-plus", "Successfully added DNS record", "");
dnsRecordsTable.ajax.reload(null, false);
$("#hosts-Table").DataTable().ajax.reload(null, false);
})
.fail(function (data, exception) {
utils.enableAll();
@@ -250,10 +232,12 @@ $(document).ready(function () {
});
$("#btnAdd-cname").on("click", function () {
utils.disableAll();
var elem = $("#Cdomain").val() + "," + $("#Ctarget").val();
var ttlVal = parseInt($("#Cttl").val(), 10);
if (isFinite(ttlVal) && ttlVal >= 0) elem += "," + ttlVal;
const url = "/api/config/dns/cnameRecords/" + encodeURIComponent(elem);
utils.showAlert("info", "", "Adding DNS record...", elem);
$.ajax({
url: url,
method: "PUT",
@@ -261,7 +245,7 @@ $(document).ready(function () {
.done(function () {
utils.enableAll();
utils.showAlert("success", "far fa-plus", "Successfully added CNAME record", "");
dnsRecordsTable.ajax.reload(null, false);
$("#cnameRecords-Table").DataTable().ajax.reload(null, false);
})
.fail(function (data, exception) {
utils.enableAll();
+14 -8
View File
@@ -18,6 +18,12 @@ function removeFromArray(arr, what) {
}
function fillDNSupstreams(value, servers) {
var disabledStr = "";
if (value.flags.env_var === true) {
$("#DNSupstreamsTextfield").prop("disabled", true);
disabledStr = 'disabled="Disabled"';
}
var i = 0;
var customServers = value.value.length;
servers.forEach(element => {
@@ -30,21 +36,21 @@ function fillDNSupstreams(value, servers) {
// Loop over available addresses (up to 2)
for (let index = 0; index < 2; index++) {
if (address.length > index) {
row +=
'<td title="' +
address[index] +
'"><div><input type="checkbox" id="DNSupstreams-' +
i +
'"';
var checkedStr = "";
if (
value.value.includes(address[index]) ||
value.value.includes(address[index] + "#53")
) {
row += " checked";
checkedStr = "checked";
customServers--;
}
row += '><label for="DNSupstreams-' + i++ + '"></label></div></td>';
row += `<td title="${address[index]}">
<div>
<input type="checkbox" id="DNSupstreams-${i}" ${disabledStr} ${checkedStr}>
<label for="DNSupstreams-${i++}"></label>
</div>
</td>`;
} else {
row += "<td></td>";
}
+25
View File
@@ -33,6 +33,7 @@ function importZIP() {
fetch("/api/teleporter", {
method: "POST",
body: formData,
headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") },
})
.then(response => response.json())
.then(data => {
@@ -70,3 +71,27 @@ function importZIP() {
console.error(error); // eslint-disable-line no-console
});
}
// Inspired by https://stackoverflow.com/a/59576416/2087442
$("#GETTeleporter").on("click", function () {
$.ajax({
url: "/api/teleporter",
headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") },
method: "GET",
xhrFields: {
responseType: "blob",
},
success: function (data, status, xhr) {
var a = document.createElement("a");
// eslint-disable-next-line compat/compat
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = xhr.getResponseHeader("Content-Disposition").match(/filename="([^"]*)"/)[1];
document.body.append(a);
a.click();
a.remove();
// eslint-disable-next-line compat/compat
window.URL.revokeObjectURL(url);
},
});
});
+26 -1
View File
@@ -37,6 +37,31 @@ function setConfigValues(topic, key, value) {
// else: we have a setting we can set
var escapedKey = key.replaceAll(".", "\\.");
var envTitle = $(`.${escapedKey}`);
if (value.flags.advanced && envTitle.find(".advanced-warning").length === 0) {
envTitle.append(
`<span class="advanced-warning">&nbsp;&nbsp;<i class="fas fa-wrench" title="This is an advanced level setting"></i></span>`
);
}
if (value.flags.restart_dnsmasq && envTitle.find(".restart-warning").length === 0) {
envTitle.append(
`<span class="restart-warning">&nbsp;&nbsp;<i class="fas fa-redo text-orange" title="Setting requires FTL restart on change"></i></span>`
);
}
if (value.flags.env_var) {
// If this setting has been set by environment variable, display a padlock in the section title
if (envTitle.find(".env-warning").length === 0) {
envTitle.append(
`<span class="env-warning">&nbsp;&nbsp;<i class="fas fa-lock text-orange env-warning" title="Settings overwritten by an environmental variable are read-only"></i></span>`
);
}
$(`#${escapedKey}`).prop("disabled", "disabled");
}
switch (value.type) {
case "enum (unsigned integer)": // fallthrough
case "enum (string)": {
@@ -44,6 +69,7 @@ function setConfigValues(topic, key, value) {
$("#" + escapedKey + " option").remove();
// Add allowed select items (if available)
value.allowed.forEach(function (allowedValue) {
$("#" + escapedKey + "-" + allowedValue.item).prop("disabled", value.flags.env_var);
var newopt = $("<option></option>")
.attr("value", allowedValue.item)
.text(allowedValue.description);
@@ -70,7 +96,6 @@ function setConfigValues(topic, key, value) {
case "string array": {
// Set input field values from array (if available)
$("#" + escapedKey).val(value.value.join("\n"));
break;
}
+122 -50
View File
@@ -83,63 +83,65 @@ function padNumber(num) {
return ("00" + num).substr(-2, 2);
}
var info = null;
var showAlertBox = null;
function showAlert(type, icon, title, message) {
var opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
const options = {
title: "&nbsp;<strong>" + title + "</strong><br>",
message: message,
},
settings = {
type: type,
delay: 5000, // default value
mouse_over: "pause",
};
switch (type) {
case "info":
opts = {
type: "info",
icon: "far fa-clock",
title: title,
message: message,
};
info = $.notify(opts);
break;
case "success":
opts = {
type: "success",
icon: icon,
title: title,
message: message,
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
options.icon = icon !== null && icon.len > 0 ? icon : "far fa-clock";
break;
case "success":
break;
case "warning":
opts = {
type: "warning",
icon: "fas fa-exclamation-triangle",
title: title,
message: message,
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
options.icon = "fas fa-exclamation-triangle";
settings.delay *= 2;
break;
case "error":
opts = {
type: "danger",
icon: "fas fa-times",
title: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message,
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
options.icon = "fas fa-times";
options.title = "&nbsp;<strong>Error, something went wrong!</strong><br>";
settings.delay *= 2;
// If the message is an API object, nicely format the error message
// Try to parse message as JSON
try {
var data = JSON.parse(message);
console.log(data); // eslint-disable-line no-console
if (data.error !== undefined) {
options.title = "&nbsp;<strong>" + data.error.message + "</strong><br>";
if (data.error.hint !== null) options.message = data.error.hint;
}
} catch {
// Do nothing
}
break;
default:
// Case not handled, do nothing
console.log("Unknown alert type: " + type); // eslint-disable-line no-console
return;
}
if (type === "info") {
// Create a new notification for info boxes
showAlertBox = $.notify(options, settings);
} else if (showAlertBox !== null) {
// Update existing notification for other boxes (if available)
showAlertBox.update(options);
showAlertBox.update(settings);
} else {
// Create a new notification for other boxes if no previous info box exists
$.notify(options, settings);
}
}
@@ -283,11 +285,6 @@ function stateLoadCallback(itemName) {
return data;
}
function getGraphType() {
// Only return line if `barchart_chkbox` is explicitly set to false. Else return bar
return localStorage && localStorage.getItem("barchart_chkbox") === "false" ? "line" : "bar";
}
function addFromQueryLog(domain, list) {
var alertModal = $("#alertModal");
var alProcessing = alertModal.find(".alProcessing");
@@ -537,6 +534,81 @@ function hexDecode(string) {
return back;
}
function listAlert(type, items, data) {
// Show simple success message if there is no "processed" object in "data" or
// if all items were processed successfully
if (data.processed === undefined || data.processed.success.length === items.length) {
showAlert(
"success",
"fas fa-plus",
"Successfully added " + type + (items.length !== 1 ? "s" : ""),
items.join(", ")
);
return;
}
// Show a more detailed message if there is a "processed" object in "data" and
// not all items were processed successfully
let message = "";
// Show a list of successful items if there are any
if (data.processed.success.length > 0) {
message +=
"<strong>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>";
}
}
}
// 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>";
}
// Show a list of failed items if there are any
if (data.processed.errors.length > 0) {
message +=
"<strong>Failed to add " +
data.processed.errors.length +
" " +
type +
(data.processed.errors.length !== 1 ? "s" : "") +
":</strong>\n";
// Loop over data.processed.errors and print "item: error"
for (const item in data.processed.errors) {
if (Object.prototype.hasOwnProperty.call(data.processed.errors, item)) {
let error = data.processed.errors[item].error;
// Replace some error messages with a more user-friendly text
if (error.indexOf("UNIQUE constraint failed") > -1) {
error = "Already present";
}
message += "<br>- <strong>" + data.processed.errors[item].item + "</strong>: " + error;
}
}
}
// Show the warning message
const total = data.processed.success.length + data.processed.errors.length;
const processed = "(" + total + " " + type + (total !== 1 ? "s" : "") + " processed)";
showAlert(
"warning",
"fas fa-exclamation-triangle",
"Some " + type + (items.length !== 1 ? "s" : "") + " could not be added " + processed,
message
);
}
window.utils = (function () {
return {
escapeHtml: escapeHtml,
@@ -553,7 +625,6 @@ window.utils = (function () {
setBsSelectDefaults: setBsSelectDefaults,
stateSaveCallback: stateSaveCallback,
stateLoadCallback: stateLoadCallback,
getGraphType: getGraphType,
validateMAC: validateMAC,
validateHostname: validateHostname,
addFromQueryLog: addFromQueryLog,
@@ -570,5 +641,6 @@ window.utils = (function () {
parseQueryString: parseQueryString,
hexEncode: hexEncode,
hexDecode: hexDecode,
listsAlert: listAlert,
};
})();
+2 -2
View File
@@ -13,8 +13,8 @@ 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/moment.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/chart.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/chartjs-adapter-moment.js')?>"></script>
</head>
<body class="<?=theme.name?> hold-transition sidebar-mini <? if pihole.boxedlayout() then ?>layout-boxed<? end ?> logged-in">
@@ -34,7 +34,7 @@ mg.include('header.lp','r')
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="index.lp" class="logo">
<a href="<?=pihole.webhome()?>" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini">P<strong>h</strong></span>
<!-- logo for regular state and mobile devices -->
+1 -1
View File
@@ -230,7 +230,7 @@
<!-- Query Lists -->
<li class="<? if scriptname == 'search' then ?> active<? end ?>">
<a href="<?=webhome?>search">
<i class="fa fa-fw menu-icon fa-search"></i> <span>Search Adlists</span>
<i class="fa fa-fw menu-icon fa-search"></i> <span>Search Lists</span>
</a>
</li>
<!-- Network -->
-13
View 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
+2 -3
View File
@@ -1,8 +1,7 @@
/*!
* chartjs-adapter-moment v1.0.0
* chartjs-adapter-moment v1.0.1
* https://www.chartjs.org
* (c) 2021 chartjs-adapter-moment Contributors
* (c) 2022 chartjs-adapter-moment Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("moment"),require("chart.js")):"function"==typeof define&&define.amd?define(["moment","chart.js"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).moment,e.Chart)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=n(e);const a={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};t._adapters._date.override("function"==typeof f.default?{_id:"moment",formats:function(){return a},parse:function(e,t){return"string"==typeof e&&"string"==typeof t?e=f.default(e,t):e instanceof f.default||(e=f.default(e)),e.isValid()?e.valueOf():null},format:function(e,t){return f.default(e).format(t)},add:function(e,t,n){return f.default(e).add(t,n).valueOf()},diff:function(e,t,n){return f.default(e).diff(f.default(t),n)},startOf:function(e,t,n){return e=f.default(e),"isoWeek"===t?(n=Math.trunc(Math.min(Math.max(0,n),6)),e.isoWeekday(n).startOf("day").valueOf()):e.startOf(t).valueOf()},endOf:function(e,t){return f.default(e).endOf(t).valueOf()}}:{})}));
//# sourceMappingURL=chartjs-adapter-moment.min.js.map
+1 -1
View File
@@ -11,7 +11,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
?>
<!-- Title -->
<div class="page-header">
<h1>Find Blocked Domain In Lists</h1>
<h1>Find Domains In Lists</h1>
</div>
<div class="row">
<div class="col-md-12">
+42 -5
View File
@@ -19,7 +19,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-12 settings-level-1">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Exclusions</h3>
<h3 class="box-title webserver.api.excludeDomains webserver.api.excludeClients">Exclusions</h3>
</div>
<div class="box-body">
<div class="row">
@@ -40,7 +40,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-12 settings-level-0">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Theme settings</h3>
<h3 class="box-title webserver.interface.theme webserver.interface.boxed">Theme settings</h3>
</div>
<div class="box-body">
<div class="row">
@@ -65,7 +65,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
</div>
</div>
</div><!--
</div><!-- TODO: Why is this commented out?
<div class="col-md-6">
<div class="box box-warning">
<div class="box-header with-border">
@@ -92,7 +92,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-6 settings-level-1">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Advanced Settings</h3>
<h3 class="box-title webserver.api.localAPIauth webserver.api.prettyJSON webserver.api.allow_destructive webserver.api.temp.limit webserver.api.temp.unit">Advanced Settings</h3>
</div>
<div class="box-body">
<div class="row">
@@ -123,7 +123,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<label for="webserver.api.temp.limit"><strong>Temperature limit for "hot":</strong></label>
</div>
<div class="col-md-6">
<input type="number" id="webserver.api.temp.limit" data-key="webserver.api.temp.limit">
<input type="number" data-type="integer" id="webserver.api.temp.limit" data-key="webserver.api.temp.limit">
</div>
</div>
<div class="row">
@@ -136,8 +136,11 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</select>
</div>
</div>
</div>
<div class="col-lg-12 pt-3">
<button type="button" id="button-enable-totp" class="btn btn-success hidden" data-toggle="modal" data-target="#modal-totp">Enable 2FA</button>
<button type="button" id="button-disable-totp" class="btn btn-danger hidden" data-toggle="modal" data-target="#modal-totp-disable">Disable 2FA</button>
<button type="button" id="button-apppw" class="btn btn-default pull-right" data-toggle="modal" data-target="#modal-apppw">Configure app password</button>
</div>
</div>
</div>
@@ -158,6 +161,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<th title="Session ID used internally by our Pi-hole">ID</th>
<th title="Session is still valid (neither expired nor closed)">Valid</th>
<th title="Connection between client and Pi-hole is end-to-end encrypted">TLS</th>
<th title="Session used application password during authentication"><i class="fas fa-robot"></i></th>
<th title="Time at which the client created the session">Login at</th>
<th title="Time at which the session expires (if not prolonged)">Valid until</th>
<th title="The client that created this session (the current connection is highlighted in bold-face)">Client IP</th>
@@ -217,6 +221,39 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
</div>
<div class="modal fade" id="modal-apppw" style="display: none;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span></button>
<h4 class="modal-title">Configure application password</h4>
</div>
<div class="modal-body">
<p>After you turn on two-factor (2FA) verification and set up an
Authenticator app, you may run into issues if you use apps or
other services that don't support two-step verification. In this
case, you can create and use an app password to sign in.</p>
<p>An app password is a long, randomly generated password that
can be used instead of your regular password + 2FA token when
signing in to the API. You can revoke the app password at any
time. The app password can also be used when 2FA is not enabled.</p>
<div class="text-center">
<i id="password-spinner" class="fas fa-spinner fa-pulse fa-5x"></i>
</div>
<p id="password_display" class="text-center hidden">&#x1F510;<br><strong>Your new app password is:</strong><br><code class="password_background m-5" id="password_code"></code></p>
<p>IMPORTANT: The app password generated here will only be shown
once and cannot be recovered. Make sure to store it in a safe
place!</p>
</div>
<div class="modal-footer">
<button type="button" id="apppw_clear" class="btn btn-default btn-danger pull-left">Remove currently set app password</button>
<button type="button" id="apppw_submit" class="btn btn-default btn-success">Enable new app password</button>
</div>
</div>
</div>
</div>
<script src="<?=pihole.fileversion('scripts/vendor/jquery.confirm.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/vendor/qrious.min.js')?>"></script>
<script src="<?=pihole.fileversion('scripts/pi-hole/js/settings-api.js')?>"></script>
+3 -3
View File
@@ -18,7 +18,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-6 settings-level-0">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">DHCP Settings</h3>
<h3 class="box-title dhcp.active dhcp.start dhcp.end dhcp.router dhcp.ipv6">DHCP Settings</h3>
</div>
<div class="box-body">
<div class="row">
@@ -71,7 +71,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-6 settings-level-1">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Advanced DHCP Settings</h3>
<h3 class="box-title dhcp.domain dhcp.leaseTime dhcp.rapidCommit chdp.multiDNS">Advanced DHCP Settings</h3>
</div>
<div class="box-body">
<div class="row">
@@ -146,7 +146,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-12 settings-level-2">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Static DHCP configuration&nbsp;&nbsp;<i class="fas fa-wrench" title="This is an advanced-level setting"></i></h3>
<h3 class="box-title">Static DHCP configuration</h3>
</div>
<div class="box-body">
<div class="row">
+5 -5
View File
@@ -17,7 +17,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-lg-6">
<div class="box box-warning">
<div class="box-header with-border">
<h1 class="box-title">Upstream DNS Servers</h1>
<h1 class="box-title dns.upstreams" id="lblDNSupstreams">Upstream DNS Servers</h1>
</div>
<div class="box-body">
<div class="row">
@@ -59,7 +59,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
<div class="box box-warning settings-level-1">
<div class="box-header with-border">
<h3 class="box-title">Conditional forwarding&nbsp;&nbsp;<i class="fas fa-wrench" title="This is an advanced-level setting"></i></h3>
<h3 class="box-title dns.revServer.active dns.revServer.cidr dns.revServer.target dns.revServer.domain">Conditional forwarding</h3>
</div>
<div class="box-body">
<div class="row">
@@ -125,7 +125,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-lg-6 settings-level-1">
<div class="box box-warning">
<div class="box-header with-border">
<h1 class="box-title">Interface settings&nbsp;&nbsp;<i class="fas fa-wrench" title="This is an advanced-level setting"></i></h1>
<h1 class="box-title dns.listeningMode">Interface settings</h1>
</div>
<div class="box-body">
<div class="row">
@@ -166,7 +166,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Advanced DNS settings</h3>
<h3 class="box-title dns.domainNeeded dns.bogusPriv dns.dnssec">Advanced DNS settings</h3>
</div>
<div class="box-body">
<div class="row">
@@ -212,7 +212,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
</div>
<div class="box box-warning settings-level-2">
<div class="box-header with-border">
<h3 class="box-title">Rate-limiting&nbsp;&nbsp;<i class="fas fa-wrench" title="This is an advanced-level setting"></i></h3>
<h3 class="box-title dns.rateLimit.count dns.rateLimit.interval">Rate-limiting</h3>
</div>
<div class="box-body">
<div class="row">
+13 -13
View File
@@ -17,7 +17,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-6 settings-level-1">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Local DNS records</h3>
<h3 class="box-title" id="title-hosts">Local DNS records<span class="restart-warning">&nbsp;&nbsp;<i class="fas fa-redo text-orange" title="Setting requires FTL restart on change"></i></span></h3>
</div>
<div class="box-body">
<div class="row">
@@ -29,7 +29,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
Add new DNS record
</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-plus"></i></button>
<button type="button" class="btn btn-box-tool hosts" data-widget="collapse"><i class="fa fa-plus"></i></button>
</div>
</div>
<!-- /.box-header -->
@@ -37,11 +37,11 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="row">
<div class="form-group col-md-6">
<label for="domain">Domain:</label>
<input id="Hdomain" type="url" class="form-control" placeholder="Add a domain (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
<input id="Hdomain" type="url" class="form-control hosts" placeholder="Add a domain (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
<div class="form-group col-md-6">
<label for="target">IP address:</label>
<input id="Hip" type="text" class="form-control" placeholder="Associated IP address" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
<input id="Hip" type="text" class="form-control hosts" placeholder="Associated IP address" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
</div>
</div>
@@ -51,14 +51,14 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<p>The reason for this is that Pi-hole will not send additional queries upstream when serving <code>CNAME</code> replies. As consequence, if you set a target that isn't already known, the reply to the client may be incomplete. Pi-hole just returns the information it knows at the time of the query. This results in certain limitations for <code>CNAME</code> targets,
for instance, only <i>active</i> DHCP leases work as targets - mere DHCP <i>leases</i> aren't sufficient as they aren't (yet) valid DNS records.</p>
<p>Additionally, you can't <code>CNAME</code> external domains (<code>bing.com</code> to <code>google.com</code>) successfully as this could result in invalid SSL certificate errors when the target server does not serve content for the requested domain.</p>
<button type="button" id="btnAdd-host" class="btn btn-primary pull-right">Add</button>
<button type="button" id="btnAdd-host" class="btn btn-primary pull-right hosts">Add</button>
</div>
</div>
</div>
<div class="col-md-12">
<h3 class="box-title">List of local DNS records</h3>
<!-- /.box-header -->
<table id="dnsRecordsTable" class="table table-striped table-bordered" width="100%">
<table id="hosts-Table" class="table table-striped table-bordered" width="100%">
<thead>
<tr>
<th>Domain</th>
@@ -76,7 +76,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-6 settings-level-1">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Local CNAME records records</h3>
<h3 class="box-title" id="title-cnameRecords">Local CNAME records records<span class="restart-warning">&nbsp;&nbsp;<i class="fas fa-redo text-orange" title="Setting requires FTL restart on change"></i></span></h3>
</div>
<div class="box-body">
<div class="row">
@@ -88,7 +88,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
Add new CNAME record
</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-plus"></i></button>
<button type="button" class="btn btn-box-tool cnameRecords" data-widget="collapse"><i class="fa fa-plus"></i></button>
</div>
</div>
<!-- /.box-header -->
@@ -96,27 +96,27 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="row">
<div class="form-group col-md-5">
<label for="domain">Domain:</label>
<input id="Cdomain" type="url" class="form-control" placeholder="Add a domain (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
<input id="Cdomain" type="url" class="form-control cnameRecords" placeholder="Add a domain (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
<div class="form-group col-md-5">
<label for="target">Target Domain:</label>
<input id="Ctarget" type="url" class="form-control" placeholder="Associated Target Domain" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
<input id="Ctarget" type="url" class="form-control cnameRecords" placeholder="Associated Target Domain" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
<div class="form-group col-md-2">
<label for="target">TTL (optional):</label>
<input id="Cttl" type="numeric" class="form-control" placeholder="TTL in seconds" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
<input id="Cttl" type="numeric" class="form-control cnameRecords" placeholder="TTL in seconds" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
</div>
</div>
<div class="box-footer clearfix">
<button type="button" id="btnAdd-cname" class="btn btn-primary pull-right">Add</button>
<button type="button" id="btnAdd-cname" class="btn btn-primary pull-right cnameRecords">Add</button>
</div>
</div>
</div>
<div class="col-md-12">
<h3 class="box-title">List of local CNAME records</h3>
<!-- /.box-header -->
<table id="customCNAMETable" class="table table-striped table-bordered" width="100%">
<table id="cnameRecords-Table" class="table table-striped table-bordered" width="100%">
<thead>
<tr>
<th>Domain</th>
+3 -3
View File
@@ -19,7 +19,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-12">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Query Logging</h3>
<h3 class="box-title dns.queryLogging">Query Logging</h3>
</div>
<div class="box-body">
<div class="row">
@@ -37,7 +37,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-12 settings-level-2">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Privacy-related database settings</h3>
<h3 class="box-title database.DBimport database.DBexport database.maxDBdays database.network.expire">Privacy-related database settings</h3>
</div>
<div class="box-body">
<div class="row">
@@ -70,7 +70,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-6 settings-level-1">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Query Anonymization ("Privacy Level")</h3>
<h3 class="box-title misc.privacylevel">Query Anonymization ("Privacy Level")</h3>
</div>
<div class="box-body">
<div class="row">
+1 -1
View File
@@ -282,7 +282,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<div class="col-md-12 settings-level-1">
<div class="box box-warning">
<div class="box-header with-border">
<h3 class="box-title">Actions<span id="actions-title"></span>&nbsp;&nbsp;<i class="fas fa-wrench" title="This is an advanced-level setting"></i></h3>
<h3 class="box-title">Actions<span id="actions-title"></span></h3>
</div>
<div class="box-body">
<div class="row">
+1 -1
View File
@@ -23,7 +23,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
<p>Warning: This archive contains sensitive information about your Pi-hole installation, e.g. the API token and the 2FA-TOTP secret (if enabled). Please be careful with this file and do not share it with anyone even if they claim to help you.</p>
<? if not is_secure then ?><p class='text-danger'>Warning: You are currently not using an end-to-end encryption. This means that your API token and 2FA-TOTP secret will be transmitted in plain text. We recommend to use HTTPS when exporting your configuration.</p><? end ?>
<div class="pull-right">
<a class="btn btn-app btn-success" href="/api/teleporter" target="_blank">
<a class="btn btn-app btn-success" id="GETTeleporter" target="_blank">
<i class="fa fa-save"></i><br>Export
</a>
</div>
+220 -3
View File
@@ -80,13 +80,13 @@ td.lookatme {
/* Optimize Queries-Table for small screens */
/* Time column */
#all-queries td:nth-of-type(1),
/* Status column */
#all-queries td:nth-of-type(5) {
/* Reply time column */
#all-queries td:nth-of-type(6) {
white-space: nowrap;
}
/* Domain column */
#all-queries td:nth-of-type(3) {
#all-queries td:nth-of-type(4) {
min-width: 200px;
word-break: break-all;
white-space: pre-wrap;
@@ -1181,3 +1181,220 @@ table.dataTable tbody > tr > .selected {
font-weight: 500;
padding-right: 20px;
}
.m-0 {
margin: 0 !important;
}
.m-1 {
margin: 0.25rem !important;
}
.m-2 {
margin: 0.5rem !important;
}
.m-3 {
margin: 1rem !important;
}
.m-4 {
margin: 1.5rem !important;
}
.m-5 {
margin: 3rem !important;
}
.mt-0 {
margin-top: 0 !important;
}
.mr-0 {
margin-right: 0 !important;
}
.mb-0 {
margin-bottom: 0 !important;
}
.ml-0 {
margin-left: 0 !important;
}
.mx-0 {
margin-left: 0 !important;
margin-right: 0 !important;
}
.my-0 {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.mt-1 {
margin-top: 0.25rem !important;
}
.mr-1 {
margin-right: 0.25rem !important;
}
.mb-1 {
margin-bottom: 0.25rem !important;
}
.ml-1 {
margin-left: 0.25rem !important;
}
.mx-1 {
margin-left: 0.25rem !important;
margin-right: 0.25rem !important;
}
.my-1 {
margin-top: 0.25rem !important;
margin-bottom: 0.25rem !important;
}
.mt-2 {
margin-top: 0.5rem !important;
}
.mr-2 {
margin-right: 0.5rem !important;
}
.mb-2 {
margin-bottom: 0.5rem !important;
}
.ml-2 {
margin-left: 0.5rem !important;
}
.mx-2 {
margin-right: 0.5rem !important;
margin-left: 0.5rem !important;
}
.my-2 {
margin-top: 0.5rem !important;
margin-bottom: 0.5rem !important;
}
.mt-3 {
margin-top: 1rem !important;
}
.mr-3 {
margin-right: 1rem !important;
}
.mb-3 {
margin-bottom: 1rem !important;
}
.ml-3 {
margin-left: 1rem !important;
}
.mx-3 {
margin-right: 1rem !important;
margin-left: 1rem !important;
}
.my-3 {
margin-bottom: 1rem !important;
margin-top: 1rem !important;
}
.p-0 {
padding: 0 !important;
}
.p-1 {
padding: 0.25rem !important;
}
.p-2 {
padding: 0.5rem !important;
}
.p-3 {
padding: 1rem !important;
}
.p-4 {
padding: 1.5rem !important;
}
.p-5 {
padding: 3rem !important;
}
.pt-0 {
padding-top: 0 !important;
}
.pr-0 {
padding-right: 0 !important;
}
.pb-0 {
padding-bottom: 0 !important;
}
.pl-0 {
padding-left: 0 !important;
}
.px-0 {
padding-left: 0 !important;
padding-right: 0 !important;
}
.py-0 {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.pt-1 {
padding-top: 0.25rem !important;
}
.pr-1 {
padding-right: 0.25rem !important;
}
.pb-1 {
padding-bottom: 0.25rem !important;
}
.pl-1 {
padding-left: 0.25rem !important;
}
.px-1 {
padding-left: 0.25rem !important;
padding-right: 0.25rem !important;
}
.py-1 {
padding-top: 0.25rem !important;
padding-bottom: 0.25rem !important;
}
.pt-2 {
padding-top: 0.5rem !important;
}
.pr-2 {
padding-right: 0.5rem !important;
}
.pb-2 {
padding-bottom: 0.5rem !important;
}
.pl-2 {
padding-left: 0.5rem !important;
}
.px-2 {
padding-right: 0.5rem !important;
padding-left: 0.5rem !important;
}
.py-2 {
padding-top: 0.5rem !important;
padding-bottom: 0.5rem !important;
}
.pt-3 {
padding-top: 1rem !important;
}
.pr-3 {
padding-right: 1rem !important;
}
.pb-3 {
padding-bottom: 1rem !important;
}
.pl-3 {
padding-left: 1rem !important;
}
.px-3 {
padding-right: 1rem !important;
padding-left: 1rem !important;
}
.py-3 {
padding-bottom: 1rem !important;
padding-top: 1rem !important;
}
.password_background {
background-image: repeating-linear-gradient(
45deg,
white 0%,
white 2%,
rgb(0, 0, 0) 2%,
rgb(0, 0, 0) 4%,
white 4%
);
}
+3 -3
View File
@@ -1,6 +1,6 @@
/* Code courtesy of https://blog.jim-nielsen.com/2019/conditional-syntax-highlighting-in-dark-mode-with-css-imports/ */
/* Assume light mode by default */
@import "default-light.css" screen;
/* Supersede dark mode when applicable */
/* Import light mode if color-scheme is different than dark (even not set) */
@import "default-light.css" screen and not (prefers-color-scheme: dark);
/* Import dark mode when applicable */
@import "default-dark.css" screen and (prefers-color-scheme: dark);