🚩 增加 Dns 切换功能
This commit is contained in:
parent
999d8b7076
commit
9a64049c60
65
components/dns/ApiChanges.vue
Normal file
65
components/dns/ApiChanges.vue
Normal 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>
|
45
components/dns/CloudflareList.vue
Normal file
45
components/dns/CloudflareList.vue
Normal 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>
|
61
components/dns/DefaultList.vue
Normal file
61
components/dns/DefaultList.vue
Normal 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>
|
43
components/dns/InfoList.vue
Normal file
43
components/dns/InfoList.vue
Normal 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>
|
@ -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"
|
||||
},
|
||||
|
@ -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>
|
||||
|
@ -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
3
pnpm-lock.yaml
generated
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user