mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-06 17:59:11 +00:00
fix android vpn permission grant (#2023)
* fix android vpn permission grant * fix url input behaviour
This commit is contained in:
@@ -209,7 +209,8 @@ watch(() => curNetwork.value, syncNormalizedNetwork, { immediate: true, deep: fa
|
||||
</div>
|
||||
<div class="items-center flex flex-col p-fluid gap-y-2">
|
||||
<UrlListInput id="initial_nodes" v-model="curNetwork.peer_urls" :protos="protos"
|
||||
:add-label="t('add_initial_node')" :placeholder="t('initial_node_placeholder')" />
|
||||
defaultUrl="tcp://:11010" :add-label="t('add_initial_node')"
|
||||
:placeholder="t('initial_node_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -300,8 +301,8 @@ watch(() => curNetwork.value, syncNormalizedNetwork, { immediate: true, deep: fa
|
||||
<label for="mtu">{{ t('mtu') }}</label>
|
||||
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t('mtu_help')"></span>
|
||||
</div>
|
||||
<InputNumber id="mtu" v-model="curNetwork.mtu" aria-describedby="mtu-help" :format="false"
|
||||
:placeholder="t('mtu_placeholder')" :min="400" :max="1380" fluid />
|
||||
<InputNumber id="mtu" v-model="curNetwork.mtu" aria-describedby="mtu-help" :format="false"
|
||||
:placeholder="t('mtu_placeholder')" :min="400" :max="1380" fluid />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ const url = defineModel<string>({ required: true })
|
||||
const editing = ref(false)
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
const internalCompact = ref(false)
|
||||
const hostFocused = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (container.value) {
|
||||
@@ -36,36 +37,86 @@ const parseUrl = (val: string | null | undefined) => {
|
||||
const p = parseInt(portStr)
|
||||
return isNaN(p) ? (props.protos[proto] ?? 11010) : p
|
||||
}
|
||||
const parseByPattern = (input: string) => {
|
||||
const trimmed = input.trim()
|
||||
if (!trimmed) {
|
||||
return null
|
||||
}
|
||||
const match = trimmed.match(/^(\w+):\/\/(.*)$/)
|
||||
const proto = match ? match[1] : 'tcp'
|
||||
const rest = match ? match[2] : trimmed
|
||||
const authority = rest.split(/[/?#]/)[0]
|
||||
if (!authority) {
|
||||
return null
|
||||
}
|
||||
const hostAndMaybePort = authority.includes('@') ? authority.slice(authority.lastIndexOf('@') + 1) : authority
|
||||
if (hostAndMaybePort.startsWith('[')) {
|
||||
const ipv6End = hostAndMaybePort.indexOf(']')
|
||||
if (ipv6End > 0) {
|
||||
const host = hostAndMaybePort.slice(0, ipv6End + 1)
|
||||
const remain = hostAndMaybePort.slice(ipv6End + 1)
|
||||
const port = remain.startsWith(':') ? getValidPort(remain.slice(1), proto) : (props.protos[proto] ?? 11010)
|
||||
return { proto, host, port }
|
||||
}
|
||||
}
|
||||
const portMatch = hostAndMaybePort.match(/^(.*):(\d+)$/)
|
||||
const host = portMatch ? portMatch[1] : hostAndMaybePort
|
||||
const port = portMatch ? parseInt(portMatch[2]) : (props.protos[proto] ?? 11010)
|
||||
return { proto, host, port }
|
||||
}
|
||||
|
||||
if (!val) {
|
||||
return { proto: 'tcp', host: '', port: props.protos['tcp'] ?? 11010 }
|
||||
}
|
||||
try {
|
||||
const urlObj = new URL(val)
|
||||
const proto = urlObj.protocol.replace(':', '')
|
||||
return {
|
||||
proto: proto,
|
||||
host: urlObj.hostname,
|
||||
port: getValidPort(urlObj.port, proto)
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback for incomplete or invalid URLs
|
||||
const match = val.match(/^(\w+):\/\/(.*)$/)
|
||||
if (match) {
|
||||
const proto = match[1]
|
||||
const rest = match[2]
|
||||
const portMatch = rest.match(/:(\d+)$/)
|
||||
return {
|
||||
proto,
|
||||
host: portMatch ? rest.slice(0, portMatch.index) : rest,
|
||||
port: portMatch ? parseInt(portMatch[1]) : (props.protos[proto] ?? 11010)
|
||||
}
|
||||
}
|
||||
return { proto: 'tcp', host: '', port: 11010 }
|
||||
const parsedByPattern = parseByPattern(val)
|
||||
if (parsedByPattern) {
|
||||
return parsedByPattern
|
||||
}
|
||||
return { proto: 'tcp', host: '', port: 11010 }
|
||||
}
|
||||
|
||||
const internalValue = ref(parseUrl(url.value))
|
||||
const defaultHost = '0.0.0.0'
|
||||
|
||||
const buildUrlValue = (value: { proto: string, host: string, port: number }, forceDefaultHost = false) => {
|
||||
const proto = value.proto || 'tcp'
|
||||
const rawHost = (value.host ?? '').trim()
|
||||
const host = rawHost || (forceDefaultHost ? defaultHost : '')
|
||||
if (!host) {
|
||||
return null
|
||||
}
|
||||
let port = value.port
|
||||
if (isNaN(parseInt(port as any))) {
|
||||
port = props.protos[proto] ?? 11010
|
||||
}
|
||||
|
||||
if (props.protos[proto] === 0) {
|
||||
return `${proto}://${host}`
|
||||
}
|
||||
return `${proto}://${host}:${port}`
|
||||
}
|
||||
|
||||
const syncUrlFromInternal = (forceDefaultHost = false) => {
|
||||
const nextUrl = buildUrlValue(internalValue.value, forceDefaultHost)
|
||||
if (!nextUrl || nextUrl === url.value) {
|
||||
return
|
||||
}
|
||||
url.value = nextUrl
|
||||
}
|
||||
|
||||
const onHostBlur = () => {
|
||||
hostFocused.value = false
|
||||
syncUrlFromInternal(true)
|
||||
}
|
||||
|
||||
const onHostFocus = () => {
|
||||
hostFocused.value = true
|
||||
}
|
||||
|
||||
const onDialogConfirm = () => {
|
||||
syncUrlFromInternal(true)
|
||||
editing.value = false
|
||||
}
|
||||
|
||||
const isNoPortProto = computed(() => {
|
||||
return props.protos[internalValue.value.proto] === 0
|
||||
@@ -73,28 +124,22 @@ const isNoPortProto = computed(() => {
|
||||
|
||||
// Sync from external
|
||||
watch(() => url.value, (newVal) => {
|
||||
if (hostFocused.value) {
|
||||
return
|
||||
}
|
||||
const parsed = parseUrl(newVal)
|
||||
const internalHost = internalValue.value.host ?? ''
|
||||
const sameHost = parsed.host === internalHost || (!internalHost.trim() && parsed.host === defaultHost)
|
||||
if (parsed.proto !== internalValue.value.proto ||
|
||||
parsed.host !== internalValue.value.host ||
|
||||
!sameHost ||
|
||||
parsed.port !== internalValue.value.port) {
|
||||
internalValue.value = parsed
|
||||
}
|
||||
})
|
||||
|
||||
// Sync to external
|
||||
watch(internalValue, (newVal) => {
|
||||
const proto = newVal.proto || 'tcp'
|
||||
const host = newVal.host || '0.0.0.0'
|
||||
let port = newVal.port
|
||||
if (isNaN(parseInt(port as any))) {
|
||||
port = props.protos[proto] ?? 11010
|
||||
}
|
||||
|
||||
if (props.protos[proto] === 0) {
|
||||
url.value = `${proto}://${host}`
|
||||
} else {
|
||||
url.value = `${proto}://${host}:${port}`
|
||||
}
|
||||
watch(internalValue, () => {
|
||||
syncUrlFromInternal(false)
|
||||
}, { deep: true })
|
||||
|
||||
const protoOptions = computed(() => Object.keys(props.protos))
|
||||
@@ -128,7 +173,8 @@ const onProtoChange = (newProto: string) => {
|
||||
<AutoComplete :model-value="internalValue.proto" :suggestions="filteredProtos" dropdown
|
||||
class="max-w-32 proto-autocomplete-in-group" @complete="searchProtos"
|
||||
@update:model-value="onProtoChange" />
|
||||
<InputText v-model="internalValue.host" :placeholder="placeholder || '0.0.0.0'" class="grow" />
|
||||
<InputText v-model="internalValue.host" :placeholder="placeholder || '0.0.0.0'" class="grow"
|
||||
@focus="onHostFocus" @blur="onHostBlur" />
|
||||
<template v-if="!isNoPortProto">
|
||||
<InputGroupAddon>
|
||||
<span style="font-weight: bold">:</span>
|
||||
@@ -156,7 +202,8 @@ const onProtoChange = (newProto: string) => {
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label>{{ t('web.common.address') || 'Address' }}</label>
|
||||
<InputText v-model="internalValue.host" :placeholder="placeholder || '0.0.0.0'" class="w-full" />
|
||||
<InputText v-model="internalValue.host" :placeholder="placeholder || '0.0.0.0'" class="w-full"
|
||||
@focus="onHostFocus" @blur="onHostBlur" />
|
||||
</div>
|
||||
<div v-if="!isNoPortProto" class="flex flex-col gap-2">
|
||||
<label>{{ t('port') }}</label>
|
||||
@@ -164,7 +211,7 @@ const onProtoChange = (newProto: string) => {
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button :label="t('web.common.confirm') || 'Done'" icon="pi pi-check" @click="editing = false"
|
||||
<Button :label="t('web.common.confirm') || 'Done'" icon="pi pi-check" @click="onDialogConfirm"
|
||||
autofocus />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
@@ -10,7 +10,7 @@ initial_nodes_help: |
|
||||
• 留空 = 节点独立启动,等别人来连,或你后续手动连。
|
||||
• 无论直接还是间接连通(通过其他节点搭桥),都能组网互通。
|
||||
初始节点可以用自己的,也可以用别人分享的。
|
||||
initial_node_placeholder: 例如:tcp://node.example.com:11010
|
||||
initial_node_placeholder: 例如:node.example.com
|
||||
virtual_ipv4: 虚拟IPv4地址
|
||||
virtual_ipv4_dhcp: DHCP
|
||||
network_name: 网络名称
|
||||
|
||||
@@ -10,7 +10,7 @@ initial_nodes_help: |
|
||||
• Leaving it empty = the node starts alone until others connect to it, or you connect it later yourself.
|
||||
• Direct or indirect connectivity, including through relay nodes, can form one network.
|
||||
Initial nodes can be your own nodes or ones shared by others.
|
||||
initial_node_placeholder: "Example: tcp://node.example.com:11010"
|
||||
initial_node_placeholder: "Example: node.example.com"
|
||||
virtual_ipv4: Virtual IPv4
|
||||
virtual_ipv4_dhcp: DHCP
|
||||
network_name: Network Name
|
||||
|
||||
Reference in New Issue
Block a user