mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-09 20:02:20 +08:00
feat(dns): protect provider DNS from unconfirmed overrides (#7902)
Fixes #7292
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
||||
Switch,
|
||||
} from '@/components/base'
|
||||
import { useClash } from '@/hooks/use-clash'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { useThemeMode } from '@/services/states'
|
||||
import type { MonacoEditorInstance } from '@/types/monaco'
|
||||
@@ -187,7 +188,8 @@ const DEFAULT_DNS_CONFIG = {
|
||||
|
||||
export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { t } = useTranslation()
|
||||
const { clash, mutateClash } = useClash()
|
||||
const { mutateClash } = useClash()
|
||||
const { verge } = useVerge()
|
||||
const themeMode = useThemeMode()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -563,7 +565,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
return
|
||||
}
|
||||
|
||||
if (clash?.dns?.enable) {
|
||||
if (verge?.enable_dns_settings) {
|
||||
await invoke('apply_dns_config', { apply: true })
|
||||
mutateClash()
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { LanRounded, SettingsRounded } from '@mui/icons-material'
|
||||
import { MenuItem, Select, TextField, Typography } from '@mui/material'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useLockFn } from 'ahooks'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { updateGeo, type LogLevel } from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import { DialogRef, Switch, TooltipIcon } from '@/components/base'
|
||||
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
|
||||
import { useClash } from '@/hooks/use-clash'
|
||||
import { useClashLog } from '@/hooks/use-clash-log'
|
||||
import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { invoke_uwp_tool } from '@/services/cmds'
|
||||
import { invoke_uwp_tool, setDnsOverride } from '@/services/cmds'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import getSystem from '@/utils/get-system'
|
||||
|
||||
@@ -36,7 +35,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { clash, version, mutateClash, patchClash } = useClash()
|
||||
const { verge, patchVerge } = useVerge()
|
||||
const { verge, mutateVerge } = useVerge()
|
||||
const displayedMixedPort = useDisplayedMixedPort()
|
||||
const [, setClashLog] = useClashLog()
|
||||
|
||||
@@ -47,10 +46,8 @@ const SettingClash = ({ onError }: Props) => {
|
||||
'unified-delay': unifiedDelay,
|
||||
} = clash ?? {}
|
||||
|
||||
// 独立跟踪DNS设置开关状态
|
||||
const [dnsSettingsEnabled, setDnsSettingsEnabled] = useState(() => {
|
||||
return verge?.enable_dns_settings ?? false
|
||||
})
|
||||
const [dnsConfirmation, setDnsConfirmation] = useState<string | null>(null)
|
||||
const [dnsUpdating, setDnsUpdating] = useState(false)
|
||||
|
||||
const webRef = useRef<DialogRef>(null)
|
||||
const portRef = useRef<DialogRef>(null)
|
||||
@@ -74,22 +71,29 @@ const SettingClash = ({ onError }: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 实现DNS设置开关处理函数
|
||||
const handleDnsToggle = useLockFn(async (enable: boolean) => {
|
||||
try {
|
||||
setDnsSettingsEnabled(enable)
|
||||
await patchVerge({ enable_dns_settings: enable })
|
||||
await invoke('apply_dns_config', { apply: enable })
|
||||
setTimeout(() => {
|
||||
const handleDnsToggle = useLockFn(
|
||||
async (enable: boolean, confirmation?: string) => {
|
||||
setDnsUpdating(true)
|
||||
try {
|
||||
const outcome = await setDnsOverride(enable, confirmation)
|
||||
if (outcome.status === 'confirmation_required') {
|
||||
setDnsConfirmation(outcome.source)
|
||||
} else {
|
||||
setDnsConfirmation(null)
|
||||
}
|
||||
mutateVerge()
|
||||
mutateClash()
|
||||
}, 500)
|
||||
} catch (err: any) {
|
||||
setDnsSettingsEnabled(!enable)
|
||||
showNotice.error(err)
|
||||
await patchVerge({ enable_dns_settings: !enable }).catch(() => {})
|
||||
throw err
|
||||
}
|
||||
})
|
||||
} catch (err: any) {
|
||||
showNotice.error(err)
|
||||
} finally {
|
||||
setDnsUpdating(false)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const closeDnsConfirmation = () => {
|
||||
if (!dnsUpdating) setDnsConfirmation(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingList title={t('settings.sections.clash.title')}>
|
||||
@@ -99,6 +103,24 @@ const SettingClash = ({ onError }: Props) => {
|
||||
<ClashCoreViewer ref={coreRef} />
|
||||
<NetworkInterfaceViewer ref={networkRef} />
|
||||
<DnsViewer ref={dnsRef} />
|
||||
<BaseDialog
|
||||
open={dnsConfirmation !== null}
|
||||
title={t('settings.modals.dns.protection.title')}
|
||||
okBtn={t('settings.modals.dns.protection.enableAnyway')}
|
||||
cancelBtn={t('settings.modals.dns.protection.keepDisabled')}
|
||||
contentSx={{ width: { xs: 320, sm: 420 } }}
|
||||
loading={dnsUpdating}
|
||||
disableCancel={dnsUpdating}
|
||||
onCancel={closeDnsConfirmation}
|
||||
onClose={closeDnsConfirmation}
|
||||
onOk={() => {
|
||||
if (dnsConfirmation) void handleDnsToggle(true, dnsConfirmation)
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
{t('settings.modals.dns.protection.message')}
|
||||
</Typography>
|
||||
</BaseDialog>
|
||||
<HeaderConfiguration ref={corsRef} />
|
||||
<TunnelsViewer ref={tunnelRef} />
|
||||
<SettingItem
|
||||
@@ -137,7 +159,8 @@ const SettingClash = ({ onError }: Props) => {
|
||||
>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={dnsSettingsEnabled}
|
||||
checked={verge?.enable_dns_settings ?? false}
|
||||
disabled={dnsUpdating || dnsConfirmation !== null}
|
||||
onChange={(_, checked) => handleDnsToggle(checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS Overwrite",
|
||||
"warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite disabled"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS Settings",
|
||||
"fallbackFilter": "Fallback Filter Settings",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS-Überschreibung",
|
||||
"warning": "Wenn Sie sich nicht mit diesen Einstellungen auskennen, ändern Sie sie nicht und lassen Sie die DNS-Überschreibung deaktiviert."
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS-Einstellungen",
|
||||
"fallbackFilter": "Rückfallfilter-Einstellungen",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS Overwrite",
|
||||
"warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite disabled"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS Settings",
|
||||
"fallbackFilter": "Fallback Filter Settings",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "Sobrescritura de DNS",
|
||||
"warning": "Si no está seguro de cómo configurar esto, no realice cambios y mantenga deshabilitada la sobrescritura de DNS."
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "Configuración de DNS",
|
||||
"fallbackFilter": "Configuración de filtrado de respaldo",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "بازنویسی DNS",
|
||||
"warning": "اگر با این تنظیمات آشنا نیستید، لطفاً آنها را تغییر ندهید و DNS Overwrite را غیرفعال نگه دارید."
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "تنظیمات DNS",
|
||||
"fallbackFilter": "Fallback Filter Settings",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS Overwrite",
|
||||
"warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite disabled"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS Settings",
|
||||
"fallbackFilter": "Fallback Filter Settings",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS上書き",
|
||||
"warning": "ここの設定がわからない場合は、変更しないでください。DNS上書きを無効にしたままにしてください。"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS設定",
|
||||
"fallbackFilter": "フォールバックフィルター設定",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS 덮어쓰기",
|
||||
"warning": "이 설정에 익숙하지 않다면 수정하지 말고 DNS 덮어쓰기를 비활성화 상태로 유지하세요"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS 설정",
|
||||
"fallbackFilter": "폴백 필터 설정",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "Переопределение настроек DNS",
|
||||
"warning": "Если вы не знакомы с этими настройками, пожалуйста, не изменяйте их и оставьте переопределение DNS отключённым"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "Настройки DNS",
|
||||
"fallbackFilter": "Настройки фильтра fallback",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS Üzerine Yazma",
|
||||
"warning": "Bu ayarlarla ilgili bilginiz yoksa, lütfen bunları değiştirmeyin ve DNS Üzerine Yazma'yı devre dışı tutun"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS Ayarları",
|
||||
"fallbackFilter": "Yedek Filtre Ayarları",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS Overwrite",
|
||||
"warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite disabled"
|
||||
},
|
||||
"protection": {
|
||||
"title": "Enable DNS Overwrite?",
|
||||
"message": "This subscription contains custom DNS settings. DNS Overwrite may replace them, preventing nodes from connecting or some websites from loading. Enable it anyway?",
|
||||
"enableAnyway": "Enable anyway",
|
||||
"keepDisabled": "Keep disabled",
|
||||
"autoDisabled": "DNS Overwrite was disabled to preserve the custom DNS settings in the current subscription."
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS Settings",
|
||||
"fallbackFilter": "Fallback Filter Settings",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS 覆写",
|
||||
"warning": "如果你不清楚这里的设置请不要修改,并保持 DNS 覆写关闭"
|
||||
},
|
||||
"protection": {
|
||||
"title": "是否开启 DNS 覆写?",
|
||||
"message": "当前订阅包含自定义 DNS 配置。开启 DNS 覆写可能覆盖这些配置,导致节点无法连接或部分网站无法访问。是否仍要开启?",
|
||||
"enableAnyway": "仍然开启",
|
||||
"keepDisabled": "保持关闭",
|
||||
"autoDisabled": "检测到当前订阅包含自定义 DNS 配置,已自动关闭 DNS 覆写。"
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS 设置",
|
||||
"fallbackFilter": "回退过滤设置",
|
||||
|
||||
@@ -510,6 +510,13 @@
|
||||
"title": "DNS 覆寫",
|
||||
"warning": "如果你不清楚這裡的設定請不要修改,並保持 DNS 覆寫關閉"
|
||||
},
|
||||
"protection": {
|
||||
"title": "是否開啟 DNS 覆寫?",
|
||||
"message": "目前訂閱包含自訂 DNS 設定。開啟 DNS 覆寫可能覆蓋這些設定,導致節點無法連線或部分網站無法存取。是否仍要開啟?",
|
||||
"enableAnyway": "仍然開啟",
|
||||
"keepDisabled": "保持關閉",
|
||||
"autoDisabled": "偵測到目前訂閱包含自訂 DNS 設定,已自動關閉 DNS 覆寫。"
|
||||
},
|
||||
"sections": {
|
||||
"general": "DNS 設定",
|
||||
"fallbackFilter": "備援篩選設定",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { beforeEach, expect, it, vi } from 'vitest'
|
||||
|
||||
import { takeDnsOverrideNotice } from '@/services/cmds'
|
||||
import { subscribeVergeEvents } from '@/services/events'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { revalidateQueries } from '@/services/query-client'
|
||||
|
||||
import { handleNoticeMessage } from '../utils/notification-handlers'
|
||||
|
||||
import { useLayoutEvents } from './use-layout-events'
|
||||
|
||||
vi.mock('react', () => ({ useEffect: (effect: () => void) => effect() }))
|
||||
vi.mock('@/hooks/use-profiles', () => ({ revalidateProfiles: vi.fn() }))
|
||||
vi.mock('@/hooks/use-system-state', () => ({ runStateQueryKey: ['state'] }))
|
||||
vi.mock('@/services/events', () => ({ subscribeVergeEvents: vi.fn() }))
|
||||
vi.mock('@/services/cmds', () => ({ takeDnsOverrideNotice: vi.fn() }))
|
||||
vi.mock('@/services/notice-service', () => ({
|
||||
showNotice: { info: vi.fn() },
|
||||
}))
|
||||
vi.mock('@/services/query-client', () => ({
|
||||
revalidateQueries: vi.fn(),
|
||||
setCacheData: vi.fn(),
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('drains a DNS notice after listeners mount without duplicating its live event', async () => {
|
||||
vi.mocked(takeDnsOverrideNotice)
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockResolvedValue(false)
|
||||
|
||||
useLayoutEvents(([status, message]) => {
|
||||
handleNoticeMessage(status, message, (key) => key, vi.fn())
|
||||
})
|
||||
|
||||
expect(takeDnsOverrideNotice).not.toHaveBeenCalled()
|
||||
const [handlers, onSubscribed] = vi.mocked(subscribeVergeEvents).mock.calls[0]
|
||||
onSubscribed?.()
|
||||
expect(takeDnsOverrideNotice).toHaveBeenCalledOnce()
|
||||
handlers['verge://notice-message']?.(['dns_override::auto_disabled', ''])
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
|
||||
expect(showNotice.info).toHaveBeenCalledExactlyOnceWith(
|
||||
'settings.modals.dns.protection.autoDisabled',
|
||||
)
|
||||
expect(revalidateQueries).toHaveBeenCalledWith([
|
||||
['getRuntimeState'],
|
||||
['getVergeConfig'],
|
||||
])
|
||||
})
|
||||
@@ -61,7 +61,10 @@ export const useLayoutEvents = (
|
||||
'verge://notice-message': handleNotice,
|
||||
},
|
||||
// Re-read event-only state after subscribing to close the initial race window.
|
||||
() => revalidateKeys(['getRuntimeState']),
|
||||
() => {
|
||||
revalidateKeys(['getRuntimeState', 'getVergeConfig'])
|
||||
handleNotice(['dns_override::auto_disabled', ''])
|
||||
},
|
||||
)
|
||||
}, [handleNotice])
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { takeDnsOverrideNotice } from '@/services/cmds'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
|
||||
type NavigateFunction = (path: string, options?: any) => void
|
||||
@@ -24,6 +25,17 @@ export const handleNoticeMessage = (
|
||||
showNotice.error(msg)
|
||||
},
|
||||
'set_config::error': () => showNotice.error(msg),
|
||||
'dns_override::auto_disabled': () => {
|
||||
void takeDnsOverrideNotice()
|
||||
.then((pending) => {
|
||||
if (pending) {
|
||||
showNotice.info('settings.modals.dns.protection.autoDisabled')
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to read the pending DNS override notice', error)
|
||||
})
|
||||
},
|
||||
'tun_mode::auto_disabled': () =>
|
||||
showNotice.info(
|
||||
'settings.sections.system.notifications.tunMode.autoDisabled',
|
||||
|
||||
@@ -172,6 +172,16 @@ export async function patchVergeConfig(payload: IVergeConfig) {
|
||||
return invoke<void>('patch_verge_config', { payload })
|
||||
}
|
||||
|
||||
export async function setDnsOverride(enabled: boolean, confirmation?: string) {
|
||||
return invoke<
|
||||
{ status: 'applied' } | { status: 'confirmation_required'; source: string }
|
||||
>('set_dns_override', { enabled, confirmation })
|
||||
}
|
||||
|
||||
export async function takeDnsOverrideNotice() {
|
||||
return invoke<boolean>('take_dns_override_notice')
|
||||
}
|
||||
|
||||
export async function getSystemProxy() {
|
||||
return invoke<{
|
||||
enable: boolean
|
||||
|
||||
@@ -697,6 +697,11 @@ export const translationKeys = [
|
||||
'settings.modals.tun.messages.routeExcludeAddressHint',
|
||||
'settings.modals.dns.dialog.title',
|
||||
'settings.modals.dns.dialog.warning',
|
||||
'settings.modals.dns.protection.title',
|
||||
'settings.modals.dns.protection.message',
|
||||
'settings.modals.dns.protection.enableAnyway',
|
||||
'settings.modals.dns.protection.keepDisabled',
|
||||
'settings.modals.dns.protection.autoDisabled',
|
||||
'settings.modals.dns.sections.general',
|
||||
'settings.modals.dns.sections.fallbackFilter',
|
||||
'settings.modals.dns.sections.hosts',
|
||||
|
||||
@@ -1037,6 +1037,13 @@ export interface TranslationResources {
|
||||
configError: string
|
||||
saved: string
|
||||
}
|
||||
protection: {
|
||||
autoDisabled: string
|
||||
enableAnyway: string
|
||||
keepDisabled: string
|
||||
message: string
|
||||
title: string
|
||||
}
|
||||
sections: {
|
||||
fallbackFilter: string
|
||||
general: string
|
||||
|
||||
Reference in New Issue
Block a user