Merge branch 'devel' of https://github.com/pi-hole/AdminLTE into devel

Signed-off-by: Tony Jeffree <tjeffree@gmail.com>
This commit is contained in:
Tony Jeffree
2020-09-14 11:11:27 +01:00
14 changed files with 209 additions and 65 deletions
+1 -1
View File
@@ -170,7 +170,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":
+42 -27
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();
@@ -221,6 +233,7 @@ function initTable() {
return data;
}
});
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
if (input !== null) {
@@ -238,6 +251,7 @@ function initTable() {
$("#resetButton").addClass("hidden");
}
});
$("#resetButton").on("click", function () {
table.order([[0, "asc"]]).draw();
$("#resetButton").addClass("hidden");
@@ -245,33 +259,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;
}
+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;
+37 -2
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;
@@ -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>");
}
+13 -1
View File
@@ -167,6 +167,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;
@@ -234,6 +244,8 @@ window.utils = (function () {
setBsSelectDefaults: setBsSelectDefaults,
stateSaveCallback: stateSaveCallback,
stateLoadCallback: stateLoadCallback,
getGraphType: getGraphType
getGraphType: getGraphType,
validateMAC: validateMAC,
validateHostname: validateHostname
};
})();
+47 -3
View File
@@ -184,7 +184,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 +206,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 +222,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 +230,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 +284,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');
+2
View File
@@ -194,6 +194,7 @@
<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?v=<?=$cacheVer?>">
@@ -208,6 +209,7 @@
<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>
+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";
+2
View File
File diff suppressed because one or more lines are too long