Merge branch 'release/v5.0' into fix/queries_over_time

This commit is contained in:
DL6ER
2020-03-31 21:17:46 +00:00
41 changed files with 1388 additions and 1245 deletions
-5
View File
@@ -68,14 +68,9 @@
</div>
<!-- /.box -->
</div>
<div class="col-md-12">
<p><strong>Important:</strong> Note that black- and whitelisted domains are not automatically applied on this page to avoid restarting the DNS service too often. Instead, click on this button, to have the new settings become effective:</p>
</div>
<!-- /.col -->
</div>
<!-- /.row -->
<button class="btn btn-lg btn-primary btn-block" id="gravityBtn" disabled="true">Update black-/whitelists</button>
<script src="scripts/pi-hole/js/auditlog.js"></script>
+2 -2
View File
@@ -29,11 +29,11 @@
<div class="box-body">
<div class="row">
<div class="col-md-6">
<label for="ex1">Name:</label>
<label for="domain">Domain:</label>
<input id="domain" type="text" class="form-control" placeholder="Add a domain (example.com or sub.example.com)">
</div>
<div class="col-md-6">
<label for="ex2">Description:</label>
<label for="ip">IP Address:</label>
<input id="ip" type="text" class="form-control" placeholder="Associated IP address">
</div>
</div>
+19 -14
View File
@@ -45,34 +45,39 @@ $token = $_SESSION['token'];
<label>Query status:</label>
</div>
<div class="form-group">
<div class="col-md-2">
<div class="col-md-3">
<div class="checkbox">
<label><input type="checkbox" id="type_gravity" checked>Blocked (exact)</label>
<label><input type="checkbox" id="type_forwarded" checked><strong>Permitted: </strong>forwarded</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="type_cached" checked><strong>Permitted: </strong>cached</label>
</div>
</div>
<div class="col-md-2">
<div class="col-md-3">
<div class="checkbox">
<label><input type="checkbox" id="type_forwarded" checked>OK (forwarded)</label>
<label><input type="checkbox" id="type_gravity" checked><strong>Blocked: </strong>gravity</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="type_external" checked><strong>Blocked: </strong>external</label>
</div>
</div>
<div class="col-md-2">
<div class="col-md-3">
<div class="checkbox">
<label><input type="checkbox" id="type_cached" checked>OK (cached)</label>
<label><input type="checkbox" id="type_blacklist" checked><strong>Blocked: </strong>exact blacklist</label>
</div>
<div class="checkbox">
<label><input type="checkbox" id="type_regex" checked><strong>Blocked: </strong>regex blacklist</label>
</div>
</div>
<div class="col-md-2">
<div class="col-md-3">
<div class="checkbox">
<label><input type="checkbox" id="type_regex" checked>Blocked (regex/wildcard)</label>
<label><input type="checkbox" id="type_gravity_CNAME" checked><strong>Blocked: </strong>gravity (CNAME)</label>
</div>
</div>
<div class="col-md-2">
<div class="checkbox">
<label><input type="checkbox" id="type_blacklist" checked>Blocked (blacklist)</label>
<label><input type="checkbox" id="type_blacklist_CNAME" checked><strong>Blocked: </strong>exact blacklist (CNAME)</label>
</div>
</div>
<div class="col-md-2">
<div class="checkbox">
<label><input type="checkbox" id="type_external" checked>Blocked (external)</label>
<label><input type="checkbox" id="type_regex_CNAME" checked><strong>Blocked: </strong>regex blacklist (CNAME)</label>
</div>
</div>
</div>
+1 -1
View File
@@ -15,7 +15,7 @@
<!-- Alerts -->
<div id="alInfo" class="alert alert-info alert-dismissible fade in" role="alert" hidden="true">
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
Updating...
Updating...this may take a while. <strong>Please do not navigate away from or close this page.</strong>
</div>
<div id="alSuccess" class="alert alert-success alert-dismissible fade in" role="alert" hidden="true">
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+2
View File
@@ -37,6 +37,7 @@
</div>
</div>
<div class="box-footer clearfix">
<strong>Hint:</strong>&nbsp;Please run <code>pihole -g</code> or update your gravity list <a href="gravity.php">online</a> after modifying your adlists.
<button id="btnAdd" class="btn btn-primary pull-right">Add</button>
</div>
</div>
@@ -72,6 +73,7 @@
</div>
</div>
<script src="scripts/pi-hole/js/groups-common.js"></script>
<script src="scripts/pi-hole/js/groups-adlists.js"></script>
<?php
+6 -3
View File
@@ -30,11 +30,12 @@
<label for="ex1">Known clients:</label>
<select id="select" class="form-control" placeholder="">
<option disabled selected>Loading...</option>
</select>
</select><br>
<input id="ip-custom" type="text" class="form-control" disabled placeholder="Client IP address (IPv4 or IPv6, CIDR subnetting available, optional)">
</div>
<div class="col-md-6">
<label for="ex2">Custom client:</label>
<input id="ip-custom" type="text" class="form-control" disabled placeholder="Client IP address (IPv4 or IPv6, optional)">
<label for="ex3">Comment:</label>
<input id="new_comment" type="text" class="form-control" placeholder="Client description (optional)">
</div>
</div>
</div>
@@ -59,6 +60,7 @@
<tr>
<th>ID</th>
<th>IP address</th>
<th>Comment</th>
<th>Group assignment</th>
<th>Action</th>
</tr>
@@ -72,6 +74,7 @@
</div>
</div>
<script src="scripts/pi-hole/js/groups-common.js"></script>
<script src="scripts/pi-hole/js/groups-clients.js"></script>
<?php
+79 -25
View File
@@ -6,57 +6,110 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
require "scripts/pi-hole/php/header.php";
$type = "all";
$pagetitle = "Domain";
$adjective = "";
if (isset($_GET['type']) && ($_GET['type'] === "white" || $_GET['type'] === "black")) {
$type = $_GET['type'];
$pagetitle = ucfirst($type)."list";
$adjective = $type."listed";
}
?>
<!-- Title -->
<div class="page-header">
<h1>Domain group management</h1>
<h1><?php echo $pagetitle; ?> management</h1>
</div>
<!-- Domain Input -->
<div class="row">
<div class="col-md-12">
<div class="box" id="add-group">
<!-- /.box-header -->
<div class="box-header with-border">
<h3 class="box-title">
Add a new domain
Add a new <?php echo $adjective; ?> domain or regex filter
</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="col-md-6">
<label for="ex1">Domain:</label>
<input id="new_domain" type="text" class="form-control" placeholder="Domain to be added">
</div>
<div class="col-md-2">
<label for="ex2">Type:</label>
<select id="new_type" class="form-control">
<option value="0">Exact whitelist</option>
<option value="1">Exact blacklist</option>
<option value="2">Regex whitelist</option>
<option value="3">Regex blacklist</option>
</select>
</div>
<div class="col-md-4">
<label for="ex3">Comment:</label>
<input id="new_comment" type="text" class="form-control" placeholder="Domain description (optional)">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active"><a aria-expanded="true" href="#tab_domain" data-toggle="tab">Domain</a></li>
<li class=""><a aria-expanded="false" href="#tab_regex" data-toggle="tab">RegEx filter</a></li>
</ul>
<div class="tab-content">
<!-- Domain tab -->
<div id="tab_domain" class="tab-pane active in">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="new_domain">Domain:</label>
<div class="input-group">
<input id="new_domain" type="text" class="form-control active" placeholder="Domain to be added">
<span class="input-group-addon">
<input type="checkbox" id="wildcard_checkbox">
wildcard</span>
</span>
</div>
</div>
<div class="form-group">
<strong><i class="fa fa-question-circle"></i> Checkbox "wildcard":</span></strong> Check this box if you want to involve
all subdomains. The entered domain will be converted to a RegEx filter while adding.
</div>
</div>
<div class="col-md-6 form-group">
<label for="new_domain_comment">Comment:</label>
<input id="new_domain_comment" type="text" class="form-control" placeholder="Description (optional)">
</div>
</div>
</div>
<!-- RegEx tab -->
<div id="tab_regex" class="tab-pane">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="new_regex">Regular Expression:</label>
<input id="new_regex" type="text" class="form-control active" placeholder="RegEx to be added">
</div>
<div class="form-group">
<strong>Hint:</strong> Need help to write a proper RegEx rule? Have a look at our online
<a href="https://docs.pi-hole.net/ftldns/regex/tutorial" rel="noopener" target="_blank">
regular expressions tutorial</a>.
</div>
</div>
<div class="form-group col-md-6">
<label for="new_regex_comment">Comment:</label>
<input id="new_regex_comment" type="text" class="form-control" placeholder="Description (optional)">
</div>
</div>
</div>
</div>
</div>
<div class="btn-toolbar pull-right" role="toolbar" aria-label="Toolbar with buttons">
<?php if ( $type !== "white" ) { ?>
<div class="btn-group" role="group" aria-label="Third group">
<button type="button" class="btn btn-primary" id="add2black">Add to Blacklist</button>
</div>
<?php } if ( $type !== "black" ) { ?>
<div class="btn-group" role="group" aria-label="Third group">
<button type="button" class="btn btn-primary" id="add2white">Add to Whitelist</button>
</div>
<?php } ?>
</div>
</div>
<div class="box-footer clearfix">
<button id="btnAdd" class="btn btn-primary pull-right">Add</button>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</div>
<!-- Domain List -->
<div class="row">
<div class="col-md-12">
<div class="box" id="domains-list">
<div class="box-header with-border">
<h3 class="box-title">
List of configured domains
List of <?php echo $adjective; ?> entries
</h3>
</div>
<!-- /.box-header -->
@@ -65,7 +118,7 @@
<thead>
<tr>
<th>ID</th>
<th>Domain</th>
<th>Domain/RegEx</th>
<th>Type</th>
<th>Status</th>
<th>Comment</th>
@@ -82,6 +135,7 @@
</div>
</div>
<script src="scripts/pi-hole/js/groups-common.js"></script>
<script src="scripts/pi-hole/js/groups-domains.js"></script>
<?php
+1
View File
@@ -71,6 +71,7 @@
</div>
</div>
<script src="scripts/pi-hole/js/groups-common.js"></script>
<script src="scripts/pi-hole/js/groups.js"></script>
<?php
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"name": "Pi-hole Admin Console",
"short_name": "Pi-hole",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#367fa9",
"background_color": "#367fa9",
"display": "standalone"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

-84
View File
@@ -1,84 +0,0 @@
<?php /*
* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
require "scripts/pi-hole/php/header.php";
$list = isset($_GET['l']) ? $_GET['l'] : '';
if ($list == "white")
{
$listtitle = "Whitelist";
$heading = "whitelisting";
}
elseif ($list == "black")
{
$listtitle = "Blacklist";
$heading = "blocking";
}
else
{
echo "Invalid list parameter ".$list;
require "scripts/pi-hole/php/footer.php";
die();
}
?>
<!-- Send list type to JS -->
<div id="list-type" hidden><?php echo $list ?></div>
<!-- Title -->
<div class="page-header">
<h1><?php echo $listtitle; ?></h1>
</div>
<div class="row">
<div class="col-md-6">
<label for="ex1">Domain:</label>
<input id="domain" type="text" class="form-control" placeholder="Add a domain (example.com)">
</div>
<div class="col-md-6">
<label for="ex2">Comment:</label>
<div class="form-group input-group">
<input id="comment" type="text" class="form-control" placeholder="Include a comment (optional)">
<span class="input-group-btn">
<button id="btnAdd" class="btn btn-default" type="button">Add (exact)</button>
<button id="btnAddWildcard" class="btn btn-default" type="button">Add (wildcard)</button>
<button id="btnAddRegex" class="btn btn-default" type="button">Add (regex)</button>
<button id="btnRefresh" class="btn btn-default" type="button"><i class="fa fa-sync"></i></button>
</span>
</div>
</div>
</div>
<!-- Alerts -->
<div id="alInfo" class="alert alert-info alert-dismissible fade in" role="alert" hidden="true">
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
Adding to the <?php echo $listtitle; ?>...
</div>
<div id="alSuccess" class="alert alert-success alert-dismissible fade in" role="alert" hidden="true">
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span id="success-message"></span>
</div>
<div id="alFailure" class="alert alert-danger alert-dismissible fade in" role="alert" hidden="true">
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
Failure! Something went wrong, see output below:<br/><br/><pre><span id="err"></span></pre>
</div>
<div id="alWarning" class="alert alert-warning alert-dismissible fade in" role="alert" hidden="true">
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
At least one domain was already present, see output below:<br/><br/><pre><span id="warn"></span></pre>
</div>
<!-- Domain List -->
<h3 id="h3-exact" hidden="true">Exact <?php echo $heading; ?></h3>
<ul class="list-group" id="list"></ul>
<h3 id="h3-regex" hidden="true"><a href="https://docs.pi-hole.net/ftldns/regex/overview/" rel="noopener" target="_blank" title="Click for Pi-hole Regex documentation">Regex</a> &amp; Wildcard <?php echo $heading; ?></h3>
<ul class="list-group" id="list-regex"></ul>
<script src="scripts/pi-hole/js/list.js"></script>
<?php
require "scripts/pi-hole/php/footer.php";
?>
+39 -39
View File
@@ -6,8 +6,7 @@
* Please see LICENSE file for your rights under this license. */
// Define global variables
var auditList = [],
auditTimeout;
var auditTimeout = null;
// Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406
function escapeHtml(text) {
@@ -46,7 +45,7 @@ function updateTopLists() {
domain = escapeHtml(domain);
url = '<a href="queries.php?domain=' + domain + '">' + domain + "</a>";
domaintable.append(
"<tr> <td>" +
"<tr><td>" +
url +
"</td> <td>" +
data.top_queries[domain] +
@@ -68,7 +67,7 @@ function updateTopLists() {
printdomain +
"</a> (wildcard blocked)";
adtable.append(
"<tr> <td>" +
"<tr><td>" +
url +
"</td> <td>" +
data.top_ads[domain] +
@@ -77,7 +76,7 @@ function updateTopLists() {
} else {
url = '<a href="queries.php?domain=' + printdomain + '">' + printdomain + "</a>";
adtable.append(
"<tr> <td>" +
"<tr><td>" +
url +
"</td> <td>" +
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");
});
+32 -2
View File
@@ -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 <br class='hidden-lg'>(regex/wildcard)";
fieldtext = "Blocked <br class='hidden-lg'>(regex blacklist)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
case 5:
color = "red";
fieldtext = "Blocked <br class='hidden-lg'>(blacklist)";
fieldtext = "Blocked <br class='hidden-lg'>(exact blacklist)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
@@ -307,6 +319,24 @@ $(document).ready(function() {
fieldtext = "Blocked <br class='hidden-lg'>(external, NXRA)";
buttontext = "";
break;
case 9:
color = "red";
fieldtext = "Blocked (gravity, CNAME)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
case 10:
color = "red";
fieldtext = "Blocked <br class='hidden-lg'>(regex blacklist, CNAME)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
case 11:
color = "red";
fieldtext = "Blocked <br class='hidden-lg'>(exact blacklist, CNAME)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
default:
color = "black";
fieldtext = "Unknown";
+119 -125
View File
@@ -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").html();
var info = null;
function showAlert(type, icon, title, message) {
var opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
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: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
default:
}
}
function get_groups() {
$.post(
@@ -83,19 +23,10 @@ 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");
});
});
function initTable() {
@@ -115,46 +46,50 @@ 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(
'<code id="address" title="' + tooltip + '">' + data.address + "</code>"
'<code id="address_' + data.id + '" title="' + tooltip + '">' + data.address + "</code>"
);
var disabled = data.enabled === 0;
$("td:eq(1)", row).html(
'<input type="checkbox" id="status"' + (disabled ? "" : " checked") + ">"
'<input type="checkbox" id="status_' + data.id + '"' + (disabled ? "" : " checked") + ">"
);
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(
'<input id="comment" class="form-control"><input id="id" type="hidden" value="' +
data.id +
'">'
);
var comment = $("#comment", row);
comment.val(data.comment);
comment.on("change", editAdlist);
$("td:eq(2)", row).html('<input id="comment_' + data.id + '" class="form-control">');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(data.comment);
commentEl.on("change", editAdlist);
$("td:eq(3)", row).empty();
$("td:eq(3)", row).append('<select id="multiselect" multiple="multiple"></select>');
var sel = $("#multiselect", row);
$("td:eq(3)", row).append(
'<div id="selectHome_' +
data.id +
'">' +
'<select id="multiselect_' +
data.id +
'" multiple="multiple"></select></div>'
);
var selectEl = $("#multiselect_" + data.id, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
@@ -162,7 +97,7 @@ function initTable() {
extra = " (disabled)";
}
sel.append(
selectEl.append(
$("<option />")
.val(groups[i].id)
.text(groups[i].name + extra)
@@ -170,13 +105,41 @@ 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: '<div id="container_' + data.id + '" class="btn-group"/>',
maxHeight: 200,
onDropdownShown: function() {
var el = $("#container_" + data.id);
var top = el[0].getBoundingClientRect().top;
var bottom = $(window).height() - top - el.height();
if (bottom < 200) {
el.addClass("dropup");
}
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 =
'<button class="btn btn-danger btn-xs deleteAdlist" type="button" data-id="' +
'<button class="btn btn-danger btn-xs" type="button" id="deleteAdlist_' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
@@ -234,10 +197,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;
}
@@ -252,17 +216,24 @@ function addAdlist() {
token: token
},
success: function(response) {
utils.enableAll();
if (response.success) {
showAlert("success", "glyphicon glyphicon-plus", "Successfully added adlist", address);
utils.showAlert(
"success",
"glyphicon glyphicon-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);
}
});
@@ -271,29 +242,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",
@@ -308,15 +290,16 @@ function editAdlist() {
token: token
},
success: function(response) {
utils.enableAll();
if (response.success) {
showAlert(
utils.showAlert(
"success",
"glyphicon glyphicon-pencil",
"Successfully " + done + " adlist ",
address
);
} else {
showAlert(
utils.showAlert(
"error",
"",
"Error while " + not_done + " adlist with ID " + id,
@@ -325,7 +308,8 @@ function editAdlist() {
}
},
error: function(jqXHR, exception) {
showAlert(
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while " + not_done + " adlist with ID " + id,
@@ -337,27 +321,37 @@ 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", "glyphicon glyphicon-trash", "Successfully deleted adlist ", address);
utils.showAlert(
"success",
"glyphicon glyphicon-trash",
"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);
}
});
+145 -106
View File
@@ -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").html();
var info = null;
function showAlert(type, icon, title, message) {
var opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
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: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
default:
}
}
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(
$("<option />")
.val("custom")
.text("Custom, specified on the right")
.text("Custom, specified below...")
);
if (customWasSelected) {
sel.val("custom");
}
},
"json"
);
@@ -138,29 +84,43 @@ 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;
var ip_name =
'<code id="ip" title="' +
tooltip +
'">' +
data.ip +
'</code><input id="id" type="hidden" value="' +
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 = '<code id="ip_' + data.id + '" title="' + tooltip + '">' + data.ip + "</code>";
if (data.name !== null && data.name.length > 0)
ip_name += '<br><code id="name" title="' + tooltip + '">' + data.name + "</code>";
ip_name +=
'<br><code id="name_' + data.id + '" title="' + tooltip + '">' + data.name + "</code>";
$("td:eq(0)", row).html(ip_name);
$("td:eq(1)", row).empty();
$("td:eq(1)", row).append('<select id="multiselect" multiple="multiple"></select>');
var sel = $("#multiselect", row);
$("td:eq(1)", row).html('<input id="comment_' + data.id + '" class="form-control">');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(data.comment);
commentEl.on("change", editClient);
$("td:eq(2)", row).empty();
$("td:eq(2)", row).append(
'<div id="selectHome_' +
data.id +
'">' +
'<select id="multiselect_' +
data.id +
'" multiple="multiple"></select></div>'
);
var selectEl = $("#multiselect_" + data.id, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
@@ -168,7 +128,7 @@ function initTable() {
extra = " (disabled)";
}
sel.append(
selectEl.append(
$("<option />")
.val(groups[i].id)
.text(groups[i].name + extra)
@@ -176,18 +136,46 @@ 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: '<div id="container_' + data.id + '" class="btn-group"/>',
maxHeight: 200,
onDropdownShown: function() {
var el = $("#container_" + data.id);
var top = el[0].getBoundingClientRect().top;
var bottom = $(window).height() - top - el.height();
if (bottom < 200) {
el.addClass("dropup");
}
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 =
'<button class="btn btn-danger btn-xs deleteClient" type="button" data-id="' +
'<button class="btn btn-danger btn-xs" type="button" id="deleteClient_' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
"</button>";
$("td:eq(2)", row).html(button);
$("td:eq(3)", row).html(button);
},
dom:
"<'row'<'col-sm-4'l><'col-sm-8'f>>" +
@@ -238,14 +226,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;
}
@@ -253,18 +259,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", "glyphicon glyphicon-plus", "Successfully added client", ip);
utils.showAlert("success", "glyphicon glyphicon-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);
}
});
@@ -273,16 +281,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;
@@ -290,26 +308,39 @@ 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(
utils.showAlert(
"success",
"glyphicon glyphicon-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,
@@ -321,36 +352,44 @@ 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", "glyphicon glyphicon-trash", "Successfully deleted client ", ip_name);
utils.showAlert(
"success",
"glyphicon glyphicon-trash",
"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);
}
});
+141
View File
@@ -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 = "&nbsp;<strong>" + title + "</strong><br>";
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: "&nbsp;<strong>Error, something went wrong!</strong><br>",
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
};
})();
+308 -162
View File
@@ -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").html();
var info = null;
function showAlert(type, icon, title, message) {
var opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
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: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
default:
}
}
var GETDict = {};
var showtype = "all";
function get_groups() {
$.post(
@@ -83,26 +25,44 @@ 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];
});
if ("type" in GETDict && (GETDict.type === "white" || GETDict.type === "black")) {
showtype = GETDict.type;
}
// 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);
}
});
$("#add2black, #add2white").on("click", addDomain);
get_groups();
$("#select").on("change", function() {
$("#ip-custom").val("");
$("#ip-custom").prop("disabled", $("#select option:selected").val() !== "custom");
});
});
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"]],
@@ -116,89 +76,158 @@ 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(
'<code id="domain" title="' + tooltip + '">' + data.domain + "</code>"
'<code id="domain_' +
data.id +
'" title="' +
tooltip +
'" class="breakall">' +
data.domain +
"</code>"
);
$("td:eq(1)", row).html(
'<select id="type" class="form-control">' +
var whitelist_options = "";
if (showtype === "all" || showtype === "white") {
whitelist_options =
'<option value="0"' +
(data.type === 0 ? " selected" : "") +
">Exact whitelist</option>" +
'<option value="1"' +
(data.type === 1 ? " selected" : "") +
">Exact blacklist</option>" +
'<option value="2"' +
(data.type === 2 ? " selected" : "") +
">Regex whitelist</option>" +
">Regex whitelist</option>";
}
var blacklist_options = "";
if (showtype === "all" || showtype === "black") {
blacklist_options =
'<option value="1"' +
(data.type === 1 ? " selected " : " ") +
">Exact blacklist</option>" +
'<option value="3"' +
(data.type === 3 ? " selected" : "") +
">Regex blacklist</option>" +
">Regex blacklist</option>";
}
$("td:eq(1)", row).html(
'<select id="type_' +
data.id +
'" class="form-control">' +
whitelist_options +
blacklist_options +
"</select>"
);
$("#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(
'<input type="checkbox" id="status"' + (disabled ? "" : " checked") + ">"
'<input type="checkbox" id="status_' + data.id + '"' + (disabled ? "" : " checked") + ">"
);
$("#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(
'<input id="comment" class="form-control"><input id="id" type="hidden" value="' +
data.id +
'">'
);
$("#comment", row).val(data.comment);
$("#comment", row).on("change", editDomain);
$("td:eq(3)", row).html('<input id="comment_' + data.id + '" class="form-control">');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(data.comment);
commentEl.on("change", editDomain);
$("td:eq(4)", row).empty();
$("td:eq(4)", row).append('<select id="multiselect" multiple="multiple"></select>');
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(
'<div id="selectHome_' +
data.id +
'">' +
'<select id="multiselect_' +
data.id +
'" multiple="multiple"></select></div>'
);
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(
$("<option />")
.val(groups[i].id)
.text(groups[i].name + extra)
);
}
sel.append(
$("<option />")
.val(groups[i].id)
.text(groups[i].name + extra)
);
// Select assigned groups
selectEl.val(data.groups);
// Initialize multiselect
selectEl.multiselect({
includeSelectAllOption: true,
buttonContainer: '<div id="container_' + data.id + '" class="btn-group"/>',
maxHeight: 200,
onDropdownShown: function() {
var el = $("#container_" + data.id);
var top = el[0].getBoundingClientRect().top;
var bottom = $(window).height() - top - el.height();
if (bottom < 200) {
el.addClass("dropup");
}
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 =
'<button class="btn btn-danger btn-xs deleteDomain" type="button" data-id="' +
'<button class="btn btn-danger btn-xs" type="button" id="deleteDomain_' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
"</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>>" +
@@ -228,8 +257,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);
}
}
}
});
@@ -248,14 +291,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;
}
@@ -271,15 +353,25 @@ function addDomain() {
token: token
},
success: function(response) {
utils.enableAll();
if (response.success) {
showAlert("success", "glyphicon glyphicon-plus", "Successfully added domain", domain);
$("#new_domain").val("");
$("#new_comment").val("");
utils.showAlert(
"success",
"glyphicon glyphicon-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);
}
});
@@ -288,36 +380,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",
@@ -332,26 +448,28 @@ function editDomain() {
token: token
},
success: function(response) {
utils.enableAll();
if (response.success) {
showAlert(
utils.showAlert(
"success",
"glyphicon glyphicon-pencil",
"Successfully " + done + " domain",
"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);
@@ -360,27 +478,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", "glyphicon glyphicon-trash", "Successfully deleted domain", domain);
utils.showAlert(
"success",
"glyphicon glyphicon-trash",
"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);
}
});
+78 -116
View File
@@ -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").html();
var info = null;
function showAlert(type, icon, title, message) {
var opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
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: "&nbsp;<strong>Error, something went wrong!</strong><br>",
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");
}
$(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(
'<input id="name" title="' +
tooltip +
'" class="form-control"><input id="id" type="hidden" value="' +
data.id +
'">'
'<input id="name_' + data.id + '" title="' + tooltip + '" class="form-control">'
);
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(
'<input type="checkbox" id="status"' + (disabled ? "" : " checked") + ">"
'<input type="checkbox" id="status_' + data.id + '"' + (disabled ? "" : " checked") + ">"
);
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('<input id="desc" class="form-control">');
$("td:eq(2)", row).html('<input id="desc_' + data.id + '" class="form-control">');
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 =
" &nbsp;" +
'<button class="btn btn-danger btn-xs deleteGroup" type="button" data-id="' +
'<button class="btn btn-danger btn-xs" type="button" id="deleteGroup_' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
@@ -195,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;
}
@@ -208,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", "glyphicon glyphicon-plus", "Successfully added group", name);
utils.showAlert("success", "glyphicon glyphicon-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);
}
});
@@ -227,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",
@@ -262,10 +209,16 @@ function editGroup() {
token: token
},
success: function(response) {
utils.enableAll();
if (response.success) {
showAlert("success", "glyphicon glyphicon-pencil", "Successfully " + done + " group", name);
utils.showAlert(
"success",
"glyphicon glyphicon-pencil",
"Successfully " + done + " group",
name
);
} else {
showAlert(
utils.showAlert(
"error",
"",
"Error while " + not_done + " group with ID " + id,
@@ -274,7 +227,8 @@ function editGroup() {
}
},
error: function(jqXHR, exception) {
showAlert(
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while " + not_done + " group with ID " + id,
@@ -286,29 +240,37 @@ 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", "glyphicon glyphicon-trash", "Successfully deleted group ", name);
utils.showAlert(
"success",
"glyphicon glyphicon-trash",
"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);
}
});
+3 -2
View File
@@ -800,8 +800,9 @@ $(document).ready(function() {
var percentage = 0.0;
var permitted = parseInt(data.datasets[1].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[0].data[tooltipItems.index]);
if (permitted + blocked > 0) {
percentage = (100.0 * blocked) / (permitted + blocked);
var total = permitted + blocked;
if (total > 0) {
percentage = (100.0 * blocked) / total;
}
return (
+6
View File
@@ -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 = "",
-287
View File
@@ -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 ? "&nbsp;-&nbsp;" + entry.comment : "";
var disabled_message =
disabled.length > 0 ? "&nbsp;-&nbsp;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(
'<li id="' +
index +
'" class="list-group-item ' +
used +
' clearfix">' +
'<span title="' +
tooltip +
'" data-toggle="tooltip" data-placement="right">' +
entry.domain +
comment +
disabled_message +
"</span>" +
'<button class="btn btn-danger btn-xs pull-right" type="button">' +
'<span class="glyphicon glyphicon-trash"></span></button></li>'
);
// 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(
'<div class="alert alert-info" role="alert">Your ' + fullName + " is empty!</div>"
);
} 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");
});
+13 -7
View File
@@ -101,16 +101,22 @@ $(document).ready(function() {
// 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('<a href="queries.php?client=' + ip + '">' + ip + "</a>");
}
$("td:eq(0)", row).html(shortips.join("<br>"));
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("<br>"));
$("td:eq(0)", row).hover(function() {
this.title = ips.join("\n");
this.title = data.ip.join("\n");
});
// MAC + Vendor field if available
+77 -13
View File
@@ -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 <br class='hidden-lg'>(regex/wildcard)";
fieldtext = "Blocked <br class='hidden-lg'>(regex blacklist)";
if (data.length > 9 && data[9] > 0) {
regexLink = true;
}
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
case "5":
blocked = true;
colorClass = "text-red";
fieldtext = "Blocked <br class='hidden-lg'>(blacklist)";
fieldtext = "Blocked <br class='hidden-lg'>(exact blacklist)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
@@ -221,6 +232,35 @@ $(document).ready(function() {
fieldtext = "Blocked <br class='hidden-lg'>(external, NXRA)";
buttontext = "";
break;
case "9":
blocked = true;
colorClass = "text-red";
fieldtext = "Blocked (gravity, CNAME)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
isCNAME = true;
break;
case "10":
blocked = true;
colorClass = "text-red";
fieldtext = "Blocked <br class='hidden-lg'>(regex blacklist, CNAME)";
if (data.length > 9 && data[9] > 0) {
regexLink = true;
}
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
isCNAME = true;
break;
case "11":
blocked = true;
colorClass = "text-red";
fieldtext = "Blocked <br class='hidden-lg'>(exact blacklist, CNAME)";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
isCNAME = true;
break;
default:
blocked = false;
colorClass = "text-black";
@@ -228,15 +268,37 @@ $(document).ready(function() {
buttontext = "";
}
// Add extra label when blocking happend during deep CNAME inspection
if (blocked && data.length > 6 && data[6] === "3") {
fieldtext += "<br>CNAME";
}
$(row).addClass(colorClass);
$("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) {
@@ -387,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 = "";
@@ -433,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");
}
});
+27 -7
View File
@@ -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");
}
?>
+6 -9
View File
@@ -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'));
}
}
}
+25 -11
View File
@@ -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++;
-78
View File
@@ -1,78 +0,0 @@
<?php
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
if(!isset($_GET['list']))
die("Missing parameter");
$listtype = $_GET['list'];
require_once("func.php");
require("database.php");
$GRAVITYDB = getGravityDBFilename();
$db = SQLite3_connect($GRAVITYDB);
function getTableContent($type) {
global $db;
$entries = array();
$querystr = implode(" ",array("SELECT domainlist.*,\"group\".enabled as group_enabled",
"FROM domainlist",
"LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id",
"LEFT JOIN \"group\" ON \"group\".id = domainlist_by_group.group_id",
"WHERE type = $type",
"GROUP BY domain;"));
$results = $db->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);
+139 -72
View File
@@ -49,7 +49,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'] == 'add_group') {
// Add new group
@@ -72,9 +72,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'] == 'edit_group') {
// Edit group identified by ID
@@ -111,9 +111,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 +142,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
@@ -182,7 +182,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 +193,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
@@ -227,12 +227,12 @@ 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)');
$stmt = $db->prepare('INSERT INTO client (ip,comment) VALUES (:ip,:comment)');
if (!$stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
@@ -241,18 +241,49 @@ if ($_POST['action'] == 'get_groups') {
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;
return JSON_success();
JSON_success();
} catch (\Exception $ex) {
return JSON_error($ex->getMessage());
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());
}
$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 +296,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 +319,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 +353,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,19 +377,30 @@ 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
@@ -363,12 +411,24 @@ if ($_POST['action'] == 'get_groups') {
}
$type = intval($_POST['type']);
$domain = $_POST['domain'];
if($type === ListType::whitelist || $type === ListType::blacklist)
if(strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
{
// If adding to the exact lists, we convert the domain lower case and check whether it is valid
// Apply wildcard-style formatting
$domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
}
if ($type === ListType::whitelist || $type === ListType::blacklist) {
// Convert domain name to IDNA ASCII form for international
// domains and convert the domain to lower case
// Only use IDN routine when php-intl is available
if (extension_loaded("intl")) {
$domain = idn_to_ascii($domain);
}
$domain = strtolower($domain);
// Check validity of 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.');
@@ -392,9 +452,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'] == 'edit_domain') {
// Edit domain identified by ID
@@ -434,44 +494,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 +565,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 +583,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,17 +595,22 @@ 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 {
$address = $_POST['address'];
if(preg_match("/[^a-zA-Z0-9:\/?&%=~._-]/", $address) !== 0) {
throw new Exception('Invalid adlist URL');
}
$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)) {
if (!$stmt->bindValue(':address', $address, SQLITE3_TEXT)) {
throw new Exception('While binding address: ' . $db->lastErrorMsg());
}
@@ -556,9 +623,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'] == 'edit_adlist') {
// Edit adlist identified by ID
@@ -572,7 +639,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 +673,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 +696,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 +730,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!');
+13 -13
View File
@@ -191,15 +191,15 @@
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="img/favicon.png" type="image/x-icon">
<link rel="apple-touch-icon" sizes="180x180" href="img/favicon.png">
<link rel="icon" href="img/logo.svg" type="image/png" sizes="192x192">
<link rel="icon" href="img/logo.svg" type="image/png" sizes="160x160">
<link rel="icon" href="img/logo.svg" type="image/png" sizes="96x96">
<meta name="theme-color" content="#367fa9">
<link rel="apple-touch-icon" href="img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="img/favicons/manifest.json">
<link rel="mask-icon" href="img/favicons/safari-pinned-tab.svg" color="#367fa9">
<link rel="shortcut icon" href="img/favicons/favicon.ico">
<meta name="msapplication-TileColor" content="#367fa9">
<meta name="msapplication-TileImage" content="img/logo.svg">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="msapplication-TileImage" content="img/favicons/mstile-150x150.png">
<meta name="theme-color" content="#367fa9">
<link rel="stylesheet" href="style/vendor/SourceSansPro/SourceSansPro.css">
<link rel="stylesheet" href="style/vendor/bootstrap/css/bootstrap.min.css">
@@ -428,13 +428,13 @@ if($auth) {
</div>
<!-- sidebar menu: : style can be found in sidebar.less -->
<?php
if($scriptname === "list.php")
if($scriptname === "groups-domains.php" && isset($_GET['type']))
{
if($_GET["l"] === "white")
if($_GET["type"] === "white")
{
$scriptname = "whitelist";
}
elseif($_GET["l"] === "black")
elseif($_GET["type"] === "black")
{
$scriptname = "blacklist";
}
@@ -486,13 +486,13 @@ if($auth) {
</li>
<!-- Whitelist -->
<li<?php if($scriptname === "whitelist"){ ?> class="active"<?php } ?>>
<a href="list.php?l=white">
<a href="groups-domains.php?type=white">
<i class="fa fa-check-circle "></i> <span>Whitelist</span>
</a>
</li>
<!-- Blacklist -->
<li<?php if($scriptname === "blacklist"){ ?> class="active"<?php } ?>>
<a href="list.php?l=black">
<a href="groups-domains.php?type=black">
<i class="fa fa-ban"></i> <span>Blacklist</span>
</a>
</li>
+2 -2
View File
@@ -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
-51
View File
@@ -1,51 +0,0 @@
<?php
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
require_once('auth.php');
$type = $_POST['list'];
// Perform all of the authentication for list editing
// when NOT invoked and authenticated from API
if (empty($api)) {
list_verify($type);
}
// Split individual domains into array
$domains = preg_split('/\s+/', trim($_POST['domain']));
require_once("func.php");
require("database.php");
$GRAVITYDB = getGravityDBFilename();
$db = SQLite3_connect($GRAVITYDB, SQLITE3_OPEN_READWRITE);
switch($type) {
case "white":
echo remove_from_table($db, "domainlist", $domains, false, ListType::whitelist);
break;
case "black":
echo remove_from_table($db, "domainlist", $domains, false, ListType::blacklist);
break;
case "white_regex":
echo remove_from_table($db, "domainlist", $domains, false, ListType::regex_whitelist);
break;
case "black_regex":
echo remove_from_table($db, "domainlist", $domains, false, ListType::regex_blacklist);
break;
default:
die("Invalid list!");
}
// Reload lists in pihole-FTL after having removed something
echo shell_exec("sudo pihole restartdns reload");
?>
+63 -9
View File
@@ -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)<br>\n";
echo "Processed audit log (".$num." entries)<br>\n";
$importedsomething = true;
}
if(isset($_POST["adlist"]) && $file->getFilename() === "adlists.list")
{
$num = archive_insert_into_table($file, "adlist", $flushtables);
echo "Processed adlists (".$num." entries)<br>\n";
$importedsomething = true;
}
+22
View File
@@ -239,3 +239,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;
}