Skip to content

Commit

Permalink
Merge pull request #160 from jason5ng32/dev
Browse files Browse the repository at this point in the history
Add DNS Resolver
  • Loading branch information
jason5ng32 authored Apr 10, 2024
2 parents 79df40c + 2de5661 commit 21e0762
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 90 deletions.
57 changes: 47 additions & 10 deletions api/dnsresolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,70 @@ const dnsServers = {
const dohServers = {
'Google': 'https://dns.google/resolve?',
'Cloudflare': 'https://cloudflare-dns.com/dns-query?ct=application/dns-json&',
'AdGuard': 'https://dns.adguard.com/resolve?',
'AliDNS': 'https://dns.alidns.com/resolve?',
};

const resolveDns = async (hostname, name, server) => {
const resolveDns = async (hostname, type, name, server) => {
const resolver = new Resolver();
resolver.setServers([server]);
const resolve4Async = promisify(resolver.resolve4.bind(resolver));
const resolve6Async = promisify(resolver.resolve6.bind(resolver));
const resolveTxtAsync = promisify(resolver.resolveTxt.bind(resolver));
const resolveCnameAsync = promisify(resolver.resolveCname.bind(resolver));
const resolveNSAsync = promisify(resolver.resolveNs.bind(resolver));
const resolveAnyAsync = promisify(resolver.resolveAny.bind(resolver));
try {
const addresses = await resolve4Async(hostname);
let addresses;

// 根据传入的 type 参数选择不同的解析方法
switch (type) {
case 'A':
addresses = await resolve4Async(hostname);
break;
case 'AAAA':
addresses = await resolve6Async(hostname);
break;
case 'TXT':
addresses = await resolveTxtAsync(hostname);
// TXT 记录解析的结果是一个二维数组,这里进行扁平化处理
addresses = addresses.flat();
break;
case 'CNAME':
addresses = await resolveCnameAsync(hostname);
break;
case 'NS':
addresses = await resolveNSAsync(hostname);
break;
default:
throw new Error('Unsupported type');
}

if (addresses.length === 0 || addresses === '' || addresses === null) {
return { [name]: `N/A` };
}

return { [name]: addresses };
} catch (error) {
console.log(error.message);
return { [name]: `Error: No addresses found` };
return { [name]: `N/A` };
}
};

const resolveDoh = async (hostname, name, url) => {
const resolveDoh = async (hostname, type, name, url) => {
try {
const response = await fetch(`${url}name=${hostname}&type=A`, {
const response = await fetch(`${url}name=${hostname}&type=${type}`, {
headers: { 'Accept': 'application/dns-json' }
});
const data = await response.json();
const addresses = data.Answer ? data.Answer.map(answer => answer.data) : ['Error: No addresses found'];
const addresses = data.Answer ? data.Answer.map(answer => answer.data) : ['N/A'];
if (addresses.length === 0 || addresses === '' || addresses === null) {
return { [name]: `N/A` };
}
return { [name]: addresses };
} catch (error) {
console.log(error.message);
return { [name]: `Error: No addresses found` };
return { [name]: `N/A` };
}
};

Expand All @@ -70,7 +107,7 @@ const dnsResolver = async (req, res) => {
return res.status(403).json({ error: 'What are you doing?' });
}

const { hostname } = req.query;
const { hostname, type } = req.query;

if (typeof hostname !== 'string') {
return res.status(400).send({ error: 'Hostname parameter must be a string' });
Expand All @@ -84,8 +121,8 @@ const dnsResolver = async (req, res) => {
return res.status(400).send({ error: 'Invalid hostname' });
}

const dnsPromises = Object.entries(dnsServers).map(([name, ip]) => resolveDns(hostname, name, ip));
const dohPromises = Object.entries(dohServers).map(([name, url]) => resolveDoh(hostname, name, url));
const dnsPromises = Object.entries(dnsServers).map(([name, ip]) => resolveDns(hostname, type, name, ip));
const dohPromises = Object.entries(dohServers).map(([name, url]) => resolveDoh(hostname, type, name, url));

try {
// 并行执行所有 DNS 和 DoH 查询
Expand Down
76 changes: 14 additions & 62 deletions src/components/advancedtools.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,77 +9,24 @@
<p>{{ $t('advancedtools.Note') }}</p>
</div>
<div class="row">

<div class="col-lg-3 col-md-6 col-12 mb-4">
<div class="jn-adv-card card jn-card" :class="{ 'dark-mode dark-mode-border': isDarkMode }">
<div class="card-body" @click.prevent="navigateAndToggleOffcanvas('/pingtest')" role="button">
<h3 :class="{ 'mobile-h3': isMobile }">🌐 {{ $t('pingtest.Title') }}</h3>
<p class="opacity-75">{{ $t('advancedtools.PingTestNote') }}</p>

<div class="col-lg-3 col-md-6 col-12 mb-4" v-for="(card, index) in cards" :key="index">
<div class="jn-adv-card card jn-card" :class="{ 'dark-mode dark-mode-border': isDarkMode }" >
<div class="card-body" @click.prevent="navigateAndToggleOffcanvas(card.path)" role="button">
<h3 :class="{ 'mobile-h3': isMobile }">{{ card.icon }} {{ $t(card.titleKey) }}</h3>
<p class="opacity-75">{{ $t(card.noteKey) }}</p>
<div class="go-corner">
<div class="go-arrow">
</div>
<div class="go-arrow">↓</div>
</div>
</div>
</div>
</div>

<div class="col-lg-3 col-md-6 col-12 mb-4">
<div class="jn-adv-card card jn-card" :class="{ 'dark-mode dark-mode-border': isDarkMode }">
<div class="card-body" @click.prevent="navigateAndToggleOffcanvas('/mtrtest')" role="button">
<h3 :class="{ 'mobile-h3': isMobile }">📡 {{ $t('mtrtest.Title') }}</h3>
<p class="opacity-75">{{ $t('advancedtools.MTRTestNote') }}</p>

<div class="go-corner">
<div class="go-arrow">
</div>
</div>
</div>
</div>
</div>

<div class="col-lg-3 col-md-6 col-12 mb-4">
<div class="jn-adv-card card jn-card" :class="{ 'dark-mode dark-mode-border': isDarkMode }">
<div class="card-body" @click.prevent="navigateAndToggleOffcanvas('/ruletest')" role="button">
<h3 :class="{ 'mobile-h3': isMobile }">🚏 {{ $t('ruletest.Title') }}</h3>
<p class="opacity-75">{{ $t('advancedtools.RuleTestNote') }}</p>

<div class="go-corner">
<div class="go-arrow">
</div>
</div>
</div>
</div>
</div>

<div class="col-lg-3 col-md-6 col-12 mb-4">
<div class="jn-adv-card card jn-card" :class="{ 'dark-mode dark-mode-border': isDarkMode }">
<div class="card-body" @click.prevent="navigateAndToggleOffcanvas('/dnsresolver')" role="button">
<h3 :class="{ 'mobile-h3': isMobile }">🔦 {{ $t('dnsresolver.Title') }}</h3>
<p class="opacity-75">{{ $t('advancedtools.DNSResolverNote') }}</p>

<div class="go-corner">
<div class="go-arrow">
</div>
</div>
</div>
</div>
</div>

</div>

<div :data-bs-theme="isDarkMode ? 'dark' : ''" class="offcanvas offcanvas-bottom" tabindex="-1"
:class="[isMobile ? ' h-100' : 'jn-h-80']" id="offcanvasTools" aria-labelledby="offcanvasToolsLabel">
<div class="offcanvas-header">
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body mb-4"
:class="[isMobile ? ' w-100' : 'jn-canvas-width']"
>
<div class="offcanvas-body mb-4" :class="[isMobile ? ' w-100' : 'jn-canvas-width']">
<router-view></router-view>
</div>
</div>
Expand Down Expand Up @@ -110,14 +57,19 @@ export default {
data() {
return {
cards: [
{ path: '/pingtest', icon: '🌐', titleKey: 'pingtest.Title', noteKey: 'advancedtools.PingTestNote' },
{ path: '/mtrtest', icon: '📡', titleKey: 'mtrtest.Title', noteKey: 'advancedtools.MTRTestNote' },
{ path: '/ruletest', icon: '🚏', titleKey: 'ruletest.Title', noteKey: 'advancedtools.RuleTestNote' },
{ path: '/dnsresolver', icon: '🔦', titleKey: 'dnsresolver.Title', noteKey: 'advancedtools.DNSResolverNote' },
]
}
},
methods: {
navigateAndToggleOffcanvas(routePath) {
this.$router.push(routePath);
switch(routePath) {
switch (routePath) {
case '/pingtest':
this.$trackEvent('Nav', 'NavClick', 'PingTest');
break;
Expand Down
26 changes: 22 additions & 4 deletions src/components/dnsresolver.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
:placeholder="$t('dnsresolver.Placeholder')" v-model="queryURL" @keyup.enter="onSubmit"
name="queryURL" id="queryURL" data-1p-ignore>

<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false">
{{ queryType }} {{ $t('dnsresolver.Record') }}
<span class="visually-hidden">Choose Type</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li @click="changeType('A')"><span class="dropdown-item" >A</span></li>
<li @click="changeType('AAAA')"><span class="dropdown-item" >AAAA</span></li>
<li @click="changeType('CNAME')"><span class="dropdown-item" >CNAME</span></li>
<li @click="changeType('MX')"><span class="dropdown-item" >MX</span></li>
<li @click="changeType('NS')"><span class="dropdown-item" >NS</span></li>
<li @click="changeType('TXT')"><span class="dropdown-item" >TXT</span></li>
</ul>
<button class="btn btn-primary" @click="onSubmit" :disabled="dnsCheckStatus === 'running'">
<span v-if="dnsCheckStatus === 'idle'">{{
$t('dnsresolver.Run') }}</span>
Expand All @@ -46,7 +59,7 @@
<tbody>
<tr v-for="(result, index) in combinedResults" :key="index">
<td>{{ result.provider }}</td>
<td>{{ result.address }}</td>
<td :class="[result.address === 'N/A' ? 'opacity-50' : '' ]">{{ result.address }}</td>
</tr>

</tbody>
Expand Down Expand Up @@ -81,6 +94,7 @@ export default {
data() {
return {
queryURL: '',
queryType: 'A',
dnsCheckStatus: 'idle',
errorMsg: '',
combinedResults: null,
Expand Down Expand Up @@ -110,22 +124,26 @@ export default {
return null;
},
changeType(type) {
this.queryType = type;
},
onSubmit() {
this.$trackEvent('Section', 'StartClick', 'DNSResolver');
this.errorMsg = '';
const hostname = this.validateInput(this.queryURL);
const type = this.queryType;
if (hostname) {
this.getDNSResults(hostname);
this.getDNSResults(hostname,type);
}
},
// 获取DNS结果
async getDNSResults(hostname) {
async getDNSResults(hostname,type) {
this.combinedResults = [];
this.dnsCheckStatus = 'running';
try {
const response = await fetch(`/api/dnsresolver?hostname=${hostname}`);
const response = await fetch(`/api/dnsresolver?hostname=${hostname}&type=${type}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
Expand Down
9 changes: 3 additions & 6 deletions src/components/globallatency.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,21 @@
<label for="pingIP" class="col-form-label">{{ $t('pingtest.Note3') }}</label>
</div>
<div class="col-12 col-md-auto mt-2 mt-md-0">
<div class="row justify-content-between">
<div class="col-auto">
<div class="input-group ">
<select id="pingIP" class="form-select jn-ping-form-select" v-model="selectedIP"
:class="{ 'bg-dark text-light': isDarkMode }">
<option disabled value="">{{ $t('pingtest.SelectIP') }}</option>
<option v-for="ip in allIPs" :key="ip" :value="ip">{{ ip }}</option>
</select>
</div>
<div class="col-auto">
<button class="btn btn-success" @click="startPingCheck"

<button class="btn btn-primary" @click="startPingCheck"
:disabled="pingCheckStatus === 'running' || selectedIP === ''">
<span
v-if="pingCheckStatus === 'idle' || pingCheckStatus === 'finished' || pingCheckStatus === 'error'">{{
$t('pingtest.Run') }}</span>
<span v-if="pingCheckStatus === 'running'" class="spinner-grow spinner-grow-sm"
aria-hidden="true"></span>
</button>
</div>
</div>
</div>
</div>
Expand Down
7 changes: 2 additions & 5 deletions src/components/mtrtest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@
<label for="mtrIP" class="col-form-label">{{ $t('mtrtest.Note3') }}</label>
</div>
<div class="col-12 col-md-auto mt-2 mt-md-0">
<div class="row justify-content-between">
<div class="col-auto">
<div class="input-group ">
<select id="mtrIP" class="form-select jn-ping-form-select" v-model="selectedIP"
:class="{ 'bg-dark text-light': isDarkMode }">
<option disabled value="">{{ $t('mtrtest.SelectIP') }}</option>
<option v-for="ip in allIPs" :key="ip" :value="ip">{{ ip }}</option>
</select>
</div>
<div class="col-auto">

<button class="btn btn-primary" @click="startmtrCheck"
:disabled="mtrCheckStatus === 'running' || selectedIP === ''">
<span
Expand All @@ -36,7 +34,6 @@
<span v-if="mtrCheckStatus === 'running'" class="spinner-grow spinner-grow-sm"
aria-hidden="true"></span>
</button>
</div>
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"invalidURL": "Invalid URL or Domain Name",
"fetchError": "Unable to fetch resolution results",
"Provider": "DNS Provider",
"Result": "Resolution Result"
"Result": "Resolution Result",
"Record": "Record"
},
"ruletest": {
"Title": "Rule Test",
Expand Down
3 changes: 2 additions & 1 deletion src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"invalidURL": "URL ou Nom de Domaine invalide",
"fetchError": "Impossible de récupérer les résultats de résolution",
"Provider": "Fournisseur DNS",
"Result": "Résultat de la Résolution"
"Result": "Résultat de la Résolution",
"Record": "Enregistrement"
},
"ruletest": {
"Title": "Test de règles",
Expand Down
3 changes: 2 additions & 1 deletion src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"invalidURL": "无效的 URL 或域名",
"fetchError": "无法获取解析结果",
"Provider": "DNS 服务商",
"Result": "解析结果"
"Result": "解析结果",
"Record": "记录"
},
"ruletest": {
"Title": "分流测试",
Expand Down

0 comments on commit 21e0762

Please sign in to comment.