Merge branch 'master' into master-to-development-v5.0

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER
2020-05-10 22:00:07 +02:00
23 changed files with 711 additions and 2024 deletions
+15
View File
@@ -368,15 +368,30 @@ if (isset($_GET['getGraphData']) && $auth)
// Parse the DB result into graph data, filling in missing interval sections with zero
function parseDBData($results, $interval, $from, $until) {
$data = array();
$first_db_timestamp = -1;
if(!is_bool($results)) {
// Read in the data
while($row = $results->fetchArray()) {
// $data[timestamp] = value_in_this_interval
$data[$row[0]] = intval($row[1]);
if($first_db_timestamp === -1)
$first_db_timestamp = intval($row[0]);
}
}
// It is unpredictable what the first timestamp returned by the database
// will be. This depends on live data. Hence, we re-align the FROM
// timestamp to avoid unaligned holes appearing as additional
// (incorrect) data points
$aligned_from = $from + (($first_db_timestamp - $from) % $interval);
// Fill gaps in returned data
for($i = $aligned_from; $i < $until; $i += $interval) {
if(!array_key_exists($i, $data))
$data[$i] = 0;
}
return $data;
}
+2 -2
View File
@@ -1,4 +1,4 @@
<?php /*
<?php /*
* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
@@ -12,7 +12,7 @@
<h1>Generate debug log</h1>
</div>
<p><input type="checkbox" id="upload" checked> Upload debug log and provide token once finished</p>
<p><input type="checkbox" id="upload"> Upload debug log and provide token once finished</p>
<p>Once you click this button a debug log will be generated and can automatically be uploaded if we detect a working internet connection.</p>
<button class="btn btn-lg btn-primary btn-block" id="debugBtn">Generate debug log</button>
<pre id="output" style="width: 100%; height: 100%;" hidden="true"></pre>
+5 -5
View File
@@ -11,8 +11,8 @@
<!-- Title -->
<div class="page-header">
<h1>Custom DNS configuration</h1>
<small>On this page, you can add domain/IP associations similar to records in <code>/etc/hosts</code>.</small>
<h1>Local DNS Records</h1>
<small>On this page, you can add domain/IP associations</small>
</div>
<!-- Domain Input -->
@@ -28,11 +28,11 @@
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-6">
<div class="form-group col-md-6">
<label for="domain">Domain:</label>
<input id="domain" type="url" class="form-control" placeholder="Add a domain (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
<div class="col-md-6">
<div class="form-group col-md-6">
<label for="ip">IP Address:</label>
<input id="ip" type="text" class="form-control" placeholder="Associated IP address" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
@@ -67,7 +67,7 @@
<div class="box" id="recent-queries">
<div class="box-header with-border">
<h3 class="box-title">
List of custom DNS domains
List of local DNS domains
</h3>
</div>
<!-- /.box-header -->
+5 -5
View File
@@ -26,12 +26,12 @@
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-6">
<label for="ex1">Address:</label>
<input id="new_address" type="url" class="form-control" placeholder="http://..., https://..., file://..." autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
<div class="form-group col-md-6">
<label for="new_address">Address:</label>
<input id="new_address" type="text" class="form-control" placeholder="http://..., https://..., file://..." autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
<div class="col-md-6">
<label for="ex2">Comment:</label>
<div class="form-group col-md-6">
<label for="new_comment">Comment:</label>
<input id="new_comment" type="text" class="form-control" placeholder="Adlist description (optional)">
</div>
</div>
+4 -4
View File
@@ -26,15 +26,15 @@
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-6">
<label for="ex1">Known clients:</label>
<div class="form-group col-md-6">
<label for="select">Known clients:</label>
<select id="select" class="form-control" placeholder="">
<option disabled selected>Loading...</option>
</select><br>
<input id="ip-custom" type="text" class="form-control" disabled placeholder="Client IP address (IPv4 or IPv6, CIDR subnetting available, optional)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
<div class="col-md-6">
<label for="ex3">Comment:</label>
<div class="form-group col-md-6">
<label for="new_comment">Comment:</label>
<input id="new_comment" type="text" class="form-control" placeholder="Client description (optional)">
</div>
</div>
+4 -4
View File
@@ -26,12 +26,12 @@
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-6">
<label for="ex1">Name:</label>
<div class="form-group col-md-6">
<label for="new_name">Name:</label>
<input id="new_name" type="text" class="form-control" placeholder="Group name">
</div>
<div class="col-md-6">
<label for="ex2">Description:</label>
<div class="form-group col-md-6">
<label for="new_desc">Description:</label>
<input id="new_desc" type="text" class="form-control" placeholder="Group description (optional)">
</div>
</div>
+58 -25
View File
@@ -140,23 +140,23 @@ function updateQueriesOverTime() {
for (hour in dates) {
if (Object.prototype.hasOwnProperty.call(dates, hour)) {
var date,
dom = 0,
ads = 0;
total = 0,
blocked = 0;
date = new Date(1000 * dates[hour]);
var idx = data.domains_over_time[0].indexOf(dates[hour].toString());
if (idx > -1) {
dom = data.domains_over_time[1][idx];
total = data.domains_over_time[1][idx];
}
idx = data.ads_over_time[0].indexOf(dates[hour].toString());
if (idx > -1) {
ads = data.ads_over_time[1][idx];
blocked = data.ads_over_time[1][idx];
}
timeLineChart.data.labels.push(date);
timeLineChart.data.datasets[0].data.push(dom - ads);
timeLineChart.data.datasets[1].data.push(ads);
timeLineChart.data.datasets[0].data.push(blocked);
timeLineChart.data.datasets[1].data.push(total - blocked);
}
}
@@ -170,28 +170,30 @@ function updateQueriesOverTime() {
$(document).ready(function () {
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
var blockedColor = "#999";
var permittedColor = "#00a65a";
timeLineChart = new Chart(ctx, {
type: "bar",
data: {
labels: [],
datasets: [
{
label: "Permitted DNS Queries",
label: "Blocked DNS Queries",
fill: true,
backgroundColor: "rgba(0, 166, 90,.8)",
borderColor: "rgba(0, 166, 90,.8)",
pointBorderColor: "rgba(0, 166, 90,.8)",
backgroundColor: blockedColor,
borderColor: blockedColor,
pointBorderColor: blockedColor,
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5
},
{
label: "Blocked DNS Queries",
label: "Permitted DNS Queries",
fill: true,
backgroundColor: "rgba(0,192,239,1)",
borderColor: "rgba(0,192,239,1)",
pointBorderColor: "rgba(0,192,239,1)",
backgroundColor: permittedColor,
borderColor: permittedColor,
pointBorderColor: permittedColor,
pointRadius: 1,
pointHoverRadius: 5,
data: [],
@@ -202,6 +204,9 @@ $(document).ready(function () {
options: {
tooltips: {
enabled: true,
itemSort: function(a, b) {
return b.datasetIndex - a.datasetIndex;
},
mode: "x-axis",
callbacks: {
title: function (tooltipItem) {
@@ -212,8 +217,8 @@ $(document).ready(function () {
"-" +
padNumber(time.getMonth() + 1) +
"-" +
padNumber(time.getDate()) +
" " +
padNumber(time.getDate());
var from_time =
padNumber(time.getHours()) +
":" +
padNumber(time.getMinutes()) +
@@ -225,22 +230,50 @@ $(document).ready(function () {
"-" +
padNumber(time.getMonth() + 1) +
"-" +
padNumber(time.getDate()) +
" " +
padNumber(time.getDate());
var until_time =
padNumber(time.getHours()) +
":" +
padNumber(time.getMinutes()) +
":" +
padNumber(time.getSeconds());
return "Queries from " + from_date + " to " + until_date;
if (from_date === until_date) {
// Abbreviated form for intervals on the same day
// We split title in two lines on small screens
if ($(window).width() < 992) {
until_time += "\n";
}
return ("Queries from " + from_time + " to " + until_time + " on " + from_date).split(
"\n "
);
}
// Full tooltip for intervals spanning more than one day
// We split title in two lines on small screens
if ($(window).width() < 992) {
from_date += "\n";
}
return (
"Queries from " +
from_date +
" " +
from_time +
" to " +
until_date +
" " +
until_time
).split("\n ");
},
label: function (tooltipItems, data) {
if (tooltipItems.datasetIndex === 1) {
label: function(tooltipItems, data) {
if (tooltipItems.datasetIndex === 0) {
var percentage = 0.0;
var total = parseInt(data.datasets[0].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
if (total > 0) {
percentage = (100.0 * blocked) / total;
var permitted = parseInt(data.datasets[1].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[0].data[tooltipItems.index]);
if (permitted + blocked > 0) {
percentage = (100.0 * blocked) / (permitted + blocked);
}
return (
+8
View File
@@ -322,6 +322,14 @@ $(document).ready(function () {
$(row).css("color", color);
$("td:eq(4)", row).html(fieldtext);
$("td:eq(5)", row).html(buttontext);
// Substitute domain by "." if empty
var domain = data[2];
if (domain.length === 0) {
domain = ".";
}
$("td:eq(2)", row).text(domain);
},
dom:
"<'row'<'col-sm-12'f>>" +
+79 -48
View File
@@ -26,14 +26,8 @@ function get_groups() {
$(document).ready(function () {
$("#btnAdd").on("click", addAdlist);
utils.bsSelect_defaults();
get_groups();
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
input.setAttribute("autocomplete", "off");
input.setAttribute("autocorrect", "off");
input.setAttribute("autocapitalize", "off");
input.setAttribute("spellcheck", false);
});
function initTable() {
@@ -54,6 +48,8 @@ function initTable() {
],
drawCallback: function () {
$('button[id^="deleteAdlist_"]').on("click", deleteAdlist);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
$(row).attr("data-id", data.id);
@@ -95,61 +91,73 @@ function initTable() {
$("td:eq(3)", row).empty();
$("td:eq(3)", row).append(
'<div id="selectHome_' +
data.id +
'">' +
'<select id="multiselect_' +
data.id +
'" multiple="multiple"></select></div>'
'<select class="selectpicker" id="multiselect_' + data.id + '" multiple></select>'
);
var selectEl = $("#multiselect_" + data.id, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
var data_sub = "";
if (!groups[i].enabled) {
extra = " (disabled)";
data_sub = 'data-subtext="(disabled)"';
}
selectEl.append(
$("<option />")
$("<option " + data_sub + "/>")
.val(groups[i].id)
.text(groups[i].name + extra)
.text(groups[i].name)
);
}
// Select assigned groups
selectEl.val(data.groups);
// Initialize multiselect
selectEl.multiselect({
includeSelectAllOption: true,
buttonContainer: '<div id="container_' + data.id + '" class="btn-group"/>',
maxHeight: 200,
onDropdownShown: function () {
var el = $("#container_" + data.id);
var top = el[0].getBoundingClientRect().top;
var bottom = $(window).height() - top - el.height();
if (bottom < 200) {
el.addClass("dropup");
// Initialize bootstrap-select
selectEl
// fix dropdown if it would stick out right of the viewport
.on("show.bs.select", function() {
var winWidth = $(window).width();
var dropdownEl = $("body > .bootstrap-select.dropdown");
if (dropdownEl.length > 0) {
dropdownEl.removeClass("align-right");
var width = dropdownEl.width();
var left = dropdownEl.offset().left;
if (left + width > winWidth) {
dropdownEl.addClass("align-right");
}
}
if (bottom > 200) {
el.removeClass("dropup");
})
.on("changed.bs.select", function() {
// enable Apply button
if ($(ApplyBtn).prop("disabled")) {
$(ApplyBtn)
.addClass("btn-success")
.prop("disabled", false)
.on("click", function() {
editAdlist.call(selectEl);
});
}
})
.on("hide.bs.select", function() {
// Restore values if drop-down menu is closed without clicking the Apply button
if (!$(ApplyBtn).prop("disabled")) {
$(this)
.val(data.groups)
.selectpicker("refresh");
$(ApplyBtn)
.removeClass("btn-success")
.prop("disabled", true)
.off("click");
}
})
.selectpicker()
.siblings(".dropdown-menu")
.find(".bs-actionsbox")
.prepend(
'<button type="button" id=btn_apply_' +
data.id +
' class="btn btn-block btn-sm" disabled>Apply</button>'
);
var offset = el.offset();
$("body").append(el);
el.css("position", "absolute");
el.css("top", offset.top + "px");
el.css("left", offset.left + "px");
},
onDropdownHide: function () {
var el = $("#container_" + data.id);
var home = $("#selectHome_" + data.id);
home.append(el);
el.removeAttr("style");
}
});
selectEl.on("change", editAdlist);
var ApplyBtn = "#btn_apply_" + data.id;
var button =
'<button class="btn btn-danger btn-xs" type="button" id="deleteAdlist_' +
@@ -204,6 +212,13 @@ function initTable() {
table.order([[0, "asc"]]).draw();
$("#resetButton").hide();
});
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
input.setAttribute("autocomplete", "off");
input.setAttribute("autocorrect", "off");
input.setAttribute("autocapitalize", "off");
input.setAttribute("spellcheck", false);
}
function addAdlist() {
@@ -231,7 +246,13 @@ function addAdlist() {
success: function (response) {
utils.enableAll();
if (response.success) {
utils.showAlert("success", "fas fa-plus", "Successfully added adlist", address);
utils.showAlert(
"success",
"glyphicon glyphicon-plus",
"Successfully added adlist",
address
);
table.ajax.reload(null, false);
$("#new_address").val("");
$("#new_comment").val("");
table.ajax.reload();
@@ -306,6 +327,7 @@ function editAdlist() {
"Successfully " + done + " adlist ",
address
);
table.ajax.reload(null, false);
} else {
utils.showAlert(
"error",
@@ -343,8 +365,17 @@ function deleteAdlist() {
success: function (response) {
utils.enableAll();
if (response.success) {
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted adlist ", address);
table.row(tr).remove().draw(false);
utils.showAlert(
"success",
"glyphicon glyphicon-trash",
"Successfully deleted adlist ",
address
);
table
.row(tr)
.remove()
.draw(false)
.ajax.reload(null, false);
} else {
utils.showAlert("error", "", "Error while deleting adlist with ID " + id, response.message);
}
+72 -42
View File
@@ -57,6 +57,7 @@ $(document).ready(function () {
$("#btnAdd").on("click", addClient);
reload_client_suggestions();
utils.bsSelect_defaults();
get_groups();
$("#select").on("change", function () {
@@ -88,6 +89,8 @@ function initTable() {
],
drawCallback: function () {
$('button[id^="deleteClient_"]').on("click", deleteClient);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
$(row).attr("data-id", data.id);
@@ -124,61 +127,73 @@ function initTable() {
$("td:eq(2)", row).empty();
$("td:eq(2)", row).append(
'<div id="selectHome_' +
data.id +
'">' +
'<select id="multiselect_' +
data.id +
'" multiple="multiple"></select></div>'
'<select class="selectpicker" id="multiselect_' + data.id + '" multiple></select>'
);
var selectEl = $("#multiselect_" + data.id, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
var data_sub = "";
if (!groups[i].enabled) {
extra = " (disabled)";
data_sub = 'data-subtext="(disabled)"';
}
selectEl.append(
$("<option />")
$("<option " + data_sub + "/>")
.val(groups[i].id)
.text(groups[i].name + extra)
.text(groups[i].name)
);
}
// Select assigned groups
selectEl.val(data.groups);
// Initialize multiselect
selectEl.multiselect({
includeSelectAllOption: true,
buttonContainer: '<div id="container_' + data.id + '" class="btn-group"/>',
maxHeight: 200,
onDropdownShown: function () {
var el = $("#container_" + data.id);
var top = el[0].getBoundingClientRect().top;
var bottom = $(window).height() - top - el.height();
if (bottom < 200) {
el.addClass("dropup");
// Initialize bootstrap-select
selectEl
// fix dropdown if it would stick out right of the viewport
.on("show.bs.select", function() {
var winWidth = $(window).width();
var dropdownEl = $("body > .bootstrap-select.dropdown");
if (dropdownEl.length > 0) {
dropdownEl.removeClass("align-right");
var width = dropdownEl.width();
var left = dropdownEl.offset().left;
if (left + width > winWidth) {
dropdownEl.addClass("align-right");
}
}
if (bottom > 200) {
el.removeClass("dropup");
})
.on("changed.bs.select", function() {
// enable Apply button
if ($(ApplyBtn).prop("disabled")) {
$(ApplyBtn)
.addClass("btn-success")
.prop("disabled", false)
.on("click", function() {
editClient.call(selectEl);
});
}
})
.on("hide.bs.select", function() {
// Restore values if drop-down menu is closed without clicking the Apply button
if (!$(ApplyBtn).prop("disabled")) {
$(this)
.val(data.groups)
.selectpicker("refresh");
$(ApplyBtn)
.removeClass("btn-success")
.prop("disabled", true)
.off("click");
}
})
.selectpicker()
.siblings(".dropdown-menu")
.find(".bs-actionsbox")
.prepend(
'<button type="button" id=btn_apply_' +
data.id +
' class="btn btn-block btn-sm" disabled>Apply</button>'
);
var offset = el.offset();
$("body").append(el);
el.css("position", "absolute");
el.css("top", offset.top + "px");
el.css("left", offset.left + "px");
},
onDropdownHide: function () {
var el = $("#container" + data.id);
var home = $("#selectHome" + data.id);
home.append(el);
el.removeAttr("style");
}
});
selectEl.on("change", editClient);
var ApplyBtn = "#btn_apply_" + data.id;
var button =
'<button class="btn btn-danger btn-xs" type="button" id="deleteClient_' +
@@ -276,7 +291,7 @@ function addClient() {
if (response.success) {
utils.showAlert("success", "fas fa-plus", "Successfully added client", ip);
reload_client_suggestions();
table.ajax.reload();
table.ajax.reload(null, false);
} else {
utils.showAlert("error", "", "Error while adding new client", response.message);
}
@@ -335,7 +350,13 @@ function editClient() {
success: function (response) {
utils.enableAll();
if (response.success) {
utils.showAlert("success", "fas fa-plus", "Successfully " + done + " client", ip_name);
utils.showAlert(
"success",
"glyphicon glyphicon-pencil",
"Successfully " + done + " client",
ip_name
);
table.ajax.reload(null, false);
} else {
utils.showAlert(
"error",
@@ -378,8 +399,17 @@ function deleteClient() {
success: function (response) {
utils.enableAll();
if (response.success) {
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted client ", ip_name);
table.row(tr).remove().draw(false);
utils.showAlert(
"success",
"glyphicon glyphicon-trash",
"Successfully deleted client ",
ip_name
);
table
.row(tr)
.remove()
.draw(false)
.ajax.reload(null, false);
reload_client_suggestions();
} else {
utils.showAlert("error", "", "Error while deleting client with ID " + id, response.message);
+24 -3
View File
@@ -116,7 +116,7 @@ function validateIPv6CIDR(ip) {
ipv6elem +
"))*::((?:" +
ipv6elem +
"))((?::" +
"))*((?::" +
ipv6elem +
"))*|((?:" +
ipv6elem +
@@ -129,13 +129,34 @@ function validateIPv6CIDR(ip) {
return ipv6validator.test(ip);
}
window.utils = (function () {
function bsSelect_defaults() {
// set bootstrap-select defaults
var pickerDEFAULTS = $.fn.selectpicker.Constructor.DEFAULTS;
pickerDEFAULTS.noneSelectedText = "none selected";
pickerDEFAULTS.selectedTextFormat = "count > 1";
pickerDEFAULTS.actionsBox = true;
pickerDEFAULTS.width = "fit";
pickerDEFAULTS.container = "body";
pickerDEFAULTS.dropdownAlignRight = "auto";
pickerDEFAULTS.selectAllText = "All";
pickerDEFAULTS.deselectAllText = "None";
pickerDEFAULTS.countSelectedText = function(num, total) {
if (num === total) {
return "All selected (" + num + ")";
}
return num + " selected";
};
}
window.utils = (function() {
return {
showAlert: showAlert,
datetime: datetime,
disableAll: disableAll,
enableAll: enableAll,
validateIPv4CIDR: validateIPv4CIDR,
validateIPv6CIDR: validateIPv6CIDR
validateIPv6CIDR: validateIPv6CIDR,
bsSelect_defaults: bsSelect_defaults
};
})();
+61 -41
View File
@@ -61,6 +61,7 @@ $(document).ready(function () {
$("#add2black, #add2white").on("click", addDomain);
utils.bsSelect_defaults();
get_groups();
});
@@ -83,6 +84,8 @@ function initTable() {
],
drawCallback: function () {
$('button[id^="deleteDomain_"]').on("click", deleteDomain);
// Remove visible dropdown to prevent orphaning
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
$(row).attr("data-id", data.id);
@@ -159,63 +162,75 @@ function initTable() {
if (table.column(5).visible()) {
$("td:eq(4)", row).empty();
$("td:eq(4)", row).append(
'<div id="selectHome_' +
data.id +
'">' +
'<select id="multiselect_' +
data.id +
'" multiple="multiple"></select></div>'
'<select class="selectpicker" id="multiselect_' + data.id + '" multiple></select>'
);
var selectEl = $("#multiselect_" + data.id, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
var data_sub = "";
if (!groups[i].enabled) {
extra = " (disabled)";
data_sub = 'data-subtext="(disabled)"';
}
selectEl.append(
$("<option />")
$("<option " + data_sub + "/>")
.val(groups[i].id)
.text(groups[i].name + extra)
.text(groups[i].name)
);
}
// Select assigned groups
selectEl.val(data.groups);
// Initialize multiselect
selectEl.multiselect({
includeSelectAllOption: true,
buttonContainer: '<div id="container_' + data.id + '" class="btn-group"/>',
maxHeight: 200,
onDropdownShown: function () {
var el = $("#container_" + data.id);
var top = el[0].getBoundingClientRect().top;
var bottom = $(window).height() - top - el.height();
if (bottom < 200) {
el.addClass("dropup");
// Initialize bootstrap-select
selectEl
// fix dropdown if it would stick out right of the viewport
.on("show.bs.select", function() {
var winWidth = $(window).width();
var dropdownEl = $("body > .bootstrap-select.dropdown");
if (dropdownEl.length > 0) {
dropdownEl.removeClass("align-right");
var width = dropdownEl.width();
var left = dropdownEl.offset().left;
if (left + width > winWidth) {
dropdownEl.addClass("align-right");
}
}
if (bottom > 200) {
el.removeClass("dropup");
})
.on("changed.bs.select", function() {
// enable Apply button
if ($(ApplyBtn).prop("disabled")) {
$(ApplyBtn)
.addClass("btn-success")
.prop("disabled", false)
.on("click", function() {
editDomain.call(selectEl);
});
}
var offset = el.offset();
$("body").append(el);
el.css("position", "absolute");
el.css("top", offset.top + "px");
el.css("left", offset.left + "px");
},
onDropdownHide: function () {
var el = $("#container_" + data.id);
var home = $("#selectHome_" + data.id);
home.append(el);
el.removeAttr("style");
}
});
selectEl.on("change", editDomain);
})
.on("hide.bs.select", function() {
// Restore values if drop-down menu is closed without clicking the Apply button
if (!$(ApplyBtn).prop("disabled")) {
$(this)
.val(data.groups)
.selectpicker("refresh");
$(ApplyBtn)
.removeClass("btn-success")
.prop("disabled", true)
.off("click");
}
})
.selectpicker()
.siblings(".dropdown-menu")
.find(".bs-actionsbox")
.prepend(
'<button type="button" id=btn_apply_' +
data.id +
' class="btn btn-block btn-sm" disabled>Apply</button>'
);
}
var ApplyBtn = "#btn_apply_" + data.id;
// Highlight row (if url parameter "domainid=" is used)
if ("domainid" in GETDict && data.id === parseInt(GETDict.domainid)) {
$(row).find("td").addClass("highlight");
@@ -360,7 +375,7 @@ function addDomain() {
domainEl.val("");
commentEl.val("");
wildcardEl.prop("checked", false);
table.ajax.reload();
table.ajax.reload(null, false);
} else {
utils.showAlert("error", "", "Error while adding new " + domain_regex, response.message);
}
@@ -452,6 +467,7 @@ function editDomain() {
"Successfully " + done + " " + domain_regex,
domain
);
table.ajax.reload(null, false);
} else
utils.showAlert(
"error",
@@ -502,7 +518,11 @@ function deleteDomain() {
"Successfully deleted " + domain_regex,
domain
);
table.row(tr).remove().draw(false);
table
.row(tr)
.remove()
.draw(false)
.ajax.reload(null, false);
} else {
utils.showAlert(
"error",
+178 -71
View File
@@ -32,16 +32,26 @@ function objectToArray(p) {
return [idx, arr];
}
var lastTooltipTime = 0;
var customTooltips = function (tooltip) {
// Tooltip Element
var tooltipEl = document.getElementById("chartjs-tooltip");
var customTooltips = function(tooltip) {
var tooltipEl = document.getElementById(this._chart.canvas.id + "-customTooltip");
if (!tooltipEl) {
// Create Tooltip Element once per chart
tooltipEl = document.createElement("div");
tooltipEl.id = "chartjs-tooltip";
document.body.appendChild(tooltipEl);
$(tooltipEl).html("<table></table>");
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
// font-size was set to 14px by bootstrap's css
var fontZoom = parseFloat($("body").css("font-size")) / 14;
// set styles and font
tooltipEl.style.padding = tooltip.yPadding + "px " + tooltip.xPadding + "px";
tooltipEl.style.borderRadius = tooltip.cornerRadius + "px";
tooltipEl.style.fontFamily = tooltip._bodyFontFamily;
tooltipEl.style.fontSize = tooltip.bodyFontSize / fontZoom + "px";
tooltipEl.style.fontStyle = tooltip._bodyFontStyle;
// append Tooltip next to canvas-containing box
tooltipEl.ancestor = this._chart.canvas.closest(".box[id]").parentNode;
tooltipEl.ancestor.appendChild(tooltipEl);
}
// Hide if no tooltip
@@ -50,22 +60,9 @@ var customTooltips = function (tooltip) {
return;
}
// Limit rendering to once every 50ms. This gives the DOM time to react,
// and avoids "lag" caused by not giving the DOM time to reapply CSS.
var now = Date.now();
if (now - lastTooltipTime < 50) {
return;
}
lastTooltipTime = now;
// Set caret Position
tooltipEl.classList.remove("above", "below", "no-transform");
if (tooltip.yAlign) {
tooltipEl.classList.add(tooltip.yAlign);
} else {
tooltipEl.classList.add("above");
}
// Set caret position
tooltipEl.classList.remove("left", "right", "center", "top", "bottom");
tooltipEl.classList.add(tooltip.xAlign, tooltip.yAlign);
function getBody(bodyItem) {
return bodyItem.lines;
@@ -75,22 +72,26 @@ var customTooltips = function (tooltip) {
if (tooltip.body) {
var titleLines = tooltip.title || [];
var bodyLines = tooltip.body.map(getBody);
var innerHtml = "<table><thead>";
titleLines.forEach(function (title) {
var innerHtml = "<thead>";
titleLines.forEach(function(title) {
innerHtml += "<tr><th>" + title + "</th></tr>";
});
innerHtml += "</thead><tbody>";
var printed = 0;
bodyLines.forEach(function (body, i) {
var devicePixel = (1 / window.devicePixelRatio).toFixed(1);
bodyLines.forEach(function(body, i) {
var colors = tooltip.labelColors[i];
var style = "background:" + colors.backgroundColor;
style += "; border-color:" + colors.borderColor;
style += "; border-width: 2px";
var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
var style = "background: " + colors.backgroundColor;
style += "; outline: 1px solid " + colors.backgroundColor;
style += "; border: " + devicePixel + "px solid #fff";
var span = "<span class='chartjs-tooltip-key' style='" + style + "'></span>";
var num = body[0].split(": ");
// remove percent symbol from amount to allow numeric comparison
var number = num[1].replace(/%/i, "");
if (number > 0) {
// do not display entries with value of 0 (in bar chart),
// but pass through entries with "0.0% (in pie charts)
if (num[1] !== "0") {
innerHtml += "<tr><td>" + span + body + "</td></tr>";
printed++;
}
@@ -99,30 +100,119 @@ var customTooltips = function (tooltip) {
innerHtml += "<tr><td>No activity recorded</td></tr>";
}
innerHtml += "</tbody></table>";
$(tooltipEl).html(innerHtml);
innerHtml += "</tbody>";
var tableRoot = tooltipEl.querySelector("table");
tableRoot.innerHTML = innerHtml;
}
// Display, position, and set styles for font
var position = this._chart.canvas.getBoundingClientRect();
var width = tooltip.caretX;
// Prevent compression of the tooltip at the right edge of the screen
if ($(document).width() - tooltip.caretX < 400) {
width = $(document).width() - 400;
var canvasPos = this._chart.canvas.getBoundingClientRect();
var boxPos = tooltipEl.ancestor.getBoundingClientRect();
var offsetX = canvasPos.left - boxPos.left;
var offsetY = canvasPos.top - boxPos.top;
var tooltipWidth = tooltipEl.offsetWidth;
var tooltipHeight = tooltipEl.offsetHeight;
var caretX = tooltip.caretX;
var caretY = tooltip.caretY;
var caretPadding = tooltip.caretPadding;
var tooltipX, tooltipY, arrowX;
var arrowMinIndent = 2 * tooltip.cornerRadius;
var arrowSize = 5;
// Compute X position
if ($(document).width() > 2 * tooltip.width || tooltip.xAlign !== "center") {
// If the viewport is wide enough, let the tooltip follow the caret position
tooltipX = offsetX + caretX;
if (tooltip.yAlign === "top" || tooltip.yAlign === "bottom") {
switch (tooltip.xAlign) {
case "center":
// set a minimal X position to 5px to prevent
// the tooltip to stick out left of the viewport
var minX = 5;
if (2 * tooltipX < tooltipWidth + minX) {
arrowX = tooltipX - minX;
tooltipX = minX;
} else {
tooltipX -= tooltipWidth / 2;
}
break;
case "left":
tooltipX -= arrowMinIndent;
arrowX = arrowMinIndent;
break;
case "right":
tooltipX -= tooltipWidth - arrowMinIndent;
arrowX = tooltipWidth - arrowMinIndent;
break;
default:
break;
}
} else if (tooltip.yAlign === "center") {
switch (tooltip.xAlign) {
case "left":
tooltipX += caretPadding;
break;
case "right":
tooltipX -= tooltipWidth - caretPadding;
break;
case "center":
tooltipX -= tooltipWidth / 2;
break;
default:
break;
}
}
} else {
// compute the tooltip's center inside ancestor element
tooltipX = (tooltipEl.ancestor.offsetWidth - tooltipWidth) / 2;
// move the tooltip if the arrow would stick out to the left
if (offsetX + caretX - arrowMinIndent < tooltipX) {
tooltipX = offsetX + caretX - arrowMinIndent;
}
// move the tooltip if the arrow would stick out to the right
if (offsetX + caretX - tooltipWidth + arrowMinIndent > tooltipX) {
tooltipX = offsetX + caretX - tooltipWidth + arrowMinIndent;
}
arrowX = offsetX + caretX - tooltipX;
}
// Prevent tooltip disapearing behind the sidebar
if (tooltip.caretX < 100) {
width = 100;
// Compute Y position
switch (tooltip.yAlign) {
case "top":
tooltipY = offsetY + caretY + arrowSize + caretPadding;
break;
case "center":
tooltipY = offsetY + caretY - tooltipHeight / 2;
if (tooltip.xAlign === "left") {
tooltipX += arrowSize;
} else if (tooltip.xAlign === "right") {
tooltipX -= arrowSize;
}
break;
case "bottom":
tooltipY = offsetY + caretY - tooltipHeight - arrowSize - caretPadding;
break;
default:
break;
}
// Position tooltip and display
tooltipEl.style.top = tooltipY.toFixed(1) + "px";
tooltipEl.style.left = tooltipX.toFixed(1) + "px";
if (arrowX === undefined) {
tooltipEl.querySelector(".arrow").style.left = "";
} else {
// Calculate percentage X value depending on the tooltip's
// width to avoid hanging arrow out on tooltip width changes
var arrowXpercent = ((100 / tooltipWidth) * arrowX).toFixed(1);
tooltipEl.querySelector(".arrow").style.left = arrowXpercent + "%";
}
tooltipEl.style.opacity = 1;
tooltipEl.style.left = position.left + width + "px";
tooltipEl.style.top = position.top + tooltip.caretY + window.pageYOffset + "px";
tooltipEl.style.fontFamily = tooltip._bodyFontFamily;
tooltipEl.style.fontSize = tooltip.bodyFontSize + "px";
tooltipEl.style.fontStyle = tooltip._bodyFontStyle;
tooltipEl.style.padding = tooltip.yPadding + "px " + tooltip.xPadding + "px";
};
// Functions to update data in page
@@ -160,8 +250,8 @@ function updateQueriesOverTime() {
timeLineChart.data.labels.push(d);
var blocked = data.ads_over_time[1][hour];
var permitted = data.domains_over_time[1][hour] - blocked;
timeLineChart.data.datasets[0].data.push(permitted);
timeLineChart.data.datasets[1].data.push(blocked);
timeLineChart.data.datasets[0].data.push(blocked);
timeLineChart.data.datasets[1].data.push(permitted);
}
}
@@ -572,7 +662,7 @@ function updateTopLists() {
$("#ad-frequency td").parent().remove();
var domaintable = $("#domain-frequency").find("tbody:last");
var adtable = $("#ad-frequency").find("tbody:last");
var url, domain, percentage;
var url, domain, percentage, urlText;
for (domain in data.top_queries) {
if (Object.prototype.hasOwnProperty.call(data.top_queries, domain)) {
// Sanitize domain
@@ -582,7 +672,8 @@ function updateTopLists() {
}
domain = escapeHtml(domain);
url = '<a href="queries.php?domain=' + domain + '">' + domain + "</a>";
urlText = domain === "" ? "." : domain;
url = '<a href="queries.php?domain=' + domain + '">' + urlText + "</a>";
percentage = (data.top_queries[domain] / data.dns_queries_today) * 100;
domaintable.append(
"<tr> <td>" +
@@ -614,7 +705,8 @@ function updateTopLists() {
}
domain = escapeHtml(domain);
url = '<a href="queries.php?domain=' + domain + '">' + domain + "</a>";
urlText = domain === "" ? "." : domain;
url = '<a href="queries.php?domain=' + domain + '">' + urlText + "</a>";
percentage = (data.top_ads[domain] / data.ads_blocked_today) * 100;
adtable.append(
"<tr> <td>" +
@@ -727,6 +819,9 @@ $(document).ready(function () {
// Pull in data via AJAX
updateSummaryData();
var blockedColor = "#999";
var permittedColor = "#00a65a";
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
timeLineChart = new Chart(ctx, {
type: "bar",
@@ -734,22 +829,22 @@ $(document).ready(function () {
labels: [],
datasets: [
{
label: "Permitted DNS Queries",
label: "Blocked DNS Queries",
fill: true,
backgroundColor: "rgba(0, 166, 90,.8)",
borderColor: "rgba(0, 166, 90,.8)",
pointBorderColor: "rgba(0, 166, 90,.8)",
backgroundColor: blockedColor,
borderColor: blockedColor,
pointBorderColor: blockedColor,
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5
},
{
label: "Blocked DNS Queries",
label: "Permitted DNS Queries",
fill: true,
backgroundColor: "rgba(0,192,239,1)",
borderColor: "rgba(0,192,239,1)",
pointBorderColor: "rgba(0,192,239,1)",
backgroundColor: permittedColor,
borderColor: permittedColor,
pointBorderColor: permittedColor,
pointRadius: 1,
pointHoverRadius: 5,
data: [],
@@ -761,6 +856,9 @@ $(document).ready(function () {
tooltips: {
enabled: true,
mode: "x-axis",
itemSort: function(a, b) {
return b.datasetIndex - a.datasetIndex;
},
callbacks: {
title: function (tooltipItem) {
var label = tooltipItem[0].xLabel;
@@ -769,13 +867,13 @@ $(document).ready(function () {
var m = parseInt(time[2], 10) || 0;
var from = padNumber(h) + ":" + padNumber(m - 5) + ":00";
var to = padNumber(h) + ":" + padNumber(m + 4) + ":59";
return "Upstreams from " + from + " to " + to;
return "Queries from " + from + " to " + to;
},
label: function (tooltipItems, data) {
if (tooltipItems.datasetIndex === 1) {
label: function(tooltipItems, data) {
if (tooltipItems.datasetIndex === 0) {
var percentage = 0.0;
var permitted = parseInt(data.datasets[0].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
var permitted = parseInt(data.datasets[1].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[0].data[tooltipItems.index]);
var total = permitted + blocked;
if (total > 0) {
percentage = (100.0 * blocked) / total;
@@ -844,7 +942,8 @@ $(document).ready(function () {
enabled: false,
mode: "x-axis",
custom: customTooltips,
itemSort: function (a, b) {
yAlign: "top",
itemSort: function(a, b) {
return b.yLabel - a.yLabel;
},
callbacks: {
@@ -888,7 +987,10 @@ $(document).ready(function () {
}
]
},
maintainAspectRatio: true
maintainAspectRatio: false,
hover: {
animationDuration: 0
}
}
});
@@ -998,3 +1100,8 @@ $(document).ready(function () {
updateForwardDestinationsPie();
}
});
//destroy all chartjs customTooltips on window resize
window.addEventListener("resize", function() {
$(".chartjs-tooltip").remove();
});
+11 -3
View File
@@ -276,7 +276,8 @@ $(document).ready(function () {
this.style.color = "";
}
);
$("td:eq(4)", row).click(function () {
$("td:eq(4)", row).off(); // Release any possible previous onClick event handlers
$("td:eq(4)", row).click(function() {
var new_tab = window.open("groups-domains.php?domainid=" + data[9], "_blank");
if (new_tab) {
new_tab.focus();
@@ -286,11 +287,18 @@ $(document).ready(function () {
$("td:eq(4)", row).addClass("pointer");
}
// Add domain in CNAME chain causing the query to have been blocked
// Substitute domain by "." if empty
var domain = data[2];
var CNAME_domain = data[8];
if (domain.length === 0) {
domain = ".";
}
if (isCNAME) {
var CNAME_domain = data[8];
// Add domain in CNAME chain causing the query to have been blocked
$("td:eq(2)", row).text(domain + "\n(blocked " + CNAME_domain + ")");
} else {
$("td:eq(2)", row).text(domain);
}
// Check for existence of sixth column and display only if not Pi-holed
+81 -19
View File
@@ -54,7 +54,9 @@ if ($_POST['action'] == 'get_groups') {
} elseif ($_POST['action'] == 'add_group') {
// Add new group
try {
$names = explode(' ', $_POST['name']);
$names = explode(' ', trim($_POST['name']));
$total = count($names);
$added = 0;
$stmt = $db->prepare('INSERT INTO "group" (name,description) VALUES (:name,:desc)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
@@ -66,12 +68,15 @@ if ($_POST['action'] == 'get_groups') {
foreach ($names as $name) {
if (!$stmt->bindValue(':name', $name, SQLITE3_TEXT)) {
throw new Exception('While binding name: ' . $db->lastErrorMsg());
throw new Exception('While binding name: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " groups");
}
if (!$stmt->execute()) {
throw new Exception('While executing: ' . $db->lastErrorMsg());
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " groups");
}
$added++;
}
$reload = true;
@@ -234,7 +239,9 @@ if ($_POST['action'] == 'get_groups') {
} elseif ($_POST['action'] == 'add_client') {
// Add new client
try {
$ips = explode(' ', $_POST['ip']);
$ips = explode(' ', trim($_POST['ip']));
$total = count($ips);
$added = 0;
$stmt = $db->prepare('INSERT INTO client (ip,comment) VALUES (:ip,:comment)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
@@ -251,12 +258,15 @@ if ($_POST['action'] == 'get_groups') {
$comment = null;
}
if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
throw new Exception('While binding comment: ' . $db->lastErrorMsg());
throw new Exception('While binding comment: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " clients");
}
if (!$stmt->execute()) {
throw new Exception('While executing: ' . $db->lastErrorMsg());
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " clients");
}
$added++;
}
$reload = true;
@@ -391,11 +401,31 @@ if ($_POST['action'] == 'get_groups') {
if (extension_loaded("intl") &&
($res['type'] === ListType::whitelist ||
$res['type'] === ListType::blacklist) ) {
$utf8_domain = idn_to_utf8($res['domain']);
// Try to convert possible IDNA domain to Unicode, we try the UTS #46 standard first
// as this is the new default, see https://sourceforge.net/p/icu/mailman/message/32980778/
// We know that this fails for some Google domains violating the standard
// see https://github.com/pi-hole/AdminLTE/issues/1223
$utf8_domain = false;
if (defined("INTL_IDNA_VARIANT_UTS46")) {
// We have to use the option IDNA_NONTRANSITIONAL_TO_ASCII here
// to ensure sparkasse-gießen.de is not converted into
// sparkass-giessen.de but into xn--sparkasse-gieen-2ib.de
// as mandated by the UTS #46 standard
$utf8_domain = idn_to_utf8($res['domain'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
}
// If conversion failed, try with the (deprecated!) IDNA 2003 variant
// We have to check for its existance as support of this variant is
// scheduled for removal with PHP 8.0
// see https://wiki.php.net/rfc/deprecate-and-remove-intl_idna_variant_2003
if ($utf8_domain === false && defined("INTL_IDNA_VARIANT_2003")) {
$utf8_domain = idn_to_utf8($res['domain'], IDNA_DEFAULT, INTL_IDNA_VARIANT_2003);
}
// Convert domain name to international form
// if applicable and extension is available
if($res['domain'] !== $utf8_domain)
{
if ($utf8_domain !== false && $res['domain'] !== $utf8_domain) {
$res['domain'] = $utf8_domain.' ('.$res['domain'].')';
}
}
@@ -410,7 +440,9 @@ if ($_POST['action'] == 'get_groups') {
} elseif ($_POST['action'] == 'add_domain') {
// Add new domain
try {
$domains = explode(' ', $_POST['domain']);
$domains = explode(' ', trim($_POST['domain']));
$total = count($domains);
$added = 0;
$stmt = $db->prepare('INSERT INTO domainlist (domain,type,comment) VALUES (:domain,:type,:comment)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
@@ -427,8 +459,22 @@ if ($_POST['action'] == 'get_groups') {
}
foreach ($domains as $domain) {
$input = $domain;
// Convert domain name to IDNA ASCII form for international domains
$domain = idn_to_ascii($domain);
if (extension_loaded("intl")) {
// Be prepared that this may fail and see our comments above
// (search for "idn_to_utf8)
$idn_domain = false;
if (defined("INTL_IDNA_VARIANT_UTS46")) {
$idn_domain = idn_to_ascii($domain, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
}
if ($idn_domain === false && defined("INTL_IDNA_VARIANT_2003")) {
$idn_domain = idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_2003);
}
if($idn_domain !== false) {
$domain = $idn_domain;
}
}
if(strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
{
@@ -442,17 +488,27 @@ if ($_POST['action'] == 'get_groups') {
$domain = strtolower($domain);
if(filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false)
{
throw new Exception('Domain ' . htmlentities(utf8_encode($domain)) . 'is not a valid domain.');
// This is the case when idn_to_ascii() modified the string
if($input !== $domain && strlen($domain) > 0)
$errormsg = 'Domain ' . htmlentities($input) . ' (converted to "' . htmlentities(utf8_encode($domain)) . '") is not a valid domain.';
elseif($input !== $domain)
$errormsg = 'Domain ' . htmlentities($input) . ' is not a valid domain.';
else
$errormsg = 'Domain ' . htmlentities(utf8_encode($domain)) . ' is not a valid domain.';
throw new Exception($errormsg . '<br>Added ' . $added . " out of ". $total . " domains");
}
}
if (!$stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain: ' . $db->lastErrorMsg());
throw new Exception('While binding domain: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
if (!$stmt->execute()) {
throw new Exception('While executing: ' . $db->lastErrorMsg());
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
$added++;
}
$reload = true;
@@ -604,7 +660,9 @@ if ($_POST['action'] == 'get_groups') {
} elseif ($_POST['action'] == 'add_adlist') {
// Add new adlist
try {
$addresses = explode(' ', $_POST['address']);
$addresses = explode(' ', trim($_POST['address']));
$total = count($addresses);
$added = 0;
$stmt = $db->prepare('INSERT INTO adlist (address,comment) VALUES (:address,:comment)');
if (!$stmt) {
@@ -616,17 +674,21 @@ if ($_POST['action'] == 'get_groups') {
}
foreach ($addresses as $address) {
if(preg_match("/[^a-zA-Z0-9:\/?&%=~._-]/", $address) !== 0) {
throw new Exception('Invalid adlist URL');
if(preg_match("/[^a-zA-Z0-9:\/?&%=~._()-]/", $address) !== 0) {
throw new Exception('<strong>Invalid adlist URL ' . htmlentities($address) . '</strong><br>'.
'Added ' . $added . " out of ". $total . " adlists");
}
if (!$stmt->bindValue(':address', $address, SQLITE3_TEXT)) {
throw new Exception('While binding address: ' . $db->lastErrorMsg());
throw new Exception('While binding address: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " adlists");
}
if (!$stmt->execute()) {
throw new Exception('While executing: ' . $db->lastErrorMsg());
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " adlists");
}
$added++;
}
$reload = true;
+8 -8
View File
@@ -215,8 +215,8 @@
<script src="scripts/vendor/bootstrap-notify.min.js"></script>
<?php if(in_array($scriptname, array("groups.php", "groups-clients.php", "groups-domains.php", "groups-adlists.php"))){ ?>
<script src="style/vendor/bootstrap/js/bootstrap-multiselect.js"></script>
<link rel="stylesheet" href="style/vendor/bootstrap/css/bootstrap-multiselect.css">
<script src="style/vendor/bootstrap/js/bootstrap-select.min.js"></script>
<link rel="stylesheet" href="style/vendor/bootstrap/css/bootstrap-select.min.css">
<script src="style/vendor/bootstrap/js/bootstrap-toggle.min.js"></script>
<link rel="stylesheet" href="style/vendor/bootstrap/css/bootstrap-toggle.min.css">
<script src="scripts/vendor/moment.min.js"></script>
@@ -318,7 +318,7 @@ if($auth) {
*/ ?>
<!-- PayPal -->
<div class="text-center">
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=3J2L3Z4DHW9UY" rel="noopener" target="_blank" style="background:none">
<a href="https://pi-hole.net/donate/" rel="noopener" target="_blank" style="background:none">
<img src="img/donate.gif" alt="Donate">
</a>
</div>
@@ -623,10 +623,10 @@ if($auth) {
<i class="fa fa-cogs"></i> <span>Settings</span>
</a>
</li>
<!-- Custom DNS -->
<li<?php if($scriptname === "custom_dns.php"){ ?> class="active"<?php } ?>>
<a href="custom_dns.php">
<i class="fa fa-address-book"></i> <span>Custom DNS</span>
<!-- Local DNS Records -->
<li<?php if($scriptname === "dns_records.php"){ ?> class="active"<?php } ?>>
<a href="dns_records.php">
<i class="fa fa-address-book"></i> <span>Local DNS Records</span>
</a>
</li>
<!-- Logout -->
@@ -652,7 +652,7 @@ if($auth) {
<?php } ?>
<!-- Donate -->
<li>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY" rel="noopener" target="_blank">
<a href="https://pi-hole.net/donate/" rel="noopener" target="_blank">
<i class="fa-paypal-icon fab fa-paypal"></i> <span>Donate</span>
</a>
</li>
+6 -12
View File
@@ -79,16 +79,10 @@ function readStaticLeasesFile($origin_file="/etc/dnsmasq.d/04-pihole-static-dhcp
{
global $dhcp_static_leases;
$dhcp_static_leases = array();
try
{
$dhcpstatic = @fopen($origin_file, 'r');
}
catch(Exception $e)
{
echo "Warning: Failed to read ".$origin_file.", this is not an error";
if(!file_exists($origin_file) || !is_readable($origin_file))
return false;
}
$dhcpstatic = @fopen($origin_file, 'r');
if(!is_resource($dhcpstatic))
return false;
@@ -154,16 +148,16 @@ function readDNSserversList()
$line = explode(';', $line);
$name = $line[0];
$values = [];
if (!empty($line[1])) {
if (!empty($line[1]) && validIP($line[1])) {
$values["v4_1"] = $line[1];
}
if (!empty($line[2])) {
if (!empty($line[2]) && validIP($line[2])) {
$values["v4_2"] = $line[2];
}
if (!empty($line[3])) {
if (!empty($line[3]) && validIP($line[3])) {
$values["v6_1"] = $line[3];
}
if (!empty($line[4])) {
if (!empty($line[4]) && validIP($line[4])) {
$values["v6_2"] = $line[4];
}
$list[$name] = $values;
+9
View File
@@ -688,6 +688,8 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<input type="text" name="custom1val" class="form-control" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off"
<?php if (isset($custom1)){ ?>value="<?php echo $custom1; ?>"<?php } ?>>
</div>
</div>
<div class="form-group">
<label>Custom 2 (IPv4)</label>
<div class="input-group">
<div class="input-group-addon">
@@ -710,6 +712,8 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<input type="text" name="custom3val" class="form-control" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off"
<?php if (isset($custom3)){ ?>value="<?php echo $custom3; ?>"<?php } ?>>
</div>
</div>
<div class="form-group">
<label>Custom 4 (IPv6)</label>
<div class="input-group">
<div class="input-group-addon">
@@ -969,10 +973,15 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
</div>
<h4>Administrator Email Address</h4>
<div class="form-group">
<<<<<<< HEAD
<div class="input-group">
<input type="email" class="form-control" name="adminemail"
value="<?php echo htmlspecialchars($adminemail); ?>">
</div>
=======
<input type="text" class="form-control" name="adminemail"
value="<?php echo htmlspecialchars($adminemail); ?>">
>>>>>>> master
</div>
<input type="hidden" name="field" value="webUI">
<input type="hidden" name="token" value="<?php echo $token ?>">
+66 -15
View File
@@ -157,27 +157,72 @@
user-select: none
}
#chartjs-tooltip {
opacity: 1;
.chartjs-tooltip {
opacity: 0;
position: absolute;
background: rgba(0, 0, 0, .7);
color: #fff;
border-radius: 3px;
-webkit-transition: all .1s ease;
-o-transition: all .1s ease;
transition: all .1s ease;
pointer-events: none;
-webkit-transform: translate(-50%, 0);
-ms-transform: translate(-50%, 0);
-o-transform: translate(-50%, 0);
transform: translate(-50%, 0);
color: #fff;
background-color: rgba(0,0,0,0.8);
max-width: 95%;
z-index: 900;
}
.chartjs-tooltip th {
padding-bottom: 3px;
}
.chartjs-tooltip,
.chartjs-tooltip .arrow {
-webkit-transition: all .2s cubic-bezier(0.165, 0.84, 0.44, 1);
transition: all .2s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.chartjs-tooltip .arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.chartjs-tooltip.bottom .arrow {
top: 100%;
left: 50%;
margin: 0 -5px -5px;
border-width: 5px 5px 0;
border-top-color: rgba(0, 0, 0, .8);
}
.chartjs-tooltip.top .arrow {
top: 0;
left: 50%;
border-width: 0 5px 5px;
margin: -5px -5px 0;
border-bottom-color: rgba(0, 0, 0, .8);
}
.chartjs-tooltip.right.center .arrow {
top: 50%;
left: 100%;
margin: -5px -5px -5px 0;
border-width: 5px 0 5px 5px;
border-left-color: rgba(0, 0, 0, .8);
}
.chartjs-tooltip.left.center .arrow {
top: 50%;
left: 0;
margin: -5px 0 -5px -5px;
border-width: 5px 5px 5px 0;
border-right-color: rgba(0, 0, 0, .8);
}
.chartjs-tooltip-key {
display: inline-block;
width: 20px;
height: 10px;
margin-right: 10px;
width: 12px;
height: 12px;
margin-right: 3px;
vertical-align: text-top;
}
.chart-legend {
@@ -191,6 +236,7 @@
.chart-legend li {
cursor: pointer;
word-break: break-all;
}
.chart-legend li span {
@@ -259,4 +305,9 @@ code.breakall
.pointer {
cursor: pointer;
}
.bootstrap-select.bs-container.align-right {
left: unset !important;
right: 10px;
}
-1
View File
@@ -1 +0,0 @@
span.multiselect-native-select{position:relative}span.multiselect-native-select select{border:0!important;clip:rect(0 0 0 0)!important;height:1px!important;margin:-1px -1px -1px -3px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;left:50%;top:30px}.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .input-group{margin:5px}.multiselect-container>li{padding:0}.multiselect-container>li>a.multiselect-all label{font-weight:700}.multiselect-container>li.multiselect-group label{margin:0;padding:3px 20px 3px 20px;height:100%;font-weight:700}.multiselect-container>li.multiselect-group-clickable label{cursor:pointer}.multiselect-container>li>a{padding:0}.multiselect-container>li>a>label{margin:0;height:100%;cursor:pointer;font-weight:400;padding:3px 20px 3px 40px}.multiselect-container>li>a>label.radio,.multiselect-container>li>a>label.checkbox{margin:0}.multiselect-container>li>a>label>input[type=checkbox]{margin-bottom:5px}.btn-group>.btn-group:nth-child(2)>.multiselect.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.form-inline .multiselect-container label.checkbox,.form-inline .multiselect-container label.radio{padding:3px 20px 3px 40px}.form-inline .multiselect-container li a label.checkbox input[type=checkbox],.form-inline .multiselect-container li a label.radio input[type=radio]{margin-left:-20px;margin-right:0}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long