| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- <script setup>
- import { computed, ref } from 'vue';
- import { useI18n } from 'vue-i18n';
- import {
- PlusOutlined,
- MoreOutlined,
- EditOutlined,
- DeleteOutlined,
- } from '@ant-design/icons-vue';
- import SettingListItem from '@/components/SettingListItem.vue';
- import DnsServerModal from './DnsServerModal.vue';
- const { t } = useI18n();
- // Structured DNS editor — mirrors web/html/settings/xray/dns.html.
- // Master enable switch + general DNS options + per-server table with
- // add/edit/delete (modal flow), plus a Fake DNS table. Both lists
- // flow through templateSettings.dns / .fakedns reactively so the
- // useXraySetting composable picks every edit up via its deep watch.
- const props = defineProps({
- templateSettings: { type: Object, default: null },
- });
- const STRATEGIES = ['UseSystem', 'UseIP', 'UseIPv4', 'UseIPv6'];
- // ============== Master toggle ==============
- const enableDNS = computed({
- get: () => !!props.templateSettings?.dns,
- set: (next) => {
- if (!props.templateSettings) return;
- if (next) {
- props.templateSettings.dns = {
- tag: 'dns_inbound',
- clientIp: '',
- queryStrategy: 'UseIP',
- disableCache: false,
- disableFallback: false,
- disableFallbackIfMatch: false,
- useSystemHosts: false,
- enableParallelQuery: false,
- servers: [],
- };
- props.templateSettings.fakedns = null;
- } else {
- delete props.templateSettings.dns;
- delete props.templateSettings.fakedns;
- }
- },
- });
- // ============== Field bridges ==============
- function dnsField(field, fallback) {
- return computed({
- get: () => props.templateSettings?.dns?.[field] ?? fallback,
- set: (v) => {
- if (props.templateSettings?.dns) props.templateSettings.dns[field] = v;
- },
- });
- }
- const dnsTag = dnsField('tag', 'dns_inbound');
- const dnsClientIp = dnsField('clientIp', '');
- const dnsStrategy = dnsField('queryStrategy', 'UseIP');
- const dnsDisableCache = dnsField('disableCache', false);
- const dnsDisableFallback = dnsField('disableFallback', false);
- const dnsDisableFallbackIfMatch = dnsField('disableFallbackIfMatch', false);
- const dnsEnableParallelQuery = dnsField('enableParallelQuery', false);
- const dnsUseSystemHosts = dnsField('useSystemHosts', false);
- // ============== DNS server table ==============
- const dnsServers = computed(() => {
- const list = props.templateSettings?.dns?.servers || [];
- return list.map((s, idx) => ({ key: idx, server: s }));
- });
- const dnsColumns = computed(() => [
- { title: '#', key: 'action', align: 'center', width: 60 },
- { title: t('pages.inbounds.address'), key: 'address', align: 'left' },
- { title: t('pages.xray.dns.domains'), key: 'domains', align: 'left' },
- { title: t('pages.xray.dns.expectIPs'), key: 'expectIPs', align: 'left' },
- ]);
- function addrFor(server) {
- return typeof server === 'string' ? server : server?.address || '';
- }
- function domainsFor(server) {
- return typeof server === 'object' ? (server.domains || []).join(',') : '';
- }
- function expectIPsFor(server) {
- return typeof server === 'object' ? (server.expectIPs || []).join(',') : '';
- }
- // ============== Server modal ==============
- const serverModalOpen = ref(false);
- const editingServer = ref(null);
- const editingIndex = ref(null);
- function openAddServer() {
- editingServer.value = null;
- editingIndex.value = null;
- serverModalOpen.value = true;
- }
- function openEditServer(idx) {
- editingServer.value = props.templateSettings.dns.servers[idx];
- editingIndex.value = idx;
- serverModalOpen.value = true;
- }
- function onServerConfirm(value) {
- if (!props.templateSettings?.dns) return;
- if (!Array.isArray(props.templateSettings.dns.servers)) {
- props.templateSettings.dns.servers = [];
- }
- if (editingIndex.value == null) {
- props.templateSettings.dns.servers.push(value);
- } else {
- props.templateSettings.dns.servers[editingIndex.value] = value;
- }
- serverModalOpen.value = false;
- }
- function deleteServer(idx) {
- props.templateSettings.dns.servers.splice(idx, 1);
- }
- // ============== Fake DNS table ==============
- const DEFAULT_FAKEDNS = () => ({ ipPool: '198.18.0.0/15', poolSize: 65535 });
- const fakeDnsList = computed(() => {
- const list = Array.isArray(props.templateSettings?.fakedns)
- ? props.templateSettings.fakedns
- : [];
- return list.map((entry, idx) => ({ key: idx, ...entry }));
- });
- const fakednsColumns = computed(() => [
- { title: '#', key: 'action', align: 'center', width: 60 },
- { title: 'IP pool', dataIndex: 'ipPool', key: 'ipPool', align: 'left' },
- { title: 'Pool size', dataIndex: 'poolSize', key: 'poolSize', align: 'right', width: 120 },
- ]);
- function addFakedns() {
- if (!props.templateSettings) return;
- if (!Array.isArray(props.templateSettings.fakedns)) {
- props.templateSettings.fakedns = [];
- }
- props.templateSettings.fakedns.push(DEFAULT_FAKEDNS());
- }
- function deleteFakedns(idx) {
- props.templateSettings.fakedns.splice(idx, 1);
- if (props.templateSettings.fakedns.length === 0) {
- props.templateSettings.fakedns = null;
- }
- }
- function updateFakednsField(idx, field, value) {
- if (!props.templateSettings.fakedns?.[idx]) return;
- props.templateSettings.fakedns[idx] = {
- ...props.templateSettings.fakedns[idx],
- [field]: value,
- };
- }
- </script>
- <template>
- <a-collapse default-active-key="1">
- <!-- ============== General DNS settings ============== -->
- <a-collapse-panel key="1" :header="t('pages.xray.generalConfigs')">
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.enable') }}</template>
- <template #description>{{ t('pages.xray.dns.enableDesc') }}</template>
- <template #control>
- <a-switch v-model:checked="enableDNS" />
- </template>
- </SettingListItem>
- <template v-if="enableDNS">
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.tag') }}</template>
- <template #description>{{ t('pages.xray.dns.tagDesc') }}</template>
- <template #control>
- <a-input v-model:value="dnsTag" />
- </template>
- </SettingListItem>
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.clientIp') }}</template>
- <template #description>{{ t('pages.xray.dns.clientIpDesc') }}</template>
- <template #control>
- <a-input v-model:value="dnsClientIp" />
- </template>
- </SettingListItem>
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.strategy') }}</template>
- <template #description>{{ t('pages.xray.dns.strategyDesc') }}</template>
- <template #control>
- <a-select v-model:value="dnsStrategy" :style="{ width: '100%' }">
- <a-select-option v-for="s in STRATEGIES" :key="s" :value="s">{{ s }}</a-select-option>
- </a-select>
- </template>
- </SettingListItem>
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.disableCache') }}</template>
- <template #description>{{ t('pages.xray.dns.disableCacheDesc') }}</template>
- <template #control>
- <a-switch v-model:checked="dnsDisableCache" />
- </template>
- </SettingListItem>
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.disableFallback') }}</template>
- <template #description>{{ t('pages.xray.dns.disableFallbackDesc') }}</template>
- <template #control>
- <a-switch v-model:checked="dnsDisableFallback" />
- </template>
- </SettingListItem>
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.disableFallbackIfMatch') }}</template>
- <template #description>{{ t('pages.xray.dns.disableFallbackIfMatchDesc') }}</template>
- <template #control>
- <a-switch v-model:checked="dnsDisableFallbackIfMatch" />
- </template>
- </SettingListItem>
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.enableParallelQuery') }}</template>
- <template #description>{{ t('pages.xray.dns.enableParallelQueryDesc') }}</template>
- <template #control>
- <a-switch v-model:checked="dnsEnableParallelQuery" />
- </template>
- </SettingListItem>
- <SettingListItem paddings="small">
- <template #title>{{ t('pages.xray.dns.useSystemHosts') }}</template>
- <template #description>{{ t('pages.xray.dns.useSystemHostsDesc') }}</template>
- <template #control>
- <a-switch v-model:checked="dnsUseSystemHosts" />
- </template>
- </SettingListItem>
- </template>
- </a-collapse-panel>
- <!-- ============== DNS servers ============== -->
- <a-collapse-panel v-if="enableDNS" key="2" header="DNS">
- <a-empty v-if="dnsServers.length === 0" :description="t('emptyDnsDesc')">
- <a-button type="primary" @click="openAddServer">
- <template #icon><PlusOutlined /></template>
- {{ t('pages.xray.dns.add') }}
- </a-button>
- </a-empty>
- <template v-else>
- <a-space direction="vertical" size="middle" :style="{ width: '100%' }">
- <a-button type="primary" @click="openAddServer">
- <template #icon><PlusOutlined /></template>
- {{ t('pages.xray.dns.add') }}
- </a-button>
- <a-table
- :columns="dnsColumns"
- :data-source="dnsServers"
- :row-key="(r) => r.key"
- :pagination="false"
- size="small"
- bordered
- >
- <template #bodyCell="{ column, record, index }">
- <template v-if="column.key === 'action'">
- <a-space :size="6">
- <span class="row-index">{{ index + 1 }}</span>
- <a-dropdown :trigger="['click']">
- <a-button shape="circle" size="small">
- <MoreOutlined />
- </a-button>
- <template #overlay>
- <a-menu>
- <a-menu-item @click="openEditServer(index)">
- <EditOutlined /> {{ t('edit') }}
- </a-menu-item>
- <a-menu-item class="danger" @click="deleteServer(index)">
- <DeleteOutlined /> {{ t('delete') }}
- </a-menu-item>
- </a-menu>
- </template>
- </a-dropdown>
- </a-space>
- </template>
- <template v-else-if="column.key === 'address'">
- {{ addrFor(record.server) }}
- </template>
- <template v-else-if="column.key === 'domains'">
- <span class="muted">{{ domainsFor(record.server) }}</span>
- </template>
- <template v-else-if="column.key === 'expectIPs'">
- <span class="muted">{{ expectIPsFor(record.server) }}</span>
- </template>
- </template>
- </a-table>
- </a-space>
- </template>
- </a-collapse-panel>
- <!-- ============== Fake DNS ============== -->
- <a-collapse-panel v-if="enableDNS" key="3" header="Fake DNS">
- <a-empty v-if="fakeDnsList.length === 0" :description="t('emptyFakeDnsDesc')">
- <a-button type="primary" @click="addFakedns">
- <template #icon><PlusOutlined /></template>
- {{ t('pages.xray.fakedns.add') }}
- </a-button>
- </a-empty>
- <template v-else>
- <a-space direction="vertical" size="middle" :style="{ width: '100%' }">
- <a-button type="primary" @click="addFakedns">
- <template #icon><PlusOutlined /></template>
- {{ t('pages.xray.fakedns.add') }}
- </a-button>
- <a-table
- :columns="fakednsColumns"
- :data-source="fakeDnsList"
- :row-key="(r) => r.key"
- :pagination="false"
- size="small"
- bordered
- >
- <template #bodyCell="{ column, record, index }">
- <template v-if="column.key === 'action'">
- <a-space :size="6">
- <span class="row-index">{{ index + 1 }}</span>
- <a-button shape="circle" size="small" danger @click="deleteFakedns(index)">
- <DeleteOutlined />
- </a-button>
- </a-space>
- </template>
- <template v-else-if="column.key === 'ipPool'">
- <a-input
- :value="record.ipPool"
- size="small"
- @change="(e) => updateFakednsField(index, 'ipPool', e.target.value)"
- />
- </template>
- <template v-else-if="column.key === 'poolSize'">
- <a-input-number
- :value="record.poolSize"
- :min="1"
- size="small"
- @change="(v) => updateFakednsField(index, 'poolSize', v)"
- />
- </template>
- </template>
- </a-table>
- </a-space>
- </template>
- </a-collapse-panel>
- </a-collapse>
- <DnsServerModal
- v-model:open="serverModalOpen"
- :server="editingServer"
- :is-edit="editingIndex != null"
- @confirm="onServerConfirm"
- />
- </template>
- <style scoped>
- .row-index {
- font-weight: 500;
- opacity: 0.7;
- }
- .muted { opacity: 0.7; word-break: break-all; }
- .danger { color: #ff4d4f; }
- </style>
|