From 4d3d64b90426fc97eb6dc0425b33ed8956f71c14 Mon Sep 17 00:00:00 2001
From: DL6ER
' + data.address + ""
- );
- } else {
- $("td:eq(2)", row).html(
- '' +
- data.address +
- ""
- );
- }
-
- $("td:eq(3)", row).html(
- '"
- );
- var statusEl = $("#status_" + data.id, row);
- statusEl.bootstrapToggle({
- on: "Enabled",
- off: "Disabled",
- size: "small",
- onstyle: "success",
- width: "80px",
- });
- statusEl.on("change", editAdlist);
-
- $("td:eq(4)", row).html('');
- var commentEl = $("#comment_" + data.id, row);
- commentEl.val(utils.unescapeHtml(data.comment));
- commentEl.on("change", editAdlist);
-
- $("td:eq(5)", row).empty();
- $("td:eq(5)", row).append(
- ''
- );
- var selectEl = $("#multiselect_" + data.id, row);
- // Add all known groups
- for (var i = 0; i < groups.length; i++) {
- var dataSub = "";
- if (!groups[i].enabled) {
- dataSub = 'data-subtext="(disabled)"';
+ var statusCode = 0,
+ statusIcon;
+ // If there is no status or the list is disabled, we keep
+ // status 0 (== unknown)
+ if (data.status !== null && data.enabled) {
+ statusCode = parseInt(data.status, 10);
}
- selectEl.append(
- $("")
- .val(groups[i].id)
- .text(groups[i].name)
- );
- }
+ switch (statusCode) {
+ case 1:
+ statusIcon = "fa-check";
+ break;
+ case 2:
+ statusIcon = "fa-history";
+ break;
+ case 3:
+ statusIcon = "fa-exclamation-circle";
+ break;
+ case 4:
+ statusIcon = "fa-times";
+ break;
+ default:
+ statusIcon = "fa-question-circle";
+ break;
+ }
- // Select assigned groups
- selectEl.val(data.groups);
- // Initialize bootstrap-select
- selectEl
- // fix dropdown if it would stick out right of the viewport
- .on("show.bs.select", function () {
- var winWidth = $(window).width();
- var dropdownEl = $("body > .bootstrap-select.dropdown");
- if (dropdownEl.length > 0) {
- dropdownEl.removeClass("align-right");
- var width = dropdownEl.width();
- var left = dropdownEl.offset().left;
- if (left + width > winWidth) {
- dropdownEl.addClass("align-right");
+ $("td:eq(1)", row).addClass("list-status-" + statusCode);
+ $("td:eq(1)", row).html(
+ ""
+ );
+
+ if (data.address.startsWith("file://")) {
+ // Local files cannot be downloaded from a distant client so don't show
+ // a link to such a list here
+ $("td:eq(2)", row).html(
+ '' + data.address + ""
+ );
+ } else {
+ $("td:eq(2)", row).html(
+ '' +
+ data.address +
+ ""
+ );
+ }
+
+ $("td:eq(3)", row).html(
+ '"
+ );
+ var statusEl = $("#enabled_" + dataId, row);
+ statusEl.bootstrapToggle({
+ on: "Enabled",
+ off: "Disabled",
+ size: "small",
+ onstyle: "success",
+ width: "80px",
+ });
+ statusEl.on("change", editAdlist);
+
+ $("td:eq(4)", row).html('');
+ var commentEl = $("#comment_" + dataId, row);
+ commentEl.val(utils.unescapeHtml(data.comment));
+ commentEl.on("change", editAdlist);
+
+ $("td:eq(5)", row).empty();
+ $("td:eq(5)", row).append(
+ ''
+ );
+ var selectEl = $("#multiselect_" + dataId, row);
+ // Add all known groups
+ for (var i = 0; i < groups.length; i++) {
+ var dataSub = "";
+ if (!groups[i].enabled) {
+ dataSub = 'data-subtext="(disabled)"';
+ }
+
+ selectEl.append(
+ $("")
+ .val(groups[i].id)
+ .text(groups[i].name)
+ );
+ }
+
+ // Select assigned groups
+ selectEl.val(data.groups);
+ // Initialize bootstrap-select
+ selectEl
+ // fix dropdown if it would stick out right of the viewport
+ .on("show.bs.select", function () {
+ var winWidth = $(window).width();
+ var dropdownEl = $("body > .bootstrap-select.dropdown");
+ if (dropdownEl.length > 0) {
+ dropdownEl.removeClass("align-right");
+ var width = dropdownEl.width();
+ var left = dropdownEl.offset().left;
+ if (left + width > winWidth) {
+ dropdownEl.addClass("align-right");
+ }
}
- }
- })
- .on("changed.bs.select", function () {
- // enable Apply button
- if ($(applyBtn).prop("disabled")) {
- $(applyBtn)
- .addClass("btn-success")
- .prop("disabled", false)
- .on("click", function () {
- editAdlist.call(selectEl);
- });
- }
- })
- .on("hide.bs.select", function () {
- // Restore values if drop-down menu is closed without clicking the Apply button
- if (!$(applyBtn).prop("disabled")) {
- $(this).val(data.groups).selectpicker("refresh");
- $(applyBtn).removeClass("btn-success").prop("disabled", true).off("click");
- }
- })
- .selectpicker()
- .siblings(".dropdown-menu")
- .find(".bs-actionsbox")
- .prepend(
- ''
- );
+ })
+ .on("changed.bs.select", function () {
+ // enable Apply button
+ if ($(applyBtn).prop("disabled")) {
+ $(applyBtn)
+ .addClass("btn-success")
+ .prop("disabled", false)
+ .on("click", function () {
+ editAdlist.call(selectEl);
+ });
+ }
+ })
+ .on("hide.bs.select", function () {
+ // Restore values if drop-down menu is closed without clicking the Apply button
+ if (!$(applyBtn).prop("disabled")) {
+ $(this).val(data.groups).selectpicker("refresh");
+ $(applyBtn).removeClass("btn-success").prop("disabled", true).off("click");
+ }
+ })
+ .selectpicker()
+ .siblings(".dropdown-menu")
+ .find(".bs-actionsbox")
+ .prepend(
+ ''
+ );
- var applyBtn = "#btn_apply_" + data.id;
+ var applyBtn = "#btn_apply_" + dataId;
- // Highlight row (if url parameter "adlistid=" is used)
- if ("adlistid" in GETDict && data.id === parseInt(GETDict.adlistid, 10)) {
- $(row).find("td").addClass("highlight");
- }
-
- var button =
- '";
- $("td:eq(6)", row).html(button);
- },
- dom:
- "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
- "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
- "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
- "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
- "<'row'<'col-sm-12'i>>",
- lengthMenu: [
- [10, 25, 50, 100, -1],
- [10, 25, 50, 100, "All"],
- ],
- select: {
- style: "multi",
- selector: "td:not(:last-child)",
- info: false,
- },
- buttons: [
- {
- text: '',
- titleAttr: "Select All",
- className: "btn-sm datatable-bt selectAll",
- action: function () {
- table.rows({ page: "current" }).select();
- },
- },
- {
- text: '',
- titleAttr: "Select All",
- className: "btn-sm datatable-bt selectMore",
- action: function () {
- table.rows({ page: "current" }).select();
- },
- },
- {
- extend: "selectNone",
- text: '',
- titleAttr: "Deselect All",
- className: "btn-sm datatable-bt removeAll",
- },
- {
- text: '',
- titleAttr: "Delete Selected",
- className: "btn-sm datatable-bt deleteSelected",
- action: function () {
- // For each ".selected" row ...
- var ids = [];
- $("tr.selected").each(function () {
- // ... add the row identified by "data-id".
- ids.push(parseInt($(this).attr("data-id"), 10));
- });
- // Delete all selected rows at once
- delItems(ids);
- },
- },
- ],
- stateSave: true,
- stateDuration: 0,
- stateSaveCallback: function (settings, data) {
- utils.stateSaveCallback("groups-adlists-table", data);
- },
- stateLoadCallback: function () {
- var data = utils.stateLoadCallback("groups-adlists-table");
-
- // Return if not available
- if (data === null) {
- return null;
- }
-
- // Reset visibility of ID column
- data.columns[0].visible = false;
- // Apply loaded state to table
- return data;
- },
- initComplete: function () {
- if ("adlistid" in GETDict) {
- var pos = table
- .column(0, { order: "current" })
- .data()
- .indexOf(parseInt(GETDict.adlistid, 10));
- if (pos >= 0) {
- var page = Math.floor(pos / table.page.info().length);
- table.page(page).draw(false);
+ // Highlight row (if url parameter "adlistid=" is used)
+ if ("adlistid" in GETDict && data.id === parseInt(GETDict.adlistid, 10)) {
+ $(row).find("td").addClass("highlight");
}
- }
- },
- });
+
+ var button =
+ '";
+ $("td:eq(6)", row).html(button);
+ },
+ dom:
+ "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'i>>",
+ lengthMenu: [
+ [10, 25, 50, 100, -1],
+ [10, 25, 50, 100, "All"],
+ ],
+ select: {
+ style: "multi",
+ selector: "td:not(:last-child)",
+ info: false,
+ },
+ buttons: [
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectAll",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectMore",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ extend: "selectNone",
+ text: '',
+ titleAttr: "Deselect All",
+ className: "btn-sm datatable-bt removeAll",
+ },
+ {
+ text: '',
+ titleAttr: "Delete Selected",
+ className: "btn-sm datatable-bt deleteSelected",
+ action: function () {
+ // For each ".selected" row ...
+ var ids = [];
+ $("tr.selected").each(function () {
+ // ... add the row identified by "data-id".
+ ids.push($(this).attr("data-id"), 10);
+ });
+ // Delete all selected rows at once
+ delItems(ids);
+ },
+ },
+ ],
+ stateSave: true,
+ stateDuration: 0,
+ stateSaveCallback: function (settings, data) {
+ utils.stateSaveCallback("groups-adlists-table", data);
+ },
+ stateLoadCallback: function () {
+ var data = utils.stateLoadCallback("groups-adlists-table");
+
+ // Return if not available
+ if (data === null) {
+ return null;
+ }
+
+ // Reset visibility of ID column
+ data.columns[0].visible = false;
+ // Apply loaded state to table
+ return data;
+ },
+ initComplete: function () {
+ if ("adlistid" in GETDict) {
+ var pos = table
+ .column(0, { order: "current" })
+ .data()
+ .indexOf(parseInt(GETDict.adlistid, 10));
+ if (pos >= 0) {
+ var page = Math.floor(pos / table.page.info().length);
+ table.page(page).draw(false);
+ }
+ }
+ },
+ });
table.on("init select deselect", function () {
utils.changeBulkDeleteStates(table);
@@ -434,8 +424,8 @@ function initTable() {
$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteAdlist() {
- // 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
+ const ids = [$(this).attr("data-id")];
delItems(ids);
}
@@ -443,66 +433,51 @@ function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
- var address = "";
+ // Get first element from array
+ const addressRaw = ids[0];
+ const address = utils.hexDecode(addressRaw);
- // Exploit prevention: Return early for non-numeric IDs
- for (var id of ids) {
- if (typeof id !== "number") return;
- address += "' +
- data.client +
- "";
- if (data.name !== null && data.name.length > 0)
- ipName +=
- ' .bootstrap-select.dropdown").remove();
+ },
+ rowCallback: function (row, data) {
+ var dataId = utils.hexEncode(data.client);
+ $(row).attr("data-id", dataId);
+ var tooltip =
+ "Added: " +
+ utils.datetime(data.date_added, false) +
+ "\nLast modified: " +
+ utils.datetime(data.date_modified, false) +
+ "\nDatabase ID: " +
+ data.id;
+ var ipName =
+ '' +
- data.name +
+ data.client +
"";
- $("td:eq(1)", row).html(ipName);
+ if (data.name !== null && data.name.length > 0)
+ ipName +=
+ '
' +
+ data.name +
+ "";
+ $("td:eq(1)", row).html(ipName);
- $("td:eq(2)", row).html('');
- var commentEl = $("#comment_" + dataId, row);
- commentEl.val(utils.unescapeHtml(data.comment));
- commentEl.on("change", editClient);
+ $("td:eq(2)", row).html('');
+ 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(
- ''
- );
- var selectEl = $("#multiselect_" + dataId, row);
- // Add all known groups
- for (var i = 0; i < groups.length; i++) {
- var dataSub = "";
- if (!groups[i].enabled) {
- dataSub = 'data-subtext="(disabled)"';
+ $("td:eq(3)", row).empty();
+ $("td:eq(3)", row).append(
+ ''
+ );
+ var selectEl = $("#multiselect_" + dataId, row);
+ // Add all known groups
+ for (var i = 0; i < groups.length; i++) {
+ var dataSub = "";
+ if (!groups[i].enabled) {
+ dataSub = 'data-subtext="(disabled)"';
+ }
+
+ selectEl.append(
+ $("")
+ .val(groups[i].id)
+ .text(groups[i].name)
+ );
}
- selectEl.append(
- $("")
- .val(groups[i].id)
- .text(groups[i].name)
- );
- }
-
- // Select assigned groups
- selectEl.val(data.groups);
- // Initialize bootstrap-select
- selectEl
- // fix dropdown if it would stick out right of the viewport
- .on("show.bs.select", function () {
- var winWidth = $(window).width();
- var dropdownEl = $("body > .bootstrap-select.dropdown");
- if (dropdownEl.length > 0) {
- dropdownEl.removeClass("align-right");
- var width = dropdownEl.width();
- var left = dropdownEl.offset().left;
- if (left + width > winWidth) {
- dropdownEl.addClass("align-right");
+ // Select assigned groups
+ selectEl.val(data.groups);
+ // Initialize bootstrap-select
+ selectEl
+ // fix dropdown if it would stick out right of the viewport
+ .on("show.bs.select", function () {
+ var winWidth = $(window).width();
+ var dropdownEl = $("body > .bootstrap-select.dropdown");
+ if (dropdownEl.length > 0) {
+ dropdownEl.removeClass("align-right");
+ var width = dropdownEl.width();
+ var left = dropdownEl.offset().left;
+ if (left + width > winWidth) {
+ dropdownEl.addClass("align-right");
+ }
}
- }
- })
- .on("changed.bs.select", function () {
- // enable Apply button
- if ($(applyBtn).prop("disabled")) {
- $(applyBtn)
- .addClass("btn-success")
- .prop("disabled", false)
- .on("click", function () {
- editClient.call(selectEl);
- });
- }
- })
- .on("hide.bs.select", function () {
- // Restore values if drop-down menu is closed without clicking the Apply button
- if (!$(applyBtn).prop("disabled")) {
- $(this).val(data.groups).selectpicker("refresh");
- $(applyBtn).removeClass("btn-success").prop("disabled", true).off("click");
- }
- })
- .selectpicker()
- .siblings(".dropdown-menu")
- .find(".bs-actionsbox")
- .prepend(
- ''
- );
+ })
+ .on("changed.bs.select", function () {
+ // enable Apply button
+ if ($(applyBtn).prop("disabled")) {
+ $(applyBtn)
+ .addClass("btn-success")
+ .prop("disabled", false)
+ .on("click", function () {
+ editClient.call(selectEl);
+ });
+ }
+ })
+ .on("hide.bs.select", function () {
+ // Restore values if drop-down menu is closed without clicking the Apply button
+ if (!$(applyBtn).prop("disabled")) {
+ $(this).val(data.groups).selectpicker("refresh");
+ $(applyBtn).removeClass("btn-success").prop("disabled", true).off("click");
+ }
+ })
+ .selectpicker()
+ .siblings(".dropdown-menu")
+ .find(".bs-actionsbox")
+ .prepend(
+ ''
+ );
- var applyBtn = "#btn_apply_" + dataId;
+ var applyBtn = "#btn_apply_" + dataId;
- var button =
- '";
- $("td:eq(4)", row).html(button);
- },
- select: {
- style: "multi",
- selector: "td:not(:last-child)",
- info: false,
- },
- buttons: [
- {
- text: '',
- titleAttr: "Select All",
- className: "btn-sm datatable-bt selectAll",
- action: function () {
- table.rows({ page: "current" }).select();
+ var button =
+ '";
+ $("td:eq(4)", row).html(button);
+ },
+ select: {
+ style: "multi",
+ selector: "td:not(:last-child)",
+ info: false,
+ },
+ buttons: [
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectAll",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
},
- },
- {
- text: '',
- titleAttr: "Select All",
- className: "btn-sm datatable-bt selectMore",
- action: function () {
- table.rows({ page: "current" }).select();
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectMore",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
},
- },
- {
- extend: "selectNone",
- text: '',
- titleAttr: "Deselect All",
- className: "btn-sm datatable-bt removeAll",
- },
- {
- text: '',
- titleAttr: "Delete Selected",
- className: "btn-sm datatable-bt deleteSelected",
- action: function () {
- // For each ".selected" row ...
- var ids = [];
- $("tr.selected").each(function () {
- // ... add the row identified by "data-id".
- ids.push($(this).attr("data-id"));
- });
- // Delete all selected rows at once
- delItems(ids);
+ {
+ extend: "selectNone",
+ text: '',
+ titleAttr: "Deselect All",
+ className: "btn-sm datatable-bt removeAll",
},
+ {
+ text: '',
+ titleAttr: "Delete Selected",
+ className: "btn-sm datatable-bt deleteSelected",
+ action: function () {
+ // For each ".selected" row ...
+ var ids = [];
+ $("tr.selected").each(function () {
+ // ... add the row identified by "data-id".
+ ids.push($(this).attr("data-id"));
+ });
+ // Delete all selected rows at once
+ delItems(ids);
+ },
+ },
+ ],
+ dom:
+ "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'i>>",
+ lengthMenu: [
+ [10, 25, 50, 100, -1],
+ [10, 25, 50, 100, "All"],
+ ],
+ stateSave: true,
+ stateDuration: 0,
+ stateSaveCallback: function (settings, data) {
+ utils.stateSaveCallback("groups-clients-table", data);
},
- ],
- dom:
- "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
- "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
- "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
- "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
- "<'row'<'col-sm-12'i>>",
- lengthMenu: [
- [10, 25, 50, 100, -1],
- [10, 25, 50, 100, "All"],
- ],
- stateSave: true,
- stateDuration: 0,
- stateSaveCallback: function (settings, data) {
- utils.stateSaveCallback("groups-clients-table", data);
- },
- stateLoadCallback: function () {
- var data = utils.stateLoadCallback("groups-clients-table");
+ stateLoadCallback: function () {
+ var data = utils.stateLoadCallback("groups-clients-table");
- // Return if not available
- if (data === null) {
- return null;
- }
+ // Return if not available
+ if (data === null) {
+ return null;
+ }
- // Reset visibility of ID column
- data.columns[0].visible = false;
- // Apply loaded state to table
- return data;
- },
- });
+ // Reset visibility of ID column
+ data.columns[0].visible = false;
+ // Apply loaded state to table
+ return data;
+ },
+ });
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
@@ -358,7 +349,7 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteClient() {
// Passes the button data-id attribute as ID
- var ids = [$(this).attr("data-id")];
+ const ids = [$(this).attr("data-id")];
delItems(ids);
}
@@ -366,18 +357,19 @@ function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
- var clientRaw = ids[0];
- var client = clientRaw.replaceAll("__", ":");
+ // Get first element from array
+ const clientRaw = ids[0];
+ const client = utils.hexDecode(clientRaw);
// Remove first element from array
ids.shift();
utils.disableAll();
- var idstring = ids.join(", ");
+ const idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting client...", client);
$.ajax({
- url: "/api/clients/" + client,
+ url: "/api/clients/" + encodeURIComponent(client),
method: "delete",
})
.done(function () {
@@ -396,16 +388,17 @@ function delItems(ids) {
table.rows().deselect();
utils.changeBulkDeleteStates(table);
- // Update number of groups in the sidebar
+ // Update number of clients in the sidebar
updateFtlInfo();
})
- .fail(function (jqXHR, exception) {
+ .fail(function (data, exception) {
+ apiFailure(data);
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while deleting client(s): " + idstring,
- jqXHR.responseText
+ data.responseText
);
console.log(exception); // eslint-disable-line no-console
});
@@ -413,7 +406,7 @@ function delItems(ids) {
function addClient() {
var ip = utils.escapeHtml($("#select").val().trim());
- var comment = utils.escapeHtml($("#new_comment").val());
+ const comment = utils.escapeHtml($("#new_comment").val());
utils.disableAll();
utils.showAlert("info", "", "Adding client...", ip);
@@ -458,24 +451,26 @@ function addClient() {
// Update number of groups in the sidebar
updateFtlInfo();
},
- error: function (jqXHR, exception) {
+ error: function (data, exception) {
+ apiFailure(data);
utils.enableAll();
- utils.showAlert("error", "", "Error while adding new client", jqXHR.responseText);
+ utils.showAlert("error", "", "Error while adding new client", data.responseText);
console.log(exception); // eslint-disable-line no-console
},
});
}
function editClient() {
- var elem = $(this).attr("id");
- var tr = $(this).closest("tr");
- 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());
+ const elem = $(this).attr("id");
+ const tr = $(this).closest("tr");
+ const client = tr.attr("data-id");
+ // Convert list of string integers to list of integers using map(Number)
+ const groups = tr
+ .find("#multiselect_" + client)
+ .val()
+ .map(Number);
+ const comment = utils.escapeHtml(tr.find("#comment_" + client).val());
+ const enabled = tr.find("#enabled_" + client).is(":checked");
var done = "edited";
var notDone = "editing";
@@ -493,35 +488,37 @@ function editClient() {
return;
}
- if (name.length > 0) {
- ip += " (" + name + ")";
- }
-
utils.disableAll();
- const clientRaw = client.replaceAll("__", ":");
- utils.showAlert("info", "", "Editing client...", ip);
+ const clientDecoded = utils.hexDecode(client);
+ utils.showAlert("info", "", "Editing client...", clientDecoded);
$.ajax({
- url: "/api/clients/" + clientRaw,
+ url: "/api/clients/" + encodeURIComponent(clientDecoded),
method: "put",
dataType: "json",
data: JSON.stringify({
client: client,
groups: groups,
- token: token,
comment: comment,
+ enabled: enabled,
}),
success: function () {
utils.enableAll();
- utils.showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " client", ip);
+ utils.showAlert(
+ "success",
+ "fas fa-pencil-alt",
+ "Successfully " + done + " client",
+ clientDecoded
+ );
table.ajax.reload(null, false);
},
- error: function (jqXHR, exception) {
+ error: function (data, exception) {
+ apiFailure(data);
utils.enableAll();
utils.showAlert(
"error",
"",
- "Error while " + notDone + " client " + clientRaw,
- jqXHR.responseText
+ "Error while " + notDone + " client " + clientDecoded,
+ data.responseText
);
console.log(exception); // eslint-disable-line no-console
},
diff --git a/scripts/pi-hole/js/groups-common.js b/scripts/pi-hole/js/groups-common.js
new file mode 100644
index 00000000..90bbe765
--- /dev/null
+++ b/scripts/pi-hole/js/groups-common.js
@@ -0,0 +1,21 @@
+/* Pi-hole: A black hole for Internet advertisements
+ * (c) 2023 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. */
+
+// eslint-disable-next-line no-unused-vars
+var groups = [];
+
+// eslint-disable-next-line no-unused-vars
+function getGroups() {
+ $.ajax({
+ url: "/api/groups",
+ type: "GET",
+ dataType: "json",
+ success: function (data) {
+ groups = data.groups;
+ },
+ });
+}
diff --git a/scripts/pi-hole/js/groups-domains.js b/scripts/pi-hole/js/groups-domains.js
index 76d29d5e..1429a95a 100644
--- a/scripts/pi-hole/js/groups-domains.js
+++ b/scripts/pi-hole/js/groups-domains.js
@@ -5,32 +5,13 @@
* 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, groups:false,, getGroups:false, updateFtlInfo:false, apiFailure:false */
var table;
-var groups = [];
-var token = $("#token").text();
var GETDict = {};
-function getGroups() {
- $.post(
- "scripts/pi-hole/php/groups.php",
- { action: "get_groups", token: token },
- function (data) {
- groups = data.data;
- initTable();
- },
- "json"
- );
-}
-
$(function () {
- window.location.search
- .substr(1)
- .split("&")
- .forEach(function (item) {
- GETDict[item.split("=")[0]] = item.split("=")[1];
- });
+ GETDict = utils.parseQueryString();
// sync description fields, reset inactive inputs on tab change
$('a[data-toggle="tab"]').on("shown.bs.tab", function () {
@@ -51,7 +32,7 @@ $(function () {
$("#suggest_domains").hide();
});
- $("#add2black, #add2white").on("click", addDomain);
+ $("#add_deny, #add_allow").on("click", addDomain);
var suggestTimeout;
$("#new_domain").on("input", function (e) {
@@ -61,7 +42,7 @@ $(function () {
});
utils.setBsSelectDefaults();
- getGroups();
+ initTable();
});
function showSuggestDomains(value) {
@@ -108,272 +89,288 @@ function hideSuggestDomains() {
}
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: null, visible: true, orderable: false, width: "15px" },
- { data: "domain" },
- { data: "type", searchable: false },
- { data: "enabled", searchable: false },
- { data: "comment" },
- { data: "groups", searchable: false },
- { data: null, width: "22px", orderable: false },
- ],
- columnDefs: [
- {
- targets: 1,
- className: "select-checkbox",
- render: function () {
- return "";
+ table = $("#domainsTable")
+ .on("preXhr.dt", function () {
+ getGroups();
+ })
+ .DataTable({
+ processing: true,
+ ajax: {
+ url: "/api/domains",
+ dataSrc: "domains",
+ type: "GET",
+ },
+ order: [[0, "asc"]],
+ columns: [
+ { data: "id", visible: false },
+ { data: null, visible: true, orderable: false, width: "15px" },
+ { data: "domain" },
+ { data: "type", searchable: false },
+ { data: "enabled", searchable: false },
+ { data: "comment" },
+ { data: "groups", searchable: false },
+ { data: null, width: "22px", orderable: false },
+ ],
+ columnDefs: [
+ {
+ targets: 1,
+ className: "select-checkbox",
+ render: function () {
+ return "";
+ },
},
+ {
+ targets: "_all",
+ render: $.fn.dataTable.render.text(),
+ },
+ ],
+ drawCallback: function () {
+ // Hide buttons if all domains were deleted
+ var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
+ $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
+
+ $('button[id^="deleteDomain_"]').on("click", deleteDomain);
+ // Remove visible dropdown to prevent orphaning
+ $("body > .bootstrap-select.dropdown").remove();
},
- {
- targets: "_all",
- render: $.fn.dataTable.render.text(),
- },
- ],
- drawCallback: function () {
- // Hide buttons if all domains were deleted
- var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
- $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
+ rowCallback: function (row, data) {
+ var dataId = utils.hexEncode(data.domain);
+ $(row).attr("data-id", dataId);
+ var tooltip =
+ "Added: " +
+ utils.datetime(data.date_added, false) +
+ "\nLast modified: " +
+ utils.datetime(data.date_modified, false) +
+ "\nDatabase ID: " +
+ data.id;
+ $("td:eq(1)", row).html(
+ '' +
+ data.domain +
+ ""
+ );
- $('button[id^="deleteDomain_"]').on("click", deleteDomain);
- // Remove visible dropdown to prevent orphaning
- $("body > .bootstrap-select.dropdown").remove();
- },
- rowCallback: function (row, data) {
- $(row).attr("data-id", data.id);
- var tooltip =
- "Added: " +
- utils.datetime(data.date_added, false) +
- "\nLast modified: " +
- utils.datetime(data.date_modified, false) +
- "\nDatabase ID: " +
- data.id;
- $("td:eq(1)", row).html(
- '' +
- data.domain +
- ""
- );
+ // Drop-down type selector
+ $("td:eq(2)", row).html(
+ '" +
+ ""
+ );
+ var typeEl = $("#type_" + dataId, row);
+ typeEl.on("change", editDomain);
- // Drop-down type selector
- $("td:eq(2)", row).html(
- '"
- );
- var typeEl = $("#type_" + data.id, row);
- typeEl.on("change", editDomain);
+ $("td:eq(3)", row).html(
+ '"
+ );
+ var statusEl = $("#enabled_" + dataId, row);
+ statusEl.bootstrapToggle({
+ on: "Enabled",
+ off: "Disabled",
+ size: "small",
+ onstyle: "success",
+ width: "80px",
+ });
+ statusEl.on("change", editDomain);
- var disabled = data.enabled === 0;
- $("td:eq(3)", row).html(
- '"
- );
- var statusEl = $("#status_" + data.id, row);
- statusEl.bootstrapToggle({
- on: "Enabled",
- off: "Disabled",
- size: "small",
- onstyle: "success",
- width: "80px",
- });
- statusEl.on("change", editDomain);
+ $("td:eq(4)", row).html('');
+ var commentEl = $("#comment_" + dataId, row);
+ commentEl.val(utils.unescapeHtml(data.comment));
+ commentEl.on("change", editDomain);
- $("td:eq(4)", row).html('');
- var commentEl = $("#comment_" + data.id, row);
- commentEl.val(utils.unescapeHtml(data.comment));
- commentEl.on("change", editDomain);
+ // Group assignment field
+ $("td:eq(5)", row).empty();
+ $("td:eq(5)", row).append(
+ ''
+ );
+ var selectEl = $("#multiselect_" + dataId, row);
+ // Add all known groups
+ for (var i = 0; i < groups.length; i++) {
+ var dataSub = "";
+ if (!groups[i].enabled) {
+ dataSub = 'data-subtext="(disabled)"';
+ }
- // Group assignment field
- $("td:eq(5)", row).empty();
- $("td:eq(5)", row).append(
- ''
- );
- var selectEl = $("#multiselect_" + data.id, row);
- // Add all known groups
- for (var i = 0; i < groups.length; i++) {
- var dataSub = "";
- if (!groups[i].enabled) {
- dataSub = 'data-subtext="(disabled)"';
+ selectEl.append(
+ $("")
+ .val(groups[i].id)
+ .text(groups[i].name)
+ );
}
- selectEl.append(
- $("")
- .val(groups[i].id)
- .text(groups[i].name)
- );
- }
-
- // Select assigned groups
- selectEl.val(data.groups);
- // Initialize bootstrap-select
- selectEl
- // fix dropdown if it would stick out right of the viewport
- .on("show.bs.select", function () {
- var winWidth = $(window).width();
- var dropdownEl = $("body > .bootstrap-select.dropdown");
- if (dropdownEl.length > 0) {
- dropdownEl.removeClass("align-right");
- var width = dropdownEl.width();
- var left = dropdownEl.offset().left;
- if (left + width > winWidth) {
- dropdownEl.addClass("align-right");
+ // Select assigned groups
+ selectEl.val(data.groups);
+ // Initialize bootstrap-select
+ selectEl
+ // fix dropdown if it would stick out right of the viewport
+ .on("show.bs.select", function () {
+ var winWidth = $(window).width();
+ var dropdownEl = $("body > .bootstrap-select.dropdown");
+ if (dropdownEl.length > 0) {
+ dropdownEl.removeClass("align-right");
+ var width = dropdownEl.width();
+ var left = dropdownEl.offset().left;
+ if (left + width > winWidth) {
+ dropdownEl.addClass("align-right");
+ }
}
- }
- })
- .on("changed.bs.select", function () {
- // enable Apply button
- if ($(applyBtn).prop("disabled")) {
- $(applyBtn)
- .addClass("btn-success")
- .prop("disabled", false)
- .on("click", function () {
- editDomain.call(selectEl);
- });
- }
- })
- .on("hide.bs.select", function () {
- // Restore values if drop-down menu is closed without clicking the Apply button
- if (!$(applyBtn).prop("disabled")) {
- $(this).val(data.groups).selectpicker("refresh");
- $(applyBtn).removeClass("btn-success").prop("disabled", true).off("click");
- }
- })
- .selectpicker()
- .siblings(".dropdown-menu")
- .find(".bs-actionsbox")
- .prepend(
- ''
- );
+ })
+ .on("changed.bs.select", function () {
+ // enable Apply button
+ if ($(applyBtn).prop("disabled")) {
+ $(applyBtn)
+ .addClass("btn-success")
+ .prop("disabled", false)
+ .on("click", function () {
+ editDomain.call(selectEl);
+ });
+ }
+ })
+ .on("hide.bs.select", function () {
+ // Restore values if drop-down menu is closed without clicking the Apply button
+ if (!$(applyBtn).prop("disabled")) {
+ $(this).val(data.groups).selectpicker("refresh");
+ $(applyBtn).removeClass("btn-success").prop("disabled", true).off("click");
+ }
+ })
+ .selectpicker()
+ .siblings(".dropdown-menu")
+ .find(".bs-actionsbox")
+ .prepend(
+ ''
+ );
- var applyBtn = "#btn_apply_" + data.id;
+ var applyBtn = "#btn_apply_" + dataId;
- // Highlight row (if url parameter "domainid=" is used)
- if ("domainid" in GETDict && data.id === parseInt(GETDict.domainid, 10)) {
- $(row).find("td").addClass("highlight");
- }
-
- // Add delete domain button
- var button =
- '";
- $("td:eq(6)", row).html(button);
- },
- select: {
- style: "multi",
- selector: "td:not(:last-child)",
- info: false,
- },
- buttons: [
- {
- text: '',
- titleAttr: "Select All",
- className: "btn-sm datatable-bt selectAll",
- action: function () {
- table.rows({ page: "current" }).select();
- },
- },
- {
- text: '',
- titleAttr: "Select All",
- className: "btn-sm datatable-bt selectMore",
- action: function () {
- table.rows({ page: "current" }).select();
- },
- },
- {
- extend: "selectNone",
- text: '',
- titleAttr: "Deselect All",
- className: "btn-sm datatable-bt removeAll",
- },
- {
- text: '',
- titleAttr: "Delete Selected",
- className: "btn-sm datatable-bt deleteSelected",
- action: function () {
- // For each ".selected" row ...
- var ids = [];
- $("tr.selected").each(function () {
- // ... add the row identified by "data-id".
- ids.push(parseInt($(this).attr("data-id"), 10));
- });
- // Delete all selected rows at once
- delItems(ids);
- },
- },
- ],
- dom:
- "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
- "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
- "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
- "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
- "<'row'<'col-sm-12'i>>",
- lengthMenu: [
- [10, 25, 50, 100, -1],
- [10, 25, 50, 100, "All"],
- ],
- stateSave: true,
- stateDuration: 0,
- stateSaveCallback: function (settings, data) {
- utils.stateSaveCallback("groups-domains-table", data);
- },
- stateLoadCallback: function () {
- var data = utils.stateLoadCallback("groups-domains-table");
-
- // Return if not available
- if (data === null) {
- return null;
- }
-
- // Reset visibility of ID column
- data.columns[0].visible = false;
- // Apply loaded state to table
- return data;
- },
- initComplete: function () {
- if ("domainid" in GETDict) {
- var pos = table
- .column(0, { order: "current" })
- .data()
- .indexOf(parseInt(GETDict.domainid, 10));
- if (pos >= 0) {
- var page = Math.floor(pos / table.page.info().length);
- table.page(page).draw(false);
+ // Highlight row (if url parameter "domainid=" is used)
+ if ("domainid" in GETDict && data.id === parseInt(GETDict.domainid, 10)) {
+ $(row).find("td").addClass("highlight");
}
- }
- },
- });
+
+ // Add delete domain button
+ var button =
+ '";
+ $("td:eq(6)", row).html(button);
+ },
+ select: {
+ style: "multi",
+ selector: "td:not(:last-child)",
+ info: false,
+ },
+ buttons: [
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectAll",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ text: '',
+ titleAttr: "Select All",
+ className: "btn-sm datatable-bt selectMore",
+ action: function () {
+ table.rows({ page: "current" }).select();
+ },
+ },
+ {
+ extend: "selectNone",
+ text: '',
+ titleAttr: "Deselect All",
+ className: "btn-sm datatable-bt removeAll",
+ },
+ {
+ text: '',
+ titleAttr: "Delete Selected",
+ className: "btn-sm datatable-bt deleteSelected",
+ action: function () {
+ // For each ".selected" row ...
+ var ids = [];
+ $("tr.selected").each(function () {
+ // ... add the row identified by "data-id".
+ ids.push($(this).attr("data-id"));
+ });
+ // Delete all selected rows at once
+ delItems(ids);
+ },
+ },
+ ],
+ dom:
+ "<'row'<'col-sm-6'l><'col-sm-6'f>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'<'table-responsive'tr>>>" +
+ "<'row'<'col-sm-3'B><'col-sm-9'p>>" +
+ "<'row'<'col-sm-12'i>>",
+ lengthMenu: [
+ [10, 25, 50, 100, -1],
+ [10, 25, 50, 100, "All"],
+ ],
+ stateSave: true,
+ stateDuration: 0,
+ stateSaveCallback: function (settings, data) {
+ utils.stateSaveCallback("groups-domains-table", data);
+ },
+ stateLoadCallback: function () {
+ var data = utils.stateLoadCallback("groups-domains-table");
+
+ // Return if not available
+ if (data === null) {
+ return null;
+ }
+
+ // Reset visibility of ID column
+ data.columns[0].visible = false;
+ // Apply loaded state to table
+ return data;
+ },
+ initComplete: function () {
+ if ("domainid" in GETDict) {
+ var pos = table
+ .column(0, { order: "current" })
+ .data()
+ .indexOf(parseInt(GETDict.domainid, 10));
+ if (pos >= 0) {
+ var page = Math.floor(pos / table.page.info().length);
+ table.page(page).draw(false);
+ }
+ }
+ },
+ });
// Disable autocorrect in the search box
var input = document.querySelector("input[type=search]");
if (input !== null) {
@@ -410,7 +407,8 @@ $.fn.dataTable.ext.search.push(function (settings, searchData, index, rowData) {
})
.get();
- if (types.indexOf(rowData.type.toString()) !== -1) {
+ const typeStr = rowData.type + "/" + rowData.kind;
+ if (types.indexOf(typeStr) !== -1) {
return true;
}
@@ -424,8 +422,8 @@ $(".filter_types input:checkbox").on("change", function () {
$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteDomain() {
- // 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
+ const ids = [$(this).attr("data-id")];
delItems(ids);
}
@@ -433,250 +431,209 @@ function delItems(ids) {
// Check input validity
if (!Array.isArray(ids)) return;
- var items = "";
- var type = "";
- var typeID = "";
+ // Get first element from array
+ const domainRaw = ids[0];
+ const domain = utils.hexDecode(domainRaw);
+ const typestr = $("#old_type_" + domainRaw).val();
- for (var id of ids) {
- // Exploit prevention: Return early for non-numeric IDs
- if (typeof id !== "number") return;
-
- // Retrieve domain type
- typeID = $("#type_" + id).val();
- if (typeID === "0" || typeID === "1") {
- type = " (domain)";
- } else if (typeID === "2" || typeID === "3") {
- type = " (regex)";
- }
-
- // Add item
- items += "" + utils.escapeHtml($("#domain_" + id).text()) + type + " ";
- }
+ // Remove first element from array
+ ids.shift();
utils.disableAll();
- var idstring = ids.join(", ");
- utils.showAlert("info", "", "Deleting domain(s)...", "" + items + "
");
+ const idstring = ids.join(", ");
+ utils.showAlert("info", "", "Deleting domain...", "" + domain + "
");
$.ajax({
- url: "scripts/pi-hole/php/groups.php",
- method: "post",
- dataType: "json",
- data: { action: "delete_domain", id: JSON.stringify(ids), token: token },
+ url: "/api/domains/" + typestr + "/" + encodeURIComponent(domain),
+ method: "delete",
})
- .done(function (response) {
+ .done(function () {
utils.enableAll();
- if (response.success) {
- utils.showAlert(
- "success",
- "far fa-trash-alt",
- "Successfully deleted domain(s): ",
- "" + items + "
"
- );
- 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 domain(s): " + idstring,
- response.message
- );
+ utils.showAlert("success", "far fa-trash-alt", "Successfully deleted list: ", domain);
+ table.row(domainRaw).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 lists in the sidebar
+ updateFtlInfo();
})
- .fail(function (jqXHR, exception) {
+ .fail(function (data, exception) {
+ apiFailure(data);
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while deleting domain(s): " + idstring,
- jqXHR.responseText
+ data.responseText
);
console.log(exception); // eslint-disable-line no-console
});
}
function addDomain() {
- var action = this.id;
- var tabHref = $('a[data-toggle="tab"][aria-expanded="true"]').attr("href");
- var wildcardEl = $("#wildcard_checkbox");
- var wildcardChecked = wildcardEl.prop("checked");
- var type;
+ const action = this.id;
+ const tabHref = $('a[data-toggle="tab"][aria-expanded="true"]').attr("href");
+ const wildcardEl = $("#wildcard_checkbox");
+ const wildcardChecked = wildcardEl.prop("checked");
// current tab's inputs
- var domainRegex, domainEl, commentEl;
+ var kind, domainEl, commentEl;
if (tabHref === "#tab_domain") {
- domainRegex = "domain";
+ kind = "exact";
domainEl = $("#new_domain");
commentEl = $("#new_domain_comment");
} else if (tabHref === "#tab_regex") {
- domainRegex = "regex";
+ kind = "regex";
domainEl = $("#new_regex");
commentEl = $("#new_regex_comment");
}
var domain = utils.escapeHtml(domainEl.val());
- var comment = utils.escapeHtml(commentEl.val());
+ const comment = utils.escapeHtml(commentEl.val());
utils.disableAll();
- utils.showAlert("info", "", "Adding " + domainRegex + "...", domain);
+ utils.showAlert("info", "", "Adding domain...", domain);
- if (domain.length > 0) {
- // strip "*." if specified by user in wildcard mode
- if (domainRegex === "domain" && wildcardChecked && domain.startsWith("*.")) {
- domain = domain.substr(2);
- }
-
- // determine list type
- if (domainRegex === "domain" && action === "add2black" && wildcardChecked) {
- type = "3W";
- } else if (domainRegex === "domain" && action === "add2black" && !wildcardChecked) {
- type = "1";
- } else if (domainRegex === "domain" && action === "add2white" && wildcardChecked) {
- type = "2W";
- } else if (domainRegex === "domain" && action === "add2white" && !wildcardChecked) {
- type = "0";
- } else if (domainRegex === "regex" && action === "add2black") {
- type = "3";
- } else if (domainRegex === "regex" && action === "add2white") {
- type = "2";
- }
- } else {
+ if (domain.length < 2) {
utils.enableAll();
- utils.showAlert("warning", "", "Warning", "Please specify a " + domainRegex);
+ utils.showAlert("warning", "", "Warning", "Please specify a domain");
return;
}
+ // strip "*." if specified by user in wildcard mode
+ if (kind === "exact" && wildcardChecked && domain.startsWith("*.")) {
+ domain = domain.substr(2);
+ }
+
+ // determine list type
+ const type = action === "add_deny" ? "deny" : "allow";
+
+ // Transform domain to wildcard if specified by user
+ if (kind === "exact" && wildcardChecked) {
+ domain = "(\\.|^)" + domain.replaceAll(".", "\\.") + "$";
+ kind = "regex";
+ }
+
$.ajax({
- url: "scripts/pi-hole/php/groups.php",
+ url: "/api/domains/" + type + "/" + kind,
method: "post",
dataType: "json",
- data: {
- action: "add_domain",
+ data: JSON.stringify({
domain: domain,
- type: type,
comment: comment,
- token: token,
- },
- success: function (response) {
+ type: type,
+ kind: kind,
+ }),
+ success: function () {
utils.enableAll();
- if (response.success) {
- utils.showAlert("success", "fas fa-plus", "Success!", response.message);
- domainEl.val("");
- commentEl.val("");
- wildcardEl.prop("checked", false);
- table.ajax.reload(null, false);
- table.rows().deselect();
- domainEl.focus();
- } else {
- utils.showAlert("error", "", "Error while adding new " + domainRegex, response.message);
- }
+ utils.showAlert("success", "fas fa-plus", "Successfully added domain", domain);
+ table.ajax.reload(null, false);
+ table.rows().deselect();
+
+ // Update number of groups in the sidebar
+ updateFtlInfo();
},
- error: function (jqXHR, exception) {
+ error: function (data, exception) {
+ apiFailure(data);
utils.enableAll();
- utils.showAlert("error", "", "Error while adding new " + domainRegex, jqXHR.responseText);
+ utils.showAlert("error", "", "Error while adding new domain", data.responseText);
console.log(exception); // eslint-disable-line no-console
},
});
}
function editDomain() {
- var elem = $(this).attr("id");
- var tr = $(this).closest("tr");
- var id = tr.attr("data-id");
- var domain = utils.escapeHtml(tr.find("#domain_" + id).text());
- var type = tr.find("#type_" + id).val();
- var status = tr.find("#status_" + id).is(":checked") ? 1 : 0;
- var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
- var groups = tr.find("#multiselect_" + id).val();
+ const elem = $(this).attr("id");
+ const tr = $(this).closest("tr");
+ const domain = tr.attr("data-id");
+ const newTypestr = tr.find("#type_" + domain).val();
+ const oldTypeStr = tr.find("#old_type_" + domain).val();
+ const enabled = tr.find("#enabled_" + domain).is(":checked");
+ const comment = utils.escapeHtml(tr.find("#comment_" + domain).val());
+ // Convert list of string integers to list of integers using map
+ const groups = tr
+ .find("#multiselect_" + domain)
+ .val()
+ .map(Number);
- var domainRegex;
- if (type === "0" || type === "1") {
- domainRegex = "domain";
- } else if (type === "2" || type === "3") {
- domainRegex = "regex";
- }
+ const oldType = oldTypeStr.split("/")[0];
+ const oldKind = oldTypeStr.split("/")[1];
var done = "edited";
var notDone = "editing";
switch (elem) {
- case "status_" + id:
- if (status === 0) {
+ case "enabled_" + domain:
+ if (enabled) {
done = "disabled";
notDone = "disabling";
- } else if (status === 1) {
+ } else {
done = "enabled";
notDone = "enabling";
}
break;
- case "name_" + id:
+ case "name_" + domain:
done = "edited name of";
notDone = "editing name of";
break;
- case "comment_" + id:
+ case "comment_" + domain:
done = "edited comment of";
notDone = "editing comment of";
break;
- case "type_" + id:
+ case "type_" + domain:
done = "edited type of";
notDone = "editing type of";
break;
- case "multiselect_" + id:
+ case "multiselect_" + domain:
done = "edited groups of";
notDone = "editing groups of";
break;
default:
- alert("bad element or invalid data-id!");
+ alert("bad element (" + elem + ") or invalid data-id!");
return;
}
utils.disableAll();
- utils.showAlert("info", "", "Editing " + domainRegex + "...", name);
+ const domainDecoded = utils.hexDecode(domain);
+ utils.showAlert("info", "", "Editing domain...", domain);
$.ajax({
- url: "scripts/pi-hole/php/groups.php",
- method: "post",
+ url: "/api/domains/" + newTypestr + "/" + encodeURIComponent(domainDecoded),
+ method: "put",
dataType: "json",
- data: {
- action: "edit_domain",
- id: id,
- type: type,
- comment: comment,
- status: status,
+ data: JSON.stringify({
groups: groups,
- token: token,
- },
- success: function (response) {
+ comment: comment,
+ enabled: enabled,
+ type: oldType,
+ kind: oldKind,
+ }),
+ success: function () {
utils.enableAll();
- if (response.success) {
- utils.showAlert(
- "success",
- "fas fa-pencil-alt",
- "Successfully " + done + " " + domainRegex,
- domain
- );
- table.ajax.reload(null, false);
- } else
- utils.showAlert(
- "error",
- "",
- "Error while " + notDone + " " + domainRegex + " with ID " + id,
- response.message
- );
+ utils.showAlert(
+ "success",
+ "fas fa-pencil-alt",
+ "Successfully " + done + " domain",
+ domainDecoded
+ );
+ table.ajax.reload(null, false);
},
- error: function (jqXHR, exception) {
+ error: function (data, exception) {
+ apiFailure(data);
utils.enableAll();
utils.showAlert(
"error",
"",
- "Error while " + notDone + " " + domainRegex + " with ID " + id,
- jqXHR.responseText
+ "Error while " + notDone + " domain " + domainDecoded,
+ data.responseText
);
console.log(exception); // eslint-disable-line no-console
},
diff --git a/scripts/pi-hole/js/groups.js b/scripts/pi-hole/js/groups.js
index a6aec9ed..27ed8ce3 100644
--- a/scripts/pi-hole/js/groups.js
+++ b/scripts/pi-hole/js/groups.js
@@ -5,7 +5,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global utils:false, updateFtlInfo:false */
+/* global utils:false, apiFailure:false, updateFtlInfo:false */
var table,
idNames = {};
@@ -25,6 +25,7 @@ $(function () {
$("#btnAdd").on("click", addGroup);
table = $("#groupsTable").DataTable({
+ processing: true,
ajax: {
url: "/api/groups",
error: handleAjaxError,
@@ -220,7 +221,7 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
function deleteGroup() {
// Passes the button data-del-id attribute as ID
- var ids = [parseInt($(this).attr("data-del-id"), 10)];
+ const ids = [parseInt($(this).attr("data-del-id"), 10)];
delItems(ids);
}
@@ -233,14 +234,15 @@ function delItems(ids) {
if (typeof id !== "number") return;
}
- var id = ids[0];
- var name = idNames[id];
+ // Get first element from array
+ const id = ids[0];
+ const name = idNames[id];
// Remove first element from array
ids.shift();
utils.disableAll();
- var idstring = ids.join(", ");
+ const idstring = ids.join(", ");
utils.showAlert("info", "", "Deleting group...", name);
$.ajax({
@@ -266,21 +268,17 @@ function delItems(ids) {
// Update number of groups in the sidebar
updateFtlInfo();
})
- .fail(function (jqXHR, exception) {
+ .fail(function (data, exception) {
+ apiFailure(data);
utils.enableAll();
- utils.showAlert(
- "error",
- "",
- "Error while deleting group(s): " + idstring,
- jqXHR.responseText
- );
+ utils.showAlert("error", "", "Error while deleting group(s): " + idstring, data.responseText);
console.log(exception); // eslint-disable-line no-console
});
}
function addGroup() {
- var name = utils.escapeHtml($("#new_name").val());
- var comment = utils.escapeHtml($("#new_comment").val());
+ const name = utils.escapeHtml($("#new_name").val());
+ const comment = utils.escapeHtml($("#new_comment").val());
utils.disableAll();
utils.showAlert("info", "", "Adding group...", name);
@@ -312,22 +310,23 @@ function addGroup() {
// Update number of groups in the sidebar
updateFtlInfo();
},
- error: function (jqXHR, exception) {
+ error: function (data, exception) {
+ apiFailure(data);
utils.enableAll();
- utils.showAlert("error", "", "Error while adding new group", jqXHR.responseText);
+ utils.showAlert("error", "", "Error while adding new group", data.responseText);
console.log(exception); // eslint-disable-line no-console
},
});
}
function editGroup() {
- var elem = $(this).attr("id");
- var tr = $(this).closest("tr");
- var id = tr.attr("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());
+ const elem = $(this).attr("id");
+ const tr = $(this).closest("tr");
+ const id = tr.attr("data-id");
+ const oldName = idNames[id];
+ const name = utils.escapeHtml(tr.find("#name_" + id).val());
+ const enabled = tr.find("#enabled_" + id).is(":checked");
+ const comment = utils.escapeHtml(tr.find("#comment_" + id).val());
var done = "edited";
var notDone = "editing";
@@ -370,13 +369,14 @@ function editGroup() {
utils.enableAll();
utils.showAlert("success", "fas fa-pencil-alt", "Successfully " + done + " group", oldName);
},
- error: function (jqXHR, exception) {
+ error: function (data, exception) {
+ apiFailure(data);
utils.enableAll();
utils.showAlert(
"error",
"",
"Error while " + notDone + " group with name " + oldName,
- jqXHR.responseText
+ data.responseText
);
console.log(exception); // eslint-disable-line no-console
},
diff --git a/scripts/pi-hole/js/queries.js b/scripts/pi-hole/js/queries.js
index 592a200f..8d183b68 100644
--- a/scripts/pi-hole/js/queries.js
+++ b/scripts/pi-hole/js/queries.js
@@ -489,7 +489,7 @@ $(function () {
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
"<'row'<'col-sm-5'i><'col-sm-7'p>>",
autoWidth: false,
- processing: false,
+ processing: true,
order: [[0, "desc"]],
columns: [
{
diff --git a/scripts/pi-hole/js/utils.js b/scripts/pi-hole/js/utils.js
index 5ef37eaf..82eaf3cd 100644
--- a/scripts/pi-hole/js/utils.js
+++ b/scripts/pi-hole/js/utils.js
@@ -72,7 +72,7 @@ function padNumber(num) {
return ("00" + num).substr(-2, 2);
}
-var info = null; // TODO clear this up; there shouldn't be a global var here
+var info = null;
function showAlert(type, icon, title, message) {
var opts = {};
title = " " + title + "
";
@@ -487,6 +487,43 @@ function getCSSval(cssclass, cssproperty) {
return val;
}
+function parseQueryString(queryString = window.location.search) {
+ const GETDict = {};
+ queryString
+ .substr(1)
+ .split("&")
+ .forEach(function (item) {
+ GETDict[item.split("=")[0]] = item.split("=")[1];
+ });
+
+ return GETDict;
+}
+
+// https://stackoverflow.com/q/21647928
+function hexEncode(string) {
+ var hex, i;
+
+ var result = "";
+ for (i = 0; i < string.length; i++) {
+ hex = string.codePointAt(i).toString(16);
+ result += ("000" + hex).slice(-4);
+ }
+
+ return result;
+}
+
+// https://stackoverflow.com/q/21647928
+function hexDecode(string) {
+ var j;
+ var hexes = string.match(/.{1,4}/g) || [];
+ var back = "";
+ for (j = 0; j < hexes.length; j++) {
+ back += String.fromCodePoint(parseInt(hexes[j], 16));
+ }
+
+ return back;
+}
+
window.utils = (function () {
return {
escapeHtml: escapeHtml,
@@ -517,5 +554,8 @@ window.utils = (function () {
htmlPass: htmlPass,
changeTableButtonStates: changeTableButtonStates,
getCSSval: getCSSval,
+ parseQueryString: parseQueryString,
+ hexEncode: hexEncode,
+ hexDecode: hexDecode,
};
})();
diff --git a/scripts/pi-hole/lua/sidebar.lp b/scripts/pi-hole/lua/sidebar.lp
index 31ca0ecb..b87e2d23 100644
--- a/scripts/pi-hole/lua/sidebar.lp
+++ b/scripts/pi-hole/lua/sidebar.lp
@@ -59,7 +59,7 @@
">
- Domains
+ Domains
@@ -68,7 +68,7 @@
">
- Adlists
+ Adlists