mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-15 15:32:21 +08:00
feat(connections): select the root domain for suffix rules
This commit is contained in:
+1
-1
@@ -51,7 +51,7 @@
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- 新增连接页面按网络协议(TCP/UDP)筛选连接
|
||||
- 新增从连接详情快捷设置域名、进程或源/目标 IP 规则,支持父域及子域匹配与修改已有规则
|
||||
- 新增从连接详情设置域名、进程或源/目标 IP 规则,支持自动识别主域、匹配子域及修改已有规则
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"swr": "^2.5.1",
|
||||
"tauri-plugin-mihomo-api": "github:clash-verge-rev/tauri-plugin-mihomo#main",
|
||||
"tldts": "7.4.19",
|
||||
"types-pac": "^1.0.3",
|
||||
"validator": "^13.15.35"
|
||||
},
|
||||
|
||||
Generated
+16
@@ -295,6 +295,9 @@ importers:
|
||||
tauri-plugin-mihomo-api:
|
||||
specifier: github:clash-verge-rev/tauri-plugin-mihomo#main
|
||||
version: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/e49bbedcc67d60da2c10e81bc6433b0f8e8fa232
|
||||
tldts:
|
||||
specifier: 7.4.19
|
||||
version: 7.4.19
|
||||
types-pac:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
@@ -4035,6 +4038,13 @@ packages:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tldts-core@7.5.2:
|
||||
resolution: {integrity: sha512-8W1topazDwhMKbm2I83IPa57Ow5VUxFgLbXv42+vSILnv8FY5OHvdEY/tS6WoWeMbxkHZQhra5cmeFHriKptSQ==}
|
||||
|
||||
tldts@7.4.19:
|
||||
resolution: {integrity: sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q==}
|
||||
hasBin: true
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
@@ -8138,6 +8148,12 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.7)
|
||||
picomatch: 4.0.7
|
||||
|
||||
tldts-core@7.5.2: {}
|
||||
|
||||
tldts@7.4.19:
|
||||
dependencies:
|
||||
tldts-core: 7.5.2
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
trough@2.2.0: {}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { showNotice } from '@/services/notice-service'
|
||||
import {
|
||||
getConnectionRuleTargets,
|
||||
getConnectionRules,
|
||||
getConnectionRootDomain,
|
||||
isConnectionRuleDomain,
|
||||
findConnectionRule,
|
||||
saveConnectionRule,
|
||||
@@ -43,6 +44,8 @@ export interface ConnectionRuleTarget {
|
||||
preferredKind: ConnectionRuleKind
|
||||
}
|
||||
|
||||
type DomainScope = 'DOMAIN' | 'DOMAIN-SUFFIX' | 'ROOT-DOMAIN'
|
||||
|
||||
function getInitialKind(target: ConnectionRuleTarget) {
|
||||
const targets = getConnectionRuleTargets(target.metadata)
|
||||
return targets[target.preferredKind]
|
||||
@@ -63,11 +66,12 @@ export function ConnectionRuleDialog({
|
||||
const { proxyView } = useProxiesData()
|
||||
const { clashConfig } = useClashConfigData()
|
||||
const targets = getConnectionRuleTargets(target.metadata)
|
||||
const [rootDomain, setRootDomain] = useState<string>()
|
||||
const [kind, setKind] = useState<ConnectionRuleKind>(() =>
|
||||
getInitialKind(target),
|
||||
)
|
||||
const [policy, setPolicy] = useState<string | null>(null)
|
||||
const [scope, setScope] = useState<'DOMAIN' | 'DOMAIN-SUFFIX'>('DOMAIN')
|
||||
const [scope, setScope] = useState<DomainScope>('DOMAIN')
|
||||
const [domainValue, setDomainValue] = useState(targets.domain?.value ?? '')
|
||||
const [rules, setRules] = useState<string[] | null>(null)
|
||||
const [loadAttempt, setLoadAttempt] = useState(0)
|
||||
@@ -75,14 +79,23 @@ export function ConnectionRuleDialog({
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const selected = targets[kind]
|
||||
const matchingRoot = kind === 'domain' && scope === 'ROOT-DOMAIN'
|
||||
const editingDomain = kind === 'domain' && scope === 'DOMAIN-SUFFIX'
|
||||
const matchingValue = matchingRoot
|
||||
? (rootDomain ?? '')
|
||||
: editingDomain
|
||||
? domainValue
|
||||
: (selected?.value ?? '')
|
||||
const invalidDomain =
|
||||
editingDomain &&
|
||||
!isConnectionRuleDomain(domainValue, targets.domain?.value ?? '')
|
||||
const condition = selected
|
||||
? {
|
||||
type: kind === 'domain' ? scope : selected.type,
|
||||
value: editingDomain ? domainValue.trim() : selected.value,
|
||||
type:
|
||||
kind === 'domain' && scope !== 'DOMAIN'
|
||||
? 'DOMAIN-SUFFIX'
|
||||
: selected.type,
|
||||
value: kind === 'domain' ? matchingValue.trim() : selected.value,
|
||||
}
|
||||
: undefined
|
||||
const existing =
|
||||
@@ -111,10 +124,15 @@ export function ConnectionRuleDialog({
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
readProfileFile(target.rulesUid)
|
||||
.then((content) => {
|
||||
const ruleTargets = getConnectionRuleTargets(target.metadata)
|
||||
Promise.all([
|
||||
readProfileFile(target.rulesUid),
|
||||
ruleTargets.domain
|
||||
? getConnectionRootDomain(ruleTargets.domain.value)
|
||||
: undefined,
|
||||
])
|
||||
.then(([content, root]) => {
|
||||
const loadedRules = getConnectionRules(content)
|
||||
const ruleTargets = getConnectionRuleTargets(target.metadata)
|
||||
const initialKind = getInitialKind(target)
|
||||
const selectedTarget = ruleTargets[initialKind]
|
||||
const initial = selectedTarget
|
||||
@@ -127,6 +145,7 @@ export function ConnectionRuleDialog({
|
||||
: undefined))
|
||||
: undefined
|
||||
if (cancelled) return
|
||||
setRootDomain(root)
|
||||
setRules(loadedRules)
|
||||
setPolicy(initial?.policy ?? null)
|
||||
setScope(initial?.type === 'DOMAIN-SUFFIX' ? 'DOMAIN-SUFFIX' : 'DOMAIN')
|
||||
@@ -279,11 +298,11 @@ export function ConnectionRuleDialog({
|
||||
fullWidth
|
||||
size="small"
|
||||
label={
|
||||
editingDomain
|
||||
editingDomain || matchingRoot
|
||||
? t('connections.ruleDialog.matchingDomain')
|
||||
: kindLabels[kind]
|
||||
}
|
||||
value={editingDomain ? domainValue : (selected?.value ?? '')}
|
||||
value={matchingValue}
|
||||
disabled={editingDomain && blocked}
|
||||
onChange={(event) => {
|
||||
const value = event.target.value
|
||||
@@ -300,7 +319,7 @@ export function ConnectionRuleDialog({
|
||||
}}
|
||||
error={invalidDomain}
|
||||
helperText={
|
||||
editingDomain ? (
|
||||
editingDomain || matchingRoot ? (
|
||||
<>
|
||||
{t('connections.ruleDialog.connectionHost', {
|
||||
host: targets.domain?.value,
|
||||
@@ -317,7 +336,7 @@ export function ConnectionRuleDialog({
|
||||
slotProps={{
|
||||
input: { readOnly: !editingDomain },
|
||||
htmlInput: {
|
||||
title: editingDomain ? domainValue : selected?.value,
|
||||
title: condition?.value,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -329,14 +348,17 @@ export function ConnectionRuleDialog({
|
||||
aria-label={t('connections.ruleDialog.domainScope')}
|
||||
value={scope}
|
||||
onChange={(_, value) => {
|
||||
const nextScope = value as 'DOMAIN' | 'DOMAIN-SUFFIX'
|
||||
const nextScope = value as DomainScope
|
||||
setScope(nextScope)
|
||||
setDomainValue(targets.domain?.value ?? '')
|
||||
const match =
|
||||
rules && targets.domain
|
||||
? findConnectionRule(rules, {
|
||||
...targets.domain,
|
||||
type: nextScope,
|
||||
type: nextScope === 'DOMAIN' ? 'DOMAIN' : 'DOMAIN-SUFFIX',
|
||||
value:
|
||||
nextScope === 'ROOT-DOMAIN'
|
||||
? (rootDomain ?? '')
|
||||
: targets.domain.value,
|
||||
})
|
||||
: undefined
|
||||
setPolicy(match?.policy ?? null)
|
||||
@@ -355,6 +377,18 @@ export function ConnectionRuleDialog({
|
||||
disabled={blocked}
|
||||
label={t('connections.ruleDialog.allSubdomains')}
|
||||
/>
|
||||
{rootDomain &&
|
||||
rootDomain.toLowerCase() !==
|
||||
targets.domain?.value.toLowerCase() && (
|
||||
<FormControlLabel
|
||||
value="ROOT-DOMAIN"
|
||||
control={<Radio size="small" />}
|
||||
disabled={blocked}
|
||||
label={t('connections.ruleDialog.rootDomain', {
|
||||
domain: rootDomain,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</RadioGroup>
|
||||
)}
|
||||
{(kind === 'source' || kind === 'destination') && (
|
||||
|
||||
@@ -56,6 +56,14 @@ export function isConnectionRuleDomain(value: string, host: string) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function getConnectionRootDomain(host: string) {
|
||||
const { parse } = await import('tldts')
|
||||
const { domain, isIcann, isPrivate } = parse(host, {
|
||||
allowPrivateDomains: true,
|
||||
})
|
||||
return domain && (isIcann || isPrivate) ? domain : undefined
|
||||
}
|
||||
|
||||
function parseRuleExtension(content: string) {
|
||||
const parsed: unknown = load(content)
|
||||
if (
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "النطاق الرئيسي وجميع نطاقاته الفرعية ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Hauptdomain und alle Subdomains ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Root domain and all its subdomains ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Dominio principal y todos sus subdominios ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "دامنهٔ اصلی و همهٔ زیردامنههای آن ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Domain utama dan semua subdomainnya ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "メインドメインとすべてのサブドメイン({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "기본 도메인 및 모든 하위 도메인 ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Domínio principal e todos os seus subdomínios ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Основной домен и все его поддомены ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Ana alan adı ve tüm alt alan adları ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "Төп домен һәм аның барлык субдоменнары ({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "域名匹配范围",
|
||||
"exactDomain": "仅此域名",
|
||||
"allSubdomains": "此域名及所有子域名",
|
||||
"rootDomain": "主域名及所有子域名({{domain}})",
|
||||
"policyUnavailable": "此策略已不可用,请选择其他策略。",
|
||||
"editDestination": "修改「{{name}}」中的现有规则,保留排序。",
|
||||
"saveAndApply": "保存并应用",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"domainScope": "Domain scope",
|
||||
"exactDomain": "Only this domain",
|
||||
"allSubdomains": "This domain and all its subdomains",
|
||||
"rootDomain": "主網域及所有子網域({{domain}})",
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
|
||||
@@ -43,6 +43,7 @@ export const translationKeys = [
|
||||
'connections.ruleDialog.domainScope',
|
||||
'connections.ruleDialog.exactDomain',
|
||||
'connections.ruleDialog.allSubdomains',
|
||||
'connections.ruleDialog.rootDomain',
|
||||
'connections.ruleDialog.policyUnavailable',
|
||||
'connections.ruleDialog.editDestination',
|
||||
'connections.ruleDialog.saveAndApply',
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface TranslationResources {
|
||||
processNameHint: string
|
||||
processPathHint: string
|
||||
profileChanged: string
|
||||
rootDomain: string
|
||||
ruleModeHint: string
|
||||
saveAndApply: string
|
||||
saved: string
|
||||
|
||||
Reference in New Issue
Block a user