/* 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. * Precord see LICENSE file for your rights under this license. */ /* global utils: false, apiFailure:false, setConfigValues: false */ "use strict"; globalThis.toasts ??= {}; function hostsDomain(data) { // Split record in format IP NAME1 [NAME2 [NAME3 [NAME...]]] // We split both on spaces and tabs to support both formats // Also, we remove any comments after the name(s) const name = data .split(/[\t ]+/u) .slice(1) .join(" ") .split("#", 1)[0] .trim(); return name; } function hostsIP(data) { // Split record in format IP NAME1 [NAME2 [NAME3 [NAME...]]] // We split both on spaces and tabs to support both formats const ip = data.split(/[\t ]+/u, 1)[0].trim(); return ip; } function CNAMEdomain(data) { // Split record in format ,[,] const CNAMEarr = data.split(","); return CNAMEarr[0].trim(); } function CNAMEtarget(data) { // Split record in format ,[,] const CNAMEarr = data.split(","); return CNAMEarr[1].trim(); } function CNAMEttl(data) { // Split record in format ,[,] const CNAMEarr = data.split(","); return CNAMEarr.length > 2 ? CNAMEarr[2] : "-"; } function populateDataTable(endpoint) { let columns = ""; if (endpoint === "hosts") { columns = [ { data: null, render: hostsDomain }, { data: null, type: "ip-address", render: hostsIP }, { data: null, width: "22px", orderable: false }, ]; } else { columns = [ { data: null, render: CNAMEdomain }, { data: null, render: CNAMEtarget }, { data: null, width: "40px", render: CNAMEttl }, { data: null, width: "22px", orderable: false }, ]; } const setByEnv = false; $.ajax({ url: document.body.dataset.apiurl + "/config/dns/" + endpoint + "?detailed=true", }).done(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[endpoint].flags.env_var) { $(`.${endpoint}`).prop("disabled", true); } }); $(`#${endpoint}-Table`).DataTable({ ajax: { url: document.body.dataset.apiurl + "/config/dns/" + endpoint, type: "GET", dataSrc: `config.dns.${endpoint}`, }, autoWidth: false, columns, columnDefs: [ { targets: "_all", render: $.fn.dataTable.render.text(), }, ], drawCallback() { $(`button[id^="delete${endpoint}"]`).on("click", deleteRecord); }, rowCallback(row, data) { $(row).attr("data-id", data); // Create delete button const button = document.createElement("button"); // Set button ID and add CSS classes button.id = `delete${endpoint}${utils.hexEncode(data)}`; button.classList.add("btn", "btn-danger", "btn-xs"); // Set data-* attributes button.dataset.type = endpoint; button.dataset.tag = data; // Disable the button if set by environment variables button.disabled = setByEnv; // Add a trash icon to the button const iconSpan = document.createElement("span"); iconSpan.classList.add("far", "fa-trash-alt"); button.append(iconSpan); // Add the button to the table row $(`td:eq(${endpoint === "hosts" ? 2 : 3})`, row).html(button); }, dom: "<'row'<'col-sm-5'l><'col-sm-7'f>>" + "<'row'<'col-sm-12'p>>" + "<'row'<'col-sm-12'<'table-responsive'tr>>>" + "<'row'<'col-sm-12'p>>" + "<'row'<'col-sm-12'i>>", lengthMenu: [ [10, 25, 50, 100, -1], [10, 25, 50, 100, "All"], ], language: { emptyTable() { return endpoint === "hosts" ? "No local DNS records defined." : "No local CNAME records defined."; }, }, stateSave: true, stateDuration: 0, processing: true, stateSaveCallback(settings, data) { utils.stateSaveCallback(`${endpoint}-records-table`, data); }, stateLoadCallback() { const data = utils.stateLoadCallback(`${endpoint}-records-table`); // Return if not available if (data === null) { return null; } // Apply loaded state to table return data; }, }); } function deleteRecord() { if ($(this).attr("data-type") === "hosts") { delHosts($(this).attr("data-tag")); } else { delCNAME($(this).attr("data-tag")); } } function delHosts(elem) { utils.disableAll(); globalThis.toasts.delHosts = utils.showAlert("info", "", "Deleting DNS record...", elem, null); const url = document.body.dataset.apiurl + "/config/dns/hosts/" + encodeURIComponent(elem); $.ajax({ url, method: "DELETE", }) .done(() => { utils.enableAll(); utils.showAlert( "success", "fas fa-trash-alt", "Successfully deleted DNS record", elem, globalThis.toasts.delHosts ); $("#hosts-Table").DataTable().ajax.reload(null, false); }) .fail((data, exception) => { utils.enableAll(); apiFailure(data); utils.showAlert( "error", "", "Error while deleting DNS record: " + elem + "", data.responseText, globalThis.toasts.delHosts ); console.log(exception); // eslint-disable-line no-console }); } function delCNAME(elem) { utils.disableAll(); globalThis.toasts.delCNAME = utils.showAlert( "info", "", "Deleting local CNAME record...", elem, null ); const url = document.body.dataset.apiurl + "/config/dns/cnameRecords/" + encodeURIComponent(elem); $.ajax({ url, method: "DELETE", }) .done(() => { utils.enableAll(); utils.showAlert( "success", "fas fa-trash-alt", "Successfully deleted local CNAME record", elem, globalThis.toasts.delCNAME ); // Show loading overlay utils.loadingOverlay(true); $("#cnameRecords-Table").DataTable().ajax.reload(null, false); }) .fail((data, exception) => { utils.enableAll(); apiFailure(data); utils.showAlert( "error", "", "Error while deleting CNAME record: " + elem + "", data.responseText, globalThis.toasts.delCNAME ); console.log(exception); // eslint-disable-line no-console }); } $(() => { populateDataTable("hosts"); populateDataTable("cnameRecords"); $("#btnAdd-host").on("click", () => { utils.disableAll(); const elem = $("#Hip").val().trim() + " " + $("#Hdomain").val().trim(); const url = document.body.dataset.apiurl + "/config/dns/hosts/" + encodeURIComponent(elem); globalThis.toasts.addHosts = utils.showAlert("info", "", "Adding DNS record...", elem, null); $.ajax({ url, method: "PUT", }) .done(() => { utils.enableAll(); utils.showAlert( "success", "fas fa-plus", "Successfully added DNS record", elem, globalThis.toasts.addHosts ); $("#Hdomain").val(""); $("#Hip").val(""); $("#hosts-Table").DataTable().ajax.reload(null, false); }) .fail((data, exception) => { utils.enableAll(); apiFailure(data); utils.showAlert( "error", "", "Error while adding DNS record", data.responseText, globalThis.toasts.addHosts ); console.log(exception); // eslint-disable-line no-console }); }); $("#btnAdd-cname").on("click", () => { utils.disableAll(); let elem = $("#Cdomain").val().trim() + "," + $("#Ctarget").val().trim(); const ttlInput = $("#Cttl").val(); const ttlVal = ttlInput === "" ? NaN : Math.trunc(ttlInput); if (Number.isFinite(ttlVal) && ttlVal >= 0) { elem += "," + ttlVal; } const url = document.body.dataset.apiurl + "/config/dns/cnameRecords/" + encodeURIComponent(elem); globalThis.toasts.addCNAME = utils.showAlert("info", "", "Adding DNS record...", elem, null); $.ajax({ url, method: "PUT", }) .done(() => { utils.enableAll(); utils.showAlert( "success", "fas fa-plus", "Successfully added CNAME record", elem, globalThis.toasts.addCNAME ); // Show loading overlay utils.loadingOverlay(true); $("#Cdomain").val(""); $("#Ctarget").val(""); $("#cnameRecords-Table").DataTable().ajax.reload(null, false); }) .fail((data, exception) => { utils.enableAll(); apiFailure(data); utils.showAlert( "error", "", "Error while adding CNAME record", data.responseText, globalThis.toasts.addCNAME ); console.log(exception); // eslint-disable-line no-console }); }); // Add a small legend below the CNAME table $("#cnameRecords-Table").after( "* TTL in seconds (optional)" ); });