Merge pull request #1625 from pi-hole/tweak/replace_domains

Add new replace_domain action to allow adding domain(s) exclusively to a specific list
This commit is contained in:
Adam Warner
2020-11-28 16:25:38 +00:00
committed by GitHub
6 changed files with 202 additions and 154 deletions
+35
View File
@@ -123,6 +123,40 @@
<!-- ./col -->
</div>
<!-- Alert Modal -->
<div id="alertModal" class="modal fade" role="dialog" data-backdrop="static" data-keyboard="false">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center">
<div class="modal-content">
<div class="modal-body text-center">
<span class="fa-stack fa-2x" style="margin-bottom: 10px">
<div class="alProcessing">
<i class="fa-stack-2x alSpinner"></i>
</div>
<div class="alSuccess" style="display: none">
<i class="fa fa-circle fa-stack-2x text-green"></i>
<i class="fa fa-check fa-stack-1x fa-inverse"></i>
</div>
<div class="alFailure" style="display: none">
<i class="fa fa-circle fa-stack-2x text-red"></i>
<i class="fa fa-times fa-stack-1x fa-inverse"></i>
</div>
</span>
<div class="alProcessing">Adding <span id="alDomain"></span> to the <span id="alList"></span>...</div>
<div class="alSuccess text-bold text-green" style="display: none"><span id="alDomain"></span> successfully added to the <span id="alList"></span></div>
<div class="alFailure text-bold text-red" style="display: none">
<span id="alNetErr">Timeout or Network Connection Error!</span>
<span id="alCustomErr"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="box" id="recent-queries">
@@ -162,6 +196,7 @@
<!-- /.row -->
<script src="scripts/pi-hole/js/ip-address-sorting.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/daterangepicker.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/db_queries.js?v=<?=$cacheVer?>"></script>
<?php
+3
View File
@@ -123,6 +123,9 @@ if(strlen($showing) > 0)
<span id="alCustomErr"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
+3 -67
View File
@@ -5,7 +5,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
/* global moment:false */
/* global moment:false, utils:false */
var start__ = moment().subtract(6, "days");
var from = moment(start__).utc().valueOf() / 1000;
@@ -73,70 +73,6 @@ $(function () {
var tableApi, statistics;
function add(domain, list) {
var token = $("#token").text();
var alInfo = $("#alInfo");
var alList = $("#alList");
var alDomain = $("#alDomain");
alDomain.html(domain);
var alSuccess = $("#alSuccess");
var alFailure = $("#alFailure");
var err = $("#err");
if (list === "white") {
alList.html("Whitelist");
} else {
alList.html("Blacklist");
}
alInfo.show();
alSuccess.hide();
alFailure.hide();
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
data: {
domain: domain,
list: list,
token: token,
action: "add_domain",
comment: "Added from Long-Term-Data Query Log"
},
success: function (response) {
if (!response.success) {
alFailure.show();
err.html(response.message);
alFailure.delay(4000).fadeOut(2000, function () {
alFailure.hide();
});
} else {
alSuccess.show();
alSuccess.delay(1000).fadeOut(2000, function () {
alSuccess.hide();
});
}
alInfo.delay(1000).fadeOut(2000, function () {
alInfo.hide();
alList.html("");
alDomain.html("");
});
},
error: function () {
alFailure.show();
err.html("");
alFailure.delay(1000).fadeOut(2000, function () {
alFailure.hide();
});
alInfo.delay(1000).fadeOut(2000, function () {
alInfo.hide();
alList.html("");
alDomain.html("");
});
}
});
}
function handleAjaxError(xhr, textStatus) {
if (textStatus === "timeout") {
alert("The server took too long to send the data.");
@@ -409,9 +345,9 @@ $(function () {
$("#all-queries tbody").on("click", "button", function () {
var data = tableApi.row($(this).parents("tr")).data();
if ([1, 4, 5, 9, 10, 11].indexOf(data[4]) !== -1) {
add(data[2], "white");
utils.addFromQueryLog(data[2], "white");
} else {
add(data[2], "black");
utils.addFromQueryLog(data[2], "black");
}
});
+2 -77
View File
@@ -25,81 +25,6 @@ var replyTypes = [
];
var colTypes = ["time", "query type", "domain", "client", "status", "reply type"];
function add(domain, list) {
var token = $("#token").text();
var alertModal = $("#alertModal");
var alProcessing = alertModal.find(".alProcessing");
var alSuccess = alertModal.find(".alSuccess");
var alFailure = alertModal.find(".alFailure");
var alNetworkErr = alertModal.find(".alFailure #alNetErr");
var alCustomErr = alertModal.find(".alFailure #alCustomErr");
var alList = "#alList";
var alDomain = "#alDomain";
// Exit the function here if the Modal is already shown (multiple running interlock)
if (alertModal.css("display") !== "none") {
return;
}
var listtype = list === "white" ? "Whitelist" : "Blacklist";
alProcessing.children(alDomain).html(domain);
alProcessing.children(alList).html(listtype);
alertModal.modal("show");
// add Domain to List after Modal has faded in
alertModal.one("shown.bs.modal", function () {
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
data: {
domain: domain,
list: list,
token: token,
action: "add_domain",
comment: "Added from Query Log"
},
success: function (response) {
alProcessing.hide();
if (!response.success) {
// Failure
alNetworkErr.hide();
alCustomErr.html(response.message);
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 3000);
} else {
// Success
alSuccess.children(alDomain).html(domain);
alSuccess.children(alList).html(listtype);
alSuccess.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 2000);
}
},
error: function () {
// Network Error
alProcessing.hide();
alNetworkErr.show();
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 3000);
}
});
});
// Reset Modal after it has faded out
alertModal.one("hidden.bs.modal", function () {
alProcessing.show();
alSuccess.add(alFailure).hide();
alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html("");
alCustomErr.html("");
});
}
function handleAjaxError(xhr, textStatus) {
if (textStatus === "timeout") {
alert("The server took too long to send the data.");
@@ -493,9 +418,9 @@ $(function () {
$("#all-queries tbody").on("click", "button", function () {
var data = tableApi.row($(this).parents("tr")).data();
if (data[4] === "2" || data[4] === "3") {
add(data[2], "black");
utils.addFromQueryLog(data[2], "black");
} else {
add(data[2], "white");
utils.addFromQueryLog(data[2], "white");
}
});
+77 -1
View File
@@ -248,6 +248,81 @@ function getGraphType() {
return localStorage.getItem("barchart_chkbox") === "false" ? "line" : "bar";
}
function addFromQueryLog(domain, list) {
var token = $("#token").text();
var alertModal = $("#alertModal");
var alProcessing = alertModal.find(".alProcessing");
var alSuccess = alertModal.find(".alSuccess");
var alFailure = alertModal.find(".alFailure");
var alNetworkErr = alertModal.find(".alFailure #alNetErr");
var alCustomErr = alertModal.find(".alFailure #alCustomErr");
var alList = "#alList";
var alDomain = "#alDomain";
// Exit the function here if the Modal is already shown (multiple running interlock)
if (alertModal.css("display") !== "none") {
return;
}
var listtype = list === "white" ? "Whitelist" : "Blacklist";
alProcessing.children(alDomain).html(domain);
alProcessing.children(alList).html(listtype);
alertModal.modal("show");
// add Domain to List after Modal has faded in
alertModal.one("shown.bs.modal", function () {
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
data: {
domain: domain,
list: list,
token: token,
action: "replace_domain",
comment: "Added from Query Log"
},
success: function (response) {
alProcessing.hide();
if (!response.success) {
// Failure
alNetworkErr.hide();
alCustomErr.html(response.message);
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 10000);
} else {
// Success
alSuccess.children(alDomain).html(domain);
alSuccess.children(alList).html(listtype);
alSuccess.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 2000);
}
},
error: function () {
// Network Error
alProcessing.hide();
alNetworkErr.show();
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 8000);
}
});
});
// Reset Modal after it has faded out
alertModal.one("hidden.bs.modal", function () {
alProcessing.show();
alSuccess.add(alFailure).hide();
alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html("");
alCustomErr.html("");
});
}
window.utils = (function () {
return {
escapeHtml: escapeHtml,
@@ -265,6 +340,7 @@ window.utils = (function () {
stateLoadCallback: stateLoadCallback,
getGraphType: getGraphType,
validateMAC: validateMAC,
validateHostname: validateHostname
validateHostname: validateHostname,
addFromQueryLog: addFromQueryLog
};
})();
+82 -9
View File
@@ -507,18 +507,41 @@ if ($_POST['action'] == 'get_groups') {
} catch (\Exception $ex) {
JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'add_domain') {
} elseif ($_POST['action'] == 'add_domain' || $_POST['action'] == 'replace_domain') {
// Add new domain
try {
$domains = explode(' ', html_entity_decode(trim($_POST['domain'])));
$before = intval($db->querySingle("SELECT COUNT(*) FROM domainlist;"));
$total = count($domains);
$added = 0;
$stmt = $db->prepare('REPLACE INTO domainlist (domain,type,comment) VALUES (:domain,:type,:comment)');
if (!$stmt) {
// Prepare INSERT INTO statement
$insert_stmt = $db->prepare('INSERT OR IGNORE INTO domainlist (domain,type) VALUES (:domain,:type)');
if (!$insert_stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
// Prepare UPDATE statement
$update_stmt = $db->prepare('UPDATE domainlist SET comment = :comment WHERE domain = :domain AND type = :type');
if (!$update_stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
$check_stmt = null;
$delete_stmt = null;
if($_POST['action'] == 'replace_domain') {
// Check statement will reveal any group associations for a given (domain,type) which do NOT belong to the default group
$check_stmt = $db->prepare('SELECT EXISTS(SELECT domain FROM domainlist_by_group dlbg JOIN domainlist dl on dlbg.domainlist_id = dl.id WHERE dl.domain = :domain AND dlbg.group_id != 0)');
if (!$check_stmt) {
throw new Exception('While preparing check statement: ' . $db->lastErrorMsg());
}
// Delete statement will remove this domain from any type of list
$delete_stmt = $db->prepare('DELETE FROM domainlist WHERE domain = :domain');
if (!$delete_stmt) {
throw new Exception('While preparing delete statement: ' . $db->lastErrorMsg());
}
}
if (isset($_POST['type'])) {
$type = intval($_POST['type']);
} else if (isset($_POST['list']) && $_POST['list'] === "white") {
@@ -527,7 +550,8 @@ if ($_POST['action'] == 'get_groups') {
$type = ListType::blacklist;
}
if (!$stmt->bindValue(':type', $type, SQLITE3_TEXT)) {
if (!$insert_stmt->bindValue(':type', $type, SQLITE3_TEXT) ||
!$update_stmt->bindValue(':type', $type, SQLITE3_TEXT)) {
throw new Exception('While binding type: ' . $db->lastErrorMsg());
}
@@ -536,7 +560,7 @@ if ($_POST['action'] == 'get_groups') {
// Store NULL in database for empty comments
$comment = null;
}
if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
if (!$update_stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
throw new Exception('While binding comment: ' . $db->lastErrorMsg());
}
@@ -563,7 +587,7 @@ if ($_POST['action'] == 'get_groups') {
}
}
if(strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
if(isset($_POST['type']) && strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
{
// Apply wildcard-style formatting
$domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
@@ -586,13 +610,62 @@ if ($_POST['action'] == 'get_groups') {
}
}
if (!$stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
// First try to delete any occurrences of this domain if we're in
// replace mode. Only do this when the domain to be replaced is in
// the default group! Otherwise, we would shuffle group settings and
// just throw an error at the user to tell them to change this
// domain manually. This ensures user's will really get what they
// want from us.
if($_POST['action'] == 'replace_domain') {
if (!$check_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain to check: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
$check_result = $check_stmt->execute();
if (!$check_result) {
throw new Exception('While executing check: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
// Check return value of CHECK query (0 = only default group, 1 = special group assignments)
$only_default_group = (($check_result->fetchArray(SQLITE3_NUM)[0]) == 0) ? true : false;
if(!$only_default_group) {
throw new Exception('Domain ' . $domain . ' is configured with special group settings.<br>'.
'Please modify the domain on the respective group management pages.');
}
if (!$delete_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
if (!$delete_stmt->execute()) {
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
}
if (!$insert_stmt->bindValue(':domain', $domain, SQLITE3_TEXT) ||
!$update_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
if (!$stmt->execute()) {
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
// First execute INSERT OR IGNORE statement to create a record for
// this domain (ignore if already existing)
if (!$insert_stmt->execute()) {
throw new Exception('While executing INSERT OT IGNORE: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
// Then update the record with a new comment (and modification date
// due to the trigger event) We are not using REPLACE INTO to avoid
// the initial DELETE event (loosing group assignments in case an
// entry did already exist).
if (!$update_stmt->execute()) {
throw new Exception('While executing UPDATE: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
$added++;