/* * pfSenseHelpers.js * * part of pfSense (https://www.pfsense.org) * Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate) * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // These helper functions are used on many/most UI pages to hide/show/disable/enable form elements where required // Cause the input to be displayed as a required field by adding the element-required class to the label function setRequired(id, req) { if (req) $('#' + id).parent().parent('div').find('span:first').addClass('element-required'); else $('#' + id).parent().parent('div').find('span:first').removeClass('element-required'); } // Hides the
in which the specified input element lives so that the input, its label and help text are hidden function hideInput(id, hide) { if (hide) $('#' + id).parent().parent('div').addClass('hidden'); else $('#' + id).parent().parent('div').removeClass('hidden'); } // Hides the
in which the specified group input element lives so that the input, // its label and help text are hidden function hideGroupInput(id, hide) { if (hide) $('#' + id).parent('div').addClass('hidden'); else $('#' + id).parent('div').removeClass('hidden'); } // Hides the
in which the specified checkbox lives so that the checkbox, its label and help text are hidden function hideCheckbox(id, hide) { if (hide) $('#' + id).parent().parent().parent('div').addClass('hidden'); else $('#' + id).parent().parent().parent('div').removeClass('hidden'); } // Disables the specified input element function disableInput(id, disable) { $('#' + id).prop("disabled", disable); } // Hides all elements of the specified class. This will usually be a section function hideClass(s_class, hide) { if (hide) $('.' + s_class).hide(); else $('.' + s_class).show(); } function hideSelect(id, hide) { if (hide) $('#' + id).parent('div').parent('div').addClass('hidden'); else $('#' + id).parent('div').parent('div').removeClass('hidden'); } function hideMultiCheckbox(id, hide) { if (hide) $("[name=" + id + "]").parent().addClass('hidden'); else $("[name=" + id + "]").parent().removeClass('hidden'); } // Hides the
in which the specified IP address element lives so that the input, any mask selector, its label and help text are hidden function hideIpAddress(id, hide) { if (hide) $('#' + id).parent().parent().parent('div').addClass('hidden'); else $('#' + id).parent().parent().parent('div').removeClass('hidden'); } // Hides all elements of the specified class belonging to a multiselect. function hideMultiClass(s_class, hide) { if (hide) $('.' + s_class).parent().parent().hide(); else $('.' + s_class).parent().parent().show(); } // Hides div whose label contains the specified text. (Good for StaticText) function hideLabel(text, hide) { var element = $('label:contains(' + text + ')'); if (hide) element.parent('div').addClass('hidden'); else element.parent('div').removeClass('hidden'); } // Hides the '/' and the subnet mask of an Ip_Address/subnet_mask group function hideMask(name, hide) { if (hide) { $('[id^=' + name + ']').hide(); $('[id^=' + name + ']').prev('span').hide(); $('[id^=' + name + ']').parent('div').removeClass('input-group'); } else { $('[id^=' + name + ']').show(); $('[id^=' + name + ']').prev('span').show(); $('[id^=' + name + ']').parent('div').addClass('input-group'); } } // Set the help text for a given input function setHelpText(id, text) { $('#' + id).parent().parent('div').find('span:nth-child(2)').html(text); } // Toggle table row checkboxes and background colors on the pages that use sortable tables: // /usr/local/www/firewall_nat.php // /usr/local/www/firewall_nat_1to1.php // /usr/local/www/firewall_nat_out.php // /usr/local/www/firewall_rules.php // /usr/local/www/vpn_ipsec.php // Striping of the tables is handled here, NOT with the Bootstrap table-striped class because it would // get confused when rows are sorted or deleted. function fr_toggle(id, prefix) { if (!prefix) prefix = 'fr'; var checkbox = document.getElementById(prefix + 'c' + id); checkbox.checked = !checkbox.checked; fr_bgcolor(id, prefix); } // Change background color of selected row based on state of checkbox function fr_bgcolor(id, prefix) { if (!prefix) prefix = 'fr'; var row = $('#' + prefix + id); if ($('#' + prefix + 'c' + id).prop('checked') ) { row.addClass('active'); } else { row.removeClass('active'); } } // The following functions are used by Form_Groups assigned a class of "repeatable" and provide the ability // to add/delete rows of sequentially numbered elements, their labels and their help text // See firewall_aliases_edit.php for an example // NOTE: retainhelp is a global var that when defined prevents any help text from being deleted as lines are inserted. // IOW it causes every row to have help text, not just the last row function setMasks() { // Find all ipaddress masks and make dynamic based on address family of input $('span.pfIpMask + select').each(function (idx, select){ var input = $(select).prevAll('input[type=text]'); input.on('change', function(e){ var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128; if (!isV6) max = 32; if (input.val() == "") return; while (select.options.length > max) select.remove(0); if (select.options.length < max) { for (var i=select.options.length; i<=max; i++) select.options.add(new Option(i, i), 0); } }); // Fire immediately input.change(); }); } // Complicated function to move all help text associated with this input id to the same id // on the row above. That way if you delete the last row, you don't lose the help function moveHelpText(id) { $('#' + id).parent('div').parent('div').find('input, select, checkbox, button').each(function() { // For each var fromId = this.id; var toId = decrStringInt(fromId); var helpSpan; if (!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) { if ($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) { helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone(); } else { helpSpan = $('#' + fromId).parent('div').find('span:last').clone(); } if ($(helpSpan).hasClass('help-block')) { if ($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) { $('#' + decrStringInt(fromId)).parent('div').after(helpSpan); } else { $('#' + decrStringInt(fromId)).after(helpSpan); } } } }); } // Increment the number at the end of the string function bumpStringInt( str ) { var data = str.match(/(\D*)(\d+)(\D*)/), newStr = ""; if (data) newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ]; return newStr || str; } // Decrement the number at the end of the string function decrStringInt( str ) { var data = str.match(/(\D*)(\d+)(\D*)/), newStr = ""; if (data) newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ]; return newStr || str; } // Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about // gaps, but I do :) function renumber() { var idx = 0; $('.repeatable').each(function() { $(this).find('input').each(function() { $(this).prop("id", this.id.replace(/\d+$/, "") + idx); $(this).prop("name", this.name.replace(/\d+$/, "") + idx); }); $(this).find('select').each(function() { $(this).prop("id", this.id.replace(/\d+$/, "") + idx); $(this).prop("name", this.name.replace(/\d+$/, "") + idx); }); $(this).find('button').each(function() { $(this).prop("id", this.id.replace(/\d+$/, "") + idx); $(this).prop("name", this.name.replace(/\d+$/, "") + idx); }); // $(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx); idx++; }); } function delete_row(rowDelBtn) { var rowLabel; // If we are deleting row zero, we need to save/restore the label if ( (rowDelBtn == "deleterow0") && ((typeof retainhelp) == "undefined")) { rowLabel = $('#' + rowDelBtn).parent('div').parent('div').find('label').text(); } $('#' + rowDelBtn).parent('div').parent('div').remove(); renumber(); checkLastRow(); if (rowDelBtn == "deleterow0") { $('#' + rowDelBtn).parent('div').parent('div').find('label').text(rowLabel); } } function checkLastRow() { if ($('.repeatable').length <= 1) { $('#deleterow0').hide(); } else { $('[id^=deleterow]').show(); } } function add_row() { // Find the last repeatable group var lastRepeatableGroup = $('.repeatable:last'); // If the number of repeats exceeds the maximum, do not add another clone if ($('.repeatable').length >= lastRepeatableGroup.attr('max_repeats')) { // Alert user if alert message is specified if (typeof lastRepeatableGroup.attr('max_repeats_alert') !== 'undefined') { alert(lastRepeatableGroup.attr('max_repeats_alert')); } return; } // Clone it var newGroup = lastRepeatableGroup.clone(); // Increment the suffix number for each input element in the new group $(newGroup).find('input').each(function() { $(this).prop("id", bumpStringInt(this.id)); $(this).prop("name", bumpStringInt(this.name)); if (!$(this).is('[id^=delete]')) $(this).val(''); }); // Increment the suffix number for the deleterow button element in the new group $(newGroup).find('[id^=deleterow]').each(function() { $(this).prop("id", bumpStringInt(this.id)); $(this).prop("name", bumpStringInt(this.name)); }); // Do the same for selectors $(newGroup).find('select').each(function() { $(this).prop("id", bumpStringInt(this.id)); $(this).prop("name", bumpStringInt(this.name)); // If this selector lists mask bits, we need it to be reset to all 128 options // and no items selected, so that automatic v4/v6 selection still works if ($(this).is('[id^=address_subnet]')) { $(this).empty(); for (idx=128; idx>0; idx--) { $(this).append($('