🚩 增加 Dns 切换功能

This commit is contained in:
7836246 2024-03-03 13:37:23 +08:00
parent 999d8b7076
commit 9a64049c60
10 changed files with 341 additions and 82 deletions

View File

@ -0,0 +1,65 @@
<script lang="ts" setup>
import {useTimeStore} from "~/stores/time";
const switchLocalePath = useSwitchLocalePath()
const timeStore = useTimeStore()
const availableDns = [
{ iso: '', name: '本地 DNS', flag: 'material-symbols:dns-outline' },
{ iso: 'google', name: 'Google', flag: 'flat-color-icons:google' },
{ iso: 'aliyun', name: 'AliYun', flag: 'ant-design:aliyun-outlined' },
{ iso: 'tencent', name: 'Tencent', flag: 'emojione:cloud' },
{ iso: 'cloudflare', name: 'CloudFlare', flag: 'skill-icons:cloudflare-light' },
// ...
]
const handlePost = async (iso: string) => {
timeStore.setDnsServer(iso)
//
await refreshNuxtData('dns')
}
</script>
<template>
<div>
<HeadlessListbox
v-model="timeStore.getDnsServer"
as="div"
class="relative flex items-center"
>
<HeadlessListboxLabel class="sr-only">
Change Language
</HeadlessListboxLabel>
<HeadlessListboxButton type="button" title="Change Language">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
>
<Icon name="gg:select-o" class=" text-lg dark:text-white" size="20" />
</div>
</HeadlessListboxButton>
<HeadlessListboxOptions
class="absolute top-full right-0 z-[999] mt-2 w-40 overflow-hidden rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
>
<div
v-for="lang in availableDns"
:key="lang.iso"
@click="handlePost(lang.iso)"
class="flex w-full cursor-pointer items-center justify-between py-2 px-3"
:class="{
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
timeStore.getDnsServer === lang.iso,
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
timeStore.getDnsServer !== lang.iso,
}"
>
<span class="truncate">
{{ lang.name }}
</span>
<span class="flex items-center justify-center text-sm">
<Icon :name="lang.flag" class="text-base" size="20" />
</span>
</div>
</HeadlessListboxOptions>
</HeadlessListbox>
</div>
</template>

View File

@ -0,0 +1,45 @@
<script setup lang="ts">
const props = defineProps({
data:{
type: Object,
required: true
}
});
const {t} = useI18n()
</script>
<template>
<div class="flex bg-gray-100 p-8">
<div class="w-full mx-auto">
<div class="bg-white shadow-lg rounded-lg p-6 mb-4" v-for="item in data.Answer">
<h2 class="text-xl font-bold">{{ item.name }}</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<p class="font-semibold">Type</p>
<p>{{ item.type }}</p>
</div>
<div>
<p class="font-semibold">TTL</p>
<p>{{ item.TTL }}</p>
</div>
<div class="col-span-2">
<p class="font-semibold">Data</p>
<p>{{ item.data }}</p>
</div>
</div>
<div class="flex space-x-2 mt-4">
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RD: {{ data.RD }}</span>
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RA: {{ data.RA }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">TC: {{ data.TC }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">AD: {{ data.AD }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">CD: {{ data.CD }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,61 @@
<script setup lang="ts">
defineProps({
data: {
type: Object,
required: true
}
})
const {t} = useI18n()
</script>
<template>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div v-if="data.aRecords">
<h3 class="font-semibold text-lg text-blue-600 mb-2">{{ t('dns.aRecord') }}</h3>
<div class="border rounded-lg p-4 bg-blue-50">
<ul
class="list-none space-y-2">
<li v-for="(record, index) in data.aRecords" :key="'a-record-' + index" class="flex justify-between items-center">
<span class="font-medium text-gray-700">IP:</span>
<span class="font-normal text-gray-600">{{ record }}</span>
</li>
</ul>
</div>
</div>
<div v-if="data.nsRecords">
<h3 class="font-semibold text-lg text-green-600 mb-2">{{ t('dns.nsRecord') }}</h3>
<div class="border rounded-lg p-4 bg-green-50">
<ul
class="list-none space-y-2">
<li v-for="(record, index) in data.nsRecords" :key="'ns-record-' + index" class="flex justify-between items-center">
<span class="font-normal text-gray-600">{{ record }}</span>
</li>
</ul>
</div>
</div>
<div
v-if="data.soaRecord"
class="md:col-span-2">
<h3 class="font-semibold text-lg text-purple-600 mb-2">{{ t('dns.soaRecord') }}</h3>
<div class="border rounded-lg p-4 bg-purple-50">
<ul class="list-none space-y-2">
<li
><span class="font-medium text-gray-700">nsname:</span> <span class="font-normal text-gray-600">{{ data.soaRecord.nsname }}</span></li>
<li><span class="font-medium text-gray-700">hostmaster:</span> <span class="font-normal text-gray-600">{{ data.soaRecord.hostmaster }}</span></li>
<li><span class="font-medium text-gray-700">serial:</span> <span class="font-normal text-gray-600">{{ data.soaRecord.serial }}</span></li>
<li><span class="font-medium text-gray-700">refresh:</span> <span class="font-normal text-gray-600">{{ data.soaRecord.refresh }}</span></li>
<li><span class="font-medium text-gray-700">retry:</span> <span class="font-normal text-gray-600">{{ data.soaRecord.retry }}</span></li>
<li><span class="font-medium text-gray-700">expire TTL:</span> <span class="font-normal text-gray-600">{{ data.soaRecord.expire }}</span></li>
<li><span class="font-medium text-gray-700">minttl TTL:</span> <span class="font-normal text-gray-600">{{ data.soaRecord.minttl }}</span></li>
</ul>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
defineProps({
data: {
type: Object,
required: true
}
})
</script>
<template>
<div class="flex bg-gray-100 p-8">
<div class="w-full mx-auto">
<div class="bg-white shadow-lg rounded-lg p-6 mb-4" v-for="item in data.Answer">
<h2 class="text-xl font-bold">{{ item.name }}</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<p class="font-semibold">Type</p>
<p>{{ item.type }}</p>
</div>
<div>
<p class="font-semibold">TTL</p>
<p>{{ item.TTL }}</p>
</div>
<div class="col-span-2">
<p class="font-semibold">Data</p>
<p>{{ item.data }}</p>
</div>
</div>
<div class="flex space-x-2 mt-4">
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RD: {{ data.RD }}</span>
<span class="bg-green-200 text-green-800 px-2 py-1 rounded">RA: {{ data.RA }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">TC: {{ data.TC }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">AD: {{ data.AD }}</span>
<span class="bg-red-200 text-red-800 px-2 py-1 rounded">CD: {{ data.CD }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -14,7 +14,6 @@
"@nuxtjs/tailwindcss": "^6.11.4",
"@pinia/nuxt": "^0.5.1",
"nuxt": "^3.10.3",
"socks": "^2.8.1",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},

View File

@ -2,6 +2,7 @@
import {useStyleStore} from "~/stores/style";
import {AdjustTimeToUTCOffset} from "~/utils/utc";
import {useTimeStore} from "~/stores/time";
import DnsInfo from "~/components/dns/InfoList.vue";
const {t} = useI18n()
@ -9,21 +10,24 @@ const route = useRoute();
const {domain} = route.params;
const domainData = domain.replace(/_/g, '.')
const timeStore = useTimeStore()
const styleStore = useStyleStore()
const localePath = useLocalePath()
styleStore.setIsPage(true)
const {data, pending, error, refresh} = await useAsyncData(
'dns',
() => $fetch('/api/dns', {
method: 'POST',
body: JSON.stringify({domain: 'xukangr.com'})
body: {
domain: domainData,
dnsServer:timeStore.getDnsServer
}
})
)
const timeStore = useTimeStore()
const styleStore = useStyleStore()
styleStore.setIsPage(true)
const localePath = useLocalePath()
if (!error.value) {
styleStore.addOrUpdateHistory(
{
@ -54,50 +58,30 @@ useHead({
<div class="mt-5">
<div class="bg-white shadow-lg rounded-lg overflow-hidden">
<div class="p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-6">{{ t('dns.dnsResult') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="font-semibold text-lg text-blue-600 mb-2">{{ t('dns.aRecord') }}</h3>
<div class="border rounded-lg p-4 bg-blue-50">
<ul
v-if="data.A.length > 0"
class="list-none space-y-2">
<li v-for="(record, index) in data.A" :key="'a-record-' + index" class="flex justify-between items-center">
<span class="font-medium text-gray-700">IP:</span>
<span class="font-normal text-gray-600">{{ record.Record }}</span>
<span class="text-sm text-gray-500">TTL: {{ record.TTL }}</span>
</li>
</ul>
</div>
</div>
<div>
<h3 class="font-semibold text-lg text-green-600 mb-2">{{ t('dns.nsRecord') }}</h3>
<div class="border rounded-lg p-4 bg-green-50">
<ul
v-if="data.NS.length > 0"
class="list-none space-y-2">
<li v-for="(record, index) in data.NS" :key="'ns-record-' + index" class="flex justify-between items-center">
<span class="font-normal text-gray-600">{{ record.Record }}</span>
<span class="text-sm text-gray-500">TTL: {{ record.TTL }}</span>
</li>
</ul>
</div>
</div>
<div class="md:col-span-2">
<h3 class="font-semibold text-lg text-purple-600 mb-2">{{ t('dns.soaRecord') }}</h3>
<div class="border rounded-lg p-4 bg-purple-50">
<ul class="list-none space-y-2">
<li><span class="font-medium text-gray-700">MName:</span> <span class="font-normal text-gray-600">{{ data.SOA.MName }}</span></li>
<li><span class="font-medium text-gray-700">Email:</span> <span class="font-normal text-gray-600">{{ data.SOA.Email }}</span></li>
<li><span class="font-medium text-gray-700">Serial:</span> <span class="font-normal text-gray-600">{{ data.SOA.Serial }}</span></li>
<li><span class="font-medium text-gray-700">Refresh:</span> <span class="font-normal text-gray-600">{{ data.SOA.Refresh }}</span></li>
<li><span class="font-medium text-gray-700">Retry:</span> <span class="font-normal text-gray-600">{{ data.SOA.Retry }}</span></li>
<li><span class="font-medium text-gray-700">Expire:</span> <span class="font-normal text-gray-600">{{ data.SOA.Expire }}</span></li>
<li><span class="font-medium text-gray-700">Minimum TTL:</span> <span class="font-normal text-gray-600">{{ data.SOA.MinimumTTL }}</span></li>
</ul>
</div>
</div>
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800"> {{ t('dns.dnsResult') }}
<span class="text-gray-300 text-sm font-normal ml-2">{{ timeStore.getDnsServer }}</span>
</h2>
<ClientOnly>
<DnsApiChanges />
</ClientOnly>
</div>
<DnsDefaultList v-if="timeStore.getDnsServer == ''"
:data="data" />
<DnsInfoList
v-if="timeStore.getDnsServer != 'cloudflare' && timeStore.getDnsServer != ''"
:data="data"
/>
<DnsCloudflareList
v-if="timeStore.getDnsServer == 'cloudflare'"
:data="data"
/>
</div>
</div>
</div>

View File

@ -18,7 +18,7 @@ const styleStore = useStyleStore()
const localePath = useLocalePath()
const {data, pending, error, refresh} = await useAsyncData(
'mountains',
'whois',
() => $fetch('/api/whois', {
method: 'POST',
body: JSON.stringify({domain: domainData})

3
pnpm-lock.yaml generated
View File

@ -17,9 +17,6 @@ dependencies:
nuxt:
specifier: ^3.10.3
version: 3.10.3(rollup@4.12.0)(typescript@5.3.3)(vite@5.1.4)
socks:
specifier: ^2.8.1
version: 2.8.1
vue:
specifier: ^3.4.21
version: 3.4.21(typescript@5.3.3)

View File

@ -1,31 +1,79 @@
import dns from 'node:dns/promises';
// 定义 DNS 服务器配置
const dnsServers:any = {
google: 'https://dns.google/resolve',
cloudflare: 'http://cloudflare-dns.com/dns-query',
aliyun: 'https://223.5.5.5/resolve',
tencent: 'https://doh.pub/dns-query',
};
interface Resp {
aRecords: string[];
nsRecords: string[];
soaRecord: soaRecord;
}
interface soaRecord {
nsname: string;
hostmaster: string;
serial: number;
refresh: number;
retry: number;
expire: number;
minttl: number;
}
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const body = await readBody(event);
const domain = body.domain;
if (!domain) {
return { error: 'Missing domain' };
}
try {
const aRecords = await dns.resolve(domain, 'A');
const nsRecords = await dns.resolve(domain, 'NS');
const soaRecord = await dns.resolveSoa(domain);
return {
A: aRecords.map(ip => ({ TTL: '600', Record: ip })), // 示例中的TTL是假设的
NS: nsRecords.map(ns => ({ TTL: '21600', Record: ns })), // 示例中的TTL是假设的
SOA: {
MName: soaRecord.nsname,
Email: soaRecord.hostmaster,
Serial: soaRecord.serial,
Refresh: soaRecord.refresh,
Retry: soaRecord.retry,
Expire: soaRecord.expire,
MinimumTTL: soaRecord.minttl,
}
};
} catch (error) {
console.error(`Error fetching DNS records for ${domain}:`, error);
return { error: 'Failed to fetch DNS records' };
const dnsServerKey = body.dnsServer;
switch (dnsServerKey) {
case 'google':
return await $fetch(dnsServers.google, {
params: {
name: domain,
type: 'A',
}
});
case 'tencent':
return await $fetch(dnsServers.tencent, {
params: {
name: domain,
type: 'A',
}
});
case 'cloudflare':
const resp = await $fetch('http://1.1.1.1/dns-query', {
method: 'GET',
params: {
name: domain,
},
headers: {
"Accept": "application/dns-json", // 设置期望的响应数据类型
}
}).then((resp:any) => {
return resp.text()
})
return JSON.parse(resp);
case 'aliyun':
return await $fetch(dnsServers.aliyun, {
params: {
name: domain,
type: '1',
}
});
default:
const resolver = new dns.Resolver();
const aRecords = await resolver.resolve(domain, 'A');
const nsRecords = await resolver.resolve(domain, 'NS');
const soaRecord = await resolver.resolveSoa(domain);
return {
aRecords: aRecords,
nsRecords: nsRecords,
soaRecord: soaRecord,
} as Resp;
}
})
});

View File

@ -4,8 +4,25 @@ export const useTimeStore = defineStore('timeZones', {
state: () => {
return {
timeZones: 'UTC+8',
dnsServer: '',
}
},
actions: {
setTimeZones(timeZones: string) {
this.timeZones = timeZones
},
setDnsServer(dnsServer: string) {
this.dnsServer = dnsServer
},
},
getters: {
getTimeZones(state) {
return state.timeZones
},
getDnsServer(state) {
return state.dnsServer
},
},
persist: {
storage: persistedState.cookiesWithOptions({
sameSite: 'strict',