diff --git a/public/calculators/subnet.js b/public/calculators/subnet.js
deleted file mode 100644
index 8986937..0000000
--- a/public/calculators/subnet.js
+++ /dev/null
@@ -1,895 +0,0 @@
-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 = `
-
- `;
-
- // 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
-
-
-
-
- Network |
- First Host |
- Last Host |
- Broadcast |
-
-
-
- ${availableNetworks.map(net => `
-
- ${net.network} |
- ${net.firstHost} |
- ${net.lastHost} |
- ${net.broadcast} |
-
- `).join('')}
-
-
-
-
- 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
-
-
-
-
- Network (Expanded) |
- Network (Compressed) |
- Broadcast (Expanded) |
- Broadcast (Compressed) |
-
-
-
- ${availableNetworks.map(net => `
-
- ${net.network} |
- ${net.networkCompressed} |
- ${net.broadcast} |
- ${net.broadcastCompressed} |
-
- `).join('')}
-
-
-
-
- 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);
- }
-}
diff --git a/public/calculators/zfs.js b/public/calculators/zfs.js
deleted file mode 100644
index c96e420..0000000
--- a/public/calculators/zfs.js
+++ /dev/null
@@ -1,390 +0,0 @@
-import {revive, persist, labelInput, labelSelect} from '/js/util.js';
-
-export default {
- id:'zfs', name:'ZFS Calculator', about:'Calculate ZFS pool configurations, performance tuning, and capacity planning.',
- render(root){
- const key='calc_zfs_v1';
- const s = revive(key,{
- poolType: 'raidz2',
- diskCount: 6,
- diskSize: 4000,
- diskSizeUnit: 'GB',
- blockSize: '128K',
- compression: 'lz4',
- dedup: false,
- ashift: 12,
- arcMax: 8192,
- arcMaxUnit: 'MB'
- });
-
- const ui = document.createElement('div');
-
- // Pool configuration section
- const poolSection = document.createElement('div');
- poolSection.innerHTML = `
- Pool Configuration
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
-
- // Performance tuning section
- const perfSection = document.createElement('div');
- perfSection.innerHTML = `
- Performance Tuning
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
-
- ui.append(poolSection, perfSection);
-
- // 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);
-
-
- // Calculation function
- function calc(){
- const poolType = ui.querySelector('[name=poolType]').value;
- const diskCount = +ui.querySelector('[name=diskCount]').value;
- const diskSize = +ui.querySelector('[name=diskSize]').value;
- const diskSizeUnit = ui.querySelector('[name=diskSizeUnit]').value;
- const blockSize = ui.querySelector('[name=blockSize]').value;
- const compression = ui.querySelector('[name=compression]').value;
- const dedup = ui.querySelector('[name=dedup]').value === 'true';
- const ashift = +ui.querySelector('[name=ashift]').value;
- const arcMax = +ui.querySelector('[name=arcMax]').value;
- const arcMaxUnit = ui.querySelector('[name=arcMaxUnit]').value;
-
- // Convert disk size to GB for calculations
- let diskSizeGB = diskSize;
- if (diskSizeUnit === 'TB') {
- diskSizeGB = diskSize * 1024;
- }
-
- // Calculate usable capacity based on pool type
- let usableCapacity, redundancy, minDisks, recommendedDisks, vdevCount;
-
- switch(poolType) {
- case 'stripe':
- usableCapacity = diskCount * diskSizeGB;
- redundancy = 'None';
- minDisks = 1;
- recommendedDisks = 1;
- vdevCount = 1;
- break;
- case 'mirror':
- usableCapacity = Math.floor(diskCount / 2) * diskSizeGB;
- redundancy = '50%';
- minDisks = 2;
- recommendedDisks = 2;
- vdevCount = Math.floor(diskCount / 2);
- break;
- case 'raidz1':
- usableCapacity = (diskCount - 1) * diskSizeGB;
- redundancy = '1 disk';
- minDisks = 3;
- recommendedDisks = 3;
- vdevCount = 1;
- break;
- case 'raidz2':
- usableCapacity = (diskCount - 2) * diskSizeGB;
- redundancy = '2 disks';
- minDisks = 4;
- recommendedDisks = 6;
- vdevCount = 1;
- break;
- case 'raidz3':
- usableCapacity = (diskCount - 3) * diskSizeGB;
- redundancy = '3 disks';
- minDisks = 5;
- recommendedDisks = 9;
- vdevCount = 1;
- break;
- case 'mirror2x2':
- usableCapacity = 2 * diskSizeGB; // 2 mirrors, each with 1 usable disk
- redundancy = '50%';
- minDisks = 4;
- recommendedDisks = 4;
- vdevCount = 2;
- break;
- case 'mirror3x2':
- usableCapacity = 3 * diskSizeGB; // 3 mirrors, each with 1 usable disk
- redundancy = '50%';
- minDisks = 6;
- recommendedDisks = 6;
- vdevCount = 3;
- break;
- case 'raidz2x2':
- usableCapacity = 2 * (diskCount / 2 - 2) * diskSizeGB; // 2 RAID-Z2 vdevs
- redundancy = '2 disks per vdev';
- minDisks = 8;
- recommendedDisks = 12;
- vdevCount = 2;
- break;
- }
-
- // Validate disk count
- if (diskCount < minDisks) {
- out.innerHTML = `
- Error: ${poolType.toUpperCase()} requires at least ${minDisks} disks
-
`;
- return;
- }
-
- // Calculate compression ratios
- const compressionRatios = {
- 'off': 1.0,
- 'lz4': 2.1,
- 'gzip': 2.5,
- 'gzip-1': 2.0,
- 'gzip-9': 3.0,
- 'zstd': 2.8,
- 'zstd-1': 2.2,
- 'zstd-19': 3.5
- };
-
- const compressionRatio = compressionRatios[compression] || 1.0;
- const effectiveCapacity = usableCapacity * compressionRatio;
-
- // Calculate performance metrics
- const blockSizeKB = parseInt(blockSize.replace(/[^0-9]/g, ''));
- const ashiftBytes = Math.pow(2, ashift);
-
- // Convert ARC max to MB
- let arcMaxMB = arcMax;
- if (arcMaxUnit === 'GB') {
- arcMaxMB = arcMax * 1024;
- }
-
- // Helper function to format capacity
- function formatCapacity(gb) {
- if (gb >= 1024) {
- return `${gb.toFixed(1)} GB (${(gb / 1024).toFixed(2)} TB)`;
- }
- return `${gb.toFixed(1)} GB`;
- }
-
- // Calculate I/O performance estimates
- const estimatedIOPS = Math.floor(diskCount * 100); // Rough estimate: 100 IOPS per disk
- const estimatedThroughput = Math.floor(diskCount * 150); // Rough estimate: 150 MB/s per disk
-
- // Calculate memory requirements (rule of thumb: 1GB RAM per 1TB storage)
- const recommendedRAM = Math.ceil((diskCount * diskSizeGB) / 1024);
-
- // Generate results
- out.innerHTML = `
-
- ZFS Pool Configuration
-
-
-
-
-
Capacity & Redundancy
-
- Raw Capacity: ${formatCapacity(diskCount * diskSizeGB)}
-
-
- Usable Capacity: ${formatCapacity(usableCapacity)}
-
-
- Effective Capacity (with compression): ${formatCapacity(effectiveCapacity)}
-
-
- Redundancy: ${redundancy}
-
-
- Efficiency: ${((usableCapacity / (diskCount * diskSizeGB)) * 100).toFixed(1)}%
-
-
- Compression Savings: ${((effectiveCapacity - usableCapacity) / usableCapacity * 100).toFixed(1)}%
-
-
- VDev Count: ${vdevCount} ${vdevCount > 1 ? 'vdevs' : 'vdev'}
-
-
-
-
-
Performance Settings
-
- Block Size: ${blockSize}
-
-
- Ashift: ${ashift} (${ashiftBytes} bytes)
-
-
- Compression: ${compression} (${compressionRatio.toFixed(1)}x ratio)
-
-
- Deduplication: ${dedup ? 'On' : 'Off'}
-
-
- ARC Max: ${arcMaxMB} MB
-
-
-
-
-
-
-
Performance Estimates
-
- Estimated IOPS: ${estimatedIOPS.toLocaleString()} (random 4K reads)
-
-
- Estimated Throughput: ${estimatedThroughput} MB/s (sequential)
-
-
- Recommended RAM: ${recommendedRAM} GB minimum
-
-
- Current ARC: ${(arcMaxMB / 1024).toFixed(1)} GB
-
-
-
-
-
System Requirements
-
- Minimum RAM: ${Math.max(8, recommendedRAM)} GB
-
-
- Recommended RAM: ${Math.max(16, recommendedRAM * 2)} GB
-
-
- CPU Cores: ${Math.max(4, Math.ceil(diskCount / 2))} cores recommended
-
-
- Network: 10 Gbps recommended for ${estimatedThroughput} MB/s throughput
-
-
-
-
-
-
- `;
-
- persist(key, {poolType, diskCount, diskSize, diskSizeUnit, blockSize, compression, dedup, ashift, arcMax, arcMaxUnit});
- }
-
- // Event listeners
- ui.querySelector('[name=poolType]').addEventListener('change', calc);
- ui.querySelector('[name=diskCount]').addEventListener('input', calc);
- ui.querySelector('[name=diskSize]').addEventListener('input', calc);
- ui.querySelector('[name=diskSizeUnit]').addEventListener('change', calc);
- ui.querySelector('[name=blockSize]').addEventListener('change', calc);
- ui.querySelector('[name=compression]').addEventListener('change', calc);
- ui.querySelector('[name=dedup]').addEventListener('change', calc);
- ui.querySelector('[name=ashift]').addEventListener('change', calc);
- ui.querySelector('[name=arcMax]').addEventListener('input', calc);
- ui.querySelector('[name=arcMaxUnit]').addEventListener('change', calc);
-
- // Initial calculation
- calc();
- root.append(ui);
- }
-}
diff --git a/public/css/styles.css b/public/css/styles.css
index 77f57af..a11f1c9 100644
--- a/public/css/styles.css
+++ b/public/css/styles.css
@@ -65,135 +65,16 @@ select:disabled{ opacity:.55; cursor:not-allowed; }
html,body{margin:0;background:var(--bg);color:var(--text);font:16px/1.5 system-ui,Segoe UI,Roboto,Ubuntu,Cantarell,sans-serif}
.wrap{max-width:var(--max);margin:0 auto;padding:16px}
-.bar{position:sticky;top:0;background:linear-gradient(180deg,rgba(0,0,0,.06),rgba(0,0,0,0));backdrop-filter:blur(8px);border-bottom:1px solid var(--border);z-index:10;min-height:70px}
-.bar__inner{display:flex;align-items:center;gap:12px;justify-content:space-between;min-height:70px}
+.bar{position:sticky;top:0;background:linear-gradient(180deg,rgba(0,0,0,.06),rgba(0,0,0,0));backdrop-filter:blur(8px);border-bottom:1px solid var(--border);z-index:10}
+.bar__inner{display:flex;align-items:center;gap:12px;justify-content:space-between}
.brand{font-weight:700}
.btn{background:transparent;border:1px solid var(--border);color:var(--text);padding:8px 10px;border-radius:999px;cursor:pointer}
-/* Mobile navigation toggle button */
-.nav-toggle {
- display: none;
- background: transparent;
- border: 1px solid var(--border);
- color: var(--text);
- padding: 8px;
- border-radius: 8px;
- cursor: pointer;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
-}
-
-.nav-toggle svg {
- width: 20px;
- height: 20px;
- fill: currentColor;
-}
-
/* ---- Layout ---- */
.layout{display:grid;grid-template-columns:240px 1fr;gap:16px}
@media (max-width: 820px){
.layout{grid-template-columns:1fr}
-
- /* Show mobile nav toggle */
- .nav-toggle {
- display: flex !important;
- }
-
- /* Hide desktop navigation by default on mobile */
- .sidenav {
- display: none;
- position: fixed;
- top: 70px;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 100;
- background: var(--card);
- border-radius: 0;
- border: none;
- box-shadow: var(--shadow);
- overflow-y: auto;
- }
-
- /* Show navigation when active */
- .sidenav.mobile-active {
- display: block;
- }
-
- /* Add mobile overlay */
- .sidenav::before {
- content: '';
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: -1;
- }
-
- /* Adjust main content spacing for mobile */
- .content {
- margin-top: 16px;
- }
-
- /* Improve mobile spacing */
- .wrap {
- padding: 12px;
- }
-
- /* Better mobile grid */
- .content {
- grid-template-columns: 1fr;
- gap: 12px;
- }
-
- /* Mobile-friendly cards */
- .card {
- padding: 12px;
- }
-
- /* Mobile-friendly inputs */
- input, select, textarea {
- padding: 12px;
- font-size: 16px; /* Prevents zoom on iOS */
- }
-
- /* Mobile-friendly results */
- .result {
- margin-top: 16px;
- padding: 12px;
- overflow-x: auto;
- }
-
- /* Mobile-friendly tables */
- table {
- font-size: 14px;
- }
-
- /* Mobile-friendly calculator inputs */
- .calculator-container {
- padding: 16px 0;
- }
-
- /* Ensure proper spacing from navigation */
- .layout {
- padding-top: 16px;
- }
-
- /* Mobile-friendly footer */
- .footer-content {
- flex-direction: column;
- gap: 12px;
- text-align: center;
- }
-
- .source-link {
- margin-left: 0;
- }
}
/* ---- Vertical nav ---- */
@@ -270,25 +151,6 @@ input,select,textarea{width:100%;background:transparent;color:var(--text);border
.k{padding:2px 6px;border-radius:6px;border:1px solid var(--k-border);background:var(--k-bg)}
.foot{color:var(--muted);font-size:13px;margin-top:20px}
-.footer-content {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.source-link {
- color: var(--accent);
- text-decoration: none;
- font-weight: 500;
- transition: color 0.2s ease;
- margin-left: auto;
-}
-
-.source-link:hover {
- color: var(--accent2);
- text-decoration: underline;
-}
-
/* ---- Status indicators ---- */
.status {
padding: 8px 12px;
diff --git a/public/index.html b/public/index.html
index b21196e..e16d5a1 100644
--- a/public/index.html
+++ b/public/index.html
@@ -14,14 +14,7 @@
calculator.127local.net
-
+
@@ -30,12 +23,7 @@
-
+