/* 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, apiFailure:false*/ "use strict"; $(() => { // Handle hiding of alerts $("[data-hide]").on("click", function () { $(this) .closest("." + $(this).attr("data-hide")) .hide(); }); // Handle saving of settings $(".save-button").on("click", () => { saveSettings(); }); }); // Globally available function to set config values // eslint-disable-next-line no-unused-vars function setConfigValues(topic, key, value) { // If the value is an object, we need to recurse if (!("description" in value)) { for (const [subkey, subvalue] of Object.entries(value)) { // If the key is empty, we are at the top level const newKey = key === "" ? subkey : key + "." + subkey; setConfigValues(topic, newKey, subvalue); } return; } // else: we have a setting we can set const escapedKey = key.replaceAll(".", "\\."); const envTitle = $(`[data-configkeys~='${key}']`); if ( envTitle.parents().parents().hasClass("settings-level-expert") && envTitle.find(".expert-warning").length === 0 ) { envTitle.append( `  ` ); } if (value.flags.restart_dnsmasq && envTitle.find(".restart-warning").length === 0) { envTitle.append( `  ` ); } if (value.flags.env_var) { // If this setting has been set by environment variable, display a padlock in the section title if (envTitle.find(".env-warning").length === 0) { envTitle.append( `  ` ); } $(`#${escapedKey}`).prop("disabled", "disabled"); } switch (value.type) { case "enum (unsigned integer)": // fallthrough case "enum (string)": { // Remove all options from select $("#" + escapedKey + " option").remove(); // Add allowed select items (if available) for (const allowedValue of value.allowed) { $("#" + escapedKey + "-" + allowedValue.item).prop("disabled", value.flags.env_var); const newopt = $("") .attr("value", allowedValue.item) .text(allowedValue.description); $("#" + escapedKey).append(newopt); } // Select the current value $("#" + escapedKey) .val(value.value) .trigger("click"); // Also select matching radio button (if any) $("#" + escapedKey + "-" + value.value).prop("checked", true); break; } case "boolean": { // Select checkboxes (if available) $("#" + escapedKey).prop("checked", value.value); break; } case "string array": { // Set input field values from array (if available) $("#" + escapedKey).val(value.value.join("\n")); break; } default: { // Set input field values (if available) // Set text if this is a or element if ($("#" + escapedKey).is("span") || $("#" + escapedKey).is("code")) { $("#" + escapedKey).text(value.value); } else { // Set value if this is an element $("#" + escapedKey).val(value.value); } } } } function saveSettings() { const settings = {}; utils.disableAll(); $("[data-key]").each(function () { const key = $(this).data("key"); let value = $(this).val(); // If this is a checkbox, use the checked state if ($(this).is(":checkbox")) { value = $(this).is(":checked"); } // If this is a radio button, skip all but the checked one if ($(this).is(":radio") && !$(this).is(":checked")) return; // If this is a string array, split the value into an array if ($(this).is("textarea")) { value = $(this).val(); value = value === "" ? [] : value.split("\n"); } // If this is an integer number, parse it accordingly if ($(this).data("type") === "integer") { value = Number.parseInt(value, 10); } // If this is a floating point value, parse it accordingly if ($(this).data("type") === "float") { value = Number.parseFloat(value); } // Build deep object // Transform "foo.bar.baz" into {foo: {bar: {baz: value}}} const parts = key.split("."); const obj = {}; let tmp = obj; for (let i = 0; i < parts.length - 1; i++) { tmp[parts[i]] = {}; tmp = tmp[parts[i]]; } tmp[parts.at(-1)] = value; // Merge deep object into settings $.extend(true, settings, obj); }); // Apply changes $.ajax({ url: document.body.dataset.apiurl + "/config", method: "PATCH", dataType: "json", processData: false, data: JSON.stringify({ config: settings }), contentType: "application/json; charset=utf-8", }) .done(() => { utils.enableAll(); // Success utils.showAlert( "success", "fa-solid fa-fw fa-floppy-disk", "Successfully saved and applied settings", "" ); // Show loading overlay utils.loadingOverlay(true); }) .fail((data, exception) => { utils.enableAll(); utils.showAlert("error", "", "Error while applying settings", data.responseText); console.log(exception); // eslint-disable-line no-console apiFailure(data); }); }