" +
url +
" " +
data.top_ads[domain] +
@@ -89,8 +88,14 @@ function updateTopLists() {
$("#domain-frequency .overlay").hide();
$("#ad-frequency .overlay").hide();
- // Update top lists data every second
- setTimeout(updateTopLists, 1000);
+ // Update top lists data every ten seconds
+ // Updates are also triggered by button actions
+ // and reset the running timer
+ if (auditTimeout !== null) {
+ window.clearTimeout(auditTimeout);
+ }
+
+ auditTimeout = setTimeout(updateTopLists, 10000);
});
}
@@ -99,10 +104,32 @@ function add(domain, list) {
$.ajax({
url: "scripts/pi-hole/php/add.php",
method: "post",
- data: { domain: domain, list: list, token: token }
+ data: { domain: domain, list: list, token: token },
+ success: function() {
+ updateTopLists();
+ },
+ error: function(jqXHR, exception) {
+ console.log(exception);
+ }
});
}
+function blacklistUrl(url) {
+ // We add to audit last as it will reload the table on success
+ add(url, "black");
+ add(url, "audit");
+}
+
+function whitelistUrl(url) {
+ // We add to audit last as it will reload the table on success
+ add(url, "white");
+ add(url, "audit");
+}
+
+function auditUrl(url) {
+ add(url, "audit");
+}
+
$(document).ready(function() {
// Pull in data via AJAX
updateTopLists();
@@ -110,11 +137,9 @@ $(document).ready(function() {
$("#domain-frequency tbody").on("click", "button", function() {
var url = $(this)
.parents("tr")[0]
- .textContent.split(" ")[0];
+ .textContent.split(" ")[0];
if ($(this).context.textContent === " Blacklist") {
- add(url, "audit");
- add(url, "black");
- $("#gravityBtn").prop("disabled", false);
+ blacklistUrl(url);
} else {
auditUrl(url);
}
@@ -123,36 +148,11 @@ $(document).ready(function() {
$("#ad-frequency tbody").on("click", "button", function() {
var url = $(this)
.parents("tr")[0]
- .textContent.split(" ")[0]
- .split(" ")[0];
+ .textContent.split(" ")[0];
if ($(this).context.textContent === " Whitelist") {
- add(url, "audit");
- add(url, "white");
- $("#gravityBtn").prop("disabled", false);
+ whitelistUrl(url);
} else {
auditUrl(url);
}
});
});
-
-function auditUrl(url) {
- if (auditList.indexOf(url) > -1) {
- return;
- }
-
- if (auditTimeout) {
- clearTimeout(auditTimeout);
- }
-
- auditList.push(url);
- // wait 3 seconds to see if more domains need auditing
- // and batch them all into a single request
- auditTimeout = setTimeout(function() {
- add(auditList.join(" "), "audit");
- auditList = [];
- }, 3000);
-}
-
-$("#gravityBtn").on("click", function() {
- window.location.replace("gravity.php?go");
-});
diff --git a/scripts/pi-hole/js/db_queries.js b/scripts/pi-hole/js/db_queries.js
index d7f4fe12..f8c22119 100644
--- a/scripts/pi-hole/js/db_queries.js
+++ b/scripts/pi-hole/js/db_queries.js
@@ -195,6 +195,18 @@ function getQueryTypes() {
queryType.push([6, 7, 8]);
}
+ if ($("#type_gravity_CNAME").prop("checked")) {
+ queryType.push(9);
+ }
+
+ if ($("#type_regex_CNAME").prop("checked")) {
+ queryType.push(10);
+ }
+
+ if ($("#type_blacklist_CNAME").prop("checked")) {
+ queryType.push(11);
+ }
+
return queryType.join(",");
}
@@ -282,13 +294,13 @@ $(document).ready(function() {
break;
case 4:
color = "red";
- fieldtext = "Blocked (regex/wildcard)";
+ fieldtext = "Blocked (regex blacklist)";
buttontext =
' Whitelist ';
break;
case 5:
color = "red";
- fieldtext = "Blocked (blacklist)";
+ fieldtext = "Blocked (exact blacklist)";
buttontext =
' Whitelist ';
break;
@@ -307,6 +319,24 @@ $(document).ready(function() {
fieldtext = "Blocked (external, NXRA)";
buttontext = "";
break;
+ case 9:
+ color = "red";
+ fieldtext = "Blocked (gravity, CNAME)";
+ buttontext =
+ ' Whitelist ';
+ break;
+ case 10:
+ color = "red";
+ fieldtext = "Blocked (regex blacklist, CNAME)";
+ buttontext =
+ ' Whitelist ';
+ break;
+ case 11:
+ color = "red";
+ fieldtext = "Blocked (exact blacklist, CNAME)";
+ buttontext =
+ ' Whitelist ';
+ break;
default:
color = "black";
fieldtext = "Unknown";
diff --git a/scripts/pi-hole/js/groups-adlists.js b/scripts/pi-hole/js/groups-adlists.js
index 9a0f98f9..ee546897 100644
--- a/scripts/pi-hole/js/groups-adlists.js
+++ b/scripts/pi-hole/js/groups-adlists.js
@@ -5,71 +5,11 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global moment:false */
+/* global utils:false */
var table;
var groups = [];
-var token = $("#token").text();
-var info = null;
-
-function showAlert(type, icon, title, message) {
- var opts = {};
- title = " " + title + " ";
- 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);
- }
-
- break;
- case "warning":
- opts = {
- type: "warning",
- icon: "fas fa-exclamation-triangle",
- title: title,
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- case "error":
- opts = {
- type: "danger",
- icon: "fas fa-times",
- title: " Error, something went wrong! ",
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- default:
- }
-}
+var token = $("#token").html();
function get_groups() {
$.post(
@@ -83,19 +23,11 @@ function get_groups() {
);
}
-function datetime(date) {
- return moment.unix(Math.floor(date)).format("Y-MM-DD HH:mm:ss z");
-}
-
$(document).ready(function() {
$("#btnAdd").on("click", addAdlist);
get_groups();
- $("#select").on("change", function() {
- $("#ip-custom").val("");
- $("#ip-custom").prop("disabled", $("#select option:selected").val() !== "custom");
- });
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
input.setAttribute("autocomplete", "off");
@@ -121,46 +53,56 @@ function initTable() {
{ data: null, width: "80px", orderable: false }
],
drawCallback: function() {
- $(".deleteAdlist").on("click", deleteAdlist);
+ $('button[id^="deleteAdlist_"]').on("click", deleteAdlist);
},
rowCallback: function(row, data) {
+ $(row).attr("data-id", data.id);
var tooltip =
"Added: " +
- datetime(data.date_added) +
+ utils.datetime(data.date_added) +
"\nLast modified: " +
- datetime(data.date_modified) +
+ utils.datetime(data.date_modified) +
"\nDatabase ID: " +
data.id;
$("td:eq(0)", row).html(
- '' + data.address + ""
+ '' +
+ data.address +
+ ""
);
var disabled = data.enabled === 0;
$("td:eq(1)", row).html(
- ' "
+ ' "
);
- var status = $("#status", row);
- status.bootstrapToggle({
+ var statusEl = $("#status_" + data.id, row);
+ statusEl.bootstrapToggle({
on: "Enabled",
off: "Disabled",
size: "small",
onstyle: "success",
width: "80px"
});
- status.on("change", editAdlist);
+ statusEl.on("change", editAdlist);
- $("td:eq(2)", row).html(
- ' '
- );
- var comment = $("#comment", row);
- comment.val(data.comment);
- comment.on("change", editAdlist);
+ $("td:eq(2)", row).html('');
+ var commentEl = $("#comment_" + data.id, row);
+ commentEl.val(data.comment);
+ commentEl.on("change", editAdlist);
$("td:eq(3)", row).empty();
- $("td:eq(3)", row).append(' ');
- var sel = $("#multiselect", row);
+ $("td:eq(3)", row).append(
+ '' +
+ '
'
+ );
+ var selectEl = $("#multiselect_" + data.id, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
@@ -168,7 +110,7 @@ function initTable() {
extra = " (disabled)";
}
- sel.append(
+ selectEl.append(
$(" ")
.val(groups[i].id)
.text(groups[i].name + extra)
@@ -176,19 +118,51 @@ function initTable() {
}
// Select assigned groups
- sel.val(data.groups);
+ selectEl.val(data.groups);
// Initialize multiselect
- sel.multiselect({ includeSelectAllOption: true });
- sel.on("change", editAdlist);
+ selectEl.multiselect({
+ includeSelectAllOption: true,
+ buttonContainer: '
',
+ 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");
+ }
+
+ if (bottom > 200) {
+ el.removeClass("dropup");
+ }
+
+ 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 button =
- '' +
' ' +
" ";
$("td:eq(4)", row).html(button);
},
+ dom:
+ "<'row'<'col-sm-4'l><'col-sm-8'f>>" +
+ "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
+ "<'row'<'col-sm-5'i><'col-sm-7'p>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
@@ -236,10 +210,11 @@ function addAdlist() {
var address = $("#new_address").val();
var comment = $("#new_comment").val();
- showAlert("info", "", "Adding adlist...", address);
+ utils.disableAll();
+ utils.showAlert("info", "", "Adding adlist...", address);
if (address.length === 0) {
- showAlert("warning", "", "Warning", "Please specify an adlist address");
+ utils.showAlert("warning", "", "Warning", "Please specify an adlist address");
return;
}
@@ -254,17 +229,19 @@ function addAdlist() {
token: token
},
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-plus", "Successfully added adlist", address);
+ utils.showAlert("success", "fas fa-plus", "Successfully added adlist", address);
$("#new_address").val("");
$("#new_comment").val("");
table.ajax.reload();
} else {
- showAlert("error", "", "Error while adding new adlist: ", response.message);
+ utils.showAlert("error", "", "Error while adding new adlist: ", response.message);
}
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while adding new adlist: ", jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while adding new adlist: ", jqXHR.responseText);
console.log(exception);
}
});
@@ -273,29 +250,40 @@ function addAdlist() {
function editAdlist() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
- var id = tr.find("#id").val();
- var status = tr.find("#status").is(":checked") ? 1 : 0;
- var comment = tr.find("#comment").val();
- var groups = tr.find("#multiselect").val();
- var address = tr.find("#address").text();
+ var id = tr.attr("data-id");
+ var status = tr.find("#status_" + id).is(":checked") ? 1 : 0;
+ var comment = tr.find("#comment_" + id).val();
+ var groups = tr.find("#multiselect_" + id).val();
+ var address = tr.find("#address_" + id).text();
var done = "edited";
var not_done = "editing";
- if (elem === "status" && status === 1) {
- done = "enabled";
- not_done = "enabling";
- } else if (elem === "status" && status === 0) {
- done = "disabled";
- not_done = "disabling";
- } else if (elem === "comment") {
- done = "edited comment of";
- not_done = "editing comment of";
- } else if (elem === "multiselect") {
- done = "edited groups of";
- not_done = "editing groups of";
+ switch (elem) {
+ case "status_" + id:
+ if (status === 0) {
+ done = "disabled";
+ not_done = "disabling";
+ } else if (status === 1) {
+ done = "enabled";
+ not_done = "enabling";
+ }
+
+ break;
+ case "comment_" + id:
+ done = "edited comment of";
+ not_done = "editing comment of";
+ break;
+ case "multiselect_" + id:
+ done = "edited groups of";
+ not_done = "editing groups of";
+ break;
+ default:
+ alert("bad element or invalid data-id!");
+ return;
}
- showAlert("info", "", "Editing adlist...", address);
+ utils.disableAll();
+ utils.showAlert("info", "", "Editing adlist...", address);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
@@ -310,10 +298,16 @@ function editAdlist() {
token: token
},
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " adlist ", address);
+ utils.showAlert(
+ "success",
+ "fas fa-pencil-alt",
+ "Successfully " + done + " adlist ",
+ address
+ );
} else {
- showAlert(
+ utils.showAlert(
"error",
"",
"Error while " + not_done + " adlist with ID " + id,
@@ -322,7 +316,8 @@ function editAdlist() {
}
},
error: function(jqXHR, exception) {
- showAlert(
+ utils.enableAll();
+ utils.showAlert(
"error",
"",
"Error while " + not_done + " adlist with ID " + id,
@@ -334,27 +329,32 @@ function editAdlist() {
}
function deleteAdlist() {
- var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
- var address = tr.find("#address").text();
+ var id = tr.attr("data-id");
+ var address = tr.find("#address_" + id).text();
- showAlert("info", "", "Deleting adlist...", address);
+ utils.disableAll();
+ utils.showAlert("info", "", "Deleting adlist...", address);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_adlist", id: id, token: token },
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "far fa-trash-alt", "Successfully deleted adlist ", address);
+ utils.showAlert("success", "far fa-trash-alt", "Successfully deleted adlist ", address);
table
.row(tr)
.remove()
.draw(false);
- } else showAlert("error", "", "Error while deleting adlist with ID " + id, response.message);
+ } else {
+ utils.showAlert("error", "", "Error while deleting adlist with ID " + id, response.message);
+ }
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while deleting adlist with ID " + id, jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while deleting adlist with ID " + id, jqXHR.responseText);
console.log(exception);
}
});
diff --git a/scripts/pi-hole/js/groups-clients.js b/scripts/pi-hole/js/groups-clients.js
index ef0200a8..74f10761 100644
--- a/scripts/pi-hole/js/groups-clients.js
+++ b/scripts/pi-hole/js/groups-clients.js
@@ -5,69 +5,11 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
+/* global utils:false */
+
var table;
var groups = [];
-var token = $("#token").text();
-var info = null;
-
-function showAlert(type, icon, title, message) {
- var opts = {};
- title = " " + title + " ";
- 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);
- }
-
- break;
- case "warning":
- opts = {
- type: "warning",
- icon: "fas fa-exclamation-triangle",
- title: title,
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- case "error":
- opts = {
- type: "danger",
- icon: "fas fa-times",
- title: " Error, something went wrong! ",
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- default:
- }
-}
+var token = $("#token").html();
function reload_client_suggestions() {
$.post(
@@ -75,6 +17,7 @@ function reload_client_suggestions() {
{ action: "get_unconfigured_clients", token: token },
function(data) {
var sel = $("#select");
+ var customWasSelected = sel.val() === "custom";
sel.empty();
for (var key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) {
@@ -96,8 +39,11 @@ function reload_client_suggestions() {
sel.append(
$(" ")
.val("custom")
- .text("Custom, specified on the right")
+ .text("Custom, specified below...")
);
+ if (customWasSelected) {
+ sel.val("custom");
+ }
},
"json"
);
@@ -144,29 +90,56 @@ function initTable() {
columns: [
{ data: "id", visible: false },
{ data: "ip" },
+ { data: "comment" },
{ data: "groups", searchable: false },
{ data: "name", width: "80px", orderable: false }
],
drawCallback: function() {
- $(".deleteClient").on("click", deleteClient);
+ $('button[id^="deleteClient_"]').on("click", deleteClient);
},
rowCallback: function(row, data) {
- var tooltip = "Database ID: " + data.id;
+ $(row).attr("data-id", data.id);
+ var tooltip =
+ "Added: " +
+ utils.datetime(data.date_added) +
+ "\nLast modified: " +
+ utils.datetime(data.date_modified) +
+ "\nDatabase ID: " +
+ data.id;
var ip_name =
- '' +
- data.ip +
- ' ';
+ '" title="' +
+ tooltip +
+ '" class="breakall">' +
+ data.ip +
+ "";
if (data.name !== null && data.name.length > 0)
- ip_name += '' + data.name + "";
+ ip_name +=
+ '' +
+ data.name +
+ "";
$("td:eq(0)", row).html(ip_name);
- $("td:eq(1)", row).empty();
- $("td:eq(1)", row).append(' ');
- var sel = $("#multiselect", row);
+ $("td:eq(1)", row).html('');
+ var commentEl = $("#comment_" + data.id, row);
+ commentEl.val(data.comment);
+ commentEl.on("change", editClient);
+
+ $("td:eq(2)", row).empty();
+ $("td:eq(2)", row).append(
+ '' +
+ '
'
+ );
+ var selectEl = $("#multiselect_" + data.id, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
@@ -174,7 +147,7 @@ function initTable() {
extra = " (disabled)";
}
- sel.append(
+ selectEl.append(
$(" ")
.val(groups[i].id)
.text(groups[i].name + extra)
@@ -182,19 +155,51 @@ function initTable() {
}
// Select assigned groups
- sel.val(data.groups);
+ selectEl.val(data.groups);
// Initialize multiselect
- sel.multiselect({ includeSelectAllOption: true });
- sel.on("change", editClient);
+ selectEl.multiselect({
+ includeSelectAllOption: true,
+ buttonContainer: '
',
+ 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");
+ }
+
+ if (bottom > 200) {
+ el.removeClass("dropup");
+ }
+
+ 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 button =
- '' +
' ' +
" ";
- $("td:eq(2)", row).html(button);
+ $("td:eq(3)", row).html(button);
},
+ dom:
+ "<'row'<'col-sm-4'l><'col-sm-8'f>>" +
+ "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
+ "<'row'<'col-sm-5'i><'col-sm-7'p>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
@@ -240,14 +245,32 @@ function initTable() {
function addClient() {
var ip = $("#select").val();
+ var comment = $("#new_comment").val();
if (ip === "custom") {
ip = $("#ip-custom").val();
}
- showAlert("info", "", "Adding client...", ip);
+ utils.disableAll();
+ utils.showAlert("info", "", "Adding client...", ip);
if (ip.length === 0) {
- showAlert("warning", "", "Warning", "Please specify a client IP address");
+ utils.enableAll();
+ utils.showAlert("warning", "", "Warning", "Please specify a client IP address");
+ return;
+ }
+
+ // Validate IP address (may contain CIDR details)
+ var ipv6format = ip.includes(":");
+
+ if (!ipv6format && !utils.validateIPv4CIDR(ip)) {
+ utils.enableAll();
+ utils.showAlert("warning", "", "Warning", "Invalid IPv4 address!");
+ return;
+ }
+
+ if (ipv6format && !utils.validateIPv6CIDR(ip)) {
+ utils.enableAll();
+ utils.showAlert("warning", "", "Warning", "Invalid IPv6 address!");
return;
}
@@ -255,18 +278,20 @@ function addClient() {
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
- data: { action: "add_client", ip: ip, token: token },
+ data: { action: "add_client", ip: ip, comment: comment, token: token },
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-plus", "Successfully added client", ip);
+ utils.showAlert("success", "fas fa-plus", "Successfully added client", ip);
reload_client_suggestions();
table.ajax.reload();
} else {
- showAlert("error", "", "Error while adding new client", response.message);
+ utils.showAlert("error", "", "Error while adding new client", response.message);
}
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while adding new client", jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while adding new client", jqXHR.responseText);
console.log(exception);
}
});
@@ -275,16 +300,26 @@ function addClient() {
function editClient() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
- var id = tr.find("#id").val();
- var groups = tr.find("#multiselect").val();
- var ip = tr.find("#ip").text();
- var name = tr.find("#name").text();
+ var id = tr.attr("data-id");
+ var groups = tr.find("#multiselect_" + id).val();
+ var ip = tr.find("#ip_" + id).text();
+ var name = tr.find("#name_" + id).text();
+ var comment = tr.find("#comment_" + id).val();
var done = "edited";
var not_done = "editing";
- if (elem === "multiselect") {
- done = "edited groups of";
- not_done = "editing groups of";
+ switch (elem) {
+ case "multiselect_" + id:
+ done = "edited groups of";
+ not_done = "editing groups of";
+ break;
+ case "comment_" + id:
+ done = "edited comment of";
+ not_done = "editing comment of";
+ break;
+ default:
+ alert("bad element or invalid data-id!");
+ return;
}
var ip_name = ip;
@@ -292,21 +327,34 @@ function editClient() {
ip_name += " (" + name + ")";
}
- showAlert("info", "", "Editing client...", ip_name);
+ utils.disableAll();
+ utils.showAlert("info", "", "Editing client...", ip_name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
- data: { action: "edit_client", id: id, groups: groups, token: token },
+ data: {
+ action: "edit_client",
+ id: id,
+ groups: groups,
+ token: token,
+ comment: comment
+ },
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-plus", "Successfully " + done + " client", ip_name);
+ utils.showAlert("success", "fas fa-plus", "Successfully " + done + " client", ip_name);
} else {
- showAlert("error", "Error while " + not_done + " client with ID " + id, response.message);
+ utils.showAlert(
+ "error",
+ "Error while " + not_done + " client with ID " + id,
+ response.message
+ );
}
},
error: function(jqXHR, exception) {
- showAlert(
+ utils.enableAll();
+ utils.showAlert(
"error",
"",
"Error while " + not_done + " client with ID " + id,
@@ -318,36 +366,39 @@ function editClient() {
}
function deleteClient() {
- var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
- var ip = tr.find("#ip").text();
- var name = tr.find("#name").text();
+ var id = tr.attr("data-id");
+ var ip = tr.find("#ip_" + id).text();
+ var name = tr.find("#name_" + id).text();
var ip_name = ip;
if (name.length > 0) {
ip_name += " (" + name + ")";
}
- showAlert("info", "", "Deleting client...", ip_name);
+ utils.disableAll();
+ utils.showAlert("info", "", "Deleting client...", ip_name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_client", id: id, token: token },
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "far fa-trash-alt", "Successfully deleted client ", ip_name);
+ utils.showAlert("success", "far fa-trash-alt", "Successfully deleted client ", ip_name);
table
.row(tr)
.remove()
.draw(false);
reload_client_suggestions();
} else {
- showAlert("error", "", "Error while deleting client with ID " + id, response.message);
+ utils.showAlert("error", "", "Error while deleting client with ID " + id, response.message);
}
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while deleting client with ID " + id, jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while deleting client with ID " + id, jqXHR.responseText);
console.log(exception);
}
});
diff --git a/scripts/pi-hole/js/groups-common.js b/scripts/pi-hole/js/groups-common.js
new file mode 100644
index 00000000..e50d0af7
--- /dev/null
+++ b/scripts/pi-hole/js/groups-common.js
@@ -0,0 +1,141 @@
+/* Pi-hole: A black hole for Internet advertisements
+ * (c) 2020 Pi-hole, LLC (https://pi-hole.net)
+ * Network-wide ad blocking via your own hardware.
+ *
+ * This file is copyright under the latest version of the EUPL.
+ * Please see LICENSE file for your rights under this license. */
+
+/* global moment:false */
+
+var info = null;
+function showAlert(type, icon, title, message) {
+ var opts = {};
+ title = " " + title + " ";
+ switch (type) {
+ case "info":
+ opts = {
+ type: "info",
+ icon: "glyphicon glyphicon-time",
+ 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);
+ }
+
+ break;
+ case "warning":
+ opts = {
+ type: "warning",
+ icon: "glyphicon glyphicon-warning-sign",
+ title: title,
+ message: message
+ };
+ if (info) {
+ info.update(opts);
+ } else {
+ $.notify(opts);
+ }
+
+ break;
+ case "error":
+ opts = {
+ type: "danger",
+ icon: "glyphicon glyphicon-remove",
+ title: " Error, something went wrong! ",
+ message: message
+ };
+ if (info) {
+ info.update(opts);
+ } else {
+ $.notify(opts);
+ }
+
+ break;
+ default:
+ }
+}
+
+function datetime(date) {
+ return moment.unix(Math.floor(date)).format("Y-MM-DD HH:mm:ss z");
+}
+
+function disableAll() {
+ $("input").attr("disabled", true);
+ $("select").attr("disabled", true);
+ $("button").attr("disabled", true);
+ $("textarea").attr("disabled", true);
+}
+
+function enableAll() {
+ $("input").attr("disabled", false);
+ $("select").attr("disabled", false);
+ $("button").attr("disabled", false);
+ $("textarea").attr("disabled", false);
+
+ // Enable custom input field only if applicable
+ var ip = $("#select") ? $("#select").val() : null;
+ if (ip !== null && ip !== "custom") {
+ ip = $("#ip-custom").attr("disabled", true);
+ }
+}
+
+// Pi-hole IPv4/CIDR validator by DL6ER, see regexr.com/50csh
+function validateIPv4CIDR(ip) {
+ // One IPv4 element is 8bit: 0 - 256
+ var ipv4elem = "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)";
+ // CIDR for IPv4 is 1 - 32 bit
+ var v4cidr = "(\\/([1-9]|[1-2][0-9]|3[0-2])){0,1}";
+ var ipv4validator = new RegExp(
+ "^" + ipv4elem + "\\." + ipv4elem + "\\." + ipv4elem + "\\." + ipv4elem + v4cidr + "$"
+ );
+ return ipv4validator.test(ip);
+}
+
+// Pi-hole IPv6/CIDR validator by DL6ER, see regexr.com/50csn
+function validateIPv6CIDR(ip) {
+ // One IPv6 element is 16bit: 0000 - FFFF
+ var ipv6elem = "[0-9A-Fa-f]{1,4}";
+ // CIDR for IPv6 is 1- 128 bit
+ var v6cidr = "(\\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}";
+ var ipv6validator = new RegExp(
+ "^(((?:" +
+ ipv6elem +
+ "))((?::" +
+ ipv6elem +
+ "))*::((?:" +
+ ipv6elem +
+ "))((?::" +
+ ipv6elem +
+ "))*|((?:" +
+ ipv6elem +
+ "))((?::" +
+ ipv6elem +
+ ")){7})" +
+ v6cidr +
+ "$"
+ );
+ return ipv6validator.test(ip);
+}
+
+window.utils = (function() {
+ return {
+ showAlert: showAlert,
+ datetime: datetime,
+ disableAll: disableAll,
+ enableAll: enableAll,
+ validateIPv4CIDR: validateIPv4CIDR,
+ validateIPv6CIDR: validateIPv6CIDR
+ };
+})();
diff --git a/scripts/pi-hole/js/groups-domains.js b/scripts/pi-hole/js/groups-domains.js
index 019dbcdd..2d79b4c1 100644
--- a/scripts/pi-hole/js/groups-domains.js
+++ b/scripts/pi-hole/js/groups-domains.js
@@ -5,71 +5,13 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global moment:false */
+/* global utils:false */
var table;
var groups = [];
-var token = $("#token").text();
-var info = null;
-
-function showAlert(type, icon, title, message) {
- var opts = {};
- title = " " + title + " ";
- 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);
- }
-
- break;
- case "warning":
- opts = {
- type: "warning",
- icon: "fas fa-exclamation-triangle",
- title: title,
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- case "error":
- opts = {
- type: "danger",
- icon: "fas fa-times",
- title: " Error, something went wrong! ",
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- default:
- }
-}
+var token = $("#token").html();
+var GETDict = {};
+var showtype = "all";
function get_groups() {
$.post(
@@ -83,18 +25,32 @@ function get_groups() {
);
}
-function datetime(date) {
- return moment.unix(Math.floor(date)).format("Y-MM-DD HH:mm:ss z");
-}
-
$(document).ready(function() {
- $("#btnAdd").on("click", addDomain);
+ window.location.search
+ .substr(1)
+ .split("&")
+ .forEach(function(item) {
+ GETDict[item.split("=")[0]] = item.split("=")[1];
+ });
- get_groups();
+ if ("type" in GETDict && (GETDict.type === "white" || GETDict.type === "black")) {
+ showtype = GETDict.type;
+ }
- $("#select").on("change", function() {
- $("#ip-custom").val("");
- $("#ip-custom").prop("disabled", $("#select option:selected").val() !== "custom");
+ // sync description fields, reset inactive inputs on tab change
+ $('a[data-toggle="tab"]').on("shown.bs.tab", function() {
+ var tabHref = $(this).attr("href");
+ var val;
+ if (tabHref === "#tab_domain") {
+ val = $("#new_regex_comment").val();
+ $("#new_domain_comment").val(val);
+ $("#new_regex").val("");
+ } else if (tabHref === "#tab_regex") {
+ val = $("#new_domain_comment").val();
+ $("#new_regex_comment").val(val);
+ $("#new_domain").val("");
+ $("#wildcard_checkbox").prop("checked", false);
+ }
});
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
@@ -102,13 +58,17 @@ $(document).ready(function() {
input.setAttribute("autocorrect", "off");
input.setAttribute("autocapitalize", "off");
input.setAttribute("spellcheck", false);
+
+ $("#add2black, #add2white").on("click", addDomain);
+
+ get_groups();
});
function initTable() {
table = $("#domainsTable").DataTable({
ajax: {
url: "scripts/pi-hole/php/groups.php",
- data: { action: "get_domains", token: token },
+ data: { action: "get_domains", showtype: showtype, token: token },
type: "POST"
},
order: [[0, "asc"]],
@@ -122,90 +82,163 @@ function initTable() {
{ data: null, width: "80px", orderable: false }
],
drawCallback: function() {
- $(".deleteDomain").on("click", deleteDomain);
+ $('button[id^="deleteDomain_"]').on("click", deleteDomain);
},
rowCallback: function(row, data) {
+ $(row).attr("data-id", data.id);
var tooltip =
"Added: " +
- datetime(data.date_added) +
+ utils.datetime(data.date_added) +
"\nLast modified: " +
- datetime(data.date_modified) +
+ utils.datetime(data.date_modified) +
"\nDatabase ID: " +
data.id;
$("td:eq(0)", row).html(
- '' + data.domain + ""
+ '' +
+ data.domain +
+ ""
);
- $("td:eq(1)", row).html(
- '' +
+ var whitelist_options = "";
+ if (showtype === "all" || showtype === "white") {
+ whitelist_options =
'Exact whitelist " +
- 'Exact blacklist " +
'Regex whitelist " +
+ ">Regex whitelist";
+ }
+
+ var blacklist_options = "";
+ if (showtype === "all" || showtype === "black") {
+ blacklist_options =
+ 'Exact blacklist " +
'Regex blacklist " +
+ ">Regex blacklist";
+ }
+
+ $("td:eq(1)", row).html(
+ '' +
+ whitelist_options +
+ blacklist_options +
" "
);
- $("#type", row).on("change", editDomain);
+ var typeEl = $("#type_" + data.id, row);
+ typeEl.on("change", editDomain);
var disabled = data.enabled === 0;
$("td:eq(2)", row).html(
- ' "
+ ' "
);
- $("#status", row).bootstrapToggle({
+ var statusEl = $("#status_" + data.id, row);
+ statusEl.bootstrapToggle({
on: "Enabled",
off: "Disabled",
size: "small",
onstyle: "success",
width: "80px"
});
- $("#status", row).on("change", editDomain);
+ statusEl.on("change", editDomain);
- $("td:eq(3)", row).html(
- ' '
- );
- $("#comment", row).val(data.comment);
- $("#comment", row).on("change", editDomain);
+ $("td:eq(3)", row).html('');
+ var commentEl = $("#comment_" + data.id, row);
+ commentEl.val(data.comment);
+ commentEl.on("change", editDomain);
- $("td:eq(4)", row).empty();
- $("td:eq(4)", row).append(' ');
- var sel = $("#multiselect", row);
- // Add all known groups
- for (var i = 0; i < groups.length; i++) {
- var extra = "";
- if (!groups[i].enabled) {
- extra = " (disabled)";
+ // Show group assignment field only if in full domain management mode
+ if (table.column(5).visible()) {
+ $("td:eq(4)", row).empty();
+ $("td:eq(4)", row).append(
+ '' +
+ '
'
+ );
+ var selectEl = $("#multiselect_" + data.id, row);
+ // Add all known groups
+ for (var i = 0; i < groups.length; i++) {
+ var extra = "";
+ if (!groups[i].enabled) {
+ extra = " (disabled)";
+ }
+
+ selectEl.append(
+ $(" ")
+ .val(groups[i].id)
+ .text(groups[i].name + extra)
+ );
}
- sel.append(
- $(" ")
- .val(groups[i].id)
- .text(groups[i].name + extra)
- );
+ // Select assigned groups
+ selectEl.val(data.groups);
+ // Initialize multiselect
+ selectEl.multiselect({
+ includeSelectAllOption: true,
+ buttonContainer: '
',
+ 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");
+ }
+
+ if (bottom > 200) {
+ el.removeClass("dropup");
+ }
+
+ 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);
}
- // Select assigned groups
- sel.val(data.groups);
- // Initialize multiselect
- sel.multiselect({ includeSelectAllOption: true });
- sel.on("change", editDomain);
+ // Highlight row (if url parameter "domainid=" is used)
+ if ("domainid" in GETDict && data.id === parseInt(GETDict.domainid)) {
+ $(row)
+ .find("td")
+ .addClass("highlight");
+ }
var button =
- '' +
' ' +
" ";
- $("td:eq(5)", row).html(button);
+ if (table.column(5).visible()) {
+ $("td:eq(5)", row).html(button);
+ } else {
+ $("td:eq(4)", row).html(button);
+ }
},
+ dom:
+ "<'row'<'col-sm-4'l><'col-sm-8'f>>" +
+ "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
+ "<'row'<'col-sm-5'i><'col-sm-7'p>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
@@ -230,8 +263,22 @@ function initTable() {
data.search.search = "";
// Reset visibility of ID column
data.columns[0].visible = false;
+ // Show group assignment column only on full page
+ data.columns[5].visible = showtype === "all";
// Apply loaded state to table
return data;
+ },
+ initComplete: function() {
+ if ("domainid" in GETDict) {
+ var pos = table
+ .column(0, { order: "current" })
+ .data()
+ .indexOf(parseInt(GETDict.domainid));
+ if (pos >= 0) {
+ var page = Math.floor(pos / table.page.info().length);
+ table.page(page).draw(false);
+ }
+ }
}
});
@@ -250,14 +297,53 @@ function initTable() {
}
function addDomain() {
- var domain = $("#new_domain").val();
- var type = $("#new_type").val();
- var comment = $("#new_comment").val();
+ var action = this.id;
+ var tabHref = $('a[data-toggle="tab"][aria-expanded="true"]').attr("href");
+ var wildcardEl = $("#wildcard_checkbox");
+ var wildcard_checked = wildcardEl.prop("checked");
+ var type;
- showAlert("info", "", "Adding domain...", domain);
+ // current tab's inputs
+ var domain_regex, domainEl, commentEl;
+ if (tabHref === "#tab_domain") {
+ domain_regex = "domain";
+ domainEl = $("#new_domain");
+ commentEl = $("#new_domain_comment");
+ } else if (tabHref === "#tab_regex") {
+ domain_regex = "regex";
+ domainEl = $("#new_regex");
+ commentEl = $("#new_regex_comment");
+ }
- if (domain.length === 0) {
- showAlert("warning", "", "Warning", "Please specify a domain");
+ var domain = domainEl.val();
+ var comment = commentEl.val();
+
+ utils.disableAll();
+ utils.showAlert("info", "", "Adding " + domain_regex + "...", domain);
+
+ if (domain.length > 0) {
+ // strip "*." if specified by user in wildcard mode
+ if (domain_regex === "domain" && wildcard_checked && domain.startsWith("*.")) {
+ domain = domain.substr(2);
+ }
+
+ // determine list type
+ if (domain_regex === "domain" && action === "add2black" && wildcard_checked) {
+ type = "3W";
+ } else if (domain_regex === "domain" && action === "add2black" && !wildcard_checked) {
+ type = "1";
+ } else if (domain_regex === "domain" && action === "add2white" && wildcard_checked) {
+ type = "2W";
+ } else if (domain_regex === "domain" && action === "add2white" && !wildcard_checked) {
+ type = "0";
+ } else if (domain_regex === "regex" && action === "add2black") {
+ type = "3";
+ } else if (domain_regex === "regex" && action === "add2white") {
+ type = "2";
+ }
+ } else {
+ utils.enableAll();
+ utils.showAlert("warning", "", "Warning", "Please specify a " + domain_regex);
return;
}
@@ -273,15 +359,20 @@ function addDomain() {
token: token
},
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-plus", "Successfully added domain", domain);
- $("#new_domain").val("");
- $("#new_comment").val("");
+ utils.showAlert("success", "fas fa-plus", "Successfully added " + domain_regex, domain);
+ domainEl.val("");
+ commentEl.val("");
+ wildcardEl.prop("checked", false);
table.ajax.reload();
- } else showAlert("error", "", "Error while adding new domain", response.message);
+ } else {
+ utils.showAlert("error", "", "Error while adding new " + domain_regex, response.message);
+ }
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while adding new domain", jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while adding new " + domain_regex, jqXHR.responseText);
console.log(exception);
}
});
@@ -290,36 +381,60 @@ function addDomain() {
function editDomain() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
- var domain = tr.find("#domain").text();
- var id = tr.find("#id").val();
- var type = tr.find("#type").val();
- var status = tr.find("#status").is(":checked") ? 1 : 0;
- var comment = tr.find("#comment").val();
- var groups = tr.find("#multiselect").val();
+ var id = tr.attr("data-id");
+ var domain = tr.find("#domain_" + id).text();
+ var type = tr.find("#type_" + id).val();
+ var status = tr.find("#status_" + id).is(":checked") ? 1 : 0;
+ var comment = tr.find("#comment_" + id).val();
+
+ // Show group assignment field only if in full domain management mode
+ // if not included, just use the row data.
+ var rowData = table.row(tr).data();
+ var groups = table.column(5).visible() ? tr.find("#multiselect_" + id).val() : rowData.groups;
+
+ var domain_regex;
+ if (type === "0" || type === "1") {
+ domain_regex = "domain";
+ } else if (type === "2" || type === "3") {
+ domain_regex = "regex";
+ }
var done = "edited";
var not_done = "editing";
- if (elem === "status" && status === 1) {
- done = "enabled";
- not_done = "enabling";
- } else if (elem === "status" && status === 0) {
- done = "disabled";
- not_done = "disabling";
- } else if (elem === "name") {
- done = "edited name of";
- not_done = "editing name of";
- } else if (elem === "comment") {
- done = "edited comment of";
- not_done = "editing comment of";
- } else if (elem === "type") {
- done = "edited type of";
- not_done = "editing type of";
- } else if (elem === "multiselect") {
- done = "edited groups of";
- not_done = "editing groups of";
+ switch (elem) {
+ case "status_" + id:
+ if (status === 0) {
+ done = "disabled";
+ not_done = "disabling";
+ } else if (status === 1) {
+ done = "enabled";
+ not_done = "enabling";
+ }
+
+ break;
+ case "name_" + id:
+ done = "edited name of";
+ not_done = "editing name of";
+ break;
+ case "comment_" + id:
+ done = "edited comment of";
+ not_done = "editing comment of";
+ break;
+ case "type_" + id:
+ done = "edited type of";
+ not_done = "editing type of";
+ break;
+ case "multiselect_" + id:
+ done = "edited groups of";
+ not_done = "editing groups of";
+ break;
+ default:
+ alert("bad element or invalid data-id!");
+ return;
}
- showAlert("info", "", "Editing domain...", name);
+ utils.disableAll();
+ utils.showAlert("info", "", "Editing " + domain_regex + "...", name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
@@ -334,21 +449,28 @@ function editDomain() {
token: token
},
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " domain", domain);
+ utils.showAlert(
+ "success",
+ "fas fa-pencil-alt",
+ "Successfully " + done + " " + domain_regex,
+ domain
+ );
} else
- showAlert(
+ utils.showAlert(
"error",
"",
- "Error while " + not_done + " domain with ID " + id,
+ "Error while " + not_done + " " + domain_regex + " with ID " + id,
response.message
);
},
error: function(jqXHR, exception) {
- showAlert(
+ utils.enableAll();
+ utils.showAlert(
"error",
"",
- "Error while " + not_done + " domain with ID " + id,
+ "Error while " + not_done + " " + domain_regex + " with ID " + id,
jqXHR.responseText
);
console.log(exception);
@@ -357,27 +479,55 @@ function editDomain() {
}
function deleteDomain() {
- var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
- var domain = tr.find("#domain").text();
+ var id = tr.attr("data-id");
+ var domain = tr.find("#domain_" + id).text();
+ var type = tr.find("#type_" + id).val();
- showAlert("info", "", "Deleting domain...", domain);
+ var domain_regex;
+ if (type === "0" || type === "1") {
+ domain_regex = "domain";
+ } else if (type === "2" || type === "3") {
+ domain_regex = "regex";
+ }
+
+ utils.disableAll();
+ utils.showAlert("info", "", "Deleting " + domain_regex + "...", domain);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_domain", id: id, token: token },
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "far fa-trash-alt", "Successfully deleted domain", domain);
+ utils.showAlert(
+ "success",
+ "far fa-trash-alt",
+ "Successfully deleted " + domain_regex,
+ domain
+ );
table
.row(tr)
.remove()
.draw(false);
- } else showAlert("error", "", "Error while deleting domain with ID " + id, response.message);
+ } else {
+ utils.showAlert(
+ "error",
+ "",
+ "Error while deleting " + domain_regex + " with ID " + id,
+ response.message
+ );
+ }
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while deleting domain with ID " + id, jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert(
+ "error",
+ "",
+ "Error while deleting " + domain_regex + " with ID " + id,
+ jqXHR.responseText
+ );
console.log(exception);
}
});
diff --git a/scripts/pi-hole/js/groups.js b/scripts/pi-hole/js/groups.js
index eb520684..e9a89e59 100644
--- a/scripts/pi-hole/js/groups.js
+++ b/scripts/pi-hole/js/groups.js
@@ -5,74 +5,10 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global moment:false */
+/* global utils:false */
var table;
-var token = $("#token").text();
-var info = null;
-
-function showAlert(type, icon, title, message) {
- var opts = {};
- title = " " + title + " ";
- 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);
- }
-
- break;
- case "warning":
- opts = {
- type: "warning",
- icon: "fas fa-exclamation-triangle",
- title: title,
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- case "error":
- opts = {
- type: "danger",
- icon: "fas fa-times",
- title: " Error, something went wrong! ",
- message: message
- };
- if (info) {
- info.update(opts);
- } else {
- $.notify(opts);
- }
-
- break;
- default:
- }
-}
-
-function datetime(date) {
- return moment.unix(Math.floor(date)).format("Y-MM-DD HH:mm:ss z");
-}
+var token = $("#token").html();
$(document).ready(function() {
$("#btnAdd").on("click", addGroup);
@@ -92,51 +28,48 @@ $(document).ready(function() {
{ data: null, width: "60px", orderable: false }
],
drawCallback: function() {
- $(".deleteGroup").on("click", deleteGroup);
+ $('button[id^="deleteGroup_"]').on("click", deleteGroup);
},
rowCallback: function(row, data) {
+ $(row).attr("data-id", data.id);
var tooltip =
"Added: " +
- datetime(data.date_added) +
+ utils.datetime(data.date_added) +
"\nLast modified: " +
- datetime(data.date_modified) +
+ utils.datetime(data.date_modified) +
"\nDatabase ID: " +
data.id;
$("td:eq(0)", row).html(
- ' '
+ ' '
);
- var name = $("#name", row);
- name.val(data.name);
- name.on("change", editGroup);
+ var nameEl = $("#name_" + data.id, row);
+ nameEl.val(data.name);
+ nameEl.on("change", editGroup);
var disabled = data.enabled === 0;
$("td:eq(1)", row).html(
- ' "
+ ' "
);
- var status = $("#status", row);
- status.bootstrapToggle({
+ var statusEl = $("#status_" + data.id, row);
+ statusEl.bootstrapToggle({
on: "Enabled",
off: "Disabled",
size: "small",
onstyle: "success",
width: "80px"
});
- status.on("change", editGroup);
+ statusEl.on("change", editGroup);
- $("td:eq(2)", row).html(' ');
+ $("td:eq(2)", row).html(' ');
var desc = data.description !== null ? data.description : "";
- $("#desc", row).val(desc);
- $("#desc", row).on("change", editGroup);
+ var descEl = $("#desc_" + data.id, row);
+ descEl.val(desc);
+ descEl.on("change", editGroup);
$("td:eq(3)", row).empty();
if (data.id !== 0) {
var button =
- " " +
- '' +
' ' +
@@ -144,6 +77,10 @@ $(document).ready(function() {
$("td:eq(3)", row).html(button);
}
},
+ dom:
+ "<'row'<'col-sm-4'l><'col-sm-8'f>>" +
+ "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
+ "<'row'<'col-sm-5'i><'col-sm-7'p>>",
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
@@ -191,10 +128,11 @@ function addGroup() {
var name = $("#new_name").val();
var desc = $("#new_desc").val();
- showAlert("info", "", "Adding group...", name);
+ utils.disableAll();
+ utils.showAlert("info", "", "Adding group...", name);
if (name.length === 0) {
- showAlert("warning", "", "Warning", "Please specify a group name");
+ utils.showAlert("warning", "", "Warning", "Please specify a group name");
return;
}
@@ -204,17 +142,19 @@ function addGroup() {
dataType: "json",
data: { action: "add_group", name: name, desc: desc, token: token },
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-plus", "Successfully added group", name);
+ utils.showAlert("success", "fas fa-plus", "Successfully added group", name);
$("#new_name").val("");
$("#new_desc").val("");
table.ajax.reload();
} else {
- showAlert("error", "", "Error while adding new group", response.message);
+ utils.showAlert("error", "", "Error while adding new group", response.message);
}
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while adding new group", jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while adding new group", jqXHR.responseText);
console.log(exception);
}
});
@@ -223,28 +163,39 @@ function addGroup() {
function editGroup() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
- var id = tr.find("#id").val();
- var name = tr.find("#name").val();
- var status = tr.find("#status").is(":checked") ? 1 : 0;
- var desc = tr.find("#desc").val();
+ var id = tr.attr("data-id");
+ var name = tr.find("#name_" + id).val();
+ var status = tr.find("#status_" + id).is(":checked") ? 1 : 0;
+ var desc = tr.find("#desc_" + id).val();
var done = "edited";
var not_done = "editing";
- if (elem === "status" && status === 1) {
- done = "enabled";
- not_done = "enabling";
- } else if (elem === "status" && status === 0) {
- done = "disabled";
- not_done = "disabling";
- } else if (elem === "name") {
- done = "edited name of";
- not_done = "editing name of";
- } else if (elem === "desc") {
- done = "edited description of";
- not_done = "editing description of";
+ switch (elem) {
+ case "status_" + id:
+ if (status === 0) {
+ done = "disabled";
+ not_done = "disabling";
+ } else if (status === 1) {
+ done = "enabled";
+ not_done = "enabling";
+ }
+
+ break;
+ case "name_" + id:
+ done = "edited name of";
+ not_done = "editing name of";
+ break;
+ case "desc_" + id:
+ done = "edited description of";
+ not_done = "editing description of";
+ break;
+ default:
+ alert("bad element or invalid data-id!");
+ return;
}
- showAlert("info", "", "Editing group...", name);
+ utils.disableAll();
+ utils.showAlert("info", "", "Editing group...", name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
@@ -258,10 +209,11 @@ function editGroup() {
token: token
},
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " group", name);
+ utils.showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " group", name);
} else {
- showAlert(
+ utils.showAlert(
"error",
"",
"Error while " + not_done + " group with ID " + id,
@@ -270,7 +222,8 @@ function editGroup() {
}
},
error: function(jqXHR, exception) {
- showAlert(
+ utils.enableAll();
+ utils.showAlert(
"error",
"",
"Error while " + not_done + " group with ID " + id,
@@ -282,29 +235,32 @@ function editGroup() {
}
function deleteGroup() {
- var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
- var name = tr.find("#name").val();
+ var id = tr.attr("data-id");
+ var name = tr.find("#name_" + id).val();
- showAlert("info", "", "Deleting group...", name);
+ utils.disableAll();
+ utils.showAlert("info", "", "Deleting group...", name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_group", id: id, token: token },
success: function(response) {
+ utils.enableAll();
if (response.success) {
- showAlert("success", "far fa-trash-alt", "Successfully deleted group ", name);
+ utils.showAlert("success", "far fa-trash-alt", "Successfully deleted group ", name);
table
.row(tr)
.remove()
.draw(false);
} else {
- showAlert("error", "", "Error while deleting group with ID " + id, response.message);
+ utils.showAlert("error", "", "Error while deleting group with ID " + id, response.message);
}
},
error: function(jqXHR, exception) {
- showAlert("error", "", "Error while deleting group with ID " + id, jqXHR.responseText);
+ utils.enableAll();
+ utils.showAlert("error", "", "Error while deleting group with ID " + id, jqXHR.responseText);
console.log(exception);
}
});
diff --git a/scripts/pi-hole/js/index.js b/scripts/pi-hole/js/index.js
index 8348a96e..d41a6d64 100644
--- a/scripts/pi-hole/js/index.js
+++ b/scripts/pi-hole/js/index.js
@@ -792,8 +792,9 @@ $(document).ready(function() {
label: function(tooltipItems, data) {
if (tooltipItems.datasetIndex === 1) {
var percentage = 0.0;
- var total = parseInt(data.datasets[0].data[tooltipItems.index]);
+ var permitted = parseInt(data.datasets[0].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
+ var total = permitted + blocked;
if (total > 0) {
percentage = (100.0 * blocked) / total;
}
diff --git a/scripts/pi-hole/js/ip-address-sorting.js b/scripts/pi-hole/js/ip-address-sorting.js
index 3055b3c1..21343fa2 100644
--- a/scripts/pi-hole/js/ip-address-sorting.js
+++ b/scripts/pi-hole/js/ip-address-sorting.js
@@ -14,6 +14,12 @@ jQuery.extend(jQuery.fn.dataTableExt.oSort, {
}
var i, item;
+ // Use the first IP in case there is a list of IPs
+ // for a given device
+ if (Array.isArray(a)) {
+ a = a[0];
+ }
+
var m = a.split("."),
n = a.split(":"),
x = "",
diff --git a/scripts/pi-hole/js/list.js b/scripts/pi-hole/js/list.js
deleted file mode 100644
index 5765e2c7..00000000
--- a/scripts/pi-hole/js/list.js
+++ /dev/null
@@ -1,287 +0,0 @@
-/* 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.
- *
- * This file is copyright under the latest version of the EUPL.
- * Please see LICENSE file for your rights under this license. */
-
-// IE likes to cache too much :P
-$.ajaxSetup({ cache: false });
-
-// Get PHP info
-var token = $("#token").text();
-var listType = $("#list-type").html();
-var fullName = listType === "white" ? "Whitelist" : "Blacklist";
-
-function addListEntry(entry, index, list, button, type) {
- var disabled = [];
- if (entry.enabled === "0") disabled.push("individual");
- // For entry.group_enabled we either get "0" (= disabled by a group),
- // "1" (= enabled by a group), or "" (= not managed by a group)
- if (entry.group_enabled === "0") disabled.push("group");
-
- var used = disabled.length === 0 ? "used" : "not-used";
- var comment = entry.comment.length > 0 ? " - " + entry.comment : "";
- var disabled_message =
- disabled.length > 0 ? " - disabled due to " + disabled.join(" + ") + " setting" : "";
- var date_added = new Date(parseInt(entry.date_added) * 1000);
- var date_modified = new Date(parseInt(entry.date_modified) * 1000);
- var tooltip =
- "Added: " + date_added.toLocaleString() + "\nModified: " + date_modified.toLocaleString();
- list.append(
- '' +
- '' +
- entry.domain +
- comment +
- disabled_message +
- " " +
- '' +
- ' '
- );
- // Handle button
- $(button + " #" + index).on("click", "button", function() {
- sub(index, entry.domain, type);
- });
-}
-
-function refresh(fade) {
- var list = $("#list");
- var listw = $("#list-regex");
- if (fade) {
- list.fadeOut(100);
- listw.fadeOut(100);
- }
-
- $.ajax({
- url: "scripts/pi-hole/php/get.php",
- method: "get",
- data: { list: listType },
- success: function(response) {
- list.html("");
- listw.html("");
-
- if (
- (listType === "black" &&
- response.blacklist.length === 0 &&
- response.regex_blacklist.length === 0) ||
- (listType === "white" &&
- response.whitelist.length === 0 &&
- response.regex_whitelist.length === 0)
- ) {
- $("h3").hide();
- list.html(
- 'Your ' + fullName + " is empty!
"
- );
- } else {
- var data, data2;
- if (listType === "white") {
- data = response.whitelist.sort();
- data2 = response.regex_whitelist.sort();
- } else if (listType === "black") {
- data = response.blacklist.sort();
- data2 = response.regex_blacklist.sort();
- }
-
- if (data.length > 0) {
- $("#h3-exact").fadeIn(100);
- }
-
- if (data2.length > 0) {
- $("#h3-regex").fadeIn(100);
- }
-
- data.forEach(function(entry, index) {
- addListEntry(entry, index, list, "#list", "exact");
- });
- data2.forEach(function(entry, index) {
- addListEntry(entry, index, listw, "#list-regex", listType + "_regex");
- });
- }
-
- list.fadeIn(100);
- listw.fadeIn(100);
- },
- error: function() {
- $("#alFailure").show();
- }
- });
-}
-
-window.addEventListener("load", refresh(false));
-
-function sub(index, entry, arg) {
- var list = "#list";
- var heading = "#h3-exact";
- var locallistType = listType;
- if (arg === "black_regex" || arg === "white_regex") {
- list = "#list-regex";
- heading = "#h3-regex";
- locallistType = arg;
- }
-
- var alInfo = $("#alInfo");
- var alSuccess = $("#alSuccess");
- var alFailure = $("#alFailure");
- var err = $("#err");
- var msg = $("#success-message");
-
- var domain = $(list + " #" + index);
- domain.hide("highlight");
- $.ajax({
- url: "scripts/pi-hole/php/sub.php",
- method: "post",
- data: { domain: entry, list: locallistType, token: token },
- success: function(response) {
- if (response.indexOf("Success") === -1) {
- alFailure.show();
- err.html(response);
- alFailure.delay(8000).fadeOut(2000, function() {
- alFailure.hide();
- });
- alInfo.delay(8000).fadeOut(2000, function() {
- alInfo.hide();
- });
- } else {
- alSuccess.show();
- msg.html(response);
- alSuccess.delay(1000).fadeOut(2000, function() {
- alSuccess.hide();
- });
- alInfo.delay(1000).fadeOut(2000, function() {
- alInfo.hide();
- });
- domain.remove();
- if ($(list + " li").length === 0) {
- $(heading).fadeOut(100);
- }
- }
- },
- error: function() {
- alert("Failed to remove the domain!");
- domain.show({ queue: true });
- }
- });
-}
-
-function add(type) {
- var domain = $("#domain");
- if (domain.val().length === 0) {
- return;
- }
-
- var comment = $("#comment");
-
- var alInfo = $("#alInfo");
- var alSuccess = $("#alSuccess");
- var alFailure = $("#alFailure");
- var alWarning = $("#alWarning");
- var err = $("#err");
- var msg = $("#success-message");
- alInfo.show();
- alSuccess.hide();
- alFailure.hide();
- alWarning.hide();
- $.ajax({
- url: "scripts/pi-hole/php/add.php",
- method: "post",
- data: { domain: domain.val().trim(), comment: comment.val(), list: type, token: token },
- success: function(response) {
- if (response.indexOf("Success") === -1) {
- alFailure.show();
- err.html(response);
- alFailure.delay(8000).fadeOut(2000, function() {
- alFailure.hide();
- });
- alInfo.delay(8000).fadeOut(2000, function() {
- alInfo.hide();
- });
- } else {
- alSuccess.show();
- msg.html(response);
- alSuccess.delay(1000).fadeOut(2000, function() {
- alSuccess.hide();
- });
- alInfo.delay(1000).fadeOut(2000, function() {
- alInfo.hide();
- });
- domain.val("");
- comment.val("");
- refresh(true);
- }
- },
- error: function() {
- alFailure.show();
- err.html("");
- alFailure.delay(1000).fadeOut(2000, function() {
- alFailure.hide();
- });
- alInfo.delay(1000).fadeOut(2000, function() {
- alInfo.hide();
- });
- }
- });
-}
-
-// Handle enter button for adding domains
-$(document).keypress(function(e) {
- if (e.which === 13 && $("#domain,#comment").is(":focus")) {
- // Enter was pressed, and the input has focus
- add(listType);
- }
-});
-
-// Handle buttons
-$("#btnAdd").on("click", function() {
- add(listType);
-});
-
-$("#btnAddWildcard").on("click", function() {
- add(listType + "_wild");
-});
-
-$("#btnAddRegex").on("click", function() {
- add(listType + "_regex");
-});
-
-$("#btnRefresh").on("click", function() {
- refresh(true);
-});
-
-// Handle hiding of alerts
-$(function() {
- $("[data-hide]").on("click", function() {
- $(this)
- .closest("." + $(this).attr("data-hide"))
- .hide();
- });
-});
-
-// Wrap form-group's buttons to next line when viewed on a small screen
-$(window).on("resize", function() {
- if ($(window).width() < 991) {
- $(".form-group.input-group")
- .removeClass("input-group")
- .addClass("input-group-block");
- $(".form-group.input-group-block > input").css("margin-bottom", "5px");
- $(".form-group.input-group-block > .input-group-btn")
- .removeClass("input-group-btn")
- .addClass("btn-block text-center");
- } else {
- $(".form-group.input-group-block")
- .removeClass("input-group-block")
- .addClass("input-group");
- $(".form-group.input-group > input").css("margin-bottom", "");
- $(".form-group.input-group > .btn-block.text-center")
- .removeClass("btn-block text-center")
- .addClass("input-group-btn");
- }
-});
-$(document).ready(function() {
- $(window).trigger("resize");
-});
diff --git a/scripts/pi-hole/js/network.js b/scripts/pi-hole/js/network.js
index eaba2450..8d3d32ed 100644
--- a/scripts/pi-hole/js/network.js
+++ b/scripts/pi-hole/js/network.js
@@ -93,30 +93,41 @@ $(document).ready(function() {
$("td:eq(5)", row).html("Never");
}
- // Set hostname to "N/A" if not available
+ // Set hostname to "unknown" if not available
if (!data.name || data.name.length === 0) {
- $("td:eq(3)", row).html("N/A");
+ $("td:eq(3)", row).html("unknown ");
}
// Set number of queries to localized string (add thousand separators)
$("td:eq(6)", row).html(data.numQueries.toLocaleString());
- var ips = data.ip;
- var shortips = ips;
- if (ips.length > MAXIPDISPLAY) {
- shortips = ips.slice(0, MAXIPDISPLAY - 1);
- shortips.push("...");
+ var ips = [];
+ var maxiter = Math.min(data.ip.length, MAXIPDISPLAY);
+ for (var index = 0; index < maxiter; index++) {
+ var ip = data.ip[index];
+ ips.push('' + ip + " ");
}
- $("td:eq(0)", row).html(shortips.join(" "));
+ if (data.ip.length > MAXIPDISPLAY) {
+ // We hit the maximum above, add "..." to symbolize we would
+ // have more to show here
+ ips.push("...");
+ }
+
+ $("td:eq(0)", row).html(ips.join(" "));
$("td:eq(0)", row).hover(function() {
- this.title = ips.join("\n");
+ this.title = data.ip.join("\n");
});
// MAC + Vendor field if available
if (data.macVendor && data.macVendor.length > 0) {
$("td:eq(1)", row).html(data.hwaddr + " " + data.macVendor);
}
+
+ // Hide mock MAC addresses
+ if (data.hwaddr.startsWith("ip-")) {
+ $("td:eq(1)", row).text("N/A");
+ }
},
dom:
"<'row'<'col-sm-12'f>>" +
diff --git a/scripts/pi-hole/js/queries.js b/scripts/pi-hole/js/queries.js
index 06952529..a8596b8f 100644
--- a/scripts/pi-hole/js/queries.js
+++ b/scripts/pi-hole/js/queries.js
@@ -108,7 +108,7 @@ function handleAjaxError(xhr, textStatus) {
}
function autofilter() {
- return document.getElementById("autofilter").checked;
+ return $("#autofilter").prop("checked");
}
$(document).ready(function() {
@@ -166,7 +166,13 @@ $(document).ready(function() {
}
// Query status
- var blocked, fieldtext, buttontext, colorClass;
+ var blocked,
+ fieldtext,
+ buttontext,
+ colorClass,
+ isCNAME = false,
+ regexLink = false;
+
switch (data[4]) {
case "1":
blocked = true;
@@ -192,14 +198,19 @@ $(document).ready(function() {
case "4":
blocked = true;
colorClass = "text-red";
- fieldtext = "Blocked (regex/wildcard)";
+ fieldtext = "Blocked (regex blacklist)";
+
+ if (data.length > 9 && data[9] > 0) {
+ regexLink = true;
+ }
+
buttontext =
' Whitelist ';
break;
case "5":
blocked = true;
colorClass = "text-red";
- fieldtext = "Blocked (blacklist)";
+ fieldtext = "Blocked (exact blacklist)";
buttontext =
' Whitelist ';
break;
@@ -221,6 +232,35 @@ $(document).ready(function() {
fieldtext = "Blocked (external, NXRA)";
buttontext = "";
break;
+ case "9":
+ blocked = true;
+ colorClass = "text-red";
+ fieldtext = "Blocked (gravity, CNAME)";
+ buttontext =
+ ' Whitelist ';
+ isCNAME = true;
+ break;
+ case "10":
+ blocked = true;
+ colorClass = "text-red";
+ fieldtext = "Blocked (regex blacklist, CNAME)";
+
+ if (data.length > 9 && data[9] > 0) {
+ regexLink = true;
+ }
+
+ buttontext =
+ ' Whitelist ';
+ isCNAME = true;
+ break;
+ case "11":
+ blocked = true;
+ colorClass = "text-red";
+ fieldtext = "Blocked (exact blacklist, CNAME)";
+ buttontext =
+ ' Whitelist ';
+ isCNAME = true;
+ break;
default:
blocked = false;
colorClass = "text-black";
@@ -232,6 +272,33 @@ $(document).ready(function() {
$("td:eq(4)", row).html(fieldtext);
$("td:eq(6)", row).html(buttontext);
+ if (regexLink) {
+ $("td:eq(4)", row).hover(
+ function() {
+ this.title = "Click to show matching regex filter";
+ this.style.color = "#72afd2";
+ },
+ function() {
+ this.style.color = "";
+ }
+ );
+ $("td:eq(4)", row).click(function() {
+ var new_tab = window.open("groups-domains.php?domainid=" + data[9], "_blank");
+ if (new_tab) {
+ new_tab.focus();
+ }
+ });
+ $("td:eq(4)", row).addClass("underline");
+ $("td:eq(4)", row).addClass("pointer");
+ }
+
+ // Add domain in CNAME chain causing the query to have been blocked
+ var domain = data[2];
+ var CNAME_domain = data[8];
+ if (isCNAME) {
+ $("td:eq(2)", row).text(domain + "\n(blocked " + CNAME_domain + ")");
+ }
+
// Check for existence of sixth column and display only if not Pi-holed
var replytext;
if (data.length > 6 && !blocked) {
@@ -382,14 +449,16 @@ $(document).ready(function() {
// Domain
api.$("td:eq(2)").click(function() {
if (autofilter()) {
- api.search(this.textContent).draw();
+ var domain = this.textContent.split("\n")[0];
+ api.search(domain).draw();
$("#resetButton").show();
}
});
api.$("td:eq(2)").hover(
function() {
if (autofilter()) {
- this.title = "Click to show only queries with domain " + this.textContent;
+ var domain = this.textContent.split("\n")[0];
+ this.title = "Click to show only queries with domain " + domain;
this.style.color = "#72afd2";
} else {
this.title = "";
@@ -428,10 +497,10 @@ $(document).ready(function() {
$("#all-queries tbody").on("click", "button", function() {
var data = tableApi.row($(this).parents("tr")).data();
- if (data[4] === "1" || data[4] === "4" || data[4] === "5") {
- add(data[2], "white");
- } else {
+ if (data[4] === "2" || data[4] === "3") {
add(data[2], "black");
+ } else {
+ add(data[2], "white");
}
});
@@ -439,10 +508,25 @@ $(document).ready(function() {
tableApi.search("").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);
+
+ var chkbox_data = localStorage.getItem("query_log_filter_chkbox");
+ if (chkbox_data !== null) {
+ // Restore checkbox state
+ $("#autofilter").prop("checked", chkbox_data === "true");
+ } else {
+ // Initialize checkbox
+ $("#autofilter").prop("checked", true);
+ localStorage.setItem("query_log_filter_chkbox", true);
+ }
+
+ $("#autofilter").click(function() {
+ localStorage.setItem("query_log_filter_chkbox", $("#autofilter").prop("checked"));
+ });
});
diff --git a/scripts/pi-hole/js/settings.js b/scripts/pi-hole/js/settings.js
index 4d310865..93974e5e 100644
--- a/scripts/pi-hole/js/settings.js
+++ b/scripts/pi-hole/js/settings.js
@@ -217,28 +217,6 @@ $(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip({ html: true, container: "body" });
});
-// Handle list deletion
-$("button[id^='adlist-btn-']").on("click", function(e) {
- var id = parseInt($(this).context.id.replace(/[^\d.]/g, ""), 10);
- e.preventDefault();
-
- var status = $('input[name="adlist-del-' + id + '"]').is(":checked");
- var textType = status ? "none" : "line-through";
-
- // Check hidden delete box (or reset)
- $('input[name="adlist-del-' + id + '"]').prop("checked", !status);
- // Untick and disable check box (or reset)
- $('input[name="adlist-enable-' + id + '"]')
- .prop("checked", status)
- .prop("disabled", !status);
- // Strike through text (or reset)
- $('a[id="adlist-text-' + id + '"]').css("text-decoration", textType);
- // Highlight that the button has to be clicked in order to make the change live
- $('button[id="blockinglistsaveupdate"]')
- .addClass("btn-danger")
- .css("font-weight", "bold");
-});
-
// Change "?tab=" parameter in URL for save and reload
$(".nav-tabs a").on("shown.bs.tab", function(e) {
var tab = e.target.hash.substring(1);
diff --git a/scripts/pi-hole/php/add.php b/scripts/pi-hole/php/add.php
index d6059a51..878b28ff 100644
--- a/scripts/pi-hole/php/add.php
+++ b/scripts/pi-hole/php/add.php
@@ -16,22 +16,38 @@ if (empty($api)) {
list_verify($list);
}
+// Split individual domains into array
+$domains = preg_split('/\s+/', trim($_POST['domain']));
+
+// Get comment if available
+$comment = null;
+if(isset($_POST['comment'])) {
+ $comment = trim($_POST['comment']);
+}
+
+// Convert domain name to IDNA ASCII form for international domains
+// Do this only for exact domains, not for regex filters
+// Only do it when the php-intl extension is available
+if (extension_loaded("intl") && ($list === "white" || $list === "black")) {
+ foreach($domains as &$domain)
+ {
+ $domain = idn_to_ascii($domain);
+ }
+}
+
// Only check domains we add to the exact lists.
// Regex are validated by FTL during import
$check_lists = ["white","black","audit"];
if(in_array($list, $check_lists)) {
- check_domain();
+ check_domain($domains);
}
-// Split individual domains into array
-$domains = preg_split('/\s+/', trim($_POST['domain']));
-$comment = trim($_POST['comment']);
-
require_once("func.php");
require_once("database.php");
$GRAVITYDB = getGravityDBFilename();
$db = SQLite3_connect($GRAVITYDB, SQLITE3_OPEN_READWRITE);
+$reload = true;
switch($list) {
case "white":
$domains = array_map('strtolower', $domains);
@@ -60,7 +76,8 @@ switch($list) {
break;
case "audit":
- echo add_to_table($db, "domain_audit", $domains, $comment);
+ $reload = false;
+ echo add_to_table($db, "domain_audit", $domains);
break;
default:
@@ -68,5 +85,8 @@ switch($list) {
}
// Reload lists in pihole-FTL after having added something
-echo shell_exec("sudo pihole restartdns reload");
+if ($reload) {
+ echo shell_exec("sudo pihole restartdns reload-lists");
+}
?>
+
diff --git a/scripts/pi-hole/php/auth.php b/scripts/pi-hole/php/auth.php
index 56f84520..7398cbdf 100644
--- a/scripts/pi-hole/php/auth.php
+++ b/scripts/pi-hole/php/auth.php
@@ -113,15 +113,12 @@ function check_csrf($token) {
}
}
-function check_domain() {
- if(isset($_POST['domain'])){
- $domains = preg_split('/\s+/', $_POST['domain']);
- foreach($domains as $domain)
- {
- $validDomain = is_valid_domain_name($domain);
- if(!$validDomain){
- log_and_die(htmlspecialchars($domain. ' is not a valid domain'));
- }
+function check_domain(&$domains) {
+ foreach($domains as &$domain)
+ {
+ $validDomain = is_valid_domain_name($domain);
+ if(!$validDomain){
+ log_and_die(htmlspecialchars($domain. ' is not a valid domain'));
}
}
}
diff --git a/scripts/pi-hole/php/database.php b/scripts/pi-hole/php/database.php
index 2bad73db..82f10485 100644
--- a/scripts/pi-hole/php/database.php
+++ b/scripts/pi-hole/php/database.php
@@ -91,7 +91,7 @@ function SQLite3_connect($filename, $mode=SQLITE3_OPEN_READONLY)
* @param $type integer The target type (0 = exact whitelist, 1 = exact blacklist, 2 = regex whitelist, 3 = regex blacklist)
* @return string Success/error and number of processed domains
*/
-function add_to_table($db, $table, $domains, $comment, $wildcardstyle=false, $returnnum=false, $type=-1)
+function add_to_table($db, $table, $domains, $comment=null, $wildcardstyle=false, $returnnum=false, $type=-1)
{
if(!is_int($type))
{
@@ -107,6 +107,16 @@ function add_to_table($db, $table, $domains, $comment, $wildcardstyle=false, $re
return "Error: Unable to begin transaction for $table table.";
}
+ // To which column should the record be added to?
+ if ($table === "adlist")
+ {
+ $field = "address";
+ }
+ else
+ {
+ $field = "domain";
+ }
+
// Get initial count of domains in this table
if($type === -1)
{
@@ -119,13 +129,15 @@ function add_to_table($db, $table, $domains, $comment, $wildcardstyle=false, $re
$initialcount = intval($db->querySingle($countquery));
// Prepare INSERT SQLite statememt
- if($type === -1)
- {
- $querystr = "INSERT OR IGNORE INTO $table (domain,comment) VALUES (:domain, :comment);";
- }
- else
- {
- $querystr = "INSERT OR IGNORE INTO $table (domain,comment,type) VALUES (:domain, :comment, $type);";
+ $bindcomment = false;
+ if($table === "domain_audit") {
+ $querystr = "INSERT OR IGNORE INTO $table ($field) VALUES (:$field);";
+ } elseif($type === -1) {
+ $querystr = "INSERT OR IGNORE INTO $table ($field,comment) VALUES (:$field, :comment);";
+ $bindcomment = true;
+ } else {
+ $querystr = "INSERT OR IGNORE INTO $table ($field,comment,type) VALUES (:$field, :comment, $type);";
+ $bindcomment = true;
}
$stmt = $db->prepare($querystr);
@@ -135,7 +147,7 @@ function add_to_table($db, $table, $domains, $comment, $wildcardstyle=false, $re
if($returnnum)
return 0;
else
- return "Error: Failed to prepare statement for $table table (type = $type).";
+ return "Error: Failed to prepare statement for $table table (type = $type, field = $field).";
}
// Loop over domains and inject the lines into the database
@@ -149,8 +161,10 @@ function add_to_table($db, $table, $domains, $comment, $wildcardstyle=false, $re
if($wildcardstyle)
$domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
- $stmt->bindValue(":domain", $domain, SQLITE3_TEXT);
- $stmt->bindValue(":comment", $comment, SQLITE3_TEXT);
+ $stmt->bindValue(":$field", $domain, SQLITE3_TEXT);
+ if($bindcomment) {
+ $stmt->bindValue(":comment", $comment, SQLITE3_TEXT);
+ }
if($stmt->execute() && $stmt->reset())
$num++;
diff --git a/scripts/pi-hole/php/get.php b/scripts/pi-hole/php/get.php
deleted file mode 100644
index f47f2c84..00000000
--- a/scripts/pi-hole/php/get.php
+++ /dev/null
@@ -1,78 +0,0 @@
-query($querystr);
-
- while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC))
- {
- array_push($entries, $res);
- }
-
- return $entries;
-}
-
-function filterArray(&$inArray) {
- $outArray = array();
- foreach ($inArray as $key => $value)
- {
- if (is_array($value))
- {
- $outArray[htmlspecialchars($key)] = filterArray($value);
- }
- else
- {
- $outArray[htmlspecialchars($key)] = htmlspecialchars($value);
- }
- }
- return $outArray;
-}
-
-switch ($listtype)
-{
- case "white":
- $exact = array("whitelist" => getTableContent(ListType::whitelist));
- $regex = array("regex_whitelist" => getTableContent(ListType::regex_whitelist));
- $list = array_merge($exact, $regex);
- break;
-
- case "black":
- $exact = array("blacklist" => getTableContent(ListType::blacklist));
- $regex = array("regex_blacklist" => getTableContent(ListType::regex_blacklist));
- $list = array_merge($exact, $regex);
- break;
-
- default:
- die("Invalid list parameter");
- break;
-}
-// Protect against XSS attacks
-$output = filterArray($list);
-
-// Return results
-header('Content-type: application/json');
-echo json_encode($output);
diff --git a/scripts/pi-hole/php/groups.php b/scripts/pi-hole/php/groups.php
index ff6f7aa5..2167295e 100644
--- a/scripts/pi-hole/php/groups.php
+++ b/scripts/pi-hole/php/groups.php
@@ -49,32 +49,35 @@ if ($_POST['action'] == 'get_groups') {
}
echo json_encode(array('data' => $data));
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'add_group') {
// Add new group
try {
+ $names = explode(' ', $_POST['name']);
$stmt = $db->prepare('INSERT INTO "group" (name,description) VALUES (:name,:desc)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':name', $_POST['name'], SQLITE3_TEXT)) {
- throw new Exception('While binding name: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->bindValue(':desc', $_POST['desc'], SQLITE3_TEXT)) {
throw new Exception('While binding desc: ' . $db->lastErrorMsg());
}
- if (!$stmt->execute()) {
- throw new Exception('While executing: ' . $db->lastErrorMsg());
+ foreach ($names as $name) {
+ if (!$stmt->bindValue(':name', $name, SQLITE3_TEXT)) {
+ throw new Exception('While binding name: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->execute()) {
+ throw new Exception('While executing: ' . $db->lastErrorMsg());
+ }
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'edit_group') {
// Edit group identified by ID
@@ -111,9 +114,9 @@ if ($_POST['action'] == 'get_groups') {
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'delete_group') {
// Delete group identified by ID
@@ -142,9 +145,9 @@ if ($_POST['action'] == 'get_groups') {
}
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'get_clients') {
// List all available groups
@@ -157,7 +160,6 @@ if ($_POST['action'] == 'get_groups') {
throw new Exception('Error while querying gravity\'s client table: ' . $db->lastErrorMsg());
}
-
$data = array();
while (($res = $query->fetchArray(SQLITE3_ASSOC)) !== false) {
$group_query = $db->query('SELECT group_id FROM client_by_group WHERE client_id = ' . $res['id'] . ';');
@@ -182,7 +184,7 @@ if ($_POST['action'] == 'get_groups') {
// There will always be a result. Unknown host names are NULL
$name_result = $result->fetchArray(SQLITE3_ASSOC);
$res['name'] = $name_result['name'];
-
+
$groups = array();
while ($gres = $group_query->fetchArray(SQLITE3_ASSOC)) {
array_push($groups, $gres['group_id']);
@@ -193,7 +195,7 @@ if ($_POST['action'] == 'get_groups') {
echo json_encode(array('data' => $data));
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'get_unconfigured_clients') {
// List all available clients WITHOUT already configured clients
@@ -209,7 +211,7 @@ if ($_POST['action'] == 'get_groups') {
// Loop over results
$ips = array();
while ($res = $query->fetchArray(SQLITE3_ASSOC)) {
- $ips[$res['ip']] = $res['name'];
+ $ips[$res['ip']] = $res['name'] !== null ? $res['name'] : '';
}
$FTLdb->close();
@@ -227,32 +229,66 @@ if ($_POST['action'] == 'get_groups') {
echo json_encode($ips);
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'add_client') {
// Add new client
try {
- $stmt = $db->prepare('INSERT INTO client (ip) VALUES (:ip)');
+ $ips = explode(' ', $_POST['ip']);
+ $stmt = $db->prepare('INSERT INTO client (ip,comment) VALUES (:ip,:comment)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':ip', $_POST['ip'], SQLITE3_TEXT)) {
- throw new Exception('While binding ip: ' . $db->lastErrorMsg());
+ foreach ($ips as $ip) {
+ if (!$stmt->bindValue(':ip', $ip, SQLITE3_TEXT)) {
+ throw new Exception('While binding ip: ' . $db->lastErrorMsg());
+ }
+
+ $comment = $_POST['comment'];
+ if (strlen($comment) == 0) {
+ // Store NULL in database for empty comments
+ $comment = null;
+ }
+ if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
+ throw new Exception('While binding comment: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->execute()) {
+ throw new Exception('While executing: ' . $db->lastErrorMsg());
+ }
+ }
+
+ $reload = true;
+ JSON_success();
+ } catch (\Exception $ex) {
+ JSON_error($ex->getMessage());
+ }
+} elseif ($_POST['action'] == 'edit_client') {
+ // Edit client identified by ID
+ try {
+ $stmt = $db->prepare('UPDATE client SET comment=:comment WHERE id = :id');
+ if (!$stmt) {
+ throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
+ }
+
+ $comment = $_POST['comment'];
+ if (strlen($comment) == 0) {
+ // Store NULL in database for empty comments
+ $comment = null;
+ }
+ if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
+ throw new Exception('While binding comment: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
+ throw new Exception('While binding id: ' . $db->lastErrorMsg());
}
if (!$stmt->execute()) {
throw new Exception('While executing: ' . $db->lastErrorMsg());
}
- $reload = true;
- return JSON_success();
- } catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
- }
-} elseif ($_POST['action'] == 'edit_client') {
- // Edit client identified by ID
- try {
$stmt = $db->prepare('DELETE FROM client_by_group WHERE client_id = :id');
if (!$stmt) {
throw new Exception('While preparing DELETE statement: ' . $db->lastErrorMsg());
@@ -265,22 +301,22 @@ if ($_POST['action'] == 'get_groups') {
if (!$stmt->execute()) {
throw new Exception('While executing DELETE statement: ' . $db->lastErrorMsg());
}
-
+
$db->query('BEGIN TRANSACTION;');
foreach ($_POST['groups'] as $gid) {
$stmt = $db->prepare('INSERT INTO client_by_group (client_id,group_id) VALUES(:id,:gid);');
if (!$stmt) {
throw new Exception('While preparing INSERT INTO statement: ' . $db->lastErrorMsg());
}
-
+
if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
throw new Exception('While binding id: ' . $db->lastErrorMsg());
}
-
+
if (!$stmt->bindValue(':gid', intval($gid), SQLITE3_INTEGER)) {
throw new Exception('While binding gid: ' . $db->lastErrorMsg());
}
-
+
if (!$stmt->execute()) {
throw new Exception('While executing INSERT INTO statement: ' . $db->lastErrorMsg());
}
@@ -288,9 +324,9 @@ if ($_POST['action'] == 'get_groups') {
$db->query('COMMIT;');
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'delete_client') {
// Delete client identified by ID
@@ -322,14 +358,20 @@ if ($_POST['action'] == 'get_groups') {
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'get_domains') {
// List all available groups
try {
- $query = $db->query('SELECT * FROM domainlist;');
+ $limit = "";
+ if (isset($_POST["showtype"]) && $_POST["showtype"] === "white"){
+ $limit = " WHERE type = 0 OR type = 2";
+ } elseif (isset($_POST["showtype"]) && $_POST["showtype"] === "black"){
+ $limit = " WHERE type = 1 OR type = 3";
+ }
+ $query = $db->query('SELECT * FROM domainlist'.$limit);
if (!$query) {
throw new Exception('Error while querying gravity\'s domainlist table: ' . $db->lastErrorMsg());
}
@@ -340,23 +382,35 @@ if ($_POST['action'] == 'get_groups') {
if (!$group_query) {
throw new Exception('Error while querying gravity\'s domainlist_by_group table: ' . $db->lastErrorMsg());
}
-
+
$groups = array();
while ($gres = $group_query->fetchArray(SQLITE3_ASSOC)) {
array_push($groups, $gres['group_id']);
}
$res['groups'] = $groups;
+ if (extension_loaded("intl") &&
+ ($res['type'] === ListType::whitelist ||
+ $res['type'] === ListType::blacklist) ) {
+ $utf8_domain = idn_to_utf8($res['domain']);
+ // Convert domain name to international form
+ // if applicable and extension is available
+ if($res['domain'] !== $utf8_domain)
+ {
+ $res['domain'] = $utf8_domain.' ('.$res['domain'].')';
+ }
+ }
array_push($data, $res);
}
echo json_encode(array('data' => $data));
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'add_domain') {
// Add new domain
try {
+ $domains = explode(' ', $_POST['domain']);
$stmt = $db->prepare('INSERT INTO domainlist (domain,type,comment) VALUES (:domain,:type,:comment)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
@@ -364,21 +418,6 @@ if ($_POST['action'] == 'get_groups') {
$type = intval($_POST['type']);
- $domain = $_POST['domain'];
- if($type === ListType::whitelist || $type === ListType::blacklist)
- {
- // If adding to the exact lists, we convert the domain lower case and check whether it is valid
- $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.');
- }
- }
-
- if (!$stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
- throw new Exception('While binding domain: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->bindValue(':type', $type, SQLITE3_TEXT)) {
throw new Exception('While binding type: ' . $db->lastErrorMsg());
}
@@ -387,14 +426,39 @@ if ($_POST['action'] == 'get_groups') {
throw new Exception('While binding comment: ' . $db->lastErrorMsg());
}
- if (!$stmt->execute()) {
- throw new Exception('While executing: ' . $db->lastErrorMsg());
+ foreach ($domains as $domain) {
+ // Convert domain name to IDNA ASCII form for international domains
+ $domain = idn_to_ascii($domain);
+
+ if(strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
+ {
+ // Apply wildcard-style formatting
+ $domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
+ }
+
+ if($type === ListType::whitelist || $type === ListType::blacklist)
+ {
+ // If adding to the exact lists, we convert the domain lower case and check whether it is valid
+ $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.');
+ }
+ }
+
+ if (!$stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
+ throw new Exception('While binding domain: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->execute()) {
+ throw new Exception('While executing: ' . $db->lastErrorMsg());
+ }
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'edit_domain') {
// Edit domain identified by ID
@@ -434,44 +498,46 @@ if ($_POST['action'] == 'get_groups') {
throw new Exception('While executing: ' . $db->lastErrorMsg());
}
- $stmt = $db->prepare('DELETE FROM domainlist_by_group WHERE domainlist_id = :id');
- if (!$stmt) {
- throw new Exception('While preparing DELETE statement: ' . $db->lastErrorMsg());
- }
-
- if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
- throw new Exception('While binding id: ' . $db->lastErrorMsg());
- }
-
- if (!$stmt->execute()) {
- throw new Exception('While executing DELETE statement: ' . $db->lastErrorMsg());
- }
-
- $db->query('BEGIN TRANSACTION;');
- foreach ($_POST['groups'] as $gid) {
- $stmt = $db->prepare('INSERT INTO domainlist_by_group (domainlist_id,group_id) VALUES(:id,:gid);');
+ if (isset($_POST['groups'])) {
+ $stmt = $db->prepare('DELETE FROM domainlist_by_group WHERE domainlist_id = :id');
if (!$stmt) {
- throw new Exception('While preparing INSERT INTO statement: ' . $db->lastErrorMsg());
+ throw new Exception('While preparing DELETE statement: ' . $db->lastErrorMsg());
}
-
+
if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
throw new Exception('While binding id: ' . $db->lastErrorMsg());
}
-
- if (!$stmt->bindValue(':gid', intval($gid), SQLITE3_INTEGER)) {
- throw new Exception('While binding gid: ' . $db->lastErrorMsg());
- }
-
+
if (!$stmt->execute()) {
- throw new Exception('While executing INSERT INTO statement: ' . $db->lastErrorMsg());
+ throw new Exception('While executing DELETE statement: ' . $db->lastErrorMsg());
}
+
+ $db->query('BEGIN TRANSACTION;');
+ foreach ($_POST['groups'] as $gid) {
+ $stmt = $db->prepare('INSERT INTO domainlist_by_group (domainlist_id,group_id) VALUES(:id,:gid);');
+ if (!$stmt) {
+ throw new Exception('While preparing INSERT INTO statement: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
+ throw new Exception('While binding id: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->bindValue(':gid', intval($gid), SQLITE3_INTEGER)) {
+ throw new Exception('While binding gid: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->execute()) {
+ throw new Exception('While executing INSERT INTO statement: ' . $db->lastErrorMsg());
+ }
+ }
+ $db->query('COMMIT;');
}
- $db->query('COMMIT;');
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'delete_domain') {
// Delete domain identified by ID
@@ -503,9 +569,9 @@ if ($_POST['action'] == 'get_groups') {
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'get_adlists') {
// List all available groups
@@ -521,7 +587,7 @@ if ($_POST['action'] == 'get_groups') {
if (!$group_query) {
throw new Exception('Error while querying gravity\'s adlist_by_group table: ' . $db->lastErrorMsg());
}
-
+
$groups = array();
while ($gres = $group_query->fetchArray(SQLITE3_ASSOC)) {
array_push($groups, $gres['group_id']);
@@ -533,32 +599,40 @@ if ($_POST['action'] == 'get_groups') {
echo json_encode(array('data' => $data));
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'add_adlist') {
// Add new adlist
try {
+ $addresses = explode(' ', $_POST['address']);
+
$stmt = $db->prepare('INSERT INTO adlist (address,comment) VALUES (:address,:comment)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
- if (!$stmt->bindValue(':address', $_POST['address'], SQLITE3_TEXT)) {
- throw new Exception('While binding address: ' . $db->lastErrorMsg());
- }
-
if (!$stmt->bindValue(':comment', $_POST['comment'], SQLITE3_TEXT)) {
throw new Exception('While binding comment: ' . $db->lastErrorMsg());
}
- if (!$stmt->execute()) {
- throw new Exception('While executing: ' . $db->lastErrorMsg());
+ foreach ($addresses as $address) {
+ if(preg_match("/[^a-zA-Z0-9:\/?&%=~._-]/", $address) !== 0) {
+ throw new Exception('Invalid adlist URL');
+ }
+
+ if (!$stmt->bindValue(':address', $address, SQLITE3_TEXT)) {
+ throw new Exception('While binding address: ' . $db->lastErrorMsg());
+ }
+
+ if (!$stmt->execute()) {
+ throw new Exception('While executing: ' . $db->lastErrorMsg());
+ }
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'edit_adlist') {
// Edit adlist identified by ID
@@ -572,7 +646,7 @@ if ($_POST['action'] == 'get_groups') {
if ($status !== 0) {
$status = 1;
}
-
+
if (!$stmt->bindValue(':enabled', $status, SQLITE3_INTEGER)) {
throw new Exception('While binding enabled: ' . $db->lastErrorMsg());
}
@@ -606,22 +680,22 @@ if ($_POST['action'] == 'get_groups') {
if (!$stmt->execute()) {
throw new Exception('While executing DELETE statement: ' . $db->lastErrorMsg());
}
-
+
$db->query('BEGIN TRANSACTION;');
foreach ($_POST['groups'] as $gid) {
$stmt = $db->prepare('INSERT INTO adlist_by_group (adlist_id,group_id) VALUES(:id,:gid);');
if (!$stmt) {
throw new Exception('While preparing INSERT INTO statement: ' . $db->lastErrorMsg());
}
-
+
if (!$stmt->bindValue(':id', intval($_POST['id']), SQLITE3_INTEGER)) {
throw new Exception('While binding id: ' . $db->lastErrorMsg());
}
-
+
if (!$stmt->bindValue(':gid', intval($gid), SQLITE3_INTEGER)) {
throw new Exception('While binding gid: ' . $db->lastErrorMsg());
}
-
+
if (!$stmt->execute()) {
throw new Exception('While executing INSERT INTO statement: ' . $db->lastErrorMsg());
}
@@ -629,9 +703,9 @@ if ($_POST['action'] == 'get_groups') {
$db->query('COMMIT;');
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'delete_adlist') {
// Delete adlist identified by ID
@@ -663,9 +737,9 @@ if ($_POST['action'] == 'get_groups') {
}
$reload = true;
- return JSON_success();
+ JSON_success();
} catch (\Exception $ex) {
- return JSON_error($ex->getMessage());
+ JSON_error($ex->getMessage());
}
} else {
log_and_die('Requested action not supported!');
diff --git a/scripts/pi-hole/php/header.php b/scripts/pi-hole/php/header.php
index bbdc13f4..81622eb2 100644
--- a/scripts/pi-hole/php/header.php
+++ b/scripts/pi-hole/php/header.php
@@ -183,15 +183,15 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
@@ -420,13 +420,13 @@ if($auth) {
class="active">
-
+
Whitelist
class="active">
-
+
Blacklist
diff --git a/scripts/pi-hole/php/savesettings.php b/scripts/pi-hole/php/savesettings.php
index a7524b77..6c8b1e89 100644
--- a/scripts/pi-hole/php/savesettings.php
+++ b/scripts/pi-hole/php/savesettings.php
@@ -35,7 +35,7 @@ function istrue(&$argument) {
// Credit: http://stackoverflow.com/a/4694816/2087442
function validDomain($domain_name)
{
- $validChars = preg_match("/^([_a-z\d](-*[_a-z\d])*)(\.([_a-z\d](-*[a-z\d])*))*(\.([a-z\d])*)*$/i", $domain_name);
+ $validChars = preg_match("/^([_a-z\d](-*[_a-z\d])*)(\.([_a-z\d](-*[a-z\d])*))*(\.([_a-z\d])*)*$/i", $domain_name);
$lengthCheck = preg_match("/^.{1,253}$/", $domain_name);
$labelLengthCheck = preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name);
return ( $validChars && $lengthCheck && $labelLengthCheck ); //length of each label
@@ -44,7 +44,7 @@ function validDomain($domain_name)
function validDomainWildcard($domain_name)
{
// There has to be either no or at most one "*" at the beginning of a line
- $validChars = preg_match("/^((\*.)?[_a-z\d](-*[_a-z\d])*)(\.([_a-z\d](-*[a-z\d])*))*(\.([a-z\d])*)*$/i", $domain_name);
+ $validChars = preg_match("/^((\*.)?[_a-z\d](-*[_a-z\d])*)(\.([_a-z\d](-*[a-z\d])*))*(\.([_a-z\d])*)*$/i", $domain_name);
$lengthCheck = preg_match("/^.{1,253}$/", $domain_name);
$labelLengthCheck = preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name);
return ( $validChars && $lengthCheck && $labelLengthCheck ); //length of each label
@@ -174,24 +174,6 @@ function readDNSserversList()
}
require_once("database.php");
-$adlist = [];
-function readAdlists()
-{
- // Reset list
- $list = [];
- $db = SQLite3_connect(getGravityDBFilename());
- if ($db)
- {
- $results = $db->query("SELECT * FROM adlist");
-
- while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC))
- {
- array_push($list, $res);
- }
- $db->close();
- }
- return $list;
-}
function addStaticDHCPLease($mac, $ip, $hostname) {
global $error, $success, $dhcp_static_leases;
@@ -251,8 +233,6 @@ function addStaticDHCPLease($mac, $ip, $hostname) {
}
}
- // Read available adlists
- $adlist = readAdlists();
// Read available DNS server list
$DNSserverslist = readDNSserversList();
@@ -555,9 +535,9 @@ function addStaticDHCPLease($mac, $ip, $hostname) {
$adminemail = trim($_POST["adminemail"]);
if(strlen($adminemail) == 0 || !isset($adminemail))
{
- $adminemail = 'noadminemail';
+ $adminemail = '';
}
- elseif(!validEmail($adminemail))
+ if(strlen($adminemail) > 0 && !validEmail($adminemail))
{
$error .= "Administrator email address (".htmlspecialchars($adminemail).") is invalid! ";
}
@@ -698,41 +678,6 @@ function addStaticDHCPLease($mac, $ip, $hostname) {
break;
- case "adlists":
- foreach ($adlist as $key => $value)
- {
- if(isset($_POST["adlist-del-".$key]))
- {
- // Delete list
- exec("sudo pihole -a adlist del ".escapeshellcmd($value["address"]));
- }
- elseif(isset($_POST["adlist-enable-".$key]) && $value["enabled"] !== 1)
- {
- // Is not enabled, but should be
- exec("sudo pihole -a adlist enable ".escapeshellcmd($value["address"]));
-
- }
- elseif(!isset($_POST["adlist-enable-".$key]) && $value["enabled"] === 1)
- {
- // Is enabled, but shouldn't be
- exec("sudo pihole -a adlist disable ".escapeshellcmd($value["address"]));
- }
- }
-
- if(strlen($_POST["newuserlists"]) > 1)
- {
- $domains = array_filter(preg_split('/\r\n|[\r\n]/', $_POST["newuserlists"]));
- $comment = "'".$_POST["newusercomment"]."'";
- foreach($domains as $domain)
- {
- exec("sudo pihole -a adlist add ".escapeshellcmd($domain)." ".escapeshellcmd($comment));
- }
- }
-
- // Reread available adlists
- $adlist = readAdlists();
- break;
-
case "privacyLevel":
$level = intval($_POST["privacylevel"]);
if($level >= 0 && $level <= 4)
diff --git a/scripts/pi-hole/php/sub.php b/scripts/pi-hole/php/sub.php
deleted file mode 100644
index cdee3ceb..00000000
--- a/scripts/pi-hole/php/sub.php
+++ /dev/null
@@ -1,51 +0,0 @@
-
diff --git a/scripts/pi-hole/php/teleporter.php b/scripts/pi-hole/php/teleporter.php
index 7c34c47f..84f96bc8 100644
--- a/scripts/pi-hole/php/teleporter.php
+++ b/scripts/pi-hole/php/teleporter.php
@@ -185,7 +185,7 @@ function archive_restore_table($file, $table, $flush=false)
/**
* Create table rows from an uploaded archive file
*
- * @param $file object The file of the file in the archive to import
+ * @param $file object The file in the archive to import
* @param $table string The target table
* @param $flush boolean Whether to flush the table before importing the archived data
* @param $wildcardstyle boolean Whether to format the input domains in legacy wildcard notation
@@ -193,22 +193,69 @@ function archive_restore_table($file, $table, $flush=false)
*/
function archive_insert_into_table($file, $table, $flush=false, $wildcardstyle=false)
{
- global $db, $flushed_tables;
+ global $db;
$domains = array_filter(explode("\n",file_get_contents($file)));
// Return early if we cannot extract the lines in the file
if(is_null($domains))
return 0;
- // Flush table if requested, only flush each table once
- if($flush && !in_array($table, $flushed_tables))
- {
- $db->exec("DELETE FROM ".$table);
- array_push($flushed_tables, $table);
+ // Generate comment
+ $prefix = "phar:///tmp/";
+ if (substr($file, 0, strlen($prefix)) == $prefix) {
+ $file = substr($file, strlen($prefix));
+ }
+ $comment = "Imported from ".$file;
+
+ // Determine table and type to import to
+ $type = null;
+ if($table === "whitelist") {
+ $table = "domainlist";
+ $type = ListType::whitelist;
+ } else if($table === "blacklist") {
+ $table = "domainlist";
+ $type = ListType::blacklist;
+ } else if($table === "regex_blacklist") {
+ $table = "domainlist";
+ $type = ListType::regex_blacklist;
+ } else if($table === "domain_audit") {
+ $table = "domain_audit";
+ $type = -1; // -1 -> not used inside add_to_table()
+ } else if($table === "adlist") {
+ $table = "adlist";
+ $type = -1; // -1 -> not used inside add_to_table()
+ }
+
+ // Flush table if requested
+ if($flush) {
+ flush_table($table, $type);
}
// Add domains to requested table
- return add_to_table($db, $table, $domains, $wildcardstyle, true);
+ return add_to_table($db, $table, $domains, $comment, $wildcardstyle, true, $type);
+}
+
+/**
+ * Flush table if requested. This subroutine flushes each table only once
+ *
+ * @param $table string The target table
+ * @param $type integer Type of item to flush in table (applies only to domainlist table)
+ */
+function flush_table($table, $type=null)
+{
+ global $db, $flushed_tables;
+
+ if(!in_array($table, $flushed_tables))
+ {
+ if($type !== null) {
+ $sql = "DELETE FROM ".$table." WHERE type = ".$type;
+ array_push($flushed_tables, $table.$type);
+ } else {
+ $sql = "DELETE FROM ".$table;
+ array_push($flushed_tables, $table);
+ }
+ $db->exec($sql);
+ }
}
function archive_add_directory($path,$subdir="")
@@ -311,7 +358,14 @@ if(isset($_POST["action"]))
if(isset($_POST["auditlog"]) && $file->getFilename() === "auditlog.list")
{
$num = archive_insert_into_table($file, "domain_audit", $flushtables);
- echo "Processed blacklist (regex) (".$num." entries) \n";
+ echo "Processed audit log (".$num." entries) \n";
+ $importedsomething = true;
+ }
+
+ if(isset($_POST["adlist"]) && $file->getFilename() === "adlists.list")
+ {
+ $num = archive_insert_into_table($file, "adlist", $flushtables);
+ echo "Processed adlists (".$num." entries) \n";
$importedsomething = true;
}
diff --git a/settings.php b/settings.php
index 328f685b..18826b59 100644
--- a/settings.php
+++ b/settings.php
@@ -244,67 +244,10 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
diff --git a/style/pi-hole.css b/style/pi-hole.css
index ac605552..135dd2c8 100644
--- a/style/pi-hole.css
+++ b/style/pi-hole.css
@@ -238,3 +238,25 @@
.text-vivid-blue {
color: #36f !important;
}
+
+td.highlight {
+ background-color: yellow;
+}
+
+code.breakall
+{
+ white-space: -moz-pre-wrap;
+ white-space: -pre-wrap;
+ white-space: -o-pre-wrap;
+ white-space: pre-wrap;
+ word-break: break-all;
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
+}
+
+.underline {
+ text-decoration: underline;
+}
+
+.pointer {
+ cursor: pointer;
+}
\ No newline at end of file