diff --git a/README.md b/README.md index 12769874..6e7e1668 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ A read-only API can be accessed at `/admin/api.php`. With either no parameters o } ``` -There are many more parameters, such as `summaryRaw`, `overTimeData10mins`, ` topClients` or `getQuerySources`, `getQueryTypes`, `getForwardDestinations`, and `getAllQueries`. -Together with a token it is also possible to enable and disable (also with a set timeout) blocking via the API. +There are many more parameters, such as `summaryRaw`, `overTimeData10mins`, `topItems`, ` topClients` or `getQuerySources`, `getQueryTypes`, `getForwardDestinations`, and finally `getAllQueries`. +Together with a token it is also possible to enable and disable (also with a set timeout) blocking via the API

diff --git a/api.php b/api.php index a9aaa3b2..b8e0024c 100644 --- a/api.php +++ b/api.php @@ -5,120 +5,238 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. --> $domains_over_time, + 'ads_over_time' => $ads_over_time); + $data = array_merge($data, $result); + } - if (isset($_GET['topItems']) && $auth) { - $data = array_merge($data, getTopItems($_GET['topItems'])); - } + if (isset($_GET['topItems']) && $auth) + { + if(is_numeric($_GET['topItems'])) + { + sendRequestFTL("top-domains ".$_GET['topItems']); + } + else + { + sendRequestFTL("top-domains"); + } - if (isset($_GET['recentItems']) && $auth) { - if (is_numeric($_GET['recentItems'])) { - $data = array_merge($data, getRecentItems($_GET['recentItems'])); - } - } + $return = getResponseFTL(); + $top_queries = array(); + foreach($return as $line) + { + $tmp = explode(" ",$line); + $top_queries[$tmp[2]] = $tmp[1]; + } - if (isset($_GET['getQueryTypes']) && $auth) { - $data = array_merge($data, getIpvType()); - } + if(is_numeric($_GET['topItems'])) + { + sendRequestFTL("top-ads ".$_GET['topItems']); + } + else + { + sendRequestFTL("top-ads"); + } - if (isset($_GET['getForwardDestinations']) && $auth) { - $data = array_merge($data, getForwardDestinations()); - } + $return = getResponseFTL(); + $top_ads = array(); + foreach($return as $line) + { + $tmp = explode(" ",$line); + $top_ads[$tmp[2]] = $tmp[1]; + } - if (isset($_GET['getQuerySources']) && $auth) { - $data = array_merge($data, getQuerySources()); - } + $result = array('top_queries' => $top_queries, + 'top_ads' => $top_ads); - if (isset($_GET['getAllQueries']) && $auth) { - $data = array_merge($data, getAllQueries($_GET['getAllQueries'])); - } + $data = array_merge($data, $result); + } - if (isset($_GET['enable'], $_GET['token']) && $auth) { - check_csrf($_GET['token']); - exec('sudo pihole enable'); - $data = array_merge($data, Array( - "status" => "enabled" - )); - } - elseif (isset($_GET['disable'], $_GET['token']) && $auth) { - check_csrf($_GET['token']); - $disable = intval($_GET['disable']); - // intval returns the integer value on success, or 0 on failure - if($disable > 0) - { - exec("sudo pihole disable ".$disable."s"); - } - else - { - exec('sudo pihole disable'); - } - $data = array_merge($data, Array( - "status" => "disabled" - )); - } + if ((isset($_GET['topClients']) || isset($_GET['getQuerySources'])) && $auth) + { + sendRequestFTL("top-clients"); + $return = getResponseFTL(); + $top_clients = array(); + foreach($return as $line) + { + $tmp = explode(" ",$line); + if(count($tmp) == 4) + { + $top_clients[$tmp[3]."|".$tmp[2]] = $tmp[1]; + } + else + { + $top_clients[$tmp[2]] = $tmp[1]; + } + } - if (isset($_GET['getGravityDomains'])) { - $data = array_merge($data, getGravity()); - } + $result = array('top_sources' => $top_clients); + $data = array_merge($data, $result); + } - if (isset($_GET['tailLog']) && $auth) { - $data = array_merge($data, tailPiholeLog($_GET['tailLog'])); - } + if (isset($_GET['getForwardDestinations']) && $auth) + { + sendRequestFTL("forward-dest"); + $return = getResponseFTL(); + $forward_dest = array(); + foreach($return as $line) + { + $tmp = explode(" ",$line); + if(count($tmp) == 4) + { + $forward_dest[$tmp[3]."|".$tmp[2]] = $tmp[1]; + } + else + { + $forward_dest[$tmp[2]] = $tmp[1]; + } + } - function filterArray(&$inArray) { - $outArray = array(); - foreach ($inArray as $key=>$value) { - if (is_array($value)) { - $outArray[htmlspecialchars($key)] = filterArray($value); - } else { - $outArray[htmlspecialchars($key)] = !is_numeric($value) ? htmlspecialchars($value) : $value; - } - } - return $outArray; - } + $result = array('forward_destinations' => $forward_dest); + $data = array_merge($data, $result); + } - $data = filterArray($data); + if (isset($_GET['getQueryTypes']) && $auth) + { + sendRequestFTL("querytypes"); + $return = getResponseFTL(); + $querytypes = array(); + foreach($return as $ret) + { + $tmp = explode(": ",$ret); + $querytypes[$tmp[0]] = $tmp[1]; + } - if(isset($_GET["jsonForceObject"])) - { - echo json_encode($data, JSON_FORCE_OBJECT); - } - else - { - echo json_encode($data); - } + $result = array('querytypes' => $querytypes); + $data = array_merge($data, $result); + } + + if (isset($_GET['getAllQueries']) && $auth) + { + if(isset($_GET['from']) && isset($_GET['until'])) + { + // Get limited time interval + sendRequestFTL("getallqueries-time ".$_GET['from']." ".$_GET['until']); + } + else if(isset($_GET['domain'])) + { + // Get specific domain only + sendRequestFTL("getallqueries-domain ".$_GET['domain']); + } + else if(isset($_GET['client'])) + { + // Get specific client only + sendRequestFTL("getallqueries-client ".$_GET['client']); + } + else + { + // Get all queries + sendRequestFTL("getallqueries"); + } + $return = getResponseFTL(); + $allQueries = array(); + foreach($return as $line) + { + $tmp = explode(" ",$line); + array_push($allQueries,$tmp); + } + + $result = array('data' => $allQueries); + $data = array_merge($data, $result); + } + + if (isset($_GET['enable'], $_GET['token']) && $auth) { + check_csrf($_GET['token']); + exec('sudo pihole enable'); + $data = array_merge($data, array("status" => "enabled")); + } + elseif (isset($_GET['disable'], $_GET['token']) && $auth) { + check_csrf($_GET['token']); + $disable = intval($_GET['disable']); + // intval returns the integer value on success, or 0 on failure + if($disable > 0) + { + exec("sudo pihole disable ".$disable."s"); + } + else + { + exec('sudo pihole disable'); + } + $data = array_merge($data, array("status" => "disabled")); + } + + if(isset($_GET["recentBlocked"])) + { + sendRequestFTL("recentBlocked"); + echo getResponseFTL()[0]; + unset($data); + } + + if(isset($data)) + { + echo json_encode($data); + } + + disconnectFTL(); ?> diff --git a/api_PHP.php b/api_PHP.php new file mode 100644 index 00000000..589ae0bd --- /dev/null +++ b/api_PHP.php @@ -0,0 +1,118 @@ + "enabled" + )); + } + elseif (isset($_GET['disable'], $_GET['token']) && $auth) { + check_csrf($_GET['token']); + $disable = intval($_GET['disable']); + // intval returns the integer value on success, or 0 on failure + if($disable > 0) + { + exec("sudo pihole disable ".$disable."s"); + } + else + { + exec('sudo pihole disable'); + } + $data = array_merge($data, Array( + "status" => "disabled" + )); + } + + if (isset($_GET['getGravityDomains'])) { + $data = array_merge($data, getGravity()); + } + + if (isset($_GET['tailLog']) && $auth) { + $data = array_merge($data, tailPiholeLog($_GET['tailLog'])); + } + + function filterArray(&$inArray) { + $outArray = array(); + foreach ($inArray as $key=>$value) { + if (is_array($value)) { + $outArray[htmlspecialchars($key)] = filterArray($value); + } else { + $outArray[htmlspecialchars($key)] = !is_numeric($value) ? htmlspecialchars($value) : $value; + } + } + return $outArray; + } + + $data = filterArray($data); + + if(isset($_GET["jsonForceObject"])) + { + echo json_encode($data, JSON_FORCE_OBJECT); + } + else + { + echo json_encode($data); + } +?> diff --git a/help.php b/help.php index e4960973..c3e729a9 100644 --- a/help.php +++ b/help.php @@ -48,20 +48,13 @@ +

The Top Domains and Top Advertisers lists may be hidden depending on the privacy Settings on the settings page

Note that the login session does not expire on the main page, as the summary is updated every 10 seconds which refreshes the session.

@@ -70,7 +63,7 @@

Query Log

-

Shows the recent queries by parsing Pi-hole's log. It is possible to search through the whole list by using the "Search" input field. If the status is reported as "OK", then the DNS request has been permitted. Otherwise ("Pi-holed") it has been blocked. By clicking on the buttons under "Action" the corresponding domains can quickly be added to the white-/blacklist. The status of the action will be reported on this page.

+

Shows the recent queries by parsing Pi-hole's log. It is possible to search through the whole list by using the "Search" input field. If the status is reported as "OK", then the DNS request has been permitted. Otherwise ("Pi-holed") it has been blocked. By clicking on the buttons under "Action" the corresponding domains can quickly be added to the white-/blacklist. The status of the action will be reported on this page. By default, only the recent 10 minutes are shown to enhance the loading speed of the query log page. All domains can be requested by clicking on the corresponding link in the header of the page. Note that the result heavily depends on your privacy settings (see Settings page).

@@ -118,8 +111,12 @@

Query Logging

Enabled/disable query logging on your Pi-hole + provide option to flush the log

API

- Change settings which apply to the API as well as the web UI
- Note that Top Clients have to be given as IP addresses + Change settings which apply to the API as well as the web UI +

Web User Interface

Other settings which affect the webUI but not the API of Pi-hole

System Administration

diff --git a/index.php b/index.php index 659a868a..5ea7b172 100644 --- a/index.php +++ b/index.php @@ -96,7 +96,7 @@
- +
@@ -112,7 +112,7 @@
- +
diff --git a/queries.php b/queries.php index 8e81c677..7dd4d1ae 100644 --- a/queries.php +++ b/queries.php @@ -19,30 +19,52 @@ if(isset($setupVars["API_QUERY_LOG_SHOW"])) { if($setupVars["API_QUERY_LOG_SHOW"] === "all") { - $showing = "(showing all queries)"; + $showing = "(showing all queries"; } elseif($setupVars["API_QUERY_LOG_SHOW"] === "permittedonly") { - $showing = "(showing permitted queries only)"; + $showing = "(showing permitted queries only"; } elseif($setupVars["API_QUERY_LOG_SHOW"] === "blockedonly") { - $showing = "(showing blocked queries only)"; + $showing = "(showing blocked queries only"; } elseif($setupVars["API_QUERY_LOG_SHOW"] === "nothing") { - $showing = "(showing no queries at all)"; + $showing = "(showing no queries at all"; } } +if(isset($_GET["all"])) +{ + $showing .= " within the Pi-hole log"; +} +else if(isset($_GET["client"])) +{ + $showing .= " for client ".htmlentities($_GET["client"]); +} +else if(isset($_GET["domain"])) +{ + $showing .= " for domain ".htmlentities($_GET["domain"]); +} +else if(isset($_GET["from"]) && isset($_GET["until"])) +{ + $showing .= " within limited time interval"; +} +else +{ + $showing .= " within recent 10 minutes, show all"; +} + if(isset($setupVars["API_PRIVACY_MODE"])) { if($setupVars["API_PRIVACY_MODE"]) { // Overwrite string from above - $showing = "(privacy mode enabled)"; + $showing .= ", privacy mode enabled"; } } +$showing .= ")"; ?> @@ -114,4 +136,5 @@ if(isset($setupVars["API_PRIVACY_MODE"])) require "scripts/pi-hole/php/footer.php"; ?> + diff --git a/scripts/pi-hole/js/index.js b/scripts/pi-hole/js/index.js index 537188fb..7c0dc79a 100644 --- a/scripts/pi-hole/js/index.js +++ b/scripts/pi-hole/js/index.js @@ -104,21 +104,26 @@ function updateQueryTypes() { var colors = []; // Get colors from AdminLTE $.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); }); - var v = [], c = []; - // Collect values and colors, immediately push individual labels - $.each(data, function(key , value) { + var v = [], c = [], k = []; + // Collect values and colors, and labels + $.each(data.querytypes, function(key , value) { v.push(value); c.push(colors.shift()); - queryTypeChart.data.labels.push(key.substr(6,key.length - 7)); + k.push(key); }); // Build a single dataset with the data to be pushed var dd = {data: v, backgroundColor: c}; // and push it at once - queryTypeChart.data.datasets.push(dd); + queryTypeChart.data.datasets[0] = dd; + queryTypeChart.data.labels = k; $("#query-types .overlay").remove(); queryTypeChart.update(); - queryTypeChart.chart.config.options.cutoutPercentage=30; + queryTypeChart.chart.config.options.cutoutPercentage=50; queryTypeChart.update(); + // Don't use rotation animation for further updates + queryTypeChart.options.animation.duration=0; + // Update query types data every 10 seconds + setTimeout(updateQueryTypes, 10000); }); } @@ -137,35 +142,39 @@ function escapeHtml(text) { function updateTopClientsChart() { $.getJSON("api.php?summaryRaw&getQuerySources", function(data) { + // Clear tables before filling them with data + $("#client-frequency td").parent().remove(); var clienttable = $("#client-frequency").find("tbody:last"); - var domain, percentage, domainname, domainip; - for (domain in data.top_sources) { + var client, percentage, clientname, clientip; + for (client in data.top_sources) { - if ({}.hasOwnProperty.call(data.top_sources, domain)){ - // Sanitize domain - domain = escapeHtml(domain); - if(domain.indexOf("|") > -1) + if ({}.hasOwnProperty.call(data.top_sources, client)){ + // Sanitize client + client = escapeHtml(client); + if(client.indexOf("|") > -1) { - var idx = domain.indexOf("|"); - domainname = domain.substr(0, idx); - domainip = domain.substr(idx+1, domain.length-idx); + var idx = client.indexOf("|"); + clientname = client.substr(0, idx); + clientip = client.substr(idx+1, client.length-idx); } else { - domainname = domain; - domainip = domain; + clientname = client; + clientip = client; } - var url = ""+domainname+""; - percentage = data.top_sources[domain] / data.dns_queries_today * 100; + var url = ""+clientname+""; + percentage = data.top_sources[client] / data.dns_queries_today * 100; clienttable.append(" " + url + - " " + data.top_sources[domain] + "
" + data.top_sources[client] + "
"); } } $("#client-frequency .overlay").remove(); + // Update top clients list data every 10 seconds + setTimeout(updateTopClientsChart, 10000); }); } @@ -174,9 +183,9 @@ function updateForwardDestinations() { var colors = []; // Get colors from AdminLTE $.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); }); - var v = [], c = []; + var v = [], c = [], k = []; // Collect values and colors, immediately push individual labels - $.each(data, function(key , value) { + $.each(data.forward_destinations, function(key , value) { v.push(value); c.push(colors.shift()); if(key.indexOf("|") > -1) @@ -184,21 +193,29 @@ function updateForwardDestinations() { var idx = key.indexOf("|"); key = key.substr(0, idx)+" ("+key.substr(idx+1, key.length-idx)+")"; } - forwardDestinationChart.data.labels.push(key); + k.push(key); }); // Build a single dataset with the data to be pushed var dd = {data: v, backgroundColor: c}; // and push it at once - forwardDestinationChart.data.datasets.push(dd); + forwardDestinationChart.data.datasets[0] = dd; + forwardDestinationChart.data.labels = k; $("#forward-destinations .overlay").remove(); forwardDestinationChart.update(); - forwardDestinationChart.chart.config.options.cutoutPercentage=30; + forwardDestinationChart.chart.config.options.cutoutPercentage=50; forwardDestinationChart.update(); + // Don't use rotation animation for further updates + forwardDestinationChart.options.animation.duration=0; + // Update forward destinations data every 10 seconds + setTimeout(updateForwardDestinations, 10000); }); } function updateTopLists() { $.getJSON("api.php?summaryRaw&topItems", function(data) { + // Clear tables before filling them with data + $("#domain-frequency td").parent().remove(); + $("#ad-frequency td").parent().remove(); var domaintable = $("#domain-frequency").find("tbody:last"); var adtable = $("#ad-frequency").find("tbody:last"); var url, domain, percentage; @@ -206,17 +223,10 @@ function updateTopLists() { if ({}.hasOwnProperty.call(data.top_queries,domain)){ // Sanitize domain domain = escapeHtml(domain); - if(domain !== "pi.hole") - { - url = ""+domain+""; - } - else - { - url = domain; - } + url = ""+domain+""; percentage = data.top_queries[domain] / data.dns_queries_today * 100; domaintable.append(" " + url + - " " + data.top_queries[domain] + "
" + data.top_queries[domain] + "
"); } } @@ -234,13 +244,21 @@ function updateTopLists() { url = ""+domain+""; percentage = data.top_ads[domain] / data.ads_blocked_today * 100; adtable.append(" " + url + - " " + data.top_ads[domain] + "
" + data.top_ads[domain] + "
"); } } + // Remove table if there are no results (e.g. privacy mode enabled) + if(jQuery.isEmptyObject(data.top_ads)) + { + $("#ad-frequency").parent().remove(); + } + $("#domain-frequency .overlay").remove(); $("#ad-frequency .overlay").remove(); + // Update top lists data every 10 seconds + setTimeout(updateTopLists, 10000); }); } @@ -429,10 +447,8 @@ $(document).ready(function() { var label = timeLineChart.data.labels[clickedElementindex]; //get value by index - //var value = timeLineChart.data.datasets[0].data[clickedElementindex]; - var time = new Date(label); - var from = time.getHours()+":"+time.getMinutes(); - var until = time.getHours()+":"+padNumber(parseInt(time.getMinutes()+9),2); + var from = label/1000; + var until = label/1000 + 600; window.location.href = "queries.php?from="+from+"&until="+until; } return false; diff --git a/scripts/pi-hole/js/queries.js b/scripts/pi-hole/js/queries.js index 8a7d45a7..f5abc74e 100644 --- a/scripts/pi-hole/js/queries.js +++ b/scripts/pi-hole/js/queries.js @@ -79,11 +79,17 @@ function add(domain,list) { }); } function handleAjaxError( xhr, textStatus, error ) { - if ( textStatus === "timeout" ) { + if ( textStatus === "timeout" ) + { alert( "The server took too long to send the data." ); } - else { - alert( "An error occured while loading the data. Presumably your log is too large to be processed." ); + else if(xhr.responseText.indexOf("Connection refused") >= 0) + { + alert( "An error occured while loading the data: Connection refused. Is FTL running?" ); + } + else + { + alert( "An unknown error occured while loading the data." ); } $("#all-queries_processing").hide(); tableApi.clear(); @@ -99,32 +105,58 @@ $(document).ready(function() { var APIstring = "api.php?getAllQueries"; - if("from" in GETDict) + if("from" in GETDict && "until" in GETDict) { APIstring += "&from="+GETDict["from"]; - } - - if("until" in GETDict) - { APIstring += "&until="+GETDict["until"]; } + else if("client" in GETDict) + { + APIstring += "&client="+GETDict["client"]; + } + else if("domain" in GETDict) + { + APIstring += "&domain="+GETDict["domain"]; + } + else if(!("all" in GETDict)) + { + var timestamp = Math.floor(Date.now() / 1000); + APIstring += "&from="+(timestamp - 600); + APIstring += "&until="+(timestamp + 100); + } tableApi = $("#all-queries").DataTable( { "rowCallback": function( row, data, index ){ - status = data[4]; - if (status === "Pi-holed (exact)") { + if (data[4] === "1") + { $(row).css("color","red"); + $("td:eq(4)", row).html( "Pi-holed" ); $("td:eq(5)", row).html( "" ); } - else if (status === "Pi-holed (wildcard)") { - $(row).css("color","red"); - $("td:eq(5)", row).html( "" ); - } - else{ + else if (data[4] === "2") + { $(row).css("color","green"); + $("td:eq(4)", row).html( "OK (forwarded)" ); $("td:eq(5)", row).html( "" ); } + else if (data[4] === "3") + { + $(row).css("color","green"); + $("td:eq(4)", row).html( "OK (cached)" ); + $("td:eq(5)", row).html( "" ); + } + else if (data[4] === "4") + { + $(row).css("color","red"); + $("td:eq(4)", row).html( "Pi-holed (wildcard)" ); + $("td:eq(5)", row).html( "" ); + } + else + { + $("td:eq(4)", row).html( "Unknown" ); + $("td:eq(5)", row).html( "" ); + } }, dom: "<'row'<'col-sm-12'f>>" + "<'row'<'col-sm-4'l><'col-sm-8'p>>" + @@ -135,7 +167,7 @@ $(document).ready(function() { "processing": true, "order" : [[0, "desc"]], "columns": [ - { "width" : "20%", "type": "date" }, + { "width" : "20%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD HH:mm:ss z");}else{return data;} }}, { "width" : "10%" }, { "width" : "40%" }, { "width" : "10%" }, @@ -151,8 +183,7 @@ $(document).ready(function() { }); $("#all-queries tbody").on( "click", "button", function () { var data = tableApi.row( $(this).parents("tr") ).data(); - status = data[4]; - if (status.substr(0,2) === "Pi") + if (data[4] === "1") { add(data[2],"white"); } @@ -161,20 +192,6 @@ $(document).ready(function() { add(data[2],"black"); } } ); - - if("client" in GETDict) - { - // Search in third column (zero indexed) - // Use regular expression to only show exact matches, i.e. - // don't show 192.168.0.100 when searching for 192.168.0.1 - // true = use regex, false = don't use smart search - tableApi.column(3).search("^"+escapeRegex(GETDict["client"])+"$",true,false); - } - if("domain" in GETDict) - { - // Search in second column (zero indexed) - tableApi.column(2).search("^"+escapeRegex(GETDict["domain"])+"$",true,false); - } } ); diff --git a/scripts/pi-hole/js/taillog.js b/scripts/pi-hole/js/taillog.js index ffe370fc..32272de9 100644 --- a/scripts/pi-hole/js/taillog.js +++ b/scripts/pi-hole/js/taillog.js @@ -12,7 +12,7 @@ var interval = 200; // Function that asks the API for new data function reloadData(){ clearTimeout(timer); - $.getJSON("api.php?tailLog="+offset, function (data) + $.getJSON("api_PHP.php?tailLog="+offset, function (data) { offset = data["offset"]; pre.append(data["lines"]); @@ -27,7 +27,7 @@ function reloadData(){ $(function(){ // Get offset at first loading of page - $.getJSON("api.php?tailLog", function (data) + $.getJSON("api_PHP.php?tailLog", function (data) { offset = data["offset"]; }); diff --git a/scripts/pi-hole/php/FTL.php b/scripts/pi-hole/php/FTL.php new file mode 100644 index 00000000..91cffdd2 --- /dev/null +++ b/scripts/pi-hole/php/FTL.php @@ -0,0 +1,103 @@ +10, 'usec'=>0]); + socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, ['sec'=>10, 'usec'=>0]); + + if(!$quiet) + { + echo "Success!\n\n"; + } + + return $socket; +} +function sendRequestFTL($requestin, $quiet=true) +{ + global $socket; + + $request = ">".$requestin; + if(!$quiet) + { + echo "Sending request (".$request.")...\n"; + } + + socket_write($socket, $request, strlen($request)) or die("Could not send data to server\n"); + if(!$quiet) + { + echo "OK.\n"; + } +} + +function getResponseFTL($quiet=true) +{ + global $socket; + if(!$quiet) + { + echo "Reading response:\n"; + } + + $response = []; + + while(true) + { + $out = socket_read($socket, 2048, PHP_NORMAL_READ); + if(!$quiet) + { + echo $out; + } + if(strrpos($out,"---EOM---") !== false) + { + break; + } + $out = rtrim($out); + if(strlen($out) > 0) + { + $response[] = $out; + } + } + + return $response; +} + +function disconnectFTL($quiet=true) +{ + global $socket; + if(!$quiet) + { + echo "Closing socket..."; + } + + socket_close($socket); + + if(!$quiet) + { + echo "OK.\n\n"; + } +} diff --git a/scripts/pi-hole/php/header.php b/scripts/pi-hole/php/header.php index bda379e7..009d0892 100644 --- a/scripts/pi-hole/php/header.php +++ b/scripts/pi-hole/php/header.php @@ -130,6 +130,12 @@ $boxedlayout = false; } } + + if(strpos(exec("ps -p `cat /var/run/pihole-FTL.pid` -o comm="), "pihole-FTL") !== false) + { + $FTL = true; + } + ?> @@ -263,7 +269,7 @@

Status

- Active'; @@ -276,29 +282,36 @@ } // CPU Temp - if ($celsius >= -273.15) { - echo " 60) { - echo "#FF0000"; + if($FTL) + { + if ($celsius >= -273.15) { + echo " 60) { + echo "#FF0000"; + } + else + { + echo "#3366FF"; + } + echo "\"> Temp: "; + if($temperatureunit === "F") + { + echo round($fahrenheit,1) . "°F"; + } + elseif($temperatureunit === "K") + { + echo round($kelvin,1) . "K"; + } + else + { + echo round($celsius,1) . "°C"; + } + echo ""; } - else - { - echo "#3366FF"; - } - echo "\"> Temp: "; - if($temperatureunit === "F") - { - echo round($fahrenheit,1) . "°F"; - } - elseif($temperatureunit === "K") - { - echo round($kelvin,1) . "K"; - } - else - { - echo round($celsius,1) . "°C"; - } - echo ""; + } + else + { + echo ' FTL offline'; } ?>
diff --git a/scripts/pi-hole/php/savesettings.php b/scripts/pi-hole/php/savesettings.php index fcfae51a..ed1d8e17 100644 --- a/scripts/pi-hole/php/savesettings.php +++ b/scripts/pi-hole/php/savesettings.php @@ -44,6 +44,15 @@ function validDomain($domain_name) return ( $validChars && $lengthCheck && $labelLengthCheck ); //length of each label } +function validDomainWildcard($domain_name) +{ + // There has to be either no or at most one "*" at the beginning of a line + $validChars = preg_match("/^((\*)?[_a-z\d](-*[_a-z\d])*)(\.([_a-z\d](-*[a-z\d])*))*(\.([a-z\d])*)*$/i", $domain_name); + $lengthCheck = preg_match("/^.{1,253}$/", $domain_name); + $labelLengthCheck = preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name); + return ( $validChars && $lengthCheck && $labelLengthCheck ); //length of each label +} + function validMAC($mac_addr) { // Accepted input format: 00:01:02:1A:5F:FF (characters may be lower case) @@ -215,9 +224,9 @@ function readStaticLeasesFile() $first = true; foreach($domains as $domain) { - if(!validDomain($domain)) + if(!validDomainWildcard($domain) || validIP($domain)) { - $error .= "Top Domains/Ads entry ".htmlspecialchars($domain)." is invalid!
"; + $error .= "Top Domains/Ads entry ".htmlspecialchars($domain)." is invalid (use only domains)!
"; } if(!$first) { @@ -234,7 +243,7 @@ function readStaticLeasesFile() $first = true; foreach($clients as $client) { - if(!validIP($client)) + if(!validDomainWildcard($client)) { $error .= "Top Clients entry ".htmlspecialchars($client)." is invalid (use only IP addresses)!
"; } @@ -309,24 +318,6 @@ function readStaticLeasesFile() exec("sudo pihole -a privacymode false"); } - if(isset($_POST["resolve-forward"])) - { - exec("sudo pihole -a resolve forward true"); - } - else - { - exec("sudo pihole -a resolve forward false"); - } - - if(isset($_POST["resolve-clients"])) - { - exec("sudo pihole -a resolve clients true"); - } - else - { - exec("sudo pihole -a resolve clients false"); - } - break; case "webUI": diff --git a/scripts/vendor/moment.min.js b/scripts/vendor/moment.min.js new file mode 100644 index 00000000..8c706712 --- /dev/null +++ b/scripts/vendor/moment.min.js @@ -0,0 +1,551 @@ +//! moment.js +//! version : 2.17.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return od.apply(null,arguments)} +// This is done to register the method called with moment() +// without creating circular dependencies. +function b(a){od=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){ +// IE8 will treat undefined and null as object if it wasn't for +// input != null +return null!=a&&"[object Object]"===Object.prototype.toString.call(a)}function e(a){var b;for(b in a) +// even if its not own property I'd still call it non-empty +return!1;return!0}function f(a){return"number"==typeof a||"[object Number]"===Object.prototype.toString.call(a)}function g(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function h(a,b){var c,d=[];for(c=0;c0)for(c in rd)d=rd[c],e=b[d],p(e)||(a[d]=e);return a} +// Moment prototype object +function r(b){q(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)), +// Prevent infinite loop in case updateOffset creates new moment +// objects. +sd===!1&&(sd=!0,a.updateOffset(this),sd=!1)}function s(a){return a instanceof r||null!=a&&null!=a._isAMomentObject}function t(a){return a<0?Math.ceil(a)||0:Math.floor(a)}function u(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=t(b)),c} +// compare two arrays, return the number of differences +function v(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;d0?"future":"past"];return z(c)?c(b):c.replace(/%s/i,b)}function J(a,b){var c=a.toLowerCase();Dd[c]=Dd[c+"s"]=Dd[b]=a}function K(a){return"string"==typeof a?Dd[a]||Dd[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)i(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(a,b){Ed[a]=b}function N(a){var b=[];for(var c in a)b.push({unit:c,priority:Ed[c]});return b.sort(function(a,b){return a.priority-b.priority}),b}function O(b,c){return function(d){return null!=d?(Q(this,b,d),a.updateOffset(this,c),this):P(this,b)}}function P(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function Q(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)} +// MOMENTS +function R(a){return a=K(a),z(this[a])?this[a]():this}function S(a,b){if("object"==typeof a){a=L(a);for(var c=N(a),d=0;d=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d} +// token: 'M' +// padded: ['MM', 2] +// ordinal: 'Mo' +// callback: function () { this.month() + 1 } +function U(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Id[a]=e),b&&(Id[b[0]]=function(){return T(e.apply(this,arguments),b[1],b[2])}),c&&(Id[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function V(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function W(a){var b,c,d=a.match(Fd);for(b=0,c=d.length;b=0&&Gd.test(a);)a=a.replace(Gd,c),Gd.lastIndex=0,d-=1;return a}function Z(a,b,c){$d[a]=z(b)?b:function(a,d){return a&&c?c:b}}function $(a,b){return i($d,a)?$d[a](b._strict,b._locale):new RegExp(_(a))} +// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript +function _(a){return aa(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function aa(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function ba(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),f(b)&&(d=function(a,c){c[b]=u(a)}),c=0;c=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ta(a){var b=new Date(Date.UTC.apply(null,arguments)); +//the Date.UTC function remaps years 0-99 to 1900-1999 +return a<100&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b} +// start-of-first-week - start-of-year +function ua(a,b,c){var// first-week day -- which january is always in the first week (4 for iso, 1 for other) +d=7+b-c, +// first-week day local weekday -- which local weekday is fwd +e=(7+ta(a,0,d).getUTCDay()-b)%7;return-e+d-1} +//http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday +function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return j<=0?(f=a-1,g=pa(f)+j):j>pa(a)?(f=a+1,g=j-pa(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return g<1?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(pa(a)-d+e)/7} +// HELPERS +// LOCALES +function ya(a){return wa(a,this._week.dow,this._week.doy).week}function za(){return this._week.dow}function Aa(){return this._week.doy} +// MOMENTS +function Ba(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Ca(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")} +// HELPERS +function Da(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Ea(a,b){return"string"==typeof a?b.weekdaysParse(a)%7||7:isNaN(a)?null:a}function Fa(a,b){return a?c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]:this._weekdays}function Ga(a){return a?this._weekdaysShort[a.day()]:this._weekdaysShort}function Ha(a){return a?this._weekdaysMin[a.day()]:this._weekdaysMin}function Ia(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;d<7;++d)f=k([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=je.call(this._weekdaysParse,g),e!==-1?e:null):"ddd"===b?(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:null):(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null):"dddd"===b?(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null))):"ddd"===b?(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null))):(e=je.call(this._minWeekdaysParse,g),e!==-1?e:(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:null)))}function Ja(a,b,c){var d,e,f;if(this._weekdaysParseExact)return Ia.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;d<7;d++){ +// test the regex +if( +// make the regex if we don't have it already +e=k([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}} +// MOMENTS +function Ka(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Da(a,this.localeData()),this.add(a-b,"d")):b}function La(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ma(a){if(!this.isValid())return null!=a?this:NaN; +// behaves the same as moment#day except +// as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) +// as a setter, sunday should belong to the previous week. +if(null!=a){var b=Ea(a,this.localeData());return this.day(this.day()%7?b:b-7)}return this.day()||7}function Na(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):(i(this,"_weekdaysRegex")||(this._weekdaysRegex=ue),this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex)}function Oa(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(i(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ve),this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Pa(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(i(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=we),this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Qa(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],h=[],i=[],j=[];for(b=0;b<7;b++) +// make the regex if we don't have it already +c=k([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),h.push(e),i.push(f),j.push(d),j.push(e),j.push(f);for( +// Sorting makes sure if one weekday (or abbr) is a prefix of another it +// will match the longer piece. +g.sort(a),h.sort(a),i.sort(a),j.sort(a),b=0;b<7;b++)h[b]=aa(h[b]),i[b]=aa(i[b]),j[b]=aa(j[b]);this._weekdaysRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")} +// FORMATTING +function Ra(){return this.hours()%12||12}function Sa(){return this.hours()||24}function Ta(a,b){U(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})} +// PARSING +function Ua(a,b){return b._meridiemParse} +// LOCALES +function Va(a){ +// IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays +// Using charAt should be more compatible. +return"p"===(a+"").toLowerCase().charAt(0)}function Wa(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Xa(a){return a?a.toLowerCase().replace("_","-"):a} +// pick the locale from the array +// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each +// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root +function Ya(a){for(var b,c,d,e,f=0;f0;){if(d=Za(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&v(e,c,!0)>=b-1) +//the next array item is better than a shallower substring of this one +break;b--}f++}return null}function Za(a){var b=null; +// TODO: Find a better way to register and load all the locales in Node +if(!Be[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=xe._abbr,require("./locale/"+a), +// because defineLocale currently also sets the global locale, we +// want to undo that for lazy loaded locales +$a(b)}catch(a){}return Be[a]} +// This function will load locale and then set the global locale. If +// no arguments are passed in, it will simply return the current global +// locale key. +function $a(a,b){var c; +// moment.duration._locale = moment._locale = data; +return a&&(c=p(b)?bb(a):_a(a,b),c&&(xe=c)),xe._abbr}function _a(a,b){if(null!==b){var c=Ae;if(b.abbr=a,null!=Be[a])y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),c=Be[a]._config;else if(null!=b.parentLocale){if(null==Be[b.parentLocale])return Ce[b.parentLocale]||(Ce[b.parentLocale]=[]),Ce[b.parentLocale].push({name:a,config:b}),null;c=Be[b.parentLocale]._config} +// backwards compat for now: also set the locale +// make sure we set the locale AFTER all child locales have been +// created, so we won't end up with the child locale set. +return Be[a]=new C(B(c,b)),Ce[a]&&Ce[a].forEach(function(a){_a(a.name,a.config)}),$a(a),Be[a]} +// useful for testing +return delete Be[a],null}function ab(a,b){if(null!=b){var c,d=Ae; +// MERGE +null!=Be[a]&&(d=Be[a]._config),b=B(d,b),c=new C(b),c.parentLocale=Be[a],Be[a]=c, +// backwards compat for now: also set the locale +$a(a)}else +// pass null for config to unupdate, useful for tests +null!=Be[a]&&(null!=Be[a].parentLocale?Be[a]=Be[a].parentLocale:null!=Be[a]&&delete Be[a]);return Be[a]} +// returns locale data +function bb(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return xe;if(!c(a)){if( +//short-circuit everything else +b=Za(a))return b;a=[a]}return Ya(a)}function cb(){return wd(Be)}function db(a){var b,c=a._a;return c&&m(a).overflow===-2&&(b=c[be]<0||c[be]>11?be:c[ce]<1||c[ce]>ea(c[ae],c[be])?ce:c[de]<0||c[de]>24||24===c[de]&&(0!==c[ee]||0!==c[fe]||0!==c[ge])?de:c[ee]<0||c[ee]>59?ee:c[fe]<0||c[fe]>59?fe:c[ge]<0||c[ge]>999?ge:-1,m(a)._overflowDayOfYear&&(bce)&&(b=ce),m(a)._overflowWeeks&&b===-1&&(b=he),m(a)._overflowWeekday&&b===-1&&(b=ie),m(a).overflow=b),a} +// date from iso format +function eb(a){var b,c,d,e,f,g,h=a._i,i=De.exec(h)||Ee.exec(h);if(i){for(m(a).iso=!0,b=0,c=Ge.length;bpa(e)&&(m(a)._overflowDayOfYear=!0),c=ta(e,0,a._dayOfYear),a._a[be]=c.getUTCMonth(),a._a[ce]=c.getUTCDate()),b=0;b<3&&null==a._a[b];++b)a._a[b]=f[b]=d[b]; +// Zero out whatever was not defaulted, including time +for(;b<7;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b]; +// Check for 24:00:00.000 +24===a._a[de]&&0===a._a[ee]&&0===a._a[fe]&&0===a._a[ge]&&(a._nextDay=!0,a._a[de]=0),a._d=(a._useUTC?ta:sa).apply(null,f), +// Apply timezone offset from input. The actual utcOffset can be changed +// with parseZone. +null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[de]=24)}}function jb(a){var b,c,d,e,f,g,h,i;if(b=a._w,null!=b.GG||null!=b.W||null!=b.E)f=1,g=4, +// TODO: We need to take the current isoWeekYear, but that depends on +// how we interpret now (local, utc, fixed offset). So create +// a now version of current config (take local/utc/offset flags, and +// create now). +c=gb(b.GG,a._a[ae],wa(sb(),1,4).year),d=gb(b.W,1),e=gb(b.E,1),(e<1||e>7)&&(i=!0);else{f=a._locale._week.dow,g=a._locale._week.doy;var j=wa(sb(),f,g);c=gb(b.gg,a._a[ae],j.year), +// Default to current week. +d=gb(b.w,j.week),null!=b.d?( +// weekday -- low day numbers are considered next week +e=b.d,(e<0||e>6)&&(i=!0)):null!=b.e?( +// local weekday -- counting starts from begining of week +e=b.e+f,(b.e<0||b.e>6)&&(i=!0)): +// default to begining of week +e=f}d<1||d>xa(c,f,g)?m(a)._overflowWeeks=!0:null!=i?m(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[ae]=h.year,a._dayOfYear=h.dayOfYear)} +// date from string and format string +function kb(b){ +// TODO: Move this to another part of the creation flow to prevent circular deps +if(b._f===a.ISO_8601)return void eb(b);b._a=[],m(b).empty=!0; +// This array is used to make a Date, either with `new Date` or `Date.UTC` +var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Y(b._f,b._locale).match(Fd)||[],c=0;c0&&m(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length), +// don't parse if it's not a known token +Id[f]?(d?m(b).empty=!1:m(b).unusedTokens.push(f),da(f,d,b)):b._strict&&!d&&m(b).unusedTokens.push(f); +// add remaining unparsed input length to the string +m(b).charsLeftOver=i-j,h.length>0&&m(b).unusedInput.push(h), +// clear _12h flag if hour is <= 12 +b._a[de]<=12&&m(b).bigHour===!0&&b._a[de]>0&&(m(b).bigHour=void 0),m(b).parsedDateParts=b._a.slice(0),m(b).meridiem=b._meridiem, +// handle meridiem +b._a[de]=lb(b._locale,b._a[de],b._meridiem),ib(b),db(b)}function lb(a,b,c){var d; +// Fallback +return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&b<12&&(b+=12),d||12!==b||(b=0),b):b} +// date from string and array of format strings +function mb(a){var b,c,d,e,f;if(0===a._f.length)return m(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e +// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset +// +0200, so we adjust the time as needed, to be valid. +// +// Keeping the time actually adds/subtracts (one hour) +// from the actual represented time. That is why we call updateOffset +// a second time. In case it wants us to change the offset again +// _changeInProgress == true case, then we have to adjust, because +// there is no such time in the given timezone. +function Db(b,c){var d,e=this._offset||0;if(!this.isValid())return null!=b?this:NaN;if(null!=b){if("string"==typeof b){if(b=Ab(Xd,b),null===b)return this}else Math.abs(b)<16&&(b=60*b);return!this._isUTC&&c&&(d=Cb(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?Tb(this,Ob(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?e:Cb(this)}function Eb(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Fb(a){return this.utcOffset(0,a)}function Gb(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Cb(this),"m")),this}function Hb(){if(null!=this._tzm)this.utcOffset(this._tzm);else if("string"==typeof this._i){var a=Ab(Wd,this._i);null!=a?this.utcOffset(a):this.utcOffset(0,!0)}return this}function Ib(a){return!!this.isValid()&&(a=a?sb(a).utcOffset():0,(this.utcOffset()-a)%60===0)}function Jb(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Kb(){if(!p(this._isDSTShifted))return this._isDSTShifted;var a={};if(q(a,this),a=pb(a),a._a){var b=a._isUTC?k(a._a):sb(a._a);this._isDSTShifted=this.isValid()&&v(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Lb(){return!!this.isValid()&&!this._isUTC}function Mb(){return!!this.isValid()&&this._isUTC}function Nb(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Ob(a,b){var c,d,e,g=a, +// matching against regexp is expensive, do it on demand +h=null;// checks for null or undefined +return xb(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:f(a)?(g={},b?g[b]=a:g.milliseconds=a):(h=Ne.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:u(h[ce])*c,h:u(h[de])*c,m:u(h[ee])*c,s:u(h[fe])*c,ms:u(yb(1e3*h[ge]))*c}):(h=Oe.exec(a))?(c="-"===h[1]?-1:1,g={y:Pb(h[2],c),M:Pb(h[3],c),w:Pb(h[4],c),d:Pb(h[5],c),h:Pb(h[6],c),m:Pb(h[7],c),s:Pb(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=Rb(sb(g.from),sb(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new wb(g),xb(a)&&i(a,"_locale")&&(d._locale=a._locale),d}function Pb(a,b){ +// We'd normally use ~~inp for this, but unfortunately it also +// converts floats to ints. +// inp may be undefined, so careful calling replace on it. +var c=a&&parseFloat(a.replace(",",".")); +// apply sign while we're at it +return(isNaN(c)?0:c)*b}function Qb(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Rb(a,b){var c;return a.isValid()&&b.isValid()?(b=Bb(b,a),a.isBefore(b)?c=Qb(a,b):(c=Qb(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}} +// TODO: remove 'name' arg after deprecation is removed +function Sb(a,b){return function(c,d){var e,f; +//invert the arguments, but complain about it +return null===d||isNaN(+d)||(y(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ob(c,d),Tb(this,e,a),this}}function Tb(b,c,d,e){var f=c._milliseconds,g=yb(c._days),h=yb(c._months);b.isValid()&&(e=null==e||e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&Q(b,"Date",P(b,"Date")+g*d),h&&ja(b,P(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function Ub(a,b){var c=a.diff(b,"days",!0);return c<-6?"sameElse":c<-1?"lastWeek":c<0?"lastDay":c<1?"sameDay":c<2?"nextDay":c<7?"nextWeek":"sameElse"}function Vb(b,c){ +// We want to compare the start of today, vs this. +// Getting start-of-today depends on whether we're local/utc/offset or not. +var d=b||sb(),e=Bb(d,this).startOf("day"),f=a.calendarFormat(this,e)||"sameElse",g=c&&(z(c[f])?c[f].call(this,d):c[f]);return this.format(g||this.localeData().calendar(f,this,sb(d)))}function Wb(){return new r(this)}function Xb(a,b){var c=s(a)?a:sb(a);return!(!this.isValid()||!c.isValid())&&(b=K(p(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()f&&(b=f),Fc.call(this,a,b,c,d,e))}function Fc(a,b,c,d,e){var f=va(a,b,c,d,e),g=ta(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this} +// MOMENTS +function Gc(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)} +// HELPERS +// MOMENTS +function Hc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function Ic(a,b){b[ge]=u(1e3*("0."+a))} +// MOMENTS +function Jc(){return this._isUTC?"UTC":""}function Kc(){return this._isUTC?"Coordinated Universal Time":""}function Lc(a){return sb(1e3*a)}function Mc(){return sb.apply(null,arguments).parseZone()}function Nc(a){return a}function Oc(a,b,c,d){var e=bb(),f=k().set(d,b);return e[c](f,a)}function Pc(a,b,c){if(f(a)&&(b=a,a=void 0),a=a||"",null!=b)return Oc(a,b,c,"month");var d,e=[];for(d=0;d<12;d++)e[d]=Oc(a,d,c,"month");return e} +// () +// (5) +// (fmt, 5) +// (fmt) +// (true) +// (true, 5) +// (true, fmt, 5) +// (true, fmt) +function Qc(a,b,c,d){"boolean"==typeof a?(f(b)&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,f(b)&&(c=b,b=void 0),b=b||"");var e=bb(),g=a?e._week.dow:0;if(null!=c)return Oc(b,(c+g)%7,d,"day");var h,i=[];for(h=0;h<7;h++)i[h]=Oc(b,(h+g)%7,d,"day");return i}function Rc(a,b){return Pc(a,b,"months")}function Sc(a,b){return Pc(a,b,"monthsShort")}function Tc(a,b,c){return Qc(a,b,c,"weekdays")}function Uc(a,b,c){return Qc(a,b,c,"weekdaysShort")}function Vc(a,b,c){return Qc(a,b,c,"weekdaysMin")}function Wc(){var a=this._data;return this._milliseconds=Ze(this._milliseconds),this._days=Ze(this._days),this._months=Ze(this._months),a.milliseconds=Ze(a.milliseconds),a.seconds=Ze(a.seconds),a.minutes=Ze(a.minutes),a.hours=Ze(a.hours),a.months=Ze(a.months),a.years=Ze(a.years),this}function Xc(a,b,c,d){var e=Ob(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()} +// supports only 2.0-style add(1, 's') or add(duration) +function Yc(a,b){return Xc(this,a,b,1)} +// supports only 2.0-style subtract(1, 's') or subtract(duration) +function Zc(a,b){return Xc(this,a,b,-1)}function $c(a){return a<0?Math.floor(a):Math.ceil(a)}function _c(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data; +// if we have a mix of positive and negative values, bubble down first +// check: https://github.com/moment/moment/issues/2166 +// The following code bubbles up values, see the tests for +// examples of what that means. +// convert days to months +// 12 months -> 1 year +return f>=0&&g>=0&&h>=0||f<=0&&g<=0&&h<=0||(f+=864e5*$c(bd(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=t(f/1e3),i.seconds=a%60,b=t(a/60),i.minutes=b%60,c=t(b/60),i.hours=c%24,g+=t(c/24),e=t(ad(g)),h+=e,g-=$c(bd(e)),d=t(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function ad(a){ +// 400 years have 146097 days (taking into account leap year rules) +// 400 years have 12 months === 4800 +return 4800*a/146097}function bd(a){ +// the reverse of daysToMonths +return 146097*a/4800}function cd(a){var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+ad(b),"month"===a?c:c/12;switch( +// handle milliseconds separately because of floating point math errors (issue #1867) +b=this._days+Math.round(bd(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3; +// Math.floor prevents floating point math errors here +case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}} +// TODO: Use this.as('ms')? +function dd(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*u(this._months/12)}function ed(a){return function(){return this.as(a)}}function fd(a){return a=K(a),this[a+"s"]()}function gd(a){return function(){return this._data[a]}}function hd(){return t(this.days()/7)} +// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize +function id(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function jd(a,b,c){var d=Ob(a).abs(),e=of(d.as("s")),f=of(d.as("m")),g=of(d.as("h")),h=of(d.as("d")),i=of(d.as("M")),j=of(d.as("y")),k=e0,k[4]=c,id.apply(null,k)} +// This function allows you to set the rounding function for relative time strings +function kd(a){return void 0===a?of:"function"==typeof a&&(of=a,!0)} +// This function allows you to set a threshold for relative time strings +function ld(a,b){return void 0!==pf[a]&&(void 0===b?pf[a]:(pf[a]=b,!0))}function md(a){var b=this.localeData(),c=jd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function nd(){ +// for ISO strings we do not use the normal bubbling rules: +// * milliseconds bubble up until they become hours +// * days do not bubble at all +// * months bubble up until they become years +// This is because there is no context-free conversion between hours and days +// (think of clock changes) +// and also not between days and months (28-31 days per month) +var a,b,c,d=qf(this._milliseconds)/1e3,e=qf(this._days),f=qf(this._months); +// 3600 seconds -> 60 minutes -> 1 hour +a=t(d/60),b=t(a/60),d%=60,a%=60, +// 12 months -> 1 year +c=t(f/12),f%=12; +// inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js +var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(m<0?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var od,pd;pd=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;d68?1900:2e3)}; +// MOMENTS +var pe=O("FullYear",!0); +// FORMATTING +U("w",["ww",2],"wo","week"),U("W",["WW",2],"Wo","isoWeek"), +// ALIASES +J("week","w"),J("isoWeek","W"), +// PRIORITIES +M("week",5),M("isoWeek",5), +// PARSING +Z("w",Od),Z("ww",Od,Kd),Z("W",Od),Z("WW",Od,Kd),ca(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=u(a)});var qe={dow:0,// Sunday is the first day of the week. +doy:6}; +// FORMATTING +U("d",0,"do","day"),U("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),U("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),U("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),U("e",0,0,"weekday"),U("E",0,0,"isoWeekday"), +// ALIASES +J("day","d"),J("weekday","e"),J("isoWeekday","E"), +// PRIORITY +M("day",11),M("weekday",11),M("isoWeekday",11), +// PARSING +Z("d",Od),Z("e",Od),Z("E",Od),Z("dd",function(a,b){return b.weekdaysMinRegex(a)}),Z("ddd",function(a,b){return b.weekdaysShortRegex(a)}),Z("dddd",function(a,b){return b.weekdaysRegex(a)}),ca(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict); +// if we didn't get a weekday name, mark the date as invalid +null!=e?b.d=e:m(c).invalidWeekday=a}),ca(["d","e","E"],function(a,b,c,d){b[d]=u(a)}); +// LOCALES +var re="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),se="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),te="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ue=Zd,ve=Zd,we=Zd;U("H",["HH",2],0,"hour"),U("h",["hh",2],0,Ra),U("k",["kk",2],0,Sa),U("hmm",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)}),U("hmmss",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),U("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),U("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ta("a",!0),Ta("A",!1), +// ALIASES +J("hour","h"), +// PRIORITY +M("hour",13),Z("a",Ua),Z("A",Ua),Z("H",Od),Z("h",Od),Z("HH",Od,Kd),Z("hh",Od,Kd),Z("hmm",Pd),Z("hmmss",Qd),Z("Hmm",Pd),Z("Hmmss",Qd),ba(["H","HH"],de),ba(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),ba(["h","hh"],function(a,b,c){b[de]=u(a),m(c).bigHour=!0}),ba("hmm",function(a,b,c){var d=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d)),m(c).bigHour=!0}),ba("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d,2)),b[fe]=u(a.substr(e)),m(c).bigHour=!0}),ba("Hmm",function(a,b,c){var d=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d))}),ba("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d,2)),b[fe]=u(a.substr(e))});var xe,ye=/[ap]\.?m?\.?/i,ze=O("Hours",!0),Ae={calendar:xd,longDateFormat:yd,invalidDate:zd,ordinal:Ad,ordinalParse:Bd,relativeTime:Cd,months:le,monthsShort:me,week:qe,weekdays:re,weekdaysMin:te,weekdaysShort:se,meridiemParse:ye},Be={},Ce={},De=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ee=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Fe=/Z|[+-]\d\d(?::?\d\d)?/,Ge=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/], +// YYYYMM is NOT allowed by the standard +["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],He=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Ie=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=x("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}), +// constant that refers to the ISO standard +a.ISO_8601=function(){};var Je=x("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var a=sb.apply(null,arguments);return this.isValid()&&a.isValid()?athis?this:a:o()}),Le=function(){return Date.now?Date.now():+new Date};zb("Z",":"),zb("ZZ",""), +// PARSING +Z("Z",Xd),Z("ZZ",Xd),ba(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ab(Xd,a)}); +// HELPERS +// timezone chunker +// '+10:00' > ['10', '00'] +// '-1530' > ['-15', '30'] +var Me=/([\+\-]|\d\d)/gi; +// HOOKS +// This function will be called whenever a moment is mutated. +// It is intended to keep the offset in sync with the timezone. +a.updateOffset=function(){}; +// ASP.NET json date format regex +var Ne=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Oe=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;Ob.fn=wb.prototype;var Pe=Sb(1,"add"),Qe=Sb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Re=x("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)}); +// FORMATTING +U(0,["gg",2],0,function(){return this.weekYear()%100}),U(0,["GG",2],0,function(){return this.isoWeekYear()%100}),zc("gggg","weekYear"),zc("ggggg","weekYear"),zc("GGGG","isoWeekYear"),zc("GGGGG","isoWeekYear"), +// ALIASES +J("weekYear","gg"),J("isoWeekYear","GG"), +// PRIORITY +M("weekYear",1),M("isoWeekYear",1), +// PARSING +Z("G",Vd),Z("g",Vd),Z("GG",Od,Kd),Z("gg",Od,Kd),Z("GGGG",Sd,Md),Z("gggg",Sd,Md),Z("GGGGG",Td,Nd),Z("ggggg",Td,Nd),ca(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=u(a)}),ca(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}), +// FORMATTING +U("Q",0,"Qo","quarter"), +// ALIASES +J("quarter","Q"), +// PRIORITY +M("quarter",7), +// PARSING +Z("Q",Jd),ba("Q",function(a,b){b[be]=3*(u(a)-1)}), +// FORMATTING +U("D",["DD",2],"Do","date"), +// ALIASES +J("date","D"), +// PRIOROITY +M("date",9), +// PARSING +Z("D",Od),Z("DD",Od,Kd),Z("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),ba(["D","DD"],ce),ba("Do",function(a,b){b[ce]=u(a.match(Od)[0],10)}); +// MOMENTS +var Se=O("Date",!0); +// FORMATTING +U("DDD",["DDDD",3],"DDDo","dayOfYear"), +// ALIASES +J("dayOfYear","DDD"), +// PRIORITY +M("dayOfYear",4), +// PARSING +Z("DDD",Rd),Z("DDDD",Ld),ba(["DDD","DDDD"],function(a,b,c){c._dayOfYear=u(a)}), +// FORMATTING +U("m",["mm",2],0,"minute"), +// ALIASES +J("minute","m"), +// PRIORITY +M("minute",14), +// PARSING +Z("m",Od),Z("mm",Od,Kd),ba(["m","mm"],ee); +// MOMENTS +var Te=O("Minutes",!1); +// FORMATTING +U("s",["ss",2],0,"second"), +// ALIASES +J("second","s"), +// PRIORITY +M("second",15), +// PARSING +Z("s",Od),Z("ss",Od,Kd),ba(["s","ss"],fe); +// MOMENTS +var Ue=O("Seconds",!1); +// FORMATTING +U("S",0,0,function(){return~~(this.millisecond()/100)}),U(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),U(0,["SSS",3],0,"millisecond"),U(0,["SSSS",4],0,function(){return 10*this.millisecond()}),U(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),U(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),U(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),U(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),U(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}), +// ALIASES +J("millisecond","ms"), +// PRIORITY +M("millisecond",16), +// PARSING +Z("S",Rd,Jd),Z("SS",Rd,Kd),Z("SSS",Rd,Ld);var Ve;for(Ve="SSSS";Ve.length<=9;Ve+="S")Z(Ve,Ud);for(Ve="S";Ve.length<=9;Ve+="S")ba(Ve,Ic); +// MOMENTS +var We=O("Milliseconds",!1); +// FORMATTING +U("z",0,0,"zoneAbbr"),U("zz",0,0,"zoneName");var Xe=r.prototype;Xe.add=Pe,Xe.calendar=Vb,Xe.clone=Wb,Xe.diff=bc,Xe.endOf=oc,Xe.format=gc,Xe.from=hc,Xe.fromNow=ic,Xe.to=jc,Xe.toNow=kc,Xe.get=R,Xe.invalidAt=xc,Xe.isAfter=Xb,Xe.isBefore=Yb,Xe.isBetween=Zb,Xe.isSame=$b,Xe.isSameOrAfter=_b,Xe.isSameOrBefore=ac,Xe.isValid=vc,Xe.lang=Re,Xe.locale=lc,Xe.localeData=mc,Xe.max=Ke,Xe.min=Je,Xe.parsingFlags=wc,Xe.set=S,Xe.startOf=nc,Xe.subtract=Qe,Xe.toArray=sc,Xe.toObject=tc,Xe.toDate=rc,Xe.toISOString=ec,Xe.inspect=fc,Xe.toJSON=uc,Xe.toString=dc,Xe.unix=qc,Xe.valueOf=pc,Xe.creationData=yc, +// Year +Xe.year=pe,Xe.isLeapYear=ra, +// Week Year +Xe.weekYear=Ac,Xe.isoWeekYear=Bc, +// Quarter +Xe.quarter=Xe.quarters=Gc, +// Month +Xe.month=ka,Xe.daysInMonth=la, +// Week +Xe.week=Xe.weeks=Ba,Xe.isoWeek=Xe.isoWeeks=Ca,Xe.weeksInYear=Dc,Xe.isoWeeksInYear=Cc, +// Day +Xe.date=Se,Xe.day=Xe.days=Ka,Xe.weekday=La,Xe.isoWeekday=Ma,Xe.dayOfYear=Hc, +// Hour +Xe.hour=Xe.hours=ze, +// Minute +Xe.minute=Xe.minutes=Te, +// Second +Xe.second=Xe.seconds=Ue, +// Millisecond +Xe.millisecond=Xe.milliseconds=We, +// Offset +Xe.utcOffset=Db,Xe.utc=Fb,Xe.local=Gb,Xe.parseZone=Hb,Xe.hasAlignedHourOffset=Ib,Xe.isDST=Jb,Xe.isLocal=Lb,Xe.isUtcOffset=Mb,Xe.isUtc=Nb,Xe.isUTC=Nb, +// Timezone +Xe.zoneAbbr=Jc,Xe.zoneName=Kc, +// Deprecations +Xe.dates=x("dates accessor is deprecated. Use date instead.",Se),Xe.months=x("months accessor is deprecated. Use month instead",ka),Xe.years=x("years accessor is deprecated. Use year instead",pe),Xe.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Eb),Xe.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Kb);var Ye=C.prototype;Ye.calendar=D,Ye.longDateFormat=E,Ye.invalidDate=F,Ye.ordinal=G,Ye.preparse=Nc,Ye.postformat=Nc,Ye.relativeTime=H,Ye.pastFuture=I,Ye.set=A, +// Month +Ye.months=fa,Ye.monthsShort=ga,Ye.monthsParse=ia,Ye.monthsRegex=na,Ye.monthsShortRegex=ma, +// Week +Ye.week=ya,Ye.firstDayOfYear=Aa,Ye.firstDayOfWeek=za, +// Day of Week +Ye.weekdays=Fa,Ye.weekdaysMin=Ha,Ye.weekdaysShort=Ga,Ye.weekdaysParse=Ja,Ye.weekdaysRegex=Na,Ye.weekdaysShortRegex=Oa,Ye.weekdaysMinRegex=Pa, +// Hours +Ye.isPM=Va,Ye.meridiem=Wa,$a("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===u(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}), +// Side effect imports +a.lang=x("moment.lang is deprecated. Use moment.locale instead.",$a),a.langData=x("moment.langData is deprecated. Use moment.localeData instead.",bb);var Ze=Math.abs,$e=ed("ms"),_e=ed("s"),af=ed("m"),bf=ed("h"),cf=ed("d"),df=ed("w"),ef=ed("M"),ff=ed("y"),gf=gd("milliseconds"),hf=gd("seconds"),jf=gd("minutes"),kf=gd("hours"),lf=gd("days"),mf=gd("months"),nf=gd("years"),of=Math.round,pf={s:45,// seconds to minute +m:45,// minutes to hour +h:22,// hours to day +d:26,// days to month +M:11},qf=Math.abs,rf=wb.prototype; +// Deprecations +// Side effect imports +// FORMATTING +// PARSING +// Side effect imports +return rf.abs=Wc,rf.add=Yc,rf.subtract=Zc,rf.as=cd,rf.asMilliseconds=$e,rf.asSeconds=_e,rf.asMinutes=af,rf.asHours=bf,rf.asDays=cf,rf.asWeeks=df,rf.asMonths=ef,rf.asYears=ff,rf.valueOf=dd,rf._bubble=_c,rf.get=fd,rf.milliseconds=gf,rf.seconds=hf,rf.minutes=jf,rf.hours=kf,rf.days=lf,rf.weeks=hd,rf.months=mf,rf.years=nf,rf.humanize=md,rf.toISOString=nd,rf.toString=nd,rf.toJSON=nd,rf.locale=lc,rf.localeData=mc,rf.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",nd),rf.lang=Re,U("X",0,0,"unix"),U("x",0,0,"valueOf"),Z("x",Vd),Z("X",Yd),ba("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),ba("x",function(a,b,c){c._d=new Date(u(a))}),a.version="2.17.1",b(sb),a.fn=Xe,a.min=ub,a.max=vb,a.now=Le,a.utc=k,a.unix=Lc,a.months=Rc,a.isDate=g,a.locale=$a,a.invalid=o,a.duration=Ob,a.isMoment=s,a.weekdays=Tc,a.parseZone=Mc,a.localeData=bb,a.isDuration=xb,a.monthsShort=Sc,a.weekdaysMin=Vc,a.defineLocale=_a,a.updateLocale=ab,a.locales=cb,a.weekdaysShort=Uc,a.normalizeUnits=K,a.relativeTimeRounding=kd,a.relativeTimeThreshold=ld,a.calendarFormat=Ub,a.prototype=Xe,a}); \ No newline at end of file diff --git a/settings.php b/settings.php index 01172d50..99f96aaf 100644 --- a/settings.php +++ b/settings.php @@ -625,24 +625,6 @@ $privacyMode = false; } - if(istrue($setupVars["API_GET_UPSTREAM_DNS_HOSTNAME"])) - { - $resolveForward = true; - } - else - { - $resolveForward = false; - } - - if(istrue($setupVars["API_GET_CLIENT_HOSTNAME"])) - { - $resolveClients = true; - } - else - { - $resolveClients = false; - } - ?>
@@ -661,36 +643,24 @@
- +
-

Reverse DNS lookup

-

Try to determine the domain name via querying the Pi-hole for

+

Privacy settings (Statistics / Query Log)

-
+
-
-
-
-

Query Log

-
-
-
-
-
-
-
-
+

Privacy mode

-
+
@@ -766,6 +736,35 @@
*/ ?> + +
+
+

FTL status (Running, PID: Not running)

+
+
+ Time FTL started:
+ User / Group: /
+ Total CPU utilization: %
+ Total CPU time:
+ Memory utilization: %
+ Used memory:
+ +
+

System Administration