Merge pull request #682 from pi-hole/release/3.3

Pi-hole web v3.3
This commit is contained in:
Dan Schaper
2018-02-14 12:50:14 -08:00
committed by GitHub
19 changed files with 525 additions and 238 deletions
+137 -52
View File
@@ -12,6 +12,9 @@ require("scripts/pi-hole/php/password.php");
require("scripts/pi-hole/php/auth.php");
check_cors();
// Set maximum execution time to 10 minutes
ini_set("max_execution_time","600");
$data = array();
// Get posible non-standard location of FTL's database
@@ -62,20 +65,20 @@ if(!$db)
if (isset($_GET['getAllQueries']) && $auth)
{
if($_GET['getAllQueries'] === "empty")
{
$allQueries = array();
}
else
$allQueries = array();
if($_GET['getAllQueries'] !== "empty")
{
$from = intval($_GET["from"]);
$until = intval($_GET["until"]);
$results = $db->query('SELECT timestamp,type,domain,client,status FROM queries WHERE timestamp >= '.$from.' AND timestamp <= '.$until.' ORDER BY timestamp ASC');
$allQueries = array();
while ($row = $results->fetchArray())
{
$allQueries[] = [$row[0],$row[1] == 1 ? "IPv4" : "IPv6",$row[2],$row[3],$row[4]];
}
$stmt = $db->prepare("SELECT timestamp, type, domain, client, status FROM queries WHERE timestamp >= :from AND timestamp <= :until ORDER BY timestamp ASC");
$stmt->bindValue(":from", intval($from), SQLITE3_INTEGER);
$stmt->bindValue(":until", intval($until), SQLITE3_INTEGER);
$results = $stmt->execute();
if(!is_bool($results))
while ($row = $results->fetchArray())
{
$allQueries[] = [$row[0],$row[1] == 1 ? "IPv4" : "IPv6",$row[2],$row[3],$row[4]];
}
}
$result = array('data' => $allQueries);
$data = array_merge($data, $result);
@@ -87,23 +90,46 @@ if (isset($_GET['topClients']) && $auth)
$limit = "";
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = "WHERE timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
$limit = "WHERE timestamp >= :from AND timestamp <= :until";
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = "WHERE timestamp >= ".$_GET["from"];
$limit = "WHERE timestamp >= :from";
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = "WHERE timestamp <= ".$_GET["until"];
$limit = "WHERE timestamp <= :until";
}
$results = $db->query('SELECT client,count(client) FROM queries '.$limit.' GROUP by client order by count(client) desc limit 10');
$stmt = $db->prepare('SELECT client,count(client) FROM queries '.$limit.' GROUP by client order by count(client) desc limit 20');
$stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER);
$stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER);
$results = $stmt->execute();
$clients = array();
while ($row = $results->fetchArray())
{
$clients[$row[0]] = intval($row[1]);
// var_dump($row);
}
if(!is_bool($results))
while ($row = $results->fetchArray())
{
// Convert client to lower case
$c = strtolower($row[0]);
if(array_key_exists($c, $clients))
{
// Entry already exists, add to it (might appear multiple times due to mixed capitalization in the database)
$clients[$c] += intval($row[1]);
}
else
{
// Entry does not yet exist
$clients[$c] = intval($row[1]);
}
}
// Sort by number of hits
arsort($clients);
// Extract only the first ten entries
$clients = array_slice($clients, 0, 10);
$result = array('top_sources' => $clients);
$data = array_merge($data, $result);
}
@@ -114,22 +140,46 @@ if (isset($_GET['topDomains']) && $auth)
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
$limit = " AND timestamp >= :from AND timestamp <= :until";
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"];
$limit = " AND timestamp >= :from";
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp <= ".$_GET["until"];
$limit = " AND timestamp <= :until";
}
$results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 2 OR STATUS == 3)'.$limit.' GROUP by domain order by count(domain) desc limit 10');
$stmt = $db->prepare('SELECT domain,count(domain) FROM queries WHERE (STATUS == 2 OR STATUS == 3)'.$limit.' GROUP by domain order by count(domain) desc limit 20');
$stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER);
$stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER);
$results = $stmt->execute();
$domains = array();
while ($row = $results->fetchArray())
{
$domains[$row[0]] = intval($row[1]);
}
if(!is_bool($results))
while ($row = $results->fetchArray())
{
// Convert client to lower case
$c = strtolower($row[0]);
if(array_key_exists($c, $domains))
{
// Entry already exists, add to it (might appear multiple times due to mixed capitalization in the database)
$domains[$c] += intval($row[1]);
}
else
{
// Entry does not yet exist
$domains[$c] = intval($row[1]);
}
}
// Sort by number of hits
arsort($domains);
// Extract only the first ten entries
$domains = array_slice($domains, 0, 10);
$result = array('top_domains' => $domains);
$data = array_merge($data, $result);
}
@@ -140,22 +190,28 @@ if (isset($_GET['topAds']) && $auth)
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
$limit = " AND timestamp >= :from AND timestamp <= :until";
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"];
$limit = " AND timestamp >= :from";
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp <= ".$_GET["until"];
$limit = " AND timestamp <= :until";
}
$results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 1 OR STATUS == 4)'.$limit.' GROUP by domain order by count(domain) desc limit 10');
$stmt = $db->prepare('SELECT domain,count(domain) FROM queries WHERE (STATUS == 1 OR STATUS == 4)'.$limit.' GROUP by domain order by count(domain) desc limit 10');
$stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER);
$stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER);
$results = $stmt->execute();
$addomains = array();
while ($row = $results->fetchArray())
{
$addomains[$row[0]] = intval($row[1]);
}
if(!is_bool($results))
while ($row = $results->fetchArray())
{
$addomains[$row[0]] = intval($row[1]);
}
$result = array('top_ads' => $addomains);
$data = array_merge($data, $result);
}
@@ -163,21 +219,36 @@ if (isset($_GET['topAds']) && $auth)
if (isset($_GET['getMinTimestamp']) && $auth)
{
$results = $db->query('SELECT MIN(timestamp) FROM queries');
$result = array('mintimestamp' => $results->fetchArray()[0]);
if(!is_bool($results))
$result = array('mintimestamp' => $results->fetchArray()[0]);
else
$result = array();
$data = array_merge($data, $result);
}
if (isset($_GET['getMaxTimestamp']) && $auth)
{
$results = $db->query('SELECT MAX(timestamp) FROM queries');
$result = array('maxtimestamp' => $results->fetchArray()[0]);
if(!is_bool($results))
$result = array('maxtimestamp' => $results->fetchArray()[0]);
else
$result = array();
$data = array_merge($data, $result);
}
if (isset($_GET['getQueriesCount']) && $auth)
{
$results = $db->query('SELECT COUNT(timestamp) FROM queries');
$result = array('count' => $results->fetchArray()[0]);
if(!is_bool($results))
$result = array('count' => $results->fetchArray()[0]);
else
$result = array();
$data = array_merge($data, $result);
}
@@ -194,15 +265,15 @@ if (isset($_GET['getGraphData']) && $auth)
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp >= ".intval($_GET["from"])." AND timestamp <= ".intval($_GET["until"]);
$limit = " AND timestamp >= :from AND timestamp <= :until";
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = " AND timestamp >= ".intval($_GET["from"]);
$limit = " AND timestamp >= :from";
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp <= ".intval($_GET["until"]);
$limit = " AND timestamp <= :until";
}
$interval = 600;
@@ -215,22 +286,36 @@ if (isset($_GET['getGraphData']) && $auth)
}
// Count permitted queries in intervals
$results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 2 OR status == 3)'.$limit.' GROUP by interval ORDER by interval');
$stmt = $db->prepare('SELECT (timestamp/:interval)*:interval interval, COUNT(*) FROM queries WHERE (status != 0 )'.$limit.' GROUP by interval ORDER by interval');
$stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER);
$stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER);
$stmt->bindValue(":interval", $interval, SQLITE3_INTEGER);
$results = $stmt->execute();
$domains = array();
while ($row = $results->fetchArray())
{
$domains[$row[0]] = intval($row[1]);
}
if(!is_bool($results))
while ($row = $results->fetchArray())
{
$domains[$row[0]] = intval($row[1]);
}
$result = array('domains_over_time' => $domains);
$data = array_merge($data, $result);
// Count blocked queries in intervals
$results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 1 OR status == 4 OR status == 5)'.$limit.' GROUP by interval ORDER by interval');
$stmt = $db->prepare('SELECT (timestamp/:interval)*:interval interval, COUNT(*) FROM queries WHERE (status == 1 OR status == 4 OR status == 5)'.$limit.' GROUP by interval ORDER by interval');
$stmt->bindValue(":from", intval($_GET['from']), SQLITE3_INTEGER);
$stmt->bindValue(":until", intval($_GET['until']), SQLITE3_INTEGER);
$stmt->bindValue(":interval", $interval, SQLITE3_INTEGER);
$results = $stmt->execute();
$addomains = array();
while ($row = $results->fetchArray())
{
$addomains[$row[0]] = intval($row[1]);
}
if(!is_bool($results))
while ($row = $results->fetchArray())
{
$addomains[$row[0]] = intval($row[1]);
}
$result = array('ads_over_time' => $addomains);
$data = array_merge($data, $result);
}
+5 -1
View File
@@ -22,7 +22,6 @@ $token = $_SESSION['token'];
<h1>Compute graphical statistics from the Pi-hole query database</h1>
</div>
<div class="row">
<div class="col-md-12">
<!-- Date Input -->
@@ -39,6 +38,11 @@ $token = $_SESSION['token'];
</div>
</div>
</div>
<div id="timeoutWarning" class="alert alert-warning alert-dismissible fade in" role="alert" hidden="true">
Depending on how large of a range you specified, the request may time out while Pi-hole tries to retrieve all the data.<br/><span id="err"></span>
</div>
<div class="row">
<div class="col-md-12">
<div class="box" id="queries-over-time">
+5
View File
@@ -39,6 +39,11 @@ $token = $_SESSION['token'];
</div>
</div>
</div>
<div id="timeoutWarning" class="alert alert-warning alert-dismissible fade in" role="alert" hidden="true">
Depending on how large of a range you specified, the request may time out while Pi-hole tries to retrieve all the data.<br/><span id="err"></span>
</div>
<?php
if($boxedlayout)
{
+28 -25
View File
@@ -39,6 +39,11 @@ $token = $_SESSION['token'];
</div>
</div>
</div>
<div id="timeoutWarning" class="alert alert-warning alert-dismissible fade in" role="alert" hidden="true">
Depending on how large of a range you specified, the request may time out while Pi-hole tries to retrieve all the data.<br/><span id="err"></span>
</div>
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-lg-3 col-xs-12">
@@ -103,31 +108,29 @@ $token = $_SESSION['token'];
</div>
<!-- /.box-header -->
<div class="box-body">
<div class="table-responsive">
<table id="all-queries" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
</div>
<table id="all-queries" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
+56 -39
View File
@@ -88,18 +88,35 @@ if(strlen($showing) > 0)
</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 <span id="alDomain"></span> to the <span id="alList"></span>...
</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>
Success!
</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.<span id="err"></span>
<!-- Alert Modal -->
<div id="alertModal" class="modal fade" role="dialog" data-backdrop="static" data-keyboard="false">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center">
<div class="modal-content">
<div class="modal-body text-center">
<span class="fa-stack fa-2x" style="margin-bottom: 10px">
<div class="alProcessing">
<i class="fa-stack-2x alSpinner"></i>
</div>
<div class="alSuccess" style="display: none">
<i class="fa fa-circle fa-stack-2x text-green"></i>
<i class="fa fa-check fa-stack-1x fa-inverse"></i>
</div>
<div class="alFailure" style="display: none">
<i class="fa fa-circle fa-stack-2x text-red"></i>
<i class="fa fa-times fa-stack-1x fa-inverse"></i>
</div>
</span>
<div class="alProcessing">Adding <span id="alDomain"></span> to the <span id="alList"></span>...</div>
<div class="alSuccess text-bold text-green" style="display: none"><span id="alDomain"></span> successfully added to the <span id="alList"></span></div>
<div class="alFailure text-bold text-red" style="display: none">
<span id="alNetErr">Timeout or Network Connection Error!</span>
<span id="alCustomErr"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
@@ -110,33 +127,33 @@ if(strlen($showing) > 0)
</div>
<!-- /.box-header -->
<div class="box-body">
<div class="table-responsive">
<table id="all-queries" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
<label><input type="checkbox" id="autofilter" checked="true">&nbsp;Apply filtering on click on Type, Domain, and Clients</label><br/>
<button type="button" id="resetButton" hidden="true">Clear Filters</button>
</div>
<table id="all-queries" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>DNSSEC</th>
<th>Action</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>DNSSEC</th>
<th>Action</th>
</tr>
</tfoot>
</table>
<label><input type="checkbox" id="autofilter" checked="true">&nbsp;Apply filtering on click on Type, Domain, and Clients</label><br/>
<button type="button" id="resetButton" hidden="true">Clear Filters</button>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
+4
View File
@@ -14,6 +14,8 @@ var from = moment(start__).utc().valueOf()/1000;
var end__ = moment();
var until = moment(end__).utc().valueOf()/1000;
var timeoutWarning = $("#timeoutWarning");
$(function () {
$("#querytime").daterangepicker(
{
@@ -80,6 +82,7 @@ function compareNumbers(a, b) {
function updateQueriesOverTime() {
$("#queries-over-time .overlay").show();
timeoutWarning.show();
$.getJSON("api_db.php?getGraphData&from="+from+"&until="+until, function(data) {
// convert received objects to arrays
@@ -135,6 +138,7 @@ function updateQueriesOverTime() {
timeLineChart.options.scales.xAxes[0].display=true;
$("#queries-over-time .overlay").hide();
timeoutWarning.hide();
timeLineChart.update();
});
}
+17
View File
@@ -14,6 +14,9 @@ var from = moment(start__).utc().valueOf()/1000;
var end__ = moment();
var until = moment(end__).utc().valueOf()/1000;
var timeoutWarning = $("#timeoutWarning");
var listsStillLoading = 0;
$(function () {
$("#querytime").daterangepicker(
{
@@ -97,6 +100,10 @@ function updateTopClientsChart() {
}
$("#client-frequency .overlay").hide();
listsStillLoading--;
if(listsStillLoading === 0)
timeoutWarning.hide();
});
}
@@ -135,6 +142,10 @@ function updateTopDomainsChart() {
}
$("#domain-frequency .overlay").hide();
listsStillLoading--;
if(listsStillLoading === 0)
timeoutWarning.hide();
});
}
@@ -171,10 +182,16 @@ function updateTopAdsChart() {
}
$("#ad-frequency .overlay").hide();
listsStillLoading--;
if(listsStillLoading === 0)
timeoutWarning.hide();
});
}
$("#querytime").on("apply.daterangepicker", function(ev, picker) {
timeoutWarning.show();
listsStillLoading = 3;
updateTopClientsChart();
updateTopDomainsChart();
updateTopAdsChart();
+12 -8
View File
@@ -16,6 +16,8 @@ var until = moment(end__).utc().valueOf()/1000;
var instantquery = false;
var daterange;
var timeoutWarning = $("#timeoutWarning");
// Do we want to filter queries?
var GETDict = {};
location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];});
@@ -141,6 +143,7 @@ function handleAjaxError( xhr, textStatus, error ) {
var reloadCallback = function()
{
timeoutWarning.hide();
statistics = [0,0,0,0];
var data = tableApi.rows().data();
for (var i = 0; i < data.length; i++) {
@@ -171,6 +174,7 @@ var reloadCallback = function()
};
function refreshTableData() {
timeoutWarning.show();
var APIstring = "api_db.php?getAllQueries&from="+from+"&until="+until;
statistics = [0,0,0];
tableApi.ajax.url(APIstring).load(reloadCallback);
@@ -201,13 +205,13 @@ $(document).ready(function() {
else if (data[4] === 2)
{
$(row).css("color","green");
$("td:eq(4)", row).html( "OK (forwarded)" );
$("td:eq(4)", row).html( "OK <br class='hidden-lg'>(forwarded)" );
$("td:eq(5)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
}
else if (data[4] === 3)
{
$(row).css("color","green");
$("td:eq(4)", row).html( "OK (cached)" );
$("td:eq(4)", row).html( "OK <br class='hidden-lg'>(cached)" );
$("td:eq(5)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
// statistics[1]++;
@@ -215,20 +219,20 @@ $(document).ready(function() {
else if (data[4] === 4)
{
$(row).css("color","red");
$("td:eq(4)", row).html( "Pi-holed (wildcard)" );
$("td:eq(4)", row).html( "Pi-holed <br class='hidden-lg'>(wildcard)" );
$("td:eq(5)", row).html( "" );
// statistics[3]++;
}
else
{
$("td:eq(4)", row).html( "Unknown ("+data[4]+")" );
$("td:eq(4)", row).html( "Unknown <br class='hidden-lg'>("+data[4]+")" );
$("td:eq(5)", row).html( "" );
}
// statistics[0]++;
},
dom: "<'row'<'col-sm-12'f>>" +
"<'row'<'col-sm-4'l><'col-sm-8'p>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
"<'row'<'col-sm-5'i><'col-sm-7'p>>",
"ajax": {"url": APIstring, "error": handleAjaxError },
"autoWidth" : false,
@@ -236,12 +240,12 @@ $(document).ready(function() {
"deferRender": true,
"order" : [[0, "desc"]],
"columns": [
{ "width" : "20%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD HH:mm:ss z");}else{return data;} }},
{ "width" : "15%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD [<br class='hidden-lg'>]HH:mm:ss z");}else{return data;} }},
{ "width" : "10%" },
{ "width" : "40%" },
{ "width" : "20%" },
{ "width" : "10%" },
{ "width" : "10%" },
{ "width" : "10%" },
{ "width" : "5%" },
],
"lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
"columnDefs": [ {
+110 -66
View File
@@ -22,62 +22,76 @@ function refreshData() {
function add(domain,list) {
var token = $("#token").html();
var alInfo = $("#alInfo");
var alList = $("#alList");
var alDomain = $("#alDomain");
alDomain.html(domain);
var alSuccess = $("#alSuccess");
var alFailure = $("#alFailure");
var err = $("#err");
var alertModal = $("#alertModal");
var alProcessing = alertModal.find(".alProcessing");
var alSuccess = alertModal.find(".alSuccess");
var alFailure = alertModal.find(".alFailure");
var alNetworkErr = alertModal.find(".alFailure #alNetErr");
var alCustomErr = alertModal.find(".alFailure #alCustomErr");
var alList = "#alList";
var alDomain = "#alDomain";
if(list === "white")
{
alList.html("Whitelist");
}
else
{
alList.html("Blacklist");
// Exit the function here if the Modal is already shown (multiple running interlock)
if (alertModal.css("display") !== "none") {
return;
}
alInfo.show();
alSuccess.hide();
alFailure.hide();
$.ajax({
url: "scripts/pi-hole/php/add.php",
method: "post",
data: {"domain":domain, "list":list, "token":token},
success: function(response) {
if (response.indexOf("not a valid argument") >= 0 || response.indexOf("is not a valid domain") >= 0)
{
alFailure.show();
err.html(response);
alFailure.delay(4000).fadeOut(2000, function() { alFailure.hide(); });
var listtype;
if (list === "white") {
listtype = "Whitelist";
} else {
listtype = "Blacklist";
}
alProcessing.children(alDomain).html(domain);
alProcessing.children(alList).html(listtype);
alertModal.modal("show");
// add Domain to List after Modal has faded in
alertModal.one("shown.bs.modal", function() {
$.ajax({
url: "scripts/pi-hole/php/add.php",
method: "post",
data: {"domain":domain, "list":list, "token":token},
success: function(response) {
alProcessing.hide();
if (response.indexOf("not a valid argument") >= 0 ||
response.indexOf("is not a valid domain") >= 0 ||
response.indexOf("Wrong token") >= 0)
{
// Failure
alNetworkErr.hide();
alCustomErr.html(response.replace("[✗]", ""));
alFailure.fadeIn(1000);
setTimeout(function() { alertModal.modal("hide"); }, 3000);
}
else
{
// Success
alSuccess.children(alDomain).html(domain);
alSuccess.children(alList).html(listtype);
alSuccess.fadeIn(1000);
setTimeout(function() { alertModal.modal("hide"); }, 2000);
}
},
error: function(jqXHR, exception) {
// Network Error
alProcessing.hide();
alNetworkErr.show();
alFailure.fadeIn(1000);
setTimeout(function() { alertModal.modal("hide"); }, 3000);
}
else
{
alSuccess.show();
alSuccess.delay(1000).fadeOut(2000, function() { alSuccess.hide(); });
}
alInfo.delay(1000).fadeOut(2000, function() {
alInfo.hide();
alList.html("");
alDomain.html("");
});
},
error: function(jqXHR, exception) {
alFailure.show();
err.html("");
alFailure.delay(1000).fadeOut(2000, function() {
alFailure.hide();
});
alInfo.delay(1000).fadeOut(2000, function() {
alInfo.hide();
alList.html("");
alDomain.html("");
});
}
});
});
// Reset Modal after it has faded out
alertModal.one("hidden.bs.modal", function() {
alProcessing.show();
alSuccess.add(alFailure).hide();
alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html("");
alCustomErr.html("");
});
}
function handleAjaxError( xhr, textStatus, error ) {
if ( textStatus === "timeout" )
{
@@ -135,54 +149,84 @@ $(document).ready(function() {
{
$(row).css("color","red");
$("td:eq(4)", row).html( "Pi-holed" );
$("td:eq(5)", row).html( "<button style=\"color:green; white-space: nowrap;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button>" );
$("td:eq(6)", row).html( "<button style=\"color:green; white-space: nowrap;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button>" );
}
else if (data[4] === "2")
{
$(row).css("color","green");
$("td:eq(4)", row).html( "OK (forwarded)" );
$("td:eq(5)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
$("td:eq(4)", row).html( "OK <br class='hidden-lg'>(forwarded)" );
$("td:eq(6)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
}
else if (data[4] === "3")
{
$(row).css("color","green");
$("td:eq(4)", row).html( "OK (cached)" );
$("td:eq(5)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
$("td:eq(4)", row).html( "OK <br class='hidden-lg'>(cached)" );
$("td:eq(6)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
}
else if (data[4] === "4")
{
$(row).css("color","red");
$("td:eq(4)", row).html( "Pi-holed (wildcard)" );
$("td:eq(5)", row).html( "" );
$("td:eq(4)", row).html( "Pi-holed <br class='hidden-lg'>(wildcard)" );
$("td:eq(6)", row).html( "" );
}
else if (data[4] === "5")
{
$(row).css("color","red");
$("td:eq(4)", row).html( "Pi-holed (blacklist)" );
$("td:eq(5)", row).html( "<button style=\"color:green; white-space: nowrap;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button>" );
$("td:eq(4)", row).html( "Pi-holed <br class='hidden-lg'>(blacklist)" );
$("td:eq(6)", row).html( "<button style=\"color:green; white-space: nowrap;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button>" );
}
else
{
$("td:eq(4)", row).html( "Unknown" );
$("td:eq(5)", row).html( "" );
$("td:eq(6)", row).html( "" );
}
if (data[5] === "1")
{
$("td:eq(5)", row).css("color","green");
$("td:eq(5)", row).html( "SECURE" );
}
else if (data[5] === "2")
{
$("td:eq(5)", row).css("color","orange");
$("td:eq(5)", row).html( "INSECURE" );
}
else if (data[5] === "3")
{
$("td:eq(5)", row).css("color","red");
$("td:eq(5)", row).html( "BOGUS" );
}
else if (data[5] === "4")
{
$("td:eq(5)", row).css("color","red");
$("td:eq(5)", row).html( "ABANDONED" );
}
else if (data[5] === "5")
{
$("td:eq(5)", row).css("color","red");
$("td:eq(5)", row).html( "?" );
}
else
{
$("td:eq(5)", row).css("color","black");
$("td:eq(5)", row).html( "-" );
}
},
dom: "<'row'<'col-sm-12'f>>" +
"<'row'<'col-sm-4'l><'col-sm-8'p>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
"<'row'<'col-sm-5'i><'col-sm-7'p>>",
"ajax": {"url": APIstring, "error": handleAjaxError },
"autoWidth" : false,
"processing": true,
"order" : [[0, "desc"]],
"columns": [
{ "width" : "20%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD HH:mm:ss z");}else{return data;} }},
{ "width" : "10%" },
{ "width" : "40%", "render": $.fn.dataTable.render.text() },
{ "width" : "10%", "render": $.fn.dataTable.render.text() },
{ "width" : "15%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD [<br class='hidden-lg'>]HH:mm:ss z");}else{return data;} }},
{ "width" : "10%" },
{ "width" : "37%", "render": $.fn.dataTable.render.text() },
{ "width" : "8%", "render": $.fn.dataTable.render.text() },
{ "width" : "10%" },
{ "width" : "5%" },
{ "width" : "10%" }
],
"lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
"columnDefs": [ {
+19 -2
View File
@@ -85,7 +85,7 @@ $(".confirm-flushlogs").confirm({
});
$(".confirm-disablelogging").confirm({
text: "Note that disabling query logging will render graphs on the web user interface useless. Are you sure you want to disable your logging?",
text: "Note that disabling query logging will render graphs on the web user interface useless. Are you sure you want to disable logging and flush your Pi-hole logs?",
title: "Confirmation required",
confirm(button) {
$("#disablelogsform").submit();
@@ -93,7 +93,7 @@ $(".confirm-disablelogging").confirm({
cancel(button) {
// nothing to do
},
confirmButton: "Yes, disable logs",
confirmButton: "Yes, disable logs and flush my logs",
cancelButton: "No, go back",
post: true,
confirmButtonClass: "btn-danger",
@@ -101,6 +101,23 @@ $(".confirm-disablelogging").confirm({
dialogClass: "modal-dialog modal-mg"
});
$(".confirm-disablelogging-noflush").confirm({
text: "Note that disabling query logging will render graphs on the web user interface useless after this point. Are you sure you want to disable logging?",
title: "Confirmation required",
confirm(button) {
$("#disablelogsform-noflush").submit();
},
cancel(button) {
// nothing to do
},
confirmButton: "Yes, disable logs",
cancelButton: "No, go back",
post: true,
confirmButtonClass: "btn-warning",
cancelButtonClass: "btn-success",
dialogClass: "modal-dialog modal-mg"
});
$(".api-token").confirm({
text: "Make sure that nobody else can scan this code around you. They will have full access to the API without having to know the password. Note that the generation of the QR code will take some time.",
title: "Confirmation required",
+3 -23
View File
@@ -6,7 +6,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
require('func.php');
require_once('func.php');
$ERRORLOG = getenv('PHP_ERROR_LOG');
if (empty($ERRORLOG)) {
$ERRORLOG = '/var/log/lighttpd/error.log';
@@ -92,26 +92,6 @@ function check_csrf($token) {
session_start();
}
// Credit: http://php.net/manual/en/function.hash-equals.php#119576
if(!function_exists('hash_equals')) {
function hash_equals($known_string, $user_string) {
$ret = 0;
if (strlen($known_string) !== strlen($user_string)) {
$user_string = $known_string;
$ret = 1;
}
$res = $known_string ^ $user_string;
for ($i = strlen($res) - 1; $i >= 0; --$i) {
$ret |= ord($res[$i]);
}
return !$ret;
}
}
if(!isset($_SESSION['token']) || empty($token) || !hash_equals($_SESSION['token'], $token)) {
log_and_die("Wrong token");
}
@@ -119,7 +99,7 @@ function check_csrf($token) {
function check_domain() {
if(isset($_POST['domain'])){
$domains = preg_split('\s+', $_POST['domain']);
$domains = preg_split('/\s+/', $_POST['domain']);
foreach($domains as $domain)
{
$validDomain = is_valid_domain_name($domain);
@@ -146,7 +126,7 @@ function list_verify($type) {
require("password.php");
if($wrongpassword || !$auth)
{
log_and_die("Wrong password - ".htmlspecialchars($type)."listing of ${_POST['domain']} not permitted");
log_and_die("Wrong password - ".htmlspecialchars($type)."listing of ".htmlspecialchars($_POST['domain'])." not permitted");
}
}
else
+4 -2
View File
@@ -18,10 +18,12 @@ $token = isset($_GET["token"]) ? $_GET["token"] : "";
check_csrf($token);
function echoEvent($datatext) {
$data = htmlspecialchars($datatext);
if(!isset($_GET["IE"]))
echo "data: ".implode("\ndata: ", explode("\n", $datatext))."\n\n";
echo "data: ".implode("\ndata: ", explode("\n", $data))."\n\n";
else
echo $datatext;
echo $data;
}
if(isset($_GET["upload"]))
+20
View File
@@ -25,4 +25,24 @@ function checkfile($filename) {
}
}
// Credit: http://php.net/manual/en/function.hash-equals.php#119576
if(!function_exists('hash_equals')) {
function hash_equals($known_string, $user_string) {
$ret = 0;
if (strlen($known_string) !== strlen($user_string)) {
$user_string = $known_string;
$ret = 1;
}
$res = $known_string ^ $user_string;
for ($i = strlen($res) - 1; $i >= 0; --$i) {
$ret |= ord($res[$i]);
}
return !$ret;
}
}
?>
+5 -3
View File
@@ -6,6 +6,8 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
require_once('func.php');
// Start a new PHP session (or continue an existing one)
session_start();
@@ -37,7 +39,7 @@
if(isset($_POST["pw"]))
{
$postinput = hash('sha256',hash('sha256',$_POST["pw"]));
if($postinput == $pwhash)
if(hash_equals($pwhash, $postinput))
{
$_SESSION["hash"] = $pwhash;
@@ -57,13 +59,13 @@
// Compare auth hash with saved hash
else if (isset($_SESSION["hash"]))
{
if($_SESSION["hash"] == $pwhash)
if(hash_equals($pwhash, $_SESSION["hash"]))
$auth = true;
}
// API can use the hash to get data without logging in via plain-text password
else if (isset($api) && isset($_GET["auth"]))
{
if($_GET["auth"] == $pwhash)
if(hash_equals($pwhash, $_GET["auth"]))
$auth = true;
}
else
+9 -4
View File
@@ -123,7 +123,7 @@ function isinserverlist($addr) {
"Norton" => ["v4_1" => "199.85.126.10", "v4_2" => "199.85.127.10"],
"Comodo" => ["v4_1" => "8.26.56.26", "v4_2" => "8.20.247.20"],
"DNS.WATCH" => ["v4_1" => "84.200.69.80", "v4_2" => "84.200.70.40", "v6_1" => "2001:1608:10:25:0:0:1c04:b12f", "v6_2" => "2001:1608:10:25:0:0:9249:d69b"],
"Quad9" => ["v4_1" => "9.9.9.9", "v6_1" => "2620:fe::fe"]
"Quad9" => ["v4_1" => "9.9.9.9", "v4_2" => "149.112.112.112", "v6_1" => "2620:fe::fe"]
];
$adlist = [];
@@ -278,7 +278,12 @@ function readAdlists()
if($_POST["action"] === "Disable")
{
exec("sudo pihole -l off");
$success .= "Logging has been disabled";
$success .= "Logging has been disabled and logs have been flushed";
}
elseif($_POST["action"] === "Disable-noflush")
{
exec("sudo pihole -l off noflush");
$success .= "Logging has been disabled, your logs have <strong>not</strong> been flushed";
}
else
{
@@ -320,9 +325,9 @@ function readAdlists()
$first = true;
foreach($clients as $client)
{
if(!validDomainWildcard($client))
if(!validDomainWildcard($client) && !validIP($client))
{
$error .= "Top Clients entry ".htmlspecialchars($client)." is invalid (use only IP addresses)!<br>";
$error .= "Top Clients entry ".htmlspecialchars($client)." is invalid (use only host names and IP addresses)!<br>";
}
if(!$first)
{
+1
View File
@@ -176,6 +176,7 @@ else
archive_add_file("/etc/pihole/","blacklist.txt");
archive_add_file("/etc/pihole/","adlists.list");
archive_add_file("/etc/pihole/","setupVars.conf");
archive_add_file("/etc/pihole/","auditlog.list");
archive_add_directory("/etc/dnsmasq.d/");
$archive["wildcardblocking.txt"] = getWildcardListContent();
+2 -2
View File
@@ -44,12 +44,12 @@ else
/********** Get Pi-hole FTL (not a git repository) **********/
$FTL_branch = $branches[2];
if($FTL_branch !== "master") {
if(substr($versions[2], 0, 4) === "vDev") {
$FTL_current = "vDev";
$FTL_commit = $versions[2];
}
else {
$FTL_current = explode("-",$versions[2])[0];
$FTL_current = $versions[2];
}
// Get data from GitHub
+19 -7
View File
@@ -288,6 +288,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
</div>
<div class="box-footer clearfix">
<button type="submit" class="btn btn-primary" name="submit" value="save" id="blockinglistsave">Save</button>
<span><strong>Important: </strong>Save and Update when you're done!</span>
<button type="submit" class="btn btn-primary pull-right" name="submit" id="blockinglistsaveupdate" value="saveupdate">Save and Update</button>
</div>
</div>
@@ -344,7 +345,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
if (isset($setupVars["PIHOLE_DOMAIN"])) {
$piHoleDomain = $setupVars["PIHOLE_DOMAIN"];
} else {
$piHoleDomain = "local";
$piHoleDomain = "lan";
}
?>
<form role="form" method="post">
@@ -785,14 +786,14 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<div class="checkbox">
<label><input type="checkbox" name="DNSrequiresFQDN" title="domain-needed"
<?php if ($DNSrequiresFQDN){ ?>checked<?php }
?>>never forward non-FQDNs</label>
?>>Never forward non-FQDNs</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label><input type="checkbox" name="DNSbogusPriv" title="bogus-priv"
<?php if ($DNSbogusPriv){ ?>checked<?php }
?>>never forward reverse lookups for private IP ranges</label>
?>>Never forward reverse lookups for private IP ranges</label>
</div>
</div>
<p>Note that enabling these two options may increase your privacy
@@ -807,7 +808,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
</div>
<p>Validate DNS replies and cache DNSSEC data. When forwarding DNS
queries, Pi-hole requests the DNSSEC records needed to validate
the replies. Use Google or Norton DNS servers when activating
the replies. Use Google, Norton, DNS.WATCH or Quad9 DNS servers when activating
DNSSEC. Note that the size of your log might increase significantly
when enabling DNSSEC. A DNSSEC resolver test can be found
<a href="http://dnssec.vs.uni-due.de/" target="_blank">here</a>.</p>
@@ -1067,7 +1068,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<td><?php echo $piHoleIPv6; ?></td>
</tr>
<tr>
<th scope="row">Pi-hole hostname</th>
<th scope="row">Pi-hole hostname:</th>
<td><?php echo $hostname; ?></td>
</tr>
</tbody>
@@ -1148,7 +1149,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<div class="row">
<div class="col-md-4">
<?php if ($piHoleLogging) { ?>
<button type="button" class="btn btn-warning confirm-disablelogging form-control">Disable query logging</button>
<button type="button" class="btn btn-warning confirm-disablelogging-noflush form-control">Disable query logging</button>
<?php } else { ?>
<form role="form" method="post">
<input type="hidden" name="action" value="Enable">
@@ -1159,7 +1160,13 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<?php } ?>
</div>
<p class="hidden-md hidden-lg"></p>
<div class="col-md-4 col-md-offset-4">
<div class="col-md-4">
<?php if ($piHoleLogging) { ?>
<button type="button" class="btn btn-danger confirm-disablelogging form-control">Disable query logging and flush logs</button>
<?php } ?>
</div>
<p class="hidden-md hidden-lg"></p>
<div class="col-md-4">
<button type="button" class="btn btn-warning confirm-restartdns form-control">Restart dnsmasq</button>
</div>
</div>
@@ -1187,6 +1194,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
<input type="hidden" name="action" value="Disable">
<input type="hidden" name="token" value="<?php echo $token ?>">
</form>
<form role="form" method="post" id="disablelogsform-noflush">
<input type="hidden" name="field" value="Logging">
<input type="hidden" name="action" value="Disable-noflush">
<input type="hidden" name="token" value="<?php echo $token ?>">
</form>
<form role="form" method="post" id="poweroffform">
<input type="hidden" name="field" value="poweroff">
<input type="hidden" name="token" value="<?php echo $token ?>">
+69 -4
View File
@@ -36,12 +36,47 @@ a.lookatme {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
#all-queries {
table-layout: fixed;
.table-responsive {
-webkit-overflow-scrolling: touch;
}
#all-queries tbody {
word-wrap: break-word;
/* Optimize Queries-Table for small screens */
#all-queries td:nth-of-type(1), /* Time column */
#all-queries td:nth-of-type(5) { /* Status column */
white-space: nowrap;
}
#all-queries td:nth-of-type(3) { /* Domain column */
min-width: 200px;
word-break: break-all;
white-space: pre-wrap;
}
#all-queries_info { /* Allow Info String to wrap (useful while filtering entries on small screen) */
white-space: unset;
}
#all-queries_wrapper .pagination > li > a { /* adjust the buttons width */
padding-left: 6px;
padding-right: 6px;
min-width: 34px;
text-align: center;
}
@media screen and (max-width: 500px),
screen and (min-width: 767px) and (max-width: 1000px) {
/* Hide "Previous" & "Next"-Buttons in Pagination */
#all-queries_wrapper .pagination > li.previous,
#all-queries_wrapper .pagination > li.next {
display: none;
}
#all-queries_wrapper .pagination > li:nth-of-type(2) a {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
#all-queries_wrapper .pagination > li:nth-last-of-type(2) a {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
.main-header>.navbar {
height: 50px;
}
@@ -50,3 +85,33 @@ a.lookatme {
color: red;
font-weight: bold;
}
.vertical-alignment-helper {
display: table;
width: 100%;
height: 100%;
pointer-events: none;
}
.vertical-alignment-helper > .vertical-align-center {
display: table-cell;
vertical-align: middle;
}
.vertical-alignment-helper > .vertical-align-center > .modal-content {
width: 250px;
margin-left: auto;
margin-right: auto;
word-wrap: break-word;
pointer-events: all;
}
.alSpinner {
top: 0.1em;
left: 0.1em;
width: 0.8em;
height: 0.8em;
border-radius: 50%;
border: 4px solid silver;
border-right-color: transparent;
-webkit-animation: fa-spin 1s infinite linear;
animation: fa-spin 1s infinite linear;
}