diff --git a/scripts/pi-hole/js/footer.js b/scripts/pi-hole/js/footer.js index 65726b0b9..98b46ef40 100644 --- a/scripts/pi-hole/js/footer.js +++ b/scripts/pi-hole/js/footer.js @@ -183,13 +183,16 @@ function testCookies() { var iCheckStyle = "primary"; function applyCheckboxRadioStyle() { - // Get all radio/checkboxes for theming, with the exception of the two radio buttons on the custom disable timer, - // as well as every element with an id that starts with "status_" + // Get all radio/checkboxes for theming, with the exception of: + // - the two radio buttons on the custom disable timer, + // - radio/checkboxes elements with class "no-icheck", + // - every element with an id that starts with "status_" var sel = $("input[type='radio'],input[type='checkbox']") .not("#selSec") .not("#selMin") .not("#expert-settings") .not("#only-changed") + .not(".no-icheck") .not("[id^=status_]"); sel.parent().removeClass(); sel.parent().addClass("icheck-" + iCheckStyle); diff --git a/scripts/pi-hole/js/settings-dns.js b/scripts/pi-hole/js/settings-dns.js index 7059fc5ed..88d6d8b83 100644 --- a/scripts/pi-hole/js/settings-dns.js +++ b/scripts/pi-hole/js/settings-dns.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 applyCheckboxRadioStyle:false, setConfigValues: false, apiFailure: false */ +/* global utils:false, applyCheckboxRadioStyle:false, setConfigValues: false, apiFailure: false */ // Remove an element from an array (inline) function removeFromArray(arr, what) { @@ -107,6 +107,260 @@ function updateDNSserversTextfield(upstreams, customServers) { ); } +function revServerDataTable() { + var setByEnv = false; + $.ajax({ + url: "/api/config/dns/revServers?detailed=true", + }).done(function (data) { + // Set the title icons if needed + setConfigValues("dns", "dns", data.config.dns); + + // disable input fields if set by env var + if (data.config.dns.revServers.flags.env_var) { + $(".revServers").prop("disabled", true); + } + }); + + $("#revServers-table").DataTable({ + ajax: { + url: "/api/config/dns/revServers", + type: "GET", + dataSrc: function (json) { + const output = []; + for (let i = 0; i < json.config.dns.revServers.length; i++) { + const cols = json.config.dns.revServers[i].split(","); + output.push({ + enabled: cols[0], + network: cols[1], + ip: cols[2], + domain: cols[3], + }); + } + + return output; + }, + }, + autoWidth: false, + columns: [ + { data: null, width: "60px" }, + { data: "network" }, + { data: "ip" }, + { data: "domain" }, + { data: null, width: "50px" }, + ], + bFilter: false, + ordering: false, + columnDefs: [ + { + targets: 0, + class: "input-checkbox text-center", + render: function (data, type, row, meta) { + const name = "enabled_" + meta.row; + const ckbox = + '"; + return ckbox; + }, + }, + { + targets: "_all", + class: "input-text", + render: function (data, type, row, meta) { + let name; + switch (meta.col) { + case 1: + name = "network_" + meta.row; + break; + case 2: + name = "ip_" + meta.row; + break; + case 3: + name = "domain_" + meta.row; + break; + // No default + } + + return ( + '" + ); + }, + }, + ], + drawCallback: function () { + $('button[id^="deleteRevServers"]').on("click", deleteRecord); + $('button[id^="editRevServers"]').on("click", editRecord); + $('button[id^="saveRevServers"]').on("click", saveRecord).hide(); + $('button[id^="cancelRevServers"]').on("click", restoreRecord).hide(); + + // Remove visible dropdown to prevent orphaning + $("body > .bootstrap-select.dropdown").remove(); + }, + rowCallback: function (row, data, displayNum, displayIndex, dataIndex) { + $(row).attr("data-index", dataIndex); + var button = ` + + + `; + $("td:eq(4)", row).html(button); + }, + dom: "<'row'<'col-sm-12'<'table-responsive'tr>>><'row'<'col-sm-12'i>>", + paging: false, + language: { + emptyTable: "No revese DNS servers defined.", + }, + stateSave: true, + stateDuration: 0, + processing: true, + stateSaveCallback: function (settings, data) { + utils.stateSaveCallback("revServers-records-table", data); + }, + stateLoadCallback: function () { + var data = utils.stateLoadCallback("revServers-records-table"); + // Return if not available + if (data === null) { + return null; + } + + // Apply loaded state to table + return data; + }, + }); +} + +function editRecord() { + // Enable fields on the selected row + $(this).closest("tr").find("td input").prop("disabled", false); + + // Hide EDIT and DELETE buttons. Show SAVE and UNDO buttons + $(this).hide(); + $(this).siblings('[id^="delete"]').hide(); + $(this).siblings('[id^="save"]').show(); + $(this).siblings('[id^="cancel"]').show(); +} + +function saveRecord() { + // Find the row index + const index = $(this).attr("data-index"); + + // Get the edited values from each field + const values = []; + values[0] = $("#enabled_" + index).prop("checked") ? "true" : "false"; + values[1] = $("#network_" + index).val(); + values[2] = $("#ip_" + index).val(); + values[3] = $("#domain_" + index).val(); + + // Save the new values + // --- insert $.ajax() call to actually save the data + console.log(values.join(",")); // eslint-disable-line no-console + + // Finish the edition disabling the fields + $(this).closest("tr").find("td input").prop("disabled", true); + + // Show EDIT and DELETE buttons. Hide SAVE and UNDO buttons + $(this).siblings('[id^="edit"]').show(); + $(this).siblings('[id^="delete"]').show(); + $(this).hide(); + $(this).siblings('[id^="cancel"]').hide(); +} + +function restoreRecord() { + // Find the row index + const index = $(this).attr("data-index"); + + // Reset values + $("#enabled_" + index).prop("checked", $("#enabled_" + index).attr("data-initial-value")); + $("#network_" + index).val($("#network_" + index).attr("data-initial-value")); + $("#ip_" + index).val($("#ip_" + index).attr("data-initial-value")); + $("#domain_" + index).val($("#domain_" + index).attr("data-initial-value")); + + // Show cancellation message + utils.showAlert("info", "fas fa-undo", "Operation canceled", "Original values restored"); + + // Finish the edition disabling the fields + $(this).closest("tr").find("td input").prop("disabled", true); + + // Show EDIT and DELETE buttons. Hide SAVE and UNDO buttons + $(this).siblings('[id^="edit"]').show(); + $(this).siblings('[id^="delete"]').show(); + $(this).siblings('[id^="save"]').hide(); + $(this).hide(); +} + +function deleteRecord() { + // Get the tags + var tags = [$(this).attr("data-tag")]; + + // Check input validity + if (!Array.isArray(tags)) return; + + // Exploit prevention: Return early for non-numeric IDs + for (var tag in tags) { + if (Object.hasOwnProperty.call(tags, tag)) { + delRevServers(tags); + } + } +} + +function delRevServers(elem) { + utils.disableAll(); + utils.showAlert("info", "", "Deleting reverse server...", elem); + const url = "/api/config/dns/revServers/" + encodeURIComponent(elem); + + $.ajax({ + url: url, + method: "DELETE", + }) + .done(function () { + utils.enableAll(); + utils.showAlert("success", "fas fa-trash-alt", "Successfully deleted reverse server", elem); + $("#revServers-table").DataTable().ajax.reload(null, false); + }) + .fail(function (data, exception) { + utils.enableAll(); + apiFailure(data); + utils.showAlert( + "error", + "", + "Error while deleting DNS record: " + elem + "", + data.responseText + ); + console.log(exception); // eslint-disable-line no-console + }); +} + function processDNSConfig() { $.ajax({ url: "/api/config/dns?detailed=true", // We need the detailed output to get the DNS server list @@ -123,4 +377,35 @@ function processDNSConfig() { $(document).ready(function () { processDNSConfig(); + revServerDataTable(); + + // Button to add a new reverse server + $("#btnAdd-revServers").on("click", function () { + utils.disableAll(); + var items = []; + items[0] = $("#enabled-revServers").is(":checked") ? "true" : "false"; + items[1] = $("#network-revServers").val(); + items[2] = $("#server-revServers").val(); + items[3] = $("#domain-revServers").val(); + const elem = items.join(","); + const url = "/api/config/dns/revServers/" + encodeURIComponent(elem); + utils.showAlert("info", "", "Adding reverse server...", elem); + $.ajax({ + url: url, + method: "PUT", + }) + .done(function () { + utils.enableAll(); + utils.showAlert("success", "fas fa-plus", "Successfully added reverse server", elem); + $("#revServers-table tfoot .form-control").val(""); + $("#enabled-revServers").prop("checked", true); + $("#revServers-table").DataTable().ajax.reload(null, false); + }) + .fail(function (data, exception) { + utils.enableAll(); + apiFailure(data); + utils.showAlert("error", "", "Error while deleting reverse server", data.responseText); + console.log(exception); // eslint-disable-line no-console + }); + }); }); diff --git a/settings-dns.lp b/settings-dns.lp index 49e4b6f72..3081b1b5a 100644 --- a/settings-dns.lp +++ b/settings-dns.lp @@ -225,8 +225,45 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r') in your DHCP server for this to work. You can likely find it within the DHCP settings.

Enabling Conditional Forwarding will also forward all hostnames (i.e., non-FQDNs) to the router when "Never forward non-FQDNs" is not enabled.

-

The following list contains all reverse servers you want to add. The expected format is one server per line in form of <enabled>,<ip-address>[/<prefix-len>],<server>[#<port>][,<domain>]. A valid config line could look like true,192.168.0.0/24,192.168.0.1,fritz.box

- +

The following list contains all reverse servers you want to add.

+ + + + + + + + + + + + + + + + + + + + + + + +
EnabledNetwork Range 1Server 2Domain 3
+ + + + + + + + + +
+
1: Local network range using CIDR notation
+
2: IP address of your DHCP server (or router)
+
3: Local domain name (optional)
+
diff --git a/style/pi-hole.css b/style/pi-hole.css index f457c342a..ff52b0e4f 100644 --- a/style/pi-hole.css +++ b/style/pi-hole.css @@ -388,7 +388,8 @@ td.lookatme { } /* Table footer row used to add new items, using inline input fields */ -tfoot.add-new-item > tr > th { +#revServers-table td, +#revServers-table tfoot > tr > th { font-weight: normal; vertical-align: inherit; }