mirror of
https://github.com/pi-hole/web.git
synced 2024-12-06 19:36:21 +01:00
Merge branch 'devel' into tweak/blockpage
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@ group_defaults:
|
||||
reset_on_push:
|
||||
enabled: true
|
||||
reject_value: -2
|
||||
approve_regex: '^(Approved|:shipit:|:\+1:|Engage)'
|
||||
approve_regex: '^(Approved|:shipit:|:\+1:|Engage|:taco:)'
|
||||
reject_regex: '^(Rejected|:-1:|Borg)'
|
||||
author_approval:
|
||||
auto: true
|
||||
|
||||
@@ -11,81 +11,63 @@ This license applies to the whole project EXCEPT the files located under
|
||||
whose licenses are located therein.
|
||||
|
||||
-------------------------------------------------------------
|
||||
EUROPEAN UNION PUBLIC LICENCE v. 1.2
|
||||
|
||||
European Union Public Licence
|
||||
V. 1.1
|
||||
EUPL © the European Union 2007, 2016
|
||||
|
||||
EUPL (C) the European Community 2007
|
||||
|
||||
This European Union Public Licence (the "EUPL") applies to the Work or Software (as defined below) which is provided under the terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such use is covered by a right of the copyright holder of the Work).
|
||||
|
||||
The Original Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following notice immediately following the copyright notice for the Original Work:
|
||||
|
||||
Licensed under the EUPL V.1.1
|
||||
|
||||
or has expressed by any other mean his willingness to license under the EUPL.
|
||||
This European Union Public Licence (the EUPL) applies to the Work (as defined below) which is provided under the terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such use is covered by a right of the copyright holder of the Work).
|
||||
The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following notice immediately following the copyright notice for the Work:
|
||||
Licensed under the EUPL
|
||||
or has expressed by any other means his willingness to license under the EUPL.
|
||||
|
||||
1. Definitions
|
||||
|
||||
In this Licence, the following terms have the following meaning:
|
||||
|
||||
- The Licence: this Licence.
|
||||
|
||||
- The Original Work or the Software: the software distributed and/or communicated by the Licensor under this Licence, available as Source Code and also as Executable Code as the case may be.
|
||||
|
||||
- The Original Work: the work or software distributed or communicated by the Licensor under this Licence, available as Source Code and also as Executable Code as the case may be.
|
||||
- Derivative Works: the works or software that could be created by the Licensee, based upon the Original Work or modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in the country mentioned in Article 15.
|
||||
|
||||
- The Work: the Original Work and/or its Derivative Works.
|
||||
|
||||
- The Work: the Original Work or its Derivative Works.
|
||||
- The Source Code: the human-readable form of the Work which is the most convenient for people to study and modify.
|
||||
|
||||
- The Executable Code: any code which has generally been compiled and which is meant to be interpreted by a computer as a program.
|
||||
|
||||
- The Licensor: the natural or legal person that distributes and/or communicates the Work under the Licence.
|
||||
|
||||
- The Licensor: the natural or legal person that distributes or communicates the Work under the Licence.
|
||||
- Contributor(s): any natural or legal person who modifies the Work under the Licence, or otherwise contributes to the creation of a Derivative Work.
|
||||
|
||||
- The Licensee or "You": any natural or legal person who makes any usage of the Software under the terms of the Licence.
|
||||
|
||||
- Distribution and/or Communication: any act of selling, giving, lending, renting, distributing, communicating, transmitting, or otherwise making available, on-line or off-line, copies of the Work or providing access to its essential functionalities at the disposal of any other natural or legal person.
|
||||
- The Licensee or You: any natural or legal person who makes any usage of the Work under the terms of the Licence.
|
||||
- Distribution or Communication: any act of selling, giving, lending, renting, distributing, communicating, transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential functionalities at the disposal of any other natural or legal person.
|
||||
|
||||
2. Scope of the rights granted by the Licence
|
||||
|
||||
The Licensor hereby grants You a world-wide, royalty-free, non-exclusive, sub-licensable licence to do the following, for the duration of copyright vested in the Original Work:
|
||||
|
||||
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for the duration of copyright vested in the Original Work:
|
||||
- use the Work in any circumstance and for all usage,
|
||||
- reproduce the Work,
|
||||
- modify the Original Work, and make Derivative Works based upon the Work,
|
||||
- modify the Work, and make Derivative Works based upon the Work,
|
||||
- communicate to the public, including the right to make available or display the Work or copies thereof to the public and perform publicly, as the case may be, the Work,
|
||||
- distribute the Work or copies thereof,
|
||||
- lend and rent the Work or copies thereof,
|
||||
- sub-license rights in the Work or copies thereof.
|
||||
|
||||
- sublicense rights in the Work or copies thereof.
|
||||
Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the applicable law permits so.
|
||||
|
||||
In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed by law in order to make effective the licence of the economic rights here above listed.
|
||||
|
||||
The Licensor grants to the Licensee royalty-free, non exclusive usage rights to any patents held by the Licensor, to the extent necessary to make use of the rights granted on the Work under this Licence.
|
||||
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the extent necessary to make use of the rights granted on the Work under this Licence.
|
||||
|
||||
3. Communication of the Source Code
|
||||
|
||||
The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to distribute and/or communicate the Work.
|
||||
The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to distribute or communicate the Work.
|
||||
|
||||
4. Limitations on copyright
|
||||
|
||||
Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the exclusive rights of the rights owners in the Original Work or Software, of the exhaustion of those rights or of other applicable limitations thereto.
|
||||
Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations thereto.
|
||||
|
||||
5. Obligations of the Licensee
|
||||
|
||||
The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those obligations are the following:
|
||||
|
||||
Attribution right: the Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the Licence with every copy of the Work he/she distributes and/or communicates. The Licensee must cause any Derivative Work to carry prominent notices stating that the Work has been modified and the date of modification.
|
||||
Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work to carry prominent notices stating that the Work has been modified and the date of modification.
|
||||
|
||||
Copyleft clause: If the Licensee distributes and/or communicates copies of the Original Works or Derivative Works based upon the Original Work, this Distribution and/or Communication will be done under the terms of this Licence or of a later version of this Licence unless the Original Work is expressly distributed only under this version of the Licence. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the Work or Derivative Work that alter or restrict the terms of the Licence.
|
||||
Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless the Original Work is expressly distributed only under this version of the Licence - for example by communicating EUPL v. 1.2 only. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the Work or Derivative Work that alter or restrict the terms of the Licence.
|
||||
|
||||
Compatibility clause: If the Licensee Distributes and/or Communicates Derivative Works or copies thereof based upon both the Original Work and another work licensed under a Compatible Licence, this Distribution and/or Communication can be done under the terms of this Compatible Licence. For the sake of this clause, "Compatible Licence" refers to the licences listed in the appendix attached to this Licence. Should the Licensee’s obligations under the Compatible Licence conflict with his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.
|
||||
Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done under the terms of this Compatible Licence. For the sake of this clause, Compatible Licence refers to the licences listed in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.
|
||||
|
||||
Provision of Source Code: When distributing and/or communicating copies of the Work, the Licensee will provide a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available for as long as the Licensee continues to distribute and/or communicate the Work.
|
||||
Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available for as long as the Licensee continues to distribute or communicate the Work.
|
||||
|
||||
Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the copyright notice.
|
||||
|
||||
@@ -99,10 +81,8 @@ Each time You accept the Licence, the original Licensor and subsequent Contribut
|
||||
|
||||
7. Disclaimer of Warranty
|
||||
|
||||
The Work is a work in progress, which is continuously improved by numerous contributors. It is not a finished work and may therefore contain defects or "bugs" inherent to this type of software development.
|
||||
|
||||
For the above reason, the Work is provided under the Licence on an "as is" basis and without warranties of any kind concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this Licence.
|
||||
|
||||
The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work and may therefore contain defects or bugs inherent to this type of development.
|
||||
For the above reason, the Work is provided under the Licence on an as is basis and without warranties of any kind concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this Licence.
|
||||
This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.
|
||||
|
||||
8. Disclaimer of Liability
|
||||
@@ -111,56 +91,55 @@ Except in the cases of wilful misconduct or damages directly caused to natural p
|
||||
|
||||
9. Additional agreements
|
||||
|
||||
While distributing the Original Work or Derivative Works, You may choose to conclude an additional agreement to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or services consistent with this Licence. However, in accepting such obligations, You may act only on your own behalf and on your sole responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by the fact You have accepted any such warranty or additional liability.
|
||||
While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by the fact You have accepted any warranty or additional liability.
|
||||
|
||||
10. Acceptance of the Licence
|
||||
|
||||
The provisions of this Licence can be accepted by clicking on an icon "I agree" placed under the bottom of a window displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms and conditions.
|
||||
|
||||
Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution and/or Communication by You of the Work or copies thereof.
|
||||
The provisions of this Licence can be accepted by clicking on an icon I agree placed under the bottom of a window displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms and conditions.
|
||||
Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution or Communication by You of the Work or copies thereof.
|
||||
|
||||
11. Information to the public
|
||||
|
||||
In case of any Distribution and/or Communication of the Work by means of electronic communication by You (for example, by offering to download the Work from a remote location) the distribution channel or media (for example, a website) must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence and the way it may be accessible, concluded, stored and reproduced by the Licensee.
|
||||
In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, by offering to download the Work from a remote location) the distribution channel or media (for example, a website) must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence and the way it may be accessible, concluded, stored and reproduced by the Licensee.
|
||||
|
||||
12. Termination of the Licence
|
||||
|
||||
The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms of the Licence.
|
||||
|
||||
Such a termination will not terminate the licences of any person who has received the Work from the Licensee under the Licence, provided such persons remain in full compliance with the Licence.
|
||||
|
||||
13. Miscellaneous
|
||||
|
||||
Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the Work licensed hereunder.
|
||||
|
||||
If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or enforceability of the Licence as a whole. Such provision will be construed and/or reformed so as necessary to make it valid and enforceable.
|
||||
|
||||
The European Commission may publish other linguistic versions and/or new versions of this Licence, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. New versions of the Licence will be published with a unique version number.
|
||||
|
||||
Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the Work.
|
||||
If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid and enforceable.
|
||||
The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. New versions of the Licence will be published with a unique version number.
|
||||
All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take advantage of the linguistic version of their choice.
|
||||
|
||||
14. Jurisdiction
|
||||
|
||||
Any litigation resulting from the interpretation of this License, arising between the European Commission, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice of the European Communities, as laid down in article 238 of the Treaty establishing the European Community.
|
||||
|
||||
Any litigation arising between Parties, other than the European Commission, and resulting from the interpretation of this License, will be subject to the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.
|
||||
Without prejudice to specific agreement between parties,
|
||||
- any litigation resulting from the interpretation of this License, arising between the European Union institutions, bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union,
|
||||
- any litigation arising between other parties and resulting from the interpretation of this License, will be subject to the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.
|
||||
|
||||
15. Applicable Law
|
||||
|
||||
This Licence shall be governed by the law of the European Union country where the Licensor resides or has his registered office.
|
||||
|
||||
This licence shall be governed by the Belgian law if:
|
||||
|
||||
- a litigation arises between the European Commission, as a Licensor, and any Licensee;
|
||||
- the Licensor, other than the European Commission, has no residence or registered office inside a European Union country.
|
||||
|
||||
Without prejudice to specific agreement between parties,
|
||||
- this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, resides or has his registered office,
|
||||
- this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside a European Union Member State.
|
||||
|
||||
===
|
||||
|
||||
Appendix
|
||||
|
||||
"Compatible Licences" according to article 5 EUPL are:
|
||||
- GNU General Public License (GNU GPL) v. 2
|
||||
Compatible Licences according to Article 5 EUPL are:
|
||||
- GNU General Public License (GPL) v. 2, v. 3
|
||||
- GNU Affero General Public License (AGPL) v. 3
|
||||
- Open Software License (OSL) v. 2.1, v. 3.0
|
||||
- Common Public License v. 1.0
|
||||
- Eclipse Public License v. 1.0
|
||||
- Cecill v. 2.0
|
||||
- Eclipse Public License (EPL) v. 1.0
|
||||
- CeCILL v. 2.0, v. 2.1
|
||||
- Mozilla Public Licence (MPL) v. 2
|
||||
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
|
||||
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software
|
||||
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
|
||||
- Québec Free and Open-Source Licence - Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+)
|
||||
- The European Commission may update this Appendix to later versions of the above licences without producing a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the covered Source Code from exclusive appropriation.
|
||||
- All other changes or additions to this Appendix require the production of a new EUPL version.
|
||||
|
||||
+25
-5
@@ -45,7 +45,7 @@ if (isset($_GET['summary']) || isset($_GET['summaryRaw']) || !count($_GET))
|
||||
}
|
||||
else
|
||||
{
|
||||
$stats[$tmp[0]] = intval($tmp[1]);
|
||||
$stats[$tmp[0]] = floatval($tmp[1]);
|
||||
}
|
||||
}
|
||||
$data = array_merge($data,$stats);
|
||||
@@ -71,7 +71,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 +92,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 +110,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 +164,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)
|
||||
@@ -233,6 +251,7 @@ if (isset($_GET['overTimeDataForwards']) && $auth)
|
||||
{
|
||||
sendRequestFTL("ForwardedoverTime");
|
||||
$return = getResponseFTL();
|
||||
$over_time = array();
|
||||
|
||||
foreach($return as $line)
|
||||
{
|
||||
@@ -271,6 +290,7 @@ if (isset($_GET['overTimeDataQueryTypes']) && $auth)
|
||||
{
|
||||
sendRequestFTL("QueryTypesoverTime");
|
||||
$return = getResponseFTL();
|
||||
$over_time = array();
|
||||
|
||||
foreach($return as $line)
|
||||
{
|
||||
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
<?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
|
||||
|
||||
$db = new SQLite3('/etc/pihole/pihole-FTL.db');
|
||||
if(!$db)
|
||||
die("Cannot access database");
|
||||
|
||||
// Long-Term API functions
|
||||
// if (isset($_GET['test']))
|
||||
// {
|
||||
// // $results = $db->query('SELECT * FROM QUERIES order by TIMESTAMP ASC LIMIT 2');
|
||||
// $results = $db->query('SELECT TIMESTAMP,TYPE,DOMAIN,CLIENT,STATUS FROM QUERIES order by TIMESTAMP ASC');
|
||||
// echo "N=".$result->numColumns;
|
||||
// while ($row = $results->fetchArray())
|
||||
// var_dump($row);
|
||||
// }
|
||||
|
||||
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 <?php echo $showing; ?></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 -->
|
||||
|
||||
@@ -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>DNS Queries Blocked Today</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>DNS Queries Today</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>
|
||||
@@ -36,12 +23,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-lg-3 col-xs-12">
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-aqua">
|
||||
<div class="inner">
|
||||
<p>Queries Blocked</p>
|
||||
<h3 class="statistic"><span id="ads_blocked_today">---</span></h3>
|
||||
</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-yellow">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="ads_percentage_today">---</h3>
|
||||
<p>Of Today's Queries Were Blocked</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>
|
||||
@@ -53,8 +53,8 @@
|
||||
<!-- small box -->
|
||||
<div class="small-box bg-red">
|
||||
<div class="inner">
|
||||
<h3 class="statistic" id="domains_being_blocked">---</h3>
|
||||
<p>Domains Being Blocked</p>
|
||||
<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>
|
||||
@@ -68,7 +68,7 @@
|
||||
<div class="col-md-12">
|
||||
<div class="box" id="queries-over-time">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Queries over Time</h3>
|
||||
<h3 class="box-title">Queries over last 24 hours</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="chart">
|
||||
@@ -89,10 +89,45 @@
|
||||
// a password
|
||||
if($auth){ ?>
|
||||
<div class="row">
|
||||
<div class="hidden-xs hidden-sm col-md-12 col-lg-6">
|
||||
<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>
|
||||
|
||||
<div class="row">
|
||||
<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 +140,10 @@
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden-xs hidden-sm col-md-12 col-lg-6">
|
||||
<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">
|
||||
@@ -164,7 +199,7 @@ else
|
||||
<div class="<?php echo $tablelayout; ?>">
|
||||
<div class="box" id="ad-frequency">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Top Advertisers</h3>
|
||||
<h3 class="box-title">Top Blocked Domains</h3>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
|
||||
@@ -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 " + data.dns_queries_today + "\"> <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 " + data.dns_queries_today + "\"> <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 " + data.dns_queries_today + "\"> <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();
|
||||
});
|
||||
|
||||
@@ -4,16 +4,6 @@
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
// User menu toggle
|
||||
$("#dropdown-menu a").on("click", function(event) {
|
||||
$(this).parent().toggleClass("open");
|
||||
});
|
||||
$("body").on("click", function(event) {
|
||||
if(!$("#dropdown-menu").is(event.target) && $("#dropdown-menu").has(event.target).length === 0) {
|
||||
$("#dropdown-menu").removeClass("open");
|
||||
}
|
||||
});
|
||||
|
||||
//The following functions allow us to display time until pi-hole is enabled after disabling.
|
||||
//Works between all pages
|
||||
|
||||
@@ -141,76 +131,6 @@ $("#pihole-disable-custom").on("click", function(e){
|
||||
piholeChange("disable",custVal);
|
||||
});
|
||||
|
||||
|
||||
var piholeVersion = $("#piholeVersion").html();
|
||||
var webVersion = $("#webVersion").html();
|
||||
var FTLVersion = $("#FTLVersion").html();
|
||||
|
||||
// Credit for following function: https://gist.github.com/alexey-bass/1115557
|
||||
// Modified to discard any possible "v" in the string
|
||||
function versionCompare(left, right) {
|
||||
if (typeof left + typeof right !== "stringstring")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we are on vDev then we assume that it is always
|
||||
// newer than the latest online release, i.e. version
|
||||
// comparison should return 1
|
||||
if(left === "vDev")
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var aa = left.split("v"),
|
||||
bb = right.split("v");
|
||||
|
||||
var a = aa[aa.length-1].split(".")
|
||||
, b = bb[bb.length-1].split(".")
|
||||
, i = 0, len = Math.max(a.length, b.length);
|
||||
|
||||
for (; i < len; i++) {
|
||||
if ((a[i] && !b[i] && parseInt(a[i]) > 0) || (parseInt(a[i]) > parseInt(b[i]))) {
|
||||
return 1;
|
||||
} else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || (parseInt(a[i]) < parseInt(b[i]))) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Update check
|
||||
$.getJSON("https://api.github.com/repos/pi-hole/pi-hole/releases/latest", function(json) {
|
||||
if(versionCompare(piholeVersion, json.tag_name.slice(1)) < 0) {
|
||||
// Alert user
|
||||
$("#piholeVersion").html($("#piholeVersion").text() + " <a class=\"alert-link lookatme\" href=\"https://github.com/pi-hole/pi-hole/releases\">(Update available!)</a>");
|
||||
$("#alPiholeUpdate").show();
|
||||
}
|
||||
});
|
||||
$.getJSON("https://api.github.com/repos/pi-hole/AdminLTE/releases/latest", function(json) {
|
||||
if(versionCompare(webVersion, json.tag_name.slice(1)) < 0) {
|
||||
// Alert user
|
||||
$("#webVersion").html($("#webVersion").text() + " <a class=\"alert-link lookatme\" href=\"https://github.com/pi-hole/adminLTE/releases\">(Update available!)</a>");
|
||||
$("#alWebUpdate").show();
|
||||
}
|
||||
});
|
||||
$.getJSON("https://api.github.com/repos/pi-hole/FTL/releases/latest", function(json) {
|
||||
if(versionCompare(FTLVersion, json.tag_name.slice(1)) < 0) {
|
||||
// Alert user
|
||||
$("#FTLVersion").html($("#FTLVersion").text() + " <a class=\"alert-link lookatme\" href=\"https://github.com/pi-hole/FTL/releases\">(Update available!)</a>");
|
||||
$("#alFTLUpdate").show();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Make sure that Pi-hole is updated to at least v2.7, since that is needed to use the sudo
|
||||
* features of the interface. Skip if on dev
|
||||
*/
|
||||
if(versionCompare(piholeVersion, "v2.7") < 0) {
|
||||
alert("Pi-hole needs to be updated to at least v2.7 before you can use features such as whitelisting/blacklisting from this web interface!");
|
||||
}
|
||||
|
||||
// Session timer
|
||||
var sessionvalidity = parseInt(document.getElementById("sessiontimercounter").textContent);
|
||||
var start = new Date;
|
||||
@@ -255,12 +175,6 @@ else
|
||||
document.getElementById("sessiontimer").style.display = "none";
|
||||
}
|
||||
|
||||
// Hide "exact match" button on queryads.php page if version is 2.9.5 or lower
|
||||
if(versionCompare(piholeVersion, "v2.9.5") < 1)
|
||||
{
|
||||
$("#btnSearchExact").hide();
|
||||
}
|
||||
|
||||
// Handle Strg + Enter button on Login page
|
||||
$(document).keypress(function(e) {
|
||||
if((e.keyCode === 10 || e.keyCode === 13) && e.ctrlKey && $("#loginpw").is(":focus")) {
|
||||
|
||||
@@ -45,11 +45,6 @@ $("#gravityBtn").on("click", function(){
|
||||
eventsource();
|
||||
});
|
||||
|
||||
$("#gravityBtn").on("click", () => {
|
||||
$("#gravityBtn").attr("disabled", true);
|
||||
eventsource();
|
||||
});
|
||||
|
||||
// Handle hiding of alerts
|
||||
$(function(){
|
||||
$("[data-hide]").on("click", function(){
|
||||
|
||||
+392
-195
@@ -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;
|
||||
|
||||
function padNumber(num) {
|
||||
return ("00" + num).substr(-2,2);
|
||||
@@ -145,6 +147,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) {
|
||||
@@ -186,11 +230,13 @@ function updateForwardedOverTime() {
|
||||
// Collect values and colors, and labels
|
||||
forwardDestinationChart.data.datasets[0].backgroundColor = colors[0];
|
||||
forwardDestinationChart.data.datasets[0].pointRadius = 0;
|
||||
forwardDestinationChart.data.datasets[0].pointHitRadius = 5;
|
||||
forwardDestinationChart.data.datasets[0].pointHoverRadius = 5;
|
||||
forwardDestinationChart.data.datasets[0].label = labels[0];
|
||||
|
||||
for (i = forwardDestinationChart.data.datasets.length; i < plotdata[0].length; i++)
|
||||
{
|
||||
forwardDestinationChart.data.datasets.push({data: [], backgroundColor: colors[i], pointRadius: 0, label: labels[i]});
|
||||
forwardDestinationChart.data.datasets.push({data: [], backgroundColor: colors[i], pointRadius: 0, pointHitRadius: 5, pointHoverRadius: 5, label: labels[i]});
|
||||
}
|
||||
|
||||
// Add data for each dataset that is available
|
||||
@@ -231,6 +277,46 @@ function updateForwardedOverTime() {
|
||||
});
|
||||
}
|
||||
|
||||
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 = [];
|
||||
// Collect values and colors, immediately push individual labels
|
||||
$.each(data.forward_destinations, function(key , value) {
|
||||
v.push(value);
|
||||
c.push(colors.shift());
|
||||
if(key.indexOf("|") > -1)
|
||||
{
|
||||
key = key.substr(0, key.indexOf("|"));
|
||||
}
|
||||
k.push(key);
|
||||
});
|
||||
// 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 = {
|
||||
@@ -260,6 +346,11 @@ function updateTopClientsChart() {
|
||||
|
||||
if ({}.hasOwnProperty.call(data.top_sources, client)){
|
||||
// Sanitize client
|
||||
if(escapeHtml(client) !== client)
|
||||
{
|
||||
// Make a copy with the escaped index if necessary
|
||||
data.top_sources[escapeHtml(client)] = data.top_sources[client];
|
||||
}
|
||||
client = escapeHtml(client);
|
||||
if(client.indexOf("|") > -1)
|
||||
{
|
||||
@@ -305,6 +396,11 @@ function updateTopLists() {
|
||||
for (domain in data.top_queries) {
|
||||
if ({}.hasOwnProperty.call(data.top_queries,domain)){
|
||||
// Sanitize domain
|
||||
if(escapeHtml(domain) !== domain)
|
||||
{
|
||||
// Make a copy with the escaped index if necessary
|
||||
data.top_queries[escapeHtml(domain)] = data.top_queries[domain];
|
||||
}
|
||||
domain = escapeHtml(domain);
|
||||
url = "<a href=\"queries.php?domain="+domain+"\">"+domain+"</a>";
|
||||
percentage = data.top_queries[domain] / data.dns_queries_today * 100;
|
||||
@@ -323,6 +419,11 @@ function updateTopLists() {
|
||||
for (domain in data.top_ads) {
|
||||
if ({}.hasOwnProperty.call(data.top_ads,domain)){
|
||||
// Sanitize domain
|
||||
if(escapeHtml(domain) !== domain)
|
||||
{
|
||||
// Make a copy with the escaped index if necessary
|
||||
data.top_ads[escapeHtml(domain)] = data.top_ads[domain];
|
||||
}
|
||||
domain = escapeHtml(domain);
|
||||
url = "<a href=\"queries.php?domain="+domain+"\">"+domain+"</a>";
|
||||
percentage = data.top_ads[domain] / data.ads_blocked_today * 100;
|
||||
@@ -358,8 +459,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
|
||||
@@ -389,19 +490,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() {
|
||||
@@ -420,59 +521,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 "Queries 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: {
|
||||
@@ -486,24 +658,10 @@ $(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) + "%";
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -523,141 +681,180 @@ $(document).ready(function() {
|
||||
}],
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
mix: 0.0,
|
||||
max: 1.0,
|
||||
beginAtZero: true,
|
||||
callback: function(value, index, values) {
|
||||
return Math.round(value*100) + " %";
|
||||
}
|
||||
},
|
||||
stacked: true
|
||||
}]
|
||||
},
|
||||
maintainAspectRatio: false
|
||||
maintainAspectRatio: true
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateForwardedOverTime();
|
||||
}
|
||||
|
||||
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: {
|
||||
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
|
||||
}]
|
||||
// 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: []
|
||||
},
|
||||
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,
|
||||
data: []
|
||||
{
|
||||
label: "AAAA: IPv6 queries",
|
||||
pointRadius: 0,
|
||||
pointHitRadius: 5,
|
||||
pointHoverRadius: 5,
|
||||
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 "Query types from "+from+" to "+to;
|
||||
},
|
||||
{
|
||||
label: "AAAA: IPv6 queries",
|
||||
pointRadius: 0,
|
||||
data: []
|
||||
label: function(tooltipItems, data) {
|
||||
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
options: {
|
||||
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) + " %";
|
||||
}
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "hour",
|
||||
displayFormats: {
|
||||
hour: "HH:mm"
|
||||
},
|
||||
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;
|
||||
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
|
||||
}
|
||||
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"
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
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"
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
|
||||
// Pull in data via AJAX
|
||||
updateForwardDestinationsPie();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ function refresh(fade) {
|
||||
{
|
||||
listw.html("");
|
||||
}
|
||||
var data = JSON.parse(response);
|
||||
var data = JSON.parse(response).sort();
|
||||
|
||||
if(data.length === 0) {
|
||||
$("h3").hide();
|
||||
@@ -208,11 +208,19 @@ $(function(){
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
if (screen.width < 576) {
|
||||
$(".input-group-btn").css("display", "initial");
|
||||
// Wrap form-group's buttons to next line when viewed on a small screen
|
||||
$(window).on("resize",function() {
|
||||
if ($(window).width() < 991) {
|
||||
$(".form-group.input-group").removeClass("input-group").addClass("input-group-block");
|
||||
$(".form-group.input-group-block > input").css("margin-bottom", "5px");
|
||||
$(".form-group.input-group-block > .input-group-btn").removeClass("input-group-btn").addClass("btn-block text-center");
|
||||
}
|
||||
else {
|
||||
$(".input-group-btn").css("display", "table-cell");
|
||||
$(".form-group.input-group-block").removeClass("input-group-block").addClass( "input-group" );
|
||||
$(".form-group.input-group > input").css("margin-bottom","");
|
||||
$(".form-group.input-group > .btn-block.text-center").removeClass("btn-block text-center").addClass("input-group-btn");
|
||||
}
|
||||
});
|
||||
$(document).ready(function() {
|
||||
$(window).trigger("resize");
|
||||
});
|
||||
|
||||
@@ -152,6 +152,12 @@ $(document).ready(function() {
|
||||
$("td:eq(4)", row).html( "Pi-holed (wildcard)" );
|
||||
$("td:eq(5)", row).html( "" );
|
||||
}
|
||||
else if (data[4] === "5")
|
||||
{
|
||||
$(row).css("color","red");
|
||||
$("td:eq(4)", row).html( "Pi-holed (blacklist)" );
|
||||
$("td:eq(5)", row).html( "<button style=\"color:green; white-space: nowrap;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button>" );
|
||||
}
|
||||
else
|
||||
{
|
||||
$("td:eq(4)", row).html( "Unknown" );
|
||||
@@ -169,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%" },
|
||||
],
|
||||
@@ -183,7 +189,7 @@ $(document).ready(function() {
|
||||
});
|
||||
$("#all-queries tbody").on( "click", "button", function () {
|
||||
var data = tableApi.row( $(this).parents("tr") ).data();
|
||||
if (data[4] === "1")
|
||||
if (data[4] === "1" || data[4] === "5")
|
||||
{
|
||||
add(data[2],"white");
|
||||
}
|
||||
|
||||
@@ -122,3 +122,20 @@ $("#btnSearchExact").on("click", function() {
|
||||
exact = "exact";
|
||||
eventsource();
|
||||
});
|
||||
|
||||
// Wrap form-group's buttons to next line when viewed on a small screen
|
||||
$(window).on("resize",function() {
|
||||
if ($(window).width() < 991) {
|
||||
$(".form-group.input-group").removeClass("input-group").addClass("input-group-block");
|
||||
$(".form-group.input-group-block > input").css("margin-bottom", "5px");
|
||||
$(".form-group.input-group-block > .input-group-btn").removeClass("input-group-btn").addClass("btn-block text-center");
|
||||
}
|
||||
else {
|
||||
$(".form-group.input-group-block").removeClass("input-group-block").addClass( "input-group" );
|
||||
$(".form-group.input-group > input").css("margin-bottom","");
|
||||
$(".form-group.input-group > .btn-block.text-center").removeClass("btn-block text-center").addClass("input-group-btn");
|
||||
}
|
||||
});
|
||||
$(document).ready(function() {
|
||||
$(window).trigger("resize");
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,50 +39,31 @@
|
||||
</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">
|
||||
<?php
|
||||
// Check if on a dev branch
|
||||
$piholeBranch = exec("cd /etc/.pihole/ && git rev-parse --abbrev-ref HEAD");
|
||||
$webBranch = exec("git rev-parse --abbrev-ref HEAD");
|
||||
|
||||
// Use vDev if not on master
|
||||
if($piholeBranch !== "master") {
|
||||
$piholeVersion = "vDev";
|
||||
$piholeCommit = exec("cd /etc/.pihole/ && git describe --long --dirty --tags");
|
||||
}
|
||||
else {
|
||||
$piholeVersion = exec("cd /etc/.pihole/ && git describe --tags --abbrev=0");
|
||||
}
|
||||
|
||||
if($webBranch !== "master") {
|
||||
$webVersion = "vDev";
|
||||
$webCommit = exec("git describe --long --dirty --tags");
|
||||
}
|
||||
else {
|
||||
$webVersion = exec("git describe --tags --abbrev=0");
|
||||
}
|
||||
|
||||
$FTLVersion = exec("pihole-FTL version");
|
||||
?>
|
||||
<style type="text/css">
|
||||
@-webkit-keyframes Pulse{
|
||||
from {color:#630030;-webkit-text-shadow:0 0 9px #333;}
|
||||
50% {color:#e33100;-webkit-text-shadow:0 0 32px #e33100;}
|
||||
to {color:#630030;-webkit-text-shadow:0 0 9px #333;}
|
||||
}
|
||||
a.lookatme {
|
||||
-webkit-animation-name: Pulse;
|
||||
-webkit-animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
}
|
||||
</style>
|
||||
<div class="pull-right hidden-xs <?php if(isset($piholeCommit) || isset($webCommit)) { ?>hidden-md<?php } ?>">
|
||||
<b>Pi-hole Version </b> <span id="piholeVersion"><?php echo $piholeVersion; ?></span><?php if(isset($piholeCommit)) { echo " (".$piholeBranch.", ".$piholeCommit.")"; } ?>
|
||||
<b>Web Interface Version </b> <span id="webVersion"><?php echo $webVersion; ?></span><?php if(isset($webCommit)) { echo " (".$webBranch.", ".$webCommit.")"; } ?>
|
||||
<b>FTL Version </b> <span id="FTLVersion"><?php echo $FTLVersion; ?></span>
|
||||
<!-- Version Infos -->
|
||||
<div class="pull-right hidden-xs hidden-sm<?php if(isset($core_commit) || isset($web_commit)) { ?> hidden-md<?php } ?>">
|
||||
<b>Pi-hole Version </b> <?php
|
||||
echo $core_current;
|
||||
if(isset($core_commit)) { echo " (".$core_branch.", ".$core_commit.")"; }
|
||||
if($core_update){ ?> <a class="alert-link lookatme" href="https://github.com/pi-hole/pi-hole/releases" target="_blank">(Update available!)</a><?php } ?>
|
||||
<b>Web Interface Version </b><?php
|
||||
echo $web_current;
|
||||
if(isset($web_commit)) { echo " (".$web_branch.", ".$web_commit.")"; }
|
||||
if($web_update){ ?> <a class="alert-link lookatme" href="https://github.com/pi-hole/AdminLTE/releases" target="_blank">(Update available!)</a><?php } ?>
|
||||
<b>FTL Version </b> <?php
|
||||
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><a href="https://github.com/pi-hole"><i class="fa fa-github"></i></a> <strong><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">Donate</a></strong> if you found this useful.</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>
|
||||
</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) {
|
||||
|
||||
@@ -183,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" />
|
||||
@@ -190,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]>
|
||||
@@ -219,7 +220,7 @@ if($auth) {
|
||||
<div class="wrapper">
|
||||
<header class="main-header">
|
||||
<!-- Logo -->
|
||||
<a href="http://pi-hole.net" class="logo">
|
||||
<a href="http://pi-hole.net" class="logo" target="_blank">
|
||||
<!-- mini logo for sidebar mini 50x50 pixels -->
|
||||
<span class="logo-mini">P<b>h</b></span>
|
||||
<!-- logo for regular state and mobile devices -->
|
||||
@@ -233,16 +234,16 @@ if($auth) {
|
||||
</a>
|
||||
<div class="navbar-custom-menu">
|
||||
<ul class="nav navbar-nav">
|
||||
<!-- User Account: style can be found in dropdown.less -->
|
||||
<li id="dropdown-menu" class="dropdown user user-menu">
|
||||
<a href="#" class="dropdown-toggle">
|
||||
<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" />
|
||||
<span class="hidden-xs">Pi-hole</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<ul class="dropdown-menu" style="right:0">
|
||||
<!-- User image -->
|
||||
<li class="user-header">
|
||||
<img src="img/logo.svg" sizes="160x160" alt="User Image" />
|
||||
<img src="img/logo.svg" sizes="160x160" alt="User Image" style="border-color:transparent" />
|
||||
<p>
|
||||
Open Source Ad Blocker
|
||||
<small>Designed For Raspberry Pi</small>
|
||||
@@ -251,33 +252,41 @@ if($auth) {
|
||||
<!-- Menu Body -->
|
||||
<li class="user-body">
|
||||
<div class="col-xs-4 text-center">
|
||||
<a href="https://github.com/pi-hole/pi-hole">GitHub</a>
|
||||
<a class="btn-link" href="https://github.com/pi-hole/pi-hole" target="_blank">GitHub</a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center">
|
||||
<a href="http://jacobsalmela.com/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/">Details</a>
|
||||
<a class="btn-link" href="http://jacobsalmela.com/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/" target="_blank">Details</a>
|
||||
</div>
|
||||
<div class="col-xs-4 text-center">
|
||||
<a href="https://github.com/pi-hole/pi-hole/releases">Updates</a>
|
||||
<a class="btn-link" href="https://github.com/pi-hole/pi-hole/releases" target="_blank">Updates</a>
|
||||
</div>
|
||||
<div class="col-xs-12 text-center" id="sessiontimer">
|
||||
<b>Session is valid for <span id="sessiontimercounter"><?php if($auth && strlen($pwhash) > 0){echo $maxlifetime;}else{echo "0";} ?></span></b>
|
||||
</div>
|
||||
<div class="col-xs-12 text-center" id="sessiontimer">Session is valid for <span id="sessiontimercounter"><?php if($auth && strlen($pwhash) > 0){echo $maxlifetime;}else{echo "0";} ?></span></div>
|
||||
</li>
|
||||
<!-- Menu Footer -->
|
||||
<li class="user-footer">
|
||||
<!-- Update alerts -->
|
||||
<div id="alPiholeUpdate" class="alert alert-info alert-dismissible fade in" role="alert" hidden>
|
||||
<a class="alert-link" href="https://github.com/pi-hole/pi-hole/releases">There's an update available for this Pi-hole!</a>
|
||||
<!-- 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;
|
||||
if(isset($core_commit)) { echo "<br>(".$core_branch.", ".$core_commit.")"; }
|
||||
if($core_update){ ?> <a class="alert-link lookatme btn-link" href="https://github.com/pi-hole/pi-hole/releases" target="_blank" style="background:none">(Update available!)</a><?php } ?><br>
|
||||
<b>Web Interface Version </b><?php
|
||||
echo $web_current;
|
||||
if(isset($web_commit)) { echo "<br>(".$web_branch.", ".$web_commit.")"; }
|
||||
if($web_update){ ?> <a class="alert-link lookatme btn-link" href="https://github.com/pi-hole/AdminLTE/releases" target="_blank" style="background:none">(Update available!)</a><?php } ?><br>
|
||||
<b>FTL Version </b> <?php
|
||||
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>
|
||||
<div id="alWebUpdate" class="alert alert-info alert-dismissible fade in" role="alert" hidden>
|
||||
<a class="alert-link" href="https://github.com/pi-hole/AdminLTE/releases">There's an update available for this Web Interface!</a>
|
||||
</div>
|
||||
|
||||
*/ ?>
|
||||
<!-- PayPal -->
|
||||
<div>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_s-xclick">
|
||||
<input type="hidden" name="hosted_button_id" value="3J2L3Z4DHW9UY">
|
||||
<input style="display: block; margin: 0 auto;" type="image" src="img/donate.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
|
||||
</form>
|
||||
<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">
|
||||
<img src="img/donate.gif" alt="Donate">
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -389,6 +398,10 @@ if($auth) {
|
||||
$scriptname = "blacklist";
|
||||
}
|
||||
}
|
||||
if(!$auth && (!isset($indexpage) || isset($_GET['login'])))
|
||||
{
|
||||
$scriptname = "login";
|
||||
}
|
||||
?>
|
||||
<ul class="sidebar-menu">
|
||||
<li class="header">MAIN NAVIGATION</li>
|
||||
@@ -405,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="#">
|
||||
<i class="fa fa-clock-o"></i> <span>Long term data</span>
|
||||
<span class="pull-right-container">
|
||||
<i class="fa fa-angle-down pull-right" style="padding-right: 5px;"></i>
|
||||
</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">
|
||||
@@ -459,7 +497,7 @@ 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">
|
||||
@@ -479,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">
|
||||
@@ -520,7 +564,7 @@ if($auth) {
|
||||
<?php
|
||||
// Show Login button if $auth is *not* set and authorization is required
|
||||
if(strlen($pwhash) > 0 && !$auth) { ?>
|
||||
<li>
|
||||
<li<?php if($scriptname === "login"){ ?> class="active"<?php } ?>>
|
||||
<a href="index.php?login">
|
||||
<i class="fa fa-user"></i> <span>Login</span>
|
||||
</a>
|
||||
@@ -528,7 +572,7 @@ if($auth) {
|
||||
<?php } ?>
|
||||
<!-- Donate -->
|
||||
<li>
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY" target="_blank">
|
||||
<i class="fa fa-paypal"></i> <span>Donate</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */ ?>
|
||||
|
||||
<div class="mainbox col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3">
|
||||
<div class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2" style="float:none">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div style="text-align: center;"><img src="img/logo.svg" width="<?php if ($boxedlayout) { ?>50%<?php } else { ?>30%<?php } ?>"></div><br>
|
||||
|
||||
@@ -21,6 +21,11 @@ else
|
||||
$file = fopen("/var/log/pihole.log","r");
|
||||
}
|
||||
|
||||
if(!$file)
|
||||
{
|
||||
die(json_encode(array("offset" => 0, "lines" => array("Failed to open log file. Check permissions!\n"))));
|
||||
}
|
||||
|
||||
if(isset($_GET["offset"]))
|
||||
{
|
||||
$offset = intval($_GET['offset']);
|
||||
@@ -30,7 +35,7 @@ if(isset($_GET["offset"]))
|
||||
fseek($file, $offset);
|
||||
$lines = [];
|
||||
while (!feof($file))
|
||||
array_push($lines,fgets($file));
|
||||
array_push($lines, htmlspecialchars(fgets($file)));
|
||||
die(json_encode(array("offset" => ftell($file), "lines" => $lines)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<?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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
/********** 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))
|
||||
{
|
||||
// Obtain latest time stamp from buffer file
|
||||
$versions = explode(",",file_get_contents($versionfile));
|
||||
$date = date_create();
|
||||
$timestamp = date_timestamp_get($date);
|
||||
|
||||
// Is last check for updates older than 30 minutes?
|
||||
if($timestamp >= intval($versions[0]) + 1800)
|
||||
{
|
||||
// Yes: Retrieve new/updated version data
|
||||
$check_version = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No: We can use the buffered data
|
||||
$check_version = 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");
|
||||
|
||||
// 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")
|
||||
{
|
||||
// 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
|
||||
{
|
||||
$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
+1542
File diff suppressed because it is too large
Load Diff
+6
-1
@@ -9,6 +9,10 @@
|
||||
require "scripts/pi-hole/php/savesettings.php";
|
||||
// Reread ini file as things might have been changed
|
||||
$setupVars = parse_ini_file("/etc/pihole/setupVars.conf");
|
||||
|
||||
function command_exist($cmd) {
|
||||
return !empty(shell_exec(sprintf("which %s", escapeshellarg($cmd))));
|
||||
}
|
||||
?>
|
||||
<style type="text/css">
|
||||
.tooltip-inner {
|
||||
@@ -922,7 +926,8 @@ if($FTL)
|
||||
</form>
|
||||
<?php } else { ?>
|
||||
<p>The PHP extension <code>zip</code> is not loaded. Please ensure it is installed and loaded if you want to use the Pi-hole teleporter.</p>
|
||||
<?php } ?>
|
||||
<?php if(command_exist("apt-get") && command_exist("sudo")) { ?><p>Try:</p><pre>sudo apt-get install php-zip</pre><?php }
|
||||
} ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,3 +16,21 @@
|
||||
.skin-blue .list-group-item:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
@-webkit-keyframes Pulse{
|
||||
from {color:#630030;-webkit-text-shadow:0 0 2px transparent;}
|
||||
50% {color:#e33100;-webkit-text-shadow:0 0 5px #e33100;}
|
||||
to {color:#630030;-webkit-text-shadow:0 0 2px transparent;}
|
||||
}
|
||||
@keyframes Pulse{
|
||||
from {color:#630030;text-shadow:0 0 2px transparent;}
|
||||
50% {color:#e33100;text-shadow:0 0 5px #e33100;}
|
||||
to {color:#630030;text-shadow:0 0 2px transparent;}
|
||||
}
|
||||
a.lookatme {
|
||||
-webkit-animation-name: Pulse;
|
||||
animation-name: Pulse;
|
||||
-webkit-animation-duration: 2s;
|
||||
animation-duration: 2s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
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