diff --git a/api_FTL.php b/api_FTL.php index 85ee3f8a..85227dd7 100644 --- a/api_FTL.php +++ b/api_FTL.php @@ -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(); } ?> diff --git a/api_db.php b/api_db.php index 6203d5d1..49270671 100644 --- a/api_db.php +++ b/api_db.php @@ -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); diff --git a/auditlog.php b/auditlog.php index 64bf3efb..895dc581 100644 --- a/auditlog.php +++ b/auditlog.php @@ -72,8 +72,8 @@ - - + + + + + + + +
+
+
+ +
+

+ Add a new CNAME record +

+
+ +
+
+
+ + +
+
+ + +
+
+
+ +
+
+
+ + + + + + +
+
+
+
+

+ List of local CNAME records +

+
+ +
+ + + + + + + + +
DomainTargetAction
+ +
+ +
+ +
+
+ + + + + diff --git a/db_graph.php b/db_graph.php index 1c18a834..236bde24 100644 --- a/db_graph.php +++ b/db_graph.php @@ -67,9 +67,9 @@ - - - + + + - - - + + +

+

@@ -122,6 +123,40 @@
+ + +
@@ -159,9 +194,10 @@
- - - + + + + Generate debug log - +
- - - + + + Update - + - - - - + + + + Known clients:
- +
+
+
+

You can select an existing client or add a custom one by typing into the field above and confirming your entry with .

+

Clients may be described either by their IP addresses (IPv4 and IPv6 are supported), + IP subnets (CIDR notation, like 192.168.2.0/24), + their MAC addresses (like 12:34:56:78:9A:BC), + by their hostnames (like localhost), or by the interface they are connected to (prefaced with a colon, like :eth0).

+

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.

+
+
- - - - - + + + + + - - - - + + + + - - - - + + + + - - + + - - + + - - - + + + 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) + @@ -140,8 +165,9 @@ if(strlen($showing) > 0)

Filtering options:


@@ -150,9 +176,9 @@ if(strlen($showing) > 0) - - - + + +
-
-
- + +
+
+ +
+ + +
+
+
+ +
+
+ - - + +
@@ -32,7 +43,7 @@ - + " + + '' + + "" + ); + } + } + ], + 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 + } + }); +} diff --git a/scripts/pi-hole/js/customdns.js b/scripts/pi-hole/js/customdns.js index d2f4ad56..0c9f2f74 100644 --- a/scripts/pi-hole/js/customdns.js +++ b/scripts/pi-hole/js/customdns.js @@ -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); } diff --git a/scripts/pi-hole/js/db_graph.js b/scripts/pi-hole/js/db_graph.js index 73a9e6bd..402c680c 100644 --- a/scripts/pi-hole/js/db_graph.js +++ b/scripts/pi-hole/js/db_graph.js @@ -20,6 +20,7 @@ $(function () { { timePicker: true, timePickerIncrement: 15, + timePicker24Hour: true, locale: { format: dateformat }, startDate: start__, endDate: end__, diff --git a/scripts/pi-hole/js/db_lists.js b/scripts/pi-hole/js/db_lists.js index ea25d952..ec535874 100644 --- a/scripts/pi-hole/js/db_lists.js +++ b/scripts/pi-hole/js/db_lists.js @@ -22,6 +22,7 @@ $(function () { { timePicker: true, timePickerIncrement: 15, + timePicker24Hour: true, locale: { format: dateformat }, startDate: start__, endDate: end__, diff --git a/scripts/pi-hole/js/db_queries.js b/scripts/pi-hole/js/db_queries.js index 7707dee3..ec38f751 100644 --- a/scripts/pi-hole/js/db_queries.js +++ b/scripts/pi-hole/js/db_queries.js @@ -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 (forwarded)"; + fieldtext = + "OK (forwarded to " + + (data.length > 5 && data[5] !== "N/A" ? data[5] : "") + + ")"; buttontext = ''; break; @@ -320,6 +262,16 @@ $(function () { buttontext = ''; break; + case 12: + color = "green"; + fieldtext = "Retried"; + buttontext = ""; + break; + case 13: + color = "green"; + fieldtext = "Retried (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"); } }); diff --git a/scripts/pi-hole/js/footer.js b/scripts/pi-hole/js/footer.js index f8541810..32dbc84e 100644 --- a/scripts/pi-hole/js/footer.js +++ b/scripts/pi-hole/js/footer.js @@ -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) + " °K"); + displaytemp.html(temperature.toFixed(1) + " K"); break; case "F": diff --git a/scripts/pi-hole/js/groups-adlists.js b/scripts/pi-hole/js/groups-adlists.js index 606820b3..1f206874 100644 --- a/scripts/pi-hole/js/groups-adlists.js +++ b/scripts/pi-hole/js/groups-adlists.js @@ -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(''); 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; } diff --git a/scripts/pi-hole/js/groups-clients.js b/scripts/pi-hole/js/groups-clients.js index 62b26d65..31139d3f 100644 --- a/scripts/pi-hole/js/groups-clients.js +++ b/scripts/pi-hole/js/groups-clients.js @@ -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 + //
@@ -99,6 +99,6 @@
- + diff --git a/scripts/pi-hole/php/func.php b/scripts/pi-hole/php/func.php index efb7c3b8..105b4853 100644 --- a/scripts/pi-hole/php/func.php +++ b/scripts/pi-hole/php/func.php @@ -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."
"; - 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."
"; + return true; + } +} + +function returnError($message = "", $json = true) +{ + if ($json) { + return [ "success" => false, "message" => $message ]; + } else { + echo $msg."
"; + return false; + } } ?> diff --git a/scripts/pi-hole/php/groups.php b/scripts/pi-hole/php/groups.php index 04871d17..13e19765 100644 --- a/scripts/pi-hole/php/groups.php +++ b/scripts/pi-hole/php/groups.php @@ -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: ' . $db->lastErrorMsg() . '
'. '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: ' . $db->lastErrorMsg() . '
'. + 'Added ' . $added . " out of ". $total . " domains"); + } + + $check_result = $check_stmt->execute(); + if (!$check_result) { + throw new Exception('While executing check: ' . $db->lastErrorMsg() . '
'. + '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.
'. + 'Please modify the domain on the respective group management pages.'); + } + + if (!$delete_stmt->bindValue(':domain', $domain, SQLITE3_TEXT)) { + throw new Exception('While binding domain: ' . $db->lastErrorMsg() . '
'. + 'Added ' . $added . " out of ". $total . " domains"); + } + + if (!$delete_stmt->execute()) { + throw new Exception('While executing: ' . $db->lastErrorMsg() . '
'. + '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: ' . $db->lastErrorMsg() . '
'. 'Added ' . $added . " out of ". $total . " domains"); } - if (!$stmt->execute()) { - throw new Exception('While executing: ' . $db->lastErrorMsg() . '
'. + // 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: ' . $db->lastErrorMsg() . '
'. + '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: ' . $db->lastErrorMsg() . '
'. '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('Invalid adlist URL ' . htmlentities($address) . '
'. '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: ' . $db->lastErrorMsg() . '
'. diff --git a/scripts/pi-hole/php/header.php b/scripts/pi-hole/php/header.php index e0b1d587..feee7620 100644 --- a/scripts/pi-hole/php/header.php +++ b/scripts/pi-hole/php/header.php @@ -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; } - - - - - - + + + + + + + - - - + + + - - - + + + - - - - - - - + + + + + + + +
+
+
+ +
+
+
-
-
- - -
+
+ +
-

-
+
-

Forgot password

- +

Forgot password?

-
diff --git a/scripts/pi-hole/php/savesettings.php b/scripts/pi-hole/php/savesettings.php index af95c901..276d3940 100644 --- a/scripts/pi-hole/php/savesettings.php +++ b/scripts/pi-hole/php/savesettings.php @@ -738,7 +738,11 @@ function addStaticDHCPLease($mac, $ip, $hostname) { // Flush network table case "flusharp": $output = pihole_execute("arpflush quiet"); - $error = implode("
", $output); + $error = ""; + if(is_array($output)) + { + $error = implode("
", $output); + } if(strlen($error) == 0) { $success .= "The network table has been flushed"; diff --git a/scripts/pi-hole/php/teleporter.php b/scripts/pi-hole/php/teleporter.php index 8948c19f..5f105109 100644 --- a/scripts/pi-hole/php/teleporter.php +++ b/scripts/pi-hole/php/teleporter.php @@ -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)
\n"; + if($num > 0) { + $importedsomething = true; + } + } } if($importedsomething) diff --git a/scripts/vendor/select2.min.js b/scripts/vendor/select2.min.js new file mode 100644 index 00000000..091d4b50 --- /dev/null +++ b/scripts/vendor/select2.min.js @@ -0,0 +1,2 @@ +/*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ +!function(n){"function"==typeof define&&define.amd?define(["jquery"],n):"object"==typeof module&&module.exports?module.exports=function(e,t){return void 0===t&&(t="undefined"!=typeof window?require("jquery"):require("jquery")(e)),n(t),t}:n(jQuery)}(function(u){var e=function(){if(u&&u.fn&&u.fn.select2&&u.fn.select2.amd)var e=u.fn.select2.amd;var t,n,r,h,o,s,f,g,m,v,y,_,i,a,b;function w(e,t){return i.call(e,t)}function l(e,t){var n,r,i,o,s,a,l,c,u,d,p,h=t&&t.split("/"),f=y.map,g=f&&f["*"]||{};if(e){for(s=(e=e.split("/")).length-1,y.nodeIdCompat&&b.test(e[s])&&(e[s]=e[s].replace(b,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),u=0;u":">",'"':""","'":"'","/":"/"};return"string"!=typeof e?e:String(e).replace(/[&<>"'\/\\]/g,function(e){return t[e]})},i.appendMany=function(e,t){if("1.7"===o.fn.jquery.substr(0,3)){var n=o();o.map(t,function(e){n=n.add(e)}),t=n}e.append(t)},i.__cache={};var n=0;return i.GetUniqueElementId=function(e){var t=e.getAttribute("data-select2-id");return null==t&&(e.id?(t=e.id,e.setAttribute("data-select2-id",t)):(e.setAttribute("data-select2-id",++n),t=n.toString())),t},i.StoreData=function(e,t,n){var r=i.GetUniqueElementId(e);i.__cache[r]||(i.__cache[r]={}),i.__cache[r][t]=n},i.GetData=function(e,t){var n=i.GetUniqueElementId(e);return t?i.__cache[n]&&null!=i.__cache[n][t]?i.__cache[n][t]:o(e).data(t):i.__cache[n]},i.RemoveData=function(e){var t=i.GetUniqueElementId(e);null!=i.__cache[t]&&delete i.__cache[t],e.removeAttribute("data-select2-id")},i}),e.define("select2/results",["jquery","./utils"],function(h,f){function r(e,t,n){this.$element=e,this.data=n,this.options=t,r.__super__.constructor.call(this)}return f.Extend(r,f.Observable),r.prototype.render=function(){var e=h('
    ');return this.options.get("multiple")&&e.attr("aria-multiselectable","true"),this.$results=e},r.prototype.clear=function(){this.$results.empty()},r.prototype.displayMessage=function(e){var t=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var n=h(''),r=this.options.get("translations").get(e.message);n.append(t(r(e.args))),n[0].className+=" select2-results__message",this.$results.append(n)},r.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},r.prototype.append=function(e){this.hideLoading();var t=[];if(null!=e.results&&0!==e.results.length){e.results=this.sort(e.results);for(var n=0;n",{class:"select2-results__options select2-results__options--nested"});p.append(l),s.append(a),s.append(p)}else this.template(e,t);return f.StoreData(t,"data",e),t},r.prototype.bind=function(t,e){var l=this,n=t.id+"-results";this.$results.attr("id",n),t.on("results:all",function(e){l.clear(),l.append(e.data),t.isOpen()&&(l.setClasses(),l.highlightFirstItem())}),t.on("results:append",function(e){l.append(e.data),t.isOpen()&&l.setClasses()}),t.on("query",function(e){l.hideMessages(),l.showLoading(e)}),t.on("select",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("unselect",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("open",function(){l.$results.attr("aria-expanded","true"),l.$results.attr("aria-hidden","false"),l.setClasses(),l.ensureHighlightVisible()}),t.on("close",function(){l.$results.attr("aria-expanded","false"),l.$results.attr("aria-hidden","true"),l.$results.removeAttr("aria-activedescendant")}),t.on("results:toggle",function(){var e=l.getHighlightedResults();0!==e.length&&e.trigger("mouseup")}),t.on("results:select",function(){var e=l.getHighlightedResults();if(0!==e.length){var t=f.GetData(e[0],"data");"true"==e.attr("aria-selected")?l.trigger("close",{}):l.trigger("select",{data:t})}}),t.on("results:previous",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e);if(!(n<=0)){var r=n-1;0===e.length&&(r=0);var i=t.eq(r);i.trigger("mouseenter");var o=l.$results.offset().top,s=i.offset().top,a=l.$results.scrollTop()+(s-o);0===r?l.$results.scrollTop(0):s-o<0&&l.$results.scrollTop(a)}}),t.on("results:next",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e)+1;if(!(n>=t.length)){var r=t.eq(n);r.trigger("mouseenter");var i=l.$results.offset().top+l.$results.outerHeight(!1),o=r.offset().top+r.outerHeight(!1),s=l.$results.scrollTop()+o-i;0===n?l.$results.scrollTop(0):ithis.$results.outerHeight()||o<0)&&this.$results.scrollTop(i)}},r.prototype.template=function(e,t){var n=this.options.get("templateResult"),r=this.options.get("escapeMarkup"),i=n(e,t);null==i?t.style.display="none":"string"==typeof i?t.innerHTML=r(i):h(t).append(i)},r}),e.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),e.define("select2/selection/base",["jquery","../utils","../keys"],function(n,r,i){function o(e,t){this.$element=e,this.options=t,o.__super__.constructor.call(this)}return r.Extend(o,r.Observable),o.prototype.render=function(){var e=n('');return this._tabindex=0,null!=r.GetData(this.$element[0],"old-tabindex")?this._tabindex=r.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),e.attr("title",this.$element.attr("title")),e.attr("tabindex",this._tabindex),e.attr("aria-disabled","false"),this.$selection=e},o.prototype.bind=function(e,t){var n=this,r=e.id+"-results";this.container=e,this.$selection.on("focus",function(e){n.trigger("focus",e)}),this.$selection.on("blur",function(e){n._handleBlur(e)}),this.$selection.on("keydown",function(e){n.trigger("keypress",e),e.which===i.SPACE&&e.preventDefault()}),e.on("results:focus",function(e){n.$selection.attr("aria-activedescendant",e.data._resultId)}),e.on("selection:update",function(e){n.update(e.data)}),e.on("open",function(){n.$selection.attr("aria-expanded","true"),n.$selection.attr("aria-owns",r),n._attachCloseHandler(e)}),e.on("close",function(){n.$selection.attr("aria-expanded","false"),n.$selection.removeAttr("aria-activedescendant"),n.$selection.removeAttr("aria-owns"),n.$selection.trigger("focus"),n._detachCloseHandler(e)}),e.on("enable",function(){n.$selection.attr("tabindex",n._tabindex),n.$selection.attr("aria-disabled","false")}),e.on("disable",function(){n.$selection.attr("tabindex","-1"),n.$selection.attr("aria-disabled","true")})},o.prototype._handleBlur=function(e){var t=this;window.setTimeout(function(){document.activeElement==t.$selection[0]||n.contains(t.$selection[0],document.activeElement)||t.trigger("blur",e)},1)},o.prototype._attachCloseHandler=function(e){n(document.body).on("mousedown.select2."+e.id,function(e){var t=n(e.target).closest(".select2");n(".select2.select2-container--open").each(function(){this!=t[0]&&r.GetData(this,"element").select2("close")})})},o.prototype._detachCloseHandler=function(e){n(document.body).off("mousedown.select2."+e.id)},o.prototype.position=function(e,t){t.find(".selection").append(e)},o.prototype.destroy=function(){this._detachCloseHandler(this.container)},o.prototype.update=function(e){throw new Error("The `update` method must be defined in child classes.")},o.prototype.isEnabled=function(){return!this.isDisabled()},o.prototype.isDisabled=function(){return this.options.get("disabled")},o}),e.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(e,t,n,r){function i(){i.__super__.constructor.apply(this,arguments)}return n.Extend(i,t),i.prototype.render=function(){var e=i.__super__.render.call(this);return e.addClass("select2-selection--single"),e.html(''),e},i.prototype.bind=function(t,e){var n=this;i.__super__.bind.apply(this,arguments);var r=t.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",r).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",r),this.$selection.on("mousedown",function(e){1===e.which&&n.trigger("toggle",{originalEvent:e})}),this.$selection.on("focus",function(e){}),this.$selection.on("blur",function(e){}),t.on("focus",function(e){t.isOpen()||n.$selection.trigger("focus")})},i.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},i.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},i.prototype.selectionContainer=function(){return e("")},i.prototype.update=function(e){if(0!==e.length){var t=e[0],n=this.$selection.find(".select2-selection__rendered"),r=this.display(t,n);n.empty().append(r);var i=t.title||t.text;i?n.attr("title",i):n.removeAttr("title")}else this.clear()},i}),e.define("select2/selection/multiple",["jquery","./base","../utils"],function(i,e,l){function n(e,t){n.__super__.constructor.apply(this,arguments)}return l.Extend(n,e),n.prototype.render=function(){var e=n.__super__.render.call(this);return e.addClass("select2-selection--multiple"),e.html('
      '),e},n.prototype.bind=function(e,t){var r=this;n.__super__.bind.apply(this,arguments),this.$selection.on("click",function(e){r.trigger("toggle",{originalEvent:e})}),this.$selection.on("click",".select2-selection__choice__remove",function(e){if(!r.isDisabled()){var t=i(this).parent(),n=l.GetData(t[0],"data");r.trigger("unselect",{originalEvent:e,data:n})}})},n.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},n.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},n.prototype.selectionContainer=function(){return i('
    • ×
    • ')},n.prototype.update=function(e){if(this.clear(),0!==e.length){for(var t=[],n=0;n×');a.StoreData(r[0],"data",t),this.$selection.find(".select2-selection__rendered").prepend(r)}},e}),e.define("select2/selection/search",["jquery","../utils","../keys"],function(r,a,l){function e(e,t,n){e.call(this,t,n)}return e.prototype.render=function(e){var t=r('');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),t.on("open",function(){r.$search.attr("aria-controls",i),r.$search.trigger("focus")}),t.on("close",function(){r.$search.val(""),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.trigger("focus")}),t.on("enable",function(){r.$search.prop("disabled",!1),r._transferTabIndex()}),t.on("disable",function(){r.$search.prop("disabled",!0)}),t.on("focus",function(e){r.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){r.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){r._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented(),e.which===l.BACKSPACE&&""===r.$search.val()){var t=r.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("select",function(){r._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var r=this;this._checkIfMaximumSelected(function(){e.call(r,t,n)})},e.prototype._checkIfMaximumSelected=function(e,n){var r=this;this.current(function(e){var t=null!=e?e.length:0;0=r.maximumSelectionLength?r.trigger("results:message",{message:"maximumSelected",args:{maximum:r.maximumSelectionLength}}):n&&n()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(o,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){r.handleSearch(e)}),t.on("open",function(){r.$search.attr("tabindex",0),r.$search.attr("aria-controls",i),r.$search.trigger("focus"),window.setTimeout(function(){r.$search.trigger("focus")},0)}),t.on("close",function(){r.$search.attr("tabindex",-1),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.val(""),r.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||r.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(r.showSearch(e)?r.$searchContainer.removeClass("select2-search--hide"):r.$searchContainer.addClass("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,r){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,r)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),r=t.length-1;0<=r;r--){var i=t[r];this.placeholder.id===i.id&&n.splice(r,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,r){this.lastParams={},e.call(this,t,n,r),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("query",function(e){r.lastParams=e,r.loading=!0}),t.on("query:append",function(e){r.lastParams=e,r.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
    • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=f(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("open",function(){r._showDropdown(),r._attachPositioningHandler(t),r._bindContainerResultHandlers(t)}),t.on("close",function(){r._hideDropdown(),r._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){if(!this._containerResultsHandlersBound){var n=this;t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0}},e.prototype._attachPositioningHandler=function(e,t){var n=this,r="scroll.select2."+t.id,i="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(r,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(r+" "+i+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,r="resize.select2."+t.id,i="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+r+" "+i)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),r=null,i=this.$container.offset();i.bottom=i.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=i.top,o.bottom=i.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ai.bottom+s,d={left:i.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h={top:0,left:0};(f.contains(document.body,p[0])||p[0].isConnected)&&(h=p.offset()),d.top-=h.top,d.left-=h.left,t||n||(r="below"),u||!c||t?!c&&u&&t&&(r="below"):r="above",("above"==r||t&&"below"!==r)&&(d.top=o.top-h.top-s),null!=r&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+r),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+r)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,r){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,r)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,r=0;r');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("jquery-mousewheel",["jquery"],function(e){return e}),e.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(i,e,o,t,s){if(null==i.fn.select2){var a=["open","close","destroy"];i.fn.select2=function(t){if("object"==typeof(t=t||{}))return this.each(function(){var e=i.extend(!0,{},t);new o(i(this),e)}),this;if("string"!=typeof t)throw new Error("Invalid arguments for Select2: "+t);var n,r=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=s.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+t+"') method was called on an element that is not using Select2."),n=e[t].apply(e,r)}),-1 Time FTL started: - + User / Group: @@ -397,7 +397,7 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
      - +
      @@ -618,8 +618,8 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
      -
      checked disabled
      -
      checked disabled
      +
      checked disabled
      +
      checked disabled
      @@ -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 = "unknown"; } @@ -715,8 +715,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", " title="Lease type: IPv
      Remaining lease time:
      DHCP UID: "> "> - + + @@ -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 here.

      -

      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 - here.


      Conditional forwarding

      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).

      + Exemplary input is given below as placeholder in the text boxes (if empty).

      If your local network spans 192.168.0.1 - 192.168.0.255, then you will have to input 192.168.0.0/24. If your local network is 192.168.47.1 - 192.168.47.255, it will be 192.168.47.0/24 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", "

      +
      + + +
      @@ -1426,9 +1423,9 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
      - - - + + + .select2-results__options { + max-height: 400px; +} diff --git a/style/vendor/select2.min.css b/style/vendor/select2.min.css new file mode 100644 index 00000000..7c18ad59 --- /dev/null +++ b/style/vendor/select2.min.css @@ -0,0 +1 @@ +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/taillog-FTL.php b/taillog-FTL.php index 78189f6a..1e51660f 100644 --- a/taillog-FTL.php +++ b/taillog-FTL.php @@ -24,7 +24,7 @@
      - + Automatic scrolling on update - +