Ported groups-clients.lp

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER
2023-05-05 20:02:02 +02:00
parent 8a794ca69b
commit f3d7eb296a
4 changed files with 152 additions and 156 deletions
+7 -8
View File
@@ -1,12 +1,11 @@
<?php
/*
* Pi-hole: A black hole for Internet advertisements
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
<? --[[
* Pi-hole: A black hole for Internet advertisements
* (c) 2019 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.
*/
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license.
--]]
mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
?>
+120 -125
View File
@@ -5,17 +5,18 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
/* global utils:false */
/* global utils:false, updateFtlInfo:false */
var table;
var groups = [];
var token = $("#token").text();
function reloadClientSuggestions() {
$.post(
"scripts/pi-hole/php/groups.php",
{ action: "get_unconfigured_clients", token: token },
function (data) {
$.ajax({
url: "/api/clients/_suggestions",
type: "GET",
dataType: "json",
success: function (data) {
var sel = $("#select");
sel.empty();
@@ -27,41 +28,62 @@ function reloadClientSuggestions() {
sel.append($("<option />"));
// Add data obtained from API
for (var key in data) {
if (!Object.prototype.hasOwnProperty.call(data, key)) {
continue;
}
var text = key;
var keyPlain = key;
for (var i = 0; i < data.clients.length; i++) {
const client = data.clients[i];
let mockDevice = false;
var text = client.hwaddr.toUpperCase();
var key = text;
if (key.startsWith("IP-")) {
// Mock MAC address for address-only devices
keyPlain = key.substring(3);
text = keyPlain;
mockDevice = true;
key = key.substring(3);
text = key;
}
// Append host name if available
if (data[key].length > 0) {
text += " (" + data[key] + ")";
// Append additional infos if available
var extraInfo = "";
if (client.names !== null && client.names.length > 0) {
// Count number of "," in client.names to determine number of hostnames
var numHostnames = client.names.split(",").length;
const pluralHostnames = numHostnames > 1 ? "s" : "";
extraInfo =
numHostnames + " hostname" + pluralHostnames + ": " + utils.escapeHtml(client.names);
}
sel.append($("<option />").val(keyPlain).text(text));
if (client.macVendor !== null && client.macVendor.length > 0) {
if (extraInfo.length > 0) extraInfo += "; ";
extraInfo += "vendor: " + utils.escapeHtml(client.macVendor);
}
// Do not add addresses for mock devices as their address is already
// the hwaddr
if (client.addresses !== null && client.addresses.length > 0 && !mockDevice) {
if (extraInfo.length > 0) extraInfo += "; ";
// Count number of "," in client.addresses to determine number of addresses
var numAddresses = client.addresses.split(",").length;
const pluralAddresses = numAddresses > 1 ? "es" : "";
extraInfo +=
numAddresses + " address" + pluralAddresses + ": " + utils.escapeHtml(client.addresses);
}
if (extraInfo.length > 0) text += " (" + extraInfo + ")";
sel.append($("<option />").val(key).text(text));
}
},
"json"
);
});
}
function getGroups() {
$.post(
"scripts/pi-hole/php/groups.php",
{ action: "get_groups", token: token },
function (data) {
groups = data.data;
$.ajax({
url: "/api/groups",
type: "GET",
dataType: "json",
success: function (data) {
groups = data.groups;
initTable();
},
"json"
);
});
}
$(function () {
@@ -85,18 +107,18 @@ $(function () {
function initTable() {
table = $("#clientsTable").DataTable({
ajax: {
url: "scripts/pi-hole/php/groups.php",
data: { action: "get_clients", token: token },
type: "POST",
url: "/api/clients",
dataSrc: "clients",
type: "GET",
},
order: [[0, "asc"]],
columns: [
{ data: "id", visible: false },
{ data: null, visible: true, orderable: false, width: "15px" },
{ data: "ip", type: "ip-address" },
{ data: "client", type: "ip-address" },
{ data: "comment" },
{ data: "groups", searchable: false },
{ data: "name", width: "22px", orderable: false },
{ data: null, width: "22px", orderable: false },
],
columnDefs: [
{
@@ -121,7 +143,8 @@ function initTable() {
$("body > .bootstrap-select.dropdown").remove();
},
rowCallback: function (row, data) {
$(row).attr("data-id", data.id);
var dataId = data.client.replaceAll(":", "__");
$(row).attr("data-id", dataId);
var tooltip =
"Added: " +
utils.datetime(data.date_added, false) +
@@ -131,16 +154,16 @@ function initTable() {
data.id;
var ipName =
'<code id="ip_' +
data.id +
dataId +
'" title="' +
tooltip +
'" class="breakall">' +
data.ip +
data.client +
"</code>";
if (data.name !== null && data.name.length > 0)
ipName +=
'<br><code id="name_' +
data.id +
dataId +
'" title="' +
tooltip +
'" class="breakall">' +
@@ -148,16 +171,16 @@ function initTable() {
"</code>";
$("td:eq(1)", row).html(ipName);
$("td:eq(2)", row).html('<input id="comment_' + data.id + '" class="form-control">');
var commentEl = $("#comment_" + data.id, row);
$("td:eq(2)", row).html('<input id="comment_' + dataId + '" class="form-control">');
var commentEl = $("#comment_" + dataId, row);
commentEl.val(utils.unescapeHtml(data.comment));
commentEl.on("change", editClient);
$("td:eq(3)", row).empty();
$("td:eq(3)", row).append(
'<select class="selectpicker" id="multiselect_' + data.id + '" multiple></select>'
'<select class="selectpicker" id="multiselect_' + dataId + '" multiple></select>'
);
var selectEl = $("#multiselect_" + data.id, row);
var selectEl = $("#multiselect_" + dataId, row);
// Add all known groups
for (var i = 0; i < groups.length; i++) {
var dataSub = "";
@@ -212,17 +235,17 @@ function initTable() {
.find(".bs-actionsbox")
.prepend(
'<button type="button" id=btn_apply_' +
data.id +
dataId +
' class="btn btn-block btn-sm" disabled>Apply</button>'
);
var applyBtn = "#btn_apply_" + data.id;
var applyBtn = "#btn_apply_" + dataId;
var button =
'<button type="button" class="btn btn-danger btn-xs" id="deleteClient_' +
data.id +
'" data-del-id="' +
data.id +
dataId +
'" data-id="' +
dataId +
'">' +
'<span class="far fa-trash-alt"></span>' +
"</button>";
@@ -265,7 +288,7 @@ function initTable() {
var ids = [];
$("tr.selected").each(function () {
// ... add the row identified by "data-id".
ids.push(parseInt($(this).attr("data-id"), 10));
ids.push($(this).attr("data-id"));
});
// Delete all selected rows at once
delItems(ids);
@@ -334,8 +357,8 @@ function initTable() {
$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteClient() {
// Passes the button data-del-id attribute as ID
var ids = [parseInt($(this).attr("data-del-id"), 10)];
// Passes the button data-id attribute as ID
var ids = [$(this).attr("data-id")];
delItems(ids);
}
@@ -343,59 +366,38 @@ function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
var items = "";
var name = "";
var clientRaw = ids[0];
var client = clientRaw.replaceAll("__", ":");
for (var id of ids) {
// Exploit prevention: Return early for non-numeric IDs
if (typeof id !== "number") return;
// Retrieve details
name = utils.escapeHtml($("#name_" + id).text());
if (name.length > 0) {
name = " (<i>" + utils.escapeHtml($("#name_" + id).text()) + "</i>)";
}
// Add client
items += "<li>" + utils.escapeHtml($("#ip_" + id).text()) + name + "</li>";
}
// Remove first element from array
ids.shift();
utils.disableAll();
var idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting client(s)...", "<ul>" + items + "</ul>");
utils.showAlert("info", "", "Deleting client...", client);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
dataType: "json",
data: { action: "delete_client", id: JSON.stringify(ids), token: token },
url: "/api/clients/" + client,
method: "delete",
})
.done(function (response) {
.done(function () {
utils.enableAll();
if (response.success) {
utils.showAlert(
"success",
"far fa-trash-alt",
"Successfully deleted client(s): ",
"<ul>" + items + "</ul>"
);
for (var id in ids) {
if (Object.hasOwnProperty.call(ids, id)) {
table.row(id).remove().draw(false).ajax.reload(null, false);
}
}
} else {
utils.showAlert(
"error",
"",
"Error while deleting client(s): " + idstring,
response.message
);
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted client: ", client);
table.row(clientRaw).remove().draw(false);
if (ids.length > 0) {
// Recursively delete all remaining items
delItems(ids);
return;
}
table.ajax.reload(null, false);
// Clear selection after deletion
table.rows().deselect();
utils.changeBulkDeleteStates(table);
// Update number of groups in the sidebar
updateFtlInfo();
})
.fail(function (jqXHR, exception) {
utils.enableAll();
@@ -442,20 +444,19 @@ function addClient() {
}
$.ajax({
url: "scripts/pi-hole/php/groups.php",
url: "/api/clients",
method: "post",
dataType: "json",
data: { action: "add_client", ip: ip, comment: comment, token: token },
success: function (response) {
data: JSON.stringify({ client: ip, comment: comment }),
success: function () {
utils.enableAll();
if (response.success) {
utils.showAlert("success", "fas fa-plus", "Successfully added client", ip);
reloadClientSuggestions();
table.ajax.reload(null, false);
table.rows().deselect();
} else {
utils.showAlert("error", "", "Error while adding new client", response.message);
}
utils.showAlert("success", "fas fa-plus", "Successfully added client", ip);
reloadClientSuggestions();
table.ajax.reload(null, false);
table.rows().deselect();
// Update number of groups in the sidebar
updateFtlInfo();
},
error: function (jqXHR, exception) {
utils.enableAll();
@@ -468,25 +469,27 @@ function addClient() {
function editClient() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
var id = tr.attr("data-id");
var groups = tr.find("#multiselect_" + id).val();
var ip = utils.escapeHtml(tr.find("#ip_" + id).text());
var name = utils.escapeHtml(tr.find("#name_" + id).text());
var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
var client = tr.attr("data-id");
var groups = tr.find("#multiselect_" + client).val();
// Convert list of string integers to list of integers
groups = groups.map(Number);
var ip = utils.escapeHtml(tr.find("#ip_" + client).text());
var name = utils.escapeHtml(tr.find("#name_" + client).text());
var comment = utils.escapeHtml(tr.find("#comment_" + client).val());
var done = "edited";
var notDone = "editing";
switch (elem) {
case "multiselect_" + id:
case "multiselect_" + client:
done = "edited groups of";
notDone = "editing groups of";
break;
case "comment_" + id:
case "comment_" + client:
done = "edited comment of";
notDone = "editing comment of";
break;
default:
alert("bad element or invalid data-id!");
alert("bad element (" + elem + ") or invalid data-id!");
return;
}
@@ -495,37 +498,29 @@ function editClient() {
}
utils.disableAll();
const clientRaw = client.replaceAll("__", ":");
utils.showAlert("info", "", "Editing client...", ip);
$.ajax({
url: "scripts/pi-hole/php/groups.php",
method: "post",
url: "/api/clients/" + clientRaw,
method: "put",
dataType: "json",
data: {
action: "edit_client",
id: id,
data: JSON.stringify({
client: client,
groups: groups,
token: token,
comment: comment,
},
success: function (response) {
}),
success: function () {
utils.enableAll();
if (response.success) {
utils.showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " client", ip);
table.ajax.reload(null, false);
} else {
utils.showAlert(
"error",
"Error while " + notDone + " client with ID " + id,
response.message
);
}
utils.showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " client", ip);
table.ajax.reload(null, false);
},
error: function (jqXHR, exception) {
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while " + notDone + " client with ID " + id,
"Error while " + notDone + " client " + clientRaw,
jqXHR.responseText
);
console.log(exception); // eslint-disable-line no-console
+24 -22
View File
@@ -5,9 +5,10 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
/* global utils:false */
/* global utils:false, updateFtlInfo:false */
var table, id_names = {};
var table,
idNames = {};
function handleAjaxError(xhr, textStatus) {
if (textStatus === "timeout") {
@@ -60,7 +61,7 @@ $(function () {
$('button[id^="deleteGroup_"]').on("click", deleteGroup);
},
rowCallback: function (row, data) {
id_names[data.id] = data.name;
idNames[data.id] = data.name;
$(row).attr("data-id", data.id);
var tooltip =
"Added: " +
@@ -76,9 +77,12 @@ $(function () {
nameEl.val(utils.unescapeHtml(data.name));
nameEl.on("change", editGroup);
var disabled = data.enabled === 0;
$("td:eq(2)", row).html(
'<input type="checkbox" id="enabled_' + data.id + '"' + (disabled ? "" : " checked") + ">"
'<input type="checkbox" id="enabled_' +
data.id +
'"' +
(data.enabled ? " checked" : "") +
">"
);
var enabledEl = $("#enabled_" + data.id, row);
enabledEl.bootstrapToggle({
@@ -224,12 +228,13 @@ function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
for (var id of ids) {
for (const id of ids) {
// Exploit prevention: Return early for non-numeric IDs
if (typeof id !== "number") return;
}
var name = id_names[ids[0]];
var id = ids[0];
var name = idNames[id];
// Remove first element from array
ids.shift();
@@ -242,20 +247,16 @@ function delItems(ids) {
url: "/api/groups/" + name,
method: "delete",
})
.done(function (response) {
.done(function () {
utils.enableAll();
utils.showAlert(
"success",
"far fa-trash-alt",
"Successfully deleted group: ",
name
);
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted group: ", name);
table.row(id).remove().draw(false);
if(ids.length > 0) {
if (ids.length > 0) {
// Recursively delete all remaining items
delItems(ids);
return;
}
table.ajax.reload(null, false);
// Clear selection after deletion
@@ -300,7 +301,7 @@ function addGroup() {
comment: comment,
enabled: true,
}),
success: function (response) {
success: function () {
utils.enableAll();
utils.showAlert("success", "fas fa-plus", "Successfully added group", name);
$("#new_name").val("");
@@ -323,7 +324,7 @@ function editGroup() {
var elem = $(this).attr("id");
var tr = $(this).closest("tr");
var id = tr.attr("data-id");
var old_name = id_names[data.id];
var oldName = idNames[id];
var name = utils.escapeHtml(tr.find("#name_" + id).val());
var enabled = tr.find("#enabled_" + id).is(":checked");
var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
@@ -339,6 +340,7 @@ function editGroup() {
done = "enabled";
notDone = "enabling";
}
break;
case "name_" + id:
done = "edited name of";
@@ -354,9 +356,9 @@ function editGroup() {
}
utils.disableAll();
utils.showAlert("info", "", "Editing group...", old_name);
utils.showAlert("info", "", "Editing group...", oldName);
$.ajax({
url: "/api/groups/" + old_name,
url: "/api/groups/" + oldName,
method: "put",
dataType: "json",
data: JSON.stringify({
@@ -364,16 +366,16 @@ function editGroup() {
comment: comment,
enabled: enabled,
}),
success: function (response) {
success: function () {
utils.enableAll();
utils.showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " group", old_name);
utils.showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " group", oldName);
},
error: function (jqXHR, exception) {
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while " + notDone + " group with name " + old_name,
"Error while " + notDone + " group with name " + oldName,
jqXHR.responseText
);
console.log(exception); // eslint-disable-line no-console
+1 -1
View File
@@ -51,7 +51,7 @@
</li>
<li class="menu-group<? if scriptname == 'groups-clients.lp' then mg.write(" active") end ?>">
<a href="groups-clients.lp">
<i class="fa fa-fw menu-icon fa-laptop"></i> <span class="text-red">Clients
<i class="fa fa-fw menu-icon fa-laptop"></i> <span class="text-green">Clients
<span class="pull-right-container">
<span class="label label-primary pull-right" id="num_clients" title="Number of defined clients"></span>
</span>