mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-15 15:32:21 +08:00
feat(connections): prefer clicked targets and support IP rules
This commit is contained in:
+1
-1
@@ -51,7 +51,7 @@
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- 新增连接页面按网络协议(TCP/UDP)筛选连接
|
||||
- 新增从连接详情快捷设置域名或进程规则,支持子域匹配与修改已有规则
|
||||
- 新增从连接详情快捷设置域名、进程或源/目标 IP 规则,支持子域匹配与修改已有规则
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -8,14 +8,21 @@ import { closeConnection } from 'tauri-plugin-mihomo-api'
|
||||
import { useProfiles } from '@/hooks/use-profiles'
|
||||
import parseTraffic from '@/utils/parse-traffic'
|
||||
|
||||
import { getConnectionRuleTargets } from './connection-rule'
|
||||
import {
|
||||
getConnectionRuleTargets,
|
||||
type ConnectionRuleKind,
|
||||
} from './connection-rule'
|
||||
import {
|
||||
ConnectionRuleDialog,
|
||||
type ConnectionRuleTarget,
|
||||
} from './connection-rule-dialog'
|
||||
|
||||
export interface ConnectionDetailRef {
|
||||
open: (detail: IConnectionsItem, closed: boolean) => void
|
||||
open: (
|
||||
detail: IConnectionsItem,
|
||||
closed: boolean,
|
||||
preferredKind?: ConnectionRuleKind,
|
||||
) => void
|
||||
close: () => void
|
||||
}
|
||||
|
||||
@@ -23,6 +30,8 @@ export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [detail, setDetail] = useState<IConnectionsItem | null>(null)
|
||||
const [closed, setClosed] = useState(false)
|
||||
const [preferredKind, setPreferredKind] =
|
||||
useState<ConnectionRuleKind>('domain')
|
||||
const theme = useTheme()
|
||||
const [ruleTarget, setRuleTarget] = useState<ConnectionRuleTarget | null>(
|
||||
null,
|
||||
@@ -35,11 +44,16 @@ export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
||||
}, [])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: (detail: IConnectionsItem, closed: boolean) => {
|
||||
open: (
|
||||
detail: IConnectionsItem,
|
||||
closed: boolean,
|
||||
preferredKind = 'domain',
|
||||
) => {
|
||||
if (open) return
|
||||
setOpen(true)
|
||||
setDetail(detail)
|
||||
setClosed(closed)
|
||||
setPreferredKind(preferredKind)
|
||||
},
|
||||
close: onClose,
|
||||
}))
|
||||
@@ -67,6 +81,7 @@ export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
||||
<InnerConnectionDetail
|
||||
data={detail}
|
||||
closed={closed}
|
||||
preferredKind={preferredKind}
|
||||
onClose={onClose}
|
||||
onAddRule={(target) => {
|
||||
setRuleTarget(target)
|
||||
@@ -89,6 +104,7 @@ export function ConnectionDetail({ ref }: { ref?: Ref<ConnectionDetailRef> }) {
|
||||
interface InnerProps {
|
||||
data: IConnectionsItem
|
||||
closed: boolean
|
||||
preferredKind: ConnectionRuleKind
|
||||
onClose?: () => void
|
||||
onAddRule: (target: ConnectionRuleTarget) => void
|
||||
}
|
||||
@@ -96,6 +112,7 @@ interface InnerProps {
|
||||
const InnerConnectionDetail = ({
|
||||
data,
|
||||
closed,
|
||||
preferredKind,
|
||||
onClose,
|
||||
onAddRule,
|
||||
}: InnerProps) => {
|
||||
@@ -106,7 +123,10 @@ const InnerConnectionDetail = ({
|
||||
const canAddRule = Boolean(
|
||||
current?.uid &&
|
||||
current.option?.rules &&
|
||||
(targets.domain || targets.process),
|
||||
(targets.domain ||
|
||||
targets.process ||
|
||||
targets.source ||
|
||||
targets.destination),
|
||||
)
|
||||
const theme = useTheme()
|
||||
const chains = [...data.chains].reverse().join(' / ')
|
||||
@@ -211,6 +231,7 @@ const InnerConnectionDetail = ({
|
||||
profileUid: current.uid,
|
||||
profileName: current.name || current.uid,
|
||||
rulesUid: current.option.rules,
|
||||
preferredKind,
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -232,7 +253,10 @@ const InnerConnectionDetail = ({
|
||||
{!canAddRule && (
|
||||
<Box sx={{ mt: 1, typography: 'caption' }}>
|
||||
{t(
|
||||
targets.domain || targets.process
|
||||
targets.domain ||
|
||||
targets.process ||
|
||||
targets.source ||
|
||||
targets.destination
|
||||
? 'connections.ruleDialog.noProfile'
|
||||
: 'connections.ruleDialog.noMetadata',
|
||||
)}
|
||||
|
||||
@@ -7,11 +7,12 @@ import { closeConnection } from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import { RelativeTime } from './connection-relative-time'
|
||||
import type { ConnectionRowView } from './connection-row-view'
|
||||
import type { ConnectionRuleKind } from './connection-rule'
|
||||
|
||||
interface Props {
|
||||
row: ConnectionRowView
|
||||
closed: boolean
|
||||
onShowDetail: (id: string) => void
|
||||
onShowDetail: (id: string, preferredKind?: ConnectionRuleKind) => void
|
||||
}
|
||||
|
||||
const tagStyle = {
|
||||
@@ -86,7 +87,17 @@ export const ConnectionRowItem = memo(
|
||||
<div style={tagsStyle}>
|
||||
<span style={tagStyle}>{row.network}</span>
|
||||
<span style={tagStyle}>{row.type}</span>
|
||||
{row.process && <span style={tagStyle}>{row.process}</span>}
|
||||
{row.process && (
|
||||
<span
|
||||
style={tagStyle}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
onShowDetail(row.id, 'process')
|
||||
}}
|
||||
>
|
||||
{row.process}
|
||||
</span>
|
||||
)}
|
||||
{row.chains && <span style={tagStyle}>{row.chains}</span>}
|
||||
<span style={tagStyle}>
|
||||
<RelativeTime start={row.time} />
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
getConnectionRuleTargets,
|
||||
findConnectionRule,
|
||||
saveConnectionRule,
|
||||
type ConnectionRuleKind,
|
||||
} from './connection-rule'
|
||||
|
||||
export interface ConnectionRuleTarget {
|
||||
@@ -37,9 +38,17 @@ export interface ConnectionRuleTarget {
|
||||
profileUid: string
|
||||
profileName: string
|
||||
rulesUid: string
|
||||
preferredKind: ConnectionRuleKind
|
||||
}
|
||||
|
||||
type ConnectionRuleKind = 'domain' | 'process'
|
||||
function getInitialKind(target: ConnectionRuleTarget) {
|
||||
const targets = getConnectionRuleTargets(target.metadata)
|
||||
return targets[target.preferredKind]
|
||||
? target.preferredKind
|
||||
: ((['domain', 'process', 'source', 'destination'] as const).find(
|
||||
(kind) => targets[kind],
|
||||
) ?? 'domain')
|
||||
}
|
||||
|
||||
export function ConnectionRuleDialog({
|
||||
target,
|
||||
@@ -52,8 +61,8 @@ export function ConnectionRuleDialog({
|
||||
const { proxyView } = useProxiesData()
|
||||
const { clashConfig } = useClashConfigData()
|
||||
const targets = getConnectionRuleTargets(target.metadata)
|
||||
const [kind, setKind] = useState<ConnectionRuleKind>(
|
||||
targets.domain ? 'domain' : 'process',
|
||||
const [kind, setKind] = useState<ConnectionRuleKind>(() =>
|
||||
getInitialKind(target),
|
||||
)
|
||||
const [policy, setPolicy] = useState<string | null>(null)
|
||||
const [scope, setScope] = useState<'DOMAIN' | 'DOMAIN-SUFFIX'>('DOMAIN')
|
||||
@@ -83,6 +92,8 @@ export function ConnectionRuleDialog({
|
||||
const kindLabels = {
|
||||
domain: t('connections.ruleDialog.domain'),
|
||||
process: t('connections.components.fields.process'),
|
||||
source: t('connections.ruleDialog.sourceIp'),
|
||||
destination: t('connections.ruleDialog.destinationIp'),
|
||||
}
|
||||
|
||||
const unavailablePolicy = Boolean(policy && !policies.includes(policy))
|
||||
@@ -106,9 +117,15 @@ export function ConnectionRuleDialog({
|
||||
process: ruleTargets.process
|
||||
? findConnectionRule(content, ruleTargets.process)
|
||||
: undefined,
|
||||
source: ruleTargets.source
|
||||
? findConnectionRule(content, ruleTargets.source)
|
||||
: undefined,
|
||||
destination: ruleTargets.destination
|
||||
? findConnectionRule(content, ruleTargets.destination)
|
||||
: undefined,
|
||||
}
|
||||
if (cancelled) return
|
||||
const initialKind = ruleTargets.domain ? 'domain' : 'process'
|
||||
const initialKind = getInitialKind(target)
|
||||
const initial =
|
||||
initialKind === 'domain'
|
||||
? (rules.domain ?? rules.suffix)
|
||||
@@ -208,13 +225,19 @@ export function ConnectionRuleDialog({
|
||||
)
|
||||
}}
|
||||
>
|
||||
{(['domain', 'process'] as const).map((option) => (
|
||||
<MenuItem key={option} value={option} disabled={!targets[option]}>
|
||||
{kindLabels[option]}
|
||||
{!targets[option] &&
|
||||
` · ${t('connections.ruleDialog.unavailable')}`}
|
||||
</MenuItem>
|
||||
))}
|
||||
{(['domain', 'process', 'source', 'destination'] as const).map(
|
||||
(option) => (
|
||||
<MenuItem
|
||||
key={option}
|
||||
value={option}
|
||||
disabled={!targets[option]}
|
||||
>
|
||||
{kindLabels[option]}
|
||||
{!targets[option] &&
|
||||
` · ${t('connections.ruleDialog.unavailable')}`}
|
||||
</MenuItem>
|
||||
),
|
||||
)}
|
||||
</TextField>
|
||||
{kind === 'process' ? (
|
||||
<>
|
||||
@@ -284,6 +307,15 @@ export function ConnectionRuleDialog({
|
||||
/>
|
||||
</RadioGroup>
|
||||
)}
|
||||
{(kind === 'source' || kind === 'destination') && (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
{t(
|
||||
kind === 'source'
|
||||
? 'connections.ruleDialog.sourceHint'
|
||||
: 'connections.ruleDialog.destinationHint',
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
{kind === 'process' && (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
{selected?.type === 'PROCESS-PATH'
|
||||
|
||||
@@ -2,10 +2,27 @@ import { dump, load } from 'js-yaml'
|
||||
import isFQDN from 'validator/es/lib/isFQDN'
|
||||
import isIP from 'validator/es/lib/isIP'
|
||||
|
||||
export type ConnectionRuleKind = 'domain' | 'process' | 'source' | 'destination'
|
||||
|
||||
function getAddressRule(address: string | undefined, source: boolean) {
|
||||
const ip = address?.trim()
|
||||
if (!ip || !isIP(ip)) return undefined
|
||||
const ipv4 = isIP(ip, 4)
|
||||
return {
|
||||
type: source ? 'SRC-IP-CIDR' : ipv4 ? 'IP-CIDR' : 'IP-CIDR6',
|
||||
value: `${ip}/${ipv4 ? 32 : 128}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function getConnectionRuleTargets(
|
||||
metadata: Pick<
|
||||
IConnectionsItem['metadata'],
|
||||
'host' | 'process' | 'processPath'
|
||||
| 'host'
|
||||
| 'process'
|
||||
| 'processPath'
|
||||
| 'sourceIP'
|
||||
| 'destinationIP'
|
||||
| 'remoteDestination'
|
||||
>,
|
||||
) {
|
||||
const host = metadata.host?.trim() ?? ''
|
||||
@@ -20,7 +37,11 @@ export function getConnectionRuleTargets(
|
||||
: name?.trim()
|
||||
? { type: 'PROCESS-NAME', value: name }
|
||||
: undefined
|
||||
return { domain, process }
|
||||
const source = getAddressRule(metadata.sourceIP, true)
|
||||
const destination =
|
||||
getAddressRule(metadata.destinationIP, false) ??
|
||||
getAddressRule(metadata.remoteDestination, false)
|
||||
return { domain, process, source, destination }
|
||||
}
|
||||
|
||||
function parseRuleExtension(content: string) {
|
||||
@@ -55,7 +76,11 @@ interface RuleCondition {
|
||||
|
||||
function matchesConnectionRule(raw: string, target: RuleCondition) {
|
||||
const parts = raw.split(',').map((part) => part.trim())
|
||||
if (parts.length !== 3) return false
|
||||
const hasNoResolve =
|
||||
(target.type === 'IP-CIDR' || target.type === 'IP-CIDR6') &&
|
||||
parts.length === 4 &&
|
||||
parts[3] === 'no-resolve'
|
||||
if (parts.length !== 3 && !hasNoResolve) return false
|
||||
if (target.type === 'DOMAIN' || target.type === 'DOMAIN-SUFFIX') {
|
||||
return (
|
||||
parts[0] === target.type &&
|
||||
@@ -92,7 +117,7 @@ export function saveConnectionRule(
|
||||
if (!matchesConnectionRule(raw, target)) return [raw]
|
||||
if (replaced) return []
|
||||
replaced = true
|
||||
return [rule]
|
||||
return [raw.split(',').length === 4 ? `${rule},no-resolve` : rule]
|
||||
})
|
||||
}
|
||||
if (!replaced) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
getConnectionStartTime,
|
||||
getConnectionTypeLabel,
|
||||
} from './connection-row-view'
|
||||
import type { ConnectionRuleKind } from './connection-rule'
|
||||
|
||||
const ROW_HEIGHT = 40
|
||||
const RESIZE_HANDLE_WIDTH = 6
|
||||
@@ -247,7 +248,7 @@ const renderCell = (
|
||||
interface RowComponentProps {
|
||||
row: IConnectionsItem
|
||||
columns: DisplayColumn[]
|
||||
onShowDetail: (id: string) => void
|
||||
onShowDetail: (id: string, preferredKind?: ConnectionRuleKind) => void
|
||||
getSnapshot: (row: IConnectionsItem) => TableRowSnapshot
|
||||
borderColor: string
|
||||
virtualTop: number
|
||||
@@ -263,7 +264,22 @@ const RowComponent = memo(
|
||||
virtualTop,
|
||||
}: RowComponentProps) {
|
||||
const handleClick = useCallback(
|
||||
() => onShowDetail(row.id),
|
||||
(event: ReactMouseEvent<HTMLDivElement>) => {
|
||||
const field =
|
||||
event.target instanceof Element
|
||||
? event.target.closest<HTMLElement>('[data-connection-field]')
|
||||
?.dataset.connectionField
|
||||
: undefined
|
||||
const preferredKind =
|
||||
field === 'process'
|
||||
? 'process'
|
||||
: field === 'source'
|
||||
? 'source'
|
||||
: field === 'remoteDestination'
|
||||
? 'destination'
|
||||
: 'domain'
|
||||
onShowDetail(row.id, preferredKind)
|
||||
},
|
||||
[onShowDetail, row.id],
|
||||
)
|
||||
const snapshot = getSnapshot(row)
|
||||
@@ -285,6 +301,7 @@ const RowComponent = memo(
|
||||
{columns.map((column) => (
|
||||
<div
|
||||
key={column.field}
|
||||
data-connection-field={column.field}
|
||||
style={{
|
||||
boxSizing: 'border-box',
|
||||
flex: `0 0 ${column.size}px`,
|
||||
@@ -318,7 +335,7 @@ const RowComponent = memo(
|
||||
|
||||
interface Props {
|
||||
connections: IConnectionsItem[]
|
||||
onShowDetail: (id: string) => void
|
||||
onShowDetail: (id: string, preferredKind?: ConnectionRuleKind) => void
|
||||
columnManagerOpen: boolean
|
||||
onCloseColumnManager: () => void
|
||||
}
|
||||
@@ -333,7 +350,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
const onShowDetailRef = useRef(rawOnShowDetail)
|
||||
onShowDetailRef.current = rawOnShowDetail
|
||||
const onShowDetail = useCallback(
|
||||
(id: string) => onShowDetailRef.current(id),
|
||||
(id: string, preferredKind?: ConnectionRuleKind) =>
|
||||
onShowDetailRef.current(id, preferredKind),
|
||||
[],
|
||||
)
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "规则未能应用,已保留当前选择。",
|
||||
"unsupportedValue": "此内容包含快捷规则不支持的字符。",
|
||||
"saved": "规则已保存,配置已应用。",
|
||||
"noMetadata": "此连接没有可用于添加规则的域名或进程信息。",
|
||||
"noMetadata": "此连接没有可用于设置规则的域名、进程或 IP 信息。",
|
||||
"noProfile": "当前没有可编辑规则扩展的活动订阅。",
|
||||
"entry": "设置规则…",
|
||||
"modifyTitle": "修改规则",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "此策略已不可用,请选择其他策略。",
|
||||
"editDestination": "修改「{{name}}」中的现有规则,保留排序。",
|
||||
"saveAndApply": "保存并应用",
|
||||
"sourceIp": "源 IP",
|
||||
"destinationIp": "目标 IP",
|
||||
"sourceHint": "匹配来自此 IP 的所有请求,可能包含其他应用,不限定端口。",
|
||||
"destinationHint": "匹配访问此目标 IP 的请求,覆盖所有域名,不限定端口。",
|
||||
"fullPath": "完整路径"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"applyFailed": "The rule could not be applied. Your selection has been kept.",
|
||||
"unsupportedValue": "This value contains characters that cannot be used in a quick rule.",
|
||||
"saved": "Rule saved and configuration applied.",
|
||||
"noMetadata": "This connection has no domain or process information for adding a rule.",
|
||||
"noMetadata": "This connection has no domain, process or IP information for configuring a rule.",
|
||||
"noProfile": "No active profile with an editable rule extension is available.",
|
||||
"entry": "Configure rule…",
|
||||
"modifyTitle": "Modify rule",
|
||||
@@ -56,6 +56,10 @@
|
||||
"policyUnavailable": "This policy is no longer available. Choose another policy.",
|
||||
"editDestination": "Update the existing rule in “{{name}}”, keeping its position.",
|
||||
"saveAndApply": "Save and apply",
|
||||
"sourceIp": "Source IP",
|
||||
"destinationIp": "Destination IP",
|
||||
"sourceHint": "Matches all requests from this IP, including requests from other applications. No port filtering.",
|
||||
"destinationHint": "Matches this destination IP across all domains. No port filtering.",
|
||||
"fullPath": "Full path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
getConnectionStartTime,
|
||||
useConnectionRowViews,
|
||||
} from '@/components/connection/connection-row-view'
|
||||
import type { ConnectionRuleKind } from '@/components/connection/connection-rule'
|
||||
import { ConnectionTable } from '@/components/connection/connection-table'
|
||||
import { useConnectionData } from '@/hooks/use-connection-data'
|
||||
import { useConnectionSetting } from '@/hooks/use-connection-setting'
|
||||
@@ -161,10 +162,14 @@ const ConnectionsPage = () => {
|
||||
)
|
||||
|
||||
const showDetailById = useCallback(
|
||||
(id: string) => {
|
||||
(id: string, preferredKind: ConnectionRuleKind = 'domain') => {
|
||||
const connection = filterConn.find((item) => item.id === id)
|
||||
if (connection) {
|
||||
detailRef.current?.open(connection, connectionsType === 'closed')
|
||||
detailRef.current?.open(
|
||||
connection,
|
||||
connectionsType === 'closed',
|
||||
preferredKind,
|
||||
)
|
||||
}
|
||||
},
|
||||
[connectionsType, filterConn],
|
||||
|
||||
@@ -46,6 +46,10 @@ export const translationKeys = [
|
||||
'connections.ruleDialog.policyUnavailable',
|
||||
'connections.ruleDialog.editDestination',
|
||||
'connections.ruleDialog.saveAndApply',
|
||||
'connections.ruleDialog.sourceIp',
|
||||
'connections.ruleDialog.destinationIp',
|
||||
'connections.ruleDialog.sourceHint',
|
||||
'connections.ruleDialog.destinationHint',
|
||||
'connections.ruleDialog.fullPath',
|
||||
'home.page.tooltips.lightweightMode',
|
||||
'home.page.tooltips.manual',
|
||||
|
||||
@@ -41,6 +41,8 @@ export interface TranslationResources {
|
||||
allSubdomains: string
|
||||
applyFailed: string
|
||||
destination: string
|
||||
destinationHint: string
|
||||
destinationIp: string
|
||||
domain: string
|
||||
domainScope: string
|
||||
editDestination: string
|
||||
@@ -59,6 +61,8 @@ export interface TranslationResources {
|
||||
ruleModeHint: string
|
||||
saveAndApply: string
|
||||
saved: string
|
||||
sourceHint: string
|
||||
sourceIp: string
|
||||
title: string
|
||||
unavailable: string
|
||||
unsupportedValue: string
|
||||
|
||||
Reference in New Issue
Block a user