let subnetMap = {}; let subnetNotes = {}; let maxNetSize = 0; let infoColumnCount = 5 // NORMAL mode: // - Smallest subnet: /30 // - Two reserved addresses per subnet: // - Network Address (network + 0) // - Broadcast Address (last network address) // AWS mode (future): // - Smallest subnet: /28 // - Two reserved addresses per subnet: // - Network Address (network + 0) // - AWS Reserved - VPC Router // - AWS Reserved - VPC DNS // - AWS Reserved - Future Use // - Broadcast Address (last network address) let operatingMode = 'NORMAL' let noteTimeout; let minSubnetSize = 30 let inflightColor = 'NONE' let urlVersion = '1' let configVersion = '1' $('input#network,input#netsize').on('input', function() { $('#input_form')[0].classList.add('was-validated'); }) $('#color_palette div').on('click', function() { // We don't really NEED to convert this to hex, but it's really low overhead to do the // conversion here and saves us space in the export/save inflightColor = rgba2hex($(this).css('background-color')) }) $('#calcbody').on('click', '.row_address, .row_range, .row_usable, .row_hosts, .note, input', function(event) { if (inflightColor !== 'NONE') { mutate_subnet_map('color', this.dataset.subnet, '', inflightColor) // We could re-render here, but there is really no point, keep performant and just change the background color now //renderTable(); $(this).parent().css('background-color', inflightColor) } }) $('#btn_go').on('click', function() { reset(); }) $('#importBtn').on('click', function() { importConfig(JSON.parse($('#importExportArea').val())) }) $('#bottom_nav #colors_word_open').on('click', function() { $('#bottom_nav #color_palette').removeClass('d-none'); $('#bottom_nav #colors_word_close').removeClass('d-none'); $('#bottom_nav #colors_word_open').addClass('d-none'); }) $('#bottom_nav #colors_word_close').on('click', function() { $('#bottom_nav #color_palette').addClass('d-none'); $('#bottom_nav #colors_word_close').addClass('d-none'); $('#bottom_nav #colors_word_open').removeClass('d-none'); inflightColor = 'NONE' }) $('#bottom_nav #copy_url').on('click', function() { // TODO: Provide a warning here if the URL is longer than 2000 characters, probably using a modal. let url = window.location.origin + getConfigUrl() navigator.clipboard.writeText(url); $('#bottom_nav #copy_url span').text('Copied!') // Swap the text back after 3sec setTimeout(function(){ $('#bottom_nav #copy_url span').text('Copy Shareable URL') }, 2000) }) $('#btn_import_export').on('click', function() { $('#importExportArea').val(JSON.stringify(exportConfig(), null, 2)) }) function reset() { if (operatingMode === 'AWS') { minSubnetSize = 28 } else { minSubnetSize = 30 } let cidrInput = $('#network').val() + '/' + $('#netsize').val() let rootNetwork = get_network($('#network').val(), $('#netsize').val()) let rootCidr = rootNetwork + '/' + $('#netsize').val() if (cidrInput !== rootCidr) { show_warning_modal('
Your network input is not on a network boundary for this network size. It has been automatically changed:
' + $('#network').val() + ' -> ' + rootNetwork + '
') } $('#network').val(rootNetwork) subnetMap = {} subnetMap[rootCidr] = {} maxNetSize = parseInt($('#netsize').val()) renderTable(); } $('#calcbody').on('click', 'td.split,td.join', function(event) { // HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags mutate_subnet_map(this.dataset.mutateVerb, this.dataset.subnet, '') renderTable(); }) $('#calcbody').on('keyup', 'td.note input', function(event) { // HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags let delay = 1000; clearTimeout(noteTimeout); noteTimeout = setTimeout(function(element) { mutate_subnet_map('note', element.dataset.subnet, '', element.value) }, delay, this); }) $('#calcbody').on('focusout', 'td.note input', function(event) { // HTML DOM Data elements! Yay! See the `data-*` attributes of the HTML tags clearTimeout(noteTimeout); mutate_subnet_map('note', this.dataset.subnet, '', this.value) }) function renderTable() { // TODO: Validation Code $('#calcbody').empty(); let maxDepth = get_dict_max_depth(subnetMap, 0) addRowTree(subnetMap, 0, maxDepth) } function addRowTree(subnetTree, depth, maxDepth) { for (let mapKey in subnetTree) { if (mapKey.startsWith('_')) { continue; } if (has_network_sub_keys(subnetTree[mapKey])) { addRowTree(subnetTree[mapKey], depth + 1, maxDepth) } else { let subnet_split = mapKey.split('/') let notesWidth = '30%'; if ((maxDepth > 5) && (maxDepth <= 10)) { notesWidth = '25%'; } else if ((maxDepth > 10) && (maxDepth <= 15)) { notesWidth = '20%'; } else if ((maxDepth > 15) && (maxDepth <= 20)) { notesWidth = '15%'; } else if (maxDepth > 20) { notesWidth = '10%'; } addRow(subnet_split[0], parseInt(subnet_split[1]), (infoColumnCount + maxDepth - depth), (subnetTree[mapKey]['_note'] || ''), notesWidth, (subnetTree[mapKey]['_color'] || '')) } } } function addRow(network, netSize, colspan, note, notesWidth, color) { // TODO: do some checking here for smaller networks like /32, probably some edge cases to watch for. let addressFirst = ip2int(network) let addressLast = subnet_last_address(addressFirst, netSize) // Will need to adjust this for AWS mode let usableFirst = addressFirst + 1 if (operatingMode === 'AWS') { // https://docs.aws.amazon.com/vpc/latest/userguide/subnet-sizing.html usableFirst += 3 } let usableLast = addressLast - 1 let hostCount = 1 + usableLast - usableFirst let styleTag = '' if (color !== '') { styleTag = ' style="background-color: ' + color + '"' } let newRow = ' \n' + ' ' + network + '/' + netSize + '\n' + ' ' + int2ip(addressFirst) + ' - ' + int2ip(addressLast) + '\n' + ' ' + int2ip(usableFirst) + ' - ' + int2ip(usableLast) + '\n' + ' ' + hostCount + '\n' + ' \n' + ' /' + netSize + '\n' if (netSize > maxNetSize) { // This is wrong. Need to figure out a way to get the number of children so you can set rowspan and the number // of ancestors so you can set colspan. // DONE: If the subnet address (without the mask) matches a larger subnet address // in the heirarchy that is a signal to add more join buttons to that row, since they start at the top row and // via rowspan extend downward. let matchingNetworkList = get_matching_network_list(network, subnetMap).slice(1) for (const i in matchingNetworkList) { let matchingNetwork = matchingNetworkList[i] let networkChildrenCount = count_network_children(matchingNetwork, subnetMap, []) newRow += ' /' + matchingNetwork.split('/')[1] + '\n' } } newRow += ' '; $('#calcbody').append(newRow) } // Helper Functions function ip2int(ip) { return ip.split('.').reduce(function(ipInt, octet) { return (ipInt<<8) + parseInt(octet, 10)}, 0) >>> 0; } function int2ip (ipInt) { return ( (ipInt>>>24) +'.' + (ipInt>>16 & 255) +'.' + (ipInt>>8 & 255) +'.' + (ipInt & 255) ); } function subnet_last_address(subnet, netSize) { return subnet + subnet_addresses(netSize) - 1; } function subnet_addresses(netSize) { return 2**(32-netSize); } function get_dict_max_depth(dict, curDepth) { let maxDepth = curDepth for (let mapKey in dict) { if (mapKey.startsWith('_')) { continue; } let newDepth = get_dict_max_depth(dict[mapKey], curDepth + 1) if (newDepth > maxDepth) { maxDepth = newDepth } } return maxDepth } function get_join_children(subnetTree, childCount) { for (let mapKey in subnetTree) { if (mapKey.startsWith('_')) { continue; } if (has_network_sub_keys(subnetTree[mapKey])) { childCount += get_join_children(subnetTree[mapKey]) } else { return childCount } } } function has_network_sub_keys(dict) { let allKeys = Object.keys(dict) // Maybe an efficient way to do this with a Lambda? for (let i in allKeys) { if (!allKeys[i].startsWith('_')) { return true } } return false } function count_network_children(network, subnetTree, ancestryList) { // TODO: This might be able to be optimized. Ultimately it needs to count the number of keys underneath // the current key are unsplit networks (IE rows in the table, IE keys with a value of {}). let childCount = 0 for (let mapKey in subnetTree) { if (mapKey.startsWith('_')) { continue; } if (has_network_sub_keys(subnetTree[mapKey])) { childCount += count_network_children(network, subnetTree[mapKey], ancestryList.concat([mapKey])) } else { if (ancestryList.includes(network)) { childCount += 1 } } } return childCount } function get_network_children(network, subnetTree) { // TODO: This might be able to be optimized. Ultimately it needs to count the number of keys underneath // the current key are unsplit networks (IE rows in the table, IE keys with a value of {}). let subnetList = [] for (let mapKey in subnetTree) { if (mapKey.startsWith('_')) { continue; } if (has_network_sub_keys(subnetTree[mapKey])) { subnetList.push.apply(subnetList, get_network_children(network, subnetTree[mapKey])) } else { subnetList.push(mapKey) } } return subnetList } function get_matching_network_list(network, subnetTree) { let subnetList = [] for (let mapKey in subnetTree) { if (mapKey.startsWith('_')) { continue; } if (has_network_sub_keys(subnetTree[mapKey])) { subnetList.push.apply(subnetList, get_matching_network_list(network, subnetTree[mapKey])) } if (mapKey.split('/')[0] === network) { subnetList.push(mapKey) } } return subnetList } function get_consolidated_property(subnetTree, property) { let allValues = get_property_values(subnetTree, property) // https://stackoverflow.com/questions/14832603/check-if-all-values-of-array-are-equal let allValuesMatch = allValues.every( (val, i, arr) => val === arr[0] ) if (allValuesMatch) { return allValues[0] } else { return '' } } function get_property_values(subnetTree, property) { let propValues = [] for (let mapKey in subnetTree) { if (has_network_sub_keys(subnetTree[mapKey])) { propValues.push.apply(propValues, get_property_values(subnetTree[mapKey], property)) } else { // The "else" above is a bit different because it will start tracking values for subnets which are // in the hierarchy, but not displayed. Those are always blank so it messes up the value list propValues.push(subnetTree[mapKey][property] || '') } } return propValues } function get_network(networkInput, netSize) { let ipInt = ip2int(networkInput) netSize = parseInt(netSize) for (let i=31-netSize; i>=0; i--) { ipInt &= ~ 1< `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`