commit e5195ba1f19a3e2ae597e8a11ad314f457c9773a Author: dwindown Date: Fri Aug 1 23:13:52 2025 +0700 Initial commit: DNS Things - Comprehensive DNS utility website 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 diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..ce93806 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,231 @@ +/* DNS Things - Main Styles */ + +/* Custom Tailwind Configuration */ +.tab-btn.active { + @apply bg-indigo-600 text-white shadow-md; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +/* Custom scrollbar for dark mode */ +.dark ::-webkit-scrollbar { + width: 8px; +} + +.dark ::-webkit-scrollbar-track { + background: #374151; +} + +.dark ::-webkit-scrollbar-thumb { + background: #6b7280; + border-radius: 4px; +} + +.dark ::-webkit-scrollbar-thumb:hover { + background: #9ca3af; +} + +/* Hero section adjustments */ +.hero-section { + min-height: 60vh; +} + +/* DNS Tools section */ +.dns-tools-section { + min-height: 70vh; +} + +/* Tab navigation */ +.tab-navigation { + min-width: 200px; +} + +/* Results styling */ +.dns-result-item { + transition: all 0.2s ease-in-out; +} + +.dns-result-item:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.dark .dns-result-item:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +/* Copy button animations */ +.copy-btn { + transition: all 0.2s ease-in-out; +} + +.copy-btn:hover { + transform: scale(1.05); +} + +/* Loading animations */ +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.loading-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .tab-navigation { + min-width: auto; + width: 100%; + } + + .hero-section { + min-height: 50vh; + } + + .dns-tools-section { + min-height: auto; + } +} + +/* Mobile Tab Navigation - Horizontal Scrollable */ +@media (max-width: 767px) { + /* Main container becomes vertical stack */ + .flex.flex-col.md\:flex-row.gap-8 { + flex-direction: column !important; + gap: 1rem !important; + } + + /* Tab container takes full width */ + .w-full.md\:w-1\/3.lg\:w-1\/4 { + width: 100% !important; + margin-bottom: 1rem; + order: 1; + } + + /* Content area takes full width */ + .w-full.md\:w-2\/3.lg\:w-3\/4 { + width: 100% !important; + order: 2; + } + + /* Tab buttons container becomes horizontal scrollable */ + .flex.flex-col.space-y-2.p-2.bg-gray-50.dark\:bg-gray-700.rounded-lg { + flex-direction: row !important; + overflow-x: auto; + overflow-y: hidden; + padding: 0.75rem !important; + gap: 0.5rem !important; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ + } + + /* Hide scrollbar for webkit browsers */ + .flex.flex-col.space-y-2.p-2.bg-gray-50.dark\:bg-gray-700.rounded-lg::-webkit-scrollbar { + display: none; + } + + /* Tab buttons optimized for mobile */ + .tab-btn { + flex-shrink: 0 !important; + white-space: nowrap !important; + min-width: auto !important; + padding: 0.5rem 1rem !important; + font-size: 0.875rem !important; + text-align: center !important; + margin: 0 !important; + } + + /* Remove space-y utility on mobile */ + .flex.flex-col.space-y-2.p-2.bg-gray-50.dark\:bg-gray-700.rounded-lg > * + * { + margin-top: 0 !important; + } +} + +/* Focus states for accessibility */ +.focus-ring:focus { + outline: 2px solid #6366f1; + outline-offset: 2px; +} + +/* Error and success states */ +.error-state { + border-color: #ef4444; + background-color: #fef2f2; +} + +.dark .error-state { + border-color: #dc2626; + background-color: #7f1d1d; +} + +.success-state { + border-color: #10b981; + background-color: #f0fdf4; +} + +.dark .success-state { + border-color: #059669; + background-color: #064e3b; +} + +/* IP Tools specific styles */ +.ip-validation-result { + max-width: 100%; + word-break: break-all; +} + +/* Subnet calculator grid */ +.subnet-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; +} + +/* Punycode converter styles */ +.punycode-example { + font-family: 'Courier New', monospace; + font-size: 0.875rem; +} + +/* Whois result styles */ +.whois-badge { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; + font-size: 0.75rem; + font-weight: 500; +} + +.whois-badge.ipv4 { + background-color: #dbeafe; + color: #1e40af; +} + +.dark .whois-badge.ipv4 { + background-color: #1e3a8a; + color: #93c5fd; +} + +.whois-badge.ipv6 { + background-color: #e0e7ff; + color: #5b21b6; +} + +.dark .whois-badge.ipv6 { + background-color: #581c87; + color: #c4b5fd; +} diff --git a/assets/img/hero.webp b/assets/img/hero.webp new file mode 100644 index 0000000..5f57a6d Binary files /dev/null and b/assets/img/hero.webp differ diff --git a/assets/js/dns-tools.js b/assets/js/dns-tools.js new file mode 100644 index 0000000..5b05719 --- /dev/null +++ b/assets/js/dns-tools.js @@ -0,0 +1,262 @@ +// DNS Things - DNS Tools Module + +// Initialize IP display functionality +async function initIPDisplay() { + const ipv4Element = document.getElementById('ipv4'); + const ipv6Element = document.getElementById('ipv6'); + + if (!ipv4Element || !ipv6Element) return; + + try { + // Fetch IPv4 + const ipv4Response = await fetch('https://api.ipify.org?format=json'); + const ipv4Data = await ipv4Response.json(); + ipv4Element.textContent = ipv4Data.ip; + } catch (error) { + ipv4Element.textContent = 'Unable to fetch'; + console.error('Error fetching IPv4:', error); + } + + try { + // Fetch IPv6 + const ipv6Response = await fetch('https://api64.ipify.org?format=json'); + const ipv6Data = await ipv6Response.json(); + ipv6Element.textContent = ipv6Data.ip; + } catch (error) { + ipv6Element.textContent = 'Not available'; + console.error('Error fetching IPv6:', error); + } +} + +// Initialize DNS lookup form +function initDNSForm() { + const form = document.getElementById('dns-form'); + const input = document.getElementById('domain-input'); + const results = document.getElementById('dns-results-container'); + const loader = document.getElementById('dns-loader'); + + if (!form || !input || !results || !loader) return; + + form.addEventListener('submit', async (e) => { + e.preventDefault(); + + const domain = input.value.trim(); + if (!domain) return; + + // Show loader + loader.classList.remove('hidden'); + results.innerHTML = ''; + + try { + // DNS record types to query + const recordTypes = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'CNAME', 'SOA', 'PTR', 'SRV']; + + // Create promises for all DNS queries + const queries = recordTypes.map(type => + fetch(`https://dns.google/resolve?name=${encodeURIComponent(domain)}&type=${type}`) + .then(response => response.json()) + .then(data => ({ type, data })) + .catch(error => ({ type, error })) + ); + + // Wait for all queries to complete + const responses = await Promise.all(queries); + + // Group results by record type + const groupedResults = {}; + + responses.forEach(({ type, data, error }) => { + if (error) { + console.error(`Error fetching ${type} records:`, error); + return; + } + + if (data.Answer && data.Answer.length > 0) { + groupedResults[type] = data.Answer.map(record => { + // Clean the record data + let cleanData = record.data; + if (typeof cleanData === 'string') { + cleanData = cleanData.replace(/^"(.*)"$/, '$1').replace(/\.$/, ''); + } + return { + ...record, + data: cleanData + }; + }); + } + }); + + // Display results + if (Object.keys(groupedResults).length === 0) { + results.innerHTML = ` +
+

No DNS records found

+

The domain "${domain}" does not have any DNS records or does not exist.

+
+ `; + } else { + let resultsHTML = '
'; + + Object.entries(groupedResults).forEach(([type, records]) => { + resultsHTML += ` +
+

${type} Records (${records.length})

+
    + `; + + records.forEach(record => { + resultsHTML += ` +
  • + ${record.data} + +
  • + `; + }); + + resultsHTML += '
'; + }); + + resultsHTML += '
'; + results.innerHTML = resultsHTML; + } + } catch (error) { + console.error('DNS lookup error:', error); + results.innerHTML = ` +
+

Error

+

Failed to perform DNS lookup. Please try again.

+
+ `; + } finally { + loader.classList.add('hidden'); + } + }); + + // Auto-submit on paste + input.addEventListener('paste', () => { + setTimeout(() => { + if (input.value.trim()) { + form.dispatchEvent(new Event('submit')); + } + }, 100); + }); +} + +// Initialize reverse DNS lookup form +function initReverseDNSForm() { + const form = document.getElementById('reverse-dns-form'); + const input = document.getElementById('ip-input'); + const results = document.getElementById('reverse-dns-results'); + const loader = document.getElementById('reverse-dns-loader'); + + if (!form || !input || !results || !loader) return; + + // IPv6 reverse DNS helper + function ipv6ToReverseDNS(ipv6) { + // Expand IPv6 to full format + let expanded = ipv6; + if (ipv6.includes('::')) { + const parts = ipv6.split('::'); + const left = parts[0] ? parts[0].split(':') : []; + const right = parts[1] ? parts[1].split(':') : []; + const middle = new Array(8 - left.length - right.length).fill('0000'); + expanded = left.concat(middle).concat(right).join(':'); + } + + // Remove colons and pad each segment to 4 characters + const fullHex = expanded.split(':').map(segment => + segment.padStart(4, '0') + ).join(''); + + // Reverse and add dots + return fullHex.split('').reverse().join('.') + '.ip6.arpa'; + } + + form.addEventListener('submit', async (e) => { + e.preventDefault(); + + const ip = input.value.trim(); + if (!ip) return; + + // Show loader + loader.classList.remove('hidden'); + results.innerHTML = ''; + + try { + let reverseDomain; + + // Check if IPv4 or IPv6 + if (ip.includes(':')) { + // IPv6 + try { + reverseDomain = ipv6ToReverseDNS(ip); + } catch (error) { + throw new Error('Invalid IPv6 address format'); + } + } else { + // IPv4 + const parts = ip.split('.'); + if (parts.length !== 4 || parts.some(part => isNaN(part) || part < 0 || part > 255)) { + throw new Error('Invalid IPv4 address format'); + } + reverseDomain = parts.reverse().join('.') + '.in-addr.arpa'; + } + + // Perform reverse DNS lookup + const response = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(reverseDomain)}&type=PTR`); + const data = await response.json(); + + if (data.Answer && data.Answer.length > 0) { + let resultsHTML = '
'; + data.Answer.forEach(record => { + const hostname = record.data.replace(/\.$/, ''); + resultsHTML += ` +
+
+
Hostname found:
+
${hostname}
+
+ +
+ `; + }); + resultsHTML += '
'; + results.innerHTML = resultsHTML; + } else { + results.innerHTML = ` +
+

No reverse DNS record found

+

The IP address "${ip}" does not have a reverse DNS (PTR) record.

+
+ `; + } + } catch (error) { + console.error('Reverse DNS lookup error:', error); + results.innerHTML = ` +
+

Error

+

${error.message || 'Failed to perform reverse DNS lookup. Please check the IP address format.'}

+
+ `; + } finally { + loader.classList.add('hidden'); + } + }); + + // Auto-submit on paste + input.addEventListener('paste', () => { + setTimeout(() => { + if (input.value.trim()) { + form.dispatchEvent(new Event('submit')); + } + }, 100); + }); +} diff --git a/assets/js/ip-tools.js b/assets/js/ip-tools.js new file mode 100644 index 0000000..7dffafc --- /dev/null +++ b/assets/js/ip-tools.js @@ -0,0 +1,437 @@ +// 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 = ` +
+
+
+
✅ Valid IPv4 Address
+
+
Type: IPv4
+
Address: ${input}
+
Binary: ${ipv4ToBinary(input)}
+
Decimal: ${ipv4ToDecimal(input)}
+
+
+ +
+
+ `; + } else if (isIPv6) { + const compressed = compressIPv6(input); + const expanded = expandIPv6(input); + ipValidateResult.innerHTML = ` +
+
+
+
✅ Valid IPv6 Address
+
+
Type: IPv6
+
Compressed: ${compressed}
+
Expanded: ${expanded}
+
+
+ +
+
+ `; + } else { + ipValidateResult.innerHTML = ` +
+
❌ Invalid IP Address
+

Please enter a valid IPv4 or IPv6 address.

+
+ `; + } + }; + + 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 = ` +
+
+
+
IPv6 Mapped Address:
+
${escapedIPv6}
+

✅ IPv4-mapped IPv6 address for ${input}

+
+

⚠️ Important:

+

This is NOT your actual IPv6 address! This is a special technical format that represents your IPv4 address (${input}) in IPv6 notation.

+

Your real IPv6 address is shown in the "Your IP Address" section above and is completely different.

+

Use case: IPv4-mapped IPv6 addresses are used for network programming and IPv4/IPv6 compatibility.

+
+
+ +
+
+ `; + } else { + ipv4ToIPv6Result.innerHTML = ` +
+
Error:
+

Please enter a valid IPv4 address.

+
+ `; + } + }; + + 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 = ` +
+
+
+
Compressed Form:
+
+
${compressed}
+ +
+
+
+
Expanded Form:
+
+
${expanded}
+ +
+
+
+
+ `; + } else { + ipv6CompressResult.innerHTML = ` +
+
Error:
+

Please enter a valid IPv6 address.

+
+ `; + } + }; + + 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 = ` +
+
+
+
Binary Representation:
+
+
${binary}
+ +
+
+
+
Decimal Representation:
+
+
${decimal}
+ +
+
+
+
+ `; + } else { + ipBinaryResult.innerHTML = ` +
+
Error:
+

Please enter a valid IPv4 address.

+
+ `; + } + }; + + 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 = ` +
+
Subnet Information:
+
+
+
Network Address: ${subnet.network}
+
Broadcast Address: ${subnet.broadcast}
+
Subnet Mask: ${subnet.subnetMask}
+
+
+
Prefix Length: /${subnet.prefix}
+
Total Hosts: ${subnet.totalHosts.toLocaleString()}
+
Usable Hosts: ${subnet.usableHosts.toLocaleString()}
+
+
+
+ `; + } catch (error) { + subnetResult.innerHTML = ` +
+
Error:
+

${error.message}

+
+ `; + } + }; + + if (subnetBtn && subnetInput) { + subnetBtn.addEventListener('click', calculateSubnetInfo); + subnetInput.addEventListener('input', () => { + clearTimeout(subnetInput.timeout); + subnetInput.timeout = setTimeout(calculateSubnetInfo, 500); + }); + } +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..9aabeec --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,33 @@ +// DNS Things - Main Application Initialization + +// Initialize everything when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { + // Initialize core UI components + initTheme(); + initTabs(); + initCopyButtons(); + + // Initialize DNS tools + initIPDisplay(); + initDNSForm(); + initReverseDNSForm(); + + // Initialize additional tools + initWhoisForm(); + initPunycodeConverter(); + initIPTools(); + + // Set dynamic year in footer + initFooter(); + + console.log('DNS Things application initialized successfully'); +}); + +// Initialize footer with dynamic year +function initFooter() { + const yearElement = document.getElementById('current-year'); + if (yearElement) { + const currentYear = new Date().getFullYear(); + yearElement.textContent = currentYear; + } +} diff --git a/assets/js/punycode.js b/assets/js/punycode.js new file mode 100644 index 0000000..f708cf4 --- /dev/null +++ b/assets/js/punycode.js @@ -0,0 +1,176 @@ +// DNS Things - Punycode Module + +// Real Punycode conversion functions using punycode.js library +function toPunycode(domain) { + try { + // Check if punycode library is available + if (typeof punycode === 'undefined') { + throw new Error('Punycode library not loaded'); + } + + // Split domain into parts and convert each part + const parts = domain.split('.'); + const convertedParts = parts.map(part => { + // Check if part contains non-ASCII characters + if (/[^\x00-\x7F]/.test(part)) { + return punycode.toASCII(part); + } + return part; + }); + + return convertedParts.join('.'); + } catch (error) { + // Fallback to URL API if punycode library fails + try { + const url = new URL(`http://${domain}`); + return url.hostname; + } catch { + throw new Error('Invalid domain format'); + } + } +} + +function fromPunycode(domain) { + try { + // Check if domain contains punycode + if (!domain.includes('xn--')) { + return domain; // No punycode to convert + } + + // Check if punycode library is available + if (typeof punycode === 'undefined') { + throw new Error('Punycode library not loaded'); + } + + // Split domain into parts and convert each part + const parts = domain.split('.'); + const convertedParts = parts.map(part => { + if (part.startsWith('xn--')) { + try { + return punycode.toUnicode(part); + } catch { + return part; // Return original if conversion fails + } + } + return part; + }); + + return convertedParts.join('.'); + } catch (error) { + throw new Error('Invalid punycode format'); + } +} + +// Initialize IDN Punycode Converter +function initPunycodeConverter() { + const unicodeInput = document.getElementById('unicode-input'); + const punycodeInput = document.getElementById('punycode-input'); + const unicodeToPunycodeBtn = document.getElementById('unicode-to-punycode-btn'); + const punycodeToUnicodeBtn = document.getElementById('punycode-to-unicode-btn'); + const punycodeResult = document.getElementById('punycode-result'); + const unicodeResult = document.getElementById('unicode-result'); + + if (!unicodeInput || !punycodeInput) return; + + // Unicode to Punycode conversion + const convertUnicodeToPunycode = () => { + const input = unicodeInput.value.trim(); + if (!input) { + punycodeResult.innerHTML = ''; + return; + } + + try { + const converted = toPunycode(input); + const isConverted = converted !== input; + + punycodeResult.innerHTML = ` +
+
+
+
Punycode Result:
+
${converted}
+ ${isConverted ? '

✅ Conversion applied

' : '

ℹ️ No conversion needed (ASCII only)

'} +
+ +
+
+ `; + } catch (error) { + punycodeResult.innerHTML = ` +
+
Error:
+

${error.message}

+
+ `; + } + }; + + // Punycode to Unicode conversion + const convertPunycodeToUnicode = () => { + const input = punycodeInput.value.trim(); + if (!input) { + unicodeResult.innerHTML = ''; + return; + } + + try { + const converted = fromPunycode(input); + const isConverted = converted !== input; + + unicodeResult.innerHTML = ` +
+
+
+
Unicode Result:
+
${converted}
+ ${isConverted ? '

✅ Conversion applied

' : '

ℹ️ No conversion needed (no punycode found)

'} +
+ +
+
+ `; + } catch (error) { + unicodeResult.innerHTML = ` +
+
Error:
+

${error.message}

+
+ `; + } + }; + + // Event listeners + unicodeToPunycodeBtn.addEventListener('click', convertUnicodeToPunycode); + punycodeToUnicodeBtn.addEventListener('click', convertPunycodeToUnicode); + + // Auto-convert on input (with debounce) + let unicodeTimeout, punycodeTimeout; + + unicodeInput.addEventListener('input', () => { + clearTimeout(unicodeTimeout); + unicodeTimeout = setTimeout(convertUnicodeToPunycode, 500); + }); + + punycodeInput.addEventListener('input', () => { + clearTimeout(punycodeTimeout); + punycodeTimeout = setTimeout(convertPunycodeToUnicode, 500); + }); + + // Paste support + unicodeInput.addEventListener('paste', () => { + setTimeout(convertUnicodeToPunycode, 100); + }); + + punycodeInput.addEventListener('paste', () => { + setTimeout(convertPunycodeToUnicode, 100); + }); +} diff --git a/assets/js/theme.js b/assets/js/theme.js new file mode 100644 index 0000000..a8a7e8f --- /dev/null +++ b/assets/js/theme.js @@ -0,0 +1,197 @@ +// DNS Things - Theme and UI Utilities + +// Theme toggle functionality +function initTheme() { + const themeToggleBtn = document.getElementById('theme-toggle'); + const lightIcon = document.getElementById('theme-toggle-light-icon'); + const darkIcon = document.getElementById('theme-toggle-dark-icon'); + + if (!themeToggleBtn || !lightIcon || !darkIcon) return; + + // Check for saved theme preference or default to system preference + const savedTheme = localStorage.getItem('theme'); + const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + let currentTheme = savedTheme || (systemPrefersDark ? 'dark' : 'light'); + + // Apply initial theme + if (currentTheme === 'dark') { + document.documentElement.classList.add('dark'); + lightIcon.classList.add('hidden'); + darkIcon.classList.remove('hidden'); + } else { + document.documentElement.classList.remove('dark'); + lightIcon.classList.remove('hidden'); + darkIcon.classList.add('hidden'); + } + + // Toggle theme function + function toggleTheme() { + if (document.documentElement.classList.contains('dark')) { + document.documentElement.classList.remove('dark'); + lightIcon.classList.remove('hidden'); + darkIcon.classList.add('hidden'); + localStorage.setItem('theme', 'light'); + } else { + document.documentElement.classList.add('dark'); + lightIcon.classList.add('hidden'); + darkIcon.classList.remove('hidden'); + localStorage.setItem('theme', 'dark'); + } + } + + themeToggleBtn.addEventListener('click', toggleTheme); +} + +// Tab descriptions for dynamic updates +const tabDescriptions = { + 'dns-lookup-tab': 'Perform DNS lookups to resolve domain names and check DNS records', + 'reverse-dns-tab': 'Find domain names associated with IP addresses using reverse DNS', + 'whois-tab': 'Get detailed registration and ownership information for domains and IPs', + 'punycode-tab': 'Convert international domain names between Unicode and Punycode formats', + 'ip-tools-tab': 'Comprehensive IP address utilities including validation, conversion, and analysis' +}; + +// Tab switching functionality +function initTabs() { + const tabButtons = document.querySelectorAll('.tab-btn'); + const tabContents = document.querySelectorAll('.tab-content'); + const descriptionElement = document.querySelector('p.text-lg.text-gray-500.mt-2'); + + tabButtons.forEach(button => { + button.addEventListener('click', () => { + const targetTab = button.getAttribute('data-tab'); + + // Remove active class from all buttons and contents + tabButtons.forEach(btn => { + btn.classList.remove('active', 'text-indigo-600', 'dark:text-indigo-400', 'bg-white', 'dark:bg-gray-800', 'shadow'); + btn.classList.add('text-gray-600', 'dark:text-gray-300', 'hover:bg-white', 'hover:dark:bg-gray-800/50'); + }); + tabContents.forEach(content => { + content.classList.remove('active'); + content.classList.add('hidden'); + }); + + // Add active class to clicked button and show corresponding content + button.classList.remove('text-gray-600', 'dark:text-gray-300', 'hover:bg-white', 'hover:dark:bg-gray-800/50'); + button.classList.add('active', 'text-indigo-600', 'dark:text-indigo-400', 'bg-white', 'dark:bg-gray-800', 'shadow'); + + const targetContent = document.getElementById(targetTab); + if (targetContent) { + targetContent.classList.remove('hidden'); + targetContent.classList.add('active'); + } + + // Update description text + if (descriptionElement && tabDescriptions[targetTab]) { + descriptionElement.textContent = tabDescriptions[targetTab]; + } + }); + }); + + // Set initial description based on active tab + const activeTab = document.querySelector('.tab-btn.active'); + if (activeTab && descriptionElement) { + const activeTabId = activeTab.getAttribute('data-tab'); + if (tabDescriptions[activeTabId]) { + descriptionElement.textContent = tabDescriptions[activeTabId]; + } + } +} + +// Copy to clipboard functionality +function copyToClipboard(text, button) { + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text).then(() => { + showCopyFeedback(button); + }).catch(err => { + console.error('Failed to copy: ', err); + fallbackCopyTextToClipboard(text, button); + }); + } else { + fallbackCopyTextToClipboard(text, button); + } +} + +// Make copyToClipboard globally available +window.copyToClipboard = copyToClipboard; + +function fallbackCopyTextToClipboard(text, button) { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + showCopyFeedback(button); + } catch (err) { + console.error('Fallback: Oops, unable to copy', err); + } + + document.body.removeChild(textArea); +} + +function showCopyFeedback(button) { + const originalHTML = button.innerHTML; + button.innerHTML = ` + + + + `; + button.classList.add('bg-green-500'); + + setTimeout(() => { + button.innerHTML = originalHTML; + button.classList.remove('bg-green-500'); + }, 1000); +} + +// Initialize copy buttons for the original HTML structure +function initCopyButtons() { + document.querySelectorAll('.copy-btn').forEach(button => { + button.addEventListener('click', function(e) { + e.preventDefault(); + const targetId = this.getAttribute('data-copy-target'); + const targetElement = document.getElementById(targetId); + const copyIcon = this.querySelector('.copy-icon'); + const copyFeedback = this.querySelector('.copy-feedback'); + + if (targetElement) { + const textToCopy = targetElement.textContent.trim(); + + // Skip if text is loading or not available + if (!textToCopy || textToCopy === 'loading...' || textToCopy === 'Not available') { + return; + } + + // Try to use the modern clipboard API + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(textToCopy).then(() => { + showCopyFeedback(copyIcon, copyFeedback); + }).catch(err => { + console.error('Failed to copy: ', err); + fallbackCopyTextToClipboard(textToCopy, copyIcon, copyFeedback); + }); + } else { + // Fallback for older browsers + fallbackCopyTextToClipboard(textToCopy, copyIcon, copyFeedback); + } + } + }); + }); +} + +function showCopyFeedback(copyIcon, copyFeedback) { + if (copyIcon) copyIcon.classList.add('hidden'); + if (copyFeedback) copyFeedback.classList.remove('hidden'); + + setTimeout(() => { + if (copyIcon) copyIcon.classList.remove('hidden'); + if (copyFeedback) copyFeedback.classList.add('hidden'); + }, 2000); +} diff --git a/assets/js/whois.js b/assets/js/whois.js new file mode 100644 index 0000000..efce606 --- /dev/null +++ b/assets/js/whois.js @@ -0,0 +1,196 @@ +// DNS Things - Whois Module + +// Initialize Whois lookup form +function initWhoisForm() { + const form = document.getElementById('whois-form'); + const input = document.getElementById('whois-input'); + const results = document.getElementById('whois-results'); + const loader = document.getElementById('whois-loader'); + + if (!form || !input || !results || !loader) return; + + form.addEventListener('submit', async (e) => { + e.preventDefault(); + + const domain = input.value.trim(); + if (!domain) return; + + // Show loader + loader.classList.remove('hidden'); + results.innerHTML = ''; + + try { + // Use DNS-based domain information lookup as a free alternative + const recordTypes = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'SOA']; + + const queries = recordTypes.map(type => + fetch(`https://dns.google/resolve?name=${encodeURIComponent(domain)}&type=${type}`) + .then(response => response.json()) + .then(data => ({ type, data })) + .catch(error => ({ type, error })) + ); + + const responses = await Promise.all(queries); + + // Process DNS data + const dnsData = {}; + const ipAddresses = { ipv4: [], ipv6: [] }; + + responses.forEach(({ type, data, error }) => { + if (!error && data.Answer && data.Answer.length > 0) { + dnsData[type] = data.Answer.map(record => record.data); + + if (type === 'A') { + ipAddresses.ipv4.push(...data.Answer.map(record => record.data)); + } else if (type === 'AAAA') { + ipAddresses.ipv6.push(...data.Answer.map(record => record.data)); + } + } + }); + + // Determine TLD registry + const tld = domain.split('.').pop().toLowerCase(); + const tldRegistries = { + 'com': 'Verisign', + 'net': 'Verisign', + 'org': 'Public Interest Registry (PIR)', + 'info': 'Afilias', + 'biz': 'NeuStar', + 'name': 'Verisign', + 'mobi': 'Afilias', + 'travel': 'Tralliance', + 'museum': 'MuseDoma', + 'coop': 'DotCooperation', + 'aero': 'SITA', + 'pro': 'RegistryPro', + 'edu': 'Educause', + 'gov': 'General Services Administration', + 'mil': 'DoD Network Information Center', + 'int': 'IANA' + }; + + // Build results + let resultsHTML = '
'; + + // Domain Information + resultsHTML += ` +
+

Domain Information

+
+
+
Domain: ${domain}
+
TLD Registry: ${tldRegistries[tld] || 'Unknown'}
+
Website: ${dnsData.A || dnsData.AAAA ? '✅ Available' : '❌ Not available'}
+
Email: ${dnsData.MX ? '✅ Supported' : '❌ Not supported'}
+
+
+

Note: This is a DNS-based lookup providing practical domain information. For detailed registration data, use official whois services.

+
+
+
+ `; + + // Name Servers + if (dnsData.NS) { + resultsHTML += ` +
+

Name Servers (${dnsData.NS.length})

+
    + `; + dnsData.NS.forEach(ns => { + const cleanNS = ns.replace(/\.$/, ''); + resultsHTML += ` +
  • + ${cleanNS} + +
  • + `; + }); + resultsHTML += '
'; + } + + // DNS Records Summary + resultsHTML += ` +
+

DNS Records Summary

+
+ `; + + recordTypes.forEach(type => { + const hasRecord = dnsData[type] && dnsData[type].length > 0; + resultsHTML += ` +
+
${type}
+
${hasRecord ? '✅' : '❌'}
+
+ `; + }); + + resultsHTML += '
'; + + // IP Addresses + if (ipAddresses.ipv4.length > 0 || ipAddresses.ipv6.length > 0) { + resultsHTML += ` +
+

IP Addresses

+
+ `; + + ipAddresses.ipv4.forEach(ip => { + resultsHTML += ` +
+ ${ip} + +
+ `; + }); + + ipAddresses.ipv6.forEach(ip => { + resultsHTML += ` +
+ ${ip} + +
+ `; + }); + + resultsHTML += '
'; + } + + resultsHTML += '
'; + results.innerHTML = resultsHTML; + + } catch (error) { + console.error('Whois lookup error:', error); + results.innerHTML = ` +
+

Error

+

Failed to lookup domain information. Please try again.

+
+ `; + } finally { + loader.classList.add('hidden'); + } + }); + + // Auto-submit on paste + input.addEventListener('paste', () => { + setTimeout(() => { + if (input.value.trim()) { + form.dispatchEvent(new Event('submit')); + } + }, 100); + }); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..82ddd7e --- /dev/null +++ b/index.html @@ -0,0 +1,292 @@ + + + + + + DNS Things + + + + + + + + + + + + +
+ +
+ +
+ +
+
+
+ +
+
+

Your IP Address

+

Your public IP addresses

+
+
+
+
+ + +
+
loading...
+
+
+
+ + +
+
loading...
+
+
+
+ +
+
+
+ + +
+
+
+

DNS Tools

+

Various DNS utilities to help you analyze domains and IPs

+
+ +
+ +
+
+ + + + + +
+
+ + +
+ +
+

DNS Lookup

+
+ + +
+
+ +
+ + + + + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + +