mirror of
https://github.com/pi-hole/web.git
synced 2025-12-23 12:18:26 +00:00
306 lines
9.1 KiB
JavaScript
306 lines
9.1 KiB
JavaScript
/* 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 utils:false */
|
|
|
|
var tableApi;
|
|
var token = $("#token").text();
|
|
|
|
var API_STRING = "api_db.php?network";
|
|
|
|
// How many IPs do we show at most per device?
|
|
var MAXIPDISPLAY = 3;
|
|
|
|
var DAY_IN_SECONDS = 24 * 60 * 60;
|
|
|
|
function handleAjaxError(xhr, textStatus) {
|
|
if (textStatus === "timeout") {
|
|
alert("The server took too long to send the data.");
|
|
} else if (xhr.responseText.indexOf("Connection refused") !== -1) {
|
|
alert("An error occurred while loading the data: Connection refused. Is FTL running?");
|
|
} else {
|
|
alert("An unknown error occurred while loading the data.\n" + xhr.responseText);
|
|
}
|
|
|
|
$("#network-entries_processing").hide();
|
|
tableApi.clear();
|
|
tableApi.draw();
|
|
}
|
|
|
|
function getTimestamp() {
|
|
return Math.floor(Date.now() / 1000);
|
|
}
|
|
|
|
function valueToHex(c) {
|
|
var hex = Math.round(c).toString(16);
|
|
return hex.length === 1 ? "0" + hex : hex;
|
|
}
|
|
|
|
function rgbToHex(values) {
|
|
return "#" + valueToHex(values[0]) + valueToHex(values[1]) + valueToHex(values[2]);
|
|
}
|
|
|
|
function mixColors(ratio, rgb1, rgb2) {
|
|
return [
|
|
(1 - ratio) * rgb1[0] + ratio * rgb2[0],
|
|
(1 - ratio) * rgb1[1] + ratio * rgb2[1],
|
|
(1 - ratio) * rgb1[2] + ratio * rgb2[2],
|
|
];
|
|
}
|
|
|
|
function parseColor(input) {
|
|
var match = input.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
|
|
|
|
if (match) {
|
|
return [match[1], match[2], match[3]];
|
|
}
|
|
}
|
|
|
|
function deleteNetworkEntry() {
|
|
var tr = $(this).closest("tr");
|
|
var id = tr.attr("data-id");
|
|
|
|
utils.disableAll();
|
|
utils.showAlert("info", "", "Deleting network table entry with ID " + parseInt(id, 10), "...");
|
|
$.ajax({
|
|
url: "scripts/pi-hole/php/network.php",
|
|
method: "post",
|
|
dataType: "json",
|
|
data: { action: "delete_network_entry", id: id, token: token },
|
|
success: function (response) {
|
|
utils.enableAll();
|
|
if (response.success) {
|
|
utils.showAlert(
|
|
"success",
|
|
"far fa-trash-alt",
|
|
"Successfully deleted network table entry # ",
|
|
id
|
|
);
|
|
tableApi.row(tr).remove().draw(false).ajax.reload(null, false);
|
|
} else {
|
|
utils.showAlert(
|
|
"error",
|
|
"",
|
|
"Error while network table entry with ID " + id,
|
|
response.message
|
|
);
|
|
}
|
|
},
|
|
error: function (jqXHR, exception) {
|
|
utils.enableAll();
|
|
utils.showAlert(
|
|
"error",
|
|
"",
|
|
"Error while deleting network table entry with ID " + id,
|
|
jqXHR.responseText
|
|
);
|
|
console.log(exception); // eslint-disable-line no-console
|
|
},
|
|
});
|
|
}
|
|
|
|
$(function () {
|
|
tableApi = $("#network-entries").DataTable({
|
|
rowCallback: function (row, data) {
|
|
var color;
|
|
var index;
|
|
var maxiter;
|
|
var iconClasses;
|
|
var lastQuery = parseInt(data.lastQuery, 10);
|
|
var diff = getTimestamp() - lastQuery;
|
|
var networkRecent = $(".network-recent").css("background-color");
|
|
var networkOld = $(".network-old").css("background-color");
|
|
var networkOlder = $(".network-older").css("background-color");
|
|
var networkNever = $(".network-never").css("background-color");
|
|
|
|
if (lastQuery > 0) {
|
|
if (diff <= DAY_IN_SECONDS) {
|
|
// Last query came in within the last 24 hours
|
|
// Color: light-green to light-yellow
|
|
var ratio = Number(diff) / DAY_IN_SECONDS;
|
|
color = rgbToHex(mixColors(ratio, parseColor(networkRecent), parseColor(networkOld)));
|
|
iconClasses = "fas fa-check";
|
|
} else {
|
|
// Last query was longer than 24 hours ago
|
|
// Color: light-orange
|
|
color = networkOlder;
|
|
iconClasses = "fas fa-question";
|
|
}
|
|
} else {
|
|
// This client has never sent a query to Pi-hole, color light-red
|
|
color = networkNever;
|
|
iconClasses = "fas fa-times";
|
|
}
|
|
|
|
// Set determined background color
|
|
$(row).css("background-color", color);
|
|
$("td:eq(7)", row).html('<i class="' + iconClasses + '"></i>');
|
|
|
|
// Insert "Never" into Last Query field when we have
|
|
// never seen a query from this device
|
|
if (data.lastQuery === 0) {
|
|
$("td:eq(5)", row).html("Never");
|
|
}
|
|
|
|
// Set hostname to "unknown" if not available
|
|
if (!data.name || data.name.length === 0) {
|
|
$("td:eq(3)", row).html("<em>unknown</em>");
|
|
} else {
|
|
var names = [];
|
|
var name = "";
|
|
maxiter = Math.min(data.name.length, MAXIPDISPLAY);
|
|
index = 0;
|
|
for (index = 0; index < maxiter; index++) {
|
|
name = data.name[index];
|
|
if (name.length === 0) continue;
|
|
names.push('<a href="queries.php?client=' + name + '">' + name + "</a>");
|
|
}
|
|
|
|
if (data.name.length > MAXIPDISPLAY) {
|
|
// We hit the maximum above, add "..." to symbolize we would
|
|
// have more to show here
|
|
names.push("...");
|
|
}
|
|
|
|
maxiter = Math.min(data.ip.length, data.name.length);
|
|
var allnames = [];
|
|
for (index = 0; index < maxiter; index++) {
|
|
name = data.name[index];
|
|
if (name.length > 0) {
|
|
allnames.push(name + " (" + data.ip[index] + ")");
|
|
} else {
|
|
allnames.push("No host name for " + data.ip[index] + " known");
|
|
}
|
|
}
|
|
|
|
$("td:eq(3)", row).html(names.join("<br>"));
|
|
$("td:eq(3)", row).hover(function () {
|
|
this.title = allnames.join("\n");
|
|
});
|
|
}
|
|
|
|
// Set number of queries to localized string (add thousand separators)
|
|
$("td:eq(6)", row).html(data.numQueries.toLocaleString());
|
|
|
|
var ips = [];
|
|
maxiter = Math.min(data.ip.length, MAXIPDISPLAY);
|
|
index = 0;
|
|
for (index = 0; index < maxiter; index++) {
|
|
var ip = data.ip[index];
|
|
ips.push('<a href="queries.php?client=' + ip + '">' + ip + "</a>");
|
|
}
|
|
|
|
if (data.ip.length > MAXIPDISPLAY) {
|
|
// We hit the maximum above, add "..." to symbolize we would
|
|
// have more to show here
|
|
ips.push("...");
|
|
}
|
|
|
|
$("td:eq(0)", row).html(ips.join("<br>"));
|
|
$("td:eq(0)", row).hover(function () {
|
|
this.title = data.ip.join("\n");
|
|
});
|
|
|
|
// MAC + Vendor field if available
|
|
if (data.macVendor && data.macVendor.length > 0) {
|
|
$("td:eq(1)", row).html(data.hwaddr + "<br/>" + data.macVendor);
|
|
}
|
|
|
|
// Hide mock MAC addresses
|
|
if (data.hwaddr.startsWith("ip-")) {
|
|
$("td:eq(1)", row).text("N/A");
|
|
}
|
|
|
|
// Add delete button
|
|
$(row).attr("data-id", data.id);
|
|
var button =
|
|
'<button type="button" class="btn btn-danger btn-xs" id="deleteNetworkEntry_' +
|
|
data.id +
|
|
'">' +
|
|
'<span class="far fa-trash-alt"></span>' +
|
|
"</button>";
|
|
$("td:eq(8)", row).html(button);
|
|
},
|
|
dom:
|
|
"<'row'<'col-sm-12'f>>" +
|
|
"<'row'<'col-sm-4'l><'col-sm-8'p>>" +
|
|
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
|
|
"<'row'<'col-sm-5'i><'col-sm-7'p>>",
|
|
ajax: { url: API_STRING, error: handleAjaxError, dataSrc: "network" },
|
|
autoWidth: false,
|
|
processing: true,
|
|
order: [[6, "desc"]],
|
|
columns: [
|
|
{ data: "id", visible: false },
|
|
{ data: "ip", type: "ip-address", width: "10%" },
|
|
{ data: "hwaddr", width: "10%" },
|
|
{ data: "interface", width: "4%" },
|
|
{ data: "name", width: "15%" },
|
|
{
|
|
data: "firstSeen",
|
|
width: "8%",
|
|
render: function (data, type) {
|
|
if (type === "display") {
|
|
return utils.datetime(data);
|
|
}
|
|
|
|
return data;
|
|
},
|
|
},
|
|
{
|
|
data: "lastQuery",
|
|
width: "8%",
|
|
render: function (data, type) {
|
|
if (type === "display") {
|
|
return utils.datetime(data);
|
|
}
|
|
|
|
return data;
|
|
},
|
|
},
|
|
{ data: "numQueries", width: "9%", render: $.fn.dataTable.render.text() },
|
|
{ data: "", width: "6%", orderable: false },
|
|
{ data: "", width: "6%", orderable: false },
|
|
],
|
|
|
|
drawCallback: function () {
|
|
$('button[id^="deleteNetworkEntry_"]').on("click", deleteNetworkEntry);
|
|
// Remove visible dropdown to prevent orphaning
|
|
$("body > .bootstrap-select.dropdown").remove();
|
|
},
|
|
lengthMenu: [
|
|
[10, 25, 50, 100, -1],
|
|
[10, 25, 50, 100, "All"],
|
|
],
|
|
stateSave: true,
|
|
stateDuration: 0,
|
|
stateSaveCallback: function (settings, data) {
|
|
utils.stateSaveCallback("network_table", data);
|
|
},
|
|
stateLoadCallback: function () {
|
|
return utils.stateLoadCallback("network_table");
|
|
},
|
|
columnDefs: [
|
|
{
|
|
targets: [-1, -2],
|
|
data: null,
|
|
defaultContent: "",
|
|
},
|
|
{
|
|
targets: "_all",
|
|
render: $.fn.dataTable.render.text(),
|
|
},
|
|
],
|
|
});
|
|
// Disable autocorrect in the search box
|
|
var input = document.querySelector("input[type=search]");
|
|
input.setAttribute("autocomplete", "off");
|
|
input.setAttribute("autocorrect", "off");
|
|
input.setAttribute("autocapitalize", "off");
|
|
input.setAttribute("spellcheck", false);
|
|
});
|