mirror of
https://github.com/pi-hole/web.git
synced 2025-12-24 12:48:29 +00:00
Merge branch 'devel' into new/dhcpv6settings
This commit is contained in:
198
scripts/pi-hole/js/footer.js
Normal file
198
scripts/pi-hole/js/footer.js
Normal file
@@ -0,0 +1,198 @@
|
||||
// User menu toggle
|
||||
$("#dropdown-menu a").on("click", function(event) {
|
||||
$(this).parent().toggleClass("open");
|
||||
});
|
||||
$("body").on("click", function(event) {
|
||||
if(!$("#dropdown-menu").is(event.target) && $("#dropdown-menu").has(event.target).length === 0) {
|
||||
$("#dropdown-menu").removeClass("open");
|
||||
}
|
||||
});
|
||||
|
||||
function piholeChanged(action)
|
||||
{
|
||||
const status = $("#status");
|
||||
const ena = $("#pihole-enable");
|
||||
const dis = $("#pihole-disable");
|
||||
|
||||
switch(action) {
|
||||
case "enabled":
|
||||
status.html("<i class='fa fa-circle' style='color:#7FFF00'></i> Active");
|
||||
ena.hide();
|
||||
dis.show();
|
||||
dis.removeClass("active");
|
||||
break;
|
||||
|
||||
case "disabled":
|
||||
status.html("<i class='fa fa-circle' style='color:#FF0000'></i> Offline");
|
||||
ena.show();
|
||||
dis.hide();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function piholeChange(action, duration)
|
||||
{
|
||||
const token = encodeURIComponent($("#token").html());
|
||||
var btnStatus;
|
||||
|
||||
switch(action) {
|
||||
case "enable":
|
||||
btnStatus = $("#flip-status-enable");
|
||||
btnStatus.html("<i class='fa fa-spinner'> </i>");
|
||||
$.getJSON("api.php?enable&token=" + token, (data) => {
|
||||
if(data.status === "enabled") {
|
||||
btnStatus.html("");
|
||||
piholeChanged("enabled");
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "disable":
|
||||
btnStatus = $("#flip-status-disable");
|
||||
btnStatus.html("<i class='fa fa-spinner'> </i>");
|
||||
$.getJSON("api.php?disable=" + duration + "&token=" + token, (data) => {
|
||||
if(data.status === "disabled") {
|
||||
btnStatus.html("");
|
||||
piholeChanged("disabled");
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Enable/Disable
|
||||
$("#pihole-enable").on("click", (e) => {
|
||||
e.preventDefault();
|
||||
piholeChange("enable","");
|
||||
});
|
||||
$("#pihole-disable-permanently").on("click", (e) => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable","0");
|
||||
});
|
||||
$("#pihole-disable-10s").on("click", (e) => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable","10");
|
||||
setTimeout(function(){piholeChanged("enabled");},10000);
|
||||
});
|
||||
$("#pihole-disable-30s").on("click", (e) => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable","30");
|
||||
setTimeout(function(){piholeChanged("enabled");},30000);
|
||||
});
|
||||
$("#pihole-disable-5m").on("click", (e) => {
|
||||
e.preventDefault();
|
||||
piholeChange("disable","300");
|
||||
setTimeout(function(){piholeChanged("enabled");},300000);
|
||||
});
|
||||
|
||||
var piholeVersion = $("#piholeVersion").html();
|
||||
var webVersion = $("#webVersion").html();
|
||||
|
||||
// Credit for following function: https://gist.github.com/alexey-bass/1115557
|
||||
// Modified to discard any possible "v" in the string
|
||||
function versionCompare(left, right) {
|
||||
if (typeof left + typeof right !== "stringstring")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we are on vDev then we assume that it is always
|
||||
// newer than the latest online release, i.e. version
|
||||
// comparison should return 1
|
||||
if(left === "vDev")
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var aa = left.split("v"),
|
||||
bb = right.split("v");
|
||||
|
||||
var a = aa[aa.length-1].split(".")
|
||||
, b = bb[bb.length-1].split(".")
|
||||
, i = 0, len = Math.max(a.length, b.length);
|
||||
|
||||
for (; i < len; i++) {
|
||||
if ((a[i] && !b[i] && parseInt(a[i]) > 0) || (parseInt(a[i]) > parseInt(b[i]))) {
|
||||
return 1;
|
||||
} else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || (parseInt(a[i]) < parseInt(b[i]))) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Update check
|
||||
$.getJSON("https://api.github.com/repos/pi-hole/pi-hole/releases/latest", function(json) {
|
||||
if(versionCompare(piholeVersion, json.tag_name.slice(1)) < 0) {
|
||||
// Alert user
|
||||
$("#piholeVersion").html($("#piholeVersion").text() + "<a class=\"alert-link\" href=\"https://github.com/pi-hole/pi-hole/releases\">(Update available!)</a>");
|
||||
$("#alPiholeUpdate").show();
|
||||
}
|
||||
});
|
||||
$.getJSON("https://api.github.com/repos/pi-hole/AdminLTE/releases/latest", function(json) {
|
||||
if(versionCompare(webVersion, json.tag_name.slice(1)) < 0) {
|
||||
// Alert user
|
||||
$("#webVersion").html($("#webVersion").text() + "<a class=\"alert-link\" href=\"https://github.com/pi-hole/adminLTE/releases\">(Update available!)</a>");
|
||||
$("#alWebUpdate").show();
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Make sure that Pi-hole is updated to at least v2.7, since that is needed to use the sudo
|
||||
* features of the interface. Skip if on dev
|
||||
*/
|
||||
if(versionCompare(piholeVersion, "v2.7") < 0) {
|
||||
alert("Pi-hole needs to be updated to at least v2.7 before you can use features such as whitelisting/blacklisting from this web interface!");
|
||||
}
|
||||
|
||||
// Session timer
|
||||
var sessionvalidity = parseInt(document.getElementById("sessiontimercounter").textContent);
|
||||
var start = new Date;
|
||||
|
||||
function updateSessionTimer()
|
||||
{
|
||||
start = new Date;
|
||||
start.setSeconds(start.getSeconds() + sessionvalidity);
|
||||
}
|
||||
|
||||
if(sessionvalidity > 0)
|
||||
{
|
||||
// setSeconds will correctly handle wrap-around cases
|
||||
updateSessionTimer();
|
||||
|
||||
setInterval(function() {
|
||||
var current = new Date;
|
||||
var totalseconds = (start - current) / 1000;
|
||||
|
||||
// var hours = Math.floor(totalseconds / 3600);
|
||||
// totalseconds = totalseconds % 3600;
|
||||
|
||||
var minutes = Math.floor(totalseconds / 60);
|
||||
if(minutes < 10){ minutes = "0" + minutes; }
|
||||
|
||||
var seconds = Math.floor(totalseconds % 60);
|
||||
if(seconds < 10){ seconds = "0" + seconds; }
|
||||
|
||||
if(totalseconds > 0)
|
||||
{
|
||||
document.getElementById("sessiontimercounter").textContent = minutes + ":" + seconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("sessiontimercounter").textContent = "-- : --";
|
||||
}
|
||||
|
||||
}, 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
document.getElementById("sessiontimer").style.display = "none";
|
||||
}
|
||||
|
||||
// Hide "exact match" button on queryads.php page if version is 2.9.5 or lower
|
||||
if(versionCompare(piholeVersion, "v2.9.5") < 1)
|
||||
{
|
||||
$("#btnSearchExact").hide();
|
||||
}
|
||||
40
scripts/pi-hole/js/gravity.js
Normal file
40
scripts/pi-hole/js/gravity.js
Normal file
@@ -0,0 +1,40 @@
|
||||
function eventsource() {
|
||||
var alInfo = $("#alInfo");
|
||||
var alSuccess = $("#alSuccess");
|
||||
var ta = $("#output");
|
||||
var source = new EventSource("scripts/pi-hole/php/gravity.sh.php");
|
||||
|
||||
ta.html("");
|
||||
ta.show();
|
||||
alInfo.show();
|
||||
alSuccess.hide();
|
||||
|
||||
source.addEventListener("message", function(e) {
|
||||
if(e.data.indexOf("Pi-hole blocking is") !== -1)
|
||||
{
|
||||
alSuccess.show();
|
||||
}
|
||||
|
||||
ta.append(e.data);
|
||||
|
||||
}, false);
|
||||
|
||||
// Will be called when script has finished
|
||||
source.addEventListener("error", function(e) {
|
||||
alInfo.delay(1000).fadeOut(2000, function() { alInfo.hide(); });
|
||||
source.close();
|
||||
$("#gravityBtn").removeAttr("disabled");
|
||||
}, false);
|
||||
}
|
||||
|
||||
$("#gravityBtn").on("click", () => {
|
||||
$("#gravityBtn").attr("disabled", true);
|
||||
eventsource();
|
||||
});
|
||||
|
||||
// Handle hiding of alerts
|
||||
$(function(){
|
||||
$("[data-hide]").on("click", function(){
|
||||
$(this).closest("." + $(this).attr("data-hide")).hide();
|
||||
});
|
||||
});
|
||||
3
scripts/pi-hole/js/header.js
Normal file
3
scripts/pi-hole/js/header.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// Remove JS warning
|
||||
var jswarn = document.getElementById("js-warn-exit");
|
||||
jswarn.parentNode.removeChild(jswarn);
|
||||
410
scripts/pi-hole/js/index.js
Normal file
410
scripts/pi-hole/js/index.js
Normal file
@@ -0,0 +1,410 @@
|
||||
// Define global variables
|
||||
var timeLineChart, queryTypeChart, forwardDestinationChart;
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
// Functions to update data in page
|
||||
|
||||
|
||||
function updateSummaryData(runOnce) {
|
||||
var setTimer = function(timeInSeconds) {
|
||||
if (!runOnce) {
|
||||
setTimeout(updateSummaryData, timeInSeconds * 1000);
|
||||
}
|
||||
};
|
||||
$.getJSON("api.php?summary", function LoadSummaryData(data) {
|
||||
|
||||
["ads_blocked_today", "dns_queries_today", "ads_percentage_today"].forEach(function(today) {
|
||||
var todayElement = $("h3#" + today);
|
||||
todayElement.text() !== data[today] && todayElement.addClass("glow");
|
||||
});
|
||||
|
||||
window.setTimeout(function() {
|
||||
["ads_blocked_today", "dns_queries_today", "domains_being_blocked", "ads_percentage_today"].forEach(function(header, idx) {
|
||||
var textData = idx === 3 ? data[header] + "%" : data[header];
|
||||
$("h3#" + header).text(textData);
|
||||
});
|
||||
$("h3.statistic.glow").removeClass("glow");
|
||||
}, 500);
|
||||
|
||||
updateSessionTimer();
|
||||
}).done(function() {
|
||||
setTimer(10);
|
||||
}).fail(function() {
|
||||
setTimer(300);
|
||||
});
|
||||
}
|
||||
|
||||
var failures = 0;
|
||||
function updateQueriesOverTime() {
|
||||
$.getJSON("api.php?overTimeData10mins", 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 last data point since it not representative
|
||||
data.ads_over_time[0].splice(-1,1);
|
||||
// Remove possibly already existing data
|
||||
timeLineChart.data.labels = [];
|
||||
timeLineChart.data.datasets[0].data = [];
|
||||
timeLineChart.data.datasets[1].data = [];
|
||||
// Add data for each hour that is available
|
||||
for (var hour in data.ads_over_time[0]) {
|
||||
if ({}.hasOwnProperty.call(data.ads_over_time[0], hour)) {
|
||||
var h = parseInt(data.domains_over_time[0][hour]);
|
||||
var d = new Date().setHours(Math.floor(h / 6), 10 * (h % 6), 0, 0);
|
||||
|
||||
timeLineChart.data.labels.push(d);
|
||||
timeLineChart.data.datasets[0].data.push(data.domains_over_time[1][hour]);
|
||||
timeLineChart.data.datasets[1].data.push(data.ads_over_time[1][hour]);
|
||||
}
|
||||
}
|
||||
$("#queries-over-time .overlay").remove();
|
||||
timeLineChart.update();
|
||||
}).done(function() {
|
||||
// Reload graph after 10 minutes
|
||||
failures = 0;
|
||||
setTimeout(updateQueriesOverTime, 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(updateQueriesOverTime, 60000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateQueryTypes() {
|
||||
$.getJSON("api.php?getQueryTypes", function(data) {
|
||||
var colors = [];
|
||||
// Get colors from AdminLTE
|
||||
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
|
||||
var v = [], c = [];
|
||||
// Collect values and colors, immediately push individual labels
|
||||
$.each(data, function(key , value) {
|
||||
v.push(value);
|
||||
c.push(colors.shift());
|
||||
queryTypeChart.data.labels.push(key.substr(6,key.length - 7));
|
||||
});
|
||||
// Build a single dataset with the data to be pushed
|
||||
var dd = {data: v, backgroundColor: c};
|
||||
// and push it at once
|
||||
queryTypeChart.data.datasets.push(dd);
|
||||
$("#query-types .overlay").remove();
|
||||
queryTypeChart.update();
|
||||
queryTypeChart.chart.config.options.cutoutPercentage=30;
|
||||
queryTypeChart.update();
|
||||
});
|
||||
}
|
||||
|
||||
// Credit: http://stackoverflow.com/questions/1787322/htmlspecialchars-equivalent-in-javascript/4835406#4835406
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"\"": """,
|
||||
"\'": "'"
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function updateTopClientsChart() {
|
||||
$.getJSON("api.php?summaryRaw&getQuerySources", function(data) {
|
||||
var clienttable = $("#client-frequency").find("tbody:last");
|
||||
var domain, percentage, domainname;
|
||||
for (domain in data.top_sources) {
|
||||
|
||||
if ({}.hasOwnProperty.call(data.top_sources, domain)){
|
||||
// Sanitize domain
|
||||
domain = escapeHtml(domain);
|
||||
if(domain.indexOf("|") > -1)
|
||||
{
|
||||
domainname = domain.substr(0, domain.indexOf("|"));
|
||||
}
|
||||
else
|
||||
{
|
||||
domainname = domain;
|
||||
}
|
||||
|
||||
var url = "<a href=\"queries.php?client="+domain+"\">"+domainname+"</a>";
|
||||
percentage = data.top_sources[domain] / data.dns_queries_today * 100;
|
||||
clienttable.append("<tr> <td>" + url +
|
||||
"</td> <td>" + data.top_sources[domain] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"%\"> <div class=\"progress-bar progress-bar-blue\" style=\"width: " +
|
||||
percentage + "%\"></div> </div> </td> </tr> ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$("#client-frequency .overlay").remove();
|
||||
});
|
||||
}
|
||||
|
||||
function updateForwardDestinations() {
|
||||
$.getJSON("api.php?getForwardDestinations", function(data) {
|
||||
var colors = [];
|
||||
// Get colors from AdminLTE
|
||||
$.each($.AdminLTE.options.colors, function(key, value) { colors.push(value); });
|
||||
var v = [], c = [];
|
||||
// Collect values and colors, immediately push individual labels
|
||||
$.each(data, function(key , value) {
|
||||
v.push(value);
|
||||
c.push(colors.shift());
|
||||
if(key.indexOf("|") > -1)
|
||||
{
|
||||
key = key.substr(0, key.indexOf("|"));
|
||||
}
|
||||
forwardDestinationChart.data.labels.push(key);
|
||||
});
|
||||
// Build a single dataset with the data to be pushed
|
||||
var dd = {data: v, backgroundColor: c};
|
||||
// and push it at once
|
||||
forwardDestinationChart.data.datasets.push(dd);
|
||||
$("#forward-destinations .overlay").remove();
|
||||
forwardDestinationChart.update();
|
||||
forwardDestinationChart.chart.config.options.cutoutPercentage=30;
|
||||
forwardDestinationChart.update();
|
||||
});
|
||||
}
|
||||
|
||||
function updateTopLists() {
|
||||
$.getJSON("api.php?summaryRaw&topItems", function(data) {
|
||||
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);
|
||||
if(domain !== "pi.hole")
|
||||
{
|
||||
url = "<a href=\"queries.php?domain="+domain+"\">"+domain+"</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
url = domain;
|
||||
}
|
||||
percentage = data.top_queries[domain] / data.dns_queries_today * 100;
|
||||
domaintable.append("<tr> <td>" + url +
|
||||
"</td> <td>" + data.top_queries[domain] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"%\"> <div class=\"progress-bar progress-bar-green\" style=\"width: " +
|
||||
percentage + "%\"></div> </div> </td> </tr> ");
|
||||
}
|
||||
}
|
||||
|
||||
// Remove table if there are no results (e.g. privacy mode enabled)
|
||||
if(jQuery.isEmptyObject(data.top_queries))
|
||||
{
|
||||
$("#domain-frequency").parent().remove();
|
||||
}
|
||||
|
||||
for (domain in data.top_ads) {
|
||||
if ({}.hasOwnProperty.call(data.top_ads,domain)){
|
||||
// Sanitize domain
|
||||
domain = escapeHtml(domain);
|
||||
url = "<a href=\"queries.php?domain="+domain+"\">"+domain+"</a>";
|
||||
percentage = data.top_ads[domain] / data.ads_blocked_today * 100;
|
||||
adtable.append("<tr> <td>" + url +
|
||||
"</td> <td>" + data.top_ads[domain] + "</td> <td> <div class=\"progress progress-sm\" title=\""+percentage.toFixed(1)+"%\"> <div class=\"progress-bar progress-bar-yellow\" style=\"width: " +
|
||||
percentage + "%\"></div> </div> </td> </tr> ");
|
||||
}
|
||||
}
|
||||
|
||||
$("#domain-frequency .overlay").remove();
|
||||
$("#ad-frequency .overlay").remove();
|
||||
});
|
||||
}
|
||||
|
||||
$(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 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(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)+":00";
|
||||
var to = padNumber(h)+":"+padNumber(m+9)+":59";
|
||||
return "Queries from "+from+" to "+to;
|
||||
},
|
||||
label(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
|
||||
|
||||
updateSummaryData();
|
||||
|
||||
updateQueriesOverTime();
|
||||
|
||||
// Create / load "Query Types" only if authorized
|
||||
if(document.getElementById("queryTypeChart"))
|
||||
{
|
||||
ctx = document.getElementById("queryTypeChart").getContext("2d");
|
||||
queryTypeChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
updateQueryTypes();
|
||||
}
|
||||
|
||||
// Create / load "Forward Destinations" only if authorized
|
||||
if(document.getElementById("forwardDestinationChart"))
|
||||
{
|
||||
ctx = document.getElementById("forwardDestinationChart").getContext("2d");
|
||||
forwardDestinationChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{ data: [] }]
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
animation: {
|
||||
duration: 2000
|
||||
},
|
||||
cutoutPercentage: 0
|
||||
}
|
||||
});
|
||||
updateForwardDestinations();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
141
scripts/pi-hole/js/list.js
Normal file
141
scripts/pi-hole/js/list.js
Normal file
@@ -0,0 +1,141 @@
|
||||
// IE likes to cache too much :P
|
||||
$.ajaxSetup({cache: false});
|
||||
|
||||
// Get PHP info
|
||||
var token = $("#token").html();
|
||||
var listType = $("#list-type").html();
|
||||
var fullName = listType === "white" ? "Whitelist" : "Blacklist";
|
||||
|
||||
function sub(index, entry) {
|
||||
var domain = $("#"+index);
|
||||
domain.hide("highlight");
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/sub.php",
|
||||
method: "post",
|
||||
data: {"domain":entry, "list":listType, "token":token},
|
||||
success: function(response) {
|
||||
if(response.length !== 0){
|
||||
return;
|
||||
}
|
||||
domain.remove();
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
alert("Failed to remove the domain!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refresh(fade) {
|
||||
var list = $("#list");
|
||||
if(fade) {
|
||||
list.fadeOut(100);
|
||||
}
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/get.php",
|
||||
method: "get",
|
||||
data: {"list":listType},
|
||||
success: function(response) {
|
||||
list.html("");
|
||||
var data = JSON.parse(response);
|
||||
|
||||
if(data.length === 0) {
|
||||
list.html("<div class=\"alert alert-info\" role=\"alert\">Your " + fullName + " is empty!</div>");
|
||||
}
|
||||
else {
|
||||
data.forEach(function (entry, index) {
|
||||
list.append(
|
||||
"<li id=\"" + index + "\" class=\"list-group-item clearfix\">" + entry +
|
||||
"<button class=\"btn btn-danger btn-xs pull-right\" type=\"button\">" +
|
||||
"<span class=\"glyphicon glyphicon-trash\"></span></button></li>"
|
||||
);
|
||||
|
||||
// Handle button
|
||||
$("#list #"+index+"").on("click", "button", function() {
|
||||
sub(index, entry);
|
||||
});
|
||||
});
|
||||
}
|
||||
list.fadeIn("fast");
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
$("#alFailure").show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.onload = refresh(false);
|
||||
|
||||
function add() {
|
||||
var domain = $("#domain");
|
||||
if(domain.val().length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var alInfo = $("#alInfo");
|
||||
var alSuccess = $("#alSuccess");
|
||||
var alFailure = $("#alFailure");
|
||||
alInfo.show();
|
||||
alSuccess.hide();
|
||||
alFailure.hide();
|
||||
$.ajax({
|
||||
url: "scripts/pi-hole/php/add.php",
|
||||
method: "post",
|
||||
data: {"domain":domain.val(), "list":listType, "token":token},
|
||||
success: function(response) {
|
||||
if (response.indexOf("not a valid argument") >= 0 ||
|
||||
response.indexOf("is not a valid domain") >= 0) {
|
||||
alFailure.show();
|
||||
alFailure.delay(1000).fadeOut(2000, function() {
|
||||
alFailure.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
} else {
|
||||
alSuccess.show();
|
||||
alSuccess.delay(1000).fadeOut(2000, function() {
|
||||
alSuccess.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
domain.val("");
|
||||
refresh(true);
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
alFailure.show();
|
||||
alFailure.delay(1000).fadeOut(2000, function() {
|
||||
alFailure.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handle enter button for adding domains
|
||||
$(document).keypress(function(e) {
|
||||
if(e.which === 13 && $("#domain").is(":focus")) {
|
||||
// Enter was pressed, and the input has focus
|
||||
add();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle buttons
|
||||
$("#btnAdd").on("click", function() {
|
||||
add();
|
||||
});
|
||||
$("#btnRefresh").on("click", function() {
|
||||
refresh(true);
|
||||
});
|
||||
|
||||
// Handle hiding of alerts
|
||||
$(function(){
|
||||
$("[data-hide]").on("click", function(){
|
||||
$(this).closest("." + $(this).attr("data-hide")).hide();
|
||||
});
|
||||
});
|
||||
138
scripts/pi-hole/js/queries.js
Normal file
138
scripts/pi-hole/js/queries.js
Normal file
@@ -0,0 +1,138 @@
|
||||
var tableApi;
|
||||
|
||||
function escapeRegex(text) {
|
||||
var map = {
|
||||
"(": "\\(",
|
||||
")": "\\)",
|
||||
".": "\\.",
|
||||
};
|
||||
return text.replace(/[().]/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
tableApi.ajax.url("api.php?getAllQueries").load();
|
||||
// updateSessionTimer();
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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();
|
||||
alFailure.delay(1000).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();
|
||||
alFailure.delay(1000).fadeOut(2000, function() {
|
||||
alFailure.hide();
|
||||
});
|
||||
alInfo.delay(1000).fadeOut(2000, function() {
|
||||
alInfo.hide();
|
||||
alList.html("");
|
||||
alDomain.html("");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
tableApi = $("#all-queries").DataTable( {
|
||||
"rowCallback": function( row, data, index ){
|
||||
if (data[4] === "Pi-holed") {
|
||||
$(row).css("color","red");
|
||||
$("td:eq(5)", row).html( "<button style=\"color:green;\"><i class=\"fa fa-pencil-square-o\"></i> Whitelist</button>" );
|
||||
}
|
||||
else{
|
||||
$(row).css("color","green");
|
||||
$("td:eq(5)", row).html( "<button style=\"color:red;\"><i class=\"fa fa-ban\"></i> Blacklist</button>" );
|
||||
}
|
||||
|
||||
},
|
||||
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": "api.php?getAllQueries",
|
||||
"autoWidth" : false,
|
||||
"order" : [[0, "desc"]],
|
||||
"columns": [
|
||||
{ "width" : "20%", "type": "date" },
|
||||
{ "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": ""
|
||||
} ]
|
||||
});
|
||||
$("#all-queries tbody").on( "click", "button", function () {
|
||||
var data = tableApi.row( $(this).parents("tr") ).data();
|
||||
if (data[4] === "Pi-holed")
|
||||
{
|
||||
add(data[2],"white");
|
||||
}
|
||||
else
|
||||
{
|
||||
add(data[2],"black");
|
||||
}
|
||||
} );
|
||||
|
||||
// Do we want to filter queries?
|
||||
var GETDict = {};
|
||||
location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];});
|
||||
if("client" in GETDict)
|
||||
{
|
||||
// Search in third column (zero indexed)
|
||||
// Use regular expression to only show exact matches, i.e.
|
||||
// don't show 192.168.0.100 when searching for 192.168.0.1
|
||||
// true = use regex, false = don't use smart search
|
||||
tableApi.column(3).search("^"+escapeRegex(GETDict["client"])+"$",true,false);
|
||||
}
|
||||
if("domain" in GETDict)
|
||||
{
|
||||
// Search in second column (zero indexed)
|
||||
tableApi.column(2).search("^"+escapeRegex(GETDict["domain"])+"$",true,false);
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
72
scripts/pi-hole/js/queryads.js
Normal file
72
scripts/pi-hole/js/queryads.js
Normal file
@@ -0,0 +1,72 @@
|
||||
var exact = "";
|
||||
function eventsource() {
|
||||
var ta = $("#output");
|
||||
var domain = $("#domain");
|
||||
var q = $("#quiet");
|
||||
if(domain.val().length === 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var quiet = false;
|
||||
if(q.val() === "yes")
|
||||
{
|
||||
quiet = true;
|
||||
exact = "exact";
|
||||
}
|
||||
|
||||
var host = window.location.host;
|
||||
var source = new EventSource("http://"+host+"/admin/scripts/pi-hole/php/queryads.php?domain="+domain.val().toLowerCase()+"&"+exact);
|
||||
|
||||
// Reset and show field
|
||||
ta.empty();
|
||||
ta.show();
|
||||
|
||||
source.addEventListener("message", function(e) {
|
||||
if(!quiet)
|
||||
{
|
||||
ta.append(e.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
var lines = e.data.split("\n");
|
||||
for(var i = 0;i<lines.length;i++)
|
||||
{
|
||||
if(lines[i].indexOf("results") !== -1 && lines[i].indexOf("0 results") === -1)
|
||||
{
|
||||
var shortstring = lines[i].replace("::: /etc/pihole/","");
|
||||
// Remove "(x results)"
|
||||
shortstring = shortstring.replace(/\(.*/,"");
|
||||
ta.append(shortstring+"\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Will be called when script has finished
|
||||
source.addEventListener("error", function(e) {
|
||||
source.close();
|
||||
}, false);
|
||||
|
||||
// Reset exact variable
|
||||
exact = "";
|
||||
}
|
||||
|
||||
// Handle enter button
|
||||
$(document).keypress(function(e) {
|
||||
if(e.which === 13 && $("#domain").is(":focus")) {
|
||||
// Enter was pressed, and the input has focus
|
||||
exact = "";
|
||||
eventsource();
|
||||
}
|
||||
});
|
||||
// Handle button
|
||||
$("#btnSearch").on("click", function() {
|
||||
exact = "";
|
||||
eventsource();
|
||||
});
|
||||
// Handle exact button
|
||||
$("#btnSearchExact").on("click", function() {
|
||||
exact = "exact";
|
||||
eventsource();
|
||||
});
|
||||
89
scripts/pi-hole/js/settings.js
Normal file
89
scripts/pi-hole/js/settings.js
Normal file
@@ -0,0 +1,89 @@
|
||||
$(function () {
|
||||
$("[data-mask]").inputmask();
|
||||
});
|
||||
|
||||
$(".confirm-reboot").confirm({
|
||||
text: "Are you sure you want to send a reboot command to your Pi-Hole?",
|
||||
title: "Confirmation required",
|
||||
confirm(button) {
|
||||
$("#rebootform").submit();
|
||||
},
|
||||
cancel(button) {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, reboot",
|
||||
cancelButton: "No, go back",
|
||||
post: true,
|
||||
confirmButtonClass: "btn-danger",
|
||||
cancelButtonClass: "btn-success",
|
||||
dialogClass: "modal-dialog modal-mg" // Bootstrap classes for mid-size modal
|
||||
});
|
||||
|
||||
$(".confirm-restartdns").confirm({
|
||||
text: "Are you sure you want to send a restart command to your DNS server?",
|
||||
title: "Confirmation required",
|
||||
confirm(button) {
|
||||
$("#restartdnsform").submit();
|
||||
},
|
||||
cancel(button) {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, restart DNS",
|
||||
cancelButton: "No, go back",
|
||||
post: true,
|
||||
confirmButtonClass: "btn-danger",
|
||||
cancelButtonClass: "btn-success",
|
||||
dialogClass: "modal-dialog modal-mg"
|
||||
});
|
||||
|
||||
$(".confirm-flushlogs").confirm({
|
||||
text: "By default, the log is flushed at the end of the day via cron, but a very large log file can slow down the Web interface, so flushing it can be useful. Note that your statistics will be reset and you lose the statistics up to this point. Are you sure you want to flush your logs?",
|
||||
title: "Confirmation required",
|
||||
confirm(button) {
|
||||
$("#flushlogsform").submit();
|
||||
},
|
||||
cancel(button) {
|
||||
// nothing to do
|
||||
},
|
||||
confirmButton: "Yes, flush logs",
|
||||
cancelButton: "No, go back",
|
||||
post: true,
|
||||
confirmButtonClass: "btn-danger",
|
||||
cancelButtonClass: "btn-success",
|
||||
dialogClass: "modal-dialog modal-mg"
|
||||
});
|
||||
|
||||
$("#DHCPchk").click(function() {
|
||||
$("input.DHCPgroup").prop("disabled", !this.checked);
|
||||
});
|
||||
|
||||
var leasetable;
|
||||
$(document).ready(function() {
|
||||
if(document.getElementById("DHCPLeasesTable"))
|
||||
{
|
||||
leasetable = $("#DHCPLeasesTable").DataTable({
|
||||
dom: "<'row'<'col-sm-6'i><'col-sm-6'f>>" +
|
||||
"<'row'<'col-sm-12'tr>>",
|
||||
"paging": false,
|
||||
"scrollCollapse": true,
|
||||
"scrollY": "200px",
|
||||
"scrollX" : true
|
||||
});
|
||||
$("#leaseexpand").on( "click", function () {
|
||||
setTimeout(function(){leasetable.draw();},100);
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
// Handle hiding of alerts
|
||||
$(function(){
|
||||
$("[data-hide]").on("click", function(){
|
||||
$(this).closest("." + $(this).attr("data-hide")).hide();
|
||||
});
|
||||
});
|
||||
|
||||
// DHCP leases tooltips
|
||||
$(document).ready(function(){
|
||||
$("[data-toggle=\"tooltip\"]").tooltip({"html": true, container : "body"});
|
||||
});
|
||||
|
||||
40
scripts/pi-hole/js/taillog.js
Normal file
40
scripts/pi-hole/js/taillog.js
Normal file
@@ -0,0 +1,40 @@
|
||||
var offset, timer, pre, scrolling = true;
|
||||
|
||||
// Check every 200msec for fresh data
|
||||
var interval = 200;
|
||||
|
||||
// Function that asks the API for new data
|
||||
function reloadData(){
|
||||
clearTimeout(timer);
|
||||
$.getJSON("api.php?tailLog="+offset, function (data)
|
||||
{
|
||||
offset = data["offset"];
|
||||
pre.append(data["lines"]);
|
||||
});
|
||||
|
||||
if(scrolling)
|
||||
{
|
||||
window.scrollTo(0,document.body.scrollHeight);
|
||||
}
|
||||
timer = setTimeout(reloadData, interval);
|
||||
}
|
||||
|
||||
$(function(){
|
||||
// Get offset at first loading of page
|
||||
$.getJSON("api.php?tailLog", function (data)
|
||||
{
|
||||
offset = data["offset"];
|
||||
});
|
||||
pre = $("#output");
|
||||
// Trigger function that looks for new data
|
||||
reloadData();
|
||||
});
|
||||
|
||||
$("#chk1").click(function() {
|
||||
$("#chk2").prop("checked",this.checked);
|
||||
scrolling = this.checked;
|
||||
});
|
||||
$("#chk2").click(function() {
|
||||
$("#chk1").prop("checked",this.checked);
|
||||
scrolling = this.checked;
|
||||
});
|
||||
Reference in New Issue
Block a user