Merge branch 'devel' into patch-1

Signed-off-by: Ghalid <24234921+Ghalid@users.noreply.github.com>
This commit is contained in:
Ghalid
2019-12-29 14:07:06 -05:00
32 changed files with 8268 additions and 311 deletions

View File

@@ -34,6 +34,11 @@ $(document).ready(function() {
table = $("#customDNSTable").DataTable( {
"ajax": "scripts/pi-hole/php/customdns.php?action=get",
columns: [
{},
{},
{orderable: false, searchable: false}
],
"columnDefs": [ {
"targets": 2,
"render": function ( data, type, row ) {

View File

@@ -11,6 +11,7 @@ var start__ = moment().subtract(6, "days");
var from = moment(start__).utc().valueOf()/1000;
var end__ = moment();
var until = moment(end__).utc().valueOf()/1000;
var interval = 0;
var timeoutWarning = $("#timeoutWarning");
@@ -71,7 +72,35 @@ function compareNumbers(a, b) {
function updateQueriesOverTime() {
$("#queries-over-time .overlay").show();
timeoutWarning.show();
$.getJSON("api_db.php?getGraphData&from="+from+"&until="+until, function(data) {
// Compute interval to obtain about 200 values
var num = 200;
interval = (until-from)/num;
// Default displaying axis scaling
timeLineChart.options.scales.xAxes[0].time.unit="hour"
if(num*interval >= 6*29*24*60*60)
{
// If the requested data is more than 3 months, set ticks interval to quarterly
timeLineChart.options.scales.xAxes[0].time.unit="quarter"
}
else if(num*interval >= 3*29*24*60*60)
{
// If the requested data is more than 3 months, set ticks interval to months
timeLineChart.options.scales.xAxes[0].time.unit="month"
}
if(num*interval >= 29*24*60*60)
{
// If the requested data is more than 1 month, set ticks interval to weeks
timeLineChart.options.scales.xAxes[0].time.unit="week"
}
else if(num*interval >= 6*24*60*60)
{
// If the requested data is more than 1 week, set ticks interval to days
timeLineChart.options.scales.xAxes[0].time.unit="day"
}
$.getJSON("api_db.php?getGraphData&from="+from+"&until="+until+"&interval="+interval, function(data) {
// convert received objects to arrays
data.domains_over_time = objectToArray(data.domains_over_time);
@@ -103,8 +132,8 @@ function updateQueriesOverTime() {
// Add data for each hour that is available
for (hour in dates) {
if (Object.prototype.hasOwnProperty.call(dates, hour)) {
var d, dom = 0, ads = 0;
d = new Date(1000*dates[hour]);
var date, dom = 0, ads = 0;
date = new Date(1000*dates[hour]);
var idx = data.domains_over_time[0].indexOf(dates[hour].toString());
if (idx > -1)
@@ -118,8 +147,8 @@ function updateQueriesOverTime() {
ads = data.ads_over_time[1][idx];
}
timeLineChart.data.labels.push(d);
timeLineChart.data.datasets[0].data.push(dom);
timeLineChart.data.labels.push(date);
timeLineChart.data.datasets[0].data.push(dom - ads);
timeLineChart.data.datasets[1].data.push(ads);
}
}
@@ -134,51 +163,66 @@ function updateQueriesOverTime() {
$(document).ready(function() {
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
timeLineChart = new Chart(ctx, {
type: "line",
type: "bar",
data: {
labels: [ 0 ],
labels: [ ],
datasets: [
{
label: "Total DNS Queries",
label: "Permitted DNS Queries",
fill: true,
backgroundColor: "rgba(220,220,220,0.5)",
backgroundColor: "rgba(0, 166, 90,.8)",
borderColor: "rgba(0, 166, 90,.8)",
pointBorderColor: "rgba(0, 166, 90,.8)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
pointHitRadius: 5
},
{
label: "Blocked DNS Queries",
fill: true,
backgroundColor: "rgba(0,192,239,0.5)",
backgroundColor: "rgba(0,192,239,1)",
borderColor: "rgba(0,192,239,1)",
pointBorderColor: "rgba(0,192,239,1)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
pointHitRadius: 5
}
]
},
options: {
tooltips: {
enabled: true,
responsive: true,
mode: "x-axis",
callbacks: {
title: function(tooltipItem) {
var label = tooltipItem[0].xLabel;
var time = new Date(label);
var date = time.getFullYear()+"-"+padNumber(time.getMonth()+1)+"-"+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;
var from_date = time.getFullYear() +
"-" +
padNumber(time.getMonth()+1) +
"-" +
padNumber(time.getDate()) +
" " +
padNumber(time.getHours()) +
":" +
padNumber(time.getMinutes()) +
":" +
padNumber(time.getSeconds());
time = new Date(time.valueOf() + 1000 * interval);
var until_date = time.getFullYear() +
"-" +
padNumber(time.getMonth()+1) +
"-" +
padNumber(time.getDate()) +
" " +
padNumber(time.getHours()) +
":" +
padNumber(time.getMinutes()) +
":" +
padNumber(time.getSeconds());
return "Queries from " + from_date + " to " + until_date;
},
label: function(tooltipItems, data) {
if(tooltipItems.datasetIndex === 1)
@@ -203,20 +247,22 @@ $(document).ready(function() {
scales: {
xAxes: [{
type: "time",
display: false,
stacked: true,
time: {
unit: "hour",
displayFormats: {
"minute": "HH:mm",
"hour": "HH:mm",
"day": "HH:mm",
"week": "MMM DD HH:mm",
"month": "MMM DD",
"quarter": "MMM DD",
"year": "MMM DD"
"day": "MMM DD",
"week": "MMM DD",
"month": "MMM",
"quarter": "MMM",
"year": "YYYY MMM"
}
}
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}

View File

@@ -20,7 +20,7 @@ var dateformat = "MMMM Do YYYY, HH:mm";
// Do we want to filter queries?
var GETDict = {};
location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];});
window.location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];});
if("from" in GETDict && "until" in GETDict)
{

View File

@@ -0,0 +1,384 @@
var table;
var groups = [];
const token = $("#token").html();
var info = null;
function showAlert(type, icon, title, message) {
let opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
switch (type) {
case "info":
opts = {
type: "info",
icon: "glyphicon glyphicon-time",
title: title,
message: message
};
info = $.notify(opts);
break;
case "success":
opts = {
type: "success",
icon: icon,
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "warning":
opts = {
type: "warning",
icon: "glyphicon glyphicon-warning-sign",
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "error":
opts = {
type: "danger",
icon: "glyphicon glyphicon-remove",
title: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
default:
return;
}
}
function get_groups() {
$.post(
"scripts/pi-hole/php/groups.php",
{ action: "get_groups", token: token },
function(data) {
groups = data.data;
initTable();
},
"json"
);
}
function datetime(date) {
return moment.unix(Math.floor(date)).format("Y-MM-DD HH:mm:ss z");
}
$(document).ready(function() {
$("#btnAdd").on("click", addAdlist);
get_groups();
$("#select").on("change", function() {
$("#ip-custom").val("");
$("#ip-custom").prop(
"disabled",
$("#select option:selected").val() !== "custom"
);
});
});
function initTable() {
table = $("#adlistsTable").DataTable({
ajax: {
url: "scripts/pi-hole/php/groups.php",
data: { action: "get_adlists", token: token },
type: "POST"
},
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
{ data: "address" },
{ data: "enabled", searchable: false },
{ data: "comment" },
{ data: "groups", searchable: false },
{ data: null, width: "80px", orderable: false }
],
drawCallback: function(settings) {
$(".deleteAdlist").on("click", deleteAdlist);
},
rowCallback: function(row, data) {
const tooltip =
"Added: " +
datetime(data.date_added) +
"\nLast modified: " +
datetime(data.date_modified) +
"\nDatabase ID: " +
data.id;
$("td:eq(0)", row).html(
'<code id="address" title="' + tooltip + '">' + data.address + "</code>"
);
const disabled = data.enabled === 0;
$("td:eq(1)", row).html(
'<input type="checkbox" id="status"' +
(disabled ? "" : " checked") +
">"
);
var status = $("#status", row);
status.bootstrapToggle({
on: "Enabled",
off: "Disabled",
size: "small",
onstyle: "success",
width: "80px"
});
status.on("change", editAdlist);
$("td:eq(2)", row).html(
'<input id="comment" class="form-control"><input id="id" type="hidden" value="' +
data.id +
'">'
);
var comment = $("#comment", row);
comment.val(data.comment);
comment.on("change", editAdlist);
$("td:eq(3)", row).empty();
$("td:eq(3)", row).append(
'<select id="multiselect" multiple="multiple"></select>'
);
var sel = $("#multiselect", row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
if (!groups[i].enabled) {
extra = " (disabled)";
}
sel.append(
$("<option />")
.val(groups[i].id)
.text(groups[i].name + extra)
);
}
// Select assigned groups
sel.val(data.groups);
// Initialize multiselect
sel.multiselect({ includeSelectAllOption: true });
sel.on("change", editAdlist);
let button =
'<button class="btn btn-danger btn-xs deleteAdlist" type="button" data-id="' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
"</button>";
$("td:eq(4)", row).html(button);
},
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
],
stateSave: true,
stateSaveCallback: function(settings, data) {
// Store current state in client's local storage area
localStorage.setItem("groups-adlists-table", JSON.stringify(data));
},
stateLoadCallback: function(settings) {
// Receive previous state from client's local storage area
var data = localStorage.getItem("groups-adlists-table");
// Return if not available
if (data === null) {
return null;
}
data = JSON.parse(data);
// Always start on the first page to show most recent queries
data.start = 0;
// Always start with empty search field
data.search.search = "";
// Reset visibility of ID column
data.columns[0].visible = false;
// Apply loaded state to table
return data;
}
});
table.on("order.dt", function() {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
$("#resetButton").show();
} else {
$("#resetButton").hide();
}
});
$("#resetButton").on("click", function() {
table.order([[0, "asc"]]).draw();
$("#resetButton").hide();
});
}
function addAdlist() {
var address = $("#new_address").val();
var comment = $("#new_comment").val();
showAlert("info", "", "Adding adlist...", address);
if (address.length === 0) {
showAlert("warning", "", "Warning", "Please specify an adlist address");
return;
}
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: {
action: "add_adlist",
address: address,
comment: comment,
token: token
},
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-plus",
"Successfully added adlist",
address
);
$("#new_address").val("");
$("#new_comment").val("");
table.ajax.reload();
} else {
showAlert(
"error",
"",
"Error while adding new adlist: ",
response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while adding new adlist: ",
jqXHR.responseText
);
console.log(exception);
}
});
}
function editAdlist() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
var id = tr.find("#id").val();
var status = tr.find("#status").is(":checked") ? 1 : 0;
var comment = tr.find("#comment").val();
var groups = tr.find("#multiselect").val();
var address = tr.find("#address").text();
var done = "edited";
var not_done = "editing";
if (elem === "status" && status === 1) {
done = "enabled";
not_done = "enabling";
} else if (elem === "status" && status === 0) {
done = "disabled";
not_done = "disabling";
} else if (elem === "comment") {
done = "edited comment of";
not_done = "editing comment of";
} else if (elem === "multiselect") {
done = "edited groups of";
not_done = "editing groups of";
}
showAlert("info", "", "Editing adlist...", address);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: {
action: "edit_adlist",
id: id,
comment: comment,
status: status,
groups: groups,
token: token
},
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-pencil",
"Successfully " + done + " adlist ",
address
);
} else {
showAlert(
"error",
"",
"Error while " + not_done + " adlist with ID " + id,
+response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while " + not_done + " adlist with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}
function deleteAdlist() {
var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
var address = tr.find("#address").text();
showAlert("info", "", "Deleting adlist...", address);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_adlist", id: id, token: token },
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-trash",
"Successfully deleted adlist ",
address
);
table
.row(tr)
.remove()
.draw(false);
} else
showAlert(
"error",
"",
"Error while deleting adlist with ID " + id,
response.message
);
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while deleting adlist with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}

View File

@@ -0,0 +1,381 @@
var table;
var groups = [];
const token = $("#token").html();
var info = null;
function showAlert(type, icon, title, message) {
let opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
switch (type) {
case "info":
opts = {
type: "info",
icon: "glyphicon glyphicon-time",
title: title,
message: message
};
info = $.notify(opts);
break;
case "success":
opts = {
type: "success",
icon: icon,
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "warning":
opts = {
type: "warning",
icon: "glyphicon glyphicon-warning-sign",
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "error":
opts = {
type: "danger",
icon: "glyphicon glyphicon-remove",
title: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
default:
return;
}
}
function reload_client_suggestions() {
$.post(
"scripts/pi-hole/php/groups.php",
{ action: "get_unconfigured_clients", token: token },
function(data) {
var sel = $("#select");
sel.empty();
for (var key in data) {
if (!data.hasOwnProperty(key)) {
continue;
}
var text = key;
if (data[key].length > 0) {
text += " (" + data[key] + ")";
}
sel.append(
$("<option />")
.val(key)
.text(text)
);
}
sel.append(
$("<option />")
.val("custom")
.text("Custom, specified on the right")
);
},
"json"
);
}
function get_groups() {
$.post(
"scripts/pi-hole/php/groups.php",
{ action: "get_groups", token: token },
function(data) {
groups = data.data;
initTable();
},
"json"
);
}
$(document).ready(function() {
$("#btnAdd").on("click", addClient);
reload_client_suggestions();
get_groups();
$("#select").on("change", function() {
$("#ip-custom").val("");
$("#ip-custom").prop(
"disabled",
$("#select option:selected").val() !== "custom"
);
});
});
function initTable() {
table = $("#clientsTable").DataTable({
ajax: {
url: "scripts/pi-hole/php/groups.php",
data: { action: "get_clients", token: token },
type: "POST"
},
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
{ data: "ip" },
{ data: "groups", searchable: false },
{ data: "name", width: "80px", orderable: false }
],
drawCallback: function(settings) {
$(".deleteClient").on("click", deleteClient);
},
rowCallback: function(row, data) {
const tooltip = "Database ID: " + data.id;
var ip_name =
'<code id="ip" title="' +
tooltip +
'">' +
data.ip +
'</code><input id="id" type="hidden" value="' +
data.id +
'">';
if (data.name !== null && data.name.length > 0)
ip_name +=
'<br><code id="name" title="' +
tooltip +
'">' +
data.name +
"</code>";
$("td:eq(0)", row).html(ip_name);
$("td:eq(1)", row).empty();
$("td:eq(1)", row).append(
'<select id="multiselect" multiple="multiple"></select>'
);
var sel = $("#multiselect", row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
if (!groups[i].enabled) {
extra = " (disabled)";
}
sel.append(
$("<option />")
.val(groups[i].id)
.text(groups[i].name + extra)
);
}
// Select assigned groups
sel.val(data.groups);
// Initialize multiselect
sel.multiselect({ includeSelectAllOption: true });
sel.on("change", editClient);
let button =
'<button class="btn btn-danger btn-xs deleteClient" type="button" data-id="' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
"</button>";
$("td:eq(2)", row).html(button);
},
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
],
stateSave: true,
stateSaveCallback: function(settings, data) {
// Store current state in client's local storage area
localStorage.setItem("groups-clients-table", JSON.stringify(data));
},
stateLoadCallback: function(settings) {
// Receive previous state from client's local storage area
var data = localStorage.getItem("groups-clients-table");
// Return if not available
if (data === null) {
return null;
}
data = JSON.parse(data);
// Always start on the first page to show most recent queries
data.start = 0;
// Always start with empty search field
data.search.search = "";
// Reset visibility of ID column
data.columns[0].visible = false;
// Apply loaded state to table
return data;
}
});
table.on("order.dt", function() {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
$("#resetButton").show();
} else {
$("#resetButton").hide();
}
});
$("#resetButton").on("click", function() {
table.order([[0, "asc"]]).draw();
$("#resetButton").hide();
});
}
function addClient() {
var ip = $("#select").val();
if (ip === "custom") {
ip = $("#ip-custom").val();
}
showAlert("info", "", "Adding client...", ip);
if (ip.length === 0) {
showAlert("warning", "", "Warning", "Please specify a client IP address");
return;
}
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "add_client", ip: ip, token: token },
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-plus",
"Successfully added client",
ip
);
reload_client_suggestions();
table.ajax.reload();
} else {
showAlert(
"error",
"",
"Error while adding new client",
response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while adding new client",
jqXHR.responseText
);
console.log(exception);
}
});
}
function editClient() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
var id = tr.find("#id").val();
var groups = tr.find("#multiselect").val();
var ip = tr.find("#ip").text();
var name = tr.find("#name").text();
var done = "edited";
var not_done = "editing";
if (elem === "multiselect") {
done = "edited groups of";
not_done = "editing groups of";
}
var ip_name = ip;
if (name.length > 0) {
ip_name += " (" + name + ")";
}
showAlert("info", "", "Editing client...", ip_name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "edit_client", id: id, groups: groups, token: token },
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-plus",
"Successfully " + done + " client",
ip_name
);
} else {
showAlert(
"error",
"Error while " + not_done + " client with ID " + id,
response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while " + not_done + " client with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}
function deleteClient() {
var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
var ip = tr.find("#ip").text();
var name = tr.find("#name").text();
var ip_name = ip;
if (name.length > 0) {
ip_name += " (" + name + ")";
}
showAlert("info", "", "Deleting client...", ip_name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_client", id: id, token: token },
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-trash",
"Successfully deleted client ",
ip_name
);
table
.row(tr)
.remove()
.draw(false);
reload_client_suggestions();
} else {
showAlert(
"error",
"",
"Error while deleting client with ID " + id,
response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while deleting client with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}

View File

@@ -0,0 +1,408 @@
var table;
var groups = [];
const token = $("#token").html();
var info = null;
function showAlert(type, icon, title, message) {
let opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
switch (type) {
case "info":
opts = {
type: "info",
icon: "glyphicon glyphicon-time",
title: title,
message: message
};
info = $.notify(opts);
break;
case "success":
opts = {
type: "success",
icon: icon,
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "warning":
opts = {
type: "warning",
icon: "glyphicon glyphicon-warning-sign",
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "error":
opts = {
type: "danger",
icon: "glyphicon glyphicon-remove",
title: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
default:
return;
}
}
function get_groups() {
$.post(
"scripts/pi-hole/php/groups.php",
{ action: "get_groups", token: token },
function(data) {
groups = data.data;
initTable();
},
"json"
);
}
function datetime(date) {
return moment.unix(Math.floor(date)).format("Y-MM-DD HH:mm:ss z");
}
$(document).ready(function() {
$("#btnAdd").on("click", addDomain);
get_groups();
$("#select").on("change", function() {
$("#ip-custom").val("");
$("#ip-custom").prop(
"disabled",
$("#select option:selected").val() !== "custom"
);
});
});
function initTable() {
table = $("#domainsTable").DataTable({
ajax: {
url: "scripts/pi-hole/php/groups.php",
data: { action: "get_domains", token: token },
type: "POST"
},
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
{ data: "domain" },
{ data: "type", searchable: false },
{ data: "enabled", searchable: false },
{ data: "comment" },
{ data: "groups", searchable: false },
{ data: null, width: "80px", orderable: false }
],
drawCallback: function(settings) {
$(".deleteDomain").on("click", deleteDomain);
},
rowCallback: function(row, data) {
const tooltip =
"Added: " +
datetime(data.date_added) +
"\nLast modified: " +
datetime(data.date_modified) +
"\nDatabase ID: " +
data.id;
$("td:eq(0)", row).html(
'<code id="domain" title="' + tooltip + '">' + data.domain + "</code>"
);
$("td:eq(1)", row).html(
'<select id="type" class="form-control">' +
'<option value="0"' +
(data.type === 0 ? " selected" : "") +
">Exact whitelist</option>" +
'<option value="1"' +
(data.type === 1 ? " selected" : "") +
">Exact blacklist</option>" +
'<option value="2"' +
(data.type === 2 ? " selected" : "") +
">Regex whitelist</option>" +
'<option value="3"' +
(data.type === 3 ? " selected" : "") +
">Regex blacklist</option>" +
"</select>"
);
$("#type", row).on("change", editDomain);
const disabled = data.enabled === 0;
$("td:eq(2)", row).html(
'<input type="checkbox" id="status"' +
(disabled ? "" : " checked") +
">"
);
$("#status", row).bootstrapToggle({
on: "Enabled",
off: "Disabled",
size: "small",
onstyle: "success",
width: "80px"
});
$("#status", row).on("change", editDomain);
$("td:eq(3)", row).html(
'<input id="comment" class="form-control"><input id="id" type="hidden" value="' +
data.id +
'">'
);
$("#comment", row).val(data.comment);
$("#comment", row).on("change", editDomain);
$("td:eq(4)", row).empty();
$("td:eq(4)", row).append(
'<select id="multiselect" multiple="multiple"></select>'
);
var sel = $("#multiselect", row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var extra = "";
if (!groups[i].enabled) {
extra = " (disabled)";
}
sel.append(
$("<option />")
.val(groups[i].id)
.text(groups[i].name + extra)
);
}
// Select assigned groups
sel.val(data.groups);
// Initialize multiselect
sel.multiselect({ includeSelectAllOption: true });
sel.on("change", editDomain);
let button =
'<button class="btn btn-danger btn-xs deleteDomain" type="button" data-id="' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
"</button>";
$("td:eq(5)", row).html(button);
},
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
],
stateSave: true,
stateSaveCallback: function(settings, data) {
// Store current state in client's local storage area
localStorage.setItem("groups-domains-table", JSON.stringify(data));
},
stateLoadCallback: function(settings) {
// Receive previous state from client's local storage area
var data = localStorage.getItem("groups-domains-table");
// Return if not available
if (data === null) {
return null;
}
data = JSON.parse(data);
// Always start on the first page to show most recent queries
data.start = 0;
// Always start with empty search field
data.search.search = "";
// Reset visibility of ID column
data.columns[0].visible = false;
// Apply loaded state to table
return data;
}
});
table.on("order.dt", function() {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
$("#resetButton").show();
} else {
$("#resetButton").hide();
}
});
$("#resetButton").on("click", function() {
table.order([[0, "asc"]]).draw();
$("#resetButton").hide();
});
}
function addDomain() {
var domain = $("#new_domain").val();
var type = $("#new_type").val();
var comment = $("#new_comment").val();
showAlert("info", "", "Adding domain...", domain);
if (domain.length === 0) {
showAlert("warning", "", "Warning", "Please specify a domain");
return;
}
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: {
action: "add_domain",
domain: domain,
type: type,
comment: comment,
token: token
},
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-plus",
"Successfully added domain",
domain
);
$("#new_domain").val("");
$("#new_comment").val("");
table.ajax.reload();
} else
showAlert(
"error",
"",
"Error while adding new domain",
response.message
);
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while adding new domain",
jqXHR.responseText
);
console.log(exception);
}
});
}
function editDomain() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
var domain = tr.find("#domain").text();
var id = tr.find("#id").val();
var type = tr.find("#type").val();
var status = tr.find("#status").is(":checked") ? 1 : 0;
var comment = tr.find("#comment").val();
var groups = tr.find("#multiselect").val();
var done = "edited";
var not_done = "editing";
if (elem === "status" && status === 1) {
done = "enabled";
not_done = "enabling";
} else if (elem === "status" && status === 0) {
done = "disabled";
not_done = "disabling";
} else if (elem === "name") {
done = "edited name of";
not_done = "editing name of";
} else if (elem === "comment") {
done = "edited comment of";
not_done = "editing comment of";
} else if (elem === "type") {
done = "edited type of";
not_done = "editing type of";
} else if (elem === "multiselect") {
done = "edited groups of";
not_done = "editing groups of";
}
showAlert("info", "", "Editing domain...", name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: {
action: "edit_domain",
id: id,
type: type,
comment: comment,
status: status,
groups: groups,
token: token
},
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-pencil",
"Successfully " + done + " domain",
domain
);
} else
showAlert(
"error",
"",
"Error while " + not_done + " domain with ID " + id,
response.message
);
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while " + not_done + " domain with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}
function deleteDomain() {
var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
var domain = tr.find("#domain").text();
showAlert("info", "", "Deleting domain...", domain);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_domain", id: id, token: token },
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-trash",
"Successfully deleted domain",
domain
);
table
.row(tr)
.remove()
.draw(false);
} else
showAlert(
"error",
"",
"Error while deleting domain with ID " + id,
response.message
);
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while deleting domain with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}

View File

@@ -0,0 +1,336 @@
var table;
const token = $("#token").html();
var info = null;
function showAlert(type, icon, title, message) {
let opts = {};
title = "&nbsp;<strong>" + title + "</strong><br>";
switch (type) {
case "info":
opts = {
type: "info",
icon: "glyphicon glyphicon-time",
title: title,
message: message
};
info = $.notify(opts);
break;
case "success":
opts = {
type: "success",
icon: icon,
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "warning":
opts = {
type: "warning",
icon: "glyphicon glyphicon-warning-sign",
title: title,
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
case "error":
opts = {
type: "danger",
icon: "glyphicon glyphicon-remove",
title: "&nbsp;<strong>Error, something went wrong!</strong><br>",
message: message
};
if (info) {
info.update(opts);
} else {
$.notify(opts);
}
break;
default:
return;
}
}
function datetime(date) {
return moment.unix(Math.floor(date)).format("Y-MM-DD HH:mm:ss z");
}
$(document).ready(function() {
$("#btnAdd").on("click", addGroup);
table = $("#groupsTable").DataTable({
ajax: {
url: "scripts/pi-hole/php/groups.php",
data: { action: "get_groups", token: token },
type: "POST"
},
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
{ data: "name" },
{ data: "enabled", searchable: false },
{ data: "description" },
{ data: null, width: "60px", orderable: false }
],
drawCallback: function(settings) {
$(".deleteGroup").on("click", deleteGroup);
},
rowCallback: function(row, data) {
const tooltip =
"Added: " +
datetime(data.date_added) +
"\nLast modified: " +
datetime(data.date_modified) +
"\nDatabase ID: " +
data.id;
$("td:eq(0)", row).html(
'<input id="name" title="' +
tooltip +
'" class="form-control"><input id="id" type="hidden" value="' +
data.id +
'">'
);
var name = $("#name", row);
name.val(data.name);
name.on("change", editGroup);
const disabled = data.enabled === 0;
$("td:eq(1)", row).html(
'<input type="checkbox" id="status"' +
(disabled ? "" : " checked") +
">"
);
var status = $("#status", row);
status.bootstrapToggle({
on: "Enabled",
off: "Disabled",
size: "small",
onstyle: "success",
width: "80px"
});
status.on("change", editGroup);
$("td:eq(2)", row).html('<input id="desc" class="form-control">');
const desc = data.description !== null ? data.description : "";
$("#desc", row).val(desc);
$("#desc", row).on("change", editGroup);
$("td:eq(3)", row).empty();
if (data.id !== 0) {
let button =
" &nbsp;" +
'<button class="btn btn-danger btn-xs deleteGroup" type="button" data-id="' +
data.id +
'">' +
'<span class="glyphicon glyphicon-trash"></span>' +
"</button>";
$("td:eq(3)", row).html(button);
}
},
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
],
stateSave: true,
stateSaveCallback: function(settings, data) {
// Store current state in client's local storage area
localStorage.setItem("groups-table", JSON.stringify(data));
},
stateLoadCallback: function(settings) {
// Receive previous state from client's local storage area
var data = localStorage.getItem("groups-table");
// Return if not available
if (data === null) {
return null;
}
data = JSON.parse(data);
// Always start on the first page to show most recent queries
data.start = 0;
// Always start with empty search field
data.search.search = "";
// Reset visibility of ID column
data.columns[0].visible = false;
// Apply loaded state to table
return data;
}
});
table.on("order.dt", function() {
var order = table.order();
if (order[0][0] !== 0 || order[0][1] !== "asc") {
$("#resetButton").show();
} else {
$("#resetButton").hide();
}
});
$("#resetButton").on("click", function() {
table.order([[0, "asc"]]).draw();
$("#resetButton").hide();
});
});
function addGroup() {
var name = $("#new_name").val();
var desc = $("#new_desc").val();
showAlert("info", "", "Adding group...", name);
if (name.length === 0) {
showAlert("warning", "", "Warning", "Please specify a group name");
return;
}
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "add_group", name: name, desc: desc, token: token },
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-plus",
"Successfully added group",
name
);
$("#new_name").val("");
$("#new_desc").val("");
table.ajax.reload();
} else {
showAlert(
"error",
"",
"Error while adding new group",
response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while adding new group",
jqXHR.responseText
);
console.log(exception);
}
});
}
function editGroup() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
var id = tr.find("#id").val();
var name = tr.find("#name").val();
var status = tr.find("#status").is(":checked") ? 1 : 0;
var desc = tr.find("#desc").val();
var done = "edited";
var not_done = "editing";
if (elem === "status" && status === 1) {
done = "enabled";
not_done = "enabling";
} else if (elem === "status" && status === 0) {
done = "disabled";
not_done = "disabling";
} else if (elem === "name") {
done = "edited name of";
not_done = "editing name of";
} else if (elem === "desc") {
done = "edited description of";
not_done = "editing description of";
}
showAlert("info", "", "Editing group...", name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: {
action: "edit_group",
id: id,
name: name,
desc: desc,
status: status,
token: token
},
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-pencil",
"Successfully " + done + " group",
name
);
} else {
showAlert(
"error",
"",
"Error while " + not_done + " group with ID " + id,
response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while " + not_done + " group with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}
function deleteGroup() {
var id = $(this).attr("data-id");
var tr = $(this).closest("tr");
var name = tr.find("#name").val();
showAlert("info", "", "Deleting group...", name);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_group", id: id, token: token },
success: function(response) {
if (response.success) {
showAlert(
"success",
"glyphicon glyphicon-trash",
"Successfully deleted group ",
name
);
table
.row(tr)
.remove()
.draw(false);
} else {
showAlert(
"error",
"",
"Error while deleting group with ID " + id,
response.message
);
}
},
error: function(jqXHR, exception) {
showAlert(
"error",
"",
"Error while deleting group with ID " + id,
jqXHR.responseText
);
console.log(exception);
}
});
}

View File

@@ -7,8 +7,8 @@
// Define global variables
/* global Chart:false, updateSessionTimer:false */
var timeLineChart, forwardDestinationChart;
var queryTypePieChart, forwardDestinationPieChart, clientsChart;
var timeLineChart, clientsChart;
var queryTypePieChart, forwardDestinationPieChart;
function padNumber(num) {
return ("00" + num).substr(-2,2);
@@ -166,8 +166,10 @@ function updateQueriesOverTime() {
}
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]);
var blocked = data.ads_over_time[1][hour];
var permitted = data.domains_over_time[1][hour] - blocked;
timeLineChart.data.datasets[0].data.push(permitted);
timeLineChart.data.datasets[1].data.push(blocked);
}
}
$("#queries-over-time .overlay").hide();
@@ -718,33 +720,31 @@ $(document).ready(function() {
var ctx = document.getElementById("queryOverTimeChart").getContext("2d");
timeLineChart = new Chart(ctx, {
type: "line",
type: "bar",
data: {
labels: [],
labels: [ ],
datasets: [
{
label: "Total DNS Queries",
label: "Permitted DNS Queries",
fill: true,
backgroundColor: "rgba(220,220,220,0.5)",
backgroundColor: "rgba(0, 166, 90,.8)",
borderColor: "rgba(0, 166, 90,.8)",
pointBorderColor: "rgba(0, 166, 90,.8)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
pointHitRadius: 5
},
{
label: "Blocked DNS Queries",
fill: true,
backgroundColor: "rgba(0,192,239,0.5)",
backgroundColor: "rgba(0,192,239,1)",
borderColor: "rgba(0,192,239,1)",
pointBorderColor: "rgba(0,192,239,1)",
pointRadius: 1,
pointHoverRadius: 5,
data: [],
pointHitRadius: 5,
cubicInterpolationMode: "monotone"
pointHitRadius: 5
}
]
},
@@ -785,6 +785,7 @@ $(document).ready(function() {
scales: {
xAxes: [{
type: "time",
stacked: true,
time: {
unit: "hour",
displayFormats: {
@@ -794,6 +795,7 @@ $(document).ready(function() {
}
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
@@ -807,73 +809,13 @@ $(document).ready(function() {
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) {
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) + "%";
}
}
},
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) {
return Math.round(value*100) + " %";
}
},
stacked: true
}]
},
maintainAspectRatio: true
}
});
}
// Create / load "Top Clients over Time" only if authorized
var clientsChartEl = document.getElementById("clientsChart");
if(clientsChartEl)
{
ctx = clientsChartEl.getContext("2d");
clientsChart = new Chart(ctx, {
type: "line",
type: "bar",
data: {
labels: [],
datasets: [{ data: [] }]
@@ -907,6 +849,7 @@ $(document).ready(function() {
scales: {
xAxes: [{
type: "time",
stacked: true,
time: {
unit: "hour",
displayFormats: {

View File

@@ -106,7 +106,7 @@ function autofilter(){
$(document).ready(function() {
// Do we want to filter queries?
var GETDict = {};
location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];});
window.location.search.substr(1).split("&").forEach(function(item) {GETDict[item.split("=")[0]] = item.split("=")[1];});
var APIstring = "api.php?getAllQueries";

View File

@@ -239,7 +239,7 @@ $(".nav-tabs a").on("shown.bs.tab", function (e) {
window.history.pushState("", "", "?tab=" + tab);
if(tab === "piholedhcp")
{
location.reload();
window.location.reload();
}
window.scrollTo(0, 0);
});