Merge branch 'devel' into clean_up_style-rules

This commit is contained in:
TheME
2017-11-07 21:36:53 +01:00
committed by GitHub
45 changed files with 5724 additions and 1443 deletions

View File

@@ -1,33 +1,37 @@
**In raising this issue, I confirm the following (please check boxes, eg [X] - no spaces) Failure to fill the template will close your issue:**
**In raising this issue, I confirm the following:** `{please fill the checkboxes, e.g: [X]}`
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md).
- [] The issue I am reporting can be *replicated*
- [] The issue I'm reporting isn't a duplicate (see [FAQs](https://github.com/pi-hole/pi-hole/wiki/FAQs), [closed issues](https://github.com/pi-hole/pi-hole/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), and [open issues](https://github.com/pi-hole/pi-hole/issues)).
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/AdminLTE/blob/master/CONTRIBUTING.md).
- [] The issue I am reporting can be *replicated*.
- [] The issue I am reporting isn't a duplicate (see [FAQs](https://github.com/pi-hole/pi-hole/wiki/FAQs), [closed issues](https://github.com/pi-hole/AdminLTE/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), and [open issues](https://github.com/pi-hole/AdminLTE/issues)).
**How familiar are you with the codebase?:**
**How familiar are you with the the source code relevant to this issue?:**
_{replace this text with a number from 1 to 10, with 1 being not familiar, and 10 being very familiar}_
`{Replace this with a number from 1 to 10. 1 being not familiar, and 10 being very familiar}`
---
**[FEATURE REQUEST | QUESTION | OTHER]:**
**Expected behaviour:**
Please [submit your feature request here](https://discourse.pi-hole.net/c/feature-requests), so it is votable by the community. It's also easier for us to track.
`{A detailed description of what you expect to see}`
**[BUG | ISSUE] Expected Behaviour:**
**Actual behaviour:**
`{A detailed description and/or screenshots of what you do see}`
**[BUG | ISSUE] Actual Behaviour:**
**Steps to reproduce:**
`{Detailed steps of how we can reproduce this}`
**[BUG | ISSUE] Steps to reproduce:**
**Debug token provided by [uploading `pihole -d` log](https://discourse.pi-hole.net/t/the-pihole-command-with-examples/738#debug):**
-
-
-
-
`{Alphanumeric token}`
**(Optional) Debug token generated by `pihole -d`:**
**Troubleshooting undertaken, and/or other relevant information:**
`<token>`
`{Steps of what you have done to fix this}`
_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
> * `{Please delete this quoted section when opening your issue}`
> * You must follow the template instructions. Failure to do so will result in your issue being closed.
> * Please [submit any feature requests here](https://discourse.pi-hole.net/c/feature-requests), so it is votable and trackable by the community.
> * Please respect that Pi-hole is developed by volunteers, who can only reply in their spare time.
> * Detail helps us understand and resolve an issue quicker, but please ensure it's relevant.
> * _This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._

View File

@@ -1,19 +1,32 @@
**By submitting this pull request, I confirm the following (please check boxes, eg [X] - no spaces) _Failure to fill the template will close your PR_:**
**By submitting this pull request, I confirm the following:** `{please fill any appropriate checkboxes, e.g: [X]}`
***Please submit all pull requests against the `development` branch. Failure to do so will delay or deny your request***
`{Please ensure that your pull request is for the 'devel' branch!}`
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md).
- [] I have checked that [another pull request](https://github.com/pi-hole/pi-hole/pulls) for this purpose does not exist.
- [] I have considered, and confirmed that this submission will be valuable to others.
- [] I accept that this submission may not be used, and the pull request closed at the will of the maintainer.
- [] I give this submission freely, and claim no ownership to its content.
**How familiar are you with the codebase?:**
_{replace this text with a number from 1 to 10, with 1 being not familiar, and 10 being very familiar}_
- [] I have read and understood the [contributors guide](https://github.com/pi-hole/AdminLTE/blob/master/CONTRIBUTING.md), as well as this entire template.
- [] I have made only one major change in my proposed changes.
- [] I have commented my proposed changes within the code.
- [] I have tested my proposed changes.
- [] I am willing to help maintain this change if there are issues with it later.
- [] I give this submission freely and claim no ownership.
- [] It is compatible with the [EUPL 1.2 license](https://opensource.org/licenses/EUPL-1.1)
- [] I have squashed any insignificant commits. ([`git rebase`](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html))
- [] I have Signed Off all commits. ([`git commit --signoff`](https://git-scm.com/docs/git-commit#git-commit---signoff))
---
_{replace this line with your pull request content}_
**What does this PR aim to accomplish?:**
_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
`{A detailed description, screenshots (if necessary), as well as links to any relevant GitHub issues}`
**How does this PR accomplish the above?:**
`{A detailed description (such as a changelog) and screenshots (if necessary) of the implemented fix}`
**What documentation changes (if any) are needed to support this PR?:**
`{A detailed list of any necessary changes}`
> * `{Please delete this quoted section when opening your pull request}`
> * You must follow the template instructions. Failure to do so will result in your issue being closed.
> * Please respect that Pi-hole is developed by volunteers, who can only reply in their spare time.
> * Detail helps us understand an issue quicker, but please ensure it's relevant.

View File

@@ -35,4 +35,4 @@ groups:
- master
required: 4
teams:
- admin
- approvers

View File

@@ -1,10 +1,38 @@
This is a basic checklist for now, We will update it in the future.
_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
* Fork the repo and create your new branch based on the `devel` (development) branch.
* Use 4 spaces instead of tabs
* Commit Unix line endings
* If you want, try to keep to the theme of black holes/gravity. This can add some fun to your submission.
* Submit Pull Requests to the development branch only.
* Before Submitting your Pull Request, merge `devel` with your new branch and fix any conflicts. (Make sure you don't break anything in development!)
* Be patient. We will review all submitted pull requests, but our focus is on stability.. please don't be offended if we reject your PR, or it appears we're doing nothing with it! We'll get around to it..
* Please use the Pi-hole brand: **Pi-hole** (Take a special look at the capitalized 'P' and a low 'h' with a hyphen)
# Contributors Guide
Please read and understand the contribution guide before creating an issue or pull request.
## Etiquette
- Our goal for Pi-hole is **stability before features**. This means we focus on squashing critical bugs before adding new features. Often, we can do both in tandem, but bugs will take priority over a new feature.
- Pi-hole is open source and [powered by donations](https://pi-hole.net/donate/), and as such, we give our **free time** to build, maintain, and **provide user support** for this project. It would be extremely unfair for us to suffer abuse or anger for our hard work, so please take a moment to consider that.
- Please be considerate towards the developers and other users when raising issues or presenting pull requests.
- Respect our decision(s), and do not be upset or abusive if your submission is not used.
## Viability
When requesting or submitting new features, first consider whether it might be useful to others. Open source projects are used by many people, who may have entirely different needs to your own. Think about whether or not your feature is likely to be used by other users of the project.
## Procedure
**Before filing an issue:**
- Attempt to replicate and **document** the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.
**Before submitting a pull request:**
- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
## Technical Requirements
- Submit Pull Requests to the **devel branch only**.
- Before Submitting your Pull Request, merge `devel` with your new branch and fix any conflicts. (Make sure you don't break anything in development!)
- Commit Unix line endings.
- Please use the Pi-hole brand: **Pi-hole** (Take a special look at the capitalized 'P' and a low 'h' with a hyphen)
- (Optional fun) keep to the theme of Star Trek/black holes/gravity.

119
LICENSE
View File

@@ -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 Licensees 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.

View File

@@ -13,11 +13,12 @@ require("scripts/pi-hole/php/password.php");
require("scripts/pi-hole/php/auth.php");
check_cors();
$FTL_IP = "127.0.0.1";
$data = array();
// Common API functions
if (isset($_GET['status']) && $auth)
if (isset($_GET['status']))
{
$pistatus = exec('sudo pihole status web');
if ($pistatus == "1")
@@ -74,7 +75,7 @@ elseif (isset($_GET['disable']) && $auth)
}
// Other API functions
if(!testFTL() && !isset($_GET["PHP"]))
if(!testFTL($FTL_IP) && !isset($_GET["PHP"]))
{
$data = array_merge($data, array("FTLnotrunning" => true));
}

View File

@@ -12,7 +12,8 @@ if(!isset($api))
die("Direct call to api_FTL.php is not allowed!");
}
$socket = connectFTL("127.0.0.1");
// $FTL_IP is defined in api.php
$socket = connectFTL($FTL_IP);
if (isset($_GET['type'])) {
$data["type"] = "FTL";
@@ -32,6 +33,12 @@ if (isset($_GET['summary']) || isset($_GET['summaryRaw']) || !count($_GET))
{
$tmp = explode(" ",$line);
if(($tmp[0] === "domains_being_blocked" && !is_numeric($tmp[1])) || $tmp[0] === "status")
{
$stats[$tmp[0]] = $tmp[1];
continue;
}
if(isset($_GET['summary']))
{
if($tmp[0] !== "ads_percentage_today")
@@ -71,7 +78,11 @@ if (isset($_GET['overTimeData10mins']))
if (isset($_GET['topItems']) && $auth)
{
if(is_numeric($_GET['topItems']))
if($_GET['topItems'] === "audit")
{
sendRequestFTL("top-domains for audit");
}
else if(is_numeric($_GET['topItems']))
{
sendRequestFTL("top-domains (".$_GET['topItems'].")");
}
@@ -88,7 +99,11 @@ if (isset($_GET['topItems']) && $auth)
$top_queries[$tmp[2]] = intval($tmp[1]);
}
if(is_numeric($_GET['topItems']))
if($_GET['topItems'] === "audit")
{
sendRequestFTL("top-ads for audit");
}
else if(is_numeric($_GET['topItems']))
{
sendRequestFTL("top-ads (".$_GET['topItems'].")");
}
@@ -102,7 +117,10 @@ if (isset($_GET['topItems']) && $auth)
foreach($return as $line)
{
$tmp = explode(" ",$line);
$top_ads[$tmp[2]] = intval($tmp[1]);
if(count($tmp) === 4)
$top_ads[$tmp[2]." (".$tmp[3].")"] = intval($tmp[1]);
else
$top_ads[$tmp[2]] = intval($tmp[1]);
}
$result = array('top_queries' => $top_queries,
@@ -153,7 +171,14 @@ if ((isset($_GET['topClients']) || isset($_GET['getQuerySources'])) && $auth)
if (isset($_GET['getForwardDestinations']) && $auth)
{
sendRequestFTL("forward-dest");
if($_GET['getForwardDestinations'] === "unsorted")
{
sendRequestFTL("forward-dest unsorted");
}
else
{
sendRequestFTL("forward-dest");
}
$return = getResponseFTL();
$forward_dest = array();
foreach($return as $line)
@@ -161,11 +186,11 @@ if (isset($_GET['getForwardDestinations']) && $auth)
$tmp = explode(" ",$line);
if(count($tmp) == 4)
{
$forward_dest[$tmp[3]."|".$tmp[2]] = intval($tmp[1]);
$forward_dest[$tmp[3]."|".$tmp[2]] = floatval($tmp[1]);
}
else
{
$forward_dest[$tmp[2]] = intval($tmp[1]);
$forward_dest[$tmp[2]] = floatval($tmp[1]);
}
}
@@ -181,7 +206,7 @@ if (isset($_GET['getQueryTypes']) && $auth)
foreach($return as $ret)
{
$tmp = explode(": ",$ret);
$querytypes[$tmp[0]] = intval($tmp[1]);
$querytypes[$tmp[0]] = floatval($tmp[1]);
}
$result = array('querytypes' => $querytypes);
@@ -239,7 +264,7 @@ if (isset($_GET['overTimeDataForwards']) && $auth)
{
$tmp = explode(" ",$line);
for ($i=0; $i < count($tmp)-1; $i++) {
$over_time[intval($tmp[0])][$i] = intval($tmp[$i+1]);
$over_time[intval($tmp[0])][$i] = floatval($tmp[$i+1]);
}
}
$result = array('over_time' => $over_time);
@@ -256,11 +281,11 @@ if (isset($_GET['getForwardDestinationNames']) && $auth)
$tmp = explode(" ",$line);
if(count($tmp) == 4)
{
$forward_dest[$tmp[3]."|".$tmp[2]] = intval($tmp[1]);
$forward_dest[$tmp[3]."|".$tmp[2]] = floatval($tmp[1]);
}
else
{
$forward_dest[$tmp[2]] = intval($tmp[1]);
$forward_dest[$tmp[2]] = floatval($tmp[1]);
}
}
@@ -278,7 +303,46 @@ if (isset($_GET['overTimeDataQueryTypes']) && $auth)
{
$tmp = explode(" ",$line);
for ($i=0; $i < count($tmp)-1; $i++) {
$over_time[intval($tmp[0])][$i] = intval($tmp[$i+1]);
$over_time[intval($tmp[0])][$i] = floatval($tmp[$i+1]);
}
}
$result = array('over_time' => $over_time);
$data = array_merge($data, $result);
}
if (isset($_GET['getClientNames']) && $auth)
{
sendRequestFTL("client-names");
$return = getResponseFTL();
$forward_dest = array();
foreach($return as $line)
{
$tmp = explode(" ",$line);
if(count($tmp) == 4)
{
$forward_dest[$tmp[3]."|".$tmp[2]] = floatval($tmp[1]);
}
else
{
$forward_dest[$tmp[2]] = floatval($tmp[1]);
}
}
$result = array('clients' => $forward_dest);
$data = array_merge($data, $result);
}
if (isset($_GET['overTimeDataClients']) && $auth)
{
sendRequestFTL("ClientsoverTime");
$return = getResponseFTL();
$over_time = array();
foreach($return as $line)
{
$tmp = explode(" ",$line);
for ($i=0; $i < count($tmp)-1; $i++) {
$over_time[intval($tmp[0])][$i] = floatval($tmp[$i+1]);
}
}
$result = array('over_time' => $over_time);

225
api_db.php Normal file
View File

@@ -0,0 +1,225 @@
<?php
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license */
$api = true;
header('Content-type: application/json');
require("scripts/pi-hole/php/password.php");
require("scripts/pi-hole/php/auth.php");
check_cors();
$data = array();
// Needs package php5-sqlite, e.g.
// sudo apt-get install php5-sqlite
function SQLite3_connect($trytoreconnect)
{
try {
// connect to database
return new SQLite3('/etc/pihole/pihole-FTL.db', SQLITE3_OPEN_READONLY);
}
catch (Exception $exception) {
// sqlite3 throws an exception when it is unable to connect, try to reconnect after 3 seconds
if($trytoreconnect)
{
sleep(3);
$db = SQLite3_connect(false);
}
}
}
$db = SQLite3_connect(true);
if(!$db)
{
die("Error connecting to database");
}
if (isset($_GET['getAllQueries']) && $auth)
{
if($_GET['getAllQueries'] === "empty")
{
$allQueries = array();
}
else
{
$from = intval($_GET["from"]);
$until = intval($_GET["until"]);
$results = $db->query('SELECT timestamp,type,domain,client,status FROM queries WHERE timestamp >= '.$from.' AND timestamp <= '.$until.' ORDER BY timestamp ASC');
$allQueries = array();
while ($row = $results->fetchArray())
{
$allQueries[] = [$row[0],$row[1] == 1 ? "IPv4" : "IPv6",$row[2],$row[3],$row[4]];
}
}
$result = array('data' => $allQueries);
$data = array_merge($data, $result);
}
if (isset($_GET['topClients']) && $auth)
{
// $from = intval($_GET["from"]);
$limit = "";
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = "WHERE timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = "WHERE timestamp >= ".$_GET["from"];
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = "WHERE timestamp <= ".$_GET["until"];
}
$results = $db->query('SELECT client,count(client) FROM queries '.$limit.' GROUP by client order by count(client) desc limit 10');
$clients = array();
while ($row = $results->fetchArray())
{
$clients[$row[0]] = intval($row[1]);
// var_dump($row);
}
$result = array('top_sources' => $clients);
$data = array_merge($data, $result);
}
if (isset($_GET['topDomains']) && $auth)
{
$limit = "";
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"];
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp <= ".$_GET["until"];
}
$results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 2 OR STATUS == 3)'.$limit.' GROUP by domain order by count(domain) desc limit 10');
$domains = array();
while ($row = $results->fetchArray())
{
$domains[$row[0]] = intval($row[1]);
}
$result = array('top_domains' => $domains);
$data = array_merge($data, $result);
}
if (isset($_GET['topAds']) && $auth)
{
$limit = "";
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"]." AND timestamp <= ".$_GET["until"];
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = " AND timestamp >= ".$_GET["from"];
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp <= ".$_GET["until"];
}
$results = $db->query('SELECT domain,count(domain) FROM queries WHERE (STATUS == 1 OR STATUS == 4)'.$limit.' GROUP by domain order by count(domain) desc limit 10');
$addomains = array();
while ($row = $results->fetchArray())
{
$addomains[$row[0]] = intval($row[1]);
}
$result = array('top_ads' => $addomains);
$data = array_merge($data, $result);
}
if (isset($_GET['getMinTimestamp']) && $auth)
{
$results = $db->query('SELECT MIN(timestamp) FROM queries');
$result = array('mintimestamp' => $results->fetchArray()[0]);
$data = array_merge($data, $result);
}
if (isset($_GET['getMaxTimestamp']) && $auth)
{
$results = $db->query('SELECT MAX(timestamp) FROM queries');
$result = array('maxtimestamp' => $results->fetchArray()[0]);
$data = array_merge($data, $result);
}
if (isset($_GET['getQueriesCount']) && $auth)
{
$results = $db->query('SELECT COUNT(timestamp) FROM queries');
$result = array('count' => $results->fetchArray()[0]);
$data = array_merge($data, $result);
}
if (isset($_GET['getDBfilesize']) && $auth)
{
$filesize = filesize("/etc/pihole/pihole-FTL.db");
$result = array('filesize' => $filesize);
$data = array_merge($data, $result);
}
if (isset($_GET['getGraphData']) && $auth)
{
$limit = "";
if(isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp >= ".intval($_GET["from"])." AND timestamp <= ".intval($_GET["until"]);
}
elseif(isset($_GET["from"]) && !isset($_GET["until"]))
{
$limit = " AND timestamp >= ".intval($_GET["from"]);
}
elseif(!isset($_GET["from"]) && isset($_GET["until"]))
{
$limit = " AND timestamp <= ".intval($_GET["until"]);
}
$interval = 600;
if(isset($_GET["interval"]))
{
$q = intval($_GET["interval"]);
if($q > 10)
$interval = $q;
}
// Count permitted queries in intervals
$results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 2 OR status == 3)'.$limit.' GROUP by interval ORDER by interval');
$domains = array();
while ($row = $results->fetchArray())
{
$domains[$row[0]] = intval($row[1]);
}
$result = array('domains_over_time' => $domains);
$data = array_merge($data, $result);
// Count blocked queries in intervals
$results = $db->query('SELECT (timestamp/'.$interval.')*'.$interval.' interval, COUNT(*) FROM queries WHERE (status == 1 OR status == 4 OR status == 5)'.$limit.' GROUP by interval ORDER by interval');
$addomains = array();
while ($row = $results->fetchArray())
{
$addomains[$row[0]] = intval($row[1]);
}
$result = array('ads_over_time' => $addomains);
$data = array_merge($data, $result);
}
if(isset($_GET["jsonForceObject"]))
{
echo json_encode($data, JSON_FORCE_OBJECT);
}
else
{
echo json_encode($data);
}

89
auditlog.php Normal file
View File

@@ -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>

67
db_graph.php Normal file
View File

@@ -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
db_lists.php Normal file
View File

@@ -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
db_queries.php Normal file
View File

@@ -0,0 +1,145 @@
<?php /*
* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
require "scripts/pi-hole/php/header.php";
// Generate CSRF token
if(empty($_SESSION['token'])) {
$_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
}
$token = $_SESSION['token'];
?>
<!-- Send PHP info to JS -->
<div id="token" hidden><?php echo $token ?></div>
<!-- Title -->
<div class="page-header">
<h1>Specify date range to be queried from the Pi-hole query database</h1>
</div>
<div class="row">
<div class="col-md-12">
<!-- Date Input -->
<div class="form-group">
<label>Date and time range:</label>
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-clock-o"></i>
</div>
<input type="text" class="form-control pull-right" id="querytime">
</div>
<!-- /.input group -->
</div>
</div>
</div>
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-aqua">
<div class="inner">
<h3 class="statistic" id="ads_blocked_exact">---</h3>
<p>Queries Blocked</p>
</div>
<div class="icon">
<i class="ion ion-android-hand"></i>
</div>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-aqua">
<div class="inner">
<h3 class="statistic" id="ads_wildcard_blocked">---</h3>
<p>Queries Blocked (Wildcards)</p>
</div>
<div class="icon">
<i class="ion ion-android-hand"></i>
</div>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-green">
<div class="inner">
<h3 class="statistic" id="dns_queries">---</h3>
<p>Queries Total</p>
</div>
<div class="icon">
<i class="ion ion-earth"></i>
</div>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-yellow">
<div class="inner">
<h3 class="statistic" id="ads_percentage_today">---</h3>
<p>Queries Blocked</p>
</div>
<div class="icon">
<i class="ion ion-pie-graph"></i>
</div>
</div>
</div>
<!-- ./col -->
</div>
<div class="row">
<div class="col-md-12">
<div class="box" id="recent-queries">
<div class="box-header with-border">
<h3 class="box-title">Recent Queries</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<div class="table-responsive">
<table id="all-queries" class="display table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Time</th>
<th>Type</th>
<th>Domain</th>
<th>Client</th>
<th>Status</th>
<th>Action</th>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</div>
<!-- /.row -->
<?php
require "scripts/pi-hole/php/footer.php";
?>
<script src="scripts/vendor/moment.min.js"></script>
<script src="scripts/vendor/daterangepicker.js"></script>
<script src="scripts/pi-hole/js/db_queries.js"></script>

View File

@@ -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 -->

View File

@@ -70,7 +70,7 @@
<div class="col-md-12">
<h2>White- / Blacklist</h2>
<p>Add or remove domains (or subdomains) from the white-/blacklist. If a domain is added to e.g. the whitelist, any possible entry of the same domain will be automatically removed from the blacklist and vice versa.</p>
<p>Wildcard blacklisting is supported (entering <tt>something.de</tt> will block this domain including all subdomains like <tt>a.bb.c.999.something.de</tt>). Note that wildcard whitelisting is <em>not</em> supported.</p>
<p>Wildcard blacklisting is supported (entering <samp>something.de</samp> will block this domain including all subdomains like <samp>a.bb.c.999.something.de</samp>). Note that wildcard whitelisting is <em>not</em> supported.</p>
<p>You can white-/blacklist multiple entries at a time if you separate the domains by spaces.</p>
</div>
</div>
@@ -89,7 +89,7 @@
<div class="row">
<div class="col-md-12">
<h2>Tools &rarr; Query adlists</h2>
This function is useful to find out what list a domain appears on. Since we don't control what the third-parties put on the block lists, you may find that a domain you normally visit stops working. If this is the case, you could run this command to scan for strings in the list of blocked domains and it will return the list the domain is found on. This proved useful a while back when the Mahakala list was adding <tt>apple.com</tt> and <tt>microsoft.com</tt> to their block list.</p>
This function is useful to find out what list a domain appears on. Since we don't control what the third-parties put on the block lists, you may find that a domain you normally visit stops working. If this is the case, you could run this command to scan for strings in the list of blocked domains and it will return the list the domain is found on. This proved useful a while back when the Mahakala list was adding <samp>apple.com</samp> and <samp>microsoft.com</samp> to their block list.</p>
</div>
</div>
<div class="row">

147
index.php
View File

@@ -10,25 +10,12 @@
?>
<!-- Small boxes (Stat box) -->
<div class="row">
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-aqua">
<div class="inner">
<h3 class="statistic" id="ads_blocked_today">---</h3>
<p>Queries Blocked Last 24 Hours</p>
</div>
<div class="icon">
<i class="ion ion-android-hand"></i>
</div>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-green">
<div class="inner">
<h3 class="statistic" id="dns_queries_today">---</h3>
<p>Queries Last 24 Hours</p>
<p>Total queries (<span id="unique_clients">-</span> clients)</p>
<h3 class="statistic"><span id="dns_queries_today">---</span></h3>
</div>
<div class="icon">
<i class="ion ion-earth"></i>
@@ -38,23 +25,53 @@
<!-- ./col -->
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-yellow">
<div class="small-box bg-aqua">
<div class="inner">
<h3 class="statistic" id="ads_percentage_today">---</h3>
<p>Queries Blocked Last 24 Hours</p>
<p>Queries Blocked</p>
<h3 class="statistic"><span id="ads_blocked_today">---</span></h3>
</div>
<div class="icon">
<i class="ion ion-pie-graph"></i>
<i class="ion ion-android-hand"></i>
</div>
</div>
</div>
<!-- ./col -->
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-red">
<div class="small-box bg-yellow">
<div class="inner">
<h3 class="statistic" id="domains_being_blocked">---</h3>
<p>Domains on Blocklists</p>
<p>Percent Blocked</p>
<h3 class="statistic"><span id="ads_percentage_today">---</span></h3>
</div>
<div class="icon">
<i class="ion ion-pie-graph"></i>
</div>
</div>
</div>
<?php
$gravitylist = "/etc/pihole/gravity.list";
if (file_exists($gravitylist))
{
$gravitydiff = date_diff(date_create("@".filemtime($gravitylist)),date_create("now"));
if($gravitydiff->d > 1)
$gravitydate = $gravitydiff->format("Blocking list updated %a days, %H:%I ago");
elseif($gravitydiff->d == 1)
$gravitydate = $gravitydiff->format("Blocking list updated one day, %H:%I ago");
else
$gravitydate = $gravitydiff->format("Blocking list updated %H:%I ago");
}
else
{
$gravitydate = "Blocking list not found";
}
?>
<!-- ./col -->
<div class="col-lg-3 col-xs-12">
<!-- small box -->
<div class="small-box bg-red" title="<?php echo $gravitydate; ?>">
<div class="inner">
<p>Domains on Blocklist</p>
<h3 class="statistic"><span id="domains_being_blocked">---</span></h3>
</div>
<div class="icon">
<i class="ion ion-ios-list"></i>
@@ -72,7 +89,7 @@
</div>
<div class="box-body">
<div class="chart">
<canvas id="queryOverTimeChart" width="800" height="250"></canvas>
<canvas id="queryOverTimeChart" width="800" height="140"></canvas>
</div>
</div>
<div class="overlay">
@@ -88,11 +105,88 @@
// show since the API will respect the privacy of the user if he defines
// a password
if($auth){ ?>
<div class="row">
<div class="col-md-12">
<div class="box" id="clients">
<div class="box-header with-border">
<h3 class="box-title">Clients (over time)</h3>
</div>
<div class="box-body">
<div class="chart">
<canvas id="clientsChart" width="800" height="140"></canvas>
</div>
</div>
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
<!-- /.box-body -->
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 col-lg-6">
<div class="box" id="query-types-pie">
<div class="box-header with-border">
<h3 class="box-title">Query Types (integrated)</h3>
</div>
<div class="box-body">
<div class="chart">
<canvas id="queryTypePieChart" width="400" height="150"></canvas>
</div>
</div>
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
<!-- /.box-body -->
</div>
</div>
<div class="col-md-12 col-lg-6">
<div class="box" id="forward-destinations-pie">
<div class="box-header with-border">
<h3 class="box-title">Forward Destinations (integrated)</h3>
</div>
<div class="box-body">
<div class="chart">
<canvas id="forwardDestinationPieChart" width="400" height="150"></canvas>
</div>
</div>
<div class="overlay">
<i class="fa fa-refresh fa-spin"></i>
</div>
<!-- /.box-body -->
</div>
</div>
</div>
<?php
// Determine if "Query Types (over time)" should be shown
$queryTypesOverTime = false;
if(isset($setupVars['DASHBOARD_SHOW_QUERY_TYPES_OVER_TIME']))
{
if($setupVars['DASHBOARD_SHOW_QUERY_TYPES_OVER_TIME'])
{
$queryTypesOverTime = true;
}
}
// Determine if "Forward Destinations (over time)" should be shown
$forwardDestsOverTime = false;
if(isset($setupVars['DASHBOARD_SHOW_FORWARD_DESTS_OVER_TIME']))
{
if($setupVars['DASHBOARD_SHOW_FORWARD_DESTS_OVER_TIME'])
{
$forwardDestsOverTime = true;
}
}
?>
<?php if($forwardDestsOverTime || $queryTypesOverTime) { ?>
<div class="row">
<?php if($queryTypesOverTime) { ?>
<div class="col-md-12 col-lg-6">
<div class="box" id="query-types">
<div class="box-header with-border">
<h3 class="box-title">Query Types over Time</h3>
<h3 class="box-title">Query Types (over time)</h3>
</div>
<div class="box-body">
<div class="chart">
@@ -105,10 +199,11 @@
<!-- /.box-body -->
</div>
</div>
<?php } if($forwardDestsOverTime) { ?>
<div class="col-md-12 col-lg-6">
<div class="box" id="forward-destinations">
<div class="box-header with-border">
<h3 class="box-title">Forward Destinations over Time</h3>
<h3 class="box-title">Forward Destinations (over time)</h3>
</div>
<div class="box-body">
<div class="chart">
@@ -121,7 +216,9 @@
<!-- /.box-body -->
</div>
</div>
<?php } ?>
</div>
<?php } ?>
<?php
if($boxedlayout)

View File

@@ -11,7 +11,7 @@ $list = $_GET['l'];
if($list !== "white" && $list !== "black"){
echo "Invalid list parameter";
require "footer.php";
require "scripts/pi-hole/php/footer.php";
die();
}

View File

@@ -1,4 +1,4 @@
<?php /*
<?php /*
* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
@@ -134,6 +134,7 @@ if(strlen($showing) > 0)
</tfoot>
</table>
</div>
<button type="button" id="resetButton" hidden="true">Clear Filters</button>
</div>
<!-- /.box-body -->
</div>

View File

@@ -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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"\'": "&#039;"
};
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");
});

View File

@@ -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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"\'": "&#039;"
};
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;
});

View File

@@ -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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"\'": "&#039;"
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function updateTopClientsChart() {
$("#client-frequency .overlay").show();
$.getJSON("api_db.php?topClients&from="+from+"&until="+until, function(data) {
// Clear tables before filling them with data
$("#client-frequency td").parent().remove();
var clienttable = $("#client-frequency").find("tbody:last");
var client, percentage, clientname, clientip;
var sum = 0;
for (client in data.top_sources) {
if ({}.hasOwnProperty.call(data.top_sources, client)){
sum += data.top_sources[client];
}
}
for (client in data.top_sources) {
if ({}.hasOwnProperty.call(data.top_sources, client)){
// Sanitize client
client = escapeHtml(client);
if(escapeHtml(client) !== client)
{
// Make a copy with the escaped index if necessary
data.top_sources[escapeHtml(client)] = data.top_sources[client];
}
if(client.indexOf("|") > -1)
{
var idx = client.indexOf("|");
clientname = client.substr(0, idx);
clientip = client.substr(idx+1, client.length-idx);
}
else
{
clientname = client;
clientip = client;
}
var url = clientname;
percentage = data.top_sources[client] / sum * 100.0;
clienttable.append("<tr> <td>" + url +
"</td> <td>" + data.top_sources[client] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"% of " + sum + "\"> <div class=\"progress-bar progress-bar-blue\" style=\"width: " +
percentage + "%\"></div> </div> </td> </tr> ");
}
}
$("#client-frequency .overlay").hide();
});
}
function updateTopDomainsChart() {
$("#domain-frequency .overlay").show();
$.getJSON("api_db.php?topDomains&from="+from+"&until="+until, function(data) {
// Clear tables before filling them with data
$("#domain-frequency td").parent().remove();
var domaintable = $("#domain-frequency").find("tbody:last");
var domain, percentage;
var sum = 0;
for (domain in data.top_domains) {
if ({}.hasOwnProperty.call(data.top_domains, domain)){
sum += data.top_domains[domain];
}
}
for (domain in data.top_domains) {
if ({}.hasOwnProperty.call(data.top_domains, domain)){
// Sanitize domain
domain = escapeHtml(domain);
if(escapeHtml(domain) !== domain)
{
// Make a copy with the escaped index if necessary
data.top_domains[escapeHtml(domain)] = data.top_domains[domain];
}
percentage = data.top_domains[domain] / sum * 100.0;
domaintable.append("<tr> <td>" + domain +
"</td> <td>" + data.top_domains[domain] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"% of " + sum + "\"> <div class=\"progress-bar progress-bar-blue\" style=\"width: " +
percentage + "%\"></div> </div> </td> </tr> ");
}
}
$("#domain-frequency .overlay").hide();
});
}
function updateTopAdsChart() {
$("#ad-frequency .overlay").show();
$.getJSON("api_db.php?topAds&from="+from+"&until="+until, function(data) {
// Clear tables before filling them with data
$("#ad-frequency td").parent().remove();
var adtable = $("#ad-frequency").find("tbody:last");
var ad, percentage;
var sum = 0;
for (ad in data.top_ads) {
if ({}.hasOwnProperty.call(data.top_ads, ad)){
sum += data.top_ads[ad];
}
}
for (ad in data.top_ads) {
if ({}.hasOwnProperty.call(data.top_ads, ad)){
// Sanitize ad
ad = escapeHtml(ad);
if(escapeHtml(ad) !== ad)
{
// Make a copy with the escaped index if necessary
data.top_ads[escapeHtml(ad)] = data.top_ads[ad];
}
percentage = data.top_ads[ad] / sum * 100.0;
adtable.append("<tr> <td>" + ad + "</td> <td>" + data.top_ads[ad] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"% of " + sum + "\"> <div class=\"progress-bar progress-bar-blue\" style=\"width: " + percentage + "%\"></div> </div> </td> </tr> ");
}
}
$("#ad-frequency .overlay").hide();
});
}
$("#querytime").on("apply.daterangepicker", function(ev, picker) {
updateTopClientsChart();
updateTopDomainsChart();
updateTopAdsChart();
});

View File

@@ -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();
});

View File

@@ -28,7 +28,17 @@ function eventsource() {
alSuccess.show();
}
ta.append(e.data);
// Detect ${OVER}
if(e.data.indexOf("<------") !== -1)
{
ta.text(ta.text().substring(0, ta.text().lastIndexOf("\n")) + "\n");
var new_string = e.data.replace("<------", "");
ta.append(new_string);
}
else
{
ta.append(e.data);
}
}, false);

View File

@@ -5,7 +5,9 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
// Define global variables
/* global Chart */
var timeLineChart, queryTypeChart, forwardDestinationChart;
var queryTypePieChart, forwardDestinationPieChart, clientsChart;
function padNumber(num) {
return ("00" + num).substr(-2,2);
@@ -96,6 +98,8 @@ function updateQueryTypesOverTime() {
// convert received objects to arrays
data.over_time = objectToArray(data.over_time);
// remove last data point since it not representative
data.over_time[0].splice(-1,1);
var timestamps = data.over_time[0];
var plotdata = data.over_time[1];
// Remove possibly already existing data
@@ -116,17 +120,9 @@ function updateQueryTypesOverTime() {
// New style: Get Unix timestamps
d = new Date(1000*h);
var sum = plotdata[j][0] + plotdata[j][1];
var A = 0, AAAA = 0;
if(sum > 0)
{
A = plotdata[j][0]/sum;
AAAA = plotdata[j][1]/sum;
}
queryTypeChart.data.labels.push(d);
queryTypeChart.data.datasets[0].data.push(A);
queryTypeChart.data.datasets[1].data.push(AAAA);
queryTypeChart.data.datasets[0].data.push(1e-2*plotdata[j][0]);
queryTypeChart.data.datasets[1].data.push(1e-2*plotdata[j][1]);
}
}
$("#query-types .overlay").hide();
@@ -145,6 +141,48 @@ function updateQueryTypesOverTime() {
}
});
}
function updateQueryTypesPie() {
$.getJSON("api.php?getQueryTypes", function(data) {
if("FTLnotrunning" in data)
{
return;
}
var colors = [];
// Get colors from AdminLTE
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
var v = [], c = [], k = [], iter;
// Collect values and colors, and labels
if(data.hasOwnProperty("querytypes"))
{
iter = data.querytypes;
}
else
{
iter = data;
}
$.each(iter, function(key , value) {
v.push(value);
c.push(colors.shift());
k.push(key);
});
// Build a single dataset with the data to be pushed
var dd = {data: v, backgroundColor: c};
// and push it at once
queryTypePieChart.data.datasets[0] = dd;
queryTypePieChart.data.labels = k;
$("#query-types-pie .overlay").hide();
queryTypePieChart.update();
queryTypePieChart.chart.config.options.cutoutPercentage=50;
queryTypePieChart.update();
// Don't use rotation animation for further updates
queryTypePieChart.options.animation.duration=0;
}).done(function() {
// Reload graph after minute
setTimeout(updateQueryTypesPie, 60000);
});
}
function updateForwardedOverTime() {
$.getJSON("api.php?overTimeDataForwards&getForwardDestinationNames", function(data) {
@@ -156,6 +194,8 @@ function updateForwardedOverTime() {
// convert received objects to arrays
data.over_time = objectToArray(data.over_time);
// remove last data point since it not representative
data.over_time[0].splice(-1,1);
var timestamps = data.over_time[0];
var plotdata = data.over_time[1];
var labels = [];
@@ -189,6 +229,7 @@ function updateForwardedOverTime() {
forwardDestinationChart.data.datasets[0].pointHitRadius = 5;
forwardDestinationChart.data.datasets[0].pointHoverRadius = 5;
forwardDestinationChart.data.datasets[0].label = labels[0];
forwardDestinationChart.data.datasets[0].cubicInterpolationMode = "monotone";
for (i = forwardDestinationChart.data.datasets.length; i < plotdata[0].length; i++)
{
@@ -199,18 +240,10 @@ function updateForwardedOverTime() {
for (j in timestamps)
{
if (!{}.hasOwnProperty.call(timestamps, j)) continue;
var sum = 0.0;
for (key in plotdata[j])
{
if (!{}.hasOwnProperty.call(plotdata[j], key)) continue;
sum += plotdata[j][key];
}
var dd = [];
for (key in plotdata[j])
{
if (!{}.hasOwnProperty.call(plotdata[j], key)) continue;
var singlepoint = plotdata[j][key];
forwardDestinationChart.data.datasets[key].data.push(singlepoint/sum);
forwardDestinationChart.data.datasets[key].data.push(1e-2*plotdata[j][key]);
}
var d = new Date(1000*parseInt(timestamps[j]));
@@ -233,6 +266,138 @@ function updateForwardedOverTime() {
});
}
function updateClientsOverTime() {
$.getJSON("api.php?overTimeDataClients&getClientNames", function(data) {
if("FTLnotrunning" in data)
{
return;
}
// convert received objects to arrays
data.over_time = objectToArray(data.over_time);
// remove last data point since it not representative
data.over_time[0].splice(-1,1);
var timestamps = data.over_time[0];
var plotdata = data.over_time[1];
var labels = [];
var key, i, j;
for (key in data.clients)
{
if (!{}.hasOwnProperty.call(data.clients, key)) continue;
if(key.indexOf("|") > -1)
{
var idx = key.indexOf("|");
key = key.substr(0, idx);
}
labels.push(key);
}
// Get colors from AdminLTE
var colors = [];
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
var v = [], c = [], k = [];
// Remove possibly already existing data
clientsChart.data.labels = [];
clientsChart.data.datasets[0].data = [];
for (i = 1; i < clientsChart.data.datasets.length; i++)
{
clientsChart.data.datasets[i].data = [];
}
// Collect values and colors, and labels
clientsChart.data.datasets[0].backgroundColor = colors[0];
clientsChart.data.datasets[0].pointRadius = 0;
clientsChart.data.datasets[0].pointHitRadius = 5;
clientsChart.data.datasets[0].pointHoverRadius = 5;
clientsChart.data.datasets[0].label = labels[0];
for (i = clientsChart.data.datasets.length; plotdata.length && i < plotdata[0].length; i++)
{
clientsChart.data.datasets.push({data: [], backgroundColor: colors[i], pointRadius: 0, pointHitRadius: 5, pointHoverRadius: 5, label: labels[i], cubicInterpolationMode: "monotone" });
}
// Add data for each dataset that is available
for (j in timestamps)
{
if (!{}.hasOwnProperty.call(timestamps, j)) continue;
for (key in plotdata[j])
{
if (!{}.hasOwnProperty.call(plotdata[j], key)) continue;
clientsChart.data.datasets[key].data.push(plotdata[j][key]);
}
var d = new Date(1000*parseInt(timestamps[j]));
clientsChart.data.labels.push(d);
}
$("#clients .overlay").hide();
clientsChart.update();
}).done(function() {
// Reload graph after 10 minutes
failures = 0;
setTimeout(updateClientsOverTime, 600000);
}).fail(function() {
failures++;
if(failures < 5)
{
// Try again after 1 minute only if this has not failed more
// than five times in a row
setTimeout(updateClientsOverTime, 60000);
}
});
}
function updateForwardDestinationsPie() {
$.getJSON("api.php?getForwardDestinations=unsorted", function(data) {
if("FTLnotrunning" in data)
{
return;
}
var colors = [];
// Get colors from AdminLTE
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
var v = [], c = [], k = [], values = [];
// Collect values and colors
$.each(data.forward_destinations, function(key , value) {
if(key.indexOf("|") > -1)
{
key = key.substr(0, key.indexOf("|"));
}
values.push([key, value, colors.shift()]);
});
// Sort data ASC accorwing to 2nd column, keep already assigned labels and colors
values = values.sort(function(a,b) { return b[1] - a[1]; });
// Split data into individual arrays for the graphs
$.each(values, function(key , value) {
k.push(value[0]);
v.push(value[1]);
c.push(value[2]);
});
// Build a single dataset with the data to be pushed
var dd = {data: v, backgroundColor: c};
// and push it at once
forwardDestinationPieChart.data.labels = k;
forwardDestinationPieChart.data.datasets[0] = dd;
// and push it at once
$("#forward-destinations-pie .overlay").hide();
forwardDestinationPieChart.update();
forwardDestinationPieChart.chart.config.options.cutoutPercentage=50;
forwardDestinationPieChart.update();
// Don't use rotation animation for further updates
forwardDestinationPieChart.options.animation.duration=0;
}).done(function() {
// Reload graph after one minute
setTimeout(updateForwardDestinationsPie, 60000);
});
}
// Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406
function escapeHtml(text) {
var map = {
@@ -262,6 +427,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)
{
@@ -307,6 +477,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;
@@ -325,6 +500,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;
@@ -360,8 +540,8 @@ function updateSummaryData(runOnce) {
if("FTLnotrunning" in data)
{
data["ads_blocked_today"] = "Lost";
data["dns_queries_today"] = "connection";
data["dns_queries_today"] = "Lost";
data["ads_blocked_today"] = "connection";
data["ads_percentage_today"] = "to";
data["domains_being_blocked"] = "API";
// Adjust text
@@ -391,19 +571,19 @@ function updateSummaryData(runOnce) {
}
}
["ads_blocked_today", "dns_queries_today", "ads_percentage_today"].forEach(function(today) {
var todayElement = $("h3#" + today);
["ads_blocked_today", "dns_queries_today", "ads_percentage_today", "unique_clients"].forEach(function(today) {
var todayElement = $("span#" + today);
todayElement.text() !== data[today] &&
todayElement.text() !== data[today] + "%" &&
todayElement.addClass("glow");
$("span#" + today).addClass("glow");
});
window.setTimeout(function() {
["ads_blocked_today", "dns_queries_today", "domains_being_blocked", "ads_percentage_today"].forEach(function(header, idx) {
["ads_blocked_today", "dns_queries_today", "domains_being_blocked", "ads_percentage_today", "unique_clients"].forEach(function(header, idx) {
var textData = (idx === 3 && data[header] !== "to") ? data[header] + "%" : data[header];
$("h3#" + header).text(textData);
$("span#" + header).text(textData);
});
$("h3.statistic.glow").removeClass("glow");
$("span.glow").removeClass("glow");
}, 500);
}).done(function() {
@@ -422,59 +602,130 @@ function updateSummaryData(runOnce) {
$(document).ready(function() {
var isMobile = {
Windows: function() {
return /IEMobile/i.test(navigator.userAgent);
},
Android: function() {
return /Android/i.test(navigator.userAgent);
},
BlackBerry: function() {
return /BlackBerry/i.test(navigator.userAgent);
},
iOS: function() {
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
},
any: function() {
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Windows());
}
};
var isMobile = {
Windows: function() {
return /IEMobile/i.test(navigator.userAgent);
},
Android: function() {
return /Android/i.test(navigator.userAgent);
},
BlackBerry: function() {
return /BlackBerry/i.test(navigator.userAgent);
},
iOS: function() {
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
},
any: function() {
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Windows());
}
};
// Pull in data via AJAX
// Pull in data via AJAX
updateSummaryData();
updateSummaryData();
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
timeLineChart = new Chart(ctx, {
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
timeLineChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "Total DNS Queries",
fill: true,
backgroundColor: "rgba(220,220,220,0.5)",
borderColor: "rgba(0, 166, 90,.8)",
pointBorderColor: "rgba(0, 166, 90,.8)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
},
{
label: "Blocked DNS Queries",
fill: true,
backgroundColor: "rgba(0,192,239,0.5)",
borderColor: "rgba(0,192,239,1)",
pointBorderColor: "rgba(0,192,239,1)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
}
]
},
options: {
tooltips: {
enabled: true,
mode: "x-axis",
callbacks: {
title: function(tooltipItem, data) {
var label = tooltipItem[0].xLabel;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = padNumber(h)+":"+padNumber(m-5)+":00";
var to = padNumber(h)+":"+padNumber(m+4)+":59";
return "Forward destinations from "+from+" to "+to;
},
label: function(tooltipItems, data) {
if(tooltipItems.datasetIndex === 1)
{
var percentage = 0.0;
var total = parseInt(data.datasets[0].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
if(total > 0)
{
percentage = 100.0*blocked/total;
}
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel + " (" + percentage.toFixed(1) + "%)";
}
else
{
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
}
}
}
},
legend: {
display: false
},
scales: {
xAxes: [{
type: "time",
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm"
},
tooltipFormat: "HH:mm"
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
maintainAspectRatio: false
}
});
// Pull in data via AJAX
updateQueriesOverTime();
// Create / load "Forward Destinations over Time" only if authorized
if(document.getElementById("forwardDestinationChart"))
{
ctx = document.getElementById("forwardDestinationChart").getContext("2d");
forwardDestinationChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "Total DNS Queries",
fill: true,
backgroundColor: "rgba(220,220,220,0.5)",
borderColor: "rgba(0, 166, 90,.8)",
pointBorderColor: "rgba(0, 166, 90,.8)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
},
{
label: "Blocked DNS Queries",
fill: true,
backgroundColor: "rgba(0,192,239,0.5)",
borderColor: "rgba(0,192,239,1)",
pointBorderColor: "rgba(0,192,239,1)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
}
]
datasets: [{ data: [] }]
},
options: {
tooltips: {
@@ -488,24 +739,73 @@ $(document).ready(function() {
var m = parseInt(time[2], 10) || 0;
var from = padNumber(h)+":"+padNumber(m-5)+":00";
var to = padNumber(h)+":"+padNumber(m+4)+":59";
return "Queries from "+from+" to "+to;
return "Forward destinations from "+from+" to "+to;
},
label: function(tooltipItems, data) {
if(tooltipItems.datasetIndex === 1)
{
var percentage = 0.0;
var total = parseInt(data.datasets[0].data[tooltipItems.index]);
var blocked = parseInt(data.datasets[1].data[tooltipItems.index]);
if(total > 0)
{
percentage = 100.0*blocked/total;
}
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel + " (" + percentage.toFixed(1) + "%)";
}
else
{
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
}
}
},
legend: {
display: false
},
scales: {
xAxes: [{
type: "time",
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm"
},
tooltipFormat: "HH:mm"
}
}],
yAxes: [{
ticks: {
mix: 0.0,
max: 1.0,
beginAtZero: true,
callback: function(value, index, values) {
return Math.round(value*100) + " %";
}
},
stacked: true
}]
},
maintainAspectRatio: true
}
});
// Pull in data via AJAX
updateForwardedOverTime();
}
// Create / load "Top Clients over Time" only if authorized
if(document.getElementById("clientsChart"))
{
ctx = document.getElementById("clientsChart").getContext("2d");
clientsChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [{ data: [] }]
},
options: {
tooltips: {
enabled: true,
mode: "x-axis",
callbacks: {
title: function(tooltipItem, data) {
var label = tooltipItem[0].xLabel;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = padNumber(h)+":"+padNumber(m-5)+":00";
var to = padNumber(h)+":"+padNumber(m+4)+":59";
return "Client activity from "+from+" to "+to;
},
label: function(tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].label + ": " + tooltipItems.yLabel;
}
}
},
@@ -526,186 +826,202 @@ $(document).ready(function() {
yAxes: [{
ticks: {
beginAtZero: true
}
},
stacked: true
}]
},
maintainAspectRatio: false
maintainAspectRatio: true
}
});
// Pull in data via AJAX
updateClientsOverTime();
}
updateQueriesOverTime();
// Create / load "Forward Destinations over Time" only if authorized
if(document.getElementById("forwardDestinationChart"))
{
ctx = document.getElementById("forwardDestinationChart").getContext("2d");
forwardDestinationChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [{ data: [] }]
},
options: {
tooltips: {
enabled: true,
mode: "x-axis",
callbacks: {
title: function(tooltipItem, data) {
var label = tooltipItem[0].xLabel;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = padNumber(h)+":"+padNumber(m-5)+":00";
var to = padNumber(h)+":"+padNumber(m+4)+":59";
return "Forward destinations from "+from+" to "+to;
},
label: function(tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
}
}
// Create / load "Query Types over Time" only if authorized
if(document.getElementById("queryTypeChart"))
{
ctx = document.getElementById("queryTypeChart").getContext("2d");
queryTypeChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "A: IPv4 queries",
pointRadius: 0,
pointHitRadius: 5,
pointHoverRadius: 5,
data: [],
cubicInterpolationMode: "monotone"
},
legend: {
display: false
},
scales: {
xAxes: [{
type: "time",
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm"
},
tooltipFormat: "HH:mm"
}
}],
yAxes: [{
ticks: {
mix: 0.0,
max: 1.0,
beginAtZero: true,
callback: function(value, index, values) {
return Math.round(value*100) + " %";
}
},
stacked: true
}]
},
maintainAspectRatio: true
}
});
// Pull in data via AJAX
updateForwardedOverTime();
}
// Create / load "Query Types over Time" only if authorized
if(document.getElementById("queryTypeChart"))
{
ctx = document.getElementById("queryTypeChart").getContext("2d");
queryTypeChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "A: IPv4 queries",
pointRadius: 0,
pointHitRadius: 5,
pointHoverRadius: 5,
data: []
{
label: "AAAA: IPv6 queries",
pointRadius: 0,
pointHitRadius: 5,
pointHoverRadius: 5,
data: [],
cubicInterpolationMode: "monotone"
}
]
},
options: {
tooltips: {
enabled: true,
mode: "x-axis",
callbacks: {
title: function(tooltipItem, data) {
var label = tooltipItem[0].xLabel;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = padNumber(h)+":"+padNumber(m-5)+":00";
var to = padNumber(h)+":"+padNumber(m+4)+":59";
return "Query types from "+from+" to "+to;
},
{
label: "AAAA: IPv6 queries",
pointRadius: 0,
pointHitRadius: 5,
pointHoverRadius: 5,
data: []
label: function(tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
}
]
}
},
options: {
tooltips: {
enabled: true,
mode: "x-axis",
callbacks: {
title: function(tooltipItem, data) {
var label = tooltipItem[0].xLabel;
var time = label.match(/(\d?\d):?(\d?\d?)/);
var h = parseInt(time[1], 10);
var m = parseInt(time[2], 10) || 0;
var from = padNumber(h)+":"+padNumber(m-5)+":00";
var to = padNumber(h)+":"+padNumber(m+4)+":59";
return "Query types from "+from+" to "+to;
legend: {
display: false
},
scales: {
xAxes: [{
type: "time",
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm"
},
label: function(tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].label + ": " + (100.0*tooltipItems.yLabel).toFixed(1) + "%";
}
tooltipFormat: "HH:mm"
}
},
legend: {
display: false
},
scales: {
xAxes: [{
type: "time",
time: {
unit: "hour",
displayFormats: {
hour: "HH:mm"
},
tooltipFormat: "HH:mm"
}],
yAxes: [{
ticks: {
mix: 0.0,
max: 1.0,
beginAtZero: true,
callback: function(value, index, values) {
return Math.round(value*100) + " %";
}
}],
yAxes: [{
ticks: {
mix: 0.0,
max: 1.0,
beginAtZero: true,
callback: function(value, index, values) {
return Math.round(value*100) + " %";
}
},
stacked: true
}]
},
maintainAspectRatio: true
}
});
// Pull in data via AJAX
updateQueryTypesOverTime();
}
// Create / load "Top Domains" and "Top Advertisers" only if authorized
if(document.getElementById("domain-frequency")
&& document.getElementById("ad-frequency"))
{
updateTopLists();
}
// Create / load "Top Clients" only if authorized
if(document.getElementById("client-frequency"))
{
updateTopClientsChart();
}
$("#queryOverTimeChart").click(function(evt){
var activePoints = timeLineChart.getElementAtEvent(evt);
if(activePoints.length > 0)
{
//get the internal index of slice in pie chart
var clickedElementindex = activePoints[0]["_index"];
//get specific label by index
var label = timeLineChart.data.labels[clickedElementindex];
//get value by index
var from = label/1000 - 300;
var until = label/1000 + 300;
window.location.href = "queries.php?from="+from+"&until="+until;
},
stacked: true
}]
},
maintainAspectRatio: true
}
return false;
});
// Pull in data via AJAX
updateQueryTypesOverTime();
}
// Create / load "Top Domains" and "Top Advertisers" only if authorized
if(document.getElementById("domain-frequency")
&& document.getElementById("ad-frequency"))
{
updateTopLists();
}
// Create / load "Top Clients" only if authorized
if(document.getElementById("client-frequency"))
{
updateTopClientsChart();
}
$("#queryOverTimeChart").click(function(evt){
var activePoints = timeLineChart.getElementAtEvent(evt);
if(activePoints.length > 0)
{
//get the internal index of slice in pie chart
var clickedElementindex = activePoints[0]["_index"];
//get specific label by index
var label = timeLineChart.data.labels[clickedElementindex];
//get value by index
var from = label/1000 - 300;
var until = label/1000 + 300;
window.location.href = "queries.php?from="+from+"&until="+until;
}
return false;
});
if(document.getElementById("queryTypePieChart"))
{
ctx = document.getElementById("queryTypePieChart").getContext("2d");
queryTypePieChart = new Chart(ctx, {
type: "doughnut",
data: {
labels: [],
datasets: [{ data: [] }]
},
options: {
legend: {
display: true,
position: "right"
},
tooltips: {
enabled: true,
callbacks: {
title: function(tooltipItem, data) {
return "Query types";
},
label: function(tooltipItems, data) {
var dataset = data.datasets[tooltipItems.datasetIndex];
var label = data.labels[tooltipItems.index];
return label + ": " + dataset.data[tooltipItems.index].toFixed(1) + "%";
}
}
},
animation: {
duration: 750
},
cutoutPercentage: 0
}
});
// Pull in data via AJAX
updateQueryTypesPie();
}
if(document.getElementById("forwardDestinationPieChart"))
{
ctx = document.getElementById("forwardDestinationPieChart").getContext("2d");
forwardDestinationPieChart = new Chart(ctx, {
type: "doughnut",
data: {
labels: [],
datasets: [{ data: [] }]
},
options: {
legend: {
display: true,
position: "right"
},
tooltips: {
enabled: true,
callbacks: {
title: function(tooltipItem, data) {
return "Forward destinations";
},
label: function(tooltipItems, data) {
var dataset = data.datasets[tooltipItems.datasetIndex];
var label = data.labels[tooltipItems.index];
return label + ": " + dataset.data[tooltipItems.index].toFixed(1) + "%";
}
}
},
animation: {
duration: 750
},
cutoutPercentage: 0
}
});
// Pull in data via AJAX
updateForwardDestinationsPie();
}
});

View File

@@ -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();
@@ -143,8 +143,7 @@ function add(arg) {
method: "post",
data: {"domain":domain.val(), "list":locallistType, "token":token},
success: function(response) {
if (response.indexOf("not a valid argument") >= 0 ||
response.indexOf("is not a valid domain") >= 0) {
if (response.indexOf("] Pi-hole blocking is ") === -1) {
alFailure.show();
err.html(response);
alFailure.delay(4000).fadeOut(2000, function() {
@@ -208,11 +207,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");
});

View File

@@ -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%" },
],
@@ -179,11 +185,36 @@ $(document).ready(function() {
"targets": -1,
"data": null,
"defaultContent": ""
} ]
} ],
"initComplete": function () {
var api = this.api();
// Query type IPv4 / IPv6
api.$('td:eq(1)').click( function () { api.search( this.innerHTML ).draw(); $('#resetButton').show(); } );
api.$('td:eq(1)').hover(
function () { this.title="Click to show only "+this.innerHTML+" queries"; this.style.color="#72afd2" },
function () { this.style.color="" }
);
api.$("td:eq(1)").css("cursor","pointer");
// Domain
api.$('td:eq(2)').click( function () { api.search( this.innerHTML ).draw(); $('#resetButton').show(); } );
api.$('td:eq(2)').hover(
function () { this.title="Click to show only queries with domain "+this.innerHTML; this.style.color="#72afd2" },
function () { this.style.color="" }
);
api.$("td:eq(2)").css("cursor","pointer");
// Client
api.$('td:eq(3)').click( function () { api.search( this.innerHTML ).draw(); $('#resetButton').show(); } );
api.$('td:eq(3)').hover(
function () { this.title="Click to show only queries made by "+this.innerHTML; this.style.color="#72afd2" },
function () { this.style.color="" }
);
api.$("td:eq(3)").css("cursor","pointer");
}
});
$("#all-queries tbody").on( "click", "button", function () {
var data = tableApi.row( $(this).parents("tr") ).data();
if (data[4] === "1")
if (data[4] === "1" || data[4] === "5")
{
add(data[2],"white");
}
@@ -192,6 +223,8 @@ $(document).ready(function() {
add(data[2],"black");
}
} );
$('#resetButton').click( function () { tableApi.search("").draw(); $('#resetButton').hide(); } );
} );

View File

@@ -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");
});

View File

@@ -17,6 +17,22 @@ $(function () {
$("input[name=\"AddMAC\"]").val(mac);
});
});
$(".confirm-poweroff").confirm({
text: "Are you sure you want to send a poweroff command to your Pi-Hole?",
title: "Confirmation required",
confirm(button) {
$("#poweroffform").submit();
},
cancel(button) {
// nothing to do
},
confirmButton: "Yes, poweroff",
cancelButton: "No, go back",
post: true,
confirmButtonClass: "btn-danger",
cancelButtonClass: "btn-success",
dialogClass: "modal-dialog modal-mg" // Bootstrap classes for mid-size modal
});
$(".confirm-reboot").confirm({
text: "Are you sure you want to send a reboot command to your Pi-Hole?",
title: "Confirmation required",
@@ -68,6 +84,23 @@ $(".confirm-flushlogs").confirm({
dialogClass: "modal-dialog modal-mg"
});
$(".confirm-disablelogging").confirm({
text: "Note that disabling query logging will render graphs on the web user interface useless. Are you sure you want to disable your logging?",
title: "Confirmation required",
confirm(button) {
$("#disablelogsform").submit();
},
cancel(button) {
// nothing to do
},
confirmButton: "Yes, disable logs",
cancelButton: "No, go back",
post: true,
confirmButtonClass: "btn-danger",
cancelButtonClass: "btn-success",
dialogClass: "modal-dialog modal-mg"
});
$(".api-token").confirm({
text: "Make sure that nobody else can scan this code around you. They will have full access to the API without having to know the password. Note that the generation of the QR code will take some time.",
title: "Confirmation required",
@@ -102,9 +135,6 @@ $(document).ready(function() {
"scrollY": "200px",
"scrollX" : true
});
$("#leaseexpand").on( "click", function () {
setTimeout(function(){leasetable.draw();},100);
} );
}
if(document.getElementById("DHCPStaticLeasesTable"))
{
@@ -116,10 +146,13 @@ $(document).ready(function() {
"scrollY": "200px",
"scrollX" : true
});
$("#leaseexpand").on( "click", function () {
setTimeout(function(){staticleasetable.draw();},100);
} );
}
//call draw() on each table... they don't render properly with scrollX and scrollY set... ¯\_(ツ)_/¯
$("a[data-toggle=\"tab\"]").on("shown.bs.tab", function (e) {
leasetable.draw();
staticleasetable.draw();
});
} );
// Handle hiding of alerts
@@ -136,12 +169,39 @@ $(document).ready(function(){
// Handle list deletion
$("button[id^='adlist-btn-']").on("click", function (e) {
var id = parseInt($(this).context.id.replace(/[^0-9\.]/g, ""), 10);
e.preventDefault();
var status = $(this).siblings("input[name^='adlist-del-']").is(":checked");
var status = $("input[name=\"adlist-del-"+id+"\"]").is(":checked");
var textType = status ? "none" : "line-through";
$(this).siblings("input[name^='adlist-del-']").prop("checked", !status);
$(this).siblings("input[name^='adlist-enable-']").prop("disabled", !status);
$(this).siblings("a").css("text-decoration", textType);
// Check hidden delete box (or reset)
$("input[name=\"adlist-del-"+id+"\"]").prop("checked", !status);
// Untick and disable check box (or reset)
$("input[name=\"adlist-enable-"+id+"\"]").prop("checked", status).prop("disabled", !status);
// Strink through text (or reset)
$("a[id=\"adlist-text-"+id+"\"]").css("text-decoration", textType);
// Highlight that the button has to be clicked in order to make the change live
$("button[id=\"blockinglistsaveupdate\"]").addClass("btn-danger").css("font-weight", "bold");
});
// Change "?tab=" parameter in URL for save and reload
$(".nav-tabs a").on("shown.bs.tab", function (e) {
window.history.pushState("", "", "?tab=" + e.target.hash.substring(1));
window.scrollTo(0, 0);
});
// Auto dismissal for info and error notifications
$(document).ready(function(){
var alInfo = $("#alInfo");
var alError = $("#alError");
if(alInfo.length)
{
alInfo.delay(3000).fadeOut(2000, function() { alInfo.hide(); });
}
if(alError.length)
{
alError.delay(3000).fadeOut(2000, function() { alError.hide(); });
}
});

View File

@@ -6,10 +6,19 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
function testFTL()
function testFTL($address)
{
$ret = shell_exec("pidof pihole-FTL");
return intval($ret);
if($address === "127.0.0.1")
{
$ret = shell_exec("pidof pihole-FTL");
return intval($ret);
}
// We cannot relly test for a distant FTL instance
// in the same way, so for any other IP address
// we simply return true here and rely on the API
// socket connection itself to fail if there is nothing
// on that address
return true;
}
function connectFTL($address, $port=4711, $quiet=true)

View File

@@ -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;
}

View File

@@ -119,7 +119,7 @@ function check_csrf($token) {
function check_domain() {
if(isset($_POST['domain'])){
$domains = explode(" ",$_POST['domain']);
$domains = preg_split('\s+', $_POST['domain']);
foreach($domains as $domain)
{
$validDomain = is_valid_domain_name($domain);
@@ -131,7 +131,7 @@ function check_domain() {
}
function list_verify($type) {
global $pwhash, $wrongpassword;
global $pwhash, $wrongpassword, $auth;
if(!isset($_POST['domain']) || !isset($_POST['list']) || !(isset($_POST['pw']) || isset($_POST['token']))) {
log_and_die("Missing POST variables");
}
@@ -144,11 +144,7 @@ function list_verify($type) {
elseif(isset($_POST['pw']))
{
require("password.php");
if(strlen($pwhash) == 0)
{
log_and_die("No password set - ".htmlspecialchars($type)."listing with password not supported");
}
elseif($wrongpassword)
if($wrongpassword || !$auth)
{
log_and_die("Wrong password - ".htmlspecialchars($type)."listing of ${_POST['domain']} not permitted");
}

View File

@@ -11,17 +11,16 @@
</div>
<!-- Modal for custom disable time -->
<div class="modal fade" id="customDisableModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Custom disable timeout</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm-3"><input id="customTimeout" class="form-control" type="number" value="60"></div>
<div class="col-sm-9">
<div class="btn-group" data-toggle="buttons">
<div class="input-group">
<input id="customTimeout" class="form-control" type="number" value="60">
<div class="input-group-btn" data-toggle="buttons">
<label class="btn btn-default">
<input type="radio"/> Secs
</label>
@@ -29,7 +28,6 @@
<input type="radio" /> Mins
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
@@ -39,6 +37,14 @@
</div>
</div>
</div>
<?php
// Flushes the system write buffers of PHP. This attempts to push everything we have so far all the way to the client's browser.
flush();
// Run update checker
// - determines local branch each time,
// - determines local and remote version every 30 minutes
require "scripts/pi-hole/php/update_checker.php";
?>
<!-- /.content-wrapper -->
<footer class="main-footer">
<!-- Version Infos -->
@@ -55,7 +61,7 @@
echo $FTL_current;
if($FTL_update){ ?> <a class="alert-link lookatme" href="https://github.com/pi-hole/FTL/releases" target="_blank">(Update available!)</a><?php } ?>
</div>
<div style="display: inline-block"><a href="https://github.com/pi-hole" target="_blank"><i class="fa fa-github"></i></a> <strong><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=3J2L3Z4DHW9UY" target="_blank">Donate</a></strong> if you found this useful.</div>
<div style="display: inline-block"><strong><a href="https://pi-hole.net/donate" target="_blank"><i class="fa fa-heart"></i> Donate</a></strong> if you found this useful.</div>
</footer>
</div>
<!-- ./wrapper -->

View File

@@ -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) {

View File

@@ -16,15 +16,22 @@ header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
function echoEvent($datatext) {
echo "data: ".implode("\ndata: ", explode("\n", $datatext))."\n\n";
// Detect ${OVER} and replace it with something we can safely transmit
$datatext = str_replace("\r","<------",$datatext);
$pos = strpos($datatext,"<------");
// Detect if the ${OVER} line is within this line, e.g.
// "Pending: String to replace${OVER}Done: String has been replaced"
// If this is the case, we have to remove everything before ${OVER}
// and return only the text thereafter
if($pos !== false && $pos !== 0)
{
$datatext = substr($datatext, $pos);
}
echo "data: ".implode("\ndata: ", explode("\n",$datatext))."\n\n";
}
// echoEvent("***START***");
$proc = popen("sudo pihole -g", 'r');
while (!feof($proc)) {
echoEvent(fread($proc, 4096));
}
// echoEvent("***END***");
?>

View File

@@ -9,7 +9,6 @@
<?php
require "scripts/pi-hole/php/auth.php";
require "scripts/pi-hole/php/password.php";
require "scripts/pi-hole/php/update_checker.php";
check_cors();
@@ -184,6 +183,7 @@
<link href="style/vendor/font-awesome-4.5.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
<link href="style/vendor/ionicons-2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css" />
<link href="style/vendor/dataTables.bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="style/vendor/daterangepicker.css" rel="stylesheet" type="text/css" />
<link href="style/vendor/AdminLTE.min.css" rel="stylesheet" type="text/css" />
<link href="style/vendor/skin-blue.min.css" rel="stylesheet" type="text/css" />
@@ -230,7 +230,7 @@ if($auth) {
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- User Account: style can be found in dropdown.less -->
<li><a style="pointer-events:none;"><samp><?php echo gethostname(); ?></samp></a></li>
<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true">
<img src="img/logo.svg" class="user-image" style="border-radius: initial" sizes="160x160" alt="Pi-hole logo" />
@@ -248,10 +248,10 @@ if($auth) {
<!-- Menu Body -->
<li class="user-body">
<div class="col-xs-4 text-center">
<a class="btn-link" href="https://github.com/pi-hole/pi-hole" target="_blank">GitHub</a>
<a class="btn-link" href="https://github.com/pi-hole" target="_blank">GitHub</a>
</div>
<div class="col-xs-4 text-center">
<a class="btn-link" href="http://jacobsalmela.com/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/" target="_blank">Details</a>
<a class="btn-link" href="https://pi-hole.net" target="_blank">Website</a>
</div>
<div class="col-xs-4 text-center">
<a class="btn-link" href="https://github.com/pi-hole/pi-hole/releases" target="_blank">Updates</a>
@@ -263,6 +263,7 @@ if($auth) {
<!-- Menu Footer -->
<li class="user-footer">
<!-- Version Infos -->
<?php /*
<div class="<?php if(!isset($core_commit) && !isset($web_commit)) { ?>hidden-md <?php } ?>hidden-lg">
<b>Pi-hole Version </b> <?php
echo $core_current;
@@ -276,6 +277,7 @@ if($auth) {
echo $FTL_current;
if($FTL_update){ ?> <a class="alert-link lookatme btn-link" href="https://github.com/pi-hole/FTL/releases" target="_blank" style="background:none">(Update available!)</a><?php } ?><br><br>
</div>
*/ ?>
<!-- PayPal -->
<div class="text-center">
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=3J2L3Z4DHW9UY" target="_blank" style="background:none">
@@ -347,7 +349,7 @@ if($auth) {
?>
<br/>
<?php
echo "<a><i class=\"fa fa-circle\" style=\"color:";
echo "<a title=\"Detected $nproc cores\"><i class=\"fa fa-circle\" style=\"color:";
if ($loaddata[0] > $nproc) {
echo "#FF0000";
}
@@ -355,7 +357,7 @@ if($auth) {
{
echo "#7FFF00";
}
echo "\" title=\"Detected $nproc cores\"></i> Load:&nbsp;&nbsp;" . $loaddata[0] . "&nbsp;&nbsp;" . $loaddata[1] . "&nbsp;&nbsp;". $loaddata[2] . "</a>";
echo "\"></i> Load:&nbsp;&nbsp;" . $loaddata[0] . "&nbsp;&nbsp;" . $loaddata[1] . "&nbsp;&nbsp;". $loaddata[2] . "</a>";
?>
<br/>
<?php
@@ -412,6 +414,31 @@ if($auth) {
<i class="fa fa-file-text-o"></i> <span>Query Log</span>
</a>
</li>
<li class="treeview<?php if($scriptname === "db_queries.php" || $scriptname === "db_lists.php" || $scriptname === "db_graph.php"){ ?> active<?php } ?>">
<a href="#">
<span class="pull-right-container">
<i class="fa fa-angle-down pull-right" style="padding-right: 5px;"></i>
</span>
<i class="fa fa-clock-o"></i> <span>Long term data</span>
</a>
<ul class="treeview-menu">
<li<?php if($scriptname === "db_graph.php"){ ?> class="active"<?php } ?>>
<a href="db_graph.php">
<i class="fa fa-file-text-o"></i> <span>Graphics</span>
</a>
</li>
<li<?php if($scriptname === "db_queries.php"){ ?> class="active"<?php } ?>>
<a href="db_queries.php">
<i class="fa fa-file-text-o"></i> <span>Query Log</span>
</a>
</li>
<li<?php if($scriptname === "db_lists.php"){ ?> class="active"<?php } ?>>
<a href="db_lists.php">
<i class="fa fa-file-text-o"></i> <span>Top Lists</span>
</a>
</li>
</ul>
</li>
<!-- Whitelist -->
<li<?php if($scriptname === "whitelist"){ ?> class="active"<?php } ?>>
<a href="list.php?l=white">
@@ -428,10 +455,10 @@ if($auth) {
<li id="pihole-disable" class="treeview"<?php if ($pistatus == "0") { ?> hidden="true"<?php } ?>>
<a href="#">
<i class="fa fa-stop"></i> <span>Disable</span>&nbsp;&nbsp;&nbsp;<span id="flip-status-disable"></span>
<span class="pull-right-container">
<i class="fa fa-angle-down pull-right" style="padding-right: 5px;"></i>
</span>
<i class="fa fa-stop"></i> <span>Disable</span>&nbsp;&nbsp;&nbsp;<span id="flip-status-disable"></span>
</a>
<ul class="treeview-menu">
<li>
@@ -466,12 +493,12 @@ if($auth) {
<a href="#"><i class="fa fa-play"></i> <span id="enableLabel">Enable</span>&nbsp;&nbsp;&nbsp;<span id="flip-status-enable"></span></a>
</li>
<!-- Tools -->
<li class="treeview <?php if($scriptname === "gravity.php" || $scriptname === "queryads.php" || $scriptname === "debug.php"){ ?>active<?php } ?>">
<li class="treeview <?php if(in_array($scriptname, array("gravity.php", "queryads.php", "auditlog.php", "taillog.php", "taillog-FTL.php", "debug.php"))){ ?>active<?php } ?>">
<a href="#">
<i class="fa fa-folder"></i> <span>Tools</span>
<span class="pull-right-container">
<i class="fa fa-angle-down pull-right" style="padding-right: 5px;"></i>
</span>
<i class="fa fa-folder"></i> <span>Tools</span>
</a>
<ul class="treeview-menu">
<!-- Run gravity.sh -->
@@ -486,6 +513,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">

View File

@@ -13,7 +13,7 @@
<div class="panel-title text-center"><span class="logo-lg" style="font-size: 25px;">Pi-<b>hole</b></span></div>
<p class="login-box-msg">Sign in to start your session</p>
<div id="cookieInfo" class="panel-title text-center" style="color:#F00; font-size: 150%" hidden>Verify that cookies are allowed for <tt><?php echo $_SERVER['HTTP_HOST']; ?></tt></div>
<div id="cookieInfo" class="panel-title text-center" style="color:#F00; font-size: 150%" hidden>Verify that cookies are allowed for <samp><?php echo $_SERVER['HTTP_HOST']; ?></samp></div>
<?php if ($wrongpassword) { ?>
<div class="form-group has-error login-box-msg">
<label class="control-label"><i class="fa fa-times-circle-o"></i> Wrong password!</label>
@@ -30,8 +30,8 @@
<div class="row">
<div class="col-xs-8">
<ul>
<li><tt>Return</tt> &rarr; Log in and go to requested page (<?php echo $scriptname; ?>)</li>
<li><tt>Ctrl+Return</tt> &rarr; Log in and go to Settings page</li>
<li><samp>Return</samp> &rarr; Log in and go to requested page (<?php echo $scriptname; ?>)</li>
<li><samp>Ctrl+Return</samp> &rarr; Log in and go to Settings page</li>
</ul>
</div>
<div class="col-xs-4">

View File

@@ -40,6 +40,13 @@
if($postinput == $pwhash)
{
$_SESSION["hash"] = $pwhash;
// Login successful, redirect the user to the homepage to discard the POST request
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['QUERY_STRING'] === 'login') {
header('Location: index.php');
exit();
}
$auth = true;
}
else

View File

@@ -20,11 +20,12 @@ function echoEvent($datatext) {
}
// Credit: http://stackoverflow.com/a/4694816/2087442
ini_set("pcre.recursion_limit", 1500);
function is_valid_domain_name($domain_name)
{
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) //valid chars check
&& preg_match("/^.{1,253}$/", $domain_name) //overall length check
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name) ); //length of each label
return (preg_match("/^((-|_)*[a-z\d]((-|_)*[a-z\d])*(-|_)*)(\.(-|_)*([a-z\d]((-|_)*[a-z\d])*))*$/i", $domain_name) // Valid chars check
&& preg_match("/^.{1,253}$/", $domain_name) // Overall length check
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name) ); // Length of each label
}
// Test if domain is set
@@ -48,6 +49,10 @@ if(isset($_GET["exact"]))
{
$exact = "-exact";
}
elseif(isset($_GET["bp"]))
{
$exact = "-bp";
}
else
{
$exact = "";

View File

@@ -46,7 +46,7 @@ function validDomain($domain_name)
function validDomainWildcard($domain_name)
{
// There has to be either no or at most one "*" at the beginning of a line
$validChars = preg_match("/^((\*)?[_a-z\d](-*[_a-z\d])*)(\.([_a-z\d](-*[a-z\d])*))*(\.([a-z\d])*)*$/i", $domain_name);
$validChars = preg_match("/^((\*.)?[_a-z\d](-*[_a-z\d])*)(\.([_a-z\d](-*[a-z\d])*))*(\.([a-z\d])*)*$/i", $domain_name);
$lengthCheck = preg_match("/^.{1,253}$/", $domain_name);
$labelLengthCheck = preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name);
return ( $validChars && $lengthCheck && $labelLengthCheck ); //length of each label
@@ -420,6 +420,11 @@ function readAdlists()
$success .= "The webUI settings have been updated";
break;
case "poweroff":
exec("sudo pihole -a poweroff");
$success = "The system will poweroff in 5 seconds...";
break;
case "reboot":
exec("sudo pihole -a reboot");
$success = "The system will reboot in 5 seconds...";

View File

@@ -14,6 +14,28 @@ if (php_sapi_name() !== "cli") {
check_csrf(isset($_POST["token"]) ? $_POST["token"] : "");
}
function archive_add_file($path,$name)
{
global $archive;
if(file_exists($path.$name))
$archive[$name] = file_get_contents($path.$name);
}
function archive_add_directory($path)
{
if($dir = opendir($path))
{
while(false !== ($entry = readdir($dir)))
{
if($entry !== "." && $entry !== "..")
{
archive_add_file($path,$entry);
}
}
closedir($dir);
}
}
function limit_length(&$item, $key)
{
// limit max length for a domain entry to 253 chars
@@ -21,20 +43,8 @@ function limit_length(&$item, $key)
$item = substr($item, 0, 253);
}
function process_zip($name)
function process_file($contents)
{
global $zip;
$zippointer = $zip->getStream($name);
if(!$zippointer)
{
echo "$name not found in provided ZIP file, skipping...<br>";
return;
}
$contents = "";
while (!feof($zippointer)) {
$contents .= fread($zippointer, 4096);
}
fclose($zippointer);
$domains = array_filter(explode("\n",$contents));
// Walk array and apply a max string length
@@ -46,29 +56,6 @@ function process_zip($name)
return $domains;
}
function add_to_zip($path,$name)
{
global $zip;
if(file_exists($path.$name))
$zip->addFile($path.$name,$name);
}
function add_dir_to_zip($path)
{
global $zip;
if($dir = opendir($path))
{
while(false !== ($entry = readdir($dir)))
{
if($entry !== "." && $entry !== "..")
{
$zip->addFile($path.$entry,$entry);
}
}
closedir($dir);
}
}
function check_domains($domains)
{
foreach($domains as $domain)
@@ -109,7 +96,7 @@ if(isset($_POST["action"]))
$type = mime_content_type($source);
$name = explode(".", $filename);
$accepted_types = array('application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed');
$accepted_types = array('application/gzip', 'application/tar', 'application/x-compressed', 'application/x-gzip');
$okay = false;
foreach($accepted_types as $mime_type) {
if($mime_type == $type) {
@@ -118,40 +105,56 @@ if(isset($_POST["action"]))
}
}
$continue = strtolower($name[1]) == 'zip' ? true : false;
$continue = strtolower($name[1]) == 'tar' && strtolower($name[2]) == 'gz' ? true : false;
if(!$continue || !$okay) {
die("The file you are trying to upload is not a .zip file (filename: ".$filename.", type: ".$type."). Please try again.");
die("The file you are trying to upload is not a .tar.gz file (filename: ".htmlentities($filename).", type: ".htmlentities($type)."). Please try again.");
}
$zip = new ZipArchive();
$x = $zip->open($source);
if ($x === true) {
if(isset($_POST["blacklist"]))
{
$blacklist = process_zip("blacklist.txt");
exec("sudo pihole -b -q ".implode(" ", $blacklist));
}
if(isset($_POST["whitelist"]))
{
$whitelist = process_zip("whitelist.txt");
exec("sudo pihole -w -q ".implode(" ", $whitelist));
}
if(isset($_POST["wildlist"]))
{
$wildlist = process_zip("wildcardblocking.txt");
exec("sudo pihole -wild -q ".implode(" ", $wildlist));
}
echo "OK";
$zip->close();
}
else
$fullfilename = sys_get_temp_dir()."/".$filename;
if(!move_uploaded_file($source, $fullfilename))
{
die("Error opening uploaded archive!");
die("Failed moving ".htmlentities($source)." to ".htmlentities($fullfilename));
}
$archive = new PharData($fullfilename);
$importedsomething = false;
foreach($archive as $file)
{
if(isset($_POST["blacklist"]) && $file->getFilename() === "blacklist.txt")
{
$blacklist = process_file(file_get_contents($file));
echo "Processing blacklist.txt<br>\n";
exec("sudo pihole -b -nr --nuke");
exec("sudo pihole -b -q -nr ".implode(" ", $blacklist));
$importedsomething = true;
}
if(isset($_POST["whitelist"]) && $file->getFilename() === "whitelist.txt")
{
$whitelist = process_file(file_get_contents($file));
echo "Processing whitelist.txt<br>\n";
exec("sudo pihole -w -nr --nuke");
exec("sudo pihole -w -q -nr ".implode(" ", $whitelist));
$importedsomething = true;
}
if(isset($_POST["wildlist"]) && $file->getFilename() === "wildcardblocking.txt")
{
$wildlist = process_file(file_get_contents($file));
echo "Processing wildcardblocking.txt<br>\n";
exec("sudo pihole -wild -nr --nuke");
exec("sudo pihole -wild -q -nr ".implode(" ", $wildlist));
$importedsomething = true;
}
if($importedsomething)
{
exec("sudo pihole restartdns");
}
}
unlink($fullfilename);
echo "OK";
}
else
{
@@ -160,24 +163,25 @@ if(isset($_POST["action"]))
}
else
{
$filename = "pi-hole-teleporter_".date("Y-m-d_h-i-s").".zip";
$archive_file_name = tempnam("/tmp", "Teleporter");
$zip = new ZipArchive();
touch($archive_file_name);
$res = $zip->open($archive_file_name, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$tarname = "pi-hole-teleporter_".date("Y-m-d_h-i-s").".tar";
$filename = $tarname.".gz";
$archive_file_name = sys_get_temp_dir() ."/". $tarname;
$archive = new PharData($archive_file_name);
if ($res !== TRUE) {
exit("cannot open/create $archive_file_name<br>Error: ".$zip->getStatusString()."<br>PHP user: ".exec('whoami')."\n");
if ($archive->isWritable() !== TRUE) {
exit("cannot open/create ".htmlentities($archive_file_name)."<br>\nPHP user: ".exec('whoami')."\n");
}
add_to_zip("/etc/pihole/","whitelist.txt");
add_to_zip("/etc/pihole/","blacklist.txt");
add_to_zip("/etc/pihole/","adlists.list");
add_to_zip("/etc/pihole/","setupVars.conf");
add_dir_to_zip("/etc/dnsmasq.d/");
archive_add_file("/etc/pihole/","whitelist.txt");
archive_add_file("/etc/pihole/","blacklist.txt");
archive_add_file("/etc/pihole/","adlists.list");
archive_add_file("/etc/pihole/","setupVars.conf");
archive_add_directory("/etc/dnsmasq.d/");
$zip->addFromString("wildcardblocking.txt", getWildcardListContent());
$zip->close();
$archive["wildcardblocking.txt"] = getWildcardListContent();
$archive->compress(Phar::GZ); // Creates a gziped copy
unlink($archive_file_name); // Unlink original tar file as it is not needed anymore
$archive_file_name .= ".gz"; // Append ".gz" extension to ".tar"
header("Content-type: application/zip");
header('Content-Transfer-Encoding: binary');

View File

@@ -42,17 +42,17 @@ $FTL_current = exec("pihole-FTL version");
$versionfile = "../versions";
$check_version = false;
$date = date_create();
$timestamp = date_timestamp_get($date);
// 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)
// Is last check for updates older than 48 hours?
if($timestamp >= intval($versions[0]) + 172800)
{
// Yes: Retrieve new/updated version data
$check_version = true;

File diff suppressed because one or more lines are too long

1542
scripts/vendor/daterangepicker.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,10 @@
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
.small-box {
-webkit-user-select: none; /* Chrome/Safari */
cursor: default;
-webkit-user-select: none; /* Chrome/Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+ */
@@ -36,7 +38,21 @@
-webkit-animation-name: Pulse;
animation-name: Pulse;
-webkit-animation-duration: 2s;
animation-duration: 2s;
animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
#all-queries {
table-layout: fixed;
}
#all-queries tbody {
word-wrap: break-word;
}
.main-header>.navbar {
height: 50px;
}
#resetButton {
color: red;
font-weight: bold;
}

232
style/vendor/daterangepicker.css vendored Normal file
View File

@@ -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; } }