Features implemented: - Modular JavaScript architecture (theme.js, dns-tools.js, whois.js, punycode.js, ip-tools.js, main.js) - Responsive design with dark/light theme toggle - DNS Lookup and Reverse DNS Lookup tools - Whois Lookup functionality - IDN Punycode Converter with full Unicode support - Comprehensive IP Address Tools (validation, IPv4-to-IPv6 mapping, IPv6 compression/expansion) - Dynamic tab descriptions that change based on active tool - Mobile-responsive horizontal scrollable tabs - Copy-to-clipboard functionality for all results - Clean footer with dynamic year - IPv4-mapped IPv6 address explanation with clear warnings Technical improvements: - Separated concerns with modular JS files - Fixed browser compatibility issues with punycode library - Implemented proper error handling and user feedback - Added comprehensive input validation - Optimized for mobile devices with touch-friendly UI
438 lines
21 KiB
JavaScript
438 lines
21 KiB
JavaScript
// DNS Things - IP Tools Module
|
|
|
|
// IP Tools utility functions
|
|
function isValidIPv4(ip) {
|
|
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
return ipv4Regex.test(ip);
|
|
}
|
|
|
|
function isValidIPv6(ip) {
|
|
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$|^(?:[0-9a-fA-F]{1,4}:)*::(?:[0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4}$/;
|
|
return ipv6Regex.test(ip) || /^(?:[0-9a-fA-F]{0,4}:){0,7}[0-9a-fA-F]{0,4}$/.test(ip);
|
|
}
|
|
|
|
function ipv4ToIPv6(ipv4) {
|
|
const parts = ipv4.split('.');
|
|
const hex1 = (parseInt(parts[0]) * 256 + parseInt(parts[1])).toString(16).padStart(4, '0');
|
|
const hex2 = (parseInt(parts[2]) * 256 + parseInt(parts[3])).toString(16).padStart(4, '0');
|
|
return `::ffff:${hex1}:${hex2}`;
|
|
}
|
|
|
|
function compressIPv6(ipv6) {
|
|
// Expand first if compressed
|
|
let expanded = expandIPv6(ipv6);
|
|
// Then compress by removing leading zeros and consecutive zero groups
|
|
let compressed = expanded.replace(/\b0+([0-9a-fA-F]+)/g, '$1');
|
|
// Replace longest sequence of consecutive zero groups with ::
|
|
let parts = compressed.split(':');
|
|
let maxZeroStart = -1, maxZeroLength = 0;
|
|
let currentZeroStart = -1, currentZeroLength = 0;
|
|
|
|
for (let i = 0; i < parts.length; i++) {
|
|
if (parts[i] === '0' || parts[i] === '') {
|
|
if (currentZeroStart === -1) currentZeroStart = i;
|
|
currentZeroLength++;
|
|
} else {
|
|
if (currentZeroLength > maxZeroLength) {
|
|
maxZeroStart = currentZeroStart;
|
|
maxZeroLength = currentZeroLength;
|
|
}
|
|
currentZeroStart = -1;
|
|
currentZeroLength = 0;
|
|
}
|
|
}
|
|
|
|
if (currentZeroLength > maxZeroLength) {
|
|
maxZeroStart = currentZeroStart;
|
|
maxZeroLength = currentZeroLength;
|
|
}
|
|
|
|
if (maxZeroLength > 1) {
|
|
let before = parts.slice(0, maxZeroStart).join(':');
|
|
let after = parts.slice(maxZeroStart + maxZeroLength).join(':');
|
|
compressed = before + '::' + after;
|
|
compressed = compressed.replace(/^:/, '').replace(/:$/, '');
|
|
if (compressed === '') compressed = '::';
|
|
}
|
|
|
|
return compressed;
|
|
}
|
|
|
|
function expandIPv6(ipv6) {
|
|
if (ipv6.includes('::')) {
|
|
let parts = ipv6.split('::');
|
|
let left = parts[0] ? parts[0].split(':') : [];
|
|
let right = parts[1] ? parts[1].split(':') : [];
|
|
let middle = new Array(8 - left.length - right.length).fill('0000');
|
|
let expanded = left.concat(middle).concat(right);
|
|
return expanded.map(part => part.padStart(4, '0')).join(':');
|
|
}
|
|
return ipv6.split(':').map(part => part.padStart(4, '0')).join(':');
|
|
}
|
|
|
|
function ipv4ToBinary(ipv4) {
|
|
return ipv4.split('.').map(octet =>
|
|
parseInt(octet).toString(2).padStart(8, '0')
|
|
).join('.');
|
|
}
|
|
|
|
function ipv4ToDecimal(ipv4) {
|
|
const parts = ipv4.split('.');
|
|
return (parseInt(parts[0]) << 24) + (parseInt(parts[1]) << 16) + (parseInt(parts[2]) << 8) + parseInt(parts[3]);
|
|
}
|
|
|
|
function calculateSubnet(cidr) {
|
|
const [ip, prefixLength] = cidr.split('/');
|
|
const prefix = parseInt(prefixLength);
|
|
|
|
if (!isValidIPv4(ip) || prefix < 0 || prefix > 32) {
|
|
throw new Error('Invalid CIDR notation');
|
|
}
|
|
|
|
const ipParts = ip.split('.').map(x => parseInt(x));
|
|
const ipInt = (ipParts[0] << 24) + (ipParts[1] << 16) + (ipParts[2] << 8) + ipParts[3];
|
|
|
|
const subnetMask = (0xFFFFFFFF << (32 - prefix)) >>> 0;
|
|
const networkInt = (ipInt & subnetMask) >>> 0;
|
|
const broadcastInt = (networkInt | (0xFFFFFFFF >>> prefix)) >>> 0;
|
|
|
|
const networkIP = [
|
|
(networkInt >>> 24) & 0xFF,
|
|
(networkInt >>> 16) & 0xFF,
|
|
(networkInt >>> 8) & 0xFF,
|
|
networkInt & 0xFF
|
|
].join('.');
|
|
|
|
const broadcastIP = [
|
|
(broadcastInt >>> 24) & 0xFF,
|
|
(broadcastInt >>> 16) & 0xFF,
|
|
(broadcastInt >>> 8) & 0xFF,
|
|
broadcastInt & 0xFF
|
|
].join('.');
|
|
|
|
const subnetMaskIP = [
|
|
(subnetMask >>> 24) & 0xFF,
|
|
(subnetMask >>> 16) & 0xFF,
|
|
(subnetMask >>> 8) & 0xFF,
|
|
subnetMask & 0xFF
|
|
].join('.');
|
|
|
|
const totalHosts = Math.pow(2, 32 - prefix);
|
|
const usableHosts = totalHosts > 2 ? totalHosts - 2 : totalHosts;
|
|
|
|
return {
|
|
network: networkIP,
|
|
broadcast: broadcastIP,
|
|
subnetMask: subnetMaskIP,
|
|
totalHosts,
|
|
usableHosts,
|
|
prefix
|
|
};
|
|
}
|
|
|
|
// Initialize IP Tools
|
|
function initIPTools() {
|
|
// IP Validation
|
|
const ipValidateInput = document.getElementById('ip-validate-input');
|
|
const ipValidateBtn = document.getElementById('ip-validate-btn');
|
|
const ipValidateResult = document.getElementById('ip-validate-result');
|
|
|
|
const validateIP = () => {
|
|
const input = ipValidateInput.value.trim();
|
|
if (!input) {
|
|
ipValidateResult.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
const isIPv4 = isValidIPv4(input);
|
|
const isIPv6 = isValidIPv6(input);
|
|
|
|
if (isIPv4) {
|
|
ipValidateResult.innerHTML = `
|
|
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
|
<div class="flex justify-between items-start">
|
|
<div class="flex-1">
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">✅ Valid IPv4 Address</h5>
|
|
<div class="space-y-1 text-sm">
|
|
<div><strong>Type:</strong> IPv4</div>
|
|
<div><strong>Address:</strong> ${input}</div>
|
|
<div><strong>Binary:</strong> ${ipv4ToBinary(input)}</div>
|
|
<div><strong>Decimal:</strong> ${ipv4ToDecimal(input)}</div>
|
|
</div>
|
|
</div>
|
|
<button onclick="copyToClipboard('${input}', this)" class="ml-2 p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy to clipboard">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else if (isIPv6) {
|
|
const compressed = compressIPv6(input);
|
|
const expanded = expandIPv6(input);
|
|
ipValidateResult.innerHTML = `
|
|
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
|
<div class="flex justify-between items-start">
|
|
<div class="flex-1">
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">✅ Valid IPv6 Address</h5>
|
|
<div class="space-y-1 text-sm">
|
|
<div><strong>Type:</strong> IPv6</div>
|
|
<div><strong>Compressed:</strong> ${compressed}</div>
|
|
<div><strong>Expanded:</strong> ${expanded}</div>
|
|
</div>
|
|
</div>
|
|
<button onclick="copyToClipboard('${compressed}', this)" class="ml-2 p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy to clipboard">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
ipValidateResult.innerHTML = `
|
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
|
<h5 class="font-medium mb-1">❌ Invalid IP Address</h5>
|
|
<p class="text-sm">Please enter a valid IPv4 or IPv6 address.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
};
|
|
|
|
if (ipValidateBtn && ipValidateInput) {
|
|
ipValidateBtn.addEventListener('click', validateIP);
|
|
ipValidateInput.addEventListener('input', () => {
|
|
clearTimeout(ipValidateInput.timeout);
|
|
ipValidateInput.timeout = setTimeout(validateIP, 500);
|
|
});
|
|
}
|
|
|
|
// IPv4 to IPv6 Mapping
|
|
const ipv4ToIPv6Input = document.getElementById('ipv4-to-ipv6-input');
|
|
const ipv4ToIPv6Btn = document.getElementById('ipv4-to-ipv6-btn');
|
|
const ipv4ToIPv6Result = document.getElementById('ipv4-to-ipv6-result');
|
|
|
|
const convertIPv4ToIPv6 = () => {
|
|
const input = ipv4ToIPv6Input.value.trim();
|
|
if (!input) {
|
|
ipv4ToIPv6Result.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
if (isValidIPv4(input)) {
|
|
const ipv6 = ipv4ToIPv6(input);
|
|
// Escape the IPv6 address for safe HTML insertion
|
|
const escapedIPv6 = ipv6.replace(/'/g, ''');
|
|
ipv4ToIPv6Result.innerHTML = `
|
|
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
|
<div class="flex justify-between items-start">
|
|
<div class="flex-1">
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">IPv6 Mapped Address:</h5>
|
|
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border">${escapedIPv6}</div>
|
|
<p class="text-xs text-green-600 dark:text-green-400 mt-2">✅ IPv4-mapped IPv6 address for ${input}</p>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400 mt-2 p-2 bg-red-50 dark:bg-red-900/20 rounded border-l-2 border-red-400">
|
|
<p class="font-medium text-red-700 dark:text-red-300 mb-1">⚠️ Important:</p>
|
|
<p>This is NOT your actual IPv6 address! This is a special technical format that represents your IPv4 address (${input}) in IPv6 notation.</p>
|
|
<p class="mt-1">Your real IPv6 address is shown in the "Your IP Address" section above and is completely different.</p>
|
|
<p class="mt-1"><strong>Use case:</strong> IPv4-mapped IPv6 addresses are used for network programming and IPv4/IPv6 compatibility.</p>
|
|
</div>
|
|
</div>
|
|
<button onclick="copyToClipboard('${escapedIPv6}', this)" class="ml-2 p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy to clipboard">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
ipv4ToIPv6Result.innerHTML = `
|
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
|
<h5 class="font-medium mb-1">Error:</h5>
|
|
<p class="text-sm">Please enter a valid IPv4 address.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
};
|
|
|
|
if (ipv4ToIPv6Btn && ipv4ToIPv6Input) {
|
|
ipv4ToIPv6Btn.addEventListener('click', convertIPv4ToIPv6);
|
|
ipv4ToIPv6Input.addEventListener('input', () => {
|
|
clearTimeout(ipv4ToIPv6Input.timeout);
|
|
ipv4ToIPv6Input.timeout = setTimeout(convertIPv4ToIPv6, 500);
|
|
});
|
|
}
|
|
|
|
// IPv6 Compression/Expansion
|
|
const ipv6CompressInput = document.getElementById('ipv6-compress-input');
|
|
const ipv6CompressBtn = document.getElementById('ipv6-compress-btn');
|
|
const ipv6CompressResult = document.getElementById('ipv6-compress-result');
|
|
|
|
const processIPv6 = () => {
|
|
const input = ipv6CompressInput.value.trim();
|
|
if (!input) {
|
|
ipv6CompressResult.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
if (isValidIPv6(input)) {
|
|
const compressed = compressIPv6(input);
|
|
const expanded = expandIPv6(input);
|
|
ipv6CompressResult.innerHTML = `
|
|
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
|
<div class="space-y-3">
|
|
<div>
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Compressed Form:</h5>
|
|
<div class="flex justify-between items-center">
|
|
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${compressed}</div>
|
|
<button onclick="copyToClipboard('${compressed}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy compressed">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Expanded Form:</h5>
|
|
<div class="flex justify-between items-center">
|
|
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${expanded}</div>
|
|
<button onclick="copyToClipboard('${expanded}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy expanded">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
ipv6CompressResult.innerHTML = `
|
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
|
<h5 class="font-medium mb-1">Error:</h5>
|
|
<p class="text-sm">Please enter a valid IPv6 address.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
};
|
|
|
|
if (ipv6CompressBtn && ipv6CompressInput) {
|
|
ipv6CompressBtn.addEventListener('click', processIPv6);
|
|
ipv6CompressInput.addEventListener('input', () => {
|
|
clearTimeout(ipv6CompressInput.timeout);
|
|
ipv6CompressInput.timeout = setTimeout(processIPv6, 500);
|
|
});
|
|
}
|
|
|
|
// IP to Binary/Decimal
|
|
const ipBinaryInput = document.getElementById('ip-binary-input');
|
|
const ipBinaryBtn = document.getElementById('ip-binary-btn');
|
|
const ipBinaryResult = document.getElementById('ip-binary-result');
|
|
|
|
const convertIPToBinary = () => {
|
|
const input = ipBinaryInput.value.trim();
|
|
if (!input) {
|
|
ipBinaryResult.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
if (isValidIPv4(input)) {
|
|
const binary = ipv4ToBinary(input);
|
|
const decimal = ipv4ToDecimal(input);
|
|
ipBinaryResult.innerHTML = `
|
|
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
|
<div class="space-y-3">
|
|
<div>
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Binary Representation:</h5>
|
|
<div class="flex justify-between items-center">
|
|
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${binary}</div>
|
|
<button onclick="copyToClipboard('${binary}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy binary">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-2">Decimal Representation:</h5>
|
|
<div class="flex justify-between items-center">
|
|
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border flex-1 mr-2">${decimal}</div>
|
|
<button onclick="copyToClipboard('${decimal}', this)" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-800 rounded" title="Copy decimal">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
ipBinaryResult.innerHTML = `
|
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
|
<h5 class="font-medium mb-1">Error:</h5>
|
|
<p class="text-sm">Please enter a valid IPv4 address.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
};
|
|
|
|
if (ipBinaryBtn && ipBinaryInput) {
|
|
ipBinaryBtn.addEventListener('click', convertIPToBinary);
|
|
ipBinaryInput.addEventListener('input', () => {
|
|
clearTimeout(ipBinaryInput.timeout);
|
|
ipBinaryInput.timeout = setTimeout(convertIPToBinary, 500);
|
|
});
|
|
}
|
|
|
|
// Subnet Calculator
|
|
const subnetInput = document.getElementById('subnet-input');
|
|
const subnetBtn = document.getElementById('subnet-btn');
|
|
const subnetResult = document.getElementById('subnet-result');
|
|
|
|
const calculateSubnetInfo = () => {
|
|
const input = subnetInput.value.trim();
|
|
if (!input) {
|
|
subnetResult.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const subnet = calculateSubnet(input);
|
|
subnetResult.innerHTML = `
|
|
<div class="p-4 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
|
<h5 class="font-medium text-green-800 dark:text-green-200 mb-3">Subnet Information:</h5>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
<div class="space-y-2">
|
|
<div><strong>Network Address:</strong> ${subnet.network}</div>
|
|
<div><strong>Broadcast Address:</strong> ${subnet.broadcast}</div>
|
|
<div><strong>Subnet Mask:</strong> ${subnet.subnetMask}</div>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div><strong>Prefix Length:</strong> /${subnet.prefix}</div>
|
|
<div><strong>Total Hosts:</strong> ${subnet.totalHosts.toLocaleString()}</div>
|
|
<div><strong>Usable Hosts:</strong> ${subnet.usableHosts.toLocaleString()}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} catch (error) {
|
|
subnetResult.innerHTML = `
|
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
|
<h5 class="font-medium mb-1">Error:</h5>
|
|
<p class="text-sm">${error.message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
};
|
|
|
|
if (subnetBtn && subnetInput) {
|
|
subnetBtn.addEventListener('click', calculateSubnetInfo);
|
|
subnetInput.addEventListener('input', () => {
|
|
clearTimeout(subnetInput.timeout);
|
|
subnetInput.timeout = setTimeout(calculateSubnetInfo, 500);
|
|
});
|
|
}
|
|
}
|