mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-12 09:21:38 +07: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:
co-authored by
Claude Opus 4.7
parent
8e7d215b4a
commit
a96612f595
@@ -169,19 +169,14 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
<template>
|
||||
<a-space direction="vertical" size="middle" :style="{ width: '100%' }">
|
||||
<a-button type="primary" @click="openAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
{{ t('pages.xray.Routings') }}
|
||||
</a-button>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="rows"
|
||||
:row-key="(r) => r.key"
|
||||
:pagination="false"
|
||||
:scroll="isMobile ? {} : { x: 1000 }"
|
||||
size="small"
|
||||
class="routing-table"
|
||||
>
|
||||
<a-table :columns="columns" :data-source="rows" :row-key="(r) => r.key" :pagination="false"
|
||||
:scroll="isMobile ? {} : { x: 1000 }" size="small" class="routing-table">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<!-- ============== # / actions ============== -->
|
||||
<template v-if="column.key === 'action'">
|
||||
@@ -218,21 +213,24 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">IP</span>
|
||||
<span class="criterion-value">{{ csv(record.sourceIP)[0] }}</span>
|
||||
<span v-if="csv(record.sourceIP).length > 1" class="criterion-more">+{{ csv(record.sourceIP).length - 1 }}</span>
|
||||
<span v-if="csv(record.sourceIP).length > 1" class="criterion-more">+{{ csv(record.sourceIP).length - 1
|
||||
}}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.sourcePort" :title="`Source port: ${record.sourcePort}`">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Port</span>
|
||||
<span class="criterion-value">{{ csv(record.sourcePort)[0] }}</span>
|
||||
<span v-if="csv(record.sourcePort).length > 1" class="criterion-more">+{{ csv(record.sourcePort).length - 1 }}</span>
|
||||
<span v-if="csv(record.sourcePort).length > 1" class="criterion-more">+{{ csv(record.sourcePort).length
|
||||
- 1 }}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.vlessRoute" :title="`VLESS route: ${record.vlessRoute}`">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">VLESS</span>
|
||||
<span class="criterion-value">{{ csv(record.vlessRoute)[0] }}</span>
|
||||
<span v-if="csv(record.vlessRoute).length > 1" class="criterion-more">+{{ csv(record.vlessRoute).length - 1 }}</span>
|
||||
<span v-if="csv(record.vlessRoute).length > 1" class="criterion-more">+{{ csv(record.vlessRoute).length
|
||||
- 1 }}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span v-if="!record.sourceIP && !record.sourcePort && !record.vlessRoute" class="criterion-empty">—</span>
|
||||
@@ -246,14 +244,16 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">L4</span>
|
||||
<span class="criterion-value">{{ csv(record.network)[0] }}</span>
|
||||
<span v-if="csv(record.network).length > 1" class="criterion-more">+{{ csv(record.network).length - 1 }}</span>
|
||||
<span v-if="csv(record.network).length > 1" class="criterion-more">+{{ csv(record.network).length - 1
|
||||
}}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.protocol" :title="`Protocol: ${record.protocol}`">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Protocol</span>
|
||||
<span class="criterion-value">{{ csv(record.protocol)[0] }}</span>
|
||||
<span v-if="csv(record.protocol).length > 1" class="criterion-more">+{{ csv(record.protocol).length - 1 }}</span>
|
||||
<span v-if="csv(record.protocol).length > 1" class="criterion-more">+{{ csv(record.protocol).length - 1
|
||||
}}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.attrs" :title="`Attrs: ${record.attrs}`">
|
||||
@@ -280,14 +280,16 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Domain</span>
|
||||
<span class="criterion-value">{{ csv(record.domain)[0] }}</span>
|
||||
<span v-if="csv(record.domain).length > 1" class="criterion-more">+{{ csv(record.domain).length - 1 }}</span>
|
||||
<span v-if="csv(record.domain).length > 1" class="criterion-more">+{{ csv(record.domain).length - 1
|
||||
}}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.port" :title="`Destination port: ${record.port}`">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Port</span>
|
||||
<span class="criterion-value">{{ csv(record.port)[0] }}</span>
|
||||
<span v-if="csv(record.port).length > 1" class="criterion-more">+{{ csv(record.port).length - 1 }}</span>
|
||||
<span v-if="csv(record.port).length > 1" class="criterion-more">+{{ csv(record.port).length - 1
|
||||
}}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span v-if="!record.ip && !record.domain && !record.port" class="criterion-empty">—</span>
|
||||
@@ -301,14 +303,16 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">Tag</span>
|
||||
<span class="criterion-value">{{ csv(record.inboundTag)[0] }}</span>
|
||||
<span v-if="csv(record.inboundTag).length > 1" class="criterion-more">+{{ csv(record.inboundTag).length - 1 }}</span>
|
||||
<span v-if="csv(record.inboundTag).length > 1" class="criterion-more">+{{ csv(record.inboundTag).length
|
||||
- 1 }}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.user" :title="`User: ${record.user}`">
|
||||
<span class="criterion-row">
|
||||
<span class="criterion-label">User</span>
|
||||
<span class="criterion-value">{{ csv(record.user)[0] }}</span>
|
||||
<span v-if="csv(record.user).length > 1" class="criterion-more">+{{ csv(record.user).length - 1 }}</span>
|
||||
<span v-if="csv(record.user).length > 1" class="criterion-more">+{{ csv(record.user).length - 1
|
||||
}}</span>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<span v-if="!record.inboundTag && !record.user" class="criterion-empty">—</span>
|
||||
@@ -332,14 +336,8 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<RuleFormModal
|
||||
v-model:open="ruleModalOpen"
|
||||
:rule="editingRule"
|
||||
:inbound-tags="inboundTagOptions"
|
||||
:outbound-tags="outboundTagOptions"
|
||||
:balancer-tags="balancerTagOptions"
|
||||
@confirm="onRuleConfirm"
|
||||
/>
|
||||
<RuleFormModal v-model:open="ruleModalOpen" :rule="editingRule" :inbound-tags="inboundTagOptions"
|
||||
:outbound-tags="outboundTagOptions" :balancer-tags="balancerTagOptions" @confirm="onRuleConfirm" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
@@ -349,6 +347,7 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.row-index {
|
||||
font-weight: 500;
|
||||
opacity: 0.7;
|
||||
@@ -362,30 +361,36 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
gap: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.criterion-row {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.criterion-label {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.55;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.criterion-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.criterion-more {
|
||||
font-size: 11px;
|
||||
padding: 0 5px;
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
:global(body.dark) .criterion-more {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.criterion-empty {
|
||||
opacity: 0.4;
|
||||
}
|
||||
@@ -395,15 +400,19 @@ const columns = computed(() => (props.isMobile ? mobileColumns.value : desktopCo
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.target-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.target-icon {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.danger { color: #ff4d4f; }
|
||||
.danger {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user