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
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
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 }
|
|
}
|