mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-25 04:17:15 +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:
@@ -111,17 +111,8 @@ async function onSave() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
:open="open"
|
||||
:title="title"
|
||||
:confirm-loading="submitting"
|
||||
:ok-text="t('save')"
|
||||
:cancel-text="t('cancel')"
|
||||
:mask-closable="false"
|
||||
width="640px"
|
||||
@ok="onSave"
|
||||
@cancel="close"
|
||||
>
|
||||
<a-modal :open="open" :title="title" :confirm-loading="submitting" :ok-text="t('save')" :cancel-text="t('cancel')"
|
||||
:mask-closable="false" width="640px" @ok="onSave" @cancel="close">
|
||||
<a-form layout="vertical" :model="form">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
@@ -171,10 +162,7 @@ async function onSave() {
|
||||
</a-row>
|
||||
|
||||
<a-form-item :label="t('pages.nodes.apiToken')" required>
|
||||
<a-input-password
|
||||
v-model:value="form.apiToken"
|
||||
:placeholder="t('pages.nodes.apiTokenPlaceholder')"
|
||||
/>
|
||||
<a-input-password v-model:value="form.apiToken" :placeholder="t('pages.nodes.apiTokenPlaceholder')" />
|
||||
<div class="hint">{{ t('pages.nodes.apiTokenHint') }}</div>
|
||||
</a-form-item>
|
||||
|
||||
@@ -183,20 +171,11 @@ async function onSave() {
|
||||
{{ t('pages.nodes.testConnection') }}
|
||||
</a-button>
|
||||
<div v-if="testResult" class="test-result">
|
||||
<a-alert
|
||||
v-if="testResult.status === 'online'"
|
||||
type="success"
|
||||
show-icon
|
||||
<a-alert v-if="testResult.status === 'online'" type="success" show-icon
|
||||
:message="t('pages.nodes.connectionOk', { ms: testResult.latencyMs })"
|
||||
:description="testResult.xrayVersion ? `Xray ${testResult.xrayVersion}` : undefined"
|
||||
/>
|
||||
<a-alert
|
||||
v-else
|
||||
type="error"
|
||||
show-icon
|
||||
:message="t('pages.nodes.connectionFailed')"
|
||||
:description="testResult.error"
|
||||
/>
|
||||
:description="testResult.xrayVersion ? `Xray ${testResult.xrayVersion}` : undefined" />
|
||||
<a-alert v-else type="error" show-icon :message="t('pages.nodes.connectionFailed')"
|
||||
:description="testResult.error" />
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
|
||||
@@ -79,33 +79,15 @@ watch(() => props.node?.id, (a, b) => {
|
||||
<div class="node-history-panel">
|
||||
<div class="series">
|
||||
<div class="series-title">{{ t('pages.nodes.cpu') }}</div>
|
||||
<Sparkline
|
||||
:data="cpuPoints"
|
||||
:labels="cpuLabels"
|
||||
:vb-width="640" :height="120"
|
||||
stroke="#008771"
|
||||
:show-grid="true" :show-axes="true"
|
||||
:tick-count-x="4"
|
||||
:max-points="cpuPoints.length || 1"
|
||||
:fill-opacity="0.18"
|
||||
:marker-radius="2.6"
|
||||
:show-tooltip="true"
|
||||
/>
|
||||
<Sparkline :data="cpuPoints" :labels="cpuLabels" :vb-width="640" :height="120" stroke="#008771" :show-grid="true"
|
||||
:show-axes="true" :tick-count-x="4" :max-points="cpuPoints.length || 1" :fill-opacity="0.18"
|
||||
:marker-radius="2.6" :show-tooltip="true" />
|
||||
</div>
|
||||
<div class="series">
|
||||
<div class="series-title">{{ t('pages.nodes.mem') }}</div>
|
||||
<Sparkline
|
||||
:data="memPoints"
|
||||
:labels="memLabels"
|
||||
:vb-width="640" :height="120"
|
||||
stroke="#7c4dff"
|
||||
:show-grid="true" :show-axes="true"
|
||||
:tick-count-x="4"
|
||||
:max-points="memPoints.length || 1"
|
||||
:fill-opacity="0.18"
|
||||
:marker-radius="2.6"
|
||||
:show-tooltip="true"
|
||||
/>
|
||||
<Sparkline :data="memPoints" :labels="memLabels" :vb-width="640" :height="120" stroke="#7c4dff" :show-grid="true"
|
||||
:show-axes="true" :tick-count-x="4" :max-points="memPoints.length || 1" :fill-opacity="0.18"
|
||||
:marker-radius="2.6" :show-tooltip="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -76,19 +76,15 @@ function formatPct(p) {
|
||||
<a-card size="small" hoverable>
|
||||
<div class="toolbar">
|
||||
<a-button type="primary" @click="emit('add')">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
{{ t('pages.nodes.addNode') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:data-source="dataSource"
|
||||
:pagination="false"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
size="middle"
|
||||
row-key="id"
|
||||
>
|
||||
<a-table :data-source="dataSource" :pagination="false" :loading="loading" :scroll="{ x: 'max-content' }"
|
||||
size="middle" row-key="id">
|
||||
<template #expandedRowRender="{ record }">
|
||||
<NodeHistoryPanel :node="record" />
|
||||
</template>
|
||||
@@ -110,7 +106,8 @@ function formatPct(p) {
|
||||
<a-table-column :title="t('pages.nodes.status')" data-index="status" align="center">
|
||||
<template #default="{ record }">
|
||||
<a-space :size="4">
|
||||
<a-badge :status="statusColor(record.status) === 'green' ? 'success' : (statusColor(record.status) === 'red' ? 'error' : 'default')" />
|
||||
<a-badge
|
||||
:status="statusColor(record.status) === 'green' ? 'success' : (statusColor(record.status) === 'red' ? 'error' : 'default')" />
|
||||
<span>{{ t(`pages.nodes.statusValues.${record.status || 'unknown'}`) }}</span>
|
||||
<a-tooltip v-if="record.lastError" :title="record.lastError">
|
||||
<ExclamationCircleOutlined style="color: #faad14" />
|
||||
@@ -150,11 +147,7 @@ function formatPct(p) {
|
||||
|
||||
<a-table-column :title="t('pages.nodes.enable')" data-index="enable" align="center" :width="80">
|
||||
<template #default="{ record }">
|
||||
<a-switch
|
||||
:checked="record.enable"
|
||||
size="small"
|
||||
@change="(v) => emit('toggle-enable', record, v)"
|
||||
/>
|
||||
<a-switch :checked="record.enable" size="small" @change="(v) => emit('toggle-enable', record, v)" />
|
||||
</template>
|
||||
</a-table-column>
|
||||
|
||||
@@ -163,17 +156,23 @@ function formatPct(p) {
|
||||
<a-space>
|
||||
<a-tooltip :title="t('pages.nodes.probe')">
|
||||
<a-button type="text" size="small" @click="emit('probe', record)">
|
||||
<template #icon><ThunderboltOutlined /></template>
|
||||
<template #icon>
|
||||
<ThunderboltOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('edit')">
|
||||
<a-button type="text" size="small" @click="emit('edit', record)">
|
||||
<template #icon><EditOutlined /></template>
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('delete')">
|
||||
<a-button type="text" size="small" danger @click="emit('delete', record)">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
|
||||
@@ -100,10 +100,7 @@ async function onToggleEnable(node, next) {
|
||||
|
||||
<template>
|
||||
<a-config-provider :theme="antdThemeConfig">
|
||||
<a-layout
|
||||
class="nodes-page"
|
||||
:class="{ 'is-dark': themeState.isDark, 'is-ultra': themeState.isUltra }"
|
||||
>
|
||||
<a-layout class="nodes-page" :class="{ 'is-dark': themeState.isDark, 'is-ultra': themeState.isUltra }">
|
||||
<AppSidebar :base-path="basePath" :request-uri="requestUri" />
|
||||
|
||||
<a-layout class="content-shell">
|
||||
@@ -117,40 +114,29 @@ async function onToggleEnable(node, next) {
|
||||
<a-card size="small" hoverable class="summary-card">
|
||||
<a-row :gutter="[16, 12]">
|
||||
<a-col :sm="12" :md="6">
|
||||
<CustomStatistic
|
||||
:title="t('pages.nodes.totalNodes')"
|
||||
:value="String(totals.total)"
|
||||
>
|
||||
<CustomStatistic :title="t('pages.nodes.totalNodes')" :value="String(totals.total)">
|
||||
<template #prefix>
|
||||
<CloudServerOutlined />
|
||||
</template>
|
||||
</CustomStatistic>
|
||||
</a-col>
|
||||
<a-col :sm="12" :md="6">
|
||||
<CustomStatistic
|
||||
:title="t('pages.nodes.onlineNodes')"
|
||||
:value="String(totals.online)"
|
||||
>
|
||||
<CustomStatistic :title="t('pages.nodes.onlineNodes')" :value="String(totals.online)">
|
||||
<template #prefix>
|
||||
<CheckCircleOutlined style="color: #52c41a" />
|
||||
</template>
|
||||
</CustomStatistic>
|
||||
</a-col>
|
||||
<a-col :sm="12" :md="6">
|
||||
<CustomStatistic
|
||||
:title="t('pages.nodes.offlineNodes')"
|
||||
:value="String(totals.offline)"
|
||||
>
|
||||
<CustomStatistic :title="t('pages.nodes.offlineNodes')" :value="String(totals.offline)">
|
||||
<template #prefix>
|
||||
<CloseCircleOutlined style="color: #ff4d4f" />
|
||||
</template>
|
||||
</CustomStatistic>
|
||||
</a-col>
|
||||
<a-col :sm="12" :md="6">
|
||||
<CustomStatistic
|
||||
:title="t('pages.nodes.avgLatency')"
|
||||
:value="totals.avgLatency > 0 ? `${totals.avgLatency} ms` : '-'"
|
||||
>
|
||||
<CustomStatistic :title="t('pages.nodes.avgLatency')"
|
||||
:value="totals.avgLatency > 0 ? `${totals.avgLatency} ms` : '-'">
|
||||
<template #prefix>
|
||||
<ThunderboltOutlined />
|
||||
</template>
|
||||
@@ -162,29 +148,16 @@ async function onToggleEnable(node, next) {
|
||||
|
||||
<!-- Node table -->
|
||||
<a-col :span="24">
|
||||
<NodeList
|
||||
:nodes="nodes"
|
||||
:loading="loading"
|
||||
:is-mobile="isMobile"
|
||||
@add="onAdd"
|
||||
@edit="onEdit"
|
||||
@delete="onDelete"
|
||||
@probe="onProbe"
|
||||
@toggle-enable="onToggleEnable"
|
||||
/>
|
||||
<NodeList :nodes="nodes" :loading="loading" :is-mobile="isMobile" @add="onAdd" @edit="onEdit"
|
||||
@delete="onDelete" @probe="onProbe" @toggle-enable="onToggleEnable" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-spin>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
|
||||
<NodeFormModal
|
||||
v-model:open="formOpen"
|
||||
:mode="formMode"
|
||||
:node="formNode"
|
||||
:test-connection="testConnection"
|
||||
:save="onSave"
|
||||
/>
|
||||
<NodeFormModal v-model:open="formOpen" :mode="formMode" :node="formNode" :test-connection="testConnection"
|
||||
:save="onSave" />
|
||||
</a-layout>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user