Initial commit: DNS Test - DNS health checking tool

Go backend (miekg/dns) + Nuxt 3 frontend (Tailwind CSS v4).

8 check categories, 52 checks total:
- Overview: @ record, WWW, MX with ASN/provider lookup
- Domain Registration: expiry, registrar (RDAP + whois fallback)
- Parent Delegation: NS records, glue, consistency
- Nameservers: 17 checks (reachability, auth, recursion, TCP/UDP, AXFR, etc.)
- SOA: serial consistency, timing values
- Mail (MX): 11 checks (CNAME, PTR, public IPs, consistency)
- Mail Auth: SPF, DKIM, DMARC
- WWW: A record, CNAME

Features:
- SSE streaming (results appear as each category completes)
- SQLite history (modernc.org/sqlite)
- Rate limiting, CORS, request logging
- Dark mode, responsive design
This commit is contained in:
2026-03-20 13:39:57 +02:00
commit a70f3262e0
37 changed files with 15629 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
import type { DnsReport, Category, Summary } from '~/types/dns'
interface StreamEvent {
domain: string
category: Category
done?: boolean
}
export function useDnsCheck() {
const config = useRuntimeConfig()
const report = ref<DnsReport | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const progress = ref(0)
const totalCategories = 8
async function checkDomain(domain: string) {
loading.value = true
error.value = null
report.value = null
progress.value = 0
// Initialize empty report
const startTime = Date.now()
const partialReport: DnsReport = {
domain,
timestamp: new Date().toISOString(),
duration_ms: 0,
summary: { pass: 0, warn: 0, fail: 0, info: 0 },
categories: [],
}
try {
const url = `${config.public.apiBase}/check/stream?domain=${encodeURIComponent(domain)}`
const eventSource = new EventSource(url)
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
eventSource.close()
reject(new Error('Request timed out after 30 seconds'))
}, 30000)
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data) as StreamEvent & { done?: boolean }
if (data.done) {
clearTimeout(timeout)
eventSource.close()
partialReport.duration_ms = Date.now() - startTime
resolve()
return
}
if (data.category) {
partialReport.categories.push(data.category)
progress.value = partialReport.categories.length
// Recalculate summary
const summary: Summary = { pass: 0, warn: 0, fail: 0, info: 0 }
for (const cat of partialReport.categories) {
for (const check of cat.checks) {
summary[check.status]++
}
}
partialReport.summary = summary
partialReport.duration_ms = Date.now() - startTime
// Update reactively
report.value = { ...partialReport, categories: [...partialReport.categories] }
}
} catch {
// ignore parse errors
}
}
eventSource.onerror = () => {
clearTimeout(timeout)
eventSource.close()
if (partialReport.categories.length > 0) {
// Got some results, show them
partialReport.duration_ms = Date.now() - startTime
report.value = { ...partialReport }
resolve()
} else {
reject(new Error('Connection to DNS check server failed'))
}
}
})
} catch (e: any) {
// Fallback to non-streaming API
try {
const data = await $fetch<DnsReport>(`${config.public.apiBase}/check?domain=${encodeURIComponent(domain)}`)
report.value = data
progress.value = totalCategories
} catch (e2: any) {
error.value = e2.data?.error || e2.message || e.message || 'Failed to check domain'
}
} finally {
loading.value = false
progress.value = totalCategories
}
}
return { report, loading, error, progress, totalCategories, checkDomain }
}