Merge pull request #1638 from pi-hole/release/v5.2

Pi-hole Web 5.2 Release PR
This commit is contained in:
Adam Warner
2020-11-28 19:04:59 +00:00
committed by GitHub
54 changed files with 1936 additions and 930 deletions
+12
View File
@@ -288,6 +288,11 @@ else
// Get specific domain only
sendRequestFTL("getallqueries-domain ".$_GET['domain']);
}
else if(isset($_GET['client']) && (isset($_GET['type']) && $_GET['type'] === "blocked"))
{
// Get specific client only
sendRequestFTL("getallqueries-client-blocked ".$_GET['client']);
}
else if(isset($_GET['client']))
{
// Get specific client only
@@ -411,6 +416,13 @@ else
$data = array_merge($data, $result);
}
if (isset($_GET['delete_lease']) && $auth)
{
sendRequestFTL("delete-lease ".$_GET['delete_lease']);
$return = getResponseFTL();
$data["delete_lease"] = $return[0];
}
disconnectFTL();
}
?>
+20 -11
View File
@@ -62,18 +62,27 @@ if(isset($_GET["network"]) && $auth)
while($results !== false && $res = $results->fetchArray(SQLITE3_ASSOC))
{
$id = $res["id"];
// Empty array for holding the IP addresses
$id = intval($res["id"]);
// Get IP addresses and host names for this device
$res["ip"] = array();
// Get IP addresses for this device
$network_addresses = $db->query("SELECT ip FROM network_addresses WHERE network_id = $id ORDER BY lastSeen DESC");
while($network_addresses !== false && $ip = $network_addresses->fetchArray(SQLITE3_ASSOC))
array_push($res["ip"],$ip["ip"]);
// UTF-8 encode host name and vendor
$res["name"] = utf8_encode($res["name"]);
$res["name"] = array();
$network_addresses = $db->query("SELECT ip,name FROM network_addresses WHERE network_id = $id ORDER BY lastSeen DESC");
while($network_addresses !== false && $network_address = $network_addresses->fetchArray(SQLITE3_ASSOC))
{
array_push($res["ip"],$network_address["ip"]);
if($network_address["name"] !== null)
array_push($res["name"],utf8_encode($network_address["name"]));
else
array_push($res["name"],"");
}
$network_addresses->finalize();
// UTF-8 encode vendor
$res["macVendor"] = utf8_encode($res["macVendor"]);
array_push($network, $res);
}
$results->finalize();
$data = array_merge($data, array('network' => $network));
}
@@ -85,7 +94,7 @@ if (isset($_GET['getAllQueries']) && $auth)
{
$from = intval($_GET["from"]);
$until = intval($_GET["until"]);
$dbquery = "SELECT timestamp, type, domain, client, status FROM queries WHERE timestamp >= :from AND timestamp <= :until ";
$dbquery = "SELECT timestamp, type, domain, client, status, forward FROM queries WHERE timestamp >= :from AND timestamp <= :until ";
if(isset($_GET["types"]))
{
$types = $_GET["types"];
@@ -144,8 +153,8 @@ if (isset($_GET['getAllQueries']) && $auth)
$query_type = "UNKN";
break;
}
// array: time type domain client status
$allQueries[] = [$row[0], $query_type, utf8_encode($row[2]), utf8_encode($c), $row[4]];
// array: time type domain client status upstream destination
$allQueries[] = [$row[0], $query_type, utf8_encode($row[2]), utf8_encode($c), $row[4], utf8_encode($row[5])];
}
}
$result = array('data' => $allQueries);
+2 -2
View File
@@ -72,8 +72,8 @@
</div>
<!-- /.row -->
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/auditlog.js"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/auditlog.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+97
View File
@@ -0,0 +1,97 @@
<?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";
?>
<!-- Title -->
<div class="page-header">
<h1>Local CNAME Records</h1>
<small>On this page, you can add CNAME records.</small>
</div>
<!-- Domain Input -->
<div class="row">
<div class="col-md-12">
<div class="box">
<!-- /.box-header -->
<div class="box-header with-border">
<h3 class="box-title">
Add a new CNAME record
</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<div class="row">
<div class="form-group col-md-6">
<label for="domain">Domain:</label>
<input id="domain" type="url" class="form-control" placeholder="Add a domain (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
<div class="form-group col-md-6">
<label for="target">Target Domain:</label>
<input id="target" type="url" class="form-control" placeholder="Associated Target Domain" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</div>
</div>
</div>
<div class="box-footer clearfix">
<button type="button" id="btnAdd" class="btn btn-primary pull-right">Add</button>
</div>
</div>
</div>
</div>
<!-- Alerts -->
<div id="alInfo" class="alert alert-info alert-dismissible fade in" role="alert" hidden>
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
Updating CNAME records...
</div>
<div id="alSuccess" class="alert alert-success alert-dismissible fade in" role="alert" hidden>
<button type="button" class="close" data-hide="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
Success! The list will refresh.
</div>
<div id="alFailure" class="alert alert-danger alert-dismissible fade in" role="alert" hidden>
<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>
<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>
<div class="row">
<div class="col-md-12">
<div class="box" id="recent-queries">
<div class="box-header with-border">
<h3 class="box-title">
List of local CNAME records
</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<table id="customCNAMETable" class="table table-striped table-bordered" width="100%">
<thead>
<tr>
<th>Domain</th>
<th>Target</th>
<th>Action</th>
</tr>
</thead>
</table>
<button type="button" id="resetButton" class="btn btn-default btn-sm text-red hidden">Clear Filters</button>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</div>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/customcname.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
?>
+3 -3
View File
@@ -67,9 +67,9 @@
</div>
</div>
<script src="scripts/vendor/daterangepicker.min.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/db_graph.js"></script>
<script src="scripts/vendor/daterangepicker.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/db_graph.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+3 -3
View File
@@ -139,9 +139,9 @@ else
<!-- /.col -->
</div>
<script src="scripts/vendor/daterangepicker.min.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/db_lists.js"></script>
<script src="scripts/vendor/daterangepicker.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/db_lists.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+39 -3
View File
@@ -45,6 +45,7 @@
<div class="col-md-3">
<div><input type="checkbox" id="type_forwarded" checked><label for="type_forwarded">Permitted: forwarded</label><br></div>
<div><input type="checkbox" id="type_cached" checked><label for="type_cached">Permitted: cached</label></div>
<div><input type="checkbox" id="type_retried" checked><label for="type_retried">Permitted: Retried</label></div>
</div>
<div class="col-md-3">
<div><input type="checkbox" id="type_gravity" checked><label for="type_gravity">Blocked: gravity</label><br></div>
@@ -122,6 +123,40 @@
<!-- ./col -->
</div>
<!-- Alert Modal -->
<div id="alertModal" class="modal fade" role="dialog" data-backdrop="static" data-keyboard="false">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center">
<div class="modal-content">
<div class="modal-body text-center">
<span class="fa-stack fa-2x" style="margin-bottom: 10px">
<div class="alProcessing">
<i class="fa-stack-2x alSpinner"></i>
</div>
<div class="alSuccess" style="display: none">
<i class="fa fa-circle fa-stack-2x text-green"></i>
<i class="fa fa-check fa-stack-1x fa-inverse"></i>
</div>
<div class="alFailure" style="display: none">
<i class="fa fa-circle fa-stack-2x text-red"></i>
<i class="fa fa-times fa-stack-1x fa-inverse"></i>
</div>
</span>
<div class="alProcessing">Adding <span id="alDomain"></span> to the <span id="alList"></span>...</div>
<div class="alSuccess text-bold text-green" style="display: none"><span id="alDomain"></span> successfully added to the <span id="alList"></span></div>
<div class="alFailure text-bold text-red" style="display: none">
<span id="alNetErr">Timeout or Network Connection Error!</span>
<span id="alCustomErr"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="box" id="recent-queries">
@@ -159,9 +194,10 @@
</div>
</div>
<!-- /.row -->
<script src="scripts/pi-hole/js/ip-address-sorting.js"></script>
<script src="scripts/vendor/daterangepicker.min.js"></script>
<script src="scripts/pi-hole/js/db_queries.js"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/daterangepicker.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/db_queries.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+1 -1
View File
@@ -19,7 +19,7 @@
<button type="button" id="debugBtn" class="btn btn-lg btn-primary btn-block">Generate debug log</button>
<pre id="output" style="width: 100%; height: 100%;" hidden></pre>
<script src="scripts/pi-hole/js/debug.js"></script>
<script src="scripts/pi-hole/js/debug.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+3 -3
View File
@@ -89,9 +89,9 @@
</div>
</div>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js"></script>
<script src="scripts/pi-hole/js/customdns.js"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/customdns.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+1 -1
View File
@@ -25,7 +25,7 @@
<button type="button" id="gravityBtn" class="btn btn-lg btn-primary btn-block">Update</button>
<pre id="output" style="width: 100%; height: 100%;" hidden></pre>
<script src="scripts/pi-hole/js/gravity.js"></script>
<script src="scripts/pi-hole/js/gravity.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+4 -4
View File
@@ -77,10 +77,10 @@
</div>
</div>
<script src="scripts/vendor/bootstrap-select.min.js"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/groups-adlists.js"></script>
<script src="scripts/vendor/bootstrap-select.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/groups-adlists.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+19 -8
View File
@@ -30,14 +30,25 @@
<label for="select">Known clients:</label>
<select id="select" class="form-control" placeholder="">
<option disabled selected>Loading...</option>
</select><br>
<input id="ip-custom" type="text" class="form-control" disabled placeholder="Client IP address (IPv4 or IPv6, CIDR subnetting available, optional)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
</select>
</div>
<div class="form-group col-md-6">
<label for="new_comment">Comment:</label>
<input id="new_comment" type="text" class="form-control" placeholder="Client description (optional)">
</div>
</div>
<div class="row">
<div class="col-md-12">
<p>You can select an existing client or add a custom one by typing into the field above and confirming your entry with <kbd>&#x23CE;</kbd>.</p>
<p>Clients may be described either by their IP addresses (IPv4 and IPv6 are supported),
IP subnets (CIDR notation, like <code>192.168.2.0/24</code>),
their MAC addresses (like <code>12:34:56:78:9A:BC</code>),
by their hostnames (like <code>localhost</code>), or by the interface they are connected to (prefaced with a colon, like <code>:eth0</code>).</p>
<p>Note that client recognition by IP addresses (incl. subnet ranges) are prefered over MAC address, host name or interface recognition as
the two latter will only be available after some time.
Furthermore, MAC address recognition only works for devices at most one networking hop away from your Pi-hole.</p>
</div>
</div>
</div>
<div class="box-footer clearfix">
<button type="button" id="btnAdd" class="btn btn-primary pull-right">Add</button>
@@ -59,7 +70,7 @@
<thead>
<tr>
<th>ID</th>
<th>IP address</th>
<th title="Acceptable values are: IP address, subnet (CIDR notation), MAC address (AA:BB:CC:DD:EE:FF format) or host names.">Client</th>
<th>Comment</th>
<th>Group assignment</th>
<th>Action</th>
@@ -74,11 +85,11 @@
</div>
</div>
<script src="scripts/vendor/bootstrap-select.min.js"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/groups-clients.js"></script>
<script src="scripts/vendor/bootstrap-select.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/groups-clients.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+4 -4
View File
@@ -138,10 +138,10 @@
</div>
</div>
<script src="scripts/vendor/bootstrap-select.min.js"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/groups-domains.js"></script>
<script src="scripts/vendor/bootstrap-select.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/groups-domains.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+4 -4
View File
@@ -76,10 +76,10 @@
</div>
</div>
<script src="scripts/vendor/bootstrap-select.min.js"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/groups.js"></script>
<script src="scripts/vendor/bootstrap-select.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/bootstrap-toggle.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/groups.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+3 -3
View File
@@ -7,7 +7,7 @@
* Please see LICENSE file for your rights under this license. */
$indexpage = true;
require "scripts/pi-hole/php/header.php";
require_once("scripts/pi-hole/php/gravity.php");
require_once "scripts/pi-hole/php/gravity.php";
function getinterval()
{
@@ -294,8 +294,8 @@ else
<!-- /.row -->
<?php } ?>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/index.js"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/index.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+2 -2
View File
@@ -41,8 +41,8 @@
</div>
</div>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/messages.js"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/messages.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+3 -3
View File
@@ -66,9 +66,9 @@
</div>
<!-- /.row -->
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js"></script>
<script src="scripts/pi-hole/js/network.js"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/network.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+670 -476
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -23,11 +23,11 @@
"test": "npm run prettier:check && npm run xo"
},
"devDependencies": {
"autoprefixer": "^9.8.5",
"autoprefixer": "^9.8.6",
"eslint-plugin-compat": "^3.8.0",
"postcss-cli": "^7.1.1",
"prettier": "2.0.4",
"xo": "^0.32.1"
"postcss-cli": "^7.1.2",
"prettier": "2.2.1",
"xo": "^0.35.0"
},
"browserslist": [
"defaults",
+31 -5
View File
@@ -28,6 +28,10 @@ if(isset($setupVars["API_QUERY_LOG_SHOW"]))
$showing = "showing no queries (due to setting)";
}
}
else if(isset($_GET["type"]) && $_GET["type"] === "blocked")
{
$showing = "showing blocked";
}
else
{
// If filter variable is not set, we
@@ -44,6 +48,24 @@ else if(isset($_GET["client"]))
{
$showing .= " queries for client ".htmlentities($_GET["client"]);
}
else if(isset($_GET["forwarddest"]))
{
if($_GET["forwarddest"] === "blocklist")
$showing .= " queries answered from blocklists";
elseif($_GET["forwarddest"] === "cache")
$showing .= " queries answered from cache";
else
$showing .= " queries for upstream destination ".htmlentities($_GET["forwarddest"]);
}
else if(isset($_GET["querytype"]))
{
$qtypes = ["A (IPv4)", "AAAA (IPv6)", "ANY", "SRV", "SOA", "PTR", "TXT", "NAPTR"];
$qtype = intval($_GET["querytype"]);
if($qtype > 0 && $qtype <= count($qtypes))
$showing .= " ".$qtypes[$qtype-1]." queries";
else
$showing .= " type ".$qtype." queries";
}
else if(isset($_GET["domain"]))
{
$showing .= " queries for domain ".htmlentities($_GET["domain"]);
@@ -101,6 +123,9 @@ if(strlen($showing) > 0)
<span id="alCustomErr"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@@ -140,8 +165,9 @@ if(strlen($showing) > 0)
</table>
<p><strong>Filtering options:</strong></p>
<ul>
<li>Use <kbd>Ctrl</kbd> or <kbd>&#8984;</kbd> + <i class="fas fa-mouse-pointer"></i> to add columns to the current filter</li>
<li>Use <kbd>Shift</kbd> + <i class="fas fa-mouse-pointer"></i> to remove columns from the current filter</li>
<li>Click a value in a column to add/remove that value to/from the filter</li>
<li>On a computer: Hold down <kbd>Ctrl</kbd>, <kbd>Alt</kbd>, or <kbd>&#8984;</kbd> to allow highlighting for copying to clipboard</li>
<li>On a mobile: Long press to highlight the text and enable copying to clipboard
</ul><br/><button type="button" id="resetButton" class="btn btn-default btn-sm text-red hidden">Clear filters</button>
</div>
<!-- /.box-body -->
@@ -150,9 +176,9 @@ if(strlen($showing) > 0)
</div>
</div>
<!-- /.row -->
<script src="scripts/pi-hole/js/ip-address-sorting.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/queries.js"></script>
<script src="scripts/pi-hole/js/ip-address-sorting.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/queries.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+17 -6
View File
@@ -15,13 +15,24 @@
<div class="col-md-12">
<div class="box">
<div class="box-body">
<div class="form-group">
<div class="input-group">
<input id="domain" type="text" class="form-control" placeholder="Domain to look for (example.com or sub.example.com)">
<!-- Domain Input <992px -->
<div class="visible-xs-block visible-sm-block">
<div class="input-group-block">
<input id="domain_1" type="url" class="form-control" placeholder="Domain to look for (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off" style="margin-bottom: 5px">
<input id="quiet" type="hidden" value="no">
<div class="text-center" style="display: block; width: 100%">
<button type="button" id="btnSearch_1" class="btn btn-default">Search partial match</button>
<button type="button" id="btnSearchExact_1" class="btn btn-default">Search exact match</button>
</div>
</div>
</div>
<!-- Domain Input >=992px -->
<div class="visible-md-block visible-lg-block">
<div class="input-group">
<input id="domain_2" type="url" class="form-control" placeholder="Domain to look for (example.com or sub.example.com)" autocomplete="off" spellcheck="false" autocapitalize="none" autocorrect="off">
<span class="input-group-btn">
<button type="button" id="btnSearch" class="btn btn-default">Search partial match</button>
<button type="button" id="btnSearchExact" class="btn btn-default">Search exact match</button>
<button type="button" id="btnSearch_2" class="btn btn-default">Search partial match</button>
<button type="button" id="btnSearchExact_2" class="btn btn-default">Search exact match</button>
</span>
</div>
</div>
@@ -32,7 +43,7 @@
<pre id="output" style="width: 100%; height: 100%;" hidden></pre>
<script src="scripts/pi-hole/js/queryads.js"></script>
<script src="scripts/pi-hole/js/queryads.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+135
View File
@@ -0,0 +1,135 @@
/* 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. */
/* global utils:false */
var table;
var token = $("#token").text();
function showAlert(type, message) {
var alertElement = null;
var messageElement = null;
switch (type) {
case "info":
alertElement = $("#alInfo");
break;
case "success":
alertElement = $("#alSuccess");
break;
case "warning":
alertElement = $("#alWarning");
messageElement = $("#warn");
break;
case "error":
alertElement = $("#alFailure");
messageElement = $("#err");
break;
default:
return;
}
if (messageElement !== null) messageElement.html(message);
alertElement.fadeIn(200);
alertElement.delay(8000).fadeOut(2000);
}
$(function () {
$("#btnAdd").on("click", addCustomCNAME);
table = $("#customCNAMETable").DataTable({
ajax: {
url: "scripts/pi-hole/php/customcname.php",
data: { action: "get", token: token },
type: "POST"
},
columns: [{}, {}, { orderable: false, searchable: false }],
columnDefs: [
{
targets: 2,
render: function (data, type, row) {
return (
'<button type="button" class="btn btn-danger btn-xs deleteCustomCNAME" data-domain=\'' +
row[0] +
"' data-target='" +
row[1] +
"'>" +
'<span class="far fa-trash-alt"></span>' +
"</button>"
);
}
}
],
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
],
order: [[0, "asc"]],
stateSave: true,
stateSaveCallback: function (settings, data) {
utils.stateSaveCallback("LocalCNAMETable", data);
},
stateLoadCallback: function () {
return utils.stateLoadCallback("LocalCNAMETable");
},
drawCallback: function () {
$(".deleteCustomCNAME").on("click", deleteCustomCNAME);
}
});
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
input.setAttribute("autocomplete", "off");
input.setAttribute("autocorrect", "off");
input.setAttribute("autocapitalize", "off");
input.setAttribute("spellcheck", false);
});
function addCustomCNAME() {
var domain = utils.escapeHtml($("#domain").val());
var target = utils.escapeHtml($("#target").val());
showAlert("info");
$.ajax({
url: "scripts/pi-hole/php/customcname.php",
method: "post",
dataType: "json",
data: { action: "add", domain: domain, target: target, token: token },
success: function (response) {
if (response.success) {
showAlert("success");
table.ajax.reload();
} else showAlert("error", response.message);
},
error: function () {
showAlert("error", "Error while adding this custom CNAME record");
}
});
}
function deleteCustomCNAME() {
var domain = $(this).attr("data-domain");
var target = $(this).attr("data-target");
showAlert("info");
$.ajax({
url: "scripts/pi-hole/php/customcname.php",
method: "post",
dataType: "json",
data: { action: "delete", domain: domain, target: target, token: token },
success: function (response) {
if (response.success) {
showAlert("success");
table.ajax.reload();
} else showAlert("error", response.message);
},
error: function (jqXHR, exception) {
showAlert("error", "Error while deleting this custom CNAME record");
console.log(exception); // eslint-disable-line no-console
}
});
}
+12
View File
@@ -65,6 +65,18 @@ $(function () {
}
}
],
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
],
order: [[0, "asc"]],
stateSave: true,
stateSaveCallback: function (settings, data) {
utils.stateSaveCallback("LocalDNSTable", data);
},
stateLoadCallback: function () {
return utils.stateLoadCallback("LocalDNSTable");
},
drawCallback: function () {
$(".deleteCustomDNS").on("click", deleteCustomDNS);
}
+1
View File
@@ -20,6 +20,7 @@ $(function () {
{
timePicker: true,
timePickerIncrement: 15,
timePicker24Hour: true,
locale: { format: dateformat },
startDate: start__,
endDate: end__,
+1
View File
@@ -22,6 +22,7 @@ $(function () {
{
timePicker: true,
timePickerIncrement: 15,
timePicker24Hour: true,
locale: { format: dateformat },
startDate: start__,
endDate: end__,
+28 -76
View File
@@ -5,7 +5,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
/* global moment:false */
/* global moment:false, utils:false */
var start__ = moment().subtract(6, "days");
var from = moment(start__).utc().valueOf() / 1000;
@@ -40,6 +40,7 @@ $(function () {
{
timePicker: true,
timePickerIncrement: 15,
timePicker24Hour: true,
locale: { format: dateformat },
startDate: start__,
endDate: end__,
@@ -72,70 +73,6 @@ $(function () {
var tableApi, statistics;
function add(domain, list) {
var token = $("#token").text();
var alInfo = $("#alInfo");
var alList = $("#alList");
var alDomain = $("#alDomain");
alDomain.html(domain);
var alSuccess = $("#alSuccess");
var alFailure = $("#alFailure");
var err = $("#err");
if (list === "white") {
alList.html("Whitelist");
} else {
alList.html("Blacklist");
}
alInfo.show();
alSuccess.hide();
alFailure.hide();
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
data: {
domain: domain,
list: list,
token: token,
action: "add_domain",
comment: "Added from Long-Term-Data Query Log"
},
success: function (response) {
if (!response.success) {
alFailure.show();
err.html(response.message);
alFailure.delay(4000).fadeOut(2000, function () {
alFailure.hide();
});
} else {
alSuccess.show();
alSuccess.delay(1000).fadeOut(2000, function () {
alSuccess.hide();
});
}
alInfo.delay(1000).fadeOut(2000, function () {
alInfo.hide();
alList.html("");
alDomain.html("");
});
},
error: function () {
alFailure.show();
err.html("");
alFailure.delay(1000).fadeOut(2000, function () {
alFailure.hide();
});
alInfo.delay(1000).fadeOut(2000, function () {
alInfo.hide();
alList.html("");
alDomain.html("");
});
}
});
}
function handleAjaxError(xhr, textStatus) {
if (textStatus === "timeout") {
alert("The server took too long to send the data.");
@@ -194,6 +131,12 @@ function getQueryTypes() {
queryType.push(11);
}
if ($("#type_retried").prop("checked")) {
// Multiple IDs correspond to this status
// We request queries with all of them
queryType.push([12, 13]);
}
return queryType.join(",");
}
@@ -238,13 +181,9 @@ function refreshTableData() {
}
$(function () {
var APIstring;
if (instantquery) {
APIstring = "api_db.php?getAllQueries&from=" + from + "&until=" + until;
} else {
APIstring = "api_db.php?getAllQueries=empty";
}
var APIstring = instantquery
? "api_db.php?getAllQueries&from=" + from + "&until=" + until
: "api_db.php?getAllQueries=empty";
// Check if query type filtering is enabled
var queryType = getQueryTypes();
@@ -265,7 +204,10 @@ $(function () {
break;
case 2:
color = "green";
fieldtext = "OK <br class='hidden-lg'>(forwarded)";
fieldtext =
"OK <br class='hidden-lg'>(forwarded to " +
(data.length > 5 && data[5] !== "N/A" ? data[5] : "") +
")";
buttontext =
'<button type="button" class="btn btn-default btn-sm text-red"><i class="fa fa-ban"></i> Blacklist</button>';
break;
@@ -320,6 +262,16 @@ $(function () {
buttontext =
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
break;
case 12:
color = "green";
fieldtext = "Retried";
buttontext = "";
break;
case 13:
color = "green";
fieldtext = "Retried <br class='hidden-lg'>(ignored)";
buttontext = "";
break;
default:
color = "black";
fieldtext = "Unknown";
@@ -392,10 +344,10 @@ $(function () {
});
$("#all-queries tbody").on("click", "button", function () {
var data = tableApi.row($(this).parents("tr")).data();
if (data[4] === 1 || data[4] === 4 || data[5] === 5) {
add(data[2], "white");
if ([1, 4, 5, 9, 10, 11].indexOf(data[4]) !== -1) {
utils.addFromQueryLog(data[2], "white");
} else {
add(data[2], "black");
utils.addFromQueryLog(data[2], "black");
}
});
+7 -8
View File
@@ -96,12 +96,10 @@ function piholeChange(action, duration) {
function checkMessages() {
$.getJSON("api_db.php?status", function (data) {
if ("message_count" in data && data.message_count > 0) {
var title;
if (data.message_count > 1) {
title = "There are " + data.message_count + " warnings. Click for further details.";
} else {
title = "There is one warning. Click for further details.";
}
var title =
data.message_count > 1
? "There are " + data.message_count + " warnings. Click for further details."
: "There is one warning. Click for further details.";
$("#pihole-diagnosis").prop("title", title);
$("#pihole-diagnosis-count").text(data.message_count);
@@ -133,7 +131,8 @@ function initCheckboxRadioStyle() {
function applyCheckboxRadioStyle(style) {
boxsheet.attr("href", getCheckboxURL(style));
var sel = $("input[type='radio'],input[type='checkbox']");
// Get all radio/checkboxes for theming, with the exception of the two radio buttons on the custom disable timer
var sel = $("input[type='radio'],input[type='checkbox']").not("#selSec").not("#selMin");
sel.parent().removeClass();
sel.parent().addClass("icheck-" + style);
}
@@ -170,7 +169,7 @@ function initCPUtemp() {
switch (unit) {
case "K":
temperature += 273.15;
displaytemp.html(temperature.toFixed(1) + "&nbsp;&deg;K");
displaytemp.html(temperature.toFixed(1) + "&nbsp;K");
break;
case "F":
+7 -2
View File
@@ -56,8 +56,10 @@ function initTable() {
var tooltip =
"Added: " +
utils.datetime(data.date_added, false) +
"\nLast modified: " +
"\nLast modified (database entry): " +
utils.datetime(data.date_modified, false) +
"\nLast updated (list content): " +
(data.date_updated !== null ? utils.datetime(data.date_updated, false) : "N/A") +
"\nDatabase ID: " +
data.id;
$("td:eq(0)", row).html(
@@ -86,7 +88,7 @@ function initTable() {
$("td:eq(2)", row).html('<input id="comment_' + data.id + '" class="form-control">');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(data.comment);
commentEl.val(utils.unescapeHtml(data.comment));
commentEl.on("change", editAdlist);
$("td:eq(3)", row).empty();
@@ -176,6 +178,7 @@ function initTable() {
},
stateLoadCallback: function () {
var data = utils.stateLoadCallback("groups-adlists-table");
// Return if not available
if (data === null) {
return null;
@@ -219,6 +222,8 @@ function addAdlist() {
utils.showAlert("info", "", "Adding adlist...", address);
if (address.length === 0) {
// enable the ui elements again
utils.enableAll();
utils.showAlert("warning", "", "Warning", "Please specify an adlist address");
return;
}
+44 -28
View File
@@ -17,28 +17,35 @@ function reloadClientSuggestions() {
{ action: "get_unconfigured_clients", token: token },
function (data) {
var sel = $("#select");
var customWasSelected = sel.val() === "custom";
sel.empty();
// In order for the placeholder value to appear, we have to have a blank
// <option> as the first option in our <select> control. This is because
// the browser tries to select the first option by default. If our first
// option were non-empty, the browser would display this instead of the
// placeholder.
sel.append($("<option />"));
// Add data obtained from API
for (var key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) {
continue;
}
var text = key;
var keyPlain = key;
if (key.startsWith("IP-")) {
// Mock MAC address for address-only devices
keyPlain = key.substring(3);
text = keyPlain;
}
// Append host name if available
if (data[key].length > 0) {
text += " (" + data[key] + ")";
}
sel.append($("<option />").val(key).text(text));
}
if (data.length === 0) {
$("#ip-custom").prop("disabled", false);
}
sel.append($("<option />").val("custom").text("Custom, specified below..."));
if (customWasSelected) {
sel.val("custom");
sel.append($("<option />").val(keyPlain).text(text));
}
},
"json"
@@ -59,6 +66,11 @@ function getGroups() {
$(function () {
$("#btnAdd").on("click", addClient);
$("select").select2({
tags: true,
placeholder: "Select client...",
allowClear: true
});
reloadClientSuggestions();
utils.setBsSelectDefaults();
@@ -120,7 +132,7 @@ function initTable() {
$("td:eq(1)", row).html('<input id="comment_' + data.id + '" class="form-control">');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(data.comment);
commentEl.val(utils.unescapeHtml(data.comment));
commentEl.on("change", editClient);
$("td:eq(2)", row).empty();
@@ -210,6 +222,7 @@ function initTable() {
},
stateLoadCallback: function () {
var data = utils.stateLoadCallback("groups-clients-table");
// Return if not available
if (data === null) {
return null;
@@ -221,6 +234,7 @@ function initTable() {
return data;
}
});
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
if (input !== null) {
@@ -238,6 +252,7 @@ function initTable() {
$("#resetButton").addClass("hidden");
}
});
$("#resetButton").on("click", function () {
table.order([[0, "asc"]]).draw();
$("#resetButton").addClass("hidden");
@@ -245,33 +260,34 @@ function initTable() {
}
function addClient() {
var ip = $("#select").val();
var ip = $("#select").val().trim();
var comment = utils.escapeHtml($("#new_comment").val());
if (ip === "custom") {
ip = utils.escapeHtml($("#ip-custom").val().trim());
}
utils.disableAll();
utils.showAlert("info", "", "Adding client...", ip);
if (ip.length === 0) {
utils.enableAll();
utils.showAlert("warning", "", "Warning", "Please specify a client IP address");
utils.showAlert("warning", "", "Warning", "Please specify a client IP or MAC address");
return;
}
// Validate IP address (may contain CIDR details)
var ipv6format = ip.includes(":");
if (!ipv6format && !utils.validateIPv4CIDR(ip)) {
// Validate input, can be:
// - IPv4 address (with and without CIDR)
// - IPv6 address (with and without CIDR)
// - MAC address (in the form AA:BB:CC:DD:EE:FF)
// - host name (arbitrary form, we're only checking against some reserved charaters)
if (utils.validateIPv4CIDR(ip) || utils.validateIPv6CIDR(ip) || utils.validateMAC(ip)) {
// Convert input to upper case (important for MAC addresses)
ip = ip.toUpperCase();
} else if (!utils.validateHostname(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!");
utils.showAlert(
"warning",
"",
"Warning",
"Input is neither a valid IP or MAC address nor a valid host name!"
);
return;
}
+3 -2
View File
@@ -73,7 +73,7 @@ function initTable() {
{ data: "type", searchable: false },
{ data: "enabled", searchable: false },
{ data: "comment" },
{ data: "groups", searchable: false },
{ data: "groups", searchable: false, visible: showtype === "all" },
{ data: null, width: "80px", orderable: false }
],
drawCallback: function () {
@@ -149,7 +149,7 @@ function initTable() {
$("td:eq(3)", row).html('<input id="comment_' + data.id + '" class="form-control">');
var commentEl = $("#comment_" + data.id, row);
commentEl.val(data.comment);
commentEl.val(utils.unescapeHtml(data.comment));
commentEl.on("change", editDomain);
// Show group assignment field only if in full domain management mode
@@ -251,6 +251,7 @@ function initTable() {
},
stateLoadCallback: function () {
var data = utils.stateLoadCallback("groups-domains-table");
// Return if not available
if (data === null) {
return null;
+5 -2
View File
@@ -43,7 +43,7 @@ $(function () {
'<input id="name_' + data.id + '" title="' + tooltip + '" class="form-control">'
);
var nameEl = $("#name_" + data.id, row);
nameEl.val(data.name);
nameEl.val(utils.unescapeHtml(data.name));
nameEl.on("change", editGroup);
var disabled = data.enabled === 0;
@@ -63,7 +63,7 @@ $(function () {
$("td:eq(2)", row).html('<input id="desc_' + data.id + '" class="form-control">');
var desc = data.description !== null ? data.description : "";
var descEl = $("#desc_" + data.id, row);
descEl.val(desc);
descEl.val(utils.unescapeHtml(desc));
descEl.on("change", editGroup);
$("td:eq(3)", row).empty();
@@ -91,6 +91,7 @@ $(function () {
},
stateLoadCallback: function () {
var data = utils.stateLoadCallback("groups-table");
// Return if not available
if (data === null) {
return null;
@@ -134,6 +135,8 @@ function addGroup() {
utils.showAlert("info", "", "Adding group...", name);
if (name.length === 0) {
// enable the ui elements again
utils.enableAll();
utils.showAlert("warning", "", "Warning", "Please specify a group name");
return;
}
+26 -17
View File
@@ -251,15 +251,11 @@ function updateQueriesOverTime() {
// Add data for each hour that is available
for (var hour in data.ads_over_time[0]) {
if (Object.prototype.hasOwnProperty.call(data.ads_over_time[0], hour)) {
var d, h;
h = parseInt(data.domains_over_time[0][hour], 10);
if (parseInt(data.ads_over_time[0][0], 10) < 1200) {
// Fallback - old style
d = new Date().setHours(Math.floor(h / 6), 10 * (h % 6), 0, 0);
} else {
// New style: Get Unix timestamps
d = new Date(1000 * h);
}
var h = parseInt(data.domains_over_time[0][hour], 10);
var d =
parseInt(data.ads_over_time[0][0], 10) < 1200
? new Date().setHours(Math.floor(h / 6), 10 * (h % 6), 0, 0)
: new Date(1000 * h);
timeLineChart.data.labels.push(d);
var blocked = data.ads_over_time[1][hour];
@@ -297,14 +293,9 @@ function updateQueryTypesPie() {
var v = [],
c = [],
k = [],
i = 0,
iter;
i = 0;
// Collect values and colors, and labels
if (Object.prototype.hasOwnProperty.call(data, "querytypes")) {
iter = data.querytypes;
} else {
iter = data;
}
var iter = Object.prototype.hasOwnProperty.call(data, "querytypes") ? data.querytypes : data;
querytypeids = [];
Object.keys(iter).forEach(function (key) {
@@ -592,7 +583,7 @@ function updateTopClientsChart() {
url =
'<a href="queries.php?client=' +
clientip +
'" title="' +
'&type=blocked" title="' +
clientip +
'">' +
clientname +
@@ -1001,6 +992,24 @@ $(function () {
return false;
});
$("#clientsChart").click(function (evt) {
var activePoints = clientsChart.getElementAtEvent(evt);
if (activePoints.length > 0) {
//get the internal index of slice in pie chart
var clickedElementindex = activePoints[0]._index;
//get specific label by index
var label = clientsChart.data.labels[clickedElementindex];
//get value by index
var from = label / 1000 - 300;
var until = label / 1000 + 300;
window.location.href = "queries.php?from=" + from + "&until=" + until;
}
return false;
});
if (document.getElementById("queryTypePieChart")) {
ctx = document.getElementById("queryTypePieChart").getContext("2d");
queryTypePieChart = new Chart(ctx, {
+8 -3
View File
@@ -5,12 +5,17 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
// This code has been adapted from
// This code has been taken from
// https://datatables.net/plug-ins/sorting/ip-address
// and was modified by the Pi-hole team to support
// CIDR notation and be more robust against invalid
// input data (like empty IP addresses)
$.extend($.fn.dataTableExt.oSort, {
"ip-address-pre": function (a) {
if (!a) {
return 0;
// Skip empty fields (IP address might have expired or
// reassigned to a differenct device)
if (!a || a.length === 0) {
return Infinity;
}
var i, item;
+3
View File
@@ -73,6 +73,9 @@ function renderMessage(data, type, row) {
"&uarr;</pre>"
);
case "DNSMASQ_CONFIG":
return "FTL failed to start due to " + row.message;
default:
return "Unknown message type<pre>" + JSON.stringify(row) + "</pre>";
}
+38 -3
View File
@@ -69,6 +69,8 @@ $(function () {
tableApi = $("#network-entries").DataTable({
rowCallback: function (row, data) {
var color;
var index;
var maxiter;
var iconClasses;
var lastQuery = parseInt(data.lastQuery, 10);
var diff = getTimestamp() - lastQuery;
@@ -93,7 +95,7 @@ $(function () {
} else {
// This client has never sent a query to Pi-hole, color light-red
color = networkNever;
iconClasses = "fas fa-check";
iconClasses = "fas fa-times";
}
// Set determined background color
@@ -109,14 +111,47 @@ $(function () {
// Set hostname to "unknown" if not available
if (!data.name || data.name.length === 0) {
$("td:eq(3)", row).html("<em>unknown</em>");
} else {
var names = [];
var name = "";
maxiter = Math.min(data.name.length, MAXIPDISPLAY);
index = 0;
for (index = 0; index < maxiter; index++) {
name = data.name[index];
if (name.length === 0) continue;
names.push('<a href="queries.php?client=' + name + '">' + name + "</a>");
}
if (data.name.length > MAXIPDISPLAY) {
// We hit the maximum above, add "..." to symbolize we would
// have more to show here
names.push("...");
}
maxiter = Math.min(data.ip.length, data.name.length);
var allnames = [];
for (index = 0; index < maxiter; index++) {
name = data.name[index];
if (name.length > 0) {
allnames.push(name + " (" + data.ip[index] + ")");
} else {
allnames.push("No host name for " + data.ip[index] + " known");
}
}
$("td:eq(3)", row).html(names.join("<br>"));
$("td:eq(3)", row).hover(function () {
this.title = allnames.join("\n");
});
}
// Set number of queries to localized string (add thousand separators)
$("td:eq(6)", row).html(data.numQueries.toLocaleString());
var ips = [];
var maxiter = Math.min(data.ip.length, MAXIPDISPLAY);
for (var index = 0; index < maxiter; index++) {
maxiter = Math.min(data.ip.length, MAXIPDISPLAY);
index = 0;
for (index = 0; index < maxiter; index++) {
var ip = data.ip[index];
ips.push('<a href="queries.php?client=' + ip + '">' + ip + "</a>");
}
+29 -96
View File
@@ -25,86 +25,6 @@ var replyTypes = [
];
var colTypes = ["time", "query type", "domain", "client", "status", "reply type"];
function add(domain, list) {
var token = $("#token").text();
var alertModal = $("#alertModal");
var alProcessing = alertModal.find(".alProcessing");
var alSuccess = alertModal.find(".alSuccess");
var alFailure = alertModal.find(".alFailure");
var alNetworkErr = alertModal.find(".alFailure #alNetErr");
var alCustomErr = alertModal.find(".alFailure #alCustomErr");
var alList = "#alList";
var alDomain = "#alDomain";
// Exit the function here if the Modal is already shown (multiple running interlock)
if (alertModal.css("display") !== "none") {
return;
}
var listtype;
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/groups.php",
method: "post",
data: {
domain: domain,
list: list,
token: token,
action: "add_domain",
comment: "Added from Query Log"
},
success: function (response) {
alProcessing.hide();
if (!response.success) {
// Failure
alNetworkErr.hide();
alCustomErr.html(response.message);
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 3000);
} else {
// Success
alSuccess.children(alDomain).html(domain);
alSuccess.children(alList).html(listtype);
alSuccess.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 2000);
}
},
error: function () {
// Network Error
alProcessing.hide();
alNetworkErr.show();
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 3000);
}
});
});
// Reset Modal after it has faded out
alertModal.one("hidden.bs.modal", function () {
alProcessing.show();
alSuccess.add(alFailure).hide();
alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html("");
alCustomErr.html("");
});
}
function handleAjaxError(xhr, textStatus) {
if (textStatus === "timeout") {
alert("The server took too long to send the data.");
@@ -148,6 +68,10 @@ $(function () {
APIstring += "=100";
}
if ("type" in GETDict) {
APIstring += "&type=" + GETDict.type;
}
tableApi = $("#all-queries").DataTable({
rowCallback: function (row, data) {
// DNSSEC status
@@ -189,7 +113,11 @@ $(function () {
break;
case "2":
colorClass = "text-green";
fieldtext = "OK <br class='hidden-lg'>(forwarded)" + dnssecStatus;
fieldtext =
"OK <br class='hidden-lg'>(forwarded to " +
(data.length > 10 && data[10] !== "N/A" ? data[10] : "") +
")" +
dnssecStatus;
buttontext =
'<button type="button" class="btn btn-default btn-sm text-red"><i class="fa fa-ban"></i> Blacklist</button>';
break;
@@ -257,6 +185,16 @@ $(function () {
'<button type="button" class="btn btn-default btn-sm text-green"><i class="fas fa-check"></i> Whitelist</button>';
isCNAME = true;
break;
case 12:
colorClass = "text-green";
fieldtext = "Retried";
buttontext = "";
break;
case 13:
colorClass = "text-green";
fieldtext = "Retried <br class='hidden-lg'>(ignored)";
buttontext = "";
break;
default:
colorClass = false;
fieldtext = "Unknown (" + parseInt(data[4], 10) + ")";
@@ -307,14 +245,9 @@ $(function () {
}
// Check for existence of sixth column and display only if not Pi-holed
var replytext,
replyid = data[5];
if (replyid >= 0 && replyid < replyTypes.length) {
replytext = replyTypes[replyid];
} else {
replytext = "? (" + replyid + ")";
}
var replyid = data[5];
var replytext =
replyid >= 0 && replyid < replyTypes.length ? replyTypes[replyid] : "? (" + replyid + ")";
replytext += '<input type="hidden" name="id" value="' + replyid + '">';
@@ -485,9 +418,9 @@ $(function () {
$("#all-queries tbody").on("click", "button", function () {
var data = tableApi.row($(this).parents("tr")).data();
if (data[4] === "2" || data[4] === "3") {
add(data[2], "black");
utils.addFromQueryLog(data[2], "black");
} else {
add(data[2], "white");
utils.addFromQueryLog(data[2], "white");
}
});
@@ -504,19 +437,19 @@ function tooltipText(index, text) {
}
if (index in tableFilters && tableFilters[index].length > 0) {
return "Clear filter on " + colTypes[index] + ' "' + text + '" using Shift + Click.';
return "Click to remove " + colTypes[index] + ' "' + text + '" from filter.';
}
return "Add filter on " + colTypes[index] + ' "' + text + '" using Ctrl + Click.';
return "Click to add " + colTypes[index] + ' "' + text + '" to filter.';
}
function addColumnFilter(event, colID, filterstring) {
// Don't do anything when NOT explicitly requesting multi-selection functions
if (!event.ctrlKey && !event.metaKey && !event.shiftKey) {
// If the below modifier keys are held down, do nothing
if (event.ctrlKey || event.metaKey || event.altKey) {
return;
}
if (event.shiftKey) {
if (tableFilters[colID] === filterstring) {
filterstring = "";
}
+14 -32
View File
@@ -21,7 +21,8 @@ function quietfilter(ta, data) {
function eventsource() {
var ta = $("#output");
var domain = $("#domain").val().trim();
// process with the current visible domain input field
var domain = $("input[id^='domain']:visible").val().trim();
var q = $("#quiet");
if (domain.length === 0) {
@@ -38,7 +39,7 @@ function eventsource() {
if (typeof EventSource !== "function") {
$.ajax({
method: "GET",
url: "scripts/pi-hole/php/queryads.php?domain=" + domain.toLowerCase() + exact + "&IE",
url: "scripts/pi-hole/php/queryads.php?domain=" + domain.toLowerCase() + "&" + exact + "&IE",
async: false
}).done(function (data) {
ta.show();
@@ -86,41 +87,22 @@ function eventsource() {
exact = "";
}
// Handle enter button
$(document).keypress(function (e) {
if (e.which === 13 && $("#domain").is(":focus")) {
// Handle enter key
$("#domain_1, #domain_2").keypress(function (e) {
if (e.which === 13) {
// Enter was pressed, and the input has focus
exact = "";
eventsource();
}
});
// Handle button
$("#btnSearch").on("click", function () {
exact = "";
eventsource();
});
// Handle exact button
$("#btnSearchExact").on("click", function () {
exact = "exact";
eventsource();
});
// Wrap form-group's buttons to next line when viewed on a small screen
$(window).on("resize", function () {
if ($(window).width() < 992) {
$(".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");
// Handle search buttons
$("button[id^='btnSearch']").on("click", function () {
exact = "";
if (this.id.match("^btnSearchExact")) {
exact = "exact";
}
});
$(function () {
$(window).trigger("resize");
eventsource();
});
+46
View File
@@ -6,6 +6,7 @@
* Please see LICENSE file for your rights under this license. */
/* global utils:false */
var token = $("#token").text();
$(function () {
$("[data-static]").on("click", function () {
@@ -283,3 +284,48 @@ $(function () {
localStorage.setItem("barchart_chkbox", bargraphs.prop("checked"));
});
});
// Delete dynamic DHCP lease
$('button[id="removedynamic"]').on("click", function () {
var tr = $(this).closest("tr");
var ipaddr = utils.escapeHtml(tr.children("#IP").text());
var name = utils.escapeHtml(tr.children("#HOST").text());
var ipname = name + " (" + ipaddr + ")";
utils.disableAll();
utils.showAlert("info", "", "Deleting DHCP lease...", ipname);
$.ajax({
url: "api.php",
method: "get",
dataType: "json",
data: {
delete_lease: ipaddr,
token: token
},
success: function (response) {
utils.enableAll();
if (response.delete_lease.startsWith("OK")) {
utils.showAlert(
"success",
"far fa-trash-alt",
"Successfully deleted DHCP lease for ",
ipname
);
// Remove column on success
tr.remove();
// We have to hide the tooltips explicitly or they will stay there forever as
// the onmouseout event does not fire when the element is already gone
$.each($(".tooltip"), function () {
$(this).remove();
});
} else {
utils.showAlert("error", "Error while deleting DHCP lease for " + ipname, response);
}
},
error: function (jqXHR, exception) {
utils.enableAll();
utils.showAlert("error", "Error while deleting DHCP lease for " + ipname, jqXHR.responseText);
console.log(exception); // eslint-disable-line no-console
}
});
});
+108 -1
View File
@@ -17,11 +17,29 @@ function escapeHtml(text) {
"'": "&#039;"
};
if (text === null) return null;
return text.replace(/[&<>"']/g, function (m) {
return map[m];
});
}
function unescapeHtml(text) {
var map = {
"&amp;": "&",
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&#039;": "'"
};
if (text === null) return null;
return text.replace(/&(?:amp|lt|gt|quot|#039);/g, function (m) {
return map[m];
});
}
// Helper function for converting Objects to Arrays after sorting the keys
function objectToArray(obj) {
var arr = [];
@@ -167,6 +185,16 @@ function validateIPv6CIDR(ip) {
return ipv6validator.test(ip);
}
function validateMAC(mac) {
var macvalidator = new RegExp(/^([\da-fA-F]{2}:){5}([\da-fA-F]{2})$/);
return macvalidator.test(mac);
}
function validateHostname(name) {
var namevalidator = new RegExp(/[^<>;"]/);
return namevalidator.test(name);
}
// set bootstrap-select defaults
function setBsSelectDefaults() {
var bsSelectDefaults = $.fn.selectpicker.Constructor.DEFAULTS;
@@ -220,9 +248,85 @@ function getGraphType() {
return localStorage.getItem("barchart_chkbox") === "false" ? "line" : "bar";
}
function addFromQueryLog(domain, list) {
var token = $("#token").text();
var alertModal = $("#alertModal");
var alProcessing = alertModal.find(".alProcessing");
var alSuccess = alertModal.find(".alSuccess");
var alFailure = alertModal.find(".alFailure");
var alNetworkErr = alertModal.find(".alFailure #alNetErr");
var alCustomErr = alertModal.find(".alFailure #alCustomErr");
var alList = "#alList";
var alDomain = "#alDomain";
// Exit the function here if the Modal is already shown (multiple running interlock)
if (alertModal.css("display") !== "none") {
return;
}
var listtype = list === "white" ? "Whitelist" : "Blacklist";
alProcessing.children(alDomain).html(domain);
alProcessing.children(alList).html(listtype);
alertModal.modal("show");
// add Domain to List after Modal has faded in
alertModal.one("shown.bs.modal", function () {
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
data: {
domain: domain,
list: list,
token: token,
action: "replace_domain",
comment: "Added from Query Log"
},
success: function (response) {
alProcessing.hide();
if (!response.success) {
// Failure
alNetworkErr.hide();
alCustomErr.html(response.message);
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 10000);
} else {
// Success
alSuccess.children(alDomain).html(domain);
alSuccess.children(alList).html(listtype);
alSuccess.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 2000);
}
},
error: function () {
// Network Error
alProcessing.hide();
alNetworkErr.show();
alFailure.fadeIn(1000);
setTimeout(function () {
alertModal.modal("hide");
}, 8000);
}
});
});
// Reset Modal after it has faded out
alertModal.one("hidden.bs.modal", function () {
alProcessing.show();
alSuccess.add(alFailure).hide();
alProcessing.add(alSuccess).children(alDomain).html("").end().children(alList).html("");
alCustomErr.html("");
});
}
window.utils = (function () {
return {
escapeHtml: escapeHtml,
unescapeHtml: unescapeHtml,
objectToArray: objectToArray,
padNumber: padNumber,
showAlert: showAlert,
@@ -234,6 +338,9 @@ window.utils = (function () {
setBsSelectDefaults: setBsSelectDefaults,
stateSaveCallback: stateSaveCallback,
stateLoadCallback: stateLoadCallback,
getGraphType: getGraphType
getGraphType: getGraphType,
validateMAC: validateMAC,
validateHostname: validateHostname,
addFromQueryLog: addFromQueryLog
};
})();
+26
View File
@@ -0,0 +1,26 @@
<?php
require_once "func.php";
require_once('auth.php');
// Authentication checks
if (isset($_POST['token'])) {
check_cors();
check_csrf($_POST['token']);
} else {
log_and_die('Not allowed (login session invalid or expired, please relogin on the Pi-hole dashboard)!');
}
switch ($_POST['action'])
{
case 'get': echo json_encode(echoCustomCNAMEEntries()); break;
case 'add': echo json_encode(addCustomCNAMEEntry()); break;
case 'delete': echo json_encode(deleteCustomCNAMEEntry()); break;
default:
die("Wrong action");
}
?>
-2
View File
@@ -2,8 +2,6 @@
require_once "func.php";
$customDNSFile = "/etc/pihole/custom.list";
require_once('auth.php');
// Authentication checks
+3 -3
View File
@@ -22,10 +22,10 @@
<input id="customTimeout" class="form-control" type="number" value="60">
<div class="input-group-btn" data-toggle="buttons">
<label class="btn btn-default">
<input type="radio"> Secs
<input id="selSec" type="radio"> Secs
</label>
<label id="btnMins" class="btn btn-default active">
<input type="radio"> Mins
<input id="selMin" type="radio"> Mins
</label>
</div>
</div>
@@ -99,6 +99,6 @@
</div>
<!-- ./wrapper -->
<script src="scripts/pi-hole/js/footer.js"></script>
<script src="scripts/pi-hole/js/footer.js?v=<?=$cacheVer?>"></script>
</body>
</html>
+174 -29
View File
@@ -81,6 +81,9 @@ function pihole_execute($argument_string, $error_on_failure = true) {
return $output;
}
// Custom DNS
$customDNSFile = "/etc/pihole/custom.list";
function echoCustomDNSEntries()
{
$entries = getCustomDNSEntries();
@@ -121,19 +124,8 @@ function getCustomDNSEntries()
return $entries;
}
function addCustomDNSEntry($ip="", $domain="", $json_reply=true)
function addCustomDNSEntry($ip="", $domain="", $json=true)
{
function error($msg)
{
global $json_reply;
if($json_reply)
return errorJsonResponse($msg);
else {
echo $msg."<br>";
return false;
}
}
try
{
if(isset($_REQUEST['ip']))
@@ -143,18 +135,18 @@ function addCustomDNSEntry($ip="", $domain="", $json_reply=true)
$domain = trim($_REQUEST['domain']);
if (empty($ip))
return error("IP must be set");
return returnError("IP must be set", $json);
$ipType = get_ip_type($ip);
if (!$ipType)
return error("IP must be valid");
return returnError("IP must be valid", $json);
if (empty($domain))
return error("Domain must be set");
return returnError("Domain must be set", $json);
if (!is_valid_domain_name($domain))
return error("Domain must be valid");
return returnError("Domain must be valid", $json);
// Only check for duplicates if adding new records from the web UI (not through Teleporter)
if(isset($_REQUEST['ip']) || isset($_REQUEST['domain']))
@@ -162,17 +154,17 @@ function addCustomDNSEntry($ip="", $domain="", $json_reply=true)
$existingEntries = getCustomDNSEntries();
foreach ($existingEntries as $entry)
if ($entry->domain == $domain && get_ip_type($entry->ip) == $ipType)
return error("This domain already has a custom DNS entry for an IPv" . $ipType);
return returnError("This domain already has a custom DNS entry for an IPv" . $ipType, $json);
}
// Add record
pihole_execute("-a addcustomdns ".$ip." ".$domain);
return $json_reply ? successJsonResponse() : true;
return returnSuccess("", $json);
}
catch (\Exception $ex)
{
return error($ex->getMessage());
return error($ex->getMessage(), $json);
}
}
@@ -184,10 +176,10 @@ function deleteCustomDNSEntry()
$domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
if (empty($ip))
return errorJsonResponse("IP must be set");
return returnError("IP must be set");
if (empty($domain))
return errorJsonResponse("Domain must be set");
return returnError("Domain must be set");
$existingEntries = getCustomDNSEntries();
@@ -200,15 +192,15 @@ function deleteCustomDNSEntry()
}
if (!$found)
return errorJsonResponse("This domain/ip association does not exist");
return returnError("This domain/ip association does not exist");
pihole_execute("-a removecustomdns ".$ip." ".$domain);
return successJsonResponse();
return returnSuccess();
}
catch (\Exception $ex)
{
return errorJsonResponse($ex->getMessage());
return returnError($ex->getMessage());
}
}
@@ -244,17 +236,170 @@ function deleteAllCustomDNSEntries()
}
}
return successJsonResponse();
return returnSuccess();
}
function successJsonResponse($message = "")
// CNAME
$customCNAMEFile = "/etc/dnsmasq.d/05-pihole-custom-cname.conf";
function echoCustomCNAMEEntries()
{
return [ "success" => true, "message" => $message ];
$entries = getCustomCNAMEEntries();
$data = [];
foreach ($entries as $entry)
$data[] = [ $entry->domain, $entry->target ];
return [ "data" => $data ];
}
function errorJsonResponse($message = "")
function getCustomCNAMEEntries()
{
return [ "success" => false, "message" => $message ];
global $customCNAMEFile;
$entries = [];
if (!file_exists($customCNAMEFile)) return $entries;
$handle = fopen($customCNAMEFile, "r");
if ($handle)
{
while (($line = fgets($handle)) !== false) {
$line = str_replace("cname=","", $line);
$line = str_replace("\r","", $line);
$line = str_replace("\n","", $line);
$explodedLine = explode (",", $line);
if (count($explodedLine) <= 1)
continue;
$data = new \stdClass();
$data->domains = array_slice($explodedLine, 0, -1);
$data->domain = implode(",", $data->domains);
$data->target = $explodedLine[count($explodedLine)-1];
$entries[] = $data;
}
fclose($handle);
}
return $entries;
}
function addCustomCNAMEEntry($domain="", $target="", $json=true)
{
try
{
if(isset($_REQUEST['domain']))
$domain = $_REQUEST['domain'];
if(isset($_REQUEST['target']))
$target = $_REQUEST['target'];
if (empty($domain))
return returnError("Domain must be set", $json);
if (empty($target))
return returnError("Target must be set", $json);
// Check if each submitted domain is valid
$domains = array_map('trim', explode(",", $domain));
foreach ($domains as $d) {
if (!is_valid_domain_name($d))
return returnError("Domain '$d' is not valid", $json);
}
$existingEntries = getCustomCNAMEEntries();
// Check if a record for one of the domains already exists
foreach ($existingEntries as $entry)
foreach ($domains as $d)
if (in_array($d, $entry->domains))
return returnError("There is already a CNAME record for '$d'", $json);
pihole_execute("-a addcustomcname ".$domain." ".$target);
return returnSuccess("", $json);
}
catch (\Exception $ex)
{
return returnError($ex->getMessage(), $json);
}
}
function deleteCustomCNAMEEntry()
{
try
{
$target = !empty($_REQUEST['target']) ? $_REQUEST['target']: "";
$domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
if (empty($target))
return returnError("Target must be set");
if (empty($domain))
return returnError("Domain must be set");
$existingEntries = getCustomCNAMEEntries();
$found = false;
foreach ($existingEntries as $entry)
if ($entry->domain == $domain)
if ($entry->target == $target) {
$found = true;
break;
}
if (!$found)
return returnError("This domain/ip association does not exist");
pihole_execute("-a removecustomcname ".$domain." ".$target);
return returnSuccess();
}
catch (\Exception $ex)
{
return returnError($ex->getMessage());
}
}
function deleteAllCustomCNAMEEntries()
{
try
{
$existingEntries = getCustomCNAMEEntries();
foreach ($existingEntries as $entry) {
pihole_execute("-a removecustomcname ".$entry->domain." ".$entry->target);
}
}
catch (\Exception $ex)
{
return returnError($ex->getMessage());
}
return returnSuccess();
}
function returnSuccess($message = "", $json = true)
{
if ($json) {
return [ "success" => true, "message" => $message ];
} else {
echo $msg."<br>";
return true;
}
}
function returnError($message = "", $json = true)
{
if ($json) {
return [ "success" => false, "message" => $message ];
} else {
echo $msg."<br>";
return false;
}
}
?>
+153 -13
View File
@@ -77,6 +77,11 @@ if ($_POST['action'] == 'get_groups') {
}
foreach ($names as $name) {
// Silently skip this entry when it is empty or not a string (e.g. NULL)
if(!is_string($name) || strlen($name) == 0) {
continue;
}
if (!$stmt->bindValue(':name', $name, SQLITE3_TEXT)) {
throw new Exception('While binding name: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " groups");
@@ -184,7 +189,7 @@ if ($_POST['action'] == 'get_groups') {
throw new Exception('Error while querying gravity\'s client_by_group table: ' . $db->lastErrorMsg());
}
$stmt = $FTLdb->prepare('SELECT name FROM network WHERE id = (SELECT network_id FROM network_addresses WHERE ip = :ip);');
$stmt = $FTLdb->prepare('SELECT name FROM network_addresses WHERE ip = :ip;');
if (!$stmt) {
throw new Exception('Error while preparing network table statement: ' . $db->lastErrorMsg());
}
@@ -206,6 +211,7 @@ if ($_POST['action'] == 'get_groups') {
while ($gres = $group_query->fetchArray(SQLITE3_ASSOC)) {
array_push($groups, $gres['group_id']);
}
$group_query->finalize();
$res['groups'] = $groups;
array_push($data, $res);
}
@@ -221,7 +227,7 @@ if ($_POST['action'] == 'get_groups') {
$QUERYDB = getQueriesDBFilename();
$FTLdb = SQLite3_connect($QUERYDB);
$query = $FTLdb->query('SELECT DISTINCT ip,network.name FROM network_addresses AS name LEFT JOIN network ON network.id = network_id ORDER BY ip ASC;');
$query = $FTLdb->query('SELECT DISTINCT id,hwaddr,macVendor FROM network ORDER BY firstSeen DESC;');
if (!$query) {
throw new Exception('Error while querying FTL\'s database: ' . $db->lastErrorMsg());
}
@@ -229,7 +235,47 @@ if ($_POST['action'] == 'get_groups') {
// Loop over results
$ips = array();
while ($res = $query->fetchArray(SQLITE3_ASSOC)) {
$ips[$res['ip']] = $res['name'] !== null ? $res['name'] : '';
$id = intval($res["id"]);
// Get possibly associated IP addresses and hostnames for this client
$query_ips = $FTLdb->query("SELECT ip,name FROM network_addresses WHERE network_id = $id ORDER BY lastSeen DESC;");
$addresses = [];
$names = [];
while ($res_ips = $query_ips->fetchArray(SQLITE3_ASSOC)) {
array_push($addresses, utf8_encode($res_ips["ip"]));
if($res_ips["name"] !== null)
array_push($names,utf8_encode($res_ips["name"]));
}
$query_ips->finalize();
// Prepare extra information
$extrainfo = "";
// Add list of associated host names to info string (if available)
if(count($names) === 1)
$extrainfo .= "hostname: ".$names[0];
else if(count($names) > 0)
$extrainfo .= "hostnames: ".implode(", ", $names);
// Add device vendor to info string (if available)
if (strlen($res["macVendor"]) > 0) {
if (count($names) > 0)
$extrainfo .= "; ";
$extrainfo .= "vendor: ".htmlspecialchars($res["macVendor"]);
}
// Add list of associated host names to info string (if available and if this is not a mock device)
if (stripos($res["hwaddr"], "ip-") === FALSE) {
if ((count($names) > 0 || strlen($res["macVendor"]) > 0) && count($addresses) > 0)
$extrainfo .= "; ";
if(count($addresses) === 1)
$extrainfo .= "address: ".$addresses[0];
else if(count($addresses) > 0)
$extrainfo .= "addresses: ".implode(", ", $addresses);
}
$ips[strtoupper($res['hwaddr'])] = $extrainfo;
}
$FTLdb->close();
@@ -243,6 +289,9 @@ if ($_POST['action'] == 'get_groups') {
if (isset($ips[$res['ip']])) {
unset($ips[$res['ip']]);
}
if (isset($ips["IP-".$res['ip']])) {
unset($ips["IP-".$res['ip']]);
}
}
header('Content-type: application/json');
@@ -262,6 +311,11 @@ if ($_POST['action'] == 'get_groups') {
}
foreach ($ips as $ip) {
// Silently skip this entry when it is empty or not a string (e.g. NULL)
if(!is_string($ip) || strlen($ip) == 0) {
continue;
}
if (!$stmt->bindValue(':ip', $ip, SQLITE3_TEXT)) {
throw new Exception('While binding ip: ' . $db->lastErrorMsg());
}
@@ -453,18 +507,41 @@ if ($_POST['action'] == 'get_groups') {
} catch (\Exception $ex) {
JSON_error($ex->getMessage());
}
} elseif ($_POST['action'] == 'add_domain') {
} elseif ($_POST['action'] == 'add_domain' || $_POST['action'] == 'replace_domain') {
// Add new domain
try {
$domains = explode(' ', html_entity_decode(trim($_POST['domain'])));
$before = intval($db->querySingle("SELECT COUNT(*) FROM domainlist;"));
$total = count($domains);
$added = 0;
$stmt = $db->prepare('REPLACE INTO domainlist (domain,type,comment) VALUES (:domain,:type,:comment)');
if (!$stmt) {
// Prepare INSERT INTO statement
$insert_stmt = $db->prepare('INSERT OR IGNORE INTO domainlist (domain,type) VALUES (:domain,:type)');
if (!$insert_stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
// Prepare UPDATE statement
$update_stmt = $db->prepare('UPDATE domainlist SET comment = :comment WHERE domain = :domain AND type = :type');
if (!$update_stmt) {
throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
}
$check_stmt = null;
$delete_stmt = null;
if($_POST['action'] == 'replace_domain') {
// Check statement will reveal any group associations for a given (domain,type) which do NOT belong to the default group
$check_stmt = $db->prepare('SELECT EXISTS(SELECT domain FROM domainlist_by_group dlbg JOIN domainlist dl on dlbg.domainlist_id = dl.id WHERE dl.domain = :domain AND dlbg.group_id != 0)');
if (!$check_stmt) {
throw new Exception('While preparing check statement: ' . $db->lastErrorMsg());
}
// Delete statement will remove this domain from any type of list
$delete_stmt = $db->prepare('DELETE FROM domainlist WHERE domain = :domain');
if (!$delete_stmt) {
throw new Exception('While preparing delete statement: ' . $db->lastErrorMsg());
}
}
if (isset($_POST['type'])) {
$type = intval($_POST['type']);
} else if (isset($_POST['list']) && $_POST['list'] === "white") {
@@ -473,7 +550,8 @@ if ($_POST['action'] == 'get_groups') {
$type = ListType::blacklist;
}
if (!$stmt->bindValue(':type', $type, SQLITE3_TEXT)) {
if (!$insert_stmt->bindValue(':type', $type, SQLITE3_TEXT) ||
!$update_stmt->bindValue(':type', $type, SQLITE3_TEXT)) {
throw new Exception('While binding type: ' . $db->lastErrorMsg());
}
@@ -482,11 +560,16 @@ if ($_POST['action'] == 'get_groups') {
// Store NULL in database for empty comments
$comment = null;
}
if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
if (!$update_stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
throw new Exception('While binding comment: ' . $db->lastErrorMsg());
}
foreach ($domains as $domain) {
// Silently skip this entry when it is empty or not a string (e.g. NULL)
if(!is_string($domain) || strlen($domain) == 0) {
continue;
}
$input = $domain;
// Convert domain name to IDNA ASCII form for international domains
if (extension_loaded("intl")) {
@@ -504,7 +587,7 @@ if ($_POST['action'] == 'get_groups') {
}
}
if(strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
if(isset($_POST['type']) && strlen($_POST['type']) === 2 && $_POST['type'][1] === 'W')
{
// Apply wildcard-style formatting
$domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
@@ -527,13 +610,62 @@ if ($_POST['action'] == 'get_groups') {
}
}
if (!$stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
// First try to delete any occurrences of this domain if we're in
// replace mode. Only do this when the domain to be replaced is in
// the default group! Otherwise, we would shuffle group settings and
// just throw an error at the user to tell them to change this
// domain manually. This ensures user's will really get what they
// want from us.
if($_POST['action'] == 'replace_domain') {
if (!$check_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain to check: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
$check_result = $check_stmt->execute();
if (!$check_result) {
throw new Exception('While executing check: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
// Check return value of CHECK query (0 = only default group, 1 = special group assignments)
$only_default_group = (($check_result->fetchArray(SQLITE3_NUM)[0]) == 0) ? true : false;
if(!$only_default_group) {
throw new Exception('Domain ' . $domain . ' is configured with special group settings.<br>'.
'Please modify the domain on the respective group management pages.');
}
if (!$delete_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
if (!$delete_stmt->execute()) {
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
}
if (!$insert_stmt->bindValue(':domain', $domain, SQLITE3_TEXT) ||
!$update_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
if (!$stmt->execute()) {
throw new Exception('While executing: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
// First execute INSERT OR IGNORE statement to create a record for
// this domain (ignore if already existing)
if (!$insert_stmt->execute()) {
throw new Exception('While executing INSERT OT IGNORE: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
// Then update the record with a new comment (and modification date
// due to the trigger event) We are not using REPLACE INTO to avoid
// the initial DELETE event (loosing group assignments in case an
// entry did already exist).
if (!$update_stmt->execute()) {
throw new Exception('While executing UPDATE: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
'Added ' . $added . " out of ". $total . " domains");
}
$added++;
@@ -764,6 +896,11 @@ if ($_POST['action'] == 'get_groups') {
}
foreach ($addresses as $address) {
// Silently skip this entry when it is empty or not a string (e.g. NULL)
if(!is_string($address) || strlen($address) == 0) {
continue;
}
if(preg_match("/[^a-zA-Z0-9:\/?&%=~._()-;]/", $address) !== 0) {
throw new Exception('<strong>Invalid adlist URL ' . htmlentities($address) . '</strong><br>'.
'Added ' . $added . " out of ". $total . " adlists");
@@ -906,7 +1043,10 @@ if ($_POST['action'] == 'get_groups') {
}
foreach ($domains as $domain) {
$input = $domain;
// Silently skip this entry when it is empty or not a string (e.g. NULL)
if(!is_string($domain) || strlen($domain) == 0) {
continue;
}
if (!$stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) {
throw new Exception('While binding domain: <strong>' . $db->lastErrorMsg() . '</strong><br>'.
+46 -26
View File
@@ -14,6 +14,9 @@
$hostname = gethostname() ? gethostname() : "";
check_cors();
// Create cache busting version
$cacheVer = filemtime(__FILE__);
// Generate CSRF token
if(empty($_SESSION['token'])) {
@@ -185,29 +188,31 @@
html { background-color: #000; }
</style>
<?php } ?>
<link rel="stylesheet" href="style/vendor/SourceSansPro/SourceSansPro.css">
<link rel="stylesheet" href="style/vendor/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="style/vendor/font-awesome/css/all.min.css">
<link rel="stylesheet" href="style/vendor/datatables.min.css">
<link rel="stylesheet" href="style/vendor/daterangepicker.min.css">
<link rel="stylesheet" href="style/vendor/AdminLTE.min.css">
<link rel="stylesheet" href="style/vendor/SourceSansPro/SourceSansPro.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/bootstrap/css/bootstrap.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/font-awesome/css/all.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/datatables.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/daterangepicker.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/AdminLTE.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/select2.min.css?v=<?=$cacheVer?>">
<?php if (in_array($scriptname, array("groups.php", "groups-adlists.php", "groups-clients.php", "groups-domains.php"))){ ?>
<link rel="stylesheet" href="style/vendor/animate.min.css">
<link rel="stylesheet" href="style/vendor/bootstrap-select.min.css">
<link rel="stylesheet" href="style/vendor/bootstrap-toggle.min.css">
<link rel="stylesheet" href="style/vendor/animate.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/bootstrap-select.min.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/vendor/bootstrap-toggle.min.css?v=<?=$cacheVer?>">
<?php } ?>
<link rel="stylesheet" href="style/pi-hole.css">
<link rel="stylesheet" href="style/themes/<?php echo $theme; ?>.css">
<noscript><link rel="stylesheet" href="style/vendor/js-warn.css"></noscript>
<link rel="stylesheet" href="style/pi-hole.css?v=<?=$cacheVer?>">
<link rel="stylesheet" href="style/themes/<?php echo $theme; ?>.css?v=<?=$cacheVer?>">
<noscript><link rel="stylesheet" href="style/vendor/js-warn.css?v=<?=$cacheVer?>"></noscript>
<script src="scripts/vendor/jquery.min.js"></script>
<script src="style/vendor/bootstrap/js/bootstrap.min.js"></script>
<script src="scripts/vendor/adminlte.min.js"></script>
<script src="scripts/vendor/bootstrap-notify.min.js"></script>
<script src="scripts/vendor/datatables.min.js"></script>
<script src="scripts/vendor/moment.min.js"></script>
<script src="scripts/vendor/Chart.min.js"></script>
<script src="scripts/vendor/jquery.min.js?v=<?=$cacheVer?>"></script>
<script src="style/vendor/bootstrap/js/bootstrap.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/adminlte.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/bootstrap-notify.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/select2.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/datatables.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/moment.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/vendor/Chart.min.js?v=<?=$cacheVer?>"></script>
</head>
<body class="hold-transition sidebar-mini <?php if($boxedlayout){ ?>layout-boxed<?php } ?>">
<noscript>
@@ -343,7 +348,7 @@ if($auth) {
{
echo "text-vivid-blue";
}
?>"\"></i> Temp:&nbsp;<span id="rawtemp" hidden><?php echo $celsius;?></span><span id="tempdisplay"></span><?php
?>"></i> Temp:&nbsp;<span id="rawtemp" hidden><?php echo $celsius;?></span><span id="tempdisplay"></span></span><?php
}
}
else
@@ -454,12 +459,6 @@ if($auth) {
<i class="fa fa-ban"></i> <span>Blacklist</span>
</a>
</li>
<!-- Local DNS Records -->
<li<?php if($scriptname === "dns_records.php"){ ?> class="active"<?php } ?>>
<a href="dns_records.php">
<i class="fa fa-address-book"></i> <span>Local DNS Records</span>
</a>
</li>
<!-- Group Management -->
<li class="treeview<?php if (in_array($scriptname, array("groups.php", "groups-adlists.php", "groups-clients.php", "groups-domains.php"))){ ?> active<?php } ?>">
<a href="#">
@@ -601,6 +600,27 @@ if($auth) {
<i class="fa fa-cogs"></i> <span>Settings</span>
</a>
</li>
<!-- Local DNS Records -->
<li class="treeview <?php if(in_array($scriptname, array("dns_records.php", "cname_records.php"))){ ?>active<?php } ?>">
<a href="#">
<i class="fa fa-address-book"></i> <span>Local DNS</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li<?php if($scriptname === "dns_records.php"){ ?> class="active"<?php } ?>>
<a href="dns_records.php">
<i class="fa fa-address-book"></i> <span>DNS Records</span>
</a>
</li>
<li<?php if($scriptname === "cname_records.php"){ ?> class="active"<?php } ?>>
<a href="cname_records.php">
<i class="fa fa-address-book"></i> <span>CNAME Records</span>
</a>
</li>
</ul>
</li>
<!-- Logout -->
<?php
// Show Logout button if $auth is set and authorization is required
+16 -16
View File
@@ -30,34 +30,34 @@
<input type="password" id="loginpw" name="pw" class="form-control" placeholder="Password" autofocus>
<span class="fa fa-key form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-12 col-md-12">
<button type="submit" class="btn btn-primary form-control"><i class="fas fa-sign-in-alt"></i>&nbsp;&nbsp;&nbsp;Log in</button>
</div>
</div>
<br>
<div class="row">
<div class="col-xs-8 hidden-xs hidden-sm">
<ul>
<li><kbd>Return</kbd> &rarr; Log in and go to requested page (<?php echo $scriptname; ?>)</li>
<li><kbd>Ctrl</kbd>+<kbd>Return</kbd> &rarr; Log in and go to Settings page</li>
</ul>
<ul>
<li><kbd>Return</kbd> &rarr; Log in and go to requested page (<?php echo $scriptname; ?>)</li>
<li><kbd>Ctrl</kbd>+<kbd>Return</kbd> &rarr; Log in and go to Settings page</li>
</ul>
</div>
<div class="col-xs-12 col-md-4">
<div class="pull-right">
<div>
<input type="checkbox" id="logincookie" name="persistentlogin">
<label for="logincookie">Remember me for 7 days</label>
</div>
<div>
<input type="checkbox" id="logincookie" name="persistentlogin">
<label for="logincookie">Remember me for 7 days</label>
</div>
<button type="submit" class="btn btn-primary pull-right"><i class="fas fa-sign-in-alt"></i>&nbsp;&nbsp;&nbsp;Log in</button>
</div>
</div>
<br>
<div class="row">
<div class="col-xs-12">
<div class="box box-<?php if (!$wrongpassword) { ?>info<?php } else { ?>danger<?php }
if (!$wrongpassword) { ?> collapsed-box<?php } ?> box-solid">
<div class="box box-<?php if (!$wrongpassword) { ?>info collapsed-box<?php } else { ?>danger<?php }?>">
<div class="box-header with-border">
<h3 class="box-title">Forgot password</h3>
<h3 class="box-title">Forgot password?</h3>
<div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i
class="fa <?php if ($wrongpassword) { ?>fa-minus<?php } else { ?>fa-plus<?php } ?>"></i>
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa <?php if ($wrongpassword) { ?>fa-minus<?php } else { ?>fa-plus<?php } ?>"></i>
</button>
</div>
</div>
+5 -1
View File
@@ -738,7 +738,11 @@ function addStaticDHCPLease($mac, $ip, $hostname) {
// Flush network table
case "flusharp":
$output = pihole_execute("arpflush quiet");
$error = implode("<br>", $output);
$error = "";
if(is_array($output))
{
$error = implode("<br>", $output);
}
if(strlen($error) == 0)
{
$success .= "The network table has been flushed";
+28
View File
@@ -523,6 +523,34 @@ if(isset($_POST["action"]))
$importedsomething = true;
}
}
if(isset($_POST["localcnamerecords"]) && $file->getFilename() === "05-pihole-custom-cname.conf")
{
if($flushtables) {
// Defined in func.php included via auth.php
deleteAllCustomCNAMEEntries();
}
$num = 0;
$localcnamerecords = process_file(file_get_contents($file));
foreach($localcnamerecords as $record) {
$line = str_replace("cname=","", $record);
$line = str_replace("\r","", $line);
$line = str_replace("\n","", $line);
$explodedLine = explode (",", $line);
$domain = implode(",", array_slice($explodedLine, 0, -1));
$target = $explodedLine[count($explodedLine)-1];
if(addCustomCNAMEEntry($domain, $target, false))
$num++;
}
echo "Processed local CNAME records (".$num." entries)<br>\n";
if($num > 0) {
$importedsomething = true;
}
}
}
if($importedsomething)
+2
View File
File diff suppressed because one or more lines are too long
+19 -22
View File
@@ -14,7 +14,7 @@ $piholeFTLConf = piholeFTLConfig();
// Handling of PHP internal errors
$last_error = error_get_last();
if($last_error["type"] === E_WARNING || $last_error["type"] === E_ERROR)
if(isset($last_error) && ($last_error["type"] === E_WARNING || $last_error["type"] === E_ERROR))
{
$error .= "There was a problem applying your settings.<br>Debugging information:<br>PHP error (".htmlspecialchars($last_error["type"])."): ".htmlspecialchars($last_error["message"])." in ".htmlspecialchars($last_error["file"]).":".htmlspecialchars($last_error["line"]);
}
@@ -318,7 +318,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
</tr>
<tr>
<th scope="row">Time FTL started:</th>
<td><?php print_r(get_FTL_data("start")); ?></td>
<td><?php print_r(get_FTL_data("lstart")); ?></td>
</tr>
<tr>
<th scope="row">User / Group:</th>
@@ -397,7 +397,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
<br/>
<div class="row">
<div class="col-md-4">
<button type="button" class="btn btn-danger confirm-flushlogs btn-block">Flush logs</button>
<button type="button" class="btn btn-danger confirm-flushlogs btn-block">Flush logs (last 24 hours)</button>
</div>
<p class="hidden-md hidden-lg"></p>
<div class="col-md-4">
@@ -618,8 +618,8 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
</div>
<div class="row">
<div class="col-md-12">
<div><input type="checkbox" name="useIPv6" id="useIPv6" class="DHCPgroup" <?php if ($DHCPIPv6){ ?>checked<?php }; if (!$DHCP){ ?> disabled<?php } ?>>&nbsp;<label for="useIPv6"><strong>Enable IPv6 support (SLAAC + RA)</strong></label></div>
<div><input type="checkbox" name="DHCP_rapid_commit" id="DHCP_rapid_commit" class="DHCPgroup" <?php if ($DHCP_rapid_commit){ ?>checked<?php }; if (!$DHCP){ ?> disabled<?php } ?>>&nbsp;<label for="DHCP_rapid_commit"><strong>Enable DHCP rapid commit (fast address assignment)</strong></label></div>
<div><input type="checkbox" name="DHCP_rapid_commit" id="DHCP_rapid_commit" class="DHCPgroup" <?php if ($DHCP_rapid_commit){ ?>checked<?php }; if (!$DHCP){ ?> disabled<?php } ?>>&nbsp;<label for="DHCP_rapid_commit"><strong>Enable DHCPv4 rapid commit (fast address assignment)</strong></label></div>
<div><input type="checkbox" name="useIPv6" id="useIPv6" class="DHCPgroup" <?php if ($DHCPIPv6){ ?>checked<?php }; if (!$DHCP){ ?> disabled<?php } ?>>&nbsp;<label for="useIPv6"><strong>Enable IPv6 support (SLAAC + RA)</strong></label></div>
</div>
</div>
</div>
@@ -674,7 +674,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
$type = 4;
}
$host = $line[3];
$host = htmlentities($line[3]);
if ($host == "*") {
$host = "<i>unknown</i>";
}
@@ -715,8 +715,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
title="Lease type: IPv<?php echo $lease["type"]; ?><br/>Remaining lease time: <?php echo $lease["TIME"]; ?><br/>DHCP UID: <?php echo $lease["clid"]; ?>">
<td id="MAC"><?php echo $lease["hwaddr"]; ?></td>
<td id="IP" data-order="<?php echo bin2hex(inet_pton($lease["IP"])); ?>"><?php echo $lease["IP"]; ?></td>
<td id="HOST"><?php echo htmlentities($lease["host"]); ?></td>
<td id="HOST"><?php echo $lease["host"]; ?></td>
<td>
<button type="button" class="btn btn-danger btn-xs" id="removedynamic">
<span class="fas fas fa-trash-alt"></span>
</button>
<button type="button" id="button" class="btn btn-warning btn-xs" data-static="alert">
<span class="fas fas fa-file-import"></span>
</button>
@@ -970,21 +973,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
queries, Pi-hole requests the DNSSEC records needed to validate
the replies. If a domain fails validation or the upstream does not
support DNSSEC, this setting can cause issues resolving domains.
Use Google, Cloudflare, DNS.WATCH, Quad9, or another DNS
server which supports DNSSEC when activating DNSSEC. Note that
Use an upstream DNS server which supports DNSSEC 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="https://dnssec.vs.uni-due.de/" rel="noopener" target="_blank">here</a>.</p>
</div>
<p>Validate DNS replies and cache DNSSEC data. When forwarding DNS
queries, Pi-hole requests the DNSSEC records needed to validate
the replies. If a domain fails validation or the upstream does not
support DNSSEC, this setting can cause issues resolving domains.
Use Google, Cloudflare, DNS.WATCH, Quad9, or another DNS
server which supports DNSSEC 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="https://dnssec.vs.uni-due.de/" rel="noopener" target="_blank">here</a>.</p>
<br>
<h4>Conditional forwarding</h4>
<p>If not configured as your DHCP server, Pi-hole typically won't be able to
@@ -994,7 +987,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
requests to your DHCP server (most likely your router), but only for devices on your
home network. To configure this we will need to know the IP
address of your DHCP server and which addresses belong to your local network.
Exemplary inout is given below as placeholder in the text boxes (if empty).</p>
Exemplary input is given below as placeholder in the text boxes (if empty).</p>
<p>If your local network spans 192.168.0.1 - 192.168.0.255, then you will have to input
<code>192.168.0.0/24</code>. If your local network is 192.168.47.1 - 192.168.47.255, it will
be <code>192.168.47.0/24</code> and similar. If your network is larger, the CIDR has to be
@@ -1383,6 +1376,10 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
<input type="checkbox" name="localdnsrecords" id="tele_localdnsrecords" value="true" checked>
<label for="tele_localdnsrecords">Local DNS Records</label>
</div>
<div>
<input type="checkbox" name="localcnamerecords" id="tele_localcnamerecords" value="true" checked>
<label for="tele_localcnamerecords">Local CNAME Records</label>
</div>
</div>
</div>
<div class="row">
@@ -1426,9 +1423,9 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
</div>
</div>
<script src="scripts/vendor/jquery.confirm.min.js"></script>
<script src="scripts/pi-hole/js/utils.js"></script>
<script src="scripts/pi-hole/js/settings.js"></script>
<script src="scripts/vendor/jquery.confirm.min.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/utils.js?v=<?=$cacheVer?>"></script>
<script src="scripts/pi-hole/js/settings.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+4
View File
@@ -300,3 +300,7 @@
width: 100%;
vertical-align: middle;
}
.select2-container--default .select2-results > .select2-results__options {
max-height: 400px;
}
+1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -24,7 +24,7 @@
<label for="chk2">Automatic scrolling on update</label>
</div>
<script src="scripts/pi-hole/js/taillog-FTL.js"></script>
<script src="scripts/pi-hole/js/taillog-FTL.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";
+1 -1
View File
@@ -24,7 +24,7 @@
<label for="chk2">Automatic scrolling on update</label>
</div>
<script src="scripts/pi-hole/js/taillog.js"></script>
<script src="scripts/pi-hole/js/taillog.js?v=<?=$cacheVer?>"></script>
<?php
require "scripts/pi-hole/php/footer.php";