import {revive, persist, labelInput, labelSelect} from '/js/util.js'; export default { id:'subnet', name:'IP Subnet Calculator', about:'Calculate IPv4 and IPv6 subnet information, CIDR notation, and network ranges.', render(root){ const key='calc_subnet_v1'; const s = revive(key,{ ipVersion: 'ipv4', ipAddress: '192.168.1.0', subnetMask: '255.255.255.0', cidr: 24, customCidr: 24 }); const ui = document.createElement('div'); // IP version selector const versionSection = document.createElement('div'); versionSection.innerHTML = `

IP Version

`; // IPv4 input section const ipv4Section = document.createElement('div'); ipv4Section.innerHTML = `

IPv4 Configuration

/
`; // IPv6 input section const ipv6Section = document.createElement('div'); ipv6Section.id = 'ipv6-inputs'; ipv6Section.style.display = 'none'; // IPv6 title const ipv6Title = document.createElement('h3'); ipv6Title.style.cssText = 'color: var(--accent); margin-bottom: 15px;'; ipv6Title.textContent = 'IPv6 Configuration'; ipv6Section.appendChild(ipv6Title); // IPv6 Address input container const ipv6AddressContainer = document.createElement('div'); ipv6AddressContainer.style.cssText = 'margin-bottom: 20px;'; const ipv6AddressLabel = document.createElement('label'); ipv6AddressLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500; color: var(--text);'; ipv6AddressLabel.textContent = 'IPv6 Address'; const ipv6AddressInput = document.createElement('input'); ipv6AddressInput.type = 'text'; ipv6AddressInput.name = 'ipv6Address'; ipv6AddressInput.value = '2001:db8::'; ipv6AddressInput.placeholder = '2001:db8::'; ipv6AddressInput.style.cssText = 'width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;'; ipv6AddressContainer.appendChild(ipv6AddressLabel); ipv6AddressContainer.appendChild(ipv6AddressInput); // IPv6 CIDR input container const ipv6CidrContainer = document.createElement('div'); ipv6CidrContainer.style.cssText = 'margin-bottom: 20px;'; const ipv6CidrLabel = document.createElement('label'); ipv6CidrLabel.style.cssText = 'display: block; margin-bottom: 8px; font-weight: 500; color: var(--text);'; ipv6CidrLabel.textContent = 'CIDR Prefix Length'; const ipv6CidrInput = document.createElement('input'); ipv6CidrInput.type = 'number'; ipv6CidrInput.name = 'ipv6Cidr'; ipv6CidrInput.value = '64'; ipv6CidrInput.min = '0'; ipv6CidrInput.max = '128'; ipv6CidrInput.style.cssText = 'width: 200px; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 16px;'; const ipv6CidrSpan = document.createElement('span'); ipv6CidrSpan.style.cssText = 'color: var(--muted); margin-left: 10px;'; ipv6CidrSpan.textContent = '/'; ipv6CidrContainer.appendChild(ipv6CidrLabel); ipv6CidrContainer.appendChild(ipv6CidrInput); ipv6CidrContainer.appendChild(ipv6CidrSpan); // Add all elements to IPv6 section ipv6Section.appendChild(ipv6AddressContainer); ipv6Section.appendChild(ipv6CidrContainer); ui.append(versionSection, ipv4Section, ipv6Section); // Results section const out = document.createElement('div'); out.className = 'result'; out.style.cssText = ` margin: 20px 0; padding: 15px; background: var(--k-bg); border-radius: 8px; border-left: 4px solid var(--accent); `; ui.append(out); // Utility functions function getNetworkClass(ip) { const firstOctet = parseInt(ip.split('.')[0]); if (firstOctet >= 0 && firstOctet <= 127) return 'A'; if (firstOctet >= 128 && firstOctet <= 191) return 'B'; if (firstOctet >= 192 && firstOctet <= 223) return 'C'; if (firstOctet >= 224 && firstOctet <= 239) return 'D'; if (firstOctet >= 240 && firstOctet <= 255) return 'E'; return 'Unknown'; } function ipToLong(ip) { return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet), 0) >>> 0; } function longToIp(long) { return [ (long >>> 24) & 255, (long >>> 16) & 255, (long >>> 8) & 255, long & 255 ].join('.'); } function cidrToMask(cidr) { // Handle edge cases if (cidr === 0) return '0.0.0.0'; if (cidr === 32) return '255.255.255.255'; // For other CIDR values, use the bit manipulation approach // But handle the 32-bit overflow issue let mask; if (cidr === 31) { // Special case for /31 to avoid overflow mask = 0xFFFFFFFE; } else { // Calculate mask: create a number with 'cidr' leading 1s mask = 0; for (let i = 31; i >= (32 - cidr); i--) { mask |= (1 << i); } } return longToIp(mask); } function maskToCidr(mask) { const maskLong = ipToLong(mask); // Handle edge cases if (maskLong === 0) return 0; // 0.0.0.0 = /0 if (maskLong === 0xFFFFFFFF) return 32; // 255.255.255.255 = /32 // Count leading 1s in binary representation let cidr = 0; let temp = maskLong; // Count consecutive 1s from left to right for (let i = 31; i >= 0; i--) { if ((temp & (1 << i)) !== 0) { cidr++; } else { break; // Stop at first 0 } } return cidr; } function validateIPv4(ip) { // Handle CIDR notation by extracting just the IP part const ipPart = ip.includes('/') ? ip.split('/')[0] : ip; const parts = ipPart.split('.'); if (parts.length !== 4) return false; return parts.every(part => { const num = parseInt(part); return num >= 0 && num <= 255 && part === num.toString(); }); } function validateSubnetMask(mask) { if (!validateIPv4(mask)) return false; // Check that it's a valid subnet mask (consecutive 1s followed by 0s) const maskLong = ipToLong(mask); const binary = maskLong.toString(2).padStart(32, '0'); // Find the first 0 const firstZero = binary.indexOf('0'); // Special case: 255.255.255.255 (/32) is valid if (firstZero === -1) return true; // All 1s (255.255.255.255) is valid for /32 // Check that all bits after first 0 are also 0 return binary.substring(firstZero).indexOf('1') === -1; } function validateIPv6(ip) { // Proper IPv6 validation if (!ip || typeof ip !== 'string') return false; // Check for basic IPv6 format (contains colons) if (!ip.includes(':')) return false; // Split by double colon and validate each part const parts = ip.split('::'); if (parts.length > 2) return false; // Only one double colon allowed // Validate each part for (let part of parts) { if (part === '') continue; // Empty part is allowed for :: notation const segments = part.split(':'); for (let segment of segments) { if (segment === '') continue; // Empty segment is allowed // Each segment should be 1-4 hex characters if (!/^[0-9a-fA-F]{1,4}$/.test(segment)) { return false; } } } return true; } function expandIPv6(ip) { // Expand compressed IPv6 address const parts = ip.split('::'); if (parts.length === 1) return ip; const left = parts[0].split(':'); const right = parts[1] ? parts[1].split(':') : []; const missing = 8 - left.length - right.length; const expanded = [...left]; for (let i = 0; i < missing; i++) { expanded.push('0000'); } expanded.push(...right); return expanded.join(':'); } function compressIPv6(ip) { // Compress IPv6 address to shortest possible form const parts = ip.split(':'); // Find the longest sequence of zeros let longestZeroStart = -1; let longestZeroLength = 0; let currentZeroStart = -1; let currentZeroLength = 0; for (let i = 0; i < parts.length; i++) { if (parts[i] === '0000' || parts[i] === '0') { if (currentZeroStart === -1) { currentZeroStart = i; currentZeroLength = 1; } else { currentZeroLength++; } } else { if (currentZeroLength > longestZeroLength) { longestZeroStart = currentZeroStart; longestZeroLength = currentZeroLength; } currentZeroStart = -1; currentZeroLength = 0; } } // Check if the last sequence is the longest if (currentZeroLength > longestZeroLength) { longestZeroStart = currentZeroStart; longestZeroLength = currentZeroLength; } // Only compress if we have at least 2 consecutive zeros if (longestZeroLength >= 2) { const left = parts.slice(0, longestZeroStart); const right = parts.slice(longestZeroStart + longestZeroLength); // Remove leading zeros from each part const leftCompressed = left.map(part => { const num = parseInt(part, 16); return num.toString(16); }); const rightCompressed = right.map(part => { const num = parseInt(part, 16); return num.toString(16); }); // Handle edge cases for proper :: placement if (leftCompressed.length === 0 && rightCompressed.length === 0) { return '::'; } else if (leftCompressed.length === 0) { return '::' + rightCompressed.join(':'); } else if (rightCompressed.length === 0) { return leftCompressed.join(':') + '::'; } else { return [...leftCompressed, '', ...rightCompressed].join(':'); } } else { // No compression needed, just remove leading zeros return parts.map(part => { const num = parseInt(part, 16); return num.toString(16); }).join(':'); } } function ipv6ToLong(ip) { const expanded = expandIPv6(ip); const parts = expanded.split(':'); let result = 0n; // IPv6 addresses are big-endian (most significant byte first) // Each part is a 16-bit hex value for (let i = 0; i < 8; i++) { const part = parseInt(parts[i], 16); result = (result << 16n) + BigInt(part); } return result; } function longToIPv6(long) { const parts = []; // Extract each 16-bit segment in big-endian order // Start from the most significant bits (left side) for (let i = 7; i >= 0; i--) { const part = Number((long >> BigInt(i * 16)) & 0xFFFFn); parts.push(part.toString(16).padStart(4, '0')); } return parts.join(':'); } function generateAvailableNetworks(baseIP, cidr) { const networks = []; const networkSize = Math.pow(2, 32 - cidr); const baseLong = ipToLong(baseIP); // Calculate the base network for the IP (up to /16 level) // For example: 192.168.1.0 -> 192.168.0.0 (base /16 network) const baseNetworkLong = baseLong & ((0xFFFFFFFF << (32 - 16)) >>> 0); const networkLong = baseNetworkLong; // Show up to 64 networks const count = Math.min(64, Math.floor(65536 / networkSize)); for (let i = 0; i < count; i++) { const networkAddr = networkLong + (i * networkSize); const broadcastAddr = networkAddr + networkSize - 1; const firstHost = networkAddr + 1; const lastHost = broadcastAddr - 1; networks.push({ network: longToIp(networkAddr), firstHost: longToIp(firstHost), lastHost: longToIp(lastHost), broadcast: longToIp(broadcastAddr) }); } return networks; } function calculateIPv4() { const ipAddress = ui.querySelector('[name=ipAddress]').value; const subnetMask = ui.querySelector('[name=subnetMask]').value; const cidr = +ui.querySelector('[name=cidr]').value; if (!validateIPv4(ipAddress)) { out.innerHTML = `
Error: Invalid IPv4 address format
`; return; } if (!validateSubnetMask(subnetMask)) { out.innerHTML = `
Error: Invalid subnet mask format
`; return; } const ipLong = ipToLong(ipAddress); const maskLong = ipToLong(subnetMask); const networkLong = (ipLong & maskLong) >>> 0; // Ensure unsigned 32-bit const broadcastLong = (networkLong | (~maskLong >>> 0)) >>> 0; // Ensure unsigned 32-bit // Calculate CIDR from subnet mask const calculatedCidr = maskToCidr(subnetMask); // Handle edge cases for host calculations let totalHosts, firstHostLong, lastHostLong; if (calculatedCidr === 32) { // /32 - single host, no usable hosts totalHosts = 1; firstHostLong = networkLong; // Same as network lastHostLong = networkLong; // Same as network } else if (calculatedCidr === 31) { // /31 - point-to-point, no usable hosts totalHosts = 2; firstHostLong = networkLong; // First address lastHostLong = broadcastLong; // Second address } else if (calculatedCidr === 30) { // /30 - 4 total hosts, 2 usable totalHosts = 4; firstHostLong = networkLong + 1; lastHostLong = broadcastLong - 1; } else { // Normal case - calculate usable hosts totalHosts = Math.pow(2, 32 - calculatedCidr) - 2; firstHostLong = networkLong + 1; lastHostLong = broadcastLong - 1; } // Calculate total possible networks const networkSize = Math.pow(2, 32 - calculatedCidr); const totalPossibleNetworks = Math.floor(65536 / networkSize); // Generate available networks table const availableNetworks = generateAvailableNetworks(ipAddress, calculatedCidr); out.innerHTML = `
IPv4 Subnet Information

Input Information

IP Address: ${ipAddress}
Network Class: Class ${getNetworkClass(ipAddress)}
Subnet Mask: ${subnetMask}
CIDR Notation: /${calculatedCidr.toFixed(0)}

Network Information

Network Address: ${longToIp(networkLong)}
Broadcast Address: ${longToIp(broadcastLong)}
Total Hosts: ${totalHosts.toLocaleString()}

Host Information

First Usable Host: ${longToIp(firstHostLong)}
Last Usable Host: ${longToIp(lastHostLong)}

Binary Representation

IP Address: ${ipLong.toString(2).padStart(32, '0').match(/.{1,8}/g).join('.')} (0x${ipLong.toString(16).padStart(8, '0').toUpperCase()})
Subnet Mask: ${maskLong.toString(2).padStart(32, '0').match(/.{1,8}/g).join('.')} (0x${maskLong.toString(16).padStart(8, '0').toUpperCase()})
Network: ${networkLong.toString(2).padStart(32, '0').match(/.{1,8}/g).join('.')} (0x${networkLong.toString(16).padStart(8, '0').toUpperCase()})

Available Networks

${availableNetworks.map(net => ` `).join('')}
Network First Host Last Host Broadcast
${net.network} ${net.firstHost} ${net.lastHost} ${net.broadcast}
Showing ${availableNetworks.length} of ${totalPossibleNetworks} possible networks
`; } function calculateIPv6() { const ipv6Address = ui.querySelector('[name=ipv6Address]').value; const ipv6Cidr = +ui.querySelector('[name=ipv6Cidr]').value; if (!validateIPv6(ipv6Address)) { out.innerHTML = `
Error: Invalid IPv6 address format
`; return; } if (ipv6Cidr < 0 || ipv6Cidr > 128) { out.innerHTML = `
Error: CIDR must be between 0 and 128
`; return; } // Expand the IPv6 address const expandedIPv6 = expandIPv6(ipv6Address); // Calculate network and broadcast addresses const ipv6Long = ipv6ToLong(expandedIPv6); // For IPv6, we need to handle the network portion correctly const networkBits = BigInt(ipv6Cidr); const hostBits = BigInt(128 - ipv6Cidr); // Create network mask: 1s for network bits, 0s for host bits // For IPv6, we need to create a mask with networkBits 1s followed by hostBits 0s let networkMask = 0n; for (let i = 127n; i >= hostBits; i--) { networkMask |= (1n << i); } // Calculate network address: clear host bits (keep network bits) const networkLong = ipv6Long & networkMask; // Calculate broadcast address: set host bits to 1 (keep network bits, set host bits) const broadcastLong = networkLong | ((1n << hostBits) - 1n); // Calculate number of hosts (subtract 2 for network and broadcast) const totalHosts = (BigInt(2) ** BigInt(128 - ipv6Cidr)) - BigInt(2); // Calculate total possible subnets in a /64 (typical IPv6 subnet size) const totalPossibleSubnets = BigInt(2) ** BigInt(64 - ipv6Cidr); // Generate available networks table (show up to 8 networks) const availableNetworks = []; const networksToShow = Math.min(8, Number(totalPossibleSubnets)); for (let i = 0; i < networksToShow; i++) { const networkAddr = networkLong + (BigInt(i) * (BigInt(2) ** BigInt(128 - ipv6Cidr))); const broadcastAddr = networkAddr + (BigInt(2) ** BigInt(128 - ipv6Cidr)) - BigInt(1); const networkStr = longToIPv6(networkAddr); const broadcastStr = longToIPv6(broadcastAddr); availableNetworks.push({ network: networkStr, networkCompressed: compressIPv6(networkStr), broadcast: broadcastStr, broadcastCompressed: compressIPv6(broadcastStr) }); } // Format large numbers for display function formatBigInt(num) { if (num < BigInt(1e6)) { return num.toString(); } else if (num < BigInt(1e9)) { return (Number(num) / 1e6).toFixed(1) + 'M'; } else if (num < BigInt(1e12)) { return (Number(num) / 1e9).toFixed(1) + 'B'; } else { return Number(num).toExponential(2); } } out.innerHTML = `
IPv6 Subnet Information

Input Information

IPv6 Address: ${ipv6Address}
Expanded Address: ${expandedIPv6}
Compressed Address: ${compressIPv6(expandedIPv6)}
CIDR Prefix: /${ipv6Cidr}

Network Information

Network Address: ${longToIPv6(networkLong)}
Network Address (Compressed): ${compressIPv6(longToIPv6(networkLong))}
Broadcast Address: ${longToIPv6(broadcastLong)}
Broadcast Address (Compressed): ${compressIPv6(longToIPv6(broadcastLong))}
Address Space: ${128 - ipv6Cidr} bits

Host Information

Total Hosts: ${formatBigInt(totalHosts)}
Subnets in /64: ${formatBigInt(totalPossibleSubnets)}
Host Bits: ${128 - ipv6Cidr}
Network Bits: ${ipv6Cidr}

Available Networks

${availableNetworks.map(net => ` `).join('')}
Network (Expanded) Network (Compressed) Broadcast (Expanded) Broadcast (Compressed)
${net.network} ${net.networkCompressed} ${net.broadcast} ${net.broadcastCompressed}
Showing ${availableNetworks.length} of ${formatBigInt(totalPossibleSubnets)} possible networks in /64
`; } function calculate() { const ipVersionSelect = ui.querySelector('[name=ipVersion]'); if (!ipVersionSelect) { console.log('IP version select not found, skipping calculation'); return; } const ipVersion = ipVersionSelect.value; console.log('Calculate called with IP version:', ipVersion, 'Select element:', ipVersionSelect); if (ipVersion === 'ipv4') { console.log('Calculating IPv4'); calculateIPv4(); } else if (ipVersion === 'ipv6') { console.log('Calculating IPv6'); calculateIPv6(); } else { console.log('Unknown IP version:', ipVersion, 'Defaulting to IPv4'); calculateIPv4(); } } // Event listeners ui.querySelector('[name=ipVersion]').addEventListener('change', (e) => { const ipv4Inputs = ui.querySelector('#ipv4-inputs'); const ipv6Inputs = ui.querySelector('#ipv6-inputs'); console.log('IP version changed to:', e.target.value); console.log('IPv4 inputs element:', ipv4Inputs); console.log('IPv6 inputs element:', ipv6Inputs); if (e.target.value === 'ipv4') { ipv4Inputs.style.display = 'block'; ipv6Inputs.style.display = 'none'; console.log('Switched to IPv4 mode'); console.log('IPv4 display style:', ipv4Inputs.style.display); console.log('IPv6 display style:', ipv6Inputs.style.display); } else { ipv4Inputs.style.display = 'none'; ipv6Inputs.style.display = 'block'; console.log('Switched to IPv6 mode'); console.log('IPv4 display style:', ipv4Inputs.style.display); console.log('IPv6 display style:', ipv6Inputs.style.display); // Debug IPv6 input elements const ipv6AddressInput = ipv6Inputs.querySelector('[name=ipv6Address]'); const ipv6CidrInput = ipv6Inputs.querySelector('[name=ipv6Cidr]'); console.log('IPv6 Address input found:', ipv6AddressInput); console.log('IPv6 CIDR input found:', ipv6CidrInput); if (ipv6AddressInput) { console.log('IPv6 Address input properties:', { disabled: ipv6AddressInput.disabled, readonly: ipv6AddressInput.readOnly, style: ipv6AddressInput.style.cssText, offsetWidth: ipv6AddressInput.offsetWidth, offsetHeight: ipv6AddressInput.offsetHeight }); } } // Force recalculation immediately after switching modes setTimeout(() => { console.log('Recalculating after mode switch'); calculate(); }, 100); }); // Simple initialization setTimeout(() => { const ipVersionSelect = ui.querySelector('[name=ipVersion]'); if (ipVersionSelect) { // Ensure the default value is set if (!ipVersionSelect.value) { ipVersionSelect.value = 'ipv4'; } console.log('IP Version select initialized with value:', ipVersionSelect.value); } }, 200); // IPv4 event listeners ui.querySelector('[name=ipAddress]').addEventListener('input', (e) => { const ipInput = e.target.value; // Check if IP address contains CIDR notation (e.g., 10.0.0.1/8) if (ipInput.includes('/')) { const [ipPart, cidrPart] = ipInput.split('/'); const cidrValue = parseInt(cidrPart); // Validate CIDR value and update CIDR field if (cidrValue >= 0 && cidrValue <= 32) { const cidrInput = ui.querySelector('[name=cidr]'); cidrInput.value = cidrValue; // Update subnet mask based on new CIDR const mask = cidrToMask(cidrValue); const subnetMaskInput = ui.querySelector('[name=subnetMask]'); subnetMaskInput.value = mask; // Trigger calculation setTimeout(calculate, 10); } } // Always call calculate for normal input calculate(); }); // Subnet mask input - sync with CIDR and recalculate ui.querySelector('[name=subnetMask]').addEventListener('input', (e) => { const mask = e.target.value; if (validateIPv4(mask)) { const calculatedCidr = maskToCidr(mask); ui.querySelector('[name=cidr]').value = calculatedCidr; // Force immediate calculation update setTimeout(calculate, 10); } }); // CIDR input - sync with subnet mask and recalculate ui.querySelector('[name=cidr]').addEventListener('input', (e) => { const cidr = +e.target.value; console.log('CIDR input changed to:', cidr); // Validate CIDR range if (cidr >= 0 && cidr <= 32) { const mask = cidrToMask(cidr); ui.querySelector('[name=subnetMask]').value = mask; console.log('Updated subnet mask to:', mask); // Force immediate calculation update setTimeout(() => { console.log('Recalculating after CIDR change'); calculate(); }, 10); } else { console.log('Invalid CIDR value:', cidr); } }); // IPv6 event listeners - use the already created input elements ipv6AddressInput.addEventListener('input', () => { console.log('IPv6 address input changed:', ipv6AddressInput.value); calculate(); }); ipv6CidrInput.addEventListener('input', () => { console.log('IPv6 CIDR input changed:', ipv6CidrInput.value); calculate(); }); // Initial calculation - wait for DOM to be ready setTimeout(() => { console.log('Running initial calculation'); // Ensure default IPv4 value is set (Select Lite might override it) const ipVersionSelect = ui.querySelector('[name=ipVersion]'); if (ipVersionSelect) { console.log('Before setting default - IP version select value:', ipVersionSelect.value); ipVersionSelect.value = 'ipv4'; console.log('After setting default - IP version select value:', ipVersionSelect.value); // Force the change event to ensure proper display const event = new Event('change', { bubbles: true }); ipVersionSelect.dispatchEvent(event); } console.log('About to call calculate()'); calculate(); console.log('calculate() called'); }, 300); root.append(ui); } }