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
This commit is contained in:
231
assets/css/main.css
Normal file
231
assets/css/main.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
BIN
assets/img/hero.webp
Normal file
BIN
assets/img/hero.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
262
assets/js/dns-tools.js
Normal file
262
assets/js/dns-tools.js
Normal file
@@ -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 = `
|
||||||
|
<div class="p-4 bg-yellow-50 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300 rounded-lg">
|
||||||
|
<h3 class="font-medium mb-1">No DNS records found</h3>
|
||||||
|
<p class="text-sm">The domain "${domain}" does not have any DNS records or does not exist.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
let resultsHTML = '<div class="space-y-4">';
|
||||||
|
|
||||||
|
Object.entries(groupedResults).forEach(([type, records]) => {
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="dns-result-item p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 class="font-semibold text-gray-800 dark:text-white mb-3">${type} Records (${records.length})</h4>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
`;
|
||||||
|
|
||||||
|
records.forEach(record => {
|
||||||
|
resultsHTML += `
|
||||||
|
<li class="flex justify-between items-center p-2 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="font-mono text-sm break-all">${record.data}</span>
|
||||||
|
<button onclick="copyToClipboard('${record.data}', this)" class="ml-2 p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" 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>
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultsHTML += '</ul></div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
resultsHTML += '</div>';
|
||||||
|
results.innerHTML = resultsHTML;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNS lookup error:', error);
|
||||||
|
results.innerHTML = `
|
||||||
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
||||||
|
<h3 class="font-medium mb-1">Error</h3>
|
||||||
|
<p class="text-sm">Failed to perform DNS lookup. Please try again.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} 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 = '<div class="space-y-2">';
|
||||||
|
data.Answer.forEach(record => {
|
||||||
|
const hostname = record.data.replace(/\.$/, '');
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="flex justify-between items-center p-3 bg-green-50 dark:bg-green-900/30 rounded-lg">
|
||||||
|
<div>
|
||||||
|
<div class="font-medium text-green-800 dark:text-green-200">Hostname found:</div>
|
||||||
|
<div class="font-mono text-sm text-green-700 dark:text-green-300">${hostname}</div>
|
||||||
|
</div>
|
||||||
|
<button onclick="copyToClipboard('${hostname}', this)" class="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>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
resultsHTML += '</div>';
|
||||||
|
results.innerHTML = resultsHTML;
|
||||||
|
} else {
|
||||||
|
results.innerHTML = `
|
||||||
|
<div class="p-4 bg-yellow-50 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300 rounded-lg">
|
||||||
|
<h3 class="font-medium mb-1">No reverse DNS record found</h3>
|
||||||
|
<p class="text-sm">The IP address "${ip}" does not have a reverse DNS (PTR) record.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Reverse DNS lookup error:', error);
|
||||||
|
results.innerHTML = `
|
||||||
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
||||||
|
<h3 class="font-medium mb-1">Error</h3>
|
||||||
|
<p class="text-sm">${error.message || 'Failed to perform reverse DNS lookup. Please check the IP address format.'}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} finally {
|
||||||
|
loader.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-submit on paste
|
||||||
|
input.addEventListener('paste', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (input.value.trim()) {
|
||||||
|
form.dispatchEvent(new Event('submit'));
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
437
assets/js/ip-tools.js
Normal file
437
assets/js/ip-tools.js
Normal file
@@ -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 = `
|
||||||
|
<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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
33
assets/js/main.js
Normal file
33
assets/js/main.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
176
assets/js/punycode.js
Normal file
176
assets/js/punycode.js
Normal file
@@ -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 = `
|
||||||
|
<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">Punycode Result:</h5>
|
||||||
|
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border">${converted}</div>
|
||||||
|
${isConverted ? '<p class="text-xs text-green-600 dark:text-green-400 mt-2">✅ Conversion applied</p>' : '<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">ℹ️ No conversion needed (ASCII only)</p>'}
|
||||||
|
</div>
|
||||||
|
<button onclick="copyToClipboard('${converted}', 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>
|
||||||
|
`;
|
||||||
|
} catch (error) {
|
||||||
|
punycodeResult.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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 = `
|
||||||
|
<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">Unicode Result:</h5>
|
||||||
|
<div class="font-mono text-sm break-all p-2 bg-white dark:bg-gray-800 rounded border">${converted}</div>
|
||||||
|
${isConverted ? '<p class="text-xs text-green-600 dark:text-green-400 mt-2">✅ Conversion applied</p>' : '<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">ℹ️ No conversion needed (no punycode found)</p>'}
|
||||||
|
</div>
|
||||||
|
<button onclick="copyToClipboard('${converted}', 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 2v8a2 2 0 002 2z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} catch (error) {
|
||||||
|
unicodeResult.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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
197
assets/js/theme.js
Normal file
197
assets/js/theme.js
Normal file
@@ -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 = `
|
||||||
|
<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="M5 13l4 4L19 7"></path>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
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);
|
||||||
|
}
|
||||||
196
assets/js/whois.js
Normal file
196
assets/js/whois.js
Normal file
@@ -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 = '<div class="space-y-4">';
|
||||||
|
|
||||||
|
// Domain Information
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="p-4 bg-blue-50 dark:bg-blue-900/30 rounded-lg">
|
||||||
|
<h4 class="font-semibold text-blue-800 dark:text-blue-200 mb-3">Domain Information</h4>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<div><strong>Domain:</strong> ${domain}</div>
|
||||||
|
<div><strong>TLD Registry:</strong> ${tldRegistries[tld] || 'Unknown'}</div>
|
||||||
|
<div><strong>Website:</strong> ${dnsData.A || dnsData.AAAA ? '✅ Available' : '❌ Not available'}</div>
|
||||||
|
<div><strong>Email:</strong> ${dnsData.MX ? '✅ Supported' : '❌ Not supported'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-blue-600 dark:text-blue-400">
|
||||||
|
<p><strong>Note:</strong> This is a DNS-based lookup providing practical domain information. For detailed registration data, use official whois services.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Name Servers
|
||||||
|
if (dnsData.NS) {
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 class="font-semibold text-gray-800 dark:text-white mb-3">Name Servers (${dnsData.NS.length})</h4>
|
||||||
|
<ul class="space-y-1">
|
||||||
|
`;
|
||||||
|
dnsData.NS.forEach(ns => {
|
||||||
|
const cleanNS = ns.replace(/\.$/, '');
|
||||||
|
resultsHTML += `
|
||||||
|
<li class="flex justify-between items-center p-2 bg-gray-50 dark:bg-gray-700 rounded">
|
||||||
|
<span class="font-mono text-sm">${cleanNS}</span>
|
||||||
|
<button onclick="copyToClipboard('${cleanNS}', this)" class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" title="Copy">
|
||||||
|
<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>
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
resultsHTML += '</ul></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS Records Summary
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 class="font-semibold text-gray-800 dark:text-white mb-3">DNS Records Summary</h4>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||||
|
`;
|
||||||
|
|
||||||
|
recordTypes.forEach(type => {
|
||||||
|
const hasRecord = dnsData[type] && dnsData[type].length > 0;
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="text-center p-2 rounded ${hasRecord ? 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400'}">
|
||||||
|
<div class="font-semibold">${type}</div>
|
||||||
|
<div class="text-sm">${hasRecord ? '✅' : '❌'}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultsHTML += '</div></div>';
|
||||||
|
|
||||||
|
// IP Addresses
|
||||||
|
if (ipAddresses.ipv4.length > 0 || ipAddresses.ipv6.length > 0) {
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 class="font-semibold text-gray-800 dark:text-white mb-3">IP Addresses</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
`;
|
||||||
|
|
||||||
|
ipAddresses.ipv4.forEach(ip => {
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="whois-badge ipv4">${ip}</span>
|
||||||
|
<button onclick="copyToClipboard('${ip}', this)" class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" title="Copy">
|
||||||
|
<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>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipAddresses.ipv6.forEach(ip => {
|
||||||
|
resultsHTML += `
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="whois-badge ipv6">${ip}</span>
|
||||||
|
<button onclick="copyToClipboard('${ip}', this)" class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" title="Copy">
|
||||||
|
<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>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultsHTML += '</div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsHTML += '</div>';
|
||||||
|
results.innerHTML = resultsHTML;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Whois lookup error:', error);
|
||||||
|
results.innerHTML = `
|
||||||
|
<div class="p-4 bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg">
|
||||||
|
<h3 class="font-medium mb-1">Error</h3>
|
||||||
|
<p class="text-sm">Failed to lookup domain information. Please try again.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} finally {
|
||||||
|
loader.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-submit on paste
|
||||||
|
input.addEventListener('paste', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (input.value.trim()) {
|
||||||
|
form.dispatchEvent(new Event('submit'));
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
292
index.html
Normal file
292
index.html
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DNS Things</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<!-- Punycode library for proper IDN conversion -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/punycode/2.3.1/punycode.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<link rel="stylesheet" href="assets/css/main.css">
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: 'class',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* Simple transition for theme change */
|
||||||
|
body, .bg-white, .bg-gray-800, .text-gray-800, .text-white, .bg-gray-50, .bg-gray-700 {
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
transition: background-color 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
.nav-link:hover {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
}
|
||||||
|
.dark .nav-link {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
.dark .nav-link:hover {
|
||||||
|
background-color: #374151;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="fixed top-0 left-0 right-0 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm shadow-md z-50">
|
||||||
|
<nav class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex items-center justify-between h-16">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<a href="#" class="text-2xl font-bold text-gray-800 dark:text-white">DNS Things</a>
|
||||||
|
</div>
|
||||||
|
<div class="hidden md:flex items-center space-x-4">
|
||||||
|
<a href="#your-ip" class="nav-link">Your IP</a>
|
||||||
|
<a href="#dns-lookup" class="nav-link">DNS Tools</a>
|
||||||
|
<button id="theme-toggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200">
|
||||||
|
<svg id="theme-toggle-light-icon" class="w-6 h-6 hidden" fill="currentColor" viewBox="0 0 20 20"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 5.05a1 1 0 00-1.414 1.414l.707.707a1 1 0 001.414-1.414l-.707-.707zM3 11a1 1 0 100-2H2a1 1 0 100 2h1z"></path></svg>
|
||||||
|
<svg id="theme-toggle-dark-icon" class="w-6 h-6 hidden" fill="currentColor" viewBox="0 0 20 20"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="pt-16">
|
||||||
|
<!-- Hero Section: Your IP -->
|
||||||
|
<section id="your-ip" class="py-16 md:py-20">
|
||||||
|
<div class="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-10 gap-8 items-start">
|
||||||
|
<div class="hidden md:flex md:col-span-4 items-start justify-center mt-12">
|
||||||
|
<img src="/assets/img/hero.webp" class="w-full h-auto max-w-md" alt="DNS Things">
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:col-span-6 p-8 space-y-6 bg-white rounded-xl shadow-lg dark:bg-gray-800">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-4xl font-bold text-gray-800 dark:text-white">Your IP Address</h1>
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">Your public IP addresses</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="p-4 bg-gray-50 rounded-lg dark:bg-gray-700">
|
||||||
|
<div class="flex justify-between items-center mb-1">
|
||||||
|
<label for="ipv4" class="text-sm font-medium text-gray-700 dark:text-gray-300">IPv4 Address</label>
|
||||||
|
<button data-copy-target="ipv4" class="copy-btn p-1 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600"><span class="copy-icon"><svg class="w-5 h-5 text-gray-500 dark:text-gray-400" 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></span><span class="copy-feedback hidden text-sm text-green-600 dark:text-green-400">Copied!</span></button>
|
||||||
|
</div>
|
||||||
|
<div id="ipv4" class="p-2 w-full text-lg font-mono text-blue-600 bg-blue-100 rounded-md dark:bg-blue-900 dark:text-blue-300 break-all">loading...</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 bg-gray-50 rounded-lg dark:bg-gray-700">
|
||||||
|
<div class="flex justify-between items-center mb-1">
|
||||||
|
<label for="ipv6" class="text-sm font-medium text-gray-700 dark:text-gray-300">IPv6 Address</label>
|
||||||
|
<button data-copy-target="ipv6" class="copy-btn p-1 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600"><span class="copy-icon"><svg class="w-5 h-5 text-gray-500 dark:text-gray-400" 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></span><span class="copy-feedback hidden text-sm text-green-600 dark:text-green-400">Copied!</span></button>
|
||||||
|
</div>
|
||||||
|
<div id="ipv6" class="p-2 w-full text-lg font-mono text-purple-600 bg-purple-100 rounded-md dark:bg-purple-900 dark:text-purple-300 break-all">loading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- DNS Tools Section -->
|
||||||
|
<section id="dns-lookup" class="py-20 bg-white dark:bg-gray-800">
|
||||||
|
<div class="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h2 class="text-4xl font-bold text-gray-800 dark:text-white">DNS Tools</h2>
|
||||||
|
<p class="text-lg text-gray-500 dark:text-gray-400 mt-2">Various DNS utilities to help you analyze domains and IPs</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<!-- Vertical Tabs -->
|
||||||
|
<div class="w-full md:w-1/3 lg:w-1/4">
|
||||||
|
<div class="flex flex-col space-y-2 p-2 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||||
|
<button class="tab-btn active p-3 text-left rounded-md font-medium text-indigo-600 dark:text-indigo-400 bg-white dark:bg-gray-800 shadow" data-tab="dns-lookup-tab">
|
||||||
|
DNS Lookup
|
||||||
|
</button>
|
||||||
|
<button class="tab-btn p-3 text-left rounded-md font-medium text-gray-600 dark:text-gray-300 hover:bg-white hover:dark:bg-gray-800/50" data-tab="reverse-dns-tab">
|
||||||
|
Reverse DNS Lookup
|
||||||
|
</button>
|
||||||
|
<button class="tab-btn p-3 text-left rounded-md font-medium text-gray-600 dark:text-gray-300 hover:bg-white hover:dark:bg-gray-800/50" data-tab="whois-tab">
|
||||||
|
Whois Lookup
|
||||||
|
</button>
|
||||||
|
<button class="tab-btn p-3 text-left rounded-md font-medium text-gray-600 dark:text-gray-300 hover:bg-white hover:dark:bg-gray-800/50" data-tab="punycode-tab">
|
||||||
|
IDN Punycode
|
||||||
|
</button>
|
||||||
|
<button class="tab-btn p-3 text-left rounded-md font-medium text-gray-600 dark:text-gray-300 hover:bg-white hover:dark:bg-gray-800/50" data-tab="ip-tools-tab">
|
||||||
|
IP Tools
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab Content -->
|
||||||
|
<div class="w-full md:w-2/3 lg:w-3/4">
|
||||||
|
<!-- DNS Lookup Tab -->
|
||||||
|
<div id="dns-lookup-tab" class="tab-content active">
|
||||||
|
<h3 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-white">DNS Lookup</h3>
|
||||||
|
<form id="dns-form" class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="domain-input" placeholder="e.g., google.com" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="submit" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Check DNS</button>
|
||||||
|
</form>
|
||||||
|
<div id="dns-results-container" class="mt-6"></div>
|
||||||
|
<div id="dns-loader" class="hidden text-center mt-8">
|
||||||
|
<p class="text-gray-600 dark:text-gray-300">Loading DNS records...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reverse DNS Tab -->
|
||||||
|
<div id="reverse-dns-tab" class="tab-content hidden">
|
||||||
|
<h3 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-white">Reverse DNS Lookup</h3>
|
||||||
|
<form id="reverse-dns-form" class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="ip-input" placeholder="e.g., 8.8.8.8" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="submit" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Lookup</button>
|
||||||
|
</form>
|
||||||
|
<div id="reverse-dns-results" class="mt-6"></div>
|
||||||
|
<div id="reverse-dns-loader" class="hidden text-center mt-8">
|
||||||
|
<p class="text-gray-600 dark:text-gray-300">Looking up reverse DNS...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Whois Lookup Tab -->
|
||||||
|
<div id="whois-tab" class="tab-content hidden">
|
||||||
|
<h3 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-white">Whois Lookup</h3>
|
||||||
|
<form id="whois-form" class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="whois-input" placeholder="e.g., google.com" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="submit" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Lookup</button>
|
||||||
|
</form>
|
||||||
|
<div id="whois-results" class="mt-6"></div>
|
||||||
|
<div id="whois-loader" class="hidden text-center mt-8">
|
||||||
|
<p class="text-gray-600 dark:text-gray-300">Looking up domain information...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IDN Punycode Converter Tab -->
|
||||||
|
<div id="punycode-tab" class="tab-content hidden">
|
||||||
|
<h3 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-white">IDN Punycode Converter</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Unicode to Punycode -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-lg font-medium mb-3 text-gray-800 dark:text-white">Unicode to Punycode</h4>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="unicode-input" placeholder="e.g., 中国.com, россия.рф, العربية.com" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="button" id="unicode-to-punycode-btn" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Convert</button>
|
||||||
|
</div>
|
||||||
|
<div id="punycode-result" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Punycode to Unicode -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<h4 class="text-lg font-medium mb-3 text-gray-800 dark:text-white">Punycode to Unicode</h4>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="punycode-input" placeholder="e.g., xn--fiqs8s.com, xn--e1afmkfd.xn--p1ai" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="button" id="punycode-to-unicode-btn" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Convert</button>
|
||||||
|
</div>
|
||||||
|
<div id="unicode-result" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Examples -->
|
||||||
|
<div class="p-4 bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-lg">
|
||||||
|
<h4 class="font-medium mb-2">📚 Examples</h4>
|
||||||
|
<div class="text-sm space-y-1">
|
||||||
|
<div><strong>Chinese:</strong> 中国.com → xn--fiqs8s.com</div>
|
||||||
|
<div><strong>Russian:</strong> россия.рф → xn--e1afmkfd.xn--p1ai</div>
|
||||||
|
<div><strong>Arabic:</strong> العربية.com → xn--mgbqf7g0ndx.com</div>
|
||||||
|
<div><strong>Japanese:</strong> 日本.com → xn--wgbl6a.com</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IP Tools Tab -->
|
||||||
|
<div id="ip-tools-tab" class="tab-content hidden">
|
||||||
|
<h3 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-white">IP Address Tools</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- IP Validation -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-lg font-medium mb-3 text-gray-800 dark:text-white">IP Address Validation</h4>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="ip-validate-input" placeholder="e.g., 192.168.1.1, 2001:db8::1" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="button" id="ip-validate-btn" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Validate</button>
|
||||||
|
</div>
|
||||||
|
<div id="ip-validate-result" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IPv4 to IPv6 Mapping -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-lg font-medium mb-3 text-gray-800 dark:text-white">IPv4 to IPv6 Mapping</h4>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="ipv4-to-ipv6-input" placeholder="e.g., 192.168.1.1" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="button" id="ipv4-to-ipv6-btn" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Convert</button>
|
||||||
|
</div>
|
||||||
|
<div id="ipv4-to-ipv6-result" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IPv6 Compression/Expansion -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-lg font-medium mb-3 text-gray-800 dark:text-white">IPv6 Compression/Expansion</h4>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="ipv6-compress-input" placeholder="e.g., 2001:0db8:0000:0000:0000:0000:0000:0001" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="button" id="ipv6-compress-btn" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Process</button>
|
||||||
|
</div>
|
||||||
|
<div id="ipv6-compress-result" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IP to Binary/Decimal -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-lg font-medium mb-3 text-gray-800 dark:text-white">IP to Binary/Decimal</h4>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="ip-binary-input" placeholder="e.g., 192.168.1.1" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="button" id="ip-binary-btn" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Convert</button>
|
||||||
|
</div>
|
||||||
|
<div id="ip-binary-result" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subnet Calculator -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<h4 class="text-lg font-medium mb-3 text-gray-800 dark:text-white">Subnet Calculator</h4>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
|
<input type="text" id="subnet-input" placeholder="e.g., 192.168.1.0/24" class="flex-1 px-4 py-3 rounded-lg bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="off">
|
||||||
|
<button type="button" id="subnet-btn" class="bg-indigo-600 text-white font-bold px-6 py-3 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 whitespace-nowrap">Calculate</button>
|
||||||
|
</div>
|
||||||
|
<div id="subnet-result" class="mt-4"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Examples -->
|
||||||
|
<div class="p-4 bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-lg">
|
||||||
|
<h4 class="font-medium mb-2">🔧 Examples</h4>
|
||||||
|
<div class="text-sm space-y-1">
|
||||||
|
<div><strong>IPv4:</strong> 192.168.1.1, 10.0.0.1, 172.16.0.1</div>
|
||||||
|
<div><strong>IPv6:</strong> 2001:db8::1, ::1, fe80::1</div>
|
||||||
|
<div><strong>CIDR:</strong> 192.168.1.0/24, 10.0.0.0/8</div>
|
||||||
|
<div><strong>IPv6 Full:</strong> 2001:0db8:0000:0000:0000:0000:0000:0001</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 py-6">
|
||||||
|
<p class="text-center text-sm text-gray-500">
|
||||||
|
© <span id="current-year">2025</span> Dewe Toolsites - DNS Things.
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- JavaScript Files -->
|
||||||
|
<script src="assets/js/theme.js"></script>
|
||||||
|
<script src="assets/js/dns-tools.js"></script>
|
||||||
|
<script src="assets/js/whois.js"></script>
|
||||||
|
<script src="assets/js/punycode.js"></script>
|
||||||
|
<script src="assets/js/ip-tools.js"></script>
|
||||||
|
<script src="assets/js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user