mirror of
https://github.com/EasyTier/EasyTier.git
synced 2024-11-16 03:32:43 +08:00
gui use frontend-lib, fix memory leak (#467)
Some checks failed
EasyTier Core / pre_job (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
Some checks failed
EasyTier Core / pre_job (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
This commit is contained in:
parent
88e6de9d7e
commit
4fc3ff8ce8
765
Cargo.lock
generated
765
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +0,0 @@
|
||||||
shamefully-hoist=true
|
|
||||||
strict-peer-dependencies=false
|
|
|
@ -13,34 +13,32 @@
|
||||||
"lint:fix": "eslint . --ignore-pattern src-tauri --fix"
|
"lint:fix": "eslint . --ignore-pattern src-tauri --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@primevue/themes": "^4.1.0",
|
"@primevue/themes": "^4.2.1",
|
||||||
"@tauri-apps/plugin-autostart": "2.0.0-rc.1",
|
"@tauri-apps/plugin-autostart": "2.0.0",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "2.0.0-rc.1",
|
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
|
||||||
"@tauri-apps/plugin-os": "2.0.0-rc.1",
|
"@tauri-apps/plugin-os": "2.0.0",
|
||||||
"@tauri-apps/plugin-process": "2.0.0-rc.1",
|
"@tauri-apps/plugin-process": "2.0.0",
|
||||||
"@tauri-apps/plugin-shell": "2.0.0-rc.1",
|
"@tauri-apps/plugin-shell": "2.0.1",
|
||||||
"@vueuse/core": "^11.1.0",
|
"@vueuse/core": "^11.2.0",
|
||||||
"aura": "link:@primevue\\themes\\aura",
|
"aura": "link:@primevue\\themes\\aura",
|
||||||
|
"easytier-frontend-lib": "workspace:*",
|
||||||
"ip-num": "1.5.1",
|
"ip-num": "1.5.1",
|
||||||
"pinia": "^2.2.4",
|
"pinia": "^2.2.4",
|
||||||
"primeflex": "^3.3.1",
|
"primevue": "^4.2.1",
|
||||||
"primeicons": "^7.0.0",
|
"tauri-plugin-vpnservice-api": "workspace:*",
|
||||||
"primevue": "^4.1.0",
|
"vue": "^3.5.12",
|
||||||
"tauri-plugin-vpnservice-api": "link:..\\tauri-plugin-vpnservice",
|
|
||||||
"vue": "=3.4.38",
|
|
||||||
"vue-i18n": "^10.0.4",
|
|
||||||
"vue-router": "^4.4.5"
|
"vue-router": "^4.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^3.7.3",
|
"@antfu/eslint-config": "^3.7.3",
|
||||||
"@intlify/unplugin-vue-i18n": "^5.2.0",
|
"@intlify/unplugin-vue-i18n": "^5.2.0",
|
||||||
"@primevue/auto-import-resolver": "^4.1.0",
|
"@primevue/auto-import-resolver": "^4.1.0",
|
||||||
"@tauri-apps/api": "2.0.0-rc.0",
|
"@tauri-apps/api": "2.1.0",
|
||||||
"@tauri-apps/cli": "2.0.0-rc.3",
|
"@tauri-apps/cli": "2.1.0",
|
||||||
"@types/node": "^22.7.4",
|
"@types/node": "^22.7.4",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vue-macros/volar": "0.30.3",
|
"@vue-macros/volar": "0.30.5",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.12.0",
|
"eslint": "^9.12.0",
|
||||||
"eslint-plugin-format": "^0.1.2",
|
"eslint-plugin-format": "^0.1.2",
|
||||||
|
@ -50,7 +48,7 @@
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"unplugin-auto-import": "^0.18.3",
|
"unplugin-auto-import": "^0.18.3",
|
||||||
"unplugin-vue-components": "^0.27.4",
|
"unplugin-vue-components": "^0.27.4",
|
||||||
"unplugin-vue-macros": "^2.12.3",
|
"unplugin-vue-macros": "^2.13.3",
|
||||||
"unplugin-vue-markdown": "^0.26.2",
|
"unplugin-vue-markdown": "^0.26.2",
|
||||||
"unplugin-vue-router": "^0.10.8",
|
"unplugin-vue-router": "^0.10.8",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
|
@ -58,6 +56,6 @@
|
||||||
"vite-plugin-vue-devtools": "^7.4.6",
|
"vite-plugin-vue-devtools": "^7.4.6",
|
||||||
"vite-plugin-vue-layouts": "^0.11.0",
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
"vue-i18n": "^10.0.0",
|
"vue-i18n": "^10.0.0",
|
||||||
"vue-tsc": "^2.1.6"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,11 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
tauri-build = { version = "2.0.0-rc", features = [] }
|
tauri-build = { version = "2.0.0-rc", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.0.0-rc", features = [
|
tauri = { version = "2.1", features = [
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"image-png",
|
"image-png",
|
||||||
"image-ico",
|
"image-ico",
|
||||||
|
"devtools",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
@ -37,13 +38,13 @@ gethostname = "0.5"
|
||||||
|
|
||||||
dunce = "1.0.4"
|
dunce = "1.0.4"
|
||||||
|
|
||||||
tauri-plugin-shell = "2.0.0-rc"
|
tauri-plugin-shell = "2.0"
|
||||||
tauri-plugin-process = "2.0.0-rc"
|
tauri-plugin-process = "2.0"
|
||||||
tauri-plugin-clipboard-manager = "2.0.0-rc"
|
tauri-plugin-clipboard-manager = "2.0"
|
||||||
tauri-plugin-positioner = { version = "2.0.0-rc", features = ["tray-icon"] }
|
tauri-plugin-positioner = { version = "2.0", features = ["tray-icon"] }
|
||||||
tauri-plugin-vpnservice = { path = "../../tauri-plugin-vpnservice" }
|
tauri-plugin-vpnservice = { path = "../../tauri-plugin-vpnservice" }
|
||||||
tauri-plugin-os = "2.0.0-rc"
|
tauri-plugin-os = "2.0"
|
||||||
tauri-plugin-autostart = "2.0.0-rc"
|
tauri-plugin-autostart = "2.0"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -3,17 +3,12 @@
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use easytier::{
|
use easytier::{
|
||||||
common::config::{
|
common::config::{ConfigLoader, FileLoggerConfig, TomlConfigLoader},
|
||||||
ConfigLoader, FileLoggerConfig, Flags, NetworkIdentity, PeerConfig, TomlConfigLoader,
|
|
||||||
VpnPortalConfig,
|
|
||||||
},
|
|
||||||
launcher::{NetworkConfig, NetworkInstance, NetworkInstanceRunningInfo},
|
launcher::{NetworkConfig, NetworkInstance, NetworkInstanceRunningInfo},
|
||||||
utils::{self, NewFilterSender},
|
utils::{self, NewFilterSender},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use tauri::Manager as _;
|
use tauri::Manager as _;
|
||||||
|
|
||||||
|
|
2
easytier-gui/src/auto-imports.d.ts
vendored
2
easytier-gui/src/auto-imports.d.ts
vendored
|
@ -154,8 +154,6 @@ declare module 'vue' {
|
||||||
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
readonly num2ipv4: UnwrapRef<typeof import('./composables/utils')['num2ipv4']>
|
|
||||||
readonly num2ipv6: UnwrapRef<typeof import('./composables/utils')['num2ipv6']>
|
|
||||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
||||||
|
|
|
@ -1,296 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import InputGroup from 'primevue/inputgroup'
|
|
||||||
import InputGroupAddon from 'primevue/inputgroupaddon'
|
|
||||||
import { getOsHostname } from '~/composables/network'
|
|
||||||
|
|
||||||
import { NetworkingMethod } from '~/types/network'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
configInvalid?: boolean
|
|
||||||
instanceId?: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
defineEmits(['runNetwork'])
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const networking_methods = ref([
|
|
||||||
{ value: NetworkingMethod.PublicServer, label: () => t('public_server') },
|
|
||||||
{ value: NetworkingMethod.Manual, label: () => t('manual') },
|
|
||||||
{ value: NetworkingMethod.Standalone, label: () => t('standalone') },
|
|
||||||
])
|
|
||||||
|
|
||||||
const networkStore = useNetworkStore()
|
|
||||||
const curNetwork = computed(() => {
|
|
||||||
if (props.instanceId) {
|
|
||||||
// console.log('instanceId', props.instanceId)
|
|
||||||
const c = networkStore.networkList.find(n => n.instance_id === props.instanceId)
|
|
||||||
if (c !== undefined)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkStore.curNetwork
|
|
||||||
})
|
|
||||||
|
|
||||||
const protos: { [proto: string]: number } = { tcp: 11010, udp: 11010, wg: 11011, ws: 11011, wss: 11012 }
|
|
||||||
|
|
||||||
function searchUrlSuggestions(e: { query: string }): string[] {
|
|
||||||
const query = e.query
|
|
||||||
const ret = []
|
|
||||||
// if query match "^\w+:.*", then no proto prefix
|
|
||||||
if (query.match(/^\w+:.*/)) {
|
|
||||||
// if query is a valid url, then add to suggestions
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line no-new
|
|
||||||
new URL(query)
|
|
||||||
ret.push(query)
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (const proto in protos) {
|
|
||||||
let item = `${proto}://${query}`
|
|
||||||
// if query match ":\d+$", then no port suffix
|
|
||||||
if (!query.match(/:\d+$/)) {
|
|
||||||
item += `:${protos[proto]}`
|
|
||||||
}
|
|
||||||
ret.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
const publicServerSuggestions = ref([''])
|
|
||||||
|
|
||||||
function searchPresetPublicServers(e: { query: string }) {
|
|
||||||
const presetPublicServers = [
|
|
||||||
'tcp://public.easytier.top:11010',
|
|
||||||
]
|
|
||||||
|
|
||||||
const query = e.query
|
|
||||||
// if query is sub string of presetPublicServers, add to suggestions
|
|
||||||
let ret = presetPublicServers.filter(item => item.includes(query))
|
|
||||||
// add additional suggestions
|
|
||||||
if (query.length > 0) {
|
|
||||||
ret = ret.concat(searchUrlSuggestions(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
publicServerSuggestions.value = ret
|
|
||||||
}
|
|
||||||
|
|
||||||
const peerSuggestions = ref([''])
|
|
||||||
|
|
||||||
function searchPeerSuggestions(e: { query: string }) {
|
|
||||||
peerSuggestions.value = searchUrlSuggestions(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
const inetSuggestions = ref([''])
|
|
||||||
|
|
||||||
function searchInetSuggestions(e: { query: string }) {
|
|
||||||
if (e.query.search('/') >= 0) {
|
|
||||||
inetSuggestions.value = [e.query]
|
|
||||||
} else {
|
|
||||||
const ret = []
|
|
||||||
for (let i = 0; i < 32; i++) {
|
|
||||||
ret.push(`${e.query}/${i}`)
|
|
||||||
}
|
|
||||||
inetSuggestions.value = ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const listenerSuggestions = ref([''])
|
|
||||||
|
|
||||||
function searchListenerSuggestiong(e: { query: string }) {
|
|
||||||
const ret = []
|
|
||||||
|
|
||||||
for (const proto in protos) {
|
|
||||||
let item = `${proto}://0.0.0.0:`
|
|
||||||
// if query is a number, use it as port
|
|
||||||
if (e.query.match(/^\d+$/)) {
|
|
||||||
item += e.query
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
item += protos[proto]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.includes(e.query)) {
|
|
||||||
ret.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret.length === 0) {
|
|
||||||
ret.push(e.query)
|
|
||||||
}
|
|
||||||
|
|
||||||
listenerSuggestions.value = ret
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateHostname() {
|
|
||||||
if (curNetwork.value.hostname) {
|
|
||||||
// eslint no-useless-escape
|
|
||||||
let name = curNetwork.value.hostname!.replaceAll(/[^\u4E00-\u9FA5a-z0-9\-]*/gi, '')
|
|
||||||
if (name.length > 32)
|
|
||||||
name = name.substring(0, 32)
|
|
||||||
|
|
||||||
if (curNetwork.value.hostname !== name)
|
|
||||||
curNetwork.value.hostname = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const osHostname = ref<string>('')
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
osHostname.value = await getOsHostname()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex flex-column h-full">
|
|
||||||
<div class="flex flex-column">
|
|
||||||
<div class="w-10/12 self-center ">
|
|
||||||
<Panel :header="t('basic_settings')">
|
|
||||||
<div class="flex flex-column gap-y-2">
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<div class="flex align-items-center" for="virtual_ip">
|
|
||||||
<label class="mr-2"> {{ t('virtual_ipv4') }} </label>
|
|
||||||
<Checkbox v-model="curNetwork.dhcp" input-id="virtual_ip_auto" :binary="true" />
|
|
||||||
|
|
||||||
<label for="virtual_ip_auto" class="ml-2">
|
|
||||||
{{ t('virtual_ipv4_dhcp') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<InputGroup>
|
|
||||||
<InputText id="virtual_ip" v-model="curNetwork.virtual_ipv4" :disabled="curNetwork.dhcp"
|
|
||||||
aria-describedby="virtual_ipv4-help" />
|
|
||||||
<InputGroupAddon>
|
|
||||||
<span>/</span>
|
|
||||||
</InputGroupAddon>
|
|
||||||
<InputNumber v-model="curNetwork.network_length" :disabled="curNetwork.dhcp"
|
|
||||||
inputId="horizontal-buttons" showButtons :step="1" mode="decimal" :min="1" :max="32" fluid
|
|
||||||
class="max-w-20" />
|
|
||||||
</InputGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<label for="network_name">{{ t('network_name') }}</label>
|
|
||||||
<InputText id="network_name" v-model="curNetwork.network_name" aria-describedby="network_name-help" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<label for="network_secret">{{ t('network_secret') }}</label>
|
|
||||||
<InputText id="network_secret" v-model="curNetwork.network_secret"
|
|
||||||
aria-describedby="network_secret-help" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<label for="nm">{{ t('networking_method') }}</label>
|
|
||||||
<SelectButton v-model="curNetwork.networking_method" :options="networking_methods"
|
|
||||||
:option-label="(v: any) => v.label()" option-value="value" />
|
|
||||||
<div class="items-center flex flex-row p-fluid gap-x-1">
|
|
||||||
<AutoComplete v-if="curNetwork.networking_method === NetworkingMethod.Manual" id="chips"
|
|
||||||
v-model="curNetwork.peer_urls" :placeholder="t('chips_placeholder', ['tcp://8.8.8.8:11010'])"
|
|
||||||
class="grow" multiple fluid :suggestions="peerSuggestions" @complete="searchPeerSuggestions" />
|
|
||||||
|
|
||||||
<AutoComplete v-if="curNetwork.networking_method === NetworkingMethod.PublicServer"
|
|
||||||
v-model="curNetwork.public_server_url" :suggestions="publicServerSuggestions"
|
|
||||||
:virtual-scroller-options="{ itemSize: 38 }" class="grow" dropdown :complete-on-focus="true"
|
|
||||||
@complete="searchPresetPublicServers" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Panel>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<Panel :header="t('advanced_settings')" toggleable collapsed>
|
|
||||||
<div class="flex flex-column gap-y-2">
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<div class="flex align-items-center">
|
|
||||||
<Checkbox v-model="curNetwork.latency_first" input-id="use_latency_first" :binary="true" />
|
|
||||||
<label for="use_latency_first" class="ml-2"> {{ t('use_latency_first') }} </label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<label for="hostname">{{ t('hostname') }}</label>
|
|
||||||
<InputText id="hostname" v-model="curNetwork.hostname" aria-describedby="hostname-help" :format="true"
|
|
||||||
:placeholder="t('hostname_placeholder', [osHostname])" @blur="validateHostname" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
|
||||||
<div class="flex flex-column gap-2 grow p-fluid">
|
|
||||||
<label for="username">{{ t('proxy_cidrs') }}</label>
|
|
||||||
<AutoComplete id="subnet-proxy" v-model="curNetwork.proxy_cidrs"
|
|
||||||
:placeholder="t('chips_placeholder', ['10.0.0.0/24'])" class="w-full" multiple fluid
|
|
||||||
:suggestions="inetSuggestions" @complete="searchInetSuggestions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap ">
|
|
||||||
<div class="flex flex-column gap-2 grow">
|
|
||||||
<label for="username">VPN Portal</label>
|
|
||||||
<ToggleButton v-model="curNetwork.enable_vpn_portal" on-icon="pi pi-check" off-icon="pi pi-times"
|
|
||||||
:on-label="t('off_text')" :off-label="t('on_text')" class="w-48" />
|
|
||||||
<div v-if="curNetwork.enable_vpn_portal" class="items-center flex flex-row gap-x-4">
|
|
||||||
<div class="min-w-64">
|
|
||||||
<InputGroup>
|
|
||||||
<InputText v-model="curNetwork.vpn_portal_client_network_addr"
|
|
||||||
:placeholder="t('vpn_portal_client_network')" />
|
|
||||||
<InputGroupAddon>
|
|
||||||
<span>/{{ curNetwork.vpn_portal_client_network_len }}</span>
|
|
||||||
</InputGroupAddon>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
<InputNumber v-model="curNetwork.vpn_portal_listen_port" :allow-empty="false" :format="false"
|
|
||||||
:min="0" :max="65535" class="w-8" fluid />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 grow p-fluid">
|
|
||||||
<label for="listener_urls">{{ t('listener_urls') }}</label>
|
|
||||||
<AutoComplete id="listener_urls" v-model="curNetwork.listener_urls" :suggestions="listenerSuggestions"
|
|
||||||
class="w-full" dropdown :complete-on-focus="true"
|
|
||||||
:placeholder="t('chips_placeholder', ['tcp://1.1.1.1:11010'])" multiple
|
|
||||||
@complete="searchListenerSuggestiong" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<label for="rpc_port">{{ t('rpc_port') }}</label>
|
|
||||||
<InputNumber id="rpc_port" v-model="curNetwork.rpc_port" aria-describedby="rpc_port-help"
|
|
||||||
:format="false" :min="0" :max="65535" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
|
||||||
<label for="dev_name">{{ t('dev_name') }}</label>
|
|
||||||
<InputText id="dev_name" v-model="curNetwork.dev_name" aria-describedby="dev_name-help" :format="true"
|
|
||||||
:placeholder="t('dev_name_placeholder')" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Panel>
|
|
||||||
|
|
||||||
<div class="flex pt-4 justify-content-center">
|
|
||||||
<Button :label="t('run_network')" icon="pi pi-arrow-right" icon-pos="right" :disabled="configInvalid"
|
|
||||||
@click="$emit('runNetwork', curNetwork)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,32 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { EventType } from '~/types/network'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
event: {
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
}>()
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const eventKey = computed(() => {
|
|
||||||
const key = Object.keys(props.event)[0]
|
|
||||||
return Object.keys(EventType).includes(key) ? key : 'Unknown'
|
|
||||||
})
|
|
||||||
|
|
||||||
const eventValue = computed(() => {
|
|
||||||
const value = props.event[eventKey.value]
|
|
||||||
return typeof value === 'object' ? value : value
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Fieldset :legend="t(`event.${eventKey}`)">
|
|
||||||
<template v-if="eventKey !== 'Unknown'">
|
|
||||||
<div v-if="event.DhcpIpv4Changed">
|
|
||||||
{{ `${eventValue[0]} -> ${eventValue[1]}` }}
|
|
||||||
</div>
|
|
||||||
<pre v-else>{{ eventValue }}</pre>
|
|
||||||
</template>
|
|
||||||
<pre v-else>{{ eventValue }}</pre>
|
|
||||||
</Fieldset>
|
|
||||||
</template>
|
|
|
@ -1,459 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useTimeAgo } from '@vueuse/core'
|
|
||||||
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
|
||||||
import type { NodeInfo, PeerRoutePair } from '~/types/network'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
instanceId?: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const networkStore = useNetworkStore()
|
|
||||||
|
|
||||||
const curNetwork = computed(() => {
|
|
||||||
if (props.instanceId) {
|
|
||||||
// console.log('instanceId', props.instanceId)
|
|
||||||
const c = networkStore.networkList.find(n => n.instance_id === props.instanceId)
|
|
||||||
if (c !== undefined)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkStore.curNetwork
|
|
||||||
})
|
|
||||||
|
|
||||||
const curNetworkInst = computed(() => {
|
|
||||||
return networkStore.networkInstances.find(n => n.instance_id === curNetwork.value.instance_id)
|
|
||||||
})
|
|
||||||
|
|
||||||
const peerRouteInfos = computed(() => {
|
|
||||||
if (curNetworkInst.value) {
|
|
||||||
const my_node_info = curNetworkInst.value.detail?.my_node_info
|
|
||||||
return [{
|
|
||||||
route: {
|
|
||||||
ipv4_addr: my_node_info?.virtual_ipv4,
|
|
||||||
hostname: my_node_info?.hostname,
|
|
||||||
version: my_node_info?.version,
|
|
||||||
},
|
|
||||||
}, ...(curNetworkInst.value.detail?.peer_route_pairs || [])]
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
|
|
||||||
function routeCost(info: any) {
|
|
||||||
if (info.route) {
|
|
||||||
const cost = info.route.cost
|
|
||||||
return cost ? cost === 1 ? 'p2p' : `relay(${cost})` : t('status.local')
|
|
||||||
}
|
|
||||||
|
|
||||||
return '?'
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveObjPath(path: string, obj = globalThis, separator = '.') {
|
|
||||||
const properties = Array.isArray(path) ? path : path.split(separator)
|
|
||||||
return properties.reduce((prev, curr) => prev?.[curr], obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
function statsCommon(info: any, field: string): number | undefined {
|
|
||||||
if (!info.peer)
|
|
||||||
return undefined
|
|
||||||
|
|
||||||
const conns = info.peer.conns
|
|
||||||
return conns.reduce((acc: number, conn: any) => {
|
|
||||||
return acc + resolveObjPath(field, conn)
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function humanFileSize(bytes: number, si = false, dp = 1) {
|
|
||||||
const thresh = si ? 1000 : 1024
|
|
||||||
|
|
||||||
if (Math.abs(bytes) < thresh)
|
|
||||||
return `${bytes} B`
|
|
||||||
|
|
||||||
const units = si
|
|
||||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
||||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
|
||||||
let u = -1
|
|
||||||
const r = 10 ** dp
|
|
||||||
|
|
||||||
do {
|
|
||||||
bytes /= thresh
|
|
||||||
++u
|
|
||||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)
|
|
||||||
|
|
||||||
return `${bytes.toFixed(dp)} ${units[u]}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function latencyMs(info: PeerRoutePair) {
|
|
||||||
let lat_us_sum = statsCommon(info, 'stats.latency_us')
|
|
||||||
if (lat_us_sum === undefined)
|
|
||||||
return ''
|
|
||||||
lat_us_sum = lat_us_sum / 1000 / info.peer!.conns.length
|
|
||||||
return `${lat_us_sum % 1 > 0 ? Math.round(lat_us_sum) + 1 : Math.round(lat_us_sum)}ms`
|
|
||||||
}
|
|
||||||
|
|
||||||
function txBytes(info: PeerRoutePair) {
|
|
||||||
const tx = statsCommon(info, 'stats.tx_bytes')
|
|
||||||
return tx ? humanFileSize(tx) : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function rxBytes(info: PeerRoutePair) {
|
|
||||||
const rx = statsCommon(info, 'stats.rx_bytes')
|
|
||||||
return rx ? humanFileSize(rx) : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function lossRate(info: PeerRoutePair) {
|
|
||||||
const lossRate = statsCommon(info, 'loss_rate')
|
|
||||||
return lossRate !== undefined ? `${Math.round(lossRate * 100)}%` : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function version(info: PeerRoutePair) {
|
|
||||||
return info.route.version === '' ? 'unknown' : info.route.version
|
|
||||||
}
|
|
||||||
|
|
||||||
function ipFormat(info: PeerRoutePair) {
|
|
||||||
const ip = info.route.ipv4_addr
|
|
||||||
if (typeof ip === 'string')
|
|
||||||
return ip
|
|
||||||
return ip ? `${num2ipv4(ip.address)}/${ip.network_length}` : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const myNodeInfo = computed(() => {
|
|
||||||
if (!curNetworkInst.value)
|
|
||||||
return {} as NodeInfo
|
|
||||||
|
|
||||||
return curNetworkInst.value.detail?.my_node_info
|
|
||||||
})
|
|
||||||
|
|
||||||
interface Chip {
|
|
||||||
label: string
|
|
||||||
icon: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const myNodeInfoChips = computed(() => {
|
|
||||||
if (!curNetworkInst.value)
|
|
||||||
return []
|
|
||||||
|
|
||||||
const chips: Array<Chip> = []
|
|
||||||
const my_node_info = curNetworkInst.value.detail?.my_node_info
|
|
||||||
if (!my_node_info)
|
|
||||||
return chips
|
|
||||||
|
|
||||||
// TUN Device Name
|
|
||||||
const dev_name = curNetworkInst.value.detail?.dev_name
|
|
||||||
if (dev_name) {
|
|
||||||
chips.push({
|
|
||||||
label: `TUN Device Name: ${dev_name}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// virtual ipv4
|
|
||||||
chips.push({
|
|
||||||
label: `Virtual IPv4: ${my_node_info.virtual_ipv4}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
|
|
||||||
// local ipv4s
|
|
||||||
const local_ipv4s = my_node_info.ips?.interface_ipv4s
|
|
||||||
for (const [idx, ip] of local_ipv4s?.entries()) {
|
|
||||||
chips.push({
|
|
||||||
label: `Local IPv4 ${idx}: ${num2ipv4(ip)}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// local ipv6s
|
|
||||||
const local_ipv6s = my_node_info.ips?.interface_ipv6s
|
|
||||||
for (const [idx, ip] of local_ipv6s?.entries()) {
|
|
||||||
chips.push({
|
|
||||||
label: `Local IPv6 ${idx}: ${num2ipv6(ip)}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// public ip
|
|
||||||
const public_ip = my_node_info.ips?.public_ipv4
|
|
||||||
if (public_ip) {
|
|
||||||
chips.push({
|
|
||||||
label: `Public IP: ${IPv4.fromNumber(public_ip.addr)}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
}
|
|
||||||
|
|
||||||
const public_ipv6 = my_node_info.ips?.public_ipv6
|
|
||||||
if (public_ipv6) {
|
|
||||||
chips.push({
|
|
||||||
label: `Public IPv6: ${IPv6.fromBigInt((BigInt(public_ipv6.part1) << BigInt(96))
|
|
||||||
+ (BigInt(public_ipv6.part2) << BigInt(64))
|
|
||||||
+ (BigInt(public_ipv6.part3) << BigInt(32))
|
|
||||||
+ BigInt(public_ipv6.part4),
|
|
||||||
)}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// listeners:
|
|
||||||
const listeners = my_node_info.listeners
|
|
||||||
for (const [idx, listener] of listeners?.entries()) {
|
|
||||||
chips.push({
|
|
||||||
label: `Listener ${idx}: ${listener}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp nat type
|
|
||||||
enum NatType {
|
|
||||||
// has NAT; but own a single public IP, port is not changed
|
|
||||||
Unknown = 0,
|
|
||||||
OpenInternet = 1,
|
|
||||||
NoPAT = 2,
|
|
||||||
FullCone = 3,
|
|
||||||
Restricted = 4,
|
|
||||||
PortRestricted = 5,
|
|
||||||
Symmetric = 6,
|
|
||||||
SymUdpFirewall = 7,
|
|
||||||
SymmetricEasyInc = 8,
|
|
||||||
SymmetricEasyDec = 9,
|
|
||||||
};
|
|
||||||
const udpNatType: NatType = my_node_info.stun_info?.udp_nat_type
|
|
||||||
if (udpNatType !== undefined) {
|
|
||||||
const udpNatTypeStrMap = {
|
|
||||||
[NatType.Unknown]: 'Unknown',
|
|
||||||
[NatType.OpenInternet]: 'Open Internet',
|
|
||||||
[NatType.NoPAT]: 'No PAT',
|
|
||||||
[NatType.FullCone]: 'Full Cone',
|
|
||||||
[NatType.Restricted]: 'Restricted',
|
|
||||||
[NatType.PortRestricted]: 'Port Restricted',
|
|
||||||
[NatType.Symmetric]: 'Symmetric',
|
|
||||||
[NatType.SymUdpFirewall]: 'Symmetric UDP Firewall',
|
|
||||||
[NatType.SymmetricEasyInc]: 'Symmetric Easy Inc',
|
|
||||||
[NatType.SymmetricEasyDec]: 'Symmetric Easy Dec',
|
|
||||||
}
|
|
||||||
|
|
||||||
chips.push({
|
|
||||||
label: `UDP NAT Type: ${udpNatTypeStrMap[udpNatType]}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip)
|
|
||||||
}
|
|
||||||
|
|
||||||
return chips
|
|
||||||
})
|
|
||||||
|
|
||||||
function globalSumCommon(field: string) {
|
|
||||||
let sum = 0
|
|
||||||
if (!peerRouteInfos.value)
|
|
||||||
return sum
|
|
||||||
|
|
||||||
for (const info of peerRouteInfos.value) {
|
|
||||||
const tx = statsCommon(info, field)
|
|
||||||
if (tx)
|
|
||||||
sum += tx
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
function txGlobalSum() {
|
|
||||||
return globalSumCommon('stats.tx_bytes')
|
|
||||||
}
|
|
||||||
|
|
||||||
function rxGlobalSum() {
|
|
||||||
return globalSumCommon('stats.rx_bytes')
|
|
||||||
}
|
|
||||||
|
|
||||||
const peerCount = computed(() => {
|
|
||||||
if (!peerRouteInfos.value)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return peerRouteInfos.value.length
|
|
||||||
})
|
|
||||||
|
|
||||||
// calculate tx/rx rate every 2 seconds
|
|
||||||
let rateIntervalId = 0
|
|
||||||
const rateInterval = 2000
|
|
||||||
let prevTxSum = 0
|
|
||||||
let prevRxSum = 0
|
|
||||||
const txRate = ref('0')
|
|
||||||
const rxRate = ref('0')
|
|
||||||
onMounted(() => {
|
|
||||||
rateIntervalId = window.setInterval(() => {
|
|
||||||
const curTxSum = txGlobalSum()
|
|
||||||
txRate.value = humanFileSize((curTxSum - prevTxSum) / (rateInterval / 1000))
|
|
||||||
prevTxSum = curTxSum
|
|
||||||
|
|
||||||
const curRxSum = rxGlobalSum()
|
|
||||||
rxRate.value = humanFileSize((curRxSum - prevRxSum) / (rateInterval / 1000))
|
|
||||||
prevRxSum = curRxSum
|
|
||||||
}, rateInterval)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
clearInterval(rateIntervalId)
|
|
||||||
})
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const dialogContent = ref<any>('')
|
|
||||||
const dialogHeader = ref('event_log')
|
|
||||||
|
|
||||||
function showVpnPortalConfig() {
|
|
||||||
const my_node_info = myNodeInfo.value
|
|
||||||
if (!my_node_info)
|
|
||||||
return
|
|
||||||
|
|
||||||
const url = 'https://www.wireguardconfig.com/qrcode'
|
|
||||||
dialogContent.value = `${my_node_info.vpn_portal_cfg}\n\n # can generate QR code: ${url}`
|
|
||||||
dialogHeader.value = 'vpn_portal_config'
|
|
||||||
dialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function showEventLogs() {
|
|
||||||
const detail = curNetworkInst.value?.detail
|
|
||||||
if (!detail)
|
|
||||||
return
|
|
||||||
|
|
||||||
dialogContent.value = detail.events
|
|
||||||
dialogHeader.value = 'event_log'
|
|
||||||
dialogVisible.value = true
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-2/3 h-auto">
|
|
||||||
<ScrollPanel v-if="dialogHeader === 'vpn_portal_config'">
|
|
||||||
<pre>{{ dialogContent }}</pre>
|
|
||||||
</ScrollPanel>
|
|
||||||
<Timeline v-else :value="dialogContent">
|
|
||||||
<template #opposite="slotProps">
|
|
||||||
<small class="text-surface-500 dark:text-surface-400">{{ useTimeAgo(Date.parse(slotProps.item[0])) }}</small>
|
|
||||||
</template>
|
|
||||||
<template #content="slotProps">
|
|
||||||
<HumanEvent :event="slotProps.item[1]" />
|
|
||||||
</template>
|
|
||||||
</Timeline>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<Card v-if="curNetworkInst?.error_msg">
|
|
||||||
<template #title>
|
|
||||||
Run Network Error
|
|
||||||
</template>
|
|
||||||
<template #content>
|
|
||||||
<div class="flex flex-column gap-y-5">
|
|
||||||
<div class="text-red-500">
|
|
||||||
{{ curNetworkInst.error_msg }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<Card>
|
|
||||||
<template #title>
|
|
||||||
{{ t('my_node_info') }}
|
|
||||||
</template>
|
|
||||||
<template #content>
|
|
||||||
<div class="flex w-full flex-column gap-y-5">
|
|
||||||
<div class="m-0 flex flex-row justify-center gap-x-5">
|
|
||||||
<div
|
|
||||||
class="rounded-full w-32 h-32 flex flex-column align-items-center pt-4"
|
|
||||||
style="border: 1px solid green"
|
|
||||||
>
|
|
||||||
<div class="font-bold">
|
|
||||||
{{ t('peer_count') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-5xl mt-1">
|
|
||||||
{{ peerCount }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="rounded-full w-32 h-32 flex flex-column align-items-center pt-4"
|
|
||||||
style="border: 1px solid purple"
|
|
||||||
>
|
|
||||||
<div class="font-bold">
|
|
||||||
{{ t('upload') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-xl mt-2">
|
|
||||||
{{ txRate }}/s
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="rounded-full w-32 h-32 flex flex-column align-items-center pt-4"
|
|
||||||
style="border: 1px solid fuchsia"
|
|
||||||
>
|
|
||||||
<div class="font-bold">
|
|
||||||
{{ t('download') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-xl mt-2">
|
|
||||||
{{ rxRate }}/s
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row align-items-center flex-wrap w-full max-h-40 overflow-scroll">
|
|
||||||
<Chip
|
|
||||||
v-for="(chip, i) in myNodeInfoChips" :key="i" :label="chip.label" :icon="chip.icon"
|
|
||||||
class="mr-2 mt-2 text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="myNodeInfo" class="m-0 flex flex-row justify-center gap-x-5 text-sm">
|
|
||||||
<Button severity="info" :label="t('show_vpn_portal_config')" @click="showVpnPortalConfig" />
|
|
||||||
<Button severity="info" :label="t('show_event_log')" @click="showEventLogs" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<template #title>
|
|
||||||
{{ t('peer_info') }}
|
|
||||||
</template>
|
|
||||||
<template #content>
|
|
||||||
<DataTable :value="peerRouteInfos" column-resize-mode="fit" table-class="w-full">
|
|
||||||
<Column :field="ipFormat" :header="t('virtual_ipv4')" />
|
|
||||||
<Column :header="t('hostname')">
|
|
||||||
<template #body="slotProps">
|
|
||||||
<div
|
|
||||||
v-if="!slotProps.data.route.cost || !slotProps.data.route.feature_flag.is_public_server"
|
|
||||||
v-tooltip="slotProps.data.route.hostname"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
slotProps.data.route.hostname }}
|
|
||||||
</div>
|
|
||||||
<div v-else v-tooltip="slotProps.data.route.hostname" class="space-x-1">
|
|
||||||
<Tag v-if="slotProps.data.route.feature_flag.is_public_server" severity="info" value="Info">
|
|
||||||
{{ t('status.server') }}
|
|
||||||
</Tag>
|
|
||||||
<Tag v-if="slotProps.data.route.no_relay_data" severity="warn" value="Warn">
|
|
||||||
{{ t('status.relay') }}
|
|
||||||
</Tag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
<Column :field="routeCost" :header="t('route_cost')" />
|
|
||||||
<Column :field="latencyMs" :header="t('latency')" />
|
|
||||||
<Column :field="txBytes" :header="t('upload_bytes')" />
|
|
||||||
<Column :field="rxBytes" :header="t('download_bytes')" />
|
|
||||||
<Column :field="lossRate" :header="t('loss_rate')" />
|
|
||||||
<Column :header="t('status.version')">
|
|
||||||
<template #body="slotProps">
|
|
||||||
<span>{{ version(slotProps.data) }}</span>
|
|
||||||
</template>
|
|
||||||
</Column>
|
|
||||||
</DataTable>
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
|
||||||
.p-timeline :deep(.p-timeline-event-opposite) {
|
|
||||||
@apply flex-none;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { addPluginListener } from '@tauri-apps/api/core'
|
import { addPluginListener } from '@tauri-apps/api/core'
|
||||||
import { prepare_vpn, start_vpn, stop_vpn } from 'tauri-plugin-vpnservice-api'
|
import { prepare_vpn, start_vpn, stop_vpn } from 'tauri-plugin-vpnservice-api'
|
||||||
import type { Route } from '~/types/network'
|
import { NetworkTypes, Utils } from 'easytier-frontend-lib'
|
||||||
|
|
||||||
|
type Route = NetworkTypes.Route
|
||||||
|
|
||||||
const networkStore = useNetworkStore()
|
const networkStore = useNetworkStore()
|
||||||
|
|
||||||
|
@ -122,12 +124,17 @@ async function onNetworkInstanceChange() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const virtual_ip = curNetworkInfo?.node_info?.virtual_ipv4
|
const virtual_ip = Utils.ipv4ToString(curNetworkInfo?.my_node_info?.virtual_ipv4.address)
|
||||||
if (!virtual_ip || !virtual_ip.length) {
|
if (!virtual_ip || !virtual_ip.length) {
|
||||||
await doStopVpn()
|
await doStopVpn()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let network_length = curNetworkInfo?.my_node_info?.virtual_ipv4.network_length
|
||||||
|
if (!network_length) {
|
||||||
|
network_length = 24
|
||||||
|
}
|
||||||
|
|
||||||
const routes = getRoutesForVpn(curNetworkInfo?.routes)
|
const routes = getRoutesForVpn(curNetworkInfo?.routes)
|
||||||
|
|
||||||
const ipChanged = virtual_ip !== curVpnStatus.ipv4Addr
|
const ipChanged = virtual_ip !== curVpnStatus.ipv4Addr
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import { NetworkTypes } from 'easytier-frontend-lib'
|
||||||
|
|
||||||
import type { NetworkConfig, NetworkInstanceRunningInfo } from '~/types/network'
|
type NetworkConfig = NetworkTypes.NetworkConfig
|
||||||
|
type NetworkInstanceRunningInfo = NetworkTypes.NetworkInstanceRunningInfo
|
||||||
|
|
||||||
export async function parseNetworkConfig(cfg: NetworkConfig) {
|
export async function parseNetworkConfig(cfg: NetworkConfig) {
|
||||||
return invoke<string>('parse_network_config', { cfg })
|
return invoke<string>('parse_network_config', { cfg })
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
|
||||||
import type { Ipv4Addr, Ipv6Addr } from '~/types/network'
|
|
||||||
|
|
||||||
export function num2ipv4(ip: Ipv4Addr) {
|
|
||||||
return IPv4.fromNumber(ip.addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function num2ipv6(ip: Ipv6Addr) {
|
|
||||||
return IPv6.fromBigInt(
|
|
||||||
(BigInt(ip.part1) << BigInt(96))
|
|
||||||
+ (BigInt(ip.part2) << BigInt(64))
|
|
||||||
+ (BigInt(ip.part3) << BigInt(32))
|
|
||||||
+ BigInt(ip.part4),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -5,12 +5,11 @@ import ToastService from 'primevue/toastservice'
|
||||||
import { createRouter, createWebHistory } from 'vue-router/auto'
|
import { createRouter, createWebHistory } from 'vue-router/auto'
|
||||||
import { routes } from 'vue-router/auto-routes'
|
import { routes } from 'vue-router/auto-routes'
|
||||||
import App from '~/App.vue'
|
import App from '~/App.vue'
|
||||||
import { i18n, loadLanguageAsync } from '~/modules/i18n'
|
import EasyTierFrontendLib, { I18nUtils } from 'easytier-frontend-lib'
|
||||||
|
|
||||||
import { getAutoLaunchStatusAsync, loadAutoLaunchStatusAsync } from './modules/auto_launch'
|
import { getAutoLaunchStatusAsync, loadAutoLaunchStatusAsync } from './modules/auto_launch'
|
||||||
import '~/styles.css'
|
import '~/styles.css'
|
||||||
import 'primeicons/primeicons.css'
|
import 'easytier-frontend-lib/style.css'
|
||||||
import 'primeflex/primeflex.css'
|
|
||||||
|
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
|
@ -29,7 +28,7 @@ if (import.meta.env.PROD) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await loadLanguageAsync(localStorage.getItem('lang') || 'en')
|
await I18nUtils.loadLanguageAsync(localStorage.getItem('lang') || 'en')
|
||||||
await loadAutoLaunchStatusAsync(getAutoLaunchStatusAsync())
|
await loadAutoLaunchStatusAsync(getAutoLaunchStatusAsync())
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
@ -41,14 +40,18 @@ async function main() {
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(i18n, { useScope: 'global' })
|
app.use(EasyTierFrontendLib)
|
||||||
|
// app.use(i18n, { useScope: 'global' })
|
||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
preset: Aura,
|
preset: Aura,
|
||||||
options: {
|
options: {
|
||||||
prefix: 'p',
|
prefix: 'p',
|
||||||
darkModeSelector: 'system',
|
darkModeSelector: 'system',
|
||||||
cssLayer: false,
|
cssLayer: {
|
||||||
|
name: 'primevue',
|
||||||
|
order: 'tailwind-base, primevue, tailwind-utilities'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { createI18n } from 'vue-i18n'
|
|
||||||
import type { Locale } from 'vue-i18n'
|
|
||||||
|
|
||||||
// Import i18n resources
|
|
||||||
// https://vitejs.dev/guide/features.html#glob-import
|
|
||||||
export const i18n = createI18n({
|
|
||||||
legacy: false,
|
|
||||||
locale: '',
|
|
||||||
fallbackLocale: '',
|
|
||||||
messages: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
const localesMap = Object.fromEntries(
|
|
||||||
Object.entries(import.meta.glob('../../locales/*.yml'))
|
|
||||||
.map(([path, loadLocale]) => [path.match(/([\w-]*)\.yml$/)?.[1], loadLocale]),
|
|
||||||
) as Record<Locale, () => Promise<{ default: Record<string, string> }>>
|
|
||||||
|
|
||||||
export const availableLocales = Object.keys(localesMap)
|
|
||||||
|
|
||||||
const loadedLanguages: string[] = []
|
|
||||||
|
|
||||||
function setI18nLanguage(lang: Locale) {
|
|
||||||
i18n.global.locale.value = lang as any
|
|
||||||
localStorage.setItem('lang', lang)
|
|
||||||
return lang
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadLanguageAsync(lang: string): Promise<Locale> {
|
|
||||||
// If the same language
|
|
||||||
if (i18n.global.locale.value === lang)
|
|
||||||
return setI18nLanguage(lang)
|
|
||||||
|
|
||||||
// If the language was already loaded
|
|
||||||
if (loadedLanguages.includes(lang))
|
|
||||||
return setI18nLanguage(lang)
|
|
||||||
|
|
||||||
// If the language hasn't been loaded yet
|
|
||||||
let messages
|
|
||||||
|
|
||||||
try {
|
|
||||||
messages = await localesMap[lang]()
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
messages = await localesMap.en()
|
|
||||||
}
|
|
||||||
|
|
||||||
i18n.global.setLocaleMessage(lang, messages.default)
|
|
||||||
loadedLanguages.push(lang)
|
|
||||||
return setI18nLanguage(lang)
|
|
||||||
}
|
|
|
@ -8,14 +8,11 @@ import { exit } from '@tauri-apps/plugin-process'
|
||||||
import { open } from '@tauri-apps/plugin-shell'
|
import { open } from '@tauri-apps/plugin-shell'
|
||||||
import TieredMenu from 'primevue/tieredmenu'
|
import TieredMenu from 'primevue/tieredmenu'
|
||||||
import { useToast } from 'primevue/usetoast'
|
import { useToast } from 'primevue/usetoast'
|
||||||
import Config from '~/components/Config.vue'
|
import { NetworkTypes, Config, Status, Utils, I18nUtils } from 'easytier-frontend-lib'
|
||||||
|
|
||||||
import Status from '~/components/Status.vue'
|
|
||||||
import { isAutostart, setLoggingLevel } from '~/composables/network'
|
import { isAutostart, setLoggingLevel } from '~/composables/network'
|
||||||
import { useTray } from '~/composables/tray'
|
import { useTray } from '~/composables/tray'
|
||||||
import { getAutoLaunchStatusAsync as getAutoLaunchStatus, loadAutoLaunchStatusAsync } from '~/modules/auto_launch'
|
import { getAutoLaunchStatusAsync as getAutoLaunchStatus, loadAutoLaunchStatusAsync } from '~/modules/auto_launch'
|
||||||
import { loadLanguageAsync } from '~/modules/i18n'
|
|
||||||
import { type NetworkConfig, NetworkingMethod } from '~/types/network'
|
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
@ -65,6 +62,27 @@ const toast = useToast()
|
||||||
|
|
||||||
const networkStore = useNetworkStore()
|
const networkStore = useNetworkStore()
|
||||||
|
|
||||||
|
const curNetworkConfig = computed(() => {
|
||||||
|
if (networkStore.curNetworkId) {
|
||||||
|
// console.log('instanceId', props.instanceId)
|
||||||
|
const c = networkStore.networkList.find(n => n.instance_id === networkStore.curNetworkId)
|
||||||
|
if (c !== undefined)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkStore.curNetwork
|
||||||
|
})
|
||||||
|
|
||||||
|
const curNetworkInst = computed<NetworkTypes.NetworkInstance | null>(() => {
|
||||||
|
let ret = networkStore.networkInstances.find(n => n.instance_id === curNetworkConfig.value.instance_id)
|
||||||
|
console.log('curNetworkInst', ret)
|
||||||
|
if (ret === undefined) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function addNewNetwork() {
|
function addNewNetwork() {
|
||||||
networkStore.addNewNetwork()
|
networkStore.addNewNetwork()
|
||||||
networkStore.curNetwork = networkStore.lastNetwork
|
networkStore.curNetwork = networkStore.lastNetwork
|
||||||
|
@ -82,7 +100,7 @@ networkStore.$subscribe(async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function runNetworkCb(cfg: NetworkConfig, cb: () => void) {
|
async function runNetworkCb(cfg: NetworkTypes.NetworkConfig, cb: () => void) {
|
||||||
if (type() === 'android') {
|
if (type() === 'android') {
|
||||||
await prepareVpnService()
|
await prepareVpnService()
|
||||||
networkStore.clearNetworkInstances()
|
networkStore.clearNetworkInstances()
|
||||||
|
@ -106,7 +124,7 @@ async function runNetworkCb(cfg: NetworkConfig, cb: () => void) {
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopNetworkCb(cfg: NetworkConfig, cb: () => void) {
|
async function stopNetworkCb(cfg: NetworkTypes.NetworkConfig, cb: () => void) {
|
||||||
// console.log('stopNetworkCb', cfg, cb)
|
// console.log('stopNetworkCb', cfg, cb)
|
||||||
cb()
|
cb()
|
||||||
networkStore.removeNetworkInstance(cfg.instance_id)
|
networkStore.removeNetworkInstance(cfg.instance_id)
|
||||||
|
@ -145,7 +163,7 @@ const setting_menu_items = ref([
|
||||||
label: () => t('exchange_language'),
|
label: () => t('exchange_language'),
|
||||||
icon: 'pi pi-language',
|
icon: 'pi pi-language',
|
||||||
command: async () => {
|
command: async () => {
|
||||||
await loadLanguageAsync((locale.value === 'en' ? 'cn' : 'en'))
|
await I18nUtils.loadLanguageAsync((locale.value === 'en' ? 'cn' : 'en'))
|
||||||
await setTrayMenu([
|
await setTrayMenu([
|
||||||
await MenuItemExit(t('tray.exit')),
|
await MenuItemExit(t('tray.exit')),
|
||||||
await MenuItemShow(t('tray.show')),
|
await MenuItemShow(t('tray.show')),
|
||||||
|
@ -221,7 +239,7 @@ onBeforeMount(async () => {
|
||||||
getCurrentWindow().hide()
|
getCurrentWindow().hide()
|
||||||
const autoStartIds = networkStore.autoStartInstIds
|
const autoStartIds = networkStore.autoStartInstIds
|
||||||
for (const id of autoStartIds) {
|
for (const id of autoStartIds) {
|
||||||
const cfg = networkStore.networkList.find(item => item.instance_id === id)
|
const cfg = networkStore.networkList.find((item: NetworkTypes.NetworkConfig) => item.instance_id === id)
|
||||||
if (cfg) {
|
if (cfg) {
|
||||||
networkStore.addNetworkInstance(cfg.instance_id)
|
networkStore.addNetworkInstance(cfg.instance_id)
|
||||||
await runNetworkInstance(cfg)
|
await runNetworkInstance(cfg)
|
||||||
|
@ -245,7 +263,7 @@ function isRunning(id: string) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="root" class="flex flex-column">
|
<div id="root" class="flex flex-col">
|
||||||
<Dialog v-model:visible="visible" modal header="Config File" :style="{ width: '70%' }">
|
<Dialog v-model:visible="visible" modal header="Config File" :style="{ width: '70%' }">
|
||||||
<Panel>
|
<Panel>
|
||||||
<ScrollPanel style="width: 100%; height: 300px">
|
<ScrollPanel style="width: 100%; height: 300px">
|
||||||
|
@ -253,7 +271,7 @@ function isRunning(id: string) {
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
</Panel>
|
</Panel>
|
||||||
<Divider />
|
<Divider />
|
||||||
<div class="flex gap-2 justify-content-end">
|
<div class="flex gap-2 justify-end">
|
||||||
<Button type="button" :label="t('close')" @click="visible = false" />
|
<Button type="button" :label="t('close')" @click="visible = false" />
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -265,65 +283,55 @@ function isRunning(id: string) {
|
||||||
<div>
|
<div>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<template #start>
|
<template #start>
|
||||||
<div class="flex align-items-center">
|
<div class="flex items-center">
|
||||||
<Button icon="pi pi-plus" severity="primary" :label="t('add_new_network')" @click="addNewNetwork" />
|
<Button icon="pi pi-plus" severity="primary" :label="t('add_new_network')" @click="addNewNetwork" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #center>
|
<template #center>
|
||||||
<div class="min-w-40">
|
<div class="min-w-40">
|
||||||
<Dropdown
|
<Select v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
|
||||||
v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
|
:placeholder="t('select_network')" class="w-full">
|
||||||
:placeholder="t('select_network')" class="w-full"
|
|
||||||
>
|
|
||||||
<template #value="slotProps">
|
<template #value="slotProps">
|
||||||
<div class="flex items-start content-center">
|
<div class="flex items-start content-center">
|
||||||
<div class="mr-3 flex-column">
|
<div class="mr-4 flex-col">
|
||||||
<span>{{ slotProps.value.network_name }}</span>
|
<span>{{ slotProps.value.network_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<Tag
|
<Tag class="my-auto leading-3" :severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
|
||||||
class="my-auto leading-3" :severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
|
:value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||||
:value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #option="slotProps">
|
<template #option="slotProps">
|
||||||
<div class="flex flex-col items-start content-center max-w-full">
|
<div class="flex flex-col items-start content-center max-w-full">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="mr-3">
|
<div class="mr-4">
|
||||||
{{ t('network_name') }}: {{ slotProps.option.network_name }}
|
{{ t('network_name') }}: {{ slotProps.option.network_name }}
|
||||||
</div>
|
</div>
|
||||||
<Tag
|
<Tag class="my-auto leading-3"
|
||||||
class="my-auto leading-3"
|
|
||||||
:severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
|
:severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
|
||||||
:value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')"
|
:value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="slotProps.option.networking_method !== NetworkTypes.NetworkingMethod.Standalone"
|
||||||
v-if="slotProps.option.networking_method !== NetworkingMethod.Standalone"
|
class="max-w-full overflow-hidden text-ellipsis">
|
||||||
class="max-w-full overflow-hidden text-ellipsis"
|
{{ slotProps.option.networking_method === NetworkTypes.NetworkingMethod.Manual
|
||||||
>
|
|
||||||
{{ slotProps.option.networking_method === NetworkingMethod.Manual
|
|
||||||
? slotProps.option.peer_urls.join(', ')
|
? slotProps.option.peer_urls.join(', ')
|
||||||
: slotProps.option.public_server_url }}
|
: slotProps.option.public_server_url }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isRunning(slotProps.option.instance_id) && networkStore.instances[slotProps.option.instance_id].detail && (networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4 !== '')"
|
v-if="isRunning(slotProps.option.instance_id) && networkStore.instances[slotProps.option.instance_id].detail && (!!networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)">
|
||||||
>
|
{{
|
||||||
{{ networkStore.instances[slotProps.option.instance_id].detail
|
Utils.ipv4InetToString(networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)
|
||||||
? networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4 : '' }}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Dropdown>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #end>
|
<template #end>
|
||||||
<Button
|
<Button icon="pi pi-cog" severity="secondary" aria-haspopup="true" :label="t('settings')"
|
||||||
icon="pi pi-cog" severity="secondary" aria-haspopup="true" :label="t('settings')"
|
aria-controls="overlay_setting_menu" @click="toggle_setting_menu" />
|
||||||
aria-controls="overlay_setting_menu" @click="toggle_setting_menu"
|
|
||||||
/>
|
|
||||||
<TieredMenu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
|
<TieredMenu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
|
||||||
</template>
|
</template>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
@ -341,20 +349,16 @@ function isRunning(id: string) {
|
||||||
</StepList>
|
</StepList>
|
||||||
<StepPanels value="1">
|
<StepPanels value="1">
|
||||||
<StepPanel v-slot="{ activateCallback = (s: string) => { } } = {}" value="1">
|
<StepPanel v-slot="{ activateCallback = (s: string) => { } } = {}" value="1">
|
||||||
<Config
|
<Config :instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
|
||||||
:instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
|
:cur-network="curNetworkConfig" @run-network="runNetworkCb($event, () => activateCallback('2'))" />
|
||||||
@run-network="runNetworkCb($event, () => activateCallback('2'))"
|
|
||||||
/>
|
|
||||||
</StepPanel>
|
</StepPanel>
|
||||||
<StepPanel v-slot="{ activateCallback = (s: string) => { } } = {}" value="2">
|
<StepPanel v-slot="{ activateCallback = (s: string) => { } } = {}" value="2">
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-col">
|
||||||
<Status :instance-id="networkStore.curNetworkId" />
|
<Status :cur-network-inst="curNetworkInst" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex pt-4 justify-content-center">
|
<div class="flex pt-6 justify-center">
|
||||||
<Button
|
<Button :label="t('stop_network')" severity="danger" icon="pi pi-arrow-left"
|
||||||
:label="t('stop_network')" severity="danger" icon="pi pi-arrow-left"
|
@click="stopNetworkCb(networkStore.curNetwork, () => activateCallback('1'))" />
|
||||||
@click="stopNetworkCb(networkStore.curNetwork, () => activateCallback('1'))"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</StepPanel>
|
</StepPanel>
|
||||||
</StepPanels>
|
</StepPanels>
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
import type { NetworkConfig, NetworkInstance, NetworkInstanceRunningInfo } from '~/types/network'
|
import { NetworkTypes } from 'easytier-frontend-lib'
|
||||||
import { DEFAULT_NETWORK_CONFIG } from '~/types/network'
|
|
||||||
|
|
||||||
export const useNetworkStore = defineStore('networkStore', {
|
export const useNetworkStore = defineStore('networkStore', {
|
||||||
state: () => {
|
state: () => {
|
||||||
const networkList = [DEFAULT_NETWORK_CONFIG()]
|
const networkList = [NetworkTypes.DEFAULT_NETWORK_CONFIG()]
|
||||||
return {
|
return {
|
||||||
// for initially empty lists
|
// for initially empty lists
|
||||||
networkList: networkList as NetworkConfig[],
|
networkList: networkList as NetworkTypes.NetworkConfig[],
|
||||||
// for data that is not yet loaded
|
// for data that is not yet loaded
|
||||||
curNetwork: networkList[0],
|
curNetwork: networkList[0],
|
||||||
|
|
||||||
// uuid -> instance
|
// uuid -> instance
|
||||||
instances: {} as Record<string, NetworkInstance>,
|
instances: {} as Record<string, NetworkTypes.NetworkInstance>,
|
||||||
|
|
||||||
networkInfos: {} as Record<string, NetworkInstanceRunningInfo>,
|
networkInfos: {} as Record<string, NetworkTypes.NetworkInstanceRunningInfo>,
|
||||||
|
|
||||||
autoStartInstIds: [] as string[],
|
autoStartInstIds: [] as string[],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
lastNetwork(): NetworkConfig {
|
lastNetwork(): NetworkTypes.NetworkConfig {
|
||||||
return this.networkList[this.networkList.length - 1]
|
return this.networkList[this.networkList.length - 1]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ export const useNetworkStore = defineStore('networkStore', {
|
||||||
return this.curNetwork.instance_id
|
return this.curNetwork.instance_id
|
||||||
},
|
},
|
||||||
|
|
||||||
networkInstances(): Array<NetworkInstance> {
|
networkInstances(): Array<NetworkTypes.NetworkInstance> {
|
||||||
return Object.values(this.instances)
|
return Object.values(this.instances)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@ export const useNetworkStore = defineStore('networkStore', {
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
addNewNetwork() {
|
addNewNetwork() {
|
||||||
this.networkList.push(DEFAULT_NETWORK_CONFIG())
|
this.networkList.push(NetworkTypes.DEFAULT_NETWORK_CONFIG())
|
||||||
},
|
},
|
||||||
|
|
||||||
delCurNetwork() {
|
delCurNetwork() {
|
||||||
|
@ -66,7 +65,7 @@ export const useNetworkStore = defineStore('networkStore', {
|
||||||
this.instances = {}
|
this.instances = {}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateWithNetworkInfos(networkInfos: Record<string, NetworkInstanceRunningInfo>) {
|
updateWithNetworkInfos(networkInfos: Record<string, NetworkTypes.NetworkInstanceRunningInfo>) {
|
||||||
this.networkInfos = networkInfos
|
this.networkInfos = networkInfos
|
||||||
for (const [instanceId, info] of Object.entries(networkInfos)) {
|
for (const [instanceId, info] of Object.entries(networkInfos)) {
|
||||||
if (this.instances[instanceId] === undefined)
|
if (this.instances[instanceId] === undefined)
|
||||||
|
@ -79,17 +78,17 @@ export const useNetworkStore = defineStore('networkStore', {
|
||||||
},
|
},
|
||||||
|
|
||||||
loadFromLocalStorage() {
|
loadFromLocalStorage() {
|
||||||
let networkList: NetworkConfig[]
|
let networkList: NetworkTypes.NetworkConfig[]
|
||||||
|
|
||||||
// if localStorage default is [{}], instanceId will be undefined
|
// if localStorage default is [{}], instanceId will be undefined
|
||||||
networkList = JSON.parse(localStorage.getItem('networkList') || '[]')
|
networkList = JSON.parse(localStorage.getItem('networkList') || '[]')
|
||||||
networkList = networkList.map((cfg) => {
|
networkList = networkList.map((cfg) => {
|
||||||
return { ...DEFAULT_NETWORK_CONFIG(), ...cfg } as NetworkConfig
|
return { ...NetworkTypes.DEFAULT_NETWORK_CONFIG(), ...cfg } as NetworkTypes.NetworkConfig
|
||||||
})
|
})
|
||||||
|
|
||||||
// prevent a empty list from localStorage, should not happen
|
// prevent a empty list from localStorage, should not happen
|
||||||
if (networkList.length === 0)
|
if (networkList.length === 0)
|
||||||
networkList = [DEFAULT_NETWORK_CONFIG()]
|
networkList = [NetworkTypes.DEFAULT_NETWORK_CONFIG()]
|
||||||
|
|
||||||
this.networkList = networkList
|
this.networkList = networkList
|
||||||
this.curNetwork = this.networkList[0]
|
this.curNetwork = this.networkList[0]
|
||||||
|
|
|
@ -1,213 +0,0 @@
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
|
|
||||||
export enum NetworkingMethod {
|
|
||||||
PublicServer = 'PublicServer',
|
|
||||||
Manual = 'Manual',
|
|
||||||
Standalone = 'Standalone',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NetworkConfig {
|
|
||||||
instance_id: string
|
|
||||||
|
|
||||||
dhcp: boolean
|
|
||||||
virtual_ipv4: string
|
|
||||||
network_length: number
|
|
||||||
hostname?: string
|
|
||||||
network_name: string
|
|
||||||
network_secret: string
|
|
||||||
|
|
||||||
networking_method: NetworkingMethod
|
|
||||||
|
|
||||||
public_server_url: string
|
|
||||||
peer_urls: string[]
|
|
||||||
|
|
||||||
proxy_cidrs: string[]
|
|
||||||
|
|
||||||
enable_vpn_portal: boolean
|
|
||||||
vpn_portal_listen_port: number
|
|
||||||
vpn_portal_client_network_addr: string
|
|
||||||
vpn_portal_client_network_len: number
|
|
||||||
|
|
||||||
advanced_settings: boolean
|
|
||||||
|
|
||||||
listener_urls: string[]
|
|
||||||
rpc_port: number
|
|
||||||
latency_first: boolean
|
|
||||||
|
|
||||||
dev_name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
|
||||||
return {
|
|
||||||
instance_id: uuidv4(),
|
|
||||||
|
|
||||||
dhcp: true,
|
|
||||||
virtual_ipv4: '',
|
|
||||||
network_length: 24,
|
|
||||||
network_name: 'easytier',
|
|
||||||
network_secret: '',
|
|
||||||
|
|
||||||
networking_method: NetworkingMethod.PublicServer,
|
|
||||||
|
|
||||||
public_server_url: 'tcp://public.easytier.top:11010',
|
|
||||||
peer_urls: [],
|
|
||||||
|
|
||||||
proxy_cidrs: [],
|
|
||||||
|
|
||||||
enable_vpn_portal: false,
|
|
||||||
vpn_portal_listen_port: 22022,
|
|
||||||
vpn_portal_client_network_addr: '',
|
|
||||||
vpn_portal_client_network_len: 24,
|
|
||||||
|
|
||||||
advanced_settings: false,
|
|
||||||
|
|
||||||
listener_urls: [
|
|
||||||
'tcp://0.0.0.0:11010',
|
|
||||||
'udp://0.0.0.0:11010',
|
|
||||||
'wg://0.0.0.0:11011',
|
|
||||||
],
|
|
||||||
rpc_port: 0,
|
|
||||||
latency_first: true,
|
|
||||||
dev_name: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NetworkInstance {
|
|
||||||
instance_id: string
|
|
||||||
|
|
||||||
running: boolean
|
|
||||||
error_msg: string
|
|
||||||
|
|
||||||
detail?: NetworkInstanceRunningInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NetworkInstanceRunningInfo {
|
|
||||||
dev_name: string
|
|
||||||
my_node_info: NodeInfo
|
|
||||||
events: Record<string, any>
|
|
||||||
node_info: NodeInfo
|
|
||||||
routes: Route[]
|
|
||||||
peers: PeerInfo[]
|
|
||||||
peer_route_pairs: PeerRoutePair[]
|
|
||||||
running: boolean
|
|
||||||
error_msg?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Ipv4Addr {
|
|
||||||
addr: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Ipv6Addr {
|
|
||||||
part1: number
|
|
||||||
part2: number
|
|
||||||
part3: number
|
|
||||||
part4: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeInfo {
|
|
||||||
virtual_ipv4: string
|
|
||||||
hostname: string
|
|
||||||
version: string
|
|
||||||
ips: {
|
|
||||||
public_ipv4: Ipv4Addr
|
|
||||||
interface_ipv4s: Ipv4Addr[]
|
|
||||||
public_ipv6: Ipv6Addr
|
|
||||||
interface_ipv6s: Ipv6Addr[]
|
|
||||||
listeners: {
|
|
||||||
serialization: string
|
|
||||||
scheme_end: number
|
|
||||||
username_end: number
|
|
||||||
host_start: number
|
|
||||||
host_end: number
|
|
||||||
host: any
|
|
||||||
port?: number
|
|
||||||
path_start: number
|
|
||||||
query_start?: number
|
|
||||||
fragment_start?: number
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
stun_info: StunInfo
|
|
||||||
listeners: string[]
|
|
||||||
vpn_portal_cfg?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StunInfo {
|
|
||||||
udp_nat_type: number
|
|
||||||
tcp_nat_type: number
|
|
||||||
last_update_time: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Route {
|
|
||||||
peer_id: number
|
|
||||||
ipv4_addr: {
|
|
||||||
address: Ipv4Addr
|
|
||||||
network_length: number
|
|
||||||
} | string | null
|
|
||||||
next_hop_peer_id: number
|
|
||||||
cost: number
|
|
||||||
proxy_cidrs: string[]
|
|
||||||
hostname: string
|
|
||||||
stun_info?: StunInfo
|
|
||||||
inst_id: string
|
|
||||||
version: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerInfo {
|
|
||||||
peer_id: number
|
|
||||||
conns: PeerConnInfo[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerConnInfo {
|
|
||||||
conn_id: string
|
|
||||||
my_peer_id: number
|
|
||||||
is_client: boolean
|
|
||||||
peer_id: number
|
|
||||||
features: string[]
|
|
||||||
tunnel?: TunnelInfo
|
|
||||||
stats?: PeerConnStats
|
|
||||||
loss_rate: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerRoutePair {
|
|
||||||
route: Route
|
|
||||||
peer?: PeerInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TunnelInfo {
|
|
||||||
tunnel_type: string
|
|
||||||
local_addr: string
|
|
||||||
remote_addr: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerConnStats {
|
|
||||||
rx_bytes: number
|
|
||||||
tx_bytes: number
|
|
||||||
rx_packets: number
|
|
||||||
tx_packets: number
|
|
||||||
latency_us: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EventType {
|
|
||||||
TunDeviceReady = 'TunDeviceReady', // string
|
|
||||||
TunDeviceError = 'TunDeviceError', // string
|
|
||||||
|
|
||||||
PeerAdded = 'PeerAdded', // number
|
|
||||||
PeerRemoved = 'PeerRemoved', // number
|
|
||||||
PeerConnAdded = 'PeerConnAdded', // PeerConnInfo
|
|
||||||
PeerConnRemoved = 'PeerConnRemoved', // PeerConnInfo
|
|
||||||
|
|
||||||
ListenerAdded = 'ListenerAdded', // any
|
|
||||||
ListenerAddFailed = 'ListenerAddFailed', // any, string
|
|
||||||
ListenerAcceptFailed = 'ListenerAcceptFailed', // any, string
|
|
||||||
ConnectionAccepted = 'ConnectionAccepted', // string, string
|
|
||||||
ConnectionError = 'ConnectionError', // string, string, string
|
|
||||||
|
|
||||||
Connecting = 'Connecting', // any
|
|
||||||
ConnectError = 'ConnectError', // string, string, string
|
|
||||||
|
|
||||||
VpnPortalClientConnected = 'VpnPortalClientConnected', // string, string
|
|
||||||
VpnPortalClientDisconnected = 'VpnPortalClientDisconnected', // string, string, string
|
|
||||||
|
|
||||||
DhcpIpv4Changed = 'DhcpIpv4Changed', // ipv4 | null, ipv4 | null
|
|
||||||
DhcpIpv4Conflicted = 'DhcpIpv4Conflicted', // ipv4 | null
|
|
||||||
}
|
|
|
@ -22,6 +22,7 @@
|
||||||
"@vueuse/core": "^11.1.0",
|
"@vueuse/core": "^11.1.0",
|
||||||
"aura": "link:@primevue\\themes\\aura",
|
"aura": "link:@primevue\\themes\\aura",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
"floating-vue": "^5.2",
|
||||||
"ip-num": "1.5.1",
|
"ip-num": "1.5.1",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^4.2.1",
|
"primevue": "^4.2.1",
|
||||||
|
@ -42,6 +43,6 @@
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^5.4.10",
|
"vite": "^5.4.10",
|
||||||
"vite-plugin-dts": "^4.3.0",
|
"vite-plugin-dts": "^4.3.0",
|
||||||
"vue-tsc": "^2.1.8"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
configInvalid?: boolean
|
configInvalid?: boolean
|
||||||
instanceId?: string
|
|
||||||
hostname?: string
|
hostname?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { IPv4 } from 'ip-num/IPNumber'
|
||||||
import { NetworkInstance, type NodeInfo, type PeerRoutePair } from '../types/network'
|
import { NetworkInstance, type NodeInfo, type PeerRoutePair } from '../types/network'
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import { num2ipv4, num2ipv6 } from '../modules/utils';
|
import { ipv4InetToString, ipv4ToString, ipv6ToString } from '../modules/utils';
|
||||||
import { DataTable, Column, Tag, Chip, Button, Dialog, ScrollPanel, Timeline, Divider, Card, } from 'primevue';
|
import { DataTable, Column, Tag, Chip, Button, Dialog, ScrollPanel, Timeline, Divider, Card, } from 'primevue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -138,7 +138,7 @@ const myNodeInfoChips = computed(() => {
|
||||||
|
|
||||||
// virtual ipv4
|
// virtual ipv4
|
||||||
chips.push({
|
chips.push({
|
||||||
label: `Virtual IPv4: ${my_node_info.virtual_ipv4}`,
|
label: `Virtual IPv4: ${ipv4InetToString(my_node_info.virtual_ipv4)}`,
|
||||||
icon: '',
|
icon: '',
|
||||||
} as Chip)
|
} as Chip)
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ const myNodeInfoChips = computed(() => {
|
||||||
const local_ipv4s = my_node_info.ips?.interface_ipv4s
|
const local_ipv4s = my_node_info.ips?.interface_ipv4s
|
||||||
for (const [idx, ip] of local_ipv4s?.entries()) {
|
for (const [idx, ip] of local_ipv4s?.entries()) {
|
||||||
chips.push({
|
chips.push({
|
||||||
label: `Local IPv4 ${idx}: ${num2ipv4(ip)}`,
|
label: `Local IPv4 ${idx}: ${ipv4ToString(ip)}`,
|
||||||
icon: '',
|
icon: '',
|
||||||
} as Chip)
|
} as Chip)
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ const myNodeInfoChips = computed(() => {
|
||||||
const local_ipv6s = my_node_info.ips?.interface_ipv6s
|
const local_ipv6s = my_node_info.ips?.interface_ipv6s
|
||||||
for (const [idx, ip] of local_ipv6s?.entries()) {
|
for (const [idx, ip] of local_ipv6s?.entries()) {
|
||||||
chips.push({
|
chips.push({
|
||||||
label: `Local IPv6 ${idx}: ${num2ipv6(ip)}`,
|
label: `Local IPv6 ${idx}: ${ipv6ToString(ip)}`,
|
||||||
icon: '',
|
icon: '',
|
||||||
} as Chip)
|
} as Chip)
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ const myNodeInfoChips = computed(() => {
|
||||||
const public_ipv6 = my_node_info.ips?.public_ipv6
|
const public_ipv6 = my_node_info.ips?.public_ipv6
|
||||||
if (public_ipv6) {
|
if (public_ipv6) {
|
||||||
chips.push({
|
chips.push({
|
||||||
label: `Public IPv6: ${num2ipv6(public_ipv6)}`,
|
label: `Public IPv6: ${ipv6ToString(public_ipv6)}`,
|
||||||
icon: '',
|
icon: '',
|
||||||
} as Chip)
|
} as Chip)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,17 @@ import PrimeVue from 'primevue/config'
|
||||||
import I18nUtils from './modules/i18n'
|
import I18nUtils from './modules/i18n'
|
||||||
import * as NetworkTypes from './types/network'
|
import * as NetworkTypes from './types/network'
|
||||||
import HumanEvent from './components/HumanEvent.vue';
|
import HumanEvent from './components/HumanEvent.vue';
|
||||||
import Tooltip from 'primevue/tooltip';
|
|
||||||
|
// do not use primevue tooltip, it has serious memory leak issue
|
||||||
|
// https://github.com/primefaces/primevue/issues/5856
|
||||||
|
// import Tooltip from 'primevue/tooltip';
|
||||||
|
import { vTooltip } from 'floating-vue';
|
||||||
|
|
||||||
import * as Api from './modules/api';
|
import * as Api from './modules/api';
|
||||||
import * as Utils from './modules/utils';
|
import * as Utils from './modules/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install: (app: App) => {
|
install: (app: App): void => {
|
||||||
app.use(I18nUtils.i18n, { useScope: 'global' })
|
app.use(I18nUtils.i18n, { useScope: 'global' })
|
||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
|
@ -32,7 +37,7 @@ export default {
|
||||||
app.component('Config', Config);
|
app.component('Config', Config);
|
||||||
app.component('Status', Status);
|
app.component('Status', Status);
|
||||||
app.component('HumanEvent', HumanEvent);
|
app.component('HumanEvent', HumanEvent);
|
||||||
app.directive('tooltip', Tooltip);
|
app.directive('tooltip', vTooltip as any);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
||||||
import { Ipv4Addr, Ipv6Addr } from '../types/network'
|
import { Ipv4Addr, Ipv4Inet, Ipv6Addr } from '../types/network'
|
||||||
|
|
||||||
export function num2ipv4(ip: Ipv4Addr) {
|
export function ipv4ToString(ip: Ipv4Addr) {
|
||||||
return IPv4.fromNumber(ip.addr)
|
return IPv4.fromNumber(ip.addr).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function num2ipv6(ip: Ipv6Addr) {
|
export function ipv4InetToString(ip: Ipv4Inet | undefined) {
|
||||||
|
if (ip?.address === undefined) {
|
||||||
|
return 'undefined'
|
||||||
|
}
|
||||||
|
return `${ipv4ToString(ip.address)}/${ip.network_length}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ipv6ToString(ip: Ipv6Addr) {
|
||||||
return IPv6.fromBigInt(
|
return IPv6.fromBigInt(
|
||||||
(BigInt(ip.part1) << BigInt(96))
|
(BigInt(ip.part1) << BigInt(96))
|
||||||
+ (BigInt(ip.part2) << BigInt(64))
|
+ (BigInt(ip.part2) << BigInt(64))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@import 'primeicons/primeicons.css';
|
@import 'primeicons/primeicons.css';
|
||||||
|
@import 'floating-vue/dist/style.css';
|
||||||
|
|
||||||
.frontend-lib {
|
.frontend-lib {
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,6 @@ export interface NetworkInstanceRunningInfo {
|
||||||
dev_name: string
|
dev_name: string
|
||||||
my_node_info: NodeInfo
|
my_node_info: NodeInfo
|
||||||
events: Array<string>,
|
events: Array<string>,
|
||||||
node_info: NodeInfo
|
|
||||||
routes: Route[]
|
routes: Route[]
|
||||||
peers: PeerInfo[]
|
peers: PeerInfo[]
|
||||||
peer_route_pairs: PeerRoutePair[]
|
peer_route_pairs: PeerRoutePair[]
|
||||||
|
@ -97,6 +96,11 @@ export interface Ipv4Addr {
|
||||||
addr: number
|
addr: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Ipv4Inet {
|
||||||
|
address: Ipv4Addr
|
||||||
|
network_length: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface Ipv6Addr {
|
export interface Ipv6Addr {
|
||||||
part1: number
|
part1: number
|
||||||
part2: number
|
part2: number
|
||||||
|
@ -109,7 +113,7 @@ export interface Url {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeInfo {
|
export interface NodeInfo {
|
||||||
virtual_ipv4: string
|
virtual_ipv4: Ipv4Inet,
|
||||||
hostname: string
|
hostname: string
|
||||||
version: string
|
version: string
|
||||||
ips: {
|
ips: {
|
||||||
|
@ -143,10 +147,7 @@ export interface StunInfo {
|
||||||
|
|
||||||
export interface Route {
|
export interface Route {
|
||||||
peer_id: number
|
peer_id: number
|
||||||
ipv4_addr: {
|
ipv4_addr: Ipv4Inet | string | null
|
||||||
address: Ipv4Addr
|
|
||||||
network_length: number
|
|
||||||
} | string | null
|
|
||||||
next_hop_peer_id: number
|
next_hop_peer_id: number
|
||||||
cost: number
|
cost: number
|
||||||
proxy_cidrs: string[]
|
proxy_cidrs: string[]
|
||||||
|
|
|
@ -27,6 +27,6 @@
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^5.4.10",
|
"vite": "^5.4.10",
|
||||||
"vite-plugin-singlefile": "^2.0.3",
|
"vite-plugin-singlefile": "^2.0.3",
|
||||||
"vue-tsc": "^2.1.8"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub struct Event {
|
||||||
|
|
||||||
struct EasyTierData {
|
struct EasyTierData {
|
||||||
events: RwLock<VecDeque<Event>>,
|
events: RwLock<VecDeque<Event>>,
|
||||||
node_info: RwLock<MyNodeInfo>,
|
my_node_info: RwLock<MyNodeInfo>,
|
||||||
routes: RwLock<Vec<Route>>,
|
routes: RwLock<Vec<Route>>,
|
||||||
peers: RwLock<Vec<PeerInfo>>,
|
peers: RwLock<Vec<PeerInfo>>,
|
||||||
tun_fd: Arc<RwLock<Option<i32>>>,
|
tun_fd: Arc<RwLock<Option<i32>>>,
|
||||||
|
@ -46,7 +46,7 @@ impl Default for EasyTierData {
|
||||||
Self {
|
Self {
|
||||||
event_subscriber: RwLock::new(tx),
|
event_subscriber: RwLock::new(tx),
|
||||||
events: RwLock::new(VecDeque::new()),
|
events: RwLock::new(VecDeque::new()),
|
||||||
node_info: RwLock::new(MyNodeInfo::default()),
|
my_node_info: RwLock::new(MyNodeInfo::default()),
|
||||||
routes: RwLock::new(Vec::new()),
|
routes: RwLock::new(Vec::new()),
|
||||||
peers: RwLock::new(Vec::new()),
|
peers: RwLock::new(Vec::new()),
|
||||||
tun_fd: Arc::new(RwLock::new(None)),
|
tun_fd: Arc::new(RwLock::new(None)),
|
||||||
|
@ -162,7 +162,7 @@ impl EasyTierLauncher {
|
||||||
global_ctx_c.get_flags().dev_name.clone();
|
global_ctx_c.get_flags().dev_name.clone();
|
||||||
|
|
||||||
let node_info = MyNodeInfo {
|
let node_info = MyNodeInfo {
|
||||||
virtual_ipv4: global_ctx_c.get_ipv4().map(|x| x.address().into()),
|
virtual_ipv4: global_ctx_c.get_ipv4().map(|ip| ip.into()),
|
||||||
hostname: global_ctx_c.get_hostname(),
|
hostname: global_ctx_c.get_hostname(),
|
||||||
version: EASYTIER_VERSION.to_string(),
|
version: EASYTIER_VERSION.to_string(),
|
||||||
ips: Some(global_ctx_c.get_ip_collector().collect_ip_addrs().await),
|
ips: Some(global_ctx_c.get_ip_collector().collect_ip_addrs().await),
|
||||||
|
@ -180,7 +180,7 @@ impl EasyTierLauncher {
|
||||||
.await,
|
.await,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
*data_c.node_info.write().unwrap() = node_info.clone();
|
*data_c.my_node_info.write().unwrap() = node_info.clone();
|
||||||
*data_c.routes.write().unwrap() = peer_mgr_c.list_routes().await;
|
*data_c.routes.write().unwrap() = peer_mgr_c.list_routes().await;
|
||||||
*data_c.peers.write().unwrap() = PeerManagerRpcService::new(peer_mgr_c.clone())
|
*data_c.peers.write().unwrap() = PeerManagerRpcService::new(peer_mgr_c.clone())
|
||||||
.list_peers()
|
.list_peers()
|
||||||
|
@ -282,7 +282,7 @@ impl EasyTierLauncher {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_node_info(&self) -> MyNodeInfo {
|
pub fn get_node_info(&self) -> MyNodeInfo {
|
||||||
self.data.node_info.read().unwrap().clone()
|
self.data.my_node_info.read().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_routes(&self) -> Vec<Route> {
|
pub fn get_routes(&self) -> Vec<Route> {
|
||||||
|
@ -352,7 +352,6 @@ impl NetworkInstance {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| serde_json::to_string(e).unwrap())
|
.map(|e| serde_json::to_string(e).unwrap())
|
||||||
.collect(),
|
.collect(),
|
||||||
node_info: Some(launcher.get_node_info()),
|
|
||||||
routes,
|
routes,
|
||||||
peers,
|
peers,
|
||||||
peer_route_pairs,
|
peer_route_pairs,
|
||||||
|
|
|
@ -43,7 +43,7 @@ message NetworkConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
message MyNodeInfo {
|
message MyNodeInfo {
|
||||||
common.Ipv4Addr virtual_ipv4 = 1;
|
common.Ipv4Inet virtual_ipv4 = 1;
|
||||||
string hostname = 2;
|
string hostname = 2;
|
||||||
string version = 3;
|
string version = 3;
|
||||||
peer_rpc.GetIpListResponse ips = 4;
|
peer_rpc.GetIpListResponse ips = 4;
|
||||||
|
@ -56,12 +56,11 @@ message NetworkInstanceRunningInfo {
|
||||||
string dev_name = 1;
|
string dev_name = 1;
|
||||||
MyNodeInfo my_node_info = 2;
|
MyNodeInfo my_node_info = 2;
|
||||||
repeated string events = 3;
|
repeated string events = 3;
|
||||||
MyNodeInfo node_info = 4;
|
repeated cli.Route routes = 4;
|
||||||
repeated cli.Route routes = 5;
|
repeated cli.PeerInfo peers = 5;
|
||||||
repeated cli.PeerInfo peers = 6;
|
repeated cli.PeerRoutePair peer_route_pairs = 6;
|
||||||
repeated cli.PeerRoutePair peer_route_pairs = 7;
|
bool running = 7;
|
||||||
bool running = 8;
|
optional string error_msg = 8;
|
||||||
optional string error_msg = 9;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message NetworkInstanceRunningInfoMap {
|
message NetworkInstanceRunningInfoMap {
|
||||||
|
|
852
pnpm-lock.yaml
852
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@ Default permissions for the plugin
|
||||||
- `allow-ping`
|
- `allow-ping`
|
||||||
- `allow-start-vpn`
|
- `allow-start-vpn`
|
||||||
|
|
||||||
### Permission Table
|
## Permission Table
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -295,81 +295,59 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"description": "allow-ping -> Enables the ping command without any pre-configured scope.",
|
"description": "Enables the ping command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "allow-ping"
|
||||||
"allow-ping"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "deny-ping -> Denies the ping command without any pre-configured scope.",
|
"description": "Denies the ping command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "deny-ping"
|
||||||
"deny-ping"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "allow-prepare-vpn -> Enables the prepare_vpn command without any pre-configured scope.",
|
"description": "Enables the prepare_vpn command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "allow-prepare-vpn"
|
||||||
"allow-prepare-vpn"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "deny-prepare-vpn -> Denies the prepare_vpn command without any pre-configured scope.",
|
"description": "Denies the prepare_vpn command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "deny-prepare-vpn"
|
||||||
"deny-prepare-vpn"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "allow-register-listener -> Enables the register_listener command without any pre-configured scope.",
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "allow-register-listener"
|
||||||
"allow-register-listener"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "deny-register-listener -> Denies the register_listener command without any pre-configured scope.",
|
"description": "Denies the register_listener command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "deny-register-listener"
|
||||||
"deny-register-listener"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "allow-start-vpn -> Enables the start_vpn command without any pre-configured scope.",
|
"description": "Enables the start_vpn command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "allow-start-vpn"
|
||||||
"allow-start-vpn"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "deny-start-vpn -> Denies the start_vpn command without any pre-configured scope.",
|
"description": "Denies the start_vpn command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "deny-start-vpn"
|
||||||
"deny-start-vpn"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "allow-stop-vpn -> Enables the stop_vpn command without any pre-configured scope.",
|
"description": "Enables the stop_vpn command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "allow-stop-vpn"
|
||||||
"allow-stop-vpn"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "deny-stop-vpn -> Denies the stop_vpn command without any pre-configured scope.",
|
"description": "Denies the stop_vpn command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "deny-stop-vpn"
|
||||||
"deny-stop-vpn"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "default -> Default permissions for the plugin",
|
"description": "Default permissions for the plugin",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"const": "default"
|
||||||
"default"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user