mirror of
https://github.com/pi-hole/web.git
synced 2024-12-06 19:36:21 +01:00
+22
-18
@@ -1,33 +1,37 @@
|
||||
**In raising this issue, I confirm the following (please check boxes, eg [X] - no spaces) Failure to fill the template will close your issue:**
|
||||
**In raising this issue, I confirm the following:** `{please fill the checkboxes, e.g: [X]}`
|
||||
|
||||
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md).
|
||||
- [] The issue I am reporting can be *replicated*
|
||||
- [] The issue I'm reporting isn't a duplicate (see [FAQs](https://github.com/pi-hole/pi-hole/wiki/FAQs), [closed issues](https://github.com/pi-hole/pi-hole/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), and [open issues](https://github.com/pi-hole/pi-hole/issues)).
|
||||
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/AdminLTE/blob/master/CONTRIBUTING.md).
|
||||
- [] The issue I am reporting can be *replicated*.
|
||||
- [] The issue I am reporting isn't a duplicate (see [FAQs](https://github.com/pi-hole/pi-hole/wiki/FAQs), [closed issues](https://github.com/pi-hole/AdminLTE/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), and [open issues](https://github.com/pi-hole/AdminLTE/issues)).
|
||||
|
||||
**How familiar are you with the codebase?:**
|
||||
**How familiar are you with the the source code relevant to this issue?:**
|
||||
|
||||
_{replace this text with a number from 1 to 10, with 1 being not familiar, and 10 being very familiar}_
|
||||
`{Replace this with a number from 1 to 10. 1 being not familiar, and 10 being very familiar}`
|
||||
|
||||
---
|
||||
**[FEATURE REQUEST | QUESTION | OTHER]:**
|
||||
**Expected behaviour:**
|
||||
|
||||
Please [submit your feature request here](https://discourse.pi-hole.net/c/feature-requests), so it is votable by the community. It's also easier for us to track.
|
||||
`{A detailed description of what you expect to see}`
|
||||
|
||||
**[BUG | ISSUE] Expected Behaviour:**
|
||||
**Actual behaviour:**
|
||||
|
||||
`{A detailed description and/or screenshots of what you do see}`
|
||||
|
||||
**[BUG | ISSUE] Actual Behaviour:**
|
||||
**Steps to reproduce:**
|
||||
|
||||
`{Detailed steps of how we can reproduce this}`
|
||||
|
||||
**[BUG | ISSUE] Steps to reproduce:**
|
||||
**Debug token provided by [uploading `pihole -d` log](https://discourse.pi-hole.net/t/the-pihole-command-with-examples/738#debug):**
|
||||
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
`{Alphanumeric token}`
|
||||
|
||||
**(Optional) Debug token generated by `pihole -d`:**
|
||||
**Troubleshooting undertaken, and/or other relevant information:**
|
||||
|
||||
`<token>`
|
||||
`{Steps of what you have done to fix this}`
|
||||
|
||||
_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
|
||||
> * `{Please delete this quoted section when opening your issue}`
|
||||
> * You must follow the template instructions. Failure to do so will result in your issue being closed.
|
||||
> * Please [submit any feature requests here](https://discourse.pi-hole.net/c/feature-requests), so it is votable and trackable by the community.
|
||||
> * Please respect that Pi-hole is developed by volunteers, who can only reply in their spare time.
|
||||
> * Detail helps us understand and resolve an issue quicker, but please ensure it's relevant.
|
||||
> * _This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
**By submitting this pull request, I confirm the following (please check boxes, eg [X] - no spaces) _Failure to fill the template will close your PR_:**
|
||||
**By submitting this pull request, I confirm the following:** `{please fill any appropriate checkboxes, e.g: [X]}`
|
||||
|
||||
***Please submit all pull requests against the `development` branch. Failure to do so will delay or deny your request***
|
||||
`{Please ensure that your pull request is for the 'devel' branch!}`
|
||||
|
||||
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md).
|
||||
- [] I have checked that [another pull request](https://github.com/pi-hole/pi-hole/pulls) for this purpose does not exist.
|
||||
- [] I have considered, and confirmed that this submission will be valuable to others.
|
||||
- [] I accept that this submission may not be used, and the pull request closed at the will of the maintainer.
|
||||
- [] I give this submission freely, and claim no ownership to its content.
|
||||
|
||||
**How familiar are you with the codebase?:**
|
||||
|
||||
_{replace this text with a number from 1 to 10, with 1 being not familiar, and 10 being very familiar}_
|
||||
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/AdminLTE/blob/master/CONTRIBUTING.md), as well as this entire template.
|
||||
- [] I have made only one major change in my proposed changes.
|
||||
- [] I have commented my proposed changes within the code.
|
||||
- [] I have tested my proposed changes.
|
||||
- [] I am willing to help maintain this change if there are issues with it later.
|
||||
- [] I give this submission freely and claim no ownership.
|
||||
- [] It is compatible with the [EUPL 1.2 license](https://opensource.org/licenses/EUPL-1.1)
|
||||
- [] I have squashed any insignificant commits. ([`git rebase`](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html))
|
||||
- [] I have Signed Off all commits. ([`git commit --signoff`](https://git-scm.com/docs/git-commit#git-commit---signoff))
|
||||
|
||||
---
|
||||
_{replace this line with your pull request content}_
|
||||
|
||||
**What does this PR aim to accomplish?:**
|
||||
|
||||
_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
|
||||
`{A detailed description, screenshots (if necessary), as well as links to any relevant GitHub issues}`
|
||||
|
||||
**How does this PR accomplish the above?:**
|
||||
|
||||
`{A detailed description (such as a changelog) and screenshots (if necessary) of the implemented fix}`
|
||||
|
||||
**What documentation changes (if any) are needed to support this PR?:**
|
||||
|
||||
`{A detailed list of any necessary changes}`
|
||||
|
||||
> * `{Please delete this quoted section when opening your pull request}`
|
||||
> * You must follow the template instructions. Failure to do so will result in your issue being closed.
|
||||
> * Please respect that Pi-hole is developed by volunteers, who can only reply in their spare time.
|
||||
> * Detail helps us understand an issue quicker, but please ensure it's relevant.
|
||||
|
||||
+1
-1
@@ -35,4 +35,4 @@ groups:
|
||||
- master
|
||||
required: 4
|
||||
teams:
|
||||
- admin
|
||||
- approvers
|
||||
|
||||
+37
-9
@@ -1,10 +1,38 @@
|
||||
This is a basic checklist for now, We will update it in the future.
|
||||
_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
|
||||
|
||||
* Fork the repo and create your new branch based on the `devel` (development) branch.
|
||||
* Use 4 spaces instead of tabs
|
||||
* Commit Unix line endings
|
||||
* If you want, try to keep to the theme of black holes/gravity. This can add some fun to your submission.
|
||||
* Submit Pull Requests to the development branch only.
|
||||
* Before Submitting your Pull Request, merge `devel` with your new branch and fix any conflicts. (Make sure you don't break anything in development!)
|
||||
* Be patient. We will review all submitted pull requests, but our focus is on stability.. please don't be offended if we reject your PR, or it appears we're doing nothing with it! We'll get around to it..
|
||||
* Please use the Pi-hole brand: **Pi-hole** (Take a special look at the capitalized 'P' and a low 'h' with a hyphen)
|
||||
# Contributors Guide
|
||||
|
||||
Please read and understand the contribution guide before creating an issue or pull request.
|
||||
|
||||
## Etiquette
|
||||
|
||||
- Our goal for Pi-hole is **stability before features**. This means we focus on squashing critical bugs before adding new features. Often, we can do both in tandem, but bugs will take priority over a new feature.
|
||||
- Pi-hole is open source and [powered by donations](https://pi-hole.net/donate/), and as such, we give our **free time** to build, maintain, and **provide user support** for this project. It would be extremely unfair for us to suffer abuse or anger for our hard work, so please take a moment to consider that.
|
||||
- Please be considerate towards the developers and other users when raising issues or presenting pull requests.
|
||||
- Respect our decision(s), and do not be upset or abusive if your submission is not used.
|
||||
|
||||
## Viability
|
||||
|
||||
When requesting or submitting new features, first consider whether it might be useful to others. Open source projects are used by many people, who may have entirely different needs to your own. Think about whether or not your feature is likely to be used by other users of the project.
|
||||
|
||||
## Procedure
|
||||
|
||||
**Before filing an issue:**
|
||||
|
||||
- Attempt to replicate and **document** the problem, to ensure that it wasn't a coincidental incident.
|
||||
- Check to make sure your feature suggestion isn't already present within the project.
|
||||
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
|
||||
- Check the pull requests tab to ensure that the feature isn't already in progress.
|
||||
|
||||
**Before submitting a pull request:**
|
||||
|
||||
- Check the codebase to ensure that your feature doesn't already exist.
|
||||
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
|
||||
|
||||
## Technical Requirements
|
||||
|
||||
- Submit Pull Requests to the **devel branch only**.
|
||||
- Before Submitting your Pull Request, merge `devel` with your new branch and fix any conflicts. (Make sure you don't break anything in development!)
|
||||
- Commit Unix line endings.
|
||||
- Please use the Pi-hole brand: **Pi-hole** (Take a special look at the capitalized 'P' and a low 'h' with a hyphen)
|
||||
- (Optional fun) keep to the theme of Star Trek/black holes/gravity.
|
||||
|
||||
@@ -13,11 +13,12 @@ require("scripts/pi-hole/php/password.php");
|
||||
require("scripts/pi-hole/php/auth.php");
|
||||
check_cors();
|
||||
|
||||
$FTL_IP = "127.0.0.1";
|
||||
|
||||
$data = array();
|
||||
|
||||
// Common API functions
|
||||
if (isset($_GET['status']) && $auth)
|
||||
if (isset($_GET['status']))
|
||||
{
|
||||
$pistatus = exec('sudo pihole status web');
|
||||
if ($pistatus == "1")
|
||||
@@ -74,7 +75,7 @@ elseif (isset($_GET['disable']) && $auth)
|
||||
}
|
||||
|
||||
// Other API functions
|
||||
if(!testFTL() && !isset($_GET["PHP"]))
|
||||
if(!testFTL($FTL_IP) && !isset($_GET["PHP"]))
|
||||
{
|
||||
$data = array_merge($data, array("FTLnotrunning" => true));
|
||||
}
|
||||
|
||||
+76
-12
@@ -12,7 +12,8 @@ if(!isset($api))
|
||||
die("Direct call to api_FTL.php is not allowed!");
|
||||
}
|
||||
|
||||
$socket = connectFTL("127.0.0.1");
|
||||
// $FTL_IP is defined in api.php
|
||||
$socket = connectFTL($FTL_IP);
|
||||
|
||||
if (isset($_GET['type'])) {
|
||||
$data["type"] = "FTL";
|
||||
@@ -32,6 +33,12 @@ if (isset($_GET['summary']) || isset($_GET['summaryRaw']) || !count($_GET))
|
||||
{
|
||||
$tmp = explode(" ",$line);
|
||||
|
||||
if(($tmp[0] === "domains_being_blocked" && !is_numeric($tmp[1])) || $tmp[0] === "status")
|
||||
{
|
||||
$stats[$tmp[0]] = $tmp[1];
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isset($_GET['summary']))
|
||||
{
|
||||
if($tmp[0] !== "ads_percentage_today")
|
||||
@@ -71,7 +78,11 @@ if (isset($_GET['overTimeData10mins']))
|
||||
|
||||
if (isset($_GET['topItems']) && $auth)
|
||||
{
|
||||
if(is_numeric($_GET['topItems']))
|
||||
if($_GET['topItems'] === "audit")
|
||||
{
|
||||
sendRequestFTL("top-domains for audit");
|
||||
}
|
||||
else if(is_numeric($_GET['topItems']))
|
||||
{
|
||||
sendRequestFTL("top-domains (".$_GET['topItems'].")");
|
||||
}
|
||||
@@ -88,7 +99,11 @@ if (isset($_GET['topItems']) && $auth)
|
||||
$top_queries[$tmp[2]] = intval($tmp[1]);
|
||||
}
|
||||
|
||||
if(is_numeric($_GET['topItems']))
|
||||
if($_GET['topItems'] === "audit")
|
||||
{
|
||||
sendRequestFTL("top-ads for audit");
|
||||
}
|
||||
else if(is_numeric($_GET['topItems']))
|
||||
{
|
||||
sendRequestFTL("top-ads (".$_GET['topItems'].")");
|
||||
}
|
||||
@@ -102,7 +117,10 @@ if (isset($_GET['topItems']) && $auth)
|
||||
foreach($return as $line)
|
||||
{
|
||||
$tmp = explode(" ",$line);
|
||||
$top_ads[$tmp[2]] = intval($tmp[1]);
|
||||
if(count($tmp) === 4)
|
||||
$top_ads[$tmp[2]." (".$tmp[3].")"] = intval($tmp[1]);
|
||||
else
|
||||
$top_ads[$tmp[2]] = intval($tmp[1]);
|
||||
}
|
||||
|
||||
$result = array('top_queries' => $top_queries,
|
||||
@@ -153,7 +171,14 @@ if ((isset($_GET['topClients']) || isset($_GET['getQuerySources'])) && $auth)
|
||||
|
||||
if (isset($_GET['getForwardDestinations']) && $auth)
|
||||
{
|
||||
sendRequestFTL("forward-dest");
|
||||
if($_GET['getForwardDestinations'] === "unsorted")
|
||||
{
|
||||
sendRequestFTL("forward-dest unsorted");
|
||||
}
|
||||
else
|
||||
{
|
||||
sendRequestFTL("forward-dest");
|
||||
}
|
||||
$return = getResponseFTL();
|
||||
$forward_dest = array();
|
||||
foreach($return as $line)
|
||||
@@ -161,11 +186,11 @@ if (isset($_GET['getForwardDestinations']) && $auth)
|
||||
$tmp = explode(" ",$line);
|
||||
if(count($tmp) == 4)
|
||||
{
|
||||
$forward_dest[$tmp[3]."|".$tmp[2]] = intval($tmp[1]);
|
||||
$forward_dest[$tmp[3]."|".$tmp[2]] = floatval($tmp[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$forward_dest[$tmp[2]] = intval($tmp[1]);
|
||||
$forward_dest[$tmp[2]] = floatval($tmp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +206,7 @@ if (isset($_GET['getQueryTypes']) && $auth)
|
||||
foreach($return as $ret)
|
||||
{
|
||||
$tmp = explode(": ",$ret);
|
||||
$querytypes[$tmp[0]] = intval($tmp[1]);
|
||||
$querytypes[$tmp[0]] = floatval($tmp[1]);
|
||||
}
|
||||
|
||||
$result = array('querytypes' => $querytypes);
|
||||
@@ -239,7 +264,7 @@ if (isset($_GET['overTimeDataForwards']) && $auth)
|
||||
{
|
||||
$tmp = explode(" ",$line);
|
||||
for ($i=0; $i < count($tmp)-1; $i++) {
|
||||
$over_time[intval($tmp[0])][$i] = intval($tmp[$i+1]);
|
||||
$over_time[intval($tmp[0])][$i] = floatval($tmp[$i+1]);
|
||||
}
|
||||
}
|
||||
$result = array('over_time' => $over_time);
|
||||
@@ -256,11 +281,11 @@ if (isset($_GET['getForwardDestinationNames']) && $auth)
|
||||
$tmp = explode(" ",$line);
|
||||
if(count($tmp) == 4)
|
||||
{
|
||||
$forward_dest[$tmp[3]."|".$tmp[2]] = intval($tmp[1]);
|
||||
$forward_dest[$tmp[3]."|".$tmp[2]] = floatval($tmp[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$forward_dest[$tmp[2]] = intval($tmp[1]);
|
||||
$forward_dest[$tmp[2]] = floatval($tmp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +303,46 @@ if (isset($_GET['overTimeDataQueryTypes']) && $auth)
|
||||
{
|
||||
$tmp = explode(" ",$line);
|
||||
for ($i=0; $i < count($tmp)-1; $i++) {
|
||||
$over_time[intval($tmp[0])][$i] = intval($tmp[$i+1]);
|
||||
$over_time[intval($tmp[0])][$i] = floatval($tmp[$i+1]);
|
||||
}
|
||||
}
|
||||
$result = array('over_time' => $over_time);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['getClientNames']) && $auth)
|
||||
{
|
||||
sendRequestFTL("client-names");
|
||||
$return = getResponseFTL();
|
||||
$forward_dest = array();
|
||||
foreach($return as $line)
|
||||
{
|
||||
$tmp = explode(" ",$line);
|
||||
if(count($tmp) == 4)
|
||||
{
|
||||
$forward_dest[$tmp[3]."|".$tmp[2]] = floatval($tmp[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$forward_dest[$tmp[2]] = floatval($tmp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$result = array('clients' => $forward_dest);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['overTimeDataClients']) && $auth)
|
||||
{
|
||||
sendRequestFTL("ClientsoverTime");
|
||||
$return = getResponseFTL();
|
||||
$over_time = array();
|
||||
|
||||
foreach($return as $line)
|
||||
{
|
||||
$tmp = explode(" ",$line);
|
||||
for ($i=0; $i < count($tmp)-1; $i++) {
|
||||
$over_time[intval($tmp[0])][$i] = floatval($tmp[$i+1]);
|
||||
}
|
||||
}
|
||||
$result = array('over_time' => $over_time);
|
||||
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license */
|
||||
|
||||
$api = true;
|
||||
header('Content-type: application/json');
|
||||
require("scripts/pi-hole/php/password.php");
|
||||
require("scripts/pi-hole/php/auth.php");
|
||||
check_cors();
|
||||
|
||||
$data = array();
|
||||
|
||||
// Needs package php5-sqlite, e.g.
|
||||
// sudo apt-get install php5-sqlite
|
||||
|
||||
function SQLite3_connect($trytoreconnect)
|
||||
{
|
||||
try {
|
||||
// connect to database
|
||||
return new SQLite3('/etc/pihole/pihole-FTL.db', SQLITE3_OPEN_READONLY);
|
||||
}
|
||||
catch (Exception $exception) {
|
||||
// sqlite3 throws an exception when it is unable to connect, try to reconnect after 3 seconds
|
||||
if($trytoreconnect)
|
||||
{
|
||||
sleep(3);
|
||||
$db = SQLite3_connect(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$db = SQLite3_connect(true);
|
||||
|
||||
if(!$db)
|
||||
{
|
||||
die("Error connecting to database");
|
||||
}
|
||||
|
||||
if (isset($_GET['getAllQueries']) && $auth)
|
||||
{
|
||||
if($_GET['getAllQueries'] === "empty")
|
||||
{
|
||||
$allQueries = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$from = intval($_GET["from"]);
|
||||
$until = intval($_GET["until"]);
|
||||
$results = $db->query('SELECT timestamp,type,domain,client,status FROM queries WHERE timestamp >= '.$from.' AND timestamp <= '.$until.' ORDER BY timestamp ASC');
|
||||
$allQueries = array();
|
||||
while ($row = $results->fetchArray())
|
||||
{
|
||||
$allQueries[] = [$row[0],$row[1] == 1 ? "IPv4" : "IPv6",$row[2],$row[3],$row[4]];
|
||||
}
|
||||
}
|
||||
$result = array('data' => $allQueries);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['topClients']) && $auth)
|
||||
{
|
||||
// $from = intval($_GET["from"]);
|
||||
$limit = "";
|
||||
if(isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = "WHERE timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
|
||||
}
|
||||
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
|
||||
{
|
||||
$limit = "WHERE timestamp >= ".$_GET["from"];
|
||||
}
|
||||
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = "WHERE timestamp <= ".$_GET["until"];
|
||||
}
|
||||
$results = $db->query('SELECT client,count(client) FROM queries '.$limit.' GROUP by client order by count(client) desc limit 10');
|
||||
$clients = array();
|
||||
while ($row = $results->fetchArray())
|
||||
{
|
||||
$clients[$row[0]] = intval($row[1]);
|
||||
// var_dump($row);
|
||||
}
|
||||
$result = array('top_sources' => $clients);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['topDomains']) && $auth)
|
||||
{
|
||||
$limit = "";
|
||||
|
||||
if(isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
|
||||
}
|
||||
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp >= ".$_GET["from"];
|
||||
}
|
||||
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp <= ".$_GET["until"];
|
||||
}
|
||||
$results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 2 OR STATUS == 3)'.$limit.' GROUP by domain order by count(domain) desc limit 10');
|
||||
$domains = array();
|
||||
while ($row = $results->fetchArray())
|
||||
{
|
||||
$domains[$row[0]] = intval($row[1]);
|
||||
}
|
||||
$result = array('top_domains' => $domains);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['topAds']) && $auth)
|
||||
{
|
||||
$limit = "";
|
||||
|
||||
if(isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
|
||||
}
|
||||
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp >= ".$_GET["from"];
|
||||
}
|
||||
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp <= ".$_GET["until"];
|
||||
}
|
||||
$results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 1 OR STATUS == 4)'.$limit.' GROUP by domain order by count(domain) desc limit 10');
|
||||
$addomains = array();
|
||||
while ($row = $results->fetchArray())
|
||||
{
|
||||
$addomains[$row[0]] = intval($row[1]);
|
||||
}
|
||||
$result = array('top_ads' => $addomains);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['getMinTimestamp']) && $auth)
|
||||
{
|
||||
$results = $db->query('SELECT MIN(timestamp) FROM queries');
|
||||
$result = array('mintimestamp' => $results->fetchArray()[0]);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['getMaxTimestamp']) && $auth)
|
||||
{
|
||||
$results = $db->query('SELECT MAX(timestamp) FROM queries');
|
||||
$result = array('maxtimestamp' => $results->fetchArray()[0]);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['getQueriesCount']) && $auth)
|
||||
{
|
||||
$results = $db->query('SELECT COUNT(timestamp) FROM queries');
|
||||
$result = array('count' => $results->fetchArray()[0]);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['getDBfilesize']) && $auth)
|
||||
{
|
||||
$filesize = filesize("/etc/pihole/pihole-FTL.db");
|
||||
$result = array('filesize' => $filesize);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if (isset($_GET['getGraphData']) && $auth)
|
||||
{
|
||||
$limit = "";
|
||||
|
||||
if(isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp >= ".intval($_GET["from"])." AND timestamp <= ".intval($_GET["until"]);
|
||||
}
|
||||
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp >= ".intval($_GET["from"]);
|
||||
}
|
||||
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
|
||||
{
|
||||
$limit = " AND timestamp <= ".intval($_GET["until"]);
|
||||
}
|
||||
|
||||
$interval = 600;
|
||||
|
||||
if(isset($_GET["interval"]))
|
||||
{
|
||||
$q = intval($_GET["interval"]);
|
||||
if($q > 10)
|
||||
$interval = $q;
|
||||
}
|
||||
|
||||
// Count permitted queries in intervals
|
||||
$results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 2 OR status == 3)'.$limit.' GROUP by interval ORDER by interval');
|
||||
$domains = array();
|
||||
while ($row = $results->fetchArray())
|
||||
{
|
||||
$domains[$row[0]] = intval($row[1]);
|
||||
}
|
||||
$result = array('domains_over_time' => $domains);
|
||||
$data = array_merge($data, $result);
|
||||
|
||||
// Count blocked queries in intervals
|
||||
$results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 1 OR status == 4 OR status == 5)'.$limit.' GROUP by interval ORDER by interval');
|
||||
$addomains = array();
|
||||
while ($row = $results->fetchArray())
|
||||
{
|
||||
$addomains[$row[0]] = intval($row[1]);
|
||||
}
|
||||
$result = array('ads_over_time' => $addomains);
|
||||
$data = array_merge($data, $result);
|
||||
}
|
||||
|
||||
if(isset($_GET["jsonForceObject"]))
|
||||
{
|
||||
echo json_encode($data, JSON_FORCE_OBJECT);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo json_encode($data);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php /*
|
||||
* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
require "scripts/pi-hole/php/header.php";
|
||||
?>
|
||||
<!-- Send PHP info to JS -->
|
||||
<div id="token" hidden><?php echo $token ?></div>
|
||||
<!-- Title -->
|
||||
<div class="page-header">
|
||||
<h1>Audit log (showing live data)</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="box" id="domain-frequency">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Allowed queries</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Hits</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay">
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col-md-6">
|
||||
<div class="box" id="ad-frequency">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Blocked queries</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Hits</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay">
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<p><strong>Important:</strong> Note that black- and whitelisted domains are not automatically applied on this page to avoid restarting the DNS service too often. Instead, click on this button, to have the new settings become effective:</p>
|
||||
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
<div class="container">
|
||||
<div class="row justify-content-md-right">
|
||||
<div class="col-2">
|
||||
<button class="btn btn-lg btn-primary btn-block" id="gravityBtn" disabled="true">Update black-/whitelists</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
require "scripts/pi-hole/php/footer.php";
|
||||
?>
|
||||
|
||||
<script src="scripts/pi-hole/js/auditlog.js"></script>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php /*
|
||||
* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
require "scripts/pi-hole/php/header.php";
|
||||
|
||||
// Generate CSRF token
|
||||
if(empty($_SESSION['token'])) {
|
||||
$_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
|
||||
}
|
||||
$token = $_SESSION['token'];
|
||||
|
||||
?>
|
||||
<!-- Send PHP info to JS -->
|
||||
<div id="token" hidden><?php echo $token ?></div>
|
||||
|
||||
<!-- Title -->
|
||||
<div class="page-header">
|
||||
<h1>Compute graphical statistics from the Pi-hole query database</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<!-- Date Input -->
|
||||
<div class="form-group">
|
||||
<label>Date and time range:</label>
|
||||
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<input type="text" class="form-control pull-right" id="querytime">
|
||||
</div>
|
||||
<!-- /.input group -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="queries-over-time">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Queries over the selected time period</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
<canvas id="queryOverTimeChart" width="800" height="250"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay" hidden="true">
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require "scripts/pi-hole/php/footer.php";
|
||||
?>
|
||||
|
||||
<script src="scripts/vendor/moment.min.js"></script>
|
||||
<script src="scripts/vendor/daterangepicker.js"></script>
|
||||
<script src="scripts/pi-hole/js/db_graph.js"></script>
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
<?php /*
|
||||
* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
require "scripts/pi-hole/php/header.php";
|
||||
|
||||
// Generate CSRF token
|
||||
if(empty($_SESSION['token'])) {
|
||||
$_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
|
||||
}
|
||||
$token = $_SESSION['token'];
|
||||
|
||||
?>
|
||||
<!-- Send PHP info to JS -->
|
||||
<div id="token" hidden><?php echo $token ?></div>
|
||||
|
||||
<!-- Title -->
|
||||
<div class="page-header">
|
||||
<h1>Compute Top Lists from the Pi-hole query database</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<!-- Date Input -->
|
||||
<div class="form-group">
|
||||
<label>Date and time range:</label>
|
||||
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<input type="text" class="form-control pull-right" id="querytime">
|
||||
</div>
|
||||
<!-- /.input group -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
if($boxedlayout)
|
||||
{
|
||||
$tablelayout = "col-md-6";
|
||||
}
|
||||
else
|
||||
{
|
||||
$tablelayout = "col-md-6 col-lg-4";
|
||||
}
|
||||
?>
|
||||
<div class="row">
|
||||
<div class="<?php echo $tablelayout; ?>">
|
||||
<div class="box" id="domain-frequency">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Top Domains</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Hits</th>
|
||||
<th>Frequency</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay" hidden>
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="<?php echo $tablelayout; ?>">
|
||||
<div class="box" id="ad-frequency">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Top Blocked Domains</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Hits</th>
|
||||
<th>Frequency</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay" hidden>
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="<?php echo $tablelayout; ?>">
|
||||
<div class="box" id="client-frequency">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Top Clients</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Client</th>
|
||||
<th>Requests</th>
|
||||
<th>Frequency</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay" hidden>
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require "scripts/pi-hole/php/footer.php";
|
||||
?>
|
||||
|
||||
<script src="scripts/vendor/moment.min.js"></script>
|
||||
<script src="scripts/vendor/daterangepicker.js"></script>
|
||||
<script src="scripts/pi-hole/js/db_lists.js"></script>
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
<?php /*
|
||||
* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
require "scripts/pi-hole/php/header.php";
|
||||
|
||||
// Generate CSRF token
|
||||
if(empty($_SESSION['token'])) {
|
||||
$_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
|
||||
}
|
||||
$token = $_SESSION['token'];
|
||||
|
||||
?>
|
||||
<!-- Send PHP info to JS -->
|
||||
<div id="token" hidden><?php echo $token ?></div>
|
||||
|
||||
<!-- Title -->
|
||||
<div class="page-header">
|
||||
<h1>Specify date range to be queried from the Pi-hole query database</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<!-- Date Input -->
|
||||
<div class="form-group">
|
||||
<label>Date and time range:</label>
|
||||
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<input type="text" class="form-control pull-right" id="querytime">
|
||||
</div>
|
||||
<!-- /.input group -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Small boxes (Stat box) -->
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-aqua">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="ads_blocked_exact">---</h3>
|
||||
<p>Queries Blocked</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-android-hand"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-aqua">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="ads_wildcard_blocked">---</h3>
|
||||
<p>Queries Blocked (Wildcards)</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-android-hand"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-green">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="dns_queries">---</h3>
|
||||
<p>Queries Total</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-earth"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-yellow">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="ads_percentage_today">---</h3>
|
||||
<p>Queries Blocked</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-pie-graph"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="recent-queries">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Recent Queries</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<div class="table-responsive">
|
||||
<table id="all-queries" class="display table table-striped table-bordered" cellspacing="0" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Type</th>
|
||||
<th>Domain</th>
|
||||
<th>Client</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Type</th>
|
||||
<th>Domain</th>
|
||||
<th>Client</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
|
||||
<?php
|
||||
require "scripts/pi-hole/php/footer.php";
|
||||
?>
|
||||
|
||||
<script src="scripts/vendor/moment.min.js"></script>
|
||||
<script src="scripts/vendor/daterangepicker.js"></script>
|
||||
<script src="scripts/pi-hole/js/db_queries.js"></script>
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
<?php /*
|
||||
<?php /*
|
||||
* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
@@ -9,7 +9,7 @@
|
||||
?>
|
||||
<!-- Title -->
|
||||
<div class="page-header">
|
||||
<h1>Update list of ad-serving domains</h1>
|
||||
<h1>Update list of ad-serving domains / blacklist / whitelist</h1>
|
||||
</div>
|
||||
|
||||
<!-- Alerts -->
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<div class="col-md-12">
|
||||
<h2>White- / Blacklist</h2>
|
||||
<p>Add or remove domains (or subdomains) from the white-/blacklist. If a domain is added to e.g. the whitelist, any possible entry of the same domain will be automatically removed from the blacklist and vice versa.</p>
|
||||
<p>Wildcard blacklisting is supported (entering <tt>something.de</tt> will block this domain including all subdomains like <tt>a.bb.c.999.something.de</tt>). Note that wildcard whitelisting is <em>not</em> supported.</p>
|
||||
<p>Wildcard blacklisting is supported (entering <samp>something.de</samp> will block this domain including all subdomains like <samp>a.bb.c.999.something.de</samp>). Note that wildcard whitelisting is <em>not</em> supported.</p>
|
||||
<p>You can white-/blacklist multiple entries at a time if you separate the domains by spaces.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,7 +89,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2>Tools → Query adlists</h2>
|
||||
This function is useful to find out what list a domain appears on. Since we don't control what the third-parties put on the block lists, you may find that a domain you normally visit stops working. If this is the case, you could run this command to scan for strings in the list of blocked domains and it will return the list the domain is found on. This proved useful a while back when the Mahakala list was adding <tt>apple.com</tt> and <tt>microsoft.com</tt> to their block list.</p>
|
||||
This function is useful to find out what list a domain appears on. Since we don't control what the third-parties put on the block lists, you may find that a domain you normally visit stops working. If this is the case, you could run this command to scan for strings in the list of blocked domains and it will return the list the domain is found on. This proved useful a while back when the Mahakala list was adding <samp>apple.com</samp> and <samp>microsoft.com</samp> to their block list.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -10,25 +10,12 @@
|
||||
?>
|
||||
<!-- Small boxes (Stat box) -->
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-aqua">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="ads_blocked_today">---</h3>
|
||||
<p>Queries Blocked Last 24 Hours</p>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-android-hand"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-green">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="dns_queries_today">---</h3>
|
||||
<p>Queries Last 24 Hours</p>
|
||||
<p>Total queries (<span id="unique_clients">-</span> clients)</p>
|
||||
<h3 class="statistic"><span id="dns_queries_today">---</span></h3>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-earth"></i>
|
||||
@@ -38,23 +25,53 @@
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-yellow">
|
||||
<div class="small-box bg-aqua">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="ads_percentage_today">---</h3>
|
||||
<p>Queries Blocked Last 24 Hours</p>
|
||||
<p>Queries Blocked</p>
|
||||
<h3 class="statistic"><span id="ads_blocked_today">---</span></h3>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-pie-graph"></i>
|
||||
<i class="ion ion-android-hand"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-red">
|
||||
<div class="small-box bg-yellow">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="domains_being_blocked">---</h3>
|
||||
<p>Domains on Blocklists</p>
|
||||
<p>Percent Blocked</p>
|
||||
<h3 class="statistic"><span id="ads_percentage_today">---</span></h3>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-pie-graph"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
$gravitylist = "/etc/pihole/gravity.list";
|
||||
if (file_exists($gravitylist))
|
||||
{
|
||||
$gravitydiff = date_diff(date_create("@".filemtime($gravitylist)),date_create("now"));
|
||||
if($gravitydiff->d > 1)
|
||||
$gravitydate = $gravitydiff->format("Blocking list updated %a days, %H:%I ago");
|
||||
elseif($gravitydiff->d == 1)
|
||||
$gravitydate = $gravitydiff->format("Blocking list updated one day, %H:%I ago");
|
||||
else
|
||||
$gravitydate = $gravitydiff->format("Blocking list updated %H:%I ago");
|
||||
}
|
||||
else
|
||||
{
|
||||
$gravitydate = "Blocking list not found";
|
||||
}
|
||||
?>
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-red" title="<?php echo $gravitydate; ?>">
|
||||
<div class="inner">
|
||||
<p>Domains on Blocklist</p>
|
||||
<h3 class="statistic"><span id="domains_being_blocked">---</span></h3>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="ion ion-ios-list"></i>
|
||||
@@ -72,7 +89,7 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
<canvas id="queryOverTimeChart" width="800" height="250"></canvas>
|
||||
<canvas id="queryOverTimeChart" width="800" height="140"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay">
|
||||
@@ -88,11 +105,88 @@
|
||||
// show since the API will respect the privacy of the user if he defines
|
||||
// a password
|
||||
if($auth){ ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="clients">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Clients (over time)</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
<canvas id="clientsChart" width="800" height="140"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay">
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 col-lg-6">
|
||||
<div class="box" id="query-types-pie">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Query Types (integrated)</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
<canvas id="queryTypePieChart" width="400" height="150"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay">
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 col-lg-6">
|
||||
<div class="box" id="forward-destinations-pie">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Forward Destinations (integrated)</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
<canvas id="forwardDestinationPieChart" width="400" height="150"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay">
|
||||
<i class="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
// Determine if "Query Types (over time)" should be shown
|
||||
$queryTypesOverTime = false;
|
||||
if(isset($setupVars['DASHBOARD_SHOW_QUERY_TYPES_OVER_TIME']))
|
||||
{
|
||||
if($setupVars['DASHBOARD_SHOW_QUERY_TYPES_OVER_TIME'])
|
||||
{
|
||||
$queryTypesOverTime = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if "Forward Destinations (over time)" should be shown
|
||||
$forwardDestsOverTime = false;
|
||||
if(isset($setupVars['DASHBOARD_SHOW_FORWARD_DESTS_OVER_TIME']))
|
||||
{
|
||||
if($setupVars['DASHBOARD_SHOW_FORWARD_DESTS_OVER_TIME'])
|
||||
{
|
||||
$forwardDestsOverTime = true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if($forwardDestsOverTime || $queryTypesOverTime) { ?>
|
||||
<div class="row">
|
||||
<?php if($queryTypesOverTime) { ?>
|
||||
<div class="col-md-12 col-lg-6">
|
||||
<div class="box" id="query-types">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Query Types over Time</h3>
|
||||
<h3 class="box-title">Query Types (over time)</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
@@ -105,10 +199,11 @@
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
<?php } if($forwardDestsOverTime) { ?>
|
||||
<div class="col-md-12 col-lg-6">
|
||||
<div class="box" id="forward-destinations">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Forward Destinations over Time</h3>
|
||||
<h3 class="box-title">Forward Destinations (over time)</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
@@ -121,7 +216,9 @@
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
if($boxedlayout)
|
||||
|
||||
@@ -11,7 +11,7 @@ $list = $_GET['l'];
|
||||
|
||||
if($list !== "white" && $list !== "black"){
|
||||
echo "Invalid list parameter";
|
||||
require "footer.php";
|
||||
require "scripts/pi-hole/php/footer.php";
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
<?php /*
|
||||
<?php /*
|
||||
* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
@@ -134,6 +134,7 @@ if(strlen($showing) > 0)
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<button type="button" id="resetButton" hidden="true">Clear Filters</button>
|
||||
</div>
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
// Define global variables
|
||||
var timeLineChart, queryTypeChart, forwardDestinationChart;
|
||||
|
||||
// Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"\"": """,
|
||||
"\'": "'"
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function updateTopLists() {
|
||||
$.getJSON("api.php?topItems=audit", function(data) {
|
||||
|
||||
if("FTLnotrunning" in data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
for (domain in data.top_queries) {
|
||||
if ({}.hasOwnProperty.call(data.top_queries,domain)){
|
||||
// Sanitize domain
|
||||
domain = escapeHtml(domain);
|
||||
url = "<a href=\"queries.php?domain="+domain+"\">"+domain+"</a>";
|
||||
percentage = data.top_queries[domain] / data.dns_queries_today * 100;
|
||||
domaintable.append("<tr> <td>" + url +
|
||||
"</td> <td>" + data.top_queries[domain] + "</td> <td> <button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button> <button style=\"color:orange; white-space: nowrap;\"><i class=\"fa fa-balance-scale\"></i> Audit</button> </td> </tr> ");
|
||||
}
|
||||
}
|
||||
|
||||
for (domain in data.top_ads) {
|
||||
if ({}.hasOwnProperty.call(data.top_ads,domain)){
|
||||
var input = domain.split(" ");
|
||||
// Sanitize domain
|
||||
var printdomain = escapeHtml(input[0]);
|
||||
if(input.length > 1)
|
||||
{
|
||||
url = "<a href=\"queries.php?domain="+printdomain+"\">"+printdomain+"</a> (wildcard blocked)";
|
||||
adtable.append("<tr> <td>" + url +
|
||||
"</td> <td>" + data.top_ads[domain] + "</td> <td> <button style=\"color:orange; white-space: nowrap;\"><i class=\"fa fa-balance-scale\"></i> Audit</button> </td> </tr> ");
|
||||
}
|
||||
else
|
||||
{
|
||||
url = "<a href=\"queries.php?domain="+printdomain+"\">"+printdomain+"</a>";
|
||||
adtable.append("<tr> <td>" + url +
|
||||
"</td> <td>" + data.top_ads[domain] + "</td> <td> <button style=\"color:green; white-space: nowrap;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button> <button style=\"color:orange; white-space: nowrap;\"><i class=\"fa fa-balance-scale\"></i> Audit</button> </td> </tr> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$("#domain-frequency .overlay").hide();
|
||||
$("#ad-frequency .overlay").hide();
|
||||
// Update top lists data every second
|
||||
setTimeout(updateTopLists, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function add(domain,list) {
|
||||
var token = $("#token").html();
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/add.php",
|
||||
method: "post",
|
||||
data: {"domain":domain, "list":list, "token":token, "auditlog":1}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateTopLists();
|
||||
|
||||
$("#domain-frequency tbody").on( "click", "button", function () {
|
||||
var url = ($(this).parents("tr"))[0].innerText.split(" ")[0];
|
||||
if($(this).context.innerText === " Blacklist")
|
||||
{
|
||||
add(url,"black");
|
||||
$("#gravityBtn").prop("disabled", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
add(url,"audit");
|
||||
}
|
||||
});
|
||||
|
||||
$("#ad-frequency tbody").on( "click", "button", function () {
|
||||
var url = ($(this).parents("tr"))[0].innerText.split(" ")[0].split(" ")[0];
|
||||
if($(this).context.innerText === " Whitelist")
|
||||
{
|
||||
add(url,"white");
|
||||
$("#gravityBtn").prop("disabled", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
add(url,"audit");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#gravityBtn").on("click", function() {
|
||||
window.location.replace("gravity.php?go");
|
||||
});
|
||||
@@ -0,0 +1,262 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/*global
|
||||
moment
|
||||
*/
|
||||
|
||||
var start__ = moment().subtract(6, "days");
|
||||
var from = moment(start__).utc().valueOf()/1000;
|
||||
var end__ = moment();
|
||||
var until = moment(end__).utc().valueOf()/1000;
|
||||
|
||||
$(function () {
|
||||
$("#querytime").daterangepicker(
|
||||
{
|
||||
timePicker: true, timePickerIncrement: 15,
|
||||
locale: { format: "MMMM Do YYYY, HH:mm" },
|
||||
ranges: {
|
||||
"Today": [moment().startOf("day"), moment()],
|
||||
"Yesterday": [moment().subtract(1, "days").startOf("day"), moment().subtract(1, "days").endOf("day")],
|
||||
"Last 7 Days": [moment().subtract(6, "days"), moment()],
|
||||
"Last 30 Days": [moment().subtract(29, "days"), moment()],
|
||||
"This Month": [moment().startOf("month"), moment()],
|
||||
"Last Month": [moment().subtract(1, "month").startOf("month"), moment().subtract(1, "month").endOf("month")],
|
||||
"This Year": [moment().startOf("year"), moment()],
|
||||
"All Time": [moment(0), moment()]
|
||||
},
|
||||
"opens": "center", "showDropdowns": true
|
||||
},
|
||||
function (startt, endt) {
|
||||
from = moment(startt).utc().valueOf()/1000;
|
||||
until = moment(endt).utc().valueOf()/1000;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"\"": """,
|
||||
"\'": "'"
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function padNumber(num) {
|
||||
return ("00" + num).substr(-2,2);
|
||||
}
|
||||
|
||||
// Helper function needed for converting the Objects to Arrays
|
||||
|
||||
function objectToArray(p){
|
||||
var keys = Object.keys(p);
|
||||
keys.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
var arr = [], idx = [];
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
arr.push(p[keys[i]]);
|
||||
idx.push(keys[i]);
|
||||
}
|
||||
return [idx,arr];
|
||||
}
|
||||
|
||||
var timeLineChart;
|
||||
|
||||
function compareNumbers(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
function updateQueriesOverTime() {
|
||||
$("#queries-over-time .overlay").show();
|
||||
$.getJSON("api_db.php?getGraphData&from="+from+"&until="+until, function(data) {
|
||||
|
||||
// convert received objects to arrays
|
||||
data.domains_over_time = objectToArray(data.domains_over_time);
|
||||
data.ads_over_time = objectToArray(data.ads_over_time);
|
||||
// Remove possibly already existing data
|
||||
timeLineChart.data.labels = [];
|
||||
timeLineChart.data.datasets[0].data = [];
|
||||
timeLineChart.data.datasets[1].data = [];
|
||||
|
||||
var dates = [], hour;
|
||||
|
||||
for (hour in data.domains_over_time[0]) {
|
||||
if ({}.hasOwnProperty.call(data.domains_over_time[0], hour)) {
|
||||
dates.push(parseInt(data.domains_over_time[0][hour]));
|
||||
}
|
||||
}
|
||||
|
||||
for (hour in data.ads_over_time[0]) {
|
||||
if ({}.hasOwnProperty.call(data.ads_over_time[0], hour)) {
|
||||
if(!dates.includes(parseInt(data.ads_over_time[0][hour])))
|
||||
{
|
||||
dates.push(parseInt(data.ads_over_time[0][hour]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dates.sort(compareNumbers);
|
||||
|
||||
// Add data for each hour that is available
|
||||
for (hour in dates) {
|
||||
if ({}.hasOwnProperty.call(dates, hour)) {
|
||||
var d, dom = 0, ads = 0;
|
||||
d = new Date(1000*dates[hour]);
|
||||
|
||||
var idx = data.domains_over_time[0].indexOf(dates[hour].toString());
|
||||
if (idx > -1)
|
||||
{
|
||||
dom = data.domains_over_time[1][idx];
|
||||
}
|
||||
|
||||
idx = data.ads_over_time[0].indexOf(dates[hour].toString());
|
||||
if (idx > -1)
|
||||
{
|
||||
ads = data.ads_over_time[1][idx];
|
||||
}
|
||||
|
||||
timeLineChart.data.labels.push(d);
|
||||
timeLineChart.data.datasets[0].data.push(dom);
|
||||
timeLineChart.data.datasets[1].data.push(ads);
|
||||
}
|
||||
}
|
||||
|
||||
timeLineChart.options.scales.xAxes[0].display=true;
|
||||
$("#queries-over-time .overlay").hide();
|
||||
timeLineChart.update();
|
||||
});
|
||||
}
|
||||
|
||||
/* global Chart */
|
||||
$(document).ready(function() {
|
||||
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
|
||||
timeLineChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [ 0 ],
|
||||
datasets: [
|
||||
{
|
||||
label: "Total DNS Queries",
|
||||
fill: true,
|
||||
backgroundColor: "rgba(220,220,220,0.5)",
|
||||
borderColor: "rgba(0, 166, 90,.8)",
|
||||
pointBorderColor: "rgba(0, 166, 90,.8)",
|
||||
pointRadius: 1,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
pointHitRadius: 5,
|
||||
cubicInterpolationMode: "monotone"
|
||||
},
|
||||
{
|
||||
label: "Blocked DNS Queries",
|
||||
fill: true,
|
||||
backgroundColor: "rgba(0,192,239,0.5)",
|
||||
borderColor: "rgba(0,192,239,1)",
|
||||
pointBorderColor: "rgba(0,192,239,1)",
|
||||
pointRadius: 1,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
pointHitRadius: 5,
|
||||
cubicInterpolationMode: "monotone"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
responsive: true,
|
||||
mode: "x-axis",
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
var label = tooltipItem[0].xLabel;
|
||||
var time = new Date(label);
|
||||
var date = time.getFullYear()+"-"+padNumber(time.getMonth())+"-"+padNumber(time.getDate());
|
||||
var h = time.getHours();
|
||||
var m = time.getMinutes();
|
||||
var from = padNumber(h)+":"+padNumber(m)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+9)+":59";
|
||||
return "Queries from "+from+" to "+to+" on "+date;
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
if(tooltipItems.datasetIndex === 1)
|
||||
{
|
||||
var percentage = 0.0;
|
||||
var total = parseInt(data.datasets[0].data[tooltipItems.index]);
|
||||
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
|
||||
if(total > 0)
|
||||
{
|
||||
percentage = 100.0*blocked/total;
|
||||
}
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel + " (" + percentage.toFixed(1) + "%)";
|
||||
}
|
||||
else
|
||||
{
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
display: false,
|
||||
time: {
|
||||
displayFormats: {
|
||||
"minute": "HH:mm",
|
||||
"hour": "HH:mm",
|
||||
"day": "HH:mm",
|
||||
"week": "MMM DD HH:mm",
|
||||
"month": "MMM DD",
|
||||
"quarter": "MMM DD",
|
||||
"year": "MMM DD"
|
||||
}
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#querytime").on("apply.daterangepicker", function(ev, picker) {
|
||||
$("#queries-over-time").show();
|
||||
updateQueriesOverTime();
|
||||
});
|
||||
|
||||
$("#queryOverTimeChart").click(function(evt){
|
||||
var activePoints = timeLineChart.getElementAtEvent(evt);
|
||||
if(activePoints.length > 0)
|
||||
{
|
||||
//get the internal index in the chart
|
||||
var clickedElementindex = activePoints[0]["_index"];
|
||||
|
||||
//get specific label by index
|
||||
var label = timeLineChart.data.labels[clickedElementindex];
|
||||
|
||||
//get value by index
|
||||
var from = label/1000;
|
||||
var until = label/1000 + 600;
|
||||
window.location.href = "db_queries.php?from="+from+"&until="+until;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -0,0 +1,181 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/*global
|
||||
moment
|
||||
*/
|
||||
|
||||
var start__ = moment().subtract(6, "days");
|
||||
var from = moment(start__).utc().valueOf()/1000;
|
||||
var end__ = moment();
|
||||
var until = moment(end__).utc().valueOf()/1000;
|
||||
|
||||
$(function () {
|
||||
$("#querytime").daterangepicker(
|
||||
{
|
||||
timePicker: true, timePickerIncrement: 15,
|
||||
locale: { format: "MMMM Do YYYY, HH:mm" },
|
||||
ranges: {
|
||||
"Today": [moment().startOf("day"), moment()],
|
||||
"Yesterday": [moment().subtract(1, "days").startOf("day"), moment().subtract(1, "days").endOf("day")],
|
||||
"Last 7 Days": [moment().subtract(6, "days"), moment()],
|
||||
"Last 30 Days": [moment().subtract(29, "days"), moment()],
|
||||
"This Month": [moment().startOf("month"), moment()],
|
||||
"Last Month": [moment().subtract(1, "month").startOf("month"), moment().subtract(1, "month").endOf("month")],
|
||||
"This Year": [moment().startOf("year"), moment()],
|
||||
"All Time": [moment(0), moment()]
|
||||
},
|
||||
"opens": "center", "showDropdowns": true
|
||||
},
|
||||
function (startt, endt) {
|
||||
from = moment(startt).utc().valueOf()/1000;
|
||||
until = moment(endt).utc().valueOf()/1000;
|
||||
});
|
||||
});
|
||||
|
||||
// Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"\"": """,
|
||||
"\'": "'"
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function updateTopClientsChart() {
|
||||
$("#client-frequency .overlay").show();
|
||||
$.getJSON("api_db.php?topClients&from="+from+"&until="+until, function(data) {
|
||||
|
||||
// Clear tables before filling them with data
|
||||
$("#client-frequency td").parent().remove();
|
||||
var clienttable = $("#client-frequency").find("tbody:last");
|
||||
var client, percentage, clientname, clientip;
|
||||
var sum = 0;
|
||||
for (client in data.top_sources) {
|
||||
if ({}.hasOwnProperty.call(data.top_sources, client)){
|
||||
sum += data.top_sources[client];
|
||||
}
|
||||
}
|
||||
|
||||
for (client in data.top_sources) {
|
||||
|
||||
if ({}.hasOwnProperty.call(data.top_sources, client)){
|
||||
// Sanitize client
|
||||
client = escapeHtml(client);
|
||||
if(escapeHtml(client) !== client)
|
||||
{
|
||||
// Make a copy with the escaped index if necessary
|
||||
data.top_sources[escapeHtml(client)] = data.top_sources[client];
|
||||
}
|
||||
if(client.indexOf("|") > -1)
|
||||
{
|
||||
var idx = client.indexOf("|");
|
||||
clientname = client.substr(0, idx);
|
||||
clientip = client.substr(idx+1, client.length-idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientname = client;
|
||||
clientip = client;
|
||||
}
|
||||
|
||||
var url = clientname;
|
||||
percentage = data.top_sources[client] / sum * 100.0;
|
||||
clienttable.append("<tr> <td>" + url +
|
||||
"</td> <td>" + data.top_sources[client] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"% of " + sum + "\"> <div class=\"progress-bar progress-bar-blue\" style=\"width: " +
|
||||
percentage + "%\"></div> </div> </td> </tr> ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$("#client-frequency .overlay").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function updateTopDomainsChart() {
|
||||
$("#domain-frequency .overlay").show();
|
||||
$.getJSON("api_db.php?topDomains&from="+from+"&until="+until, function(data) {
|
||||
|
||||
// Clear tables before filling them with data
|
||||
$("#domain-frequency td").parent().remove();
|
||||
var domaintable = $("#domain-frequency").find("tbody:last");
|
||||
var domain, percentage;
|
||||
var sum = 0;
|
||||
for (domain in data.top_domains) {
|
||||
if ({}.hasOwnProperty.call(data.top_domains, domain)){
|
||||
sum += data.top_domains[domain];
|
||||
}
|
||||
}
|
||||
|
||||
for (domain in data.top_domains) {
|
||||
|
||||
if ({}.hasOwnProperty.call(data.top_domains, domain)){
|
||||
// Sanitize domain
|
||||
domain = escapeHtml(domain);
|
||||
if(escapeHtml(domain) !== domain)
|
||||
{
|
||||
// Make a copy with the escaped index if necessary
|
||||
data.top_domains[escapeHtml(domain)] = data.top_domains[domain];
|
||||
}
|
||||
|
||||
percentage = data.top_domains[domain] / sum * 100.0;
|
||||
domaintable.append("<tr> <td>" + domain +
|
||||
"</td> <td>" + data.top_domains[domain] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"% of " + sum + "\"> <div class=\"progress-bar progress-bar-blue\" style=\"width: " +
|
||||
percentage + "%\"></div> </div> </td> </tr> ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$("#domain-frequency .overlay").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function updateTopAdsChart() {
|
||||
$("#ad-frequency .overlay").show();
|
||||
$.getJSON("api_db.php?topAds&from="+from+"&until="+until, function(data) {
|
||||
|
||||
// Clear tables before filling them with data
|
||||
$("#ad-frequency td").parent().remove();
|
||||
var adtable = $("#ad-frequency").find("tbody:last");
|
||||
var ad, percentage;
|
||||
var sum = 0;
|
||||
for (ad in data.top_ads) {
|
||||
if ({}.hasOwnProperty.call(data.top_ads, ad)){
|
||||
sum += data.top_ads[ad];
|
||||
}
|
||||
}
|
||||
|
||||
for (ad in data.top_ads) {
|
||||
|
||||
if ({}.hasOwnProperty.call(data.top_ads, ad)){
|
||||
// Sanitize ad
|
||||
ad = escapeHtml(ad);
|
||||
if(escapeHtml(ad) !== ad)
|
||||
{
|
||||
// Make a copy with the escaped index if necessary
|
||||
data.top_ads[escapeHtml(ad)] = data.top_ads[ad];
|
||||
}
|
||||
|
||||
percentage = data.top_ads[ad] / sum * 100.0;
|
||||
adtable.append("<tr> <td>" + ad + "</td> <td>" + data.top_ads[ad] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"% of " + sum + "\"> <div class=\"progress-bar progress-bar-blue\" style=\"width: " + percentage + "%\"></div> </div> </td> </tr> ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$("#ad-frequency .overlay").hide();
|
||||
});
|
||||
}
|
||||
|
||||
$("#querytime").on("apply.daterangepicker", function(ev, picker) {
|
||||
updateTopClientsChart();
|
||||
updateTopDomainsChart();
|
||||
updateTopAdsChart();
|
||||
});
|
||||
@@ -0,0 +1,275 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
/*global
|
||||
moment
|
||||
*/
|
||||
|
||||
var start__ = moment().subtract(6, "days");
|
||||
var from = moment(start__).utc().valueOf()/1000;
|
||||
var end__ = moment();
|
||||
var until = moment(end__).utc().valueOf()/1000;
|
||||
var instantquery = false;
|
||||
var daterange;
|
||||
|
||||
// Do we want to filter queries?
|
||||
var GETDict = {};
|
||||
location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];});
|
||||
|
||||
if("from" in GETDict && "until" in GETDict)
|
||||
{
|
||||
from = parseInt(GETDict["from"]);
|
||||
until = parseInt(GETDict["until"]);
|
||||
start__ = moment(1000*from);
|
||||
end__ = moment(1000*until);
|
||||
instantquery = true;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
daterange = $("#querytime").daterangepicker(
|
||||
{
|
||||
timePicker: true, timePickerIncrement: 15,
|
||||
locale: { format: "MMMM Do YYYY, HH:mm" },
|
||||
ranges: {
|
||||
"Today": [moment().startOf("day"), moment()],
|
||||
"Yesterday": [moment().subtract(1, "days").startOf("day"), moment().subtract(1, "days").endOf("day")],
|
||||
"Last 7 Days": [moment().subtract(6, "days"), moment()],
|
||||
"Last 30 Days": [moment().subtract(29, "days"), moment()],
|
||||
"This Month": [moment().startOf("month"), moment()],
|
||||
"Last Month": [moment().subtract(1, "month").startOf("month"), moment().subtract(1, "month").endOf("month")],
|
||||
"This Year": [moment().startOf("year"), moment()],
|
||||
"All Time": [moment(0), moment()]
|
||||
},
|
||||
"opens": "center", "showDropdowns": true
|
||||
},
|
||||
function (startt, endt) {
|
||||
from = moment(startt).utc().valueOf()/1000;
|
||||
until = moment(endt).utc().valueOf()/1000;
|
||||
});
|
||||
});
|
||||
|
||||
var tableApi, statistics;
|
||||
|
||||
function escapeRegex(text) {
|
||||
var map = {
|
||||
"(": "\\(",
|
||||
")": "\\)",
|
||||
".": "\\.",
|
||||
};
|
||||
return text.replace(/[().]/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function add(domain,list) {
|
||||
var token = $("#token").html();
|
||||
var alInfo = $("#alInfo");
|
||||
var alList = $("#alList");
|
||||
var alDomain = $("#alDomain");
|
||||
alDomain.html(domain);
|
||||
var alSuccess = $("#alSuccess");
|
||||
var alFailure = $("#alFailure");
|
||||
var err = $("#err");
|
||||
|
||||
if(list === "white")
|
||||
{
|
||||
alList.html("Whitelist");
|
||||
}
|
||||
else
|
||||
{
|
||||
alList.html("Blacklist");
|
||||
}
|
||||
|
||||
alInfo.show();
|
||||
alSuccess.hide();
|
||||
alFailure.hide();
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/add.php",
|
||||
method: "post",
|
||||
data: {"domain":domain, "list":list, "token":token},
|
||||
success: function(response) {
|
||||
if (response.indexOf("not a valid argument") >= 0 || response.indexOf("is not a valid domain") >= 0)
|
||||
{
|
||||
alFailure.show();
|
||||
err.html(response);
|
||||
alFailure.delay(4000).fadeOut(2000, function() { alFailure.hide(); });
|
||||
}
|
||||
else
|
||||
{
|
||||
alSuccess.show();
|
||||
alSuccess.delay(1000).fadeOut(2000, function() { alSuccess.hide(); });
|
||||
}
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
alList.html("");
|
||||
alDomain.html("");
|
||||
});
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
alFailure.show();
|
||||
err.html("");
|
||||
alFailure.delay(1000).fadeOut(2000, function() {
|
||||
alFailure.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
alList.html("");
|
||||
alDomain.html("");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleAjaxError( xhr, textStatus, error ) {
|
||||
if ( textStatus === "timeout" )
|
||||
{
|
||||
alert( "The server took too long to send the data." );
|
||||
}
|
||||
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.\n"+xhr.responseText );
|
||||
}
|
||||
$("#all-queries_processing").hide();
|
||||
tableApi.clear();
|
||||
tableApi.draw();
|
||||
}
|
||||
|
||||
var reloadCallback = function()
|
||||
{
|
||||
statistics = [0,0,0,0];
|
||||
var data = tableApi.rows().data();
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
statistics[0]++;
|
||||
if(data[i][4] === 1)
|
||||
{
|
||||
statistics[2]++;
|
||||
}
|
||||
else if(data[i][4] === 3)
|
||||
{
|
||||
statistics[1]++;
|
||||
}
|
||||
else if(data[i][4] === 4)
|
||||
{
|
||||
statistics[3]++;
|
||||
}
|
||||
}
|
||||
$("h3#dns_queries").text(statistics[0]);
|
||||
$("h3#ads_blocked_exact").text(statistics[2]);
|
||||
$("h3#ads_wildcard_blocked").text(statistics[3]);
|
||||
|
||||
var percent = 0.0;
|
||||
if(statistics[2] + statistics[3] > 0)
|
||||
{
|
||||
percent = 100.0*(statistics[2] + statistics[3]) / statistics[0];
|
||||
}
|
||||
$("h3#ads_percentage_today").text(parseFloat(percent).toFixed(1)+" %");
|
||||
};
|
||||
|
||||
function refreshTableData() {
|
||||
var APIstring = "api_db.php?getAllQueries&from="+from+"&until="+until;
|
||||
statistics = [0,0,0];
|
||||
tableApi.ajax.url(APIstring).load(reloadCallback);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var status;
|
||||
|
||||
var APIstring;
|
||||
if(instantquery)
|
||||
{
|
||||
APIstring = "api_db.php?getAllQueries&from="+from+"&until="+until;
|
||||
}
|
||||
else
|
||||
{
|
||||
APIstring = "api_db.php?getAllQueries=empty";
|
||||
}
|
||||
|
||||
tableApi = $("#all-queries").DataTable( {
|
||||
"rowCallback": function( row, data, index ){
|
||||
if (data[4] === 1)
|
||||
{
|
||||
$(row).css("color","red");
|
||||
$("td:eq(4)", row).html( "Pi-holed" );
|
||||
$("td:eq(5)", row).html( "<button style=\"color:green; white-space: nowrap;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button>" );
|
||||
// statistics[2]++;
|
||||
}
|
||||
else if (data[4] === 2)
|
||||
{
|
||||
$(row).css("color","green");
|
||||
$("td:eq(4)", row).html( "OK (forwarded)" );
|
||||
$("td:eq(5)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
|
||||
}
|
||||
else if (data[4] === 3)
|
||||
{
|
||||
$(row).css("color","green");
|
||||
$("td:eq(4)", row).html( "OK (cached)" );
|
||||
$("td:eq(5)", row).html( "<button style=\"color:red; white-space: nowrap;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
|
||||
// statistics[1]++;
|
||||
|
||||
}
|
||||
else if (data[4] === 4)
|
||||
{
|
||||
$(row).css("color","red");
|
||||
$("td:eq(4)", row).html( "Pi-holed (wildcard)" );
|
||||
$("td:eq(5)", row).html( "" );
|
||||
// statistics[3]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
$("td:eq(4)", row).html( "Unknown ("+data[4]+")" );
|
||||
$("td:eq(5)", row).html( "" );
|
||||
}
|
||||
// statistics[0]++;
|
||||
},
|
||||
dom: "<'row'<'col-sm-12'f>>" +
|
||||
"<'row'<'col-sm-4'l><'col-sm-8'p>>" +
|
||||
"<'row'<'col-sm-12'tr>>" +
|
||||
"<'row'<'col-sm-5'i><'col-sm-7'p>>",
|
||||
"ajax": {"url": APIstring, "error": handleAjaxError },
|
||||
"autoWidth" : false,
|
||||
"processing": true,
|
||||
"deferRender": true,
|
||||
"order" : [[0, "desc"]],
|
||||
"columns": [
|
||||
{ "width" : "20%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD HH:mm:ss z");}else{return data;} }},
|
||||
{ "width" : "10%" },
|
||||
{ "width" : "40%" },
|
||||
{ "width" : "10%" },
|
||||
{ "width" : "10%" },
|
||||
{ "width" : "10%" },
|
||||
],
|
||||
"lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
|
||||
"columnDefs": [ {
|
||||
"targets": -1,
|
||||
"data": null,
|
||||
"defaultContent": ""
|
||||
} ],
|
||||
"initComplete": reloadCallback
|
||||
});
|
||||
$("#all-queries tbody").on( "click", "button", function () {
|
||||
var data = tableApi.row( $(this).parents("tr") ).data();
|
||||
if (data[4] === "1")
|
||||
{
|
||||
add(data[2],"white");
|
||||
}
|
||||
else
|
||||
{
|
||||
add(data[2],"black");
|
||||
}
|
||||
} );
|
||||
|
||||
if(instantquery)
|
||||
{
|
||||
daterange.val(start__.format("MMMM Do YYYY, HH:mm") + " - " + end__.format("MMMM Do YYYY, HH:mm"));
|
||||
}
|
||||
} );
|
||||
|
||||
$("#querytime").on("apply.daterangepicker", function(ev, picker) {
|
||||
refreshTableData();
|
||||
});
|
||||
|
||||
@@ -28,7 +28,17 @@ function eventsource() {
|
||||
alSuccess.show();
|
||||
}
|
||||
|
||||
ta.append(e.data);
|
||||
// Detect ${OVER}
|
||||
if(e.data.indexOf("<------") !== -1)
|
||||
{
|
||||
ta.text(ta.text().substring(0, ta.text().lastIndexOf("\n")) + "\n");
|
||||
var new_string = e.data.replace("<------", "");
|
||||
ta.append(new_string);
|
||||
}
|
||||
else
|
||||
{
|
||||
ta.append(e.data);
|
||||
}
|
||||
|
||||
}, false);
|
||||
|
||||
|
||||
+556
-255
@@ -5,7 +5,9 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
// Define global variables
|
||||
/* global Chart */
|
||||
var timeLineChart, queryTypeChart, forwardDestinationChart;
|
||||
var queryTypePieChart, forwardDestinationPieChart, clientsChart;
|
||||
|
||||
function padNumber(num) {
|
||||
return ("00" + num).substr(-2,2);
|
||||
@@ -96,6 +98,8 @@ function updateQueryTypesOverTime() {
|
||||
|
||||
// convert received objects to arrays
|
||||
data.over_time = objectToArray(data.over_time);
|
||||
// remove last data point since it not representative
|
||||
data.over_time[0].splice(-1,1);
|
||||
var timestamps = data.over_time[0];
|
||||
var plotdata = data.over_time[1];
|
||||
// Remove possibly already existing data
|
||||
@@ -116,17 +120,9 @@ function updateQueryTypesOverTime() {
|
||||
// New style: Get Unix timestamps
|
||||
d = new Date(1000*h);
|
||||
|
||||
var sum = plotdata[j][0] + plotdata[j][1];
|
||||
var A = 0, AAAA = 0;
|
||||
if(sum > 0)
|
||||
{
|
||||
A = plotdata[j][0]/sum;
|
||||
AAAA = plotdata[j][1]/sum;
|
||||
}
|
||||
|
||||
queryTypeChart.data.labels.push(d);
|
||||
queryTypeChart.data.datasets[0].data.push(A);
|
||||
queryTypeChart.data.datasets[1].data.push(AAAA);
|
||||
queryTypeChart.data.datasets[0].data.push(1e-2*plotdata[j][0]);
|
||||
queryTypeChart.data.datasets[1].data.push(1e-2*plotdata[j][1]);
|
||||
}
|
||||
}
|
||||
$("#query-types .overlay").hide();
|
||||
@@ -145,6 +141,48 @@ function updateQueryTypesOverTime() {
|
||||
}
|
||||
});
|
||||
}
|
||||
function updateQueryTypesPie() {
|
||||
$.getJSON("api.php?getQueryTypes", function(data) {
|
||||
|
||||
if("FTLnotrunning" in data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var colors = [];
|
||||
// Get colors from AdminLTE
|
||||
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
|
||||
var v = [], c = [], k = [], iter;
|
||||
// Collect values and colors, and labels
|
||||
if(data.hasOwnProperty("querytypes"))
|
||||
{
|
||||
iter = data.querytypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
iter = data;
|
||||
}
|
||||
$.each(iter, function(key , value) {
|
||||
v.push(value);
|
||||
c.push(colors.shift());
|
||||
k.push(key);
|
||||
});
|
||||
// Build a single dataset with the data to be pushed
|
||||
var dd = {data: v, backgroundColor: c};
|
||||
// and push it at once
|
||||
queryTypePieChart.data.datasets[0] = dd;
|
||||
queryTypePieChart.data.labels = k;
|
||||
$("#query-types-pie .overlay").hide();
|
||||
queryTypePieChart.update();
|
||||
queryTypePieChart.chart.config.options.cutoutPercentage=50;
|
||||
queryTypePieChart.update();
|
||||
// Don't use rotation animation for further updates
|
||||
queryTypePieChart.options.animation.duration=0;
|
||||
}).done(function() {
|
||||
// Reload graph after minute
|
||||
setTimeout(updateQueryTypesPie, 60000);
|
||||
});
|
||||
}
|
||||
|
||||
function updateForwardedOverTime() {
|
||||
$.getJSON("api.php?overTimeDataForwards&getForwardDestinationNames", function(data) {
|
||||
@@ -156,6 +194,8 @@ function updateForwardedOverTime() {
|
||||
|
||||
// convert received objects to arrays
|
||||
data.over_time = objectToArray(data.over_time);
|
||||
// remove last data point since it not representative
|
||||
data.over_time[0].splice(-1,1);
|
||||
var timestamps = data.over_time[0];
|
||||
var plotdata = data.over_time[1];
|
||||
var labels = [];
|
||||
@@ -189,6 +229,7 @@ function updateForwardedOverTime() {
|
||||
forwardDestinationChart.data.datasets[0].pointHitRadius = 5;
|
||||
forwardDestinationChart.data.datasets[0].pointHoverRadius = 5;
|
||||
forwardDestinationChart.data.datasets[0].label = labels[0];
|
||||
forwardDestinationChart.data.datasets[0].cubicInterpolationMode = "monotone";
|
||||
|
||||
for (i = forwardDestinationChart.data.datasets.length; i < plotdata[0].length; i++)
|
||||
{
|
||||
@@ -199,18 +240,10 @@ function updateForwardedOverTime() {
|
||||
for (j in timestamps)
|
||||
{
|
||||
if (!{}.hasOwnProperty.call(timestamps, j)) continue;
|
||||
var sum = 0.0;
|
||||
for (key in plotdata[j])
|
||||
{
|
||||
if (!{}.hasOwnProperty.call(plotdata[j], key)) continue;
|
||||
sum += plotdata[j][key];
|
||||
}
|
||||
var dd = [];
|
||||
for (key in plotdata[j])
|
||||
{
|
||||
if (!{}.hasOwnProperty.call(plotdata[j], key)) continue;
|
||||
var singlepoint = plotdata[j][key];
|
||||
forwardDestinationChart.data.datasets[key].data.push(singlepoint/sum);
|
||||
forwardDestinationChart.data.datasets[key].data.push(1e-2*plotdata[j][key]);
|
||||
}
|
||||
|
||||
var d = new Date(1000*parseInt(timestamps[j]));
|
||||
@@ -233,6 +266,138 @@ function updateForwardedOverTime() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function updateClientsOverTime() {
|
||||
$.getJSON("api.php?overTimeDataClients&getClientNames", function(data) {
|
||||
|
||||
if("FTLnotrunning" in data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// convert received objects to arrays
|
||||
data.over_time = objectToArray(data.over_time);
|
||||
// remove last data point since it not representative
|
||||
data.over_time[0].splice(-1,1);
|
||||
var timestamps = data.over_time[0];
|
||||
var plotdata = data.over_time[1];
|
||||
var labels = [];
|
||||
var key, i, j;
|
||||
for (key in data.clients)
|
||||
{
|
||||
if (!{}.hasOwnProperty.call(data.clients, key)) continue;
|
||||
if(key.indexOf("|") > -1)
|
||||
{
|
||||
var idx = key.indexOf("|");
|
||||
key = key.substr(0, idx);
|
||||
}
|
||||
labels.push(key);
|
||||
}
|
||||
// Get colors from AdminLTE
|
||||
var colors = [];
|
||||
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
|
||||
var v = [], c = [], k = [];
|
||||
|
||||
// Remove possibly already existing data
|
||||
clientsChart.data.labels = [];
|
||||
clientsChart.data.datasets[0].data = [];
|
||||
for (i = 1; i < clientsChart.data.datasets.length; i++)
|
||||
{
|
||||
clientsChart.data.datasets[i].data = [];
|
||||
}
|
||||
|
||||
// Collect values and colors, and labels
|
||||
clientsChart.data.datasets[0].backgroundColor = colors[0];
|
||||
clientsChart.data.datasets[0].pointRadius = 0;
|
||||
clientsChart.data.datasets[0].pointHitRadius = 5;
|
||||
clientsChart.data.datasets[0].pointHoverRadius = 5;
|
||||
clientsChart.data.datasets[0].label = labels[0];
|
||||
|
||||
for (i = clientsChart.data.datasets.length; plotdata.length && i < plotdata[0].length; i++)
|
||||
{
|
||||
clientsChart.data.datasets.push({data: [], backgroundColor: colors[i], pointRadius: 0, pointHitRadius: 5, pointHoverRadius: 5, label: labels[i], cubicInterpolationMode: "monotone" });
|
||||
}
|
||||
|
||||
// Add data for each dataset that is available
|
||||
for (j in timestamps)
|
||||
{
|
||||
if (!{}.hasOwnProperty.call(timestamps, j)) continue;
|
||||
for (key in plotdata[j])
|
||||
{
|
||||
if (!{}.hasOwnProperty.call(plotdata[j], key)) continue;
|
||||
clientsChart.data.datasets[key].data.push(plotdata[j][key]);
|
||||
}
|
||||
|
||||
var d = new Date(1000*parseInt(timestamps[j]));
|
||||
clientsChart.data.labels.push(d);
|
||||
}
|
||||
$("#clients .overlay").hide();
|
||||
clientsChart.update();
|
||||
}).done(function() {
|
||||
// Reload graph after 10 minutes
|
||||
failures = 0;
|
||||
setTimeout(updateClientsOverTime, 600000);
|
||||
}).fail(function() {
|
||||
failures++;
|
||||
if(failures < 5)
|
||||
{
|
||||
// Try again after 1 minute only if this has not failed more
|
||||
// than five times in a row
|
||||
setTimeout(updateClientsOverTime, 60000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateForwardDestinationsPie() {
|
||||
$.getJSON("api.php?getForwardDestinations=unsorted", function(data) {
|
||||
|
||||
if("FTLnotrunning" in data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var colors = [];
|
||||
// Get colors from AdminLTE
|
||||
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
|
||||
var v = [], c = [], k = [], values = [];
|
||||
|
||||
// Collect values and colors
|
||||
$.each(data.forward_destinations, function(key , value) {
|
||||
if(key.indexOf("|") > -1)
|
||||
{
|
||||
key = key.substr(0, key.indexOf("|"));
|
||||
}
|
||||
values.push([key, value, colors.shift()]);
|
||||
});
|
||||
|
||||
// Sort data ASC accorwing to 2nd column, keep already assigned labels and colors
|
||||
values = values.sort(function(a,b) { return b[1] - a[1]; });
|
||||
|
||||
// Split data into individual arrays for the graphs
|
||||
$.each(values, function(key , value) {
|
||||
k.push(value[0]);
|
||||
v.push(value[1]);
|
||||
c.push(value[2]);
|
||||
});
|
||||
|
||||
// Build a single dataset with the data to be pushed
|
||||
var dd = {data: v, backgroundColor: c};
|
||||
// and push it at once
|
||||
forwardDestinationPieChart.data.labels = k;
|
||||
forwardDestinationPieChart.data.datasets[0] = dd;
|
||||
// and push it at once
|
||||
$("#forward-destinations-pie .overlay").hide();
|
||||
forwardDestinationPieChart.update();
|
||||
forwardDestinationPieChart.chart.config.options.cutoutPercentage=50;
|
||||
forwardDestinationPieChart.update();
|
||||
// Don't use rotation animation for further updates
|
||||
forwardDestinationPieChart.options.animation.duration=0;
|
||||
}).done(function() {
|
||||
// Reload graph after one minute
|
||||
setTimeout(updateForwardDestinationsPie, 60000);
|
||||
});
|
||||
}
|
||||
|
||||
// Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
@@ -375,8 +540,8 @@ function updateSummaryData(runOnce) {
|
||||
|
||||
if("FTLnotrunning" in data)
|
||||
{
|
||||
data["ads_blocked_today"] = "Lost";
|
||||
data["dns_queries_today"] = "connection";
|
||||
data["dns_queries_today"] = "Lost";
|
||||
data["ads_blocked_today"] = "connection";
|
||||
data["ads_percentage_today"] = "to";
|
||||
data["domains_being_blocked"] = "API";
|
||||
// Adjust text
|
||||
@@ -406,19 +571,19 @@ function updateSummaryData(runOnce) {
|
||||
}
|
||||
}
|
||||
|
||||
["ads_blocked_today", "dns_queries_today", "ads_percentage_today"].forEach(function(today) {
|
||||
var todayElement = $("h3#" + today);
|
||||
["ads_blocked_today", "dns_queries_today", "ads_percentage_today", "unique_clients"].forEach(function(today) {
|
||||
var todayElement = $("span#" + today);
|
||||
todayElement.text() !== data[today] &&
|
||||
todayElement.text() !== data[today] + "%" &&
|
||||
todayElement.addClass("glow");
|
||||
$("span#" + today).addClass("glow");
|
||||
});
|
||||
|
||||
window.setTimeout(function() {
|
||||
["ads_blocked_today", "dns_queries_today", "domains_being_blocked", "ads_percentage_today"].forEach(function(header, idx) {
|
||||
["ads_blocked_today", "dns_queries_today", "domains_being_blocked", "ads_percentage_today", "unique_clients"].forEach(function(header, idx) {
|
||||
var textData = (idx === 3 && data[header] !== "to") ? data[header] + "%" : data[header];
|
||||
$("h3#" + header).text(textData);
|
||||
$("span#" + header).text(textData);
|
||||
});
|
||||
$("h3.statistic.glow").removeClass("glow");
|
||||
$("span.glow").removeClass("glow");
|
||||
}, 500);
|
||||
|
||||
}).done(function() {
|
||||
@@ -437,59 +602,130 @@ function updateSummaryData(runOnce) {
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var isMobile = {
|
||||
Windows: function() {
|
||||
return /IEMobile/i.test(navigator.userAgent);
|
||||
},
|
||||
Android: function() {
|
||||
return /Android/i.test(navigator.userAgent);
|
||||
},
|
||||
BlackBerry: function() {
|
||||
return /BlackBerry/i.test(navigator.userAgent);
|
||||
},
|
||||
iOS: function() {
|
||||
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
||||
},
|
||||
any: function() {
|
||||
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Windows());
|
||||
}
|
||||
};
|
||||
var isMobile = {
|
||||
Windows: function() {
|
||||
return /IEMobile/i.test(navigator.userAgent);
|
||||
},
|
||||
Android: function() {
|
||||
return /Android/i.test(navigator.userAgent);
|
||||
},
|
||||
BlackBerry: function() {
|
||||
return /BlackBerry/i.test(navigator.userAgent);
|
||||
},
|
||||
iOS: function() {
|
||||
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
||||
},
|
||||
any: function() {
|
||||
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Windows());
|
||||
}
|
||||
};
|
||||
|
||||
// Pull in data via AJAX
|
||||
// Pull in data via AJAX
|
||||
|
||||
updateSummaryData();
|
||||
updateSummaryData();
|
||||
|
||||
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
|
||||
timeLineChart = new Chart(ctx, {
|
||||
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
|
||||
timeLineChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: "Total DNS Queries",
|
||||
fill: true,
|
||||
backgroundColor: "rgba(220,220,220,0.5)",
|
||||
borderColor: "rgba(0, 166, 90,.8)",
|
||||
pointBorderColor: "rgba(0, 166, 90,.8)",
|
||||
pointRadius: 1,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
pointHitRadius: 5,
|
||||
cubicInterpolationMode: "monotone"
|
||||
},
|
||||
{
|
||||
label: "Blocked DNS Queries",
|
||||
fill: true,
|
||||
backgroundColor: "rgba(0,192,239,0.5)",
|
||||
borderColor: "rgba(0,192,239,1)",
|
||||
pointBorderColor: "rgba(0,192,239,1)",
|
||||
pointRadius: 1,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
pointHitRadius: 5,
|
||||
cubicInterpolationMode: "monotone"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
mode: "x-axis",
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
var label = tooltipItem[0].xLabel;
|
||||
var time = label.match(/(\d?\d):?(\d?\d?)/);
|
||||
var h = parseInt(time[1], 10);
|
||||
var m = parseInt(time[2], 10) || 0;
|
||||
var from = padNumber(h)+":"+padNumber(m-5)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+4)+":59";
|
||||
return "Forward destinations from "+from+" to "+to;
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
if(tooltipItems.datasetIndex === 1)
|
||||
{
|
||||
var percentage = 0.0;
|
||||
var total = parseInt(data.datasets[0].data[tooltipItems.index]);
|
||||
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
|
||||
if(total > 0)
|
||||
{
|
||||
percentage = 100.0*blocked/total;
|
||||
}
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel + " (" + percentage.toFixed(1) + "%)";
|
||||
}
|
||||
else
|
||||
{
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "hour",
|
||||
displayFormats: {
|
||||
hour: "HH:mm"
|
||||
},
|
||||
tooltipFormat: "HH:mm"
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
|
||||
updateQueriesOverTime();
|
||||
|
||||
// Create / load "Forward Destinations over Time" only if authorized
|
||||
if(document.getElementById("forwardDestinationChart"))
|
||||
{
|
||||
ctx = document.getElementById("forwardDestinationChart").getContext("2d");
|
||||
forwardDestinationChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: "Total DNS Queries",
|
||||
fill: true,
|
||||
backgroundColor: "rgba(220,220,220,0.5)",
|
||||
borderColor: "rgba(0, 166, 90,.8)",
|
||||
pointBorderColor: "rgba(0, 166, 90,.8)",
|
||||
pointRadius: 1,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
pointHitRadius: 5,
|
||||
cubicInterpolationMode: "monotone"
|
||||
},
|
||||
{
|
||||
label: "Blocked DNS Queries",
|
||||
fill: true,
|
||||
backgroundColor: "rgba(0,192,239,0.5)",
|
||||
borderColor: "rgba(0,192,239,1)",
|
||||
pointBorderColor: "rgba(0,192,239,1)",
|
||||
pointRadius: 1,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
pointHitRadius: 5,
|
||||
cubicInterpolationMode: "monotone"
|
||||
}
|
||||
]
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
@@ -503,24 +739,73 @@ $(document).ready(function() {
|
||||
var m = parseInt(time[2], 10) || 0;
|
||||
var from = padNumber(h)+":"+padNumber(m-5)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+4)+":59";
|
||||
return "Queries from "+from+" to "+to;
|
||||
return "Forward destinations from "+from+" to "+to;
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
if(tooltipItems.datasetIndex === 1)
|
||||
{
|
||||
var percentage = 0.0;
|
||||
var total = parseInt(data.datasets[0].data[tooltipItems.index]);
|
||||
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
|
||||
if(total > 0)
|
||||
{
|
||||
percentage = 100.0*blocked/total;
|
||||
}
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel + " (" + percentage.toFixed(1) + "%)";
|
||||
}
|
||||
else
|
||||
{
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "hour",
|
||||
displayFormats: {
|
||||
hour: "HH:mm"
|
||||
},
|
||||
tooltipFormat: "HH:mm"
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
mix: 0.0,
|
||||
max: 1.0,
|
||||
beginAtZero: true,
|
||||
callback: function(value, index, values) {
|
||||
return Math.round(value*100) + " %";
|
||||
}
|
||||
},
|
||||
stacked: true
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: true
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateForwardedOverTime();
|
||||
}
|
||||
|
||||
// Create / load "Top Clients over Time" only if authorized
|
||||
if(document.getElementById("clientsChart"))
|
||||
{
|
||||
ctx = document.getElementById("clientsChart").getContext("2d");
|
||||
clientsChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
mode: "x-axis",
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
var label = tooltipItem[0].xLabel;
|
||||
var time = label.match(/(\d?\d):?(\d?\d?)/);
|
||||
var h = parseInt(time[1], 10);
|
||||
var m = parseInt(time[2], 10) || 0;
|
||||
var from = padNumber(h)+":"+padNumber(m-5)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+4)+":59";
|
||||
return "Client activity from "+from+" to "+to;
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -541,186 +826,202 @@ $(document).ready(function() {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
stacked: true
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: false
|
||||
maintainAspectRatio: true
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateClientsOverTime();
|
||||
}
|
||||
|
||||
updateQueriesOverTime();
|
||||
|
||||
// Create / load "Forward Destinations over Time" only if authorized
|
||||
if(document.getElementById("forwardDestinationChart"))
|
||||
{
|
||||
ctx = document.getElementById("forwardDestinationChart").getContext("2d");
|
||||
forwardDestinationChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
mode: "x-axis",
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
var label = tooltipItem[0].xLabel;
|
||||
var time = label.match(/(\d?\d):?(\d?\d?)/);
|
||||
var h = parseInt(time[1], 10);
|
||||
var m = parseInt(time[2], 10) || 0;
|
||||
var from = padNumber(h)+":"+padNumber(m-5)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+4)+":59";
|
||||
return "Forward destinations from "+from+" to "+to;
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
|
||||
}
|
||||
}
|
||||
// Create / load "Query Types over Time" only if authorized
|
||||
if(document.getElementById("queryTypeChart"))
|
||||
{
|
||||
ctx = document.getElementById("queryTypeChart").getContext("2d");
|
||||
queryTypeChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: "A: IPv4 queries",
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
cubicInterpolationMode: "monotone"
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "hour",
|
||||
displayFormats: {
|
||||
hour: "HH:mm"
|
||||
},
|
||||
tooltipFormat: "HH:mm"
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
mix: 0.0,
|
||||
max: 1.0,
|
||||
beginAtZero: true,
|
||||
callback: function(value, index, values) {
|
||||
return Math.round(value*100) + " %";
|
||||
}
|
||||
},
|
||||
stacked: true
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: true
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateForwardedOverTime();
|
||||
}
|
||||
|
||||
// Create / load "Query Types over Time" only if authorized
|
||||
if(document.getElementById("queryTypeChart"))
|
||||
{
|
||||
ctx = document.getElementById("queryTypeChart").getContext("2d");
|
||||
queryTypeChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: "A: IPv4 queries",
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
data: []
|
||||
{
|
||||
label: "AAAA: IPv6 queries",
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
data: [],
|
||||
cubicInterpolationMode: "monotone"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
mode: "x-axis",
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
var label = tooltipItem[0].xLabel;
|
||||
var time = label.match(/(\d?\d):?(\d?\d?)/);
|
||||
var h = parseInt(time[1], 10);
|
||||
var m = parseInt(time[2], 10) || 0;
|
||||
var from = padNumber(h)+":"+padNumber(m-5)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+4)+":59";
|
||||
return "Query types from "+from+" to "+to;
|
||||
},
|
||||
{
|
||||
label: "AAAA: IPv6 queries",
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
data: []
|
||||
label: function(tooltipItems, data) {
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
options: {
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
mode: "x-axis",
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
var label = tooltipItem[0].xLabel;
|
||||
var time = label.match(/(\d?\d):?(\d?\d?)/);
|
||||
var h = parseInt(time[1], 10);
|
||||
var m = parseInt(time[2], 10) || 0;
|
||||
var from = padNumber(h)+":"+padNumber(m-5)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+4)+":59";
|
||||
return "Query types from "+from+" to "+to;
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "hour",
|
||||
displayFormats: {
|
||||
hour: "HH:mm"
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
|
||||
}
|
||||
tooltipFormat: "HH:mm"
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "hour",
|
||||
displayFormats: {
|
||||
hour: "HH:mm"
|
||||
},
|
||||
tooltipFormat: "HH:mm"
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
mix: 0.0,
|
||||
max: 1.0,
|
||||
beginAtZero: true,
|
||||
callback: function(value, index, values) {
|
||||
return Math.round(value*100) + " %";
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
mix: 0.0,
|
||||
max: 1.0,
|
||||
beginAtZero: true,
|
||||
callback: function(value, index, values) {
|
||||
return Math.round(value*100) + " %";
|
||||
}
|
||||
},
|
||||
stacked: true
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: true
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateQueryTypesOverTime();
|
||||
}
|
||||
|
||||
// Create / load "Top Domains" and "Top Advertisers" only if authorized
|
||||
if(document.getElementById("domain-frequency")
|
||||
&& document.getElementById("ad-frequency"))
|
||||
{
|
||||
updateTopLists();
|
||||
}
|
||||
|
||||
// Create / load "Top Clients" only if authorized
|
||||
if(document.getElementById("client-frequency"))
|
||||
{
|
||||
updateTopClientsChart();
|
||||
}
|
||||
|
||||
$("#queryOverTimeChart").click(function(evt){
|
||||
var activePoints = timeLineChart.getElementAtEvent(evt);
|
||||
if(activePoints.length > 0)
|
||||
{
|
||||
//get the internal index of slice in pie chart
|
||||
var clickedElementindex = activePoints[0]["_index"];
|
||||
|
||||
//get specific label by index
|
||||
var label = timeLineChart.data.labels[clickedElementindex];
|
||||
|
||||
//get value by index
|
||||
var from = label/1000 - 300;
|
||||
var until = label/1000 + 300;
|
||||
window.location.href = "queries.php?from="+from+"&until="+until;
|
||||
},
|
||||
stacked: true
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: true
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateQueryTypesOverTime();
|
||||
}
|
||||
|
||||
// Create / load "Top Domains" and "Top Advertisers" only if authorized
|
||||
if(document.getElementById("domain-frequency")
|
||||
&& document.getElementById("ad-frequency"))
|
||||
{
|
||||
updateTopLists();
|
||||
}
|
||||
|
||||
// Create / load "Top Clients" only if authorized
|
||||
if(document.getElementById("client-frequency"))
|
||||
{
|
||||
updateTopClientsChart();
|
||||
}
|
||||
|
||||
$("#queryOverTimeChart").click(function(evt){
|
||||
var activePoints = timeLineChart.getElementAtEvent(evt);
|
||||
if(activePoints.length > 0)
|
||||
{
|
||||
//get the internal index of slice in pie chart
|
||||
var clickedElementindex = activePoints[0]["_index"];
|
||||
|
||||
//get specific label by index
|
||||
var label = timeLineChart.data.labels[clickedElementindex];
|
||||
|
||||
//get value by index
|
||||
var from = label/1000 - 300;
|
||||
var until = label/1000 + 300;
|
||||
window.location.href = "queries.php?from="+from+"&until="+until;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if(document.getElementById("queryTypePieChart"))
|
||||
{
|
||||
ctx = document.getElementById("queryTypePieChart").getContext("2d");
|
||||
queryTypePieChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: "right"
|
||||
},
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
return "Query types";
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
var dataset = data.datasets[tooltipItems.datasetIndex];
|
||||
var label = data.labels[tooltipItems.index];
|
||||
return label + ": " + dataset.data[tooltipItems.index].toFixed(1) + "%";
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 750
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateQueryTypesPie();
|
||||
}
|
||||
|
||||
if(document.getElementById("forwardDestinationPieChart"))
|
||||
{
|
||||
ctx = document.getElementById("forwardDestinationPieChart").getContext("2d");
|
||||
forwardDestinationPieChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: "right"
|
||||
},
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
callbacks: {
|
||||
title: function(tooltipItem, data) {
|
||||
return "Forward destinations";
|
||||
},
|
||||
label: function(tooltipItems, data) {
|
||||
var dataset = data.datasets[tooltipItems.datasetIndex];
|
||||
var label = data.labels[tooltipItems.index];
|
||||
return label + ": " + dataset.data[tooltipItems.index].toFixed(1) + "%";
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 750
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateForwardDestinationsPie();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -141,10 +141,9 @@ function add(arg) {
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/add.php",
|
||||
method: "post",
|
||||
data: {"domain":domain.val(), "list":locallistType, "token":token},
|
||||
data: {"domain":domain.val().trim(), "list":locallistType, "token":token},
|
||||
success: function(response) {
|
||||
if (response.indexOf("not a valid argument") >= 0 ||
|
||||
response.indexOf("is not a valid domain") >= 0) {
|
||||
if (response.indexOf("] Pi-hole blocking is ") === -1) {
|
||||
alFailure.show();
|
||||
err.html(response);
|
||||
alFailure.delay(4000).fadeOut(2000, function() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
var tableApi;
|
||||
|
||||
function escapeRegex(text) {
|
||||
@@ -175,8 +175,8 @@ $(document).ready(function() {
|
||||
"columns": [
|
||||
{ "width" : "20%", "render": function (data, type, full, meta) { if(type === "display"){return moment.unix(data).format("Y-MM-DD HH:mm:ss z");}else{return data;} }},
|
||||
{ "width" : "10%" },
|
||||
{ "width" : "40%" },
|
||||
{ "width" : "10%" },
|
||||
{ "width" : "40%", "render": $.fn.dataTable.render.text() },
|
||||
{ "width" : "10%", "render": $.fn.dataTable.render.text() },
|
||||
{ "width" : "10%" },
|
||||
{ "width" : "10%" },
|
||||
],
|
||||
@@ -185,8 +185,33 @@ $(document).ready(function() {
|
||||
"targets": -1,
|
||||
"data": null,
|
||||
"defaultContent": ""
|
||||
} ]
|
||||
} ],
|
||||
"initComplete": function () {
|
||||
var api = this.api();
|
||||
// Query type IPv4 / IPv6
|
||||
api.$("td:eq(1)").click( function () { api.search( this.innerHTML ).draw(); $("#resetButton").show(); } );
|
||||
api.$("td:eq(1)").hover(
|
||||
function () { this.title="Click to show only "+this.innerHTML+" queries"; this.style.color="#72afd2"; },
|
||||
function () { this.style.color=""; }
|
||||
);
|
||||
api.$("td:eq(1)").css("cursor","pointer");
|
||||
// Domain
|
||||
api.$("td:eq(2)").click( function () { api.search( this.innerHTML ).draw(); $("#resetButton").show(); } );
|
||||
api.$("td:eq(2)").hover(
|
||||
function () { this.title="Click to show only queries with domain "+this.innerHTML; this.style.color="#72afd2"; },
|
||||
function () { this.style.color=""; }
|
||||
);
|
||||
api.$("td:eq(2)").css("cursor","pointer");
|
||||
// Client
|
||||
api.$("td:eq(3)").click( function () { api.search( this.innerHTML ).draw(); $("#resetButton").show(); } );
|
||||
api.$("td:eq(3)").hover(
|
||||
function () { this.title="Click to show only queries made by "+this.innerHTML; this.style.color="#72afd2"; },
|
||||
function () { this.style.color=""; }
|
||||
);
|
||||
api.$("td:eq(3)").css("cursor","pointer");
|
||||
}
|
||||
});
|
||||
|
||||
$("#all-queries tbody").on( "click", "button", function () {
|
||||
var data = tableApi.row( $(this).parents("tr") ).data();
|
||||
if (data[4] === "1" || data[4] === "5")
|
||||
@@ -198,6 +223,8 @@ $(document).ready(function() {
|
||||
add(data[2],"black");
|
||||
}
|
||||
} );
|
||||
|
||||
$("#resetButton").click( function () { tableApi.search("").draw(); $("#resetButton").hide(); } );
|
||||
} );
|
||||
|
||||
|
||||
|
||||
@@ -57,9 +57,10 @@ function httpGet(ta,quiet,theUrl)
|
||||
|
||||
function eventsource() {
|
||||
var ta = $("#output");
|
||||
var domain = $("#domain");
|
||||
var domain = $("#domain").val().trim();
|
||||
var q = $("#quiet");
|
||||
if(domain.val().length === 0)
|
||||
|
||||
if(domain.length === 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -73,12 +74,12 @@ function eventsource() {
|
||||
|
||||
// IE does not support EventSource - load whole content at once
|
||||
if (typeof EventSource !== "function") {
|
||||
httpGet(ta,quiet,"/admin/scripts/pi-hole/php/queryads.php?domain="+domain.val().toLowerCase()+exact+"&IE");
|
||||
httpGet(ta,quiet,"/admin/scripts/pi-hole/php/queryads.php?domain="+domain.toLowerCase()+exact+"&IE");
|
||||
return;
|
||||
}
|
||||
|
||||
var host = window.location.host;
|
||||
var source = new EventSource("/admin/scripts/pi-hole/php/queryads.php?domain="+domain.val().toLowerCase()+"&"+exact);
|
||||
var source = new EventSource("/admin/scripts/pi-hole/php/queryads.php?domain="+domain.toLowerCase()+"&"+exact);
|
||||
|
||||
// Reset and show field
|
||||
ta.empty();
|
||||
|
||||
@@ -17,6 +17,22 @@ $(function () {
|
||||
$("input[name=\"AddMAC\"]").val(mac);
|
||||
});
|
||||
});
|
||||
$(".confirm-poweroff").confirm({
|
||||
text: "Are you sure you want to send a poweroff command to your Pi-Hole?",
|
||||
title: "Confirmation required",
|
||||
confirm(button) {
|
||||
$("#poweroffform").submit();
|
||||
},
|
||||
cancel(button) {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, poweroff",
|
||||
cancelButton: "No, go back",
|
||||
post: true,
|
||||
confirmButtonClass: "btn-danger",
|
||||
cancelButtonClass: "btn-success",
|
||||
dialogClass: "modal-dialog modal-mg" // Bootstrap classes for mid-size modal
|
||||
});
|
||||
$(".confirm-reboot").confirm({
|
||||
text: "Are you sure you want to send a reboot command to your Pi-Hole?",
|
||||
title: "Confirmation required",
|
||||
@@ -68,6 +84,23 @@ $(".confirm-flushlogs").confirm({
|
||||
dialogClass: "modal-dialog modal-mg"
|
||||
});
|
||||
|
||||
$(".confirm-disablelogging").confirm({
|
||||
text: "Note that disabling query logging will render graphs on the web user interface useless. Are you sure you want to disable your logging?",
|
||||
title: "Confirmation required",
|
||||
confirm(button) {
|
||||
$("#disablelogsform").submit();
|
||||
},
|
||||
cancel(button) {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, disable logs",
|
||||
cancelButton: "No, go back",
|
||||
post: true,
|
||||
confirmButtonClass: "btn-danger",
|
||||
cancelButtonClass: "btn-success",
|
||||
dialogClass: "modal-dialog modal-mg"
|
||||
});
|
||||
|
||||
$(".api-token").confirm({
|
||||
text: "Make sure that nobody else can scan this code around you. They will have full access to the API without having to know the password. Note that the generation of the QR code will take some time.",
|
||||
title: "Confirmation required",
|
||||
@@ -102,9 +135,6 @@ $(document).ready(function() {
|
||||
"scrollY": "200px",
|
||||
"scrollX" : true
|
||||
});
|
||||
$("#leaseexpand").on( "click", function () {
|
||||
setTimeout(function(){leasetable.draw();},100);
|
||||
} );
|
||||
}
|
||||
if(document.getElementById("DHCPStaticLeasesTable"))
|
||||
{
|
||||
@@ -116,10 +146,13 @@ $(document).ready(function() {
|
||||
"scrollY": "200px",
|
||||
"scrollX" : true
|
||||
});
|
||||
$("#leaseexpand").on( "click", function () {
|
||||
setTimeout(function(){staticleasetable.draw();},100);
|
||||
} );
|
||||
}
|
||||
//call draw() on each table... they don't render properly with scrollX and scrollY set... ¯\_(ツ)_/¯
|
||||
$("a[data-toggle=\"tab\"]").on("shown.bs.tab", function (e) {
|
||||
leasetable.draw();
|
||||
staticleasetable.draw();
|
||||
});
|
||||
|
||||
} );
|
||||
|
||||
// Handle hiding of alerts
|
||||
@@ -136,12 +169,39 @@ $(document).ready(function(){
|
||||
|
||||
// Handle list deletion
|
||||
$("button[id^='adlist-btn-']").on("click", function (e) {
|
||||
var id = parseInt($(this).context.id.replace(/[^0-9\.]/g, ""), 10);
|
||||
e.preventDefault();
|
||||
|
||||
var status = $(this).siblings("input[name^='adlist-del-']").is(":checked");
|
||||
var status = $("input[name=\"adlist-del-"+id+"\"]").is(":checked");
|
||||
var textType = status ? "none" : "line-through";
|
||||
|
||||
$(this).siblings("input[name^='adlist-del-']").prop("checked", !status);
|
||||
$(this).siblings("input[name^='adlist-enable-']").prop("disabled", !status);
|
||||
$(this).siblings("a").css("text-decoration", textType);
|
||||
// Check hidden delete box (or reset)
|
||||
$("input[name=\"adlist-del-"+id+"\"]").prop("checked", !status);
|
||||
// Untick and disable check box (or reset)
|
||||
$("input[name=\"adlist-enable-"+id+"\"]").prop("checked", status).prop("disabled", !status);
|
||||
// Strink through text (or reset)
|
||||
$("a[id=\"adlist-text-"+id+"\"]").css("text-decoration", textType);
|
||||
// Highlight that the button has to be clicked in order to make the change live
|
||||
$("button[id=\"blockinglistsaveupdate\"]").addClass("btn-danger").css("font-weight", "bold");
|
||||
|
||||
});
|
||||
|
||||
// Change "?tab=" parameter in URL for save and reload
|
||||
$(".nav-tabs a").on("shown.bs.tab", function (e) {
|
||||
window.history.pushState("", "", "?tab=" + e.target.hash.substring(1));
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
// Auto dismissal for info and error notifications
|
||||
$(document).ready(function(){
|
||||
var alInfo = $("#alInfo");
|
||||
var alError = $("#alError");
|
||||
if(alInfo.length)
|
||||
{
|
||||
alInfo.delay(3000).fadeOut(2000, function() { alInfo.hide(); });
|
||||
}
|
||||
if(alError.length)
|
||||
{
|
||||
alError.delay(3000).fadeOut(2000, function() { alError.hide(); });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,10 +6,19 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
function testFTL()
|
||||
function testFTL($address)
|
||||
{
|
||||
$ret = shell_exec("pidof pihole-FTL");
|
||||
return intval($ret);
|
||||
if($address === "127.0.0.1")
|
||||
{
|
||||
$ret = shell_exec("pidof pihole-FTL");
|
||||
return intval($ret);
|
||||
}
|
||||
// We cannot relly test for a distant FTL instance
|
||||
// in the same way, so for any other IP address
|
||||
// we simply return true here and rely on the API
|
||||
// socket connection itself to fail if there is nothing
|
||||
// on that address
|
||||
return true;
|
||||
}
|
||||
|
||||
function connectFTL($address, $port=4711, $quiet=true)
|
||||
|
||||
@@ -16,13 +16,33 @@ list_verify($type);
|
||||
|
||||
switch($type) {
|
||||
case "white":
|
||||
echo exec("sudo pihole -w -q ${_POST['domain']}");
|
||||
if(!isset($_POST["auditlog"]))
|
||||
echo exec("sudo pihole -w -q ${_POST['domain']}");
|
||||
else
|
||||
{
|
||||
echo exec("sudo pihole -w -q -n ${_POST['domain']}");
|
||||
echo exec("sudo pihole -a audit ${_POST['domain']}");
|
||||
}
|
||||
break;
|
||||
case "black":
|
||||
echo exec("sudo pihole -b -q ${_POST['domain']}");
|
||||
if(!isset($_POST["auditlog"]))
|
||||
echo exec("sudo pihole -b -q ${_POST['domain']}");
|
||||
else
|
||||
{
|
||||
echo exec("sudo pihole -b -q -n ${_POST['domain']}");
|
||||
echo exec("sudo pihole -a audit ${_POST['domain']}");
|
||||
}
|
||||
break;
|
||||
case "wild":
|
||||
echo exec("sudo pihole -wild -q ${_POST['domain']}");
|
||||
if(!isset($_POST["auditlog"]))
|
||||
echo exec("sudo pihole -wild -q ${_POST['domain']}");
|
||||
else
|
||||
{
|
||||
echo exec("sudo pihole -wild -q -n ${_POST['domain']}");
|
||||
echo exec("sudo pihole -a audit ${_POST['domain']}");
|
||||
}
|
||||
case "audit":
|
||||
echo exec("sudo pihole -a audit ${_POST['domain']}");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ function check_csrf($token) {
|
||||
|
||||
function check_domain() {
|
||||
if(isset($_POST['domain'])){
|
||||
$domains = explode(" ",$_POST['domain']);
|
||||
$domains = preg_split('\s+', $_POST['domain']);
|
||||
foreach($domains as $domain)
|
||||
{
|
||||
$validDomain = is_valid_domain_name($domain);
|
||||
@@ -131,7 +131,7 @@ function check_domain() {
|
||||
}
|
||||
|
||||
function list_verify($type) {
|
||||
global $pwhash, $wrongpassword;
|
||||
global $pwhash, $wrongpassword, $auth;
|
||||
if(!isset($_POST['domain']) || !isset($_POST['list']) || !(isset($_POST['pw']) || isset($_POST['token']))) {
|
||||
log_and_die("Missing POST variables");
|
||||
}
|
||||
@@ -144,11 +144,7 @@ function list_verify($type) {
|
||||
elseif(isset($_POST['pw']))
|
||||
{
|
||||
require("password.php");
|
||||
if(strlen($pwhash) == 0)
|
||||
{
|
||||
log_and_die("No password set - ".htmlspecialchars($type)."listing with password not supported");
|
||||
}
|
||||
elseif($wrongpassword)
|
||||
if($wrongpassword || !$auth)
|
||||
{
|
||||
log_and_die("Wrong password - ".htmlspecialchars($type)."listing of ${_POST['domain']} not permitted");
|
||||
}
|
||||
|
||||
@@ -11,17 +11,16 @@
|
||||
</div>
|
||||
<!-- Modal for custom disable time -->
|
||||
<div class="modal fade" id="customDisableModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-dialog modal-sm" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">Custom disable timeout</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><input id="customTimeout" class="form-control" type="number" value="60"></div>
|
||||
<div class="col-sm-9">
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<div class="input-group">
|
||||
<input id="customTimeout" class="form-control" type="number" value="60">
|
||||
<div class="input-group-btn" data-toggle="buttons">
|
||||
<label class="btn btn-default">
|
||||
<input type="radio"/> Secs
|
||||
</label>
|
||||
@@ -29,7 +28,6 @@
|
||||
<input type="radio" /> Mins
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -39,6 +37,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
// Flushes the system write buffers of PHP. This attempts to push everything we have so far all the way to the client's browser.
|
||||
flush();
|
||||
// Run update checker
|
||||
// - determines local branch each time,
|
||||
// - determines local and remote version every 30 minutes
|
||||
require "scripts/pi-hole/php/update_checker.php";
|
||||
?>
|
||||
<!-- /.content-wrapper -->
|
||||
<footer class="main-footer">
|
||||
<!-- Version Infos -->
|
||||
@@ -55,7 +61,7 @@
|
||||
echo $FTL_current;
|
||||
if($FTL_update){ ?> <a class="alert-link lookatme" href="https://github.com/pi-hole/FTL/releases" target="_blank">(Update available!)</a><?php } ?>
|
||||
</div>
|
||||
<div style="display: inline-block"><a href="https://github.com/pi-hole" target="_blank"><i class="fa fa-github"></i></a> <strong><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY" target="_blank">Donate</a></strong> if you found this useful.</div>
|
||||
<div style="display: inline-block"><strong><a href="https://pi-hole.net/donate" target="_blank"><i class="fa fa-heart"></i> Donate</a></strong> if you found this useful.</div>
|
||||
</footer>
|
||||
</div>
|
||||
<!-- ./wrapper -->
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
function is_valid_domain_name($domain_name)
|
||||
{
|
||||
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) && //valid chars check
|
||||
preg_match("/^.{1,253}$/", $domain_name) && //overall length check
|
||||
preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); //length of each label
|
||||
return (preg_match("/^((-|_)*[a-z\d]((-|_)*[a-z\d])*(-|_)*)(\.(-|_)*([a-z\d]((-|_)*[a-z\d])*))*$/i", $domain_name) && // Valid chars check
|
||||
preg_match("/^.{1,253}$/", $domain_name) && // Overall length check
|
||||
preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); // Length of each label
|
||||
}
|
||||
|
||||
function checkfile($filename) {
|
||||
|
||||
@@ -16,15 +16,22 @@ header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
|
||||
function echoEvent($datatext) {
|
||||
echo "data: ".implode("\ndata: ", explode("\n", $datatext))."\n\n";
|
||||
// Detect ${OVER} and replace it with something we can safely transmit
|
||||
$datatext = str_replace("\r[K","<------",$datatext);
|
||||
$pos = strpos($datatext,"<------");
|
||||
// Detect if the ${OVER} line is within this line, e.g.
|
||||
// "Pending: String to replace${OVER}Done: String has been replaced"
|
||||
// If this is the case, we have to remove everything before ${OVER}
|
||||
// and return only the text thereafter
|
||||
if($pos !== false && $pos !== 0)
|
||||
{
|
||||
$datatext = substr($datatext, $pos);
|
||||
}
|
||||
echo "data: ".implode("\ndata: ", explode("\n",$datatext))."\n\n";
|
||||
}
|
||||
|
||||
// echoEvent("***START***");
|
||||
|
||||
$proc = popen("sudo pihole -g", 'r');
|
||||
while (!feof($proc)) {
|
||||
echoEvent(fread($proc, 4096));
|
||||
}
|
||||
|
||||
// echoEvent("***END***");
|
||||
?>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<?php
|
||||
require "scripts/pi-hole/php/auth.php";
|
||||
require "scripts/pi-hole/php/password.php";
|
||||
require "scripts/pi-hole/php/update_checker.php";
|
||||
|
||||
check_cors();
|
||||
|
||||
@@ -184,6 +183,7 @@
|
||||
<link href="style/vendor/font-awesome-4.5.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="style/vendor/ionicons-2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="style/vendor/dataTables.bootstrap.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="style/vendor/daterangepicker.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="style/vendor/AdminLTE.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="style/vendor/skin-blue.min.css" rel="stylesheet" type="text/css" />
|
||||
@@ -191,7 +191,7 @@
|
||||
<link rel="icon" type="image/png" sizes="160x160" href="img/logo.svg" />
|
||||
<style type="text/css">
|
||||
.glow { text-shadow: 0px 0px 5px #fff; }
|
||||
h3 { transition-duration: 500ms }
|
||||
.small-box span { transition-duration: 500ms }
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
@@ -234,7 +234,7 @@ if($auth) {
|
||||
</a>
|
||||
<div class="navbar-custom-menu">
|
||||
<ul class="nav navbar-nav">
|
||||
<!-- User Account: style can be found in dropdown.less -->
|
||||
<li><a style="pointer-events:none;"><samp><?php echo gethostname(); ?></samp></a></li>
|
||||
<li class="dropdown user user-menu">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true">
|
||||
<img src="img/logo.svg" class="user-image" style="border-radius: initial" sizes="160x160" alt="Pi-hole logo" />
|
||||
@@ -252,10 +252,10 @@ if($auth) {
|
||||
<!-- Menu Body -->
|
||||
<li class="user-body">
|
||||
<div class="col-xs-4 text-center">
|
||||
<a class="btn-link" href="https://github.com/pi-hole/pi-hole" target="_blank">GitHub</a>
|
||||
<a class="btn-link" href="https://github.com/pi-hole" target="_blank">GitHub</a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center">
|
||||
<a class="btn-link" href="http://jacobsalmela.com/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/" target="_blank">Details</a>
|
||||
<a class="btn-link" href="https://pi-hole.net" target="_blank">Website</a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center">
|
||||
<a class="btn-link" href="https://github.com/pi-hole/pi-hole/releases" target="_blank">Updates</a>
|
||||
@@ -267,6 +267,7 @@ if($auth) {
|
||||
<!-- Menu Footer -->
|
||||
<li class="user-footer">
|
||||
<!-- Version Infos -->
|
||||
<?php /*
|
||||
<div class="<?php if(!isset($core_commit) && !isset($web_commit)) { ?>hidden-md <?php } ?>hidden-lg">
|
||||
<b>Pi-hole Version </b> <?php
|
||||
echo $core_current;
|
||||
@@ -280,6 +281,7 @@ if($auth) {
|
||||
echo $FTL_current;
|
||||
if($FTL_update){ ?> <a class="alert-link lookatme btn-link" href="https://github.com/pi-hole/FTL/releases" target="_blank" style="background:none">(Update available!)</a><?php } ?><br><br>
|
||||
</div>
|
||||
*/ ?>
|
||||
<!-- PayPal -->
|
||||
<div class="text-center">
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY" target="_blank" style="background:none">
|
||||
@@ -351,7 +353,7 @@ if($auth) {
|
||||
?>
|
||||
<br/>
|
||||
<?php
|
||||
echo "<a><i class=\"fa fa-circle\" style=\"color:";
|
||||
echo "<a title=\"Detected $nproc cores\"><i class=\"fa fa-circle\" style=\"color:";
|
||||
if ($loaddata[0] > $nproc) {
|
||||
echo "#FF0000";
|
||||
}
|
||||
@@ -359,7 +361,7 @@ if($auth) {
|
||||
{
|
||||
echo "#7FFF00";
|
||||
}
|
||||
echo "\" title=\"Detected $nproc cores\"></i> Load: " . $loaddata[0] . " " . $loaddata[1] . " ". $loaddata[2] . "</a>";
|
||||
echo "\"></i> Load: " . $loaddata[0] . " " . $loaddata[1] . " ". $loaddata[2] . "</a>";
|
||||
?>
|
||||
<br/>
|
||||
<?php
|
||||
@@ -416,6 +418,31 @@ if($auth) {
|
||||
<i class="fa fa-file-text-o"></i> <span>Query Log</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="treeview<?php if($scriptname === "db_queries.php" || $scriptname === "db_lists.php" || $scriptname === "db_graph.php"){ ?> active<?php } ?>">
|
||||
<a href="#">
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-down pull-right" style="padding-right: 5px;"></i>
|
||||
</span>
|
||||
<i class="fa fa-clock-o"></i> <span>Long term data</span>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<li<?php if($scriptname === "db_graph.php"){ ?> class="active"<?php } ?>>
|
||||
<a href="db_graph.php">
|
||||
<i class="fa fa-file-text-o"></i> <span>Graphics</span>
|
||||
</a>
|
||||
</li>
|
||||
<li<?php if($scriptname === "db_queries.php"){ ?> class="active"<?php } ?>>
|
||||
<a href="db_queries.php">
|
||||
<i class="fa fa-file-text-o"></i> <span>Query Log</span>
|
||||
</a>
|
||||
</li>
|
||||
<li<?php if($scriptname === "db_lists.php"){ ?> class="active"<?php } ?>>
|
||||
<a href="db_lists.php">
|
||||
<i class="fa fa-file-text-o"></i> <span>Top Lists</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<!-- Whitelist -->
|
||||
<li<?php if($scriptname === "whitelist"){ ?> class="active"<?php } ?>>
|
||||
<a href="list.php?l=white">
|
||||
@@ -432,10 +459,10 @@ if($auth) {
|
||||
|
||||
<li id="pihole-disable" class="treeview"<?php if ($pistatus == "0") { ?> hidden="true"<?php } ?>>
|
||||
<a href="#">
|
||||
<i class="fa fa-stop"></i> <span>Disable</span> <span id="flip-status-disable"></span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-down pull-right" style="padding-right: 5px;"></i>
|
||||
</span>
|
||||
<i class="fa fa-stop"></i> <span>Disable</span> <span id="flip-status-disable"></span>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<li>
|
||||
@@ -470,12 +497,12 @@ if($auth) {
|
||||
<a href="#"><i class="fa fa-play"></i> <span id="enableLabel">Enable</span> <span id="flip-status-enable"></span></a>
|
||||
</li>
|
||||
<!-- Tools -->
|
||||
<li class="treeview <?php if($scriptname === "gravity.php" || $scriptname === "queryads.php" || $scriptname === "debug.php"){ ?>active<?php } ?>">
|
||||
<li class="treeview <?php if(in_array($scriptname, array("gravity.php", "queryads.php", "auditlog.php", "taillog.php", "taillog-FTL.php", "debug.php"))){ ?>active<?php } ?>">
|
||||
<a href="#">
|
||||
<i class="fa fa-folder"></i> <span>Tools</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-down pull-right" style="padding-right: 5px;"></i>
|
||||
</span>
|
||||
<i class="fa fa-folder"></i> <span>Tools</span>
|
||||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<!-- Run gravity.sh -->
|
||||
@@ -490,6 +517,12 @@ if($auth) {
|
||||
<i class="fa fa-search"></i> <span>Query adlists</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Audit log -->
|
||||
<li<?php if($scriptname === "auditlog.php"){ ?> class="active"<?php } ?>>
|
||||
<a href="auditlog.php">
|
||||
<i class="fa fa-balance-scale"></i> <span>Audit log</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Tail pihole.log -->
|
||||
<li<?php if($scriptname === "taillog.php"){ ?> class="active"<?php } ?>>
|
||||
<a href="taillog.php">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<div class="panel-title text-center"><span class="logo-lg" style="font-size: 25px;">Pi-<b>hole</b></span></div>
|
||||
<p class="login-box-msg">Sign in to start your session</p>
|
||||
<div id="cookieInfo" class="panel-title text-center" style="color:#F00; font-size: 150%" hidden>Verify that cookies are allowed for <tt><?php echo $_SERVER['HTTP_HOST']; ?></tt></div>
|
||||
<div id="cookieInfo" class="panel-title text-center" style="color:#F00; font-size: 150%" hidden>Verify that cookies are allowed for <samp><?php echo $_SERVER['HTTP_HOST']; ?></samp></div>
|
||||
<?php if ($wrongpassword) { ?>
|
||||
<div class="form-group has-error login-box-msg">
|
||||
<label class="control-label"><i class="fa fa-times-circle-o"></i> Wrong password!</label>
|
||||
@@ -30,8 +30,8 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<ul>
|
||||
<li><tt>Return</tt> → Log in and go to requested page (<?php echo $scriptname; ?>)</li>
|
||||
<li><tt>Ctrl+Return</tt> → Log in and go to Settings page</li>
|
||||
<li><samp>Return</samp> → Log in and go to requested page (<?php echo $scriptname; ?>)</li>
|
||||
<li><samp>Ctrl+Return</samp> → Log in and go to Settings page</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
if($postinput == $pwhash)
|
||||
{
|
||||
$_SESSION["hash"] = $pwhash;
|
||||
|
||||
// Login successful, redirect the user to the homepage to discard the POST request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['QUERY_STRING'] === 'login') {
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$auth = true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -20,11 +20,12 @@ function echoEvent($datatext) {
|
||||
}
|
||||
|
||||
// Credit: http://stackoverflow.com/a/4694816/2087442
|
||||
ini_set("pcre.recursion_limit", 1500);
|
||||
function is_valid_domain_name($domain_name)
|
||||
{
|
||||
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) //valid chars check
|
||||
&& preg_match("/^.{1,253}$/", $domain_name) //overall length check
|
||||
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name) ); //length of each label
|
||||
return (preg_match("/^((-|_)*[a-z\d]((-|_)*[a-z\d])*(-|_)*)(\.(-|_)*([a-z\d]((-|_)*[a-z\d])*))*$/i", $domain_name) // Valid chars check
|
||||
&& preg_match("/^.{1,253}$/", $domain_name) // Overall length check
|
||||
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name) ); // Length of each label
|
||||
}
|
||||
|
||||
// Test if domain is set
|
||||
@@ -48,6 +49,10 @@ if(isset($_GET["exact"]))
|
||||
{
|
||||
$exact = "-exact";
|
||||
}
|
||||
elseif(isset($_GET["bp"]))
|
||||
{
|
||||
$exact = "-bp";
|
||||
}
|
||||
else
|
||||
{
|
||||
$exact = "";
|
||||
|
||||
@@ -46,7 +46,7 @@ function validDomain($domain_name)
|
||||
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);
|
||||
$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
|
||||
@@ -420,6 +420,11 @@ function readAdlists()
|
||||
$success .= "The webUI settings have been updated";
|
||||
break;
|
||||
|
||||
case "poweroff":
|
||||
exec("sudo pihole -a poweroff");
|
||||
$success = "The system will poweroff in 5 seconds...";
|
||||
break;
|
||||
|
||||
case "reboot":
|
||||
exec("sudo pihole -a reboot");
|
||||
$success = "The system will reboot in 5 seconds...";
|
||||
|
||||
@@ -14,6 +14,28 @@ if (php_sapi_name() !== "cli") {
|
||||
check_csrf(isset($_POST["token"]) ? $_POST["token"] : "");
|
||||
}
|
||||
|
||||
function archive_add_file($path,$name)
|
||||
{
|
||||
global $archive;
|
||||
if(file_exists($path.$name))
|
||||
$archive[$name] = file_get_contents($path.$name);
|
||||
}
|
||||
|
||||
function archive_add_directory($path)
|
||||
{
|
||||
if($dir = opendir($path))
|
||||
{
|
||||
while(false !== ($entry = readdir($dir)))
|
||||
{
|
||||
if($entry !== "." && $entry !== "..")
|
||||
{
|
||||
archive_add_file($path,$entry);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
function limit_length(&$item, $key)
|
||||
{
|
||||
// limit max length for a domain entry to 253 chars
|
||||
@@ -21,20 +43,8 @@ function limit_length(&$item, $key)
|
||||
$item = substr($item, 0, 253);
|
||||
}
|
||||
|
||||
function process_zip($name)
|
||||
function process_file($contents)
|
||||
{
|
||||
global $zip;
|
||||
$zippointer = $zip->getStream($name);
|
||||
if(!$zippointer)
|
||||
{
|
||||
echo "$name not found in provided ZIP file, skipping...<br>";
|
||||
return;
|
||||
}
|
||||
$contents = "";
|
||||
while (!feof($zippointer)) {
|
||||
$contents .= fread($zippointer, 4096);
|
||||
}
|
||||
fclose($zippointer);
|
||||
$domains = array_filter(explode("\n",$contents));
|
||||
|
||||
// Walk array and apply a max string length
|
||||
@@ -46,29 +56,6 @@ function process_zip($name)
|
||||
return $domains;
|
||||
}
|
||||
|
||||
function add_to_zip($path,$name)
|
||||
{
|
||||
global $zip;
|
||||
if(file_exists($path.$name))
|
||||
$zip->addFile($path.$name,$name);
|
||||
}
|
||||
|
||||
function add_dir_to_zip($path)
|
||||
{
|
||||
global $zip;
|
||||
if($dir = opendir($path))
|
||||
{
|
||||
while(false !== ($entry = readdir($dir)))
|
||||
{
|
||||
if($entry !== "." && $entry !== "..")
|
||||
{
|
||||
$zip->addFile($path.$entry,$entry);
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
function check_domains($domains)
|
||||
{
|
||||
foreach($domains as $domain)
|
||||
@@ -109,7 +96,7 @@ if(isset($_POST["action"]))
|
||||
$type = mime_content_type($source);
|
||||
|
||||
$name = explode(".", $filename);
|
||||
$accepted_types = array('application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed');
|
||||
$accepted_types = array('application/gzip', 'application/tar', 'application/x-compressed', 'application/x-gzip');
|
||||
$okay = false;
|
||||
foreach($accepted_types as $mime_type) {
|
||||
if($mime_type == $type) {
|
||||
@@ -118,40 +105,56 @@ if(isset($_POST["action"]))
|
||||
}
|
||||
}
|
||||
|
||||
$continue = strtolower($name[1]) == 'zip' ? true : false;
|
||||
$continue = strtolower($name[1]) == 'tar' && strtolower($name[2]) == 'gz' ? true : false;
|
||||
if(!$continue || !$okay) {
|
||||
die("The file you are trying to upload is not a .zip file (filename: ".$filename.", type: ".$type."). Please try again.");
|
||||
die("The file you are trying to upload is not a .tar.gz file (filename: ".htmlentities($filename).", type: ".htmlentities($type)."). Please try again.");
|
||||
}
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$x = $zip->open($source);
|
||||
if ($x === true) {
|
||||
if(isset($_POST["blacklist"]))
|
||||
{
|
||||
$blacklist = process_zip("blacklist.txt");
|
||||
exec("sudo pihole -b -q ".implode(" ", $blacklist));
|
||||
}
|
||||
|
||||
if(isset($_POST["whitelist"]))
|
||||
{
|
||||
$whitelist = process_zip("whitelist.txt");
|
||||
exec("sudo pihole -w -q ".implode(" ", $whitelist));
|
||||
}
|
||||
|
||||
if(isset($_POST["wildlist"]))
|
||||
{
|
||||
$wildlist = process_zip("wildcardblocking.txt");
|
||||
exec("sudo pihole -wild -q ".implode(" ", $wildlist));
|
||||
}
|
||||
|
||||
echo "OK";
|
||||
|
||||
$zip->close();
|
||||
}
|
||||
else
|
||||
$fullfilename = sys_get_temp_dir()."/".$filename;
|
||||
if(!move_uploaded_file($source, $fullfilename))
|
||||
{
|
||||
die("Error opening uploaded archive!");
|
||||
die("Failed moving ".htmlentities($source)." to ".htmlentities($fullfilename));
|
||||
}
|
||||
|
||||
$archive = new PharData($fullfilename);
|
||||
|
||||
$importedsomething = false;
|
||||
|
||||
foreach($archive as $file)
|
||||
{
|
||||
if(isset($_POST["blacklist"]) && $file->getFilename() === "blacklist.txt")
|
||||
{
|
||||
$blacklist = process_file(file_get_contents($file));
|
||||
echo "Processing blacklist.txt<br>\n";
|
||||
exec("sudo pihole -b -nr --nuke");
|
||||
exec("sudo pihole -b -q -nr ".implode(" ", $blacklist));
|
||||
$importedsomething = true;
|
||||
}
|
||||
if(isset($_POST["whitelist"]) && $file->getFilename() === "whitelist.txt")
|
||||
{
|
||||
$whitelist = process_file(file_get_contents($file));
|
||||
echo "Processing whitelist.txt<br>\n";
|
||||
exec("sudo pihole -w -nr --nuke");
|
||||
exec("sudo pihole -w -q -nr ".implode(" ", $whitelist));
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["wildlist"]) && $file->getFilename() === "wildcardblocking.txt")
|
||||
{
|
||||
$wildlist = process_file(file_get_contents($file));
|
||||
echo "Processing wildcardblocking.txt<br>\n";
|
||||
exec("sudo pihole -wild -nr --nuke");
|
||||
exec("sudo pihole -wild -q -nr ".implode(" ", $wildlist));
|
||||
$importedsomething = true;
|
||||
}
|
||||
if($importedsomething)
|
||||
{
|
||||
exec("sudo pihole restartdns");
|
||||
}
|
||||
}
|
||||
|
||||
unlink($fullfilename);
|
||||
echo "OK";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -160,24 +163,25 @@ if(isset($_POST["action"]))
|
||||
}
|
||||
else
|
||||
{
|
||||
$filename = "pi-hole-teleporter_".date("Y-m-d_h-i-s").".zip";
|
||||
$archive_file_name = tempnam("/tmp", "Teleporter");
|
||||
$zip = new ZipArchive();
|
||||
touch($archive_file_name);
|
||||
$res = $zip->open($archive_file_name, ZipArchive::CREATE | ZipArchive::OVERWRITE);
|
||||
$tarname = "pi-hole-teleporter_".date("Y-m-d_h-i-s").".tar";
|
||||
$filename = $tarname.".gz";
|
||||
$archive_file_name = sys_get_temp_dir() ."/". $tarname;
|
||||
$archive = new PharData($archive_file_name);
|
||||
|
||||
if ($res !== TRUE) {
|
||||
exit("cannot open/create $archive_file_name<br>Error: ".$zip->getStatusString()."<br>PHP user: ".exec('whoami')."\n");
|
||||
if ($archive->isWritable() !== TRUE) {
|
||||
exit("cannot open/create ".htmlentities($archive_file_name)."<br>\nPHP user: ".exec('whoami')."\n");
|
||||
}
|
||||
|
||||
add_to_zip("/etc/pihole/","whitelist.txt");
|
||||
add_to_zip("/etc/pihole/","blacklist.txt");
|
||||
add_to_zip("/etc/pihole/","adlists.list");
|
||||
add_to_zip("/etc/pihole/","setupVars.conf");
|
||||
add_dir_to_zip("/etc/dnsmasq.d/");
|
||||
archive_add_file("/etc/pihole/","whitelist.txt");
|
||||
archive_add_file("/etc/pihole/","blacklist.txt");
|
||||
archive_add_file("/etc/pihole/","adlists.list");
|
||||
archive_add_file("/etc/pihole/","setupVars.conf");
|
||||
archive_add_directory("/etc/dnsmasq.d/");
|
||||
|
||||
$zip->addFromString("wildcardblocking.txt", getWildcardListContent());
|
||||
$zip->close();
|
||||
$archive["wildcardblocking.txt"] = getWildcardListContent();
|
||||
$archive->compress(Phar::GZ); // Creates a gziped copy
|
||||
unlink($archive_file_name); // Unlink original tar file as it is not needed anymore
|
||||
$archive_file_name .= ".gz"; // Append ".gz" extension to ".tar"
|
||||
|
||||
header("Content-type: application/zip");
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
|
||||
@@ -1,118 +1,83 @@
|
||||
<?php
|
||||
// Allow file_get_contents to read remote sources via URL
|
||||
ini_set("allow_url_fopen", 1);
|
||||
|
||||
// Function that grabs latest tag from GitHub
|
||||
function get_github_version($url)
|
||||
$localversions = "/etc/pihole/localversions";
|
||||
$localbranches = "/etc/pihole/localbranches";
|
||||
$GitHubVersions = "/etc/pihole/GitHubVersions";
|
||||
|
||||
if(!is_readable($localversions) || !is_readable($localbranches) || !is_readable($GitHubVersions))
|
||||
{
|
||||
// Have to fake the user agent to be able to query from Github's API
|
||||
$context = stream_context_create(array("http" => array("user_agent" => "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36")));
|
||||
|
||||
$json = file_get_contents($url, false, $context);
|
||||
$result = json_decode($json);
|
||||
|
||||
return $result->tag_name;
|
||||
$core_branch = "master";
|
||||
$core_current = "N/A";
|
||||
$core_update = false;
|
||||
$web_branch = "master";
|
||||
$web_current = "N/A";
|
||||
$web_update = false;
|
||||
$FTL_current = "N/A";
|
||||
$FTL_update = false;
|
||||
}
|
||||
|
||||
/********** Get Pi-hole core branch / version / commit **********/
|
||||
// Check if on a dev branch
|
||||
$core_branch = exec("cd /etc/.pihole/ && git rev-parse --abbrev-ref HEAD");
|
||||
if($core_branch !== "master") {
|
||||
$core_current = "vDev";
|
||||
$core_commit = exec("cd /etc/.pihole/ && git describe --long --dirty --tags");
|
||||
}
|
||||
else {
|
||||
$core_current = exec("cd /etc/.pihole/ && git describe --tags --abbrev=0");
|
||||
}
|
||||
|
||||
/********** Get Pi-hole web branch / version / commit **********/
|
||||
$web_branch = exec("git rev-parse --abbrev-ref HEAD");
|
||||
if($web_branch !== "master") {
|
||||
$web_current = "vDev";
|
||||
$web_commit = exec("git describe --long --dirty --tags");
|
||||
}
|
||||
else {
|
||||
$web_current = exec("git describe --tags --abbrev=0");
|
||||
}
|
||||
|
||||
/********** Get Pi-hole FTL version (not a git repository) **********/
|
||||
$FTL_current = exec("pihole-FTL version");
|
||||
|
||||
// can write only in the root web dir, i.e. /var/www/html
|
||||
$versionfile = "../versions";
|
||||
|
||||
$check_version = false;
|
||||
|
||||
// Check version if version buffer file does not exist
|
||||
if(is_readable($versionfile))
|
||||
else
|
||||
{
|
||||
// Obtain latest time stamp from buffer file
|
||||
$versions = explode(",",file_get_contents($versionfile));
|
||||
$date = date_create();
|
||||
$timestamp = date_timestamp_get($date);
|
||||
$versions = explode(" ", file_get_contents($localversions));
|
||||
$branches = explode(" ", file_get_contents($localbranches));
|
||||
$GitHubversions = explode(" ", file_get_contents($GitHubVersions));
|
||||
|
||||
// Is last check for updates older than 30 minutes?
|
||||
if($timestamp >= intval($versions[0]) + 1800)
|
||||
/********** Get Pi-hole core branch / version / commit **********/
|
||||
// Check if on a dev branch
|
||||
$core_branch = $branches[0];
|
||||
if($core_branch !== "master") {
|
||||
$core_current = "vDev";
|
||||
$core_commit = $versions[0];
|
||||
}
|
||||
else {
|
||||
$core_current = explode("-",$versions[0])[0];
|
||||
}
|
||||
|
||||
/********** Get Pi-hole web branch / version / commit **********/
|
||||
$web_branch = $branches[1];
|
||||
if($web_branch !== "master") {
|
||||
$web_current = "vDev";
|
||||
$web_commit = $versions[1];
|
||||
}
|
||||
else {
|
||||
$web_current = explode("-",$versions[1])[0];
|
||||
}
|
||||
|
||||
/********** Get Pi-hole FTL version (not a git repository) **********/
|
||||
$FTL_current = $versions[2];
|
||||
|
||||
// Get data from GitHub
|
||||
$core_latest = $GitHubversions[0];
|
||||
$web_latest = $GitHubversions[1];
|
||||
$FTL_latest = $GitHubversions[2];
|
||||
|
||||
// Core version comparison
|
||||
if($core_current !== "vDev")
|
||||
{
|
||||
// Yes: Retrieve new/updated version data
|
||||
$check_version = true;
|
||||
// This logic allows the local core version to be newer than the upstream version
|
||||
// The update indicator is only shown if the upstream version is NEWER
|
||||
$core_update = (version_compare($core_current, $core_latest) < 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No: We can use the buffered data
|
||||
$check_version = false;
|
||||
$core_update = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No buffer file or not readable: Request version data
|
||||
$check_version = true;
|
||||
}
|
||||
|
||||
// Get data from GitHub if requested
|
||||
if($check_version){
|
||||
$core_latest = get_github_version("https://api.github.com/repos/pi-hole/pi-hole/releases/latest");
|
||||
$web_latest = get_github_version("https://api.github.com/repos/pi-hole/AdminLTE/releases/latest");
|
||||
$FTL_latest = get_github_version("https://api.github.com/repos/pi-hole/FTL/releases/latest");
|
||||
// Web version comparison
|
||||
if($web_current !== "vDev")
|
||||
{
|
||||
// This logic allows the local core version to be newer than the upstream version
|
||||
// The update indicator is only shown if the upstream version is NEWER
|
||||
$web_update = (version_compare($web_current, $web_latest) < 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
$web_update = false;
|
||||
}
|
||||
|
||||
// Save to buffer file
|
||||
file_put_contents($versionfile, array($timestamp,",",$core_latest,",",$web_latest,",",$FTL_latest));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use data from buffer file
|
||||
$core_latest = $versions[1];
|
||||
$web_latest = $versions[2];
|
||||
$FTL_latest = $versions[3];
|
||||
}
|
||||
|
||||
// Core version comparison
|
||||
if($core_current !== "vDev")
|
||||
{
|
||||
// FTL version comparison
|
||||
// This logic allows the local core version to be newer than the upstream version
|
||||
// The update indicator is only shown if the upstream version is NEWER
|
||||
$core_update = (version_compare($core_current, $core_latest) < 0);
|
||||
$FTL_update = (version_compare($FTL_current, $FTL_latest) < 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
$core_update = false;
|
||||
}
|
||||
|
||||
// Web version comparison
|
||||
if($web_current !== "vDev")
|
||||
{
|
||||
// This logic allows the local core version to be newer than the upstream version
|
||||
// The update indicator is only shown if the upstream version is NEWER
|
||||
$web_update = (version_compare($web_current, $web_latest) < 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
$web_update = false;
|
||||
}
|
||||
|
||||
// FTL version comparison
|
||||
// This logic allows the local core version to be newer than the upstream version
|
||||
// The update indicator is only shown if the upstream version is NEWER
|
||||
$FTL_update = (version_compare($FTL_current, $FTL_latest) < 0);
|
||||
|
||||
?>
|
||||
|
||||
Vendored
+3
-8
File diff suppressed because one or more lines are too long
Vendored
+1542
File diff suppressed because it is too large
Load Diff
+1167
-886
File diff suppressed because it is too large
Load Diff
+18
-2
@@ -4,8 +4,10 @@
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
.small-box {
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
cursor: default;
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE10+ */
|
||||
|
||||
@@ -30,7 +32,21 @@ a.lookatme {
|
||||
-webkit-animation-name: Pulse;
|
||||
animation-name: Pulse;
|
||||
-webkit-animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
#all-queries {
|
||||
table-layout: fixed;
|
||||
}
|
||||
#all-queries tbody {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.main-header>.navbar {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#resetButton {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
Vendored
+232
@@ -0,0 +1,232 @@
|
||||
.daterangepicker {
|
||||
position: absolute;
|
||||
color: inherit;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
width: 278px;
|
||||
padding: 4px;
|
||||
margin-top: 1px;
|
||||
top: 100px;
|
||||
left: 20px;
|
||||
/* Calendars */ }
|
||||
.daterangepicker:before, .daterangepicker:after {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
content: ''; }
|
||||
.daterangepicker:before {
|
||||
top: -7px;
|
||||
border-right: 7px solid transparent;
|
||||
border-left: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc; }
|
||||
.daterangepicker:after {
|
||||
top: -6px;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #fff;
|
||||
border-left: 6px solid transparent; }
|
||||
.daterangepicker.opensleft:before {
|
||||
right: 9px; }
|
||||
.daterangepicker.opensleft:after {
|
||||
right: 10px; }
|
||||
.daterangepicker.openscenter:before {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto; }
|
||||
.daterangepicker.openscenter:after {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto; }
|
||||
.daterangepicker.opensright:before {
|
||||
left: 9px; }
|
||||
.daterangepicker.opensright:after {
|
||||
left: 10px; }
|
||||
.daterangepicker.dropup {
|
||||
margin-top: -5px; }
|
||||
.daterangepicker.dropup:before {
|
||||
top: initial;
|
||||
bottom: -7px;
|
||||
border-bottom: initial;
|
||||
border-top: 7px solid #ccc; }
|
||||
.daterangepicker.dropup:after {
|
||||
top: initial;
|
||||
bottom: -6px;
|
||||
border-bottom: initial;
|
||||
border-top: 6px solid #fff; }
|
||||
.daterangepicker.dropdown-menu {
|
||||
max-width: none;
|
||||
z-index: 3001; }
|
||||
.daterangepicker.single .ranges, .daterangepicker.single .calendar {
|
||||
float: none; }
|
||||
.daterangepicker.show-calendar .calendar {
|
||||
display: block; }
|
||||
.daterangepicker .calendar {
|
||||
display: none;
|
||||
max-width: 270px;
|
||||
margin: 4px; }
|
||||
.daterangepicker .calendar.single .calendar-table {
|
||||
border: none; }
|
||||
.daterangepicker .calendar th, .daterangepicker .calendar td {
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
min-width: 32px; }
|
||||
.daterangepicker .calendar-table {
|
||||
border: 1px solid #fff;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
background: #fff; }
|
||||
.daterangepicker table {
|
||||
width: 100%;
|
||||
margin: 0; }
|
||||
.daterangepicker td, .daterangepicker th {
|
||||
text-align: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
cursor: pointer; }
|
||||
.daterangepicker td.available:hover, .daterangepicker th.available:hover {
|
||||
background: #eee; }
|
||||
.daterangepicker td.week, .daterangepicker th.week {
|
||||
font-size: 80%;
|
||||
color: #ccc; }
|
||||
.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {
|
||||
background-color: #fff;
|
||||
border-color: transparent;
|
||||
color: #999; }
|
||||
.daterangepicker td.in-range {
|
||||
background-color: #ebf4f8;
|
||||
border-color: transparent;
|
||||
color: #000;
|
||||
border-radius: 0; }
|
||||
.daterangepicker td.start-date {
|
||||
border-radius: 4px 0 0 4px; }
|
||||
.daterangepicker td.end-date {
|
||||
border-radius: 0 4px 4px 0; }
|
||||
.daterangepicker td.start-date.end-date {
|
||||
border-radius: 4px; }
|
||||
.daterangepicker td.active, .daterangepicker td.active:hover {
|
||||
background-color: #357ebd;
|
||||
border-color: transparent;
|
||||
color: #fff; }
|
||||
.daterangepicker th.month {
|
||||
width: auto; }
|
||||
.daterangepicker td.disabled, .daterangepicker option.disabled {
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
text-decoration: line-through; }
|
||||
.daterangepicker select.monthselect, .daterangepicker select.yearselect {
|
||||
font-size: 12px;
|
||||
padding: 1px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
cursor: default; }
|
||||
.daterangepicker select.monthselect {
|
||||
margin-right: 2%;
|
||||
width: 56%; }
|
||||
.daterangepicker select.yearselect {
|
||||
width: 40%; }
|
||||
.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {
|
||||
width: 50px;
|
||||
margin-bottom: 0; }
|
||||
.daterangepicker .input-mini {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
color: #555;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0 6px 0 28px;
|
||||
width: 100%; }
|
||||
.daterangepicker .input-mini.active {
|
||||
border: 1px solid #08c;
|
||||
border-radius: 4px; }
|
||||
.daterangepicker .daterangepicker_input {
|
||||
position: relative; }
|
||||
.daterangepicker .daterangepicker_input i {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 8px; }
|
||||
.daterangepicker .calendar-time {
|
||||
text-align: center;
|
||||
margin: 5px auto;
|
||||
line-height: 30px;
|
||||
position: relative;
|
||||
padding-left: 28px; }
|
||||
.daterangepicker .calendar-time select.disabled {
|
||||
color: #ccc;
|
||||
cursor: not-allowed; }
|
||||
|
||||
.ranges {
|
||||
font-size: 11px;
|
||||
float: none;
|
||||
margin: 4px;
|
||||
text-align: left; }
|
||||
.ranges ul {
|
||||
list-style: none;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: 100%; }
|
||||
.ranges li {
|
||||
font-size: 13px;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #f5f5f5;
|
||||
border-radius: 4px;
|
||||
color: #08c;
|
||||
padding: 3px 12px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer; }
|
||||
.ranges li:hover {
|
||||
background: #08c;
|
||||
border: 1px solid #08c;
|
||||
color: #fff; }
|
||||
.ranges li.active {
|
||||
background: #08c;
|
||||
border: 1px solid #08c;
|
||||
color: #fff; }
|
||||
|
||||
/* Larger Screen Styling */
|
||||
@media (min-width: 564px) {
|
||||
.daterangepicker {
|
||||
width: auto; }
|
||||
.daterangepicker .ranges ul {
|
||||
width: 160px; }
|
||||
.daterangepicker.single .ranges ul {
|
||||
width: 100%; }
|
||||
.daterangepicker.single .calendar.left {
|
||||
clear: none; }
|
||||
.daterangepicker.single .ranges, .daterangepicker.single .calendar {
|
||||
float: left; }
|
||||
.daterangepicker .calendar.left {
|
||||
clear: left;
|
||||
margin-right: 0; }
|
||||
.daterangepicker .calendar.left .calendar-table {
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0; }
|
||||
.daterangepicker .calendar.right {
|
||||
margin-left: 0; }
|
||||
.daterangepicker .calendar.right .calendar-table {
|
||||
border-left: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0; }
|
||||
.daterangepicker .left .daterangepicker_input {
|
||||
padding-right: 12px; }
|
||||
.daterangepicker .calendar.left .calendar-table {
|
||||
padding-right: 12px; }
|
||||
.daterangepicker .ranges, .daterangepicker .calendar {
|
||||
float: left; } }
|
||||
|
||||
@media (min-width: 730px) {
|
||||
.daterangepicker .ranges {
|
||||
width: auto;
|
||||
float: left; }
|
||||
.daterangepicker .calendar.left {
|
||||
clear: none; } }
|
||||
|
||||
Reference in New Issue
Block a user