mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-25 12:27:13 +00:00
feat(xray/dns): align DNS settings with Xray docs + UI polish
- DNS server modal: rename expectIPs -> expectedIPs (per docs); add per-server tag, clientIP, serveStale, serveExpiredTTL, timeoutMs; flip skipFallback default to false; hydration still accepts legacy expectIPs for back-compat. - DNS tab: add hosts editor (domain -> IP/array), serveStale + serveExpiredTTL controls, "Use Preset" button bringing back the legacy preset gallery (Google / Cloudflare / AdGuard + Family variants — fixed AdGuard Family IPs that were wrong in legacy), and a "Delete All" button to wipe the server list at once. - i18n: add 15 new dns.* keys across all 13 locales. - Frontend-wide formatter pass on Vue components (whitespace and attribute layout only, no behavior changes). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -163,11 +163,8 @@ const themeClass = computed(() => ({
|
||||
<ThemeSwitchLogin />
|
||||
<span>{{ t('pages.settings.language') }}</span>
|
||||
<a-select v-model:value="lang" class="lang-select" @change="onLangChange">
|
||||
<a-select-option
|
||||
v-for="l in LanguageManager.supportedLanguages"
|
||||
:key="l.value"
|
||||
:value="l.value"
|
||||
>
|
||||
<a-select-option v-for="l in LanguageManager.supportedLanguages" :key="l.value"
|
||||
:value="l.value">
|
||||
<span :aria-label="l.name">{{ l.icon }}</span>
|
||||
<span>{{ l.name }}</span>
|
||||
</a-select-option>
|
||||
@@ -175,7 +172,9 @@ const themeClass = computed(() => ({
|
||||
</a-space>
|
||||
</template>
|
||||
<a-button shape="circle">
|
||||
<template #icon><SettingOutlined /></template>
|
||||
<template #icon>
|
||||
<SettingOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</template>
|
||||
@@ -185,12 +184,7 @@ const themeClass = computed(() => ({
|
||||
<a-col :xs="24" :sm="subJsonUrl || subClashUrl ? 12 : 24" class="qr-col">
|
||||
<div class="qr-box">
|
||||
<a-tag color="purple" class="qr-tag">{{ t('pages.settings.subSettings') }}</a-tag>
|
||||
<canvas
|
||||
ref="subQr"
|
||||
class="qr-canvas"
|
||||
:title="t('copy')"
|
||||
@click="copy(subUrl)"
|
||||
/>
|
||||
<canvas ref="subQr" class="qr-canvas" :title="t('copy')" @click="copy(subUrl)" />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col v-if="subJsonUrl" :xs="24" :sm="12" class="qr-col">
|
||||
@@ -198,23 +192,13 @@ const themeClass = computed(() => ({
|
||||
<a-tag color="purple" class="qr-tag">
|
||||
{{ t('pages.settings.subSettings') }} JSON
|
||||
</a-tag>
|
||||
<canvas
|
||||
ref="subJsonQr"
|
||||
class="qr-canvas"
|
||||
:title="t('copy')"
|
||||
@click="copy(subJsonUrl)"
|
||||
/>
|
||||
<canvas ref="subJsonQr" class="qr-canvas" :title="t('copy')" @click="copy(subJsonUrl)" />
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col v-if="subClashUrl" :xs="24" :sm="12" class="qr-col">
|
||||
<div class="qr-box">
|
||||
<a-tag color="purple" class="qr-tag">Clash / Mihomo</a-tag>
|
||||
<canvas
|
||||
ref="subClashQr"
|
||||
class="qr-canvas"
|
||||
:title="t('copy')"
|
||||
@click="copy(subClashUrl)"
|
||||
/>
|
||||
<canvas ref="subClashQr" class="qr-canvas" :title="t('copy')" @click="copy(subClashUrl)" />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -248,12 +232,7 @@ const themeClass = computed(() => ({
|
||||
|
||||
<!-- ============== Individual links ============== -->
|
||||
<div v-if="links.length" class="links-section">
|
||||
<div
|
||||
v-for="(link, idx) in links"
|
||||
:key="link"
|
||||
class="link-row"
|
||||
@click="copy(link)"
|
||||
>
|
||||
<div v-for="(link, idx) in links" :key="link" class="link-row" @click="copy(link)">
|
||||
<a-tag color="purple" class="link-tag">{{ linkName(link, idx) }}</a-tag>
|
||||
<div class="link-box">
|
||||
<CopyOutlined class="link-copy-icon" />
|
||||
@@ -267,12 +246,15 @@ const themeClass = computed(() => ({
|
||||
<a-col :xs="24" :sm="12" class="app-col">
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button :block="isMobile" size="large" type="primary">
|
||||
<AndroidOutlined /> Android <DownOutlined />
|
||||
<AndroidOutlined /> Android
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="android-v2box" @click="open(`v2box://install-sub?url=${encodeURIComponent(subUrl)}&name=${encodeURIComponent(sId)}`)">V2Box</a-menu-item>
|
||||
<a-menu-item key="android-v2rayng" @click="open(`v2rayng://install-config?url=${encodeURIComponent(subUrl)}`)">V2RayNG</a-menu-item>
|
||||
<a-menu-item key="android-v2box"
|
||||
@click="open(`v2box://install-sub?url=${encodeURIComponent(subUrl)}&name=${encodeURIComponent(sId)}`)">V2Box</a-menu-item>
|
||||
<a-menu-item key="android-v2rayng"
|
||||
@click="open(`v2rayng://install-config?url=${encodeURIComponent(subUrl)}`)">V2RayNG</a-menu-item>
|
||||
<a-menu-item key="android-singbox" @click="copy(subUrl)">Sing-box</a-menu-item>
|
||||
<a-menu-item key="android-v2raytun" @click="copy(subUrl)">V2RayTun</a-menu-item>
|
||||
<a-menu-item key="android-npvtunnel" @click="copy(subUrl)">NPV Tunnel</a-menu-item>
|
||||
@@ -284,7 +266,8 @@ const themeClass = computed(() => ({
|
||||
<a-col :xs="24" :sm="12" class="app-col">
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button :block="isMobile" size="large" type="primary">
|
||||
<AppleOutlined /> iOS <DownOutlined />
|
||||
<AppleOutlined /> iOS
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
@@ -314,14 +297,17 @@ const themeClass = computed(() => ({
|
||||
min-height: 100vh;
|
||||
background: var(--bg-page);
|
||||
}
|
||||
|
||||
.subscription-page.is-dark {
|
||||
--bg-page: #0a1222;
|
||||
--bg-card: #151f31;
|
||||
}
|
||||
|
||||
.subscription-page.is-dark.is-ultra {
|
||||
--bg-page: #050505;
|
||||
--bg-card: #0c0e12;
|
||||
}
|
||||
|
||||
.subscription-page :deep(.ant-layout),
|
||||
.subscription-page :deep(.ant-layout-content) {
|
||||
background: transparent;
|
||||
@@ -339,10 +325,12 @@ const themeClass = computed(() => ({
|
||||
.qr-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.qr-col {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-box {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
@@ -350,11 +338,13 @@ const themeClass = computed(() => ({
|
||||
gap: 4px;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.qr-tag {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.qr-canvas {
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
@@ -370,16 +360,19 @@ const themeClass = computed(() => ({
|
||||
.info-table {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.info-table :deep(.ant-descriptions-view),
|
||||
.info-table :deep(.ant-descriptions-view) table,
|
||||
.info-table :deep(.ant-descriptions-view) th,
|
||||
.info-table :deep(.ant-descriptions-view) td {
|
||||
border-color: rgba(0, 0, 0, 0.18) !important;
|
||||
}
|
||||
|
||||
.info-table :deep(tbody > tr > th),
|
||||
.info-table :deep(tbody > tr > td) {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.18) !important;
|
||||
}
|
||||
|
||||
.info-table :deep(tbody > tr:last-child > th),
|
||||
.info-table :deep(tbody > tr:last-child > td) {
|
||||
border-bottom: none !important;
|
||||
@@ -391,10 +384,12 @@ const themeClass = computed(() => ({
|
||||
.is-dark .info-table :deep(.ant-descriptions-view) td {
|
||||
border-color: rgba(255, 255, 255, 0.18) !important;
|
||||
}
|
||||
|
||||
.is-dark .info-table :deep(tbody > tr > th),
|
||||
.is-dark .info-table :deep(tbody > tr > td) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.18) !important;
|
||||
}
|
||||
|
||||
.is-dark .info-table :deep(tbody > tr:last-child > th),
|
||||
.is-dark .info-table :deep(tbody > tr:last-child > td) {
|
||||
border-bottom: none !important;
|
||||
@@ -404,17 +399,20 @@ const themeClass = computed(() => ({
|
||||
.links-section {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.link-row {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link-tag {
|
||||
margin-bottom: -10px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.link-box {
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
@@ -430,19 +428,23 @@ const themeClass = computed(() => ({
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.link-box:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-color: rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
.link-copy-icon {
|
||||
margin-right: 6px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.is-dark .link-box {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.is-dark .link-box:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
@@ -452,6 +454,7 @@ const themeClass = computed(() => ({
|
||||
.apps-row {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.app-col {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -459,6 +462,7 @@ const themeClass = computed(() => ({
|
||||
.settings-popover {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user