Merge pull request #84 from m1m1sha/perf/gui-front-perf
Optimize the GUI front-end project structure
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "easytier-gui"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "easytier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"eslint.experimental.useFlatConfig": true,
|
||||||
|
"prettier.enable": false,
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
"eslint.rules.customizations": [
|
||||||
|
{
|
||||||
|
"rule": "style/*",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "style/eol-last",
|
||||||
|
"severity": "error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "format/*",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-indent",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-spacing",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-spaces",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-order",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-dangle",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-newline",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*quotes",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*semi",
|
||||||
|
"severity": "off"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"eslint.validate": [
|
||||||
|
"code-workspace",
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"yaml",
|
||||||
|
"toml",
|
||||||
|
"gql",
|
||||||
|
"graphql"
|
||||||
|
],
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"easytier-gui/locales"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ dist-ssr
|
|||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.suo
|
*.suo
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"vue.volar",
|
||||||
|
"lokalise.i18n-ally"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"locales"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// @ts-check
|
||||||
|
import antfu from '@antfu/eslint-config'
|
||||||
|
|
||||||
|
export default antfu({
|
||||||
|
formatters: true,
|
||||||
|
rules: {
|
||||||
|
'style/eol-last': ['error', 'always'],
|
||||||
|
},
|
||||||
|
ignores: [
|
||||||
|
'src-tauri/**',
|
||||||
|
],
|
||||||
|
})
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
network: 网络
|
||||||
|
networking_method: 网络方式
|
||||||
|
public_server: 公共服务器
|
||||||
|
manual: 手动
|
||||||
|
standalone: 独立
|
||||||
|
virtual_ipv4: 虚拟IPv4地址
|
||||||
|
network_name: 网络名称
|
||||||
|
network_secret: 网络密码
|
||||||
|
public_server_url: 公共服务器地址
|
||||||
|
peer_urls: 对等节点地址
|
||||||
|
proxy_cidrs: 子网代理CIDR
|
||||||
|
enable_vpn_portal: 启用VPN门户
|
||||||
|
vpn_portal_listen_port: 监听端口
|
||||||
|
vpn_portal_client_network: 客户端子网
|
||||||
|
advanced_settings: 高级设置
|
||||||
|
listener_urls: 监听地址
|
||||||
|
rpc_port: RPC端口
|
||||||
|
config_network: 配置网络
|
||||||
|
running: 运行中
|
||||||
|
error_msg: 错误信息
|
||||||
|
detail: 详情
|
||||||
|
add_new_network: 添加新网络
|
||||||
|
del_cur_network: 删除当前网络
|
||||||
|
select_network: 选择网络
|
||||||
|
network_instances: 网络实例
|
||||||
|
instance_id: 实例ID
|
||||||
|
network_infos: 网络信息
|
||||||
|
parse_network_config: 解析网络配置
|
||||||
|
retain_network_instance: 保留网络实例
|
||||||
|
collect_network_infos: 收集网络信息
|
||||||
|
settings: 设置
|
||||||
|
exchange_language: Switch to English
|
||||||
|
exit: 退出
|
||||||
|
chips_placeholder: 例如: {0}, 按回车添加
|
||||||
|
off_text: 点击关闭
|
||||||
|
on_text: 点击开启
|
||||||
|
show_config: 显示配置
|
||||||
|
close: 关闭
|
||||||
|
|
||||||
|
my_node_info: 当前节点信息
|
||||||
|
peer_count: 已连接
|
||||||
|
upload: 上传
|
||||||
|
download: 下载
|
||||||
|
show_vpn_portal_config: 显示VPN门户配置
|
||||||
|
show_event_log: 显示事件日志
|
||||||
|
peer_info: 节点信息
|
||||||
|
hostname: 主机名
|
||||||
|
route_cost: 路由
|
||||||
|
latency: 延迟
|
||||||
|
upload_bytes: 上传
|
||||||
|
download_bytes: 下载
|
||||||
|
loss_rate: 丢包率
|
||||||
|
|
||||||
|
run_network: 运行网络
|
||||||
|
stop_network: 停止网络
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
network: Network
|
||||||
|
networking_method: Networking Method
|
||||||
|
public_server: Public Server
|
||||||
|
manual: Manual
|
||||||
|
standalone: Standalone
|
||||||
|
virtual_ipv4: Virtual IPv4
|
||||||
|
network_name: Network Name
|
||||||
|
network_secret: Network Secret
|
||||||
|
public_server_url: Public Server URL
|
||||||
|
peer_urls: Peer URLs
|
||||||
|
proxy_cidrs: Subnet Proxy CIDRs
|
||||||
|
enable_vpn_portal: Enable VPN Portal
|
||||||
|
vpn_portal_listen_port: VPN Portal Listen Port
|
||||||
|
vpn_portal_client_network: Client Sub Network
|
||||||
|
advanced_settings: Advanced Settings
|
||||||
|
listener_urls: Listener URLs
|
||||||
|
rpc_port: RPC Port
|
||||||
|
config_network: Config Network
|
||||||
|
running: Running
|
||||||
|
error_msg: Error Message
|
||||||
|
detail: Detail
|
||||||
|
add_new_network: Add New Network
|
||||||
|
del_cur_network: Delete Current Network
|
||||||
|
select_network: Select Network
|
||||||
|
network_instances: Network Instances
|
||||||
|
instance_id: Instance ID
|
||||||
|
network_infos: Network Infos
|
||||||
|
parse_network_config: Parse Network Config
|
||||||
|
retain_network_instance: Retain Network Instance
|
||||||
|
collect_network_infos: Collect Network Infos
|
||||||
|
settings: Settings
|
||||||
|
exchange_language: 切换中文
|
||||||
|
exit: Exit
|
||||||
|
|
||||||
|
chips_placeholder: 'e.g: {0}, press Enter to add'
|
||||||
|
off_text: Press to disable
|
||||||
|
on_text: Press to enable
|
||||||
|
show_config: Show Config
|
||||||
|
close: Close
|
||||||
|
my_node_info: My Node Info
|
||||||
|
peer_count: Connected
|
||||||
|
upload: Upload
|
||||||
|
download: Download
|
||||||
|
show_vpn_portal_config: Show VPN Portal Config
|
||||||
|
show_event_log: Show Event Log
|
||||||
|
peer_info: Peer Info
|
||||||
|
route_cost: Route Cost
|
||||||
|
hostname: Hostname
|
||||||
|
latency: Latency
|
||||||
|
upload_bytes: Upload
|
||||||
|
download_bytes: Download
|
||||||
|
loss_rate: Loss Rate
|
||||||
|
|
||||||
|
run_network: Run Network
|
||||||
|
stop_network: Stop Network
|
||||||
+26
-13
@@ -1,37 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "easytier-gui",
|
"name": "easytier-gui",
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri",
|
||||||
|
"lint": "eslint . --ignore-pattern src-tauri",
|
||||||
|
"lint:fix": "eslint . --ignore-pattern src-tauri --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1",
|
"@tauri-apps/api": "^1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"primeflex": "^3.3.1",
|
"primeflex": "^3.3.1",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primevue": "^3.51.0",
|
"primevue": "^3.52.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.4.26",
|
||||||
"vue-router": "^4.3.0"
|
"vue-i18n": "^9.13.1",
|
||||||
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@antfu/eslint-config": "^2.16.1",
|
||||||
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"@tauri-apps/cli": "^1",
|
"@tauri-apps/cli": "^1",
|
||||||
|
"@types/node": "^20.12.8",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@vue-macros/volar": "^0.19.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"naive-ui": "^2.38.1",
|
"eslint": "^9.2.0",
|
||||||
|
"eslint-plugin-format": "^0.1.1",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.4.5",
|
||||||
"unplugin-vue-components": "^0.26.0",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
|
"unplugin-vue-components": "^0.27.0",
|
||||||
|
"unplugin-vue-macros": "^2.9.2",
|
||||||
|
"unplugin-vue-markdown": "^0.26.2",
|
||||||
|
"unplugin-vue-router": "^0.8.6",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"vfonts": "^0.0.3",
|
"vite": "^5.2.11",
|
||||||
"vite": "^5.0.0",
|
"vite-plugin-vue-devtools": "^7.1.3",
|
||||||
"vue-i18n": "^9.12.0",
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
"vue-tsc": "^1.8.5"
|
"vue-i18n": "^9.13.1",
|
||||||
|
"vue-tsc": "^2.0.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+5195
-1159
File diff suppressed because it is too large
Load Diff
+1
-256
@@ -1,258 +1,3 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
|
||||||
|
|
||||||
import Stepper from 'primevue/stepper';
|
|
||||||
import StepperPanel from 'primevue/stepperpanel';
|
|
||||||
|
|
||||||
import { useToast } from "primevue/usetoast";
|
|
||||||
|
|
||||||
import {
|
|
||||||
i18n, loadLocaleFromLocalStorage, NetworkConfig, parseNetworkConfig,
|
|
||||||
useNetworkStore, runNetworkInstance, retainNetworkInstance, collectNetworkInfos,
|
|
||||||
changeLocale
|
|
||||||
} from './main';
|
|
||||||
|
|
||||||
import Config from './components/Config.vue';
|
|
||||||
import Status from './components/Status.vue';
|
|
||||||
|
|
||||||
import { exit } from '@tauri-apps/api/process';
|
|
||||||
|
|
||||||
const visible = ref(false);
|
|
||||||
const tomlConfig = ref("");
|
|
||||||
|
|
||||||
const items = ref([
|
|
||||||
{
|
|
||||||
label: () => i18n.global.t('show_config'),
|
|
||||||
icon: 'pi pi-file-edit',
|
|
||||||
command: async () => {
|
|
||||||
try {
|
|
||||||
const ret = await parseNetworkConfig(networkStore.curNetwork);
|
|
||||||
tomlConfig.value = ret;
|
|
||||||
} catch (e: any) {
|
|
||||||
tomlConfig.value = e;
|
|
||||||
}
|
|
||||||
visible.value = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => i18n.global.t('del_cur_network'),
|
|
||||||
icon: 'pi pi-times',
|
|
||||||
command: async () => {
|
|
||||||
networkStore.removeNetworkInstance(networkStore.curNetwork.instance_id);
|
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds);
|
|
||||||
networkStore.delCurNetwork();
|
|
||||||
},
|
|
||||||
disabled: () => networkStore.networkList.length <= 1,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
enum Severity {
|
|
||||||
None = "none",
|
|
||||||
Success = "success",
|
|
||||||
Info = "info",
|
|
||||||
Warn = "warn",
|
|
||||||
Error = "error",
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageBarSeverity = ref(Severity.None);
|
|
||||||
const messageBarContent = ref("");
|
|
||||||
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
const networkStore = useNetworkStore();
|
|
||||||
|
|
||||||
const addNewNetwork = () => {
|
|
||||||
networkStore.addNewNetwork();
|
|
||||||
networkStore.curNetwork = networkStore.lastNetwork;
|
|
||||||
}
|
|
||||||
|
|
||||||
const networkMenuName = (network: NetworkConfig) => {
|
|
||||||
return network.network_name + " (" + network.instance_id + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
networkStore.$subscribe(async () => {
|
|
||||||
networkStore.saveToLocalStroage();
|
|
||||||
try {
|
|
||||||
await parseNetworkConfig(networkStore.curNetwork);
|
|
||||||
messageBarSeverity.value = Severity.None;
|
|
||||||
} catch (e: any) {
|
|
||||||
messageBarContent.value = e;
|
|
||||||
messageBarSeverity.value = Severity.Error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function runNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) {
|
|
||||||
cb({} as MouseEvent);
|
|
||||||
networkStore.removeNetworkInstance(cfg.instance_id);
|
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds);
|
|
||||||
networkStore.addNetworkInstance(cfg.instance_id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await runNetworkInstance(cfg);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
toast.add({ severity: 'info', detail: e });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stopNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) {
|
|
||||||
console.log("stopNetworkCb", cfg, cb);
|
|
||||||
cb({} as MouseEvent);
|
|
||||||
networkStore.removeNetworkInstance(cfg.instance_id);
|
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNetworkInfos() {
|
|
||||||
networkStore.updateWithNetworkInfos(await collectNetworkInfos());
|
|
||||||
}
|
|
||||||
|
|
||||||
let intervalId = 0;
|
|
||||||
onMounted(() => {
|
|
||||||
intervalId = setInterval(async () => {
|
|
||||||
await updateNetworkInfos();
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
onUnmounted(() => clearInterval(intervalId))
|
|
||||||
|
|
||||||
const curNetworkHasInstance = computed(() => {
|
|
||||||
return networkStore.networkInstanceIds.includes(networkStore.curNetworkId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const activeStep = computed(() => {
|
|
||||||
return curNetworkHasInstance.value ? 1 : 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const setting_menu = ref();
|
|
||||||
const setting_menu_items = ref([
|
|
||||||
{
|
|
||||||
label: () => i18n.global.t('settings'),
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: () => i18n.global.t('exchange_language'),
|
|
||||||
icon: 'pi pi-refresh',
|
|
||||||
command: () => {
|
|
||||||
changeLocale((i18n.global.locale.value === 'en' ? 'cn' : 'en'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => i18n.global.t('exit'),
|
|
||||||
icon: 'pi pi-times',
|
|
||||||
command: async () => {
|
|
||||||
await exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const toggle_setting_menu = (event: any) => {
|
|
||||||
setting_menu.value.toggle(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
networkStore.loadFromLocalStorage();
|
|
||||||
changeLocale(loadLocaleFromLocalStorage());
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- <n-config-provider :theme="lightTheme"> -->
|
<RouterView />
|
||||||
<div id="root" class="flex flex-column">
|
|
||||||
<Dialog v-model:visible="visible" modal header="Config File" :style="{ width: '70%' }">
|
|
||||||
<Panel>
|
|
||||||
<ScrollPanel style="width: 100%; height: 300px">
|
|
||||||
<pre>{{ tomlConfig }}</pre>
|
|
||||||
</ScrollPanel>
|
|
||||||
</Panel>
|
|
||||||
<Divider />
|
|
||||||
<div class="flex justify-content-end gap-2">
|
|
||||||
<Button type="button" :label="$t('close')" @click="visible = false"></Button>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Toolbar>
|
|
||||||
<template #start>
|
|
||||||
<div class="flex align-items-center gap-2">
|
|
||||||
<Button icon="pi pi-plus" class="mr-2" severity="primary" :label="$t('add_new_network')"
|
|
||||||
@click="addNewNetwork" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #center>
|
|
||||||
<div class="min-w-80 mr-20">
|
|
||||||
<Dropdown v-model="networkStore.curNetwork" :options="networkStore.networkList"
|
|
||||||
:optionLabel="networkMenuName" :placeholder="$t('select_network')" :highlightOnSelect="true"
|
|
||||||
:checkmark="true" class="w-full md:w-32rem" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #end>
|
|
||||||
<Button icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" @click="toggle_setting_menu"
|
|
||||||
:label="$t('settings')" aria-controls="overlay_setting_menu" />
|
|
||||||
<Menu ref="setting_menu" id="overlay_setting_menu" :model="setting_menu_items" :popup="true" />
|
|
||||||
</template>
|
|
||||||
</Toolbar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Stepper class="h-full overflow-y-auto" :active-step="activeStep">
|
|
||||||
<StepperPanel :header="$t('config_network')" class="w">
|
|
||||||
<template #content="{ nextCallback }">
|
|
||||||
<Config @run-network="runNetworkCb($event, nextCallback)" :instance-id="networkStore.curNetworkId"
|
|
||||||
:config-invalid="messageBarSeverity != Severity.None" />
|
|
||||||
</template>
|
|
||||||
</StepperPanel>
|
|
||||||
<StepperPanel :header="$t('running')">
|
|
||||||
<template #content="{ prevCallback }">
|
|
||||||
<div class="flex flex-column">
|
|
||||||
<Status :instance-id="networkStore.curNetworkId" />
|
|
||||||
</div>
|
|
||||||
<div class="flex pt-4 justify-content-center">
|
|
||||||
<Button :label="$t('stop_network')" severity="danger" icon="pi pi-arrow-left"
|
|
||||||
@click="stopNetworkCb(networkStore.curNetwork, prevCallback)" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</StepperPanel>
|
|
||||||
</Stepper>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Menubar :model="items" breakpoint="300px">
|
|
||||||
</Menubar>
|
|
||||||
<InlineMessage v-if="messageBarSeverity !== Severity.None" class="absolute bottom-0 right-0" severity="error">
|
|
||||||
{{ messageBarContent }}</InlineMessage>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
#root {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-menubar .p-menuitem {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
.p-tabview-panel {
|
|
||||||
height: 100%;
|
|
||||||
} */
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
</script>
|
|
||||||
Vendored
+254
@@ -0,0 +1,254 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
|
const collectNetworkInfos: typeof import('./composables/network')['collectNetworkInfos']
|
||||||
|
const computed: typeof import('vue')['computed']
|
||||||
|
const createApp: typeof import('vue')['createApp']
|
||||||
|
const createPinia: typeof import('pinia')['createPinia']
|
||||||
|
const customRef: typeof import('vue')['customRef']
|
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||||
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
|
const definePage: typeof import('unplugin-vue-router/runtime')['definePage']
|
||||||
|
const defineStore: typeof import('pinia')['defineStore']
|
||||||
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const h: typeof import('vue')['h']
|
||||||
|
const inject: typeof import('vue')['inject']
|
||||||
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const mapActions: typeof import('pinia')['mapActions']
|
||||||
|
const mapGetters: typeof import('pinia')['mapGetters']
|
||||||
|
const mapState: typeof import('pinia')['mapState']
|
||||||
|
const mapStores: typeof import('pinia')['mapStores']
|
||||||
|
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||||
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
|
const onBeforeRouteLeave: typeof import('vue-router/auto')['onBeforeRouteLeave']
|
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router/auto')['onBeforeRouteUpdate']
|
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
|
const onMounted: typeof import('vue')['onMounted']
|
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
|
const parseNetworkConfig: typeof import('./composables/network')['parseNetworkConfig']
|
||||||
|
const provide: typeof import('vue')['provide']
|
||||||
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
const readonly: typeof import('vue')['readonly']
|
||||||
|
const ref: typeof import('vue')['ref']
|
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
|
const retainNetworkInstance: typeof import('./composables/network')['retainNetworkInstance']
|
||||||
|
const runNetworkInstance: typeof import('./composables/network')['runNetworkInstance']
|
||||||
|
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||||
|
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
|
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||||
|
const toRaw: typeof import('vue')['toRaw']
|
||||||
|
const toRef: typeof import('vue')['toRef']
|
||||||
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
|
const toValue: typeof import('vue')['toValue']
|
||||||
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
|
const unref: typeof import('vue')['unref']
|
||||||
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
|
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||||
|
const useLink: typeof import('vue-router/auto')['useLink']
|
||||||
|
const useNetworkStore: typeof import('./stores/network')['useNetworkStore']
|
||||||
|
const useRoute: typeof import('vue-router/auto')['useRoute']
|
||||||
|
const useRouter: typeof import('vue-router/auto')['useRouter']
|
||||||
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
|
const watch: typeof import('vue')['watch']
|
||||||
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
}
|
||||||
|
// for vue template auto import
|
||||||
|
import { UnwrapRef } from 'vue'
|
||||||
|
declare module 'vue' {
|
||||||
|
interface GlobalComponents {}
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||||
|
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||||
|
readonly collectNetworkInfos: UnwrapRef<typeof import('./composables/network')['collectNetworkInfos']>
|
||||||
|
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||||
|
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||||
|
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||||
|
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||||
|
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||||
|
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||||
|
readonly definePage: UnwrapRef<typeof import('unplugin-vue-router/runtime')['definePage']>
|
||||||
|
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
||||||
|
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||||
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||||
|
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||||
|
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||||
|
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||||
|
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||||
|
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
|
||||||
|
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||||
|
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||||
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
|
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router/auto')['onBeforeRouteLeave']>
|
||||||
|
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router/auto')['onBeforeRouteUpdate']>
|
||||||
|
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||||
|
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||||
|
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||||
|
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||||
|
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||||
|
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||||
|
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||||
|
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||||
|
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||||
|
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||||
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
|
readonly parseNetworkConfig: UnwrapRef<typeof import('./composables/network')['parseNetworkConfig']>
|
||||||
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
|
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||||
|
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||||
|
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||||
|
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
|
||||||
|
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
|
||||||
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
|
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||||
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
|
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||||
|
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
|
||||||
|
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||||
|
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||||
|
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||||
|
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||||
|
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||||
|
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||||
|
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||||
|
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||||
|
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||||
|
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
|
||||||
|
readonly useLink: UnwrapRef<typeof import('vue-router/auto')['useLink']>
|
||||||
|
readonly useNetworkStore: UnwrapRef<typeof import('./stores/network')['useNetworkStore']>
|
||||||
|
readonly useRoute: UnwrapRef<typeof import('vue-router/auto')['useRoute']>
|
||||||
|
readonly useRouter: UnwrapRef<typeof import('vue-router/auto')['useRouter']>
|
||||||
|
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||||
|
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||||
|
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||||
|
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||||
|
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
interface GlobalComponents {}
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||||
|
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||||
|
readonly collectNetworkInfos: UnwrapRef<typeof import('./composables/network')['collectNetworkInfos']>
|
||||||
|
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||||
|
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||||
|
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||||
|
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||||
|
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||||
|
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||||
|
readonly definePage: UnwrapRef<typeof import('unplugin-vue-router/runtime')['definePage']>
|
||||||
|
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
||||||
|
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||||
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||||
|
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||||
|
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||||
|
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||||
|
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||||
|
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
|
||||||
|
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||||
|
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||||
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
|
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router/auto')['onBeforeRouteLeave']>
|
||||||
|
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router/auto')['onBeforeRouteUpdate']>
|
||||||
|
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||||
|
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||||
|
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||||
|
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||||
|
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||||
|
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||||
|
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||||
|
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||||
|
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||||
|
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||||
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
|
readonly parseNetworkConfig: UnwrapRef<typeof import('./composables/network')['parseNetworkConfig']>
|
||||||
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
|
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||||
|
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||||
|
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||||
|
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
|
||||||
|
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
|
||||||
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
|
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||||
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
|
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||||
|
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
|
||||||
|
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||||
|
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||||
|
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||||
|
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||||
|
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||||
|
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||||
|
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||||
|
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||||
|
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||||
|
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
|
||||||
|
readonly useLink: UnwrapRef<typeof import('vue-router/auto')['useLink']>
|
||||||
|
readonly useNetworkStore: UnwrapRef<typeof import('./stores/network')['useNetworkStore']>
|
||||||
|
readonly useRoute: UnwrapRef<typeof import('vue-router/auto')['useRoute']>
|
||||||
|
readonly useRouter: UnwrapRef<typeof import('vue-router/auto')['useRouter']>
|
||||||
|
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||||
|
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||||
|
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||||
|
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||||
|
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import InputGroup from "primevue/inputgroup";
|
import InputGroup from 'primevue/inputgroup'
|
||||||
import InputGroupAddon from "primevue/inputgroupaddon";
|
import InputGroupAddon from 'primevue/inputgroupaddon'
|
||||||
import { ref, defineProps, computed } from "vue";
|
import { i18n } from '~/modules/i18n'
|
||||||
import { i18n, useNetworkStore, NetworkingMethod } from "../main";
|
import { NetworkingMethod } from '~/types/network'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
configInvalid?: boolean
|
||||||
|
instanceId?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits(['runNetwork'])
|
||||||
|
|
||||||
const networking_methods = ref([
|
const networking_methods = ref([
|
||||||
{ value: NetworkingMethod.PublicServer, label: i18n.global.t('public_server') },
|
{ value: NetworkingMethod.PublicServer, label: i18n.global.t('public_server') },
|
||||||
{ value: NetworkingMethod.Manual, label: i18n.global.t('manual') },
|
{ value: NetworkingMethod.Manual, label: i18n.global.t('manual') },
|
||||||
{ value: NetworkingMethod.Standalone, label: i18n.global.t('standalone') },
|
{ value: NetworkingMethod.Standalone, label: i18n.global.t('standalone') },
|
||||||
]);
|
])
|
||||||
|
|
||||||
const props = defineProps<{
|
const networkStore = useNetworkStore()
|
||||||
configInvalid?: boolean,
|
|
||||||
instanceId?: string,
|
|
||||||
}>()
|
|
||||||
|
|
||||||
defineEmits(["runNetwork"]);
|
|
||||||
|
|
||||||
const networkStore = useNetworkStore();
|
|
||||||
const curNetwork = computed(() => {
|
const curNetwork = computed(() => {
|
||||||
if (props.instanceId) {
|
if (props.instanceId) {
|
||||||
console.log("instanceId", props.instanceId);
|
// console.log('instanceId', props.instanceId)
|
||||||
const c = networkStore.networkList.find(n => n.instance_id == props.instanceId);
|
const c = networkStore.networkList.find(n => n.instance_id === props.instanceId)
|
||||||
if (c != undefined) {
|
if (c !== undefined)
|
||||||
return c;
|
return c
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return networkStore.curNetwork;
|
return networkStore.curNetwork
|
||||||
});
|
})
|
||||||
|
|
||||||
const presetPublicServers = [
|
const presetPublicServers = [
|
||||||
"tcp://easytier.public.kkrainbow.top:11010",
|
'tcp://easytier.public.kkrainbow.top:11010',
|
||||||
];
|
]
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -42,9 +39,7 @@ const presetPublicServers = [
|
|||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<div class="w-10/12 max-w-fit self-center ">
|
<div class="w-10/12 max-w-fit self-center ">
|
||||||
<Panel header="Basic Settings">
|
<Panel header="Basic Settings">
|
||||||
|
|
||||||
<div class="flex flex-column gap-y-2">
|
<div class="flex flex-column gap-y-2">
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
<div class="flex flex-row gap-x-9 flex-wrap">
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
<div class="flex flex-column gap-2 basis-5/12 grow">
|
||||||
<label for="virtual_ip">{{ $t('virtual_ipv4') }}</label>
|
<label for="virtual_ip">{{ $t('virtual_ipv4') }}</label>
|
||||||
@@ -64,8 +59,10 @@ const presetPublicServers = [
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
<div class="flex flex-column gap-2 basis-5/12 grow">
|
||||||
<label for="network_secret">{{ $t('network_secret') }}</label>
|
<label for="network_secret">{{ $t('network_secret') }}</label>
|
||||||
<InputText id="network_secret" v-model="curNetwork.network_secret"
|
<InputText
|
||||||
aria-describedby=" network_secret-help" />
|
id="network_secret" v-model="curNetwork.network_secret"
|
||||||
|
aria-describedby=" network_secret-help"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -73,15 +70,21 @@ const presetPublicServers = [
|
|||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
<div class="flex flex-column gap-2 basis-5/12 grow">
|
||||||
<label for="nm">{{ $t('networking_method') }}</label>
|
<label for="nm">{{ $t('networking_method') }}</label>
|
||||||
<div class="items-center flex flex-row p-fluid gap-x-1">
|
<div class="items-center flex flex-row p-fluid gap-x-1">
|
||||||
<Dropdown v-model="curNetwork.networking_method" :options="networking_methods" optionLabel="label"
|
<Dropdown
|
||||||
optionValue="value" placeholder="Select Method" class="" />
|
v-model="curNetwork.networking_method" :options="networking_methods" option-label="label"
|
||||||
<Chips id="chips" v-model="curNetwork.peer_urls"
|
option-value="value" placeholder="Select Method" class=""
|
||||||
:placeholder="$t('chips_placeholder', ['tcp://8.8.8.8:11010'])" separator=" " class="grow"
|
/>
|
||||||
v-if="curNetwork.networking_method == NetworkingMethod.Manual" />
|
<Chips
|
||||||
|
v-if="curNetwork.networking_method === NetworkingMethod.Manual" id="chips"
|
||||||
|
v-model="curNetwork.peer_urls" :placeholder="$t('chips_placeholder', ['tcp://8.8.8.8:11010'])"
|
||||||
|
separator=" " class="grow"
|
||||||
|
/>
|
||||||
|
|
||||||
<Dropdown :editable="true" v-model="curNetwork.public_server_url" class="grow"
|
<Dropdown
|
||||||
|
v-if="curNetwork.networking_method === NetworkingMethod.PublicServer"
|
||||||
|
v-model="curNetwork.public_server_url" :editable="true" class="grow"
|
||||||
:options="presetPublicServers"
|
:options="presetPublicServers"
|
||||||
v-if="curNetwork.networking_method == NetworkingMethod.PublicServer" />
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,8 +92,10 @@ const presetPublicServers = [
|
|||||||
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
||||||
<div class="flex flex-column gap-2 grow p-fluid">
|
<div class="flex flex-column gap-2 grow p-fluid">
|
||||||
<label for="username">{{ $t('proxy_cidrs') }}</label>
|
<label for="username">{{ $t('proxy_cidrs') }}</label>
|
||||||
<Chips id="chips" v-model="curNetwork.proxy_cidrs"
|
<Chips
|
||||||
:placeholder="$t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full" />
|
id="chips" v-model="curNetwork.proxy_cidrs"
|
||||||
|
:placeholder="$t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -98,24 +103,29 @@ const presetPublicServers = [
|
|||||||
<div class="flex flex-column gap-2 grow">
|
<div class="flex flex-column gap-2 grow">
|
||||||
<label for="username">VPN Portal</label>
|
<label for="username">VPN Portal</label>
|
||||||
<div class="items-center flex flex-row gap-x-4">
|
<div class="items-center flex flex-row gap-x-4">
|
||||||
<ToggleButton onIcon="pi pi-check" offIcon="pi pi-times" v-model="curNetwork.enable_vpn_portal"
|
<ToggleButton
|
||||||
:onLabel="$t('off_text')" :offLabel="$t('on_text')" />
|
v-model="curNetwork.enable_vpn_portal" on-icon="pi pi-check" off-icon="pi pi-times"
|
||||||
<div class="grow" v-if="curNetwork.enable_vpn_portal">
|
:on-label="$t('off_text')" :off-label="$t('on_text')"
|
||||||
|
/>
|
||||||
|
<div v-if="curNetwork.enable_vpn_portal" class="grow">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputText :placeholder="$t('vpn_portal_client_network')"
|
<InputText
|
||||||
v-model="curNetwork.vpn_portal_client_network_addr" />
|
v-model="curNetwork.vpn_portal_client_network_addr"
|
||||||
|
:placeholder="$t('vpn_portal_client_network')"
|
||||||
|
/>
|
||||||
<InputGroupAddon>
|
<InputGroupAddon>
|
||||||
<span>/{{ curNetwork.vpn_portal_client_network_len }}</span>
|
<span>/{{ curNetwork.vpn_portal_client_network_len }}</span>
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
<InputNumber :placeholder="$t('vpn_portal_listen_port')" class="" v-if="curNetwork.enable_vpn_portal"
|
<InputNumber
|
||||||
:format="false" v-model="curNetwork.vpn_portal_listne_port" :min="0" :max="65535" />
|
v-if="curNetwork.enable_vpn_portal" v-model="curNetwork.vpn_portal_listne_port"
|
||||||
|
:placeholder="$t('vpn_portal_listen_port')" class="" :format="false" :min="0" :max="65535"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
@@ -125,17 +135,20 @@ const presetPublicServers = [
|
|||||||
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
||||||
<div class="flex flex-column gap-2 grow p-fluid">
|
<div class="flex flex-column gap-2 grow p-fluid">
|
||||||
<label for="listener_urls">{{ $t('listener_urls') }}</label>
|
<label for="listener_urls">{{ $t('listener_urls') }}</label>
|
||||||
<Chips id="listener_urls" v-model="curNetwork.listener_urls"
|
<Chips
|
||||||
:placeholder="$t('chips_placeholder', ['tcp://1.1.1.1:11010'])" separator=" " class="w-full" />
|
id="listener_urls" v-model="curNetwork.listener_urls"
|
||||||
|
:placeholder="$t('chips_placeholder', ['tcp://1.1.1.1:11010'])" separator=" " class="w-full"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
<div class="flex flex-row gap-x-9 flex-wrap">
|
||||||
<div class="flex flex-column gap-2 basis-5/12 grow">
|
<div class="flex flex-column gap-2 basis-5/12 grow">
|
||||||
<label for="rpc_port">{{ $t('rpc_port') }}</label>
|
<label for="rpc_port">{{ $t('rpc_port') }}</label>
|
||||||
<InputNumber id="rpc_port" v-model="curNetwork.rpc_port" aria-describedby="username-help"
|
<InputNumber
|
||||||
:format="false" :min="0" :max="65535" />
|
id="rpc_port" v-model="curNetwork.rpc_port" aria-describedby="username-help"
|
||||||
|
:format="false" :min="0" :max="65535"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -143,10 +156,11 @@ const presetPublicServers = [
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
|
|
||||||
<div class="flex pt-4 justify-content-center">
|
<div class="flex pt-4 justify-content-center">
|
||||||
<Button :label="$t('run_network')" icon="pi pi-arrow-right" iconPos="right" @click="$emit('runNetwork', curNetwork)"
|
<Button
|
||||||
:disabled="configInvalid" />
|
:label="$t('run_network')" icon="pi pi-arrow-right" icon-pos="right" :disabled="configInvalid"
|
||||||
|
@click="$emit('runNetwork', curNetwork)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,358 +1,363 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
||||||
import { useNetworkStore } from '../main';
|
|
||||||
|
|
||||||
const networkStore = useNetworkStore();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
instanceId?: string,
|
instanceId?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const networkStore = useNetworkStore()
|
||||||
|
|
||||||
const curNetwork = computed(() => {
|
const curNetwork = computed(() => {
|
||||||
if (props.instanceId) {
|
if (props.instanceId) {
|
||||||
console.log("instanceId", props.instanceId);
|
// console.log('instanceId', props.instanceId)
|
||||||
const c = networkStore.networkList.find(n => n.instance_id == props.instanceId);
|
const c = networkStore.networkList.find(n => n.instance_id === props.instanceId)
|
||||||
if (c != undefined) {
|
if (c !== undefined)
|
||||||
return c;
|
return c
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return networkStore.curNetwork;
|
return networkStore.curNetwork
|
||||||
});
|
})
|
||||||
|
|
||||||
let curNetworkInst = computed(() => {
|
const curNetworkInst = computed(() => {
|
||||||
return networkStore.networkInstances.find(n => n.instance_id == curNetwork.value.instance_id);
|
return networkStore.networkInstances.find(n => n.instance_id === curNetwork.value.instance_id)
|
||||||
});
|
})
|
||||||
|
|
||||||
let peerRouteInfos = computed(() => {
|
const peerRouteInfos = computed(() => {
|
||||||
if (curNetworkInst.value) {
|
if (curNetworkInst.value)
|
||||||
return curNetworkInst.value.detail.peer_route_pairs;
|
return curNetworkInst.value.detail.peer_route_pairs
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
let routeCost = (info: any) => {
|
return []
|
||||||
if (info.route) {
|
})
|
||||||
const cost = info.route.cost;
|
|
||||||
return cost == 1 ? "p2p" : `relay(${cost})`
|
|
||||||
}
|
|
||||||
return '?';
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveObjPath(path: string, obj = self, separator = '.') {
|
function routeCost(info: any) {
|
||||||
var properties = Array.isArray(path) ? path : path.split(separator)
|
if (info.route) {
|
||||||
return properties.reduce((prev, curr) => prev?.[curr], obj)
|
const cost = info.route.cost
|
||||||
|
return cost === 1 ? 'p2p' : `relay(${cost})`
|
||||||
|
}
|
||||||
|
return '?'
|
||||||
}
|
}
|
||||||
|
|
||||||
let statsCommon = (info: any, field: string) => {
|
function resolveObjPath(path: string, obj = globalThis, separator = '.') {
|
||||||
if (!info.peer) {
|
const properties = Array.isArray(path) ? path : path.split(separator)
|
||||||
return undefined;
|
return properties.reduce((prev, curr) => prev?.[curr], obj)
|
||||||
}
|
}
|
||||||
let conns = info.peer.conns;
|
|
||||||
return conns.reduce((acc: number, conn: any) => {
|
function statsCommon(info: any, field: string) {
|
||||||
return acc + resolveObjPath(field, conn);
|
if (!info.peer)
|
||||||
}, 0);
|
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) {
|
function humanFileSize(bytes: number, si = false, dp = 1) {
|
||||||
const thresh = si ? 1000 : 1024;
|
const thresh = si ? 1000 : 1024
|
||||||
|
|
||||||
if (Math.abs(bytes) < thresh) {
|
if (Math.abs(bytes) < thresh)
|
||||||
return bytes + ' B';
|
return `${bytes} B`
|
||||||
}
|
|
||||||
|
|
||||||
const units = si
|
const units = si
|
||||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||||
let u = -1;
|
let u = -1
|
||||||
const r = 10 ** dp;
|
const r = 10 ** dp
|
||||||
|
|
||||||
do {
|
do {
|
||||||
bytes /= thresh;
|
bytes /= thresh
|
||||||
++u;
|
++u
|
||||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)
|
||||||
|
|
||||||
|
return `${bytes.toFixed(dp)} ${units[u]}`
|
||||||
return bytes.toFixed(dp) + ' ' + units[u];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let latencyMs = (info: any) => {
|
function latencyMs(info: any) {
|
||||||
let lat_us_sum = statsCommon(info, 'stats.latency_us');
|
const lat_us_sum = statsCommon(info, 'stats.latency_us')
|
||||||
return lat_us_sum ? `${lat_us_sum / 1000 / info.peer.conns.length}ms` : '';
|
return lat_us_sum ? `${lat_us_sum / 1000 / info.peer.conns.length}ms` : ''
|
||||||
};
|
|
||||||
|
|
||||||
let txBytes = (info: any) => {
|
|
||||||
let tx = statsCommon(info, 'stats.tx_bytes');
|
|
||||||
return tx ? humanFileSize(tx) : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let rxBytes = (info: any) => {
|
function txBytes(info: any) {
|
||||||
let rx = statsCommon(info, 'stats.rx_bytes');
|
const tx = statsCommon(info, 'stats.tx_bytes')
|
||||||
return rx ? humanFileSize(rx) : '';
|
return tx ? humanFileSize(tx) : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
let lossRate = (info: any) => {
|
function rxBytes(info: any) {
|
||||||
let lossRate = statsCommon(info, 'loss_rate');
|
const rx = statsCommon(info, 'stats.rx_bytes')
|
||||||
return lossRate != undefined ? `${Math.round(lossRate * 100)}%` : '';
|
return rx ? humanFileSize(rx) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function lossRate(info: any) {
|
||||||
|
const lossRate = statsCommon(info, 'loss_rate')
|
||||||
|
return lossRate !== undefined ? `${Math.round(lossRate * 100)}%` : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const myNodeInfo = computed(() => {
|
const myNodeInfo = computed(() => {
|
||||||
if (!curNetworkInst.value) {
|
if (!curNetworkInst.value)
|
||||||
return {};
|
return {}
|
||||||
}
|
|
||||||
return curNetworkInst.value.detail?.my_node_info;
|
return curNetworkInst.value.detail?.my_node_info
|
||||||
});
|
})
|
||||||
|
|
||||||
interface Chip {
|
interface Chip {
|
||||||
label: string;
|
label: string
|
||||||
icon: string;
|
icon: string
|
||||||
}
|
}
|
||||||
|
|
||||||
let myNodeInfoChips = computed(() => {
|
const myNodeInfoChips = computed(() => {
|
||||||
if (!curNetworkInst.value) {
|
if (!curNetworkInst.value)
|
||||||
return [];
|
return []
|
||||||
|
|
||||||
|
const chips: Array<Chip> = []
|
||||||
|
const my_node_info = curNetworkInst.value.detail?.my_node_info
|
||||||
|
if (!my_node_info)
|
||||||
|
return chips
|
||||||
|
|
||||||
|
// 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}: ${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}: ${ip}`,
|
||||||
|
icon: '',
|
||||||
|
} as Chip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// public ip
|
||||||
|
const public_ip = my_node_info.ips?.public_ipv4
|
||||||
|
if (public_ip) {
|
||||||
|
chips.push({
|
||||||
|
label: `Public IP: ${public_ip}`,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
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',
|
||||||
}
|
}
|
||||||
|
|
||||||
let chips: Array<Chip> = [];
|
chips.push({
|
||||||
let my_node_info = curNetworkInst.value.detail?.my_node_info;
|
label: `UDP NAT Type: ${udpNatTypeStrMap[udpNatType]}`,
|
||||||
if (!my_node_info) {
|
icon: '',
|
||||||
return chips;
|
} as Chip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// local ipv4s
|
return chips
|
||||||
let local_ipv4s = my_node_info.ips?.interface_ipv4s;
|
})
|
||||||
for (let [idx, ip] of local_ipv4s?.entries()) {
|
|
||||||
chips.push({
|
|
||||||
label: `Local IPv4 ${idx}: ${ip}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip);
|
|
||||||
}
|
|
||||||
|
|
||||||
// local ipv6s
|
function globalSumCommon(field: string) {
|
||||||
let local_ipv6s = my_node_info.ips?.interface_ipv6s;
|
let sum = 0
|
||||||
for (let [idx, ip] of local_ipv6s?.entries()) {
|
if (!peerRouteInfos.value)
|
||||||
chips.push({
|
return sum
|
||||||
label: `Local IPv6 ${idx}: ${ip}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip);
|
|
||||||
}
|
|
||||||
|
|
||||||
// public ip
|
for (const info of peerRouteInfos.value) {
|
||||||
let public_ip = my_node_info.ips?.public_ipv4;
|
const tx = statsCommon(info, field)
|
||||||
if (public_ip) {
|
if (tx)
|
||||||
chips.push({
|
sum += tx
|
||||||
label: `Public IP: ${public_ip}`,
|
}
|
||||||
icon: '',
|
return sum
|
||||||
} as Chip);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// listeners:
|
|
||||||
let listeners = my_node_info.listeners;
|
|
||||||
for (let [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,
|
|
||||||
};
|
|
||||||
let udpNatType: NatType = my_node_info.stun_info?.udp_nat_type;
|
|
||||||
if (udpNatType != undefined) {
|
|
||||||
let 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',
|
|
||||||
};
|
|
||||||
|
|
||||||
chips.push({
|
|
||||||
label: `UDP NAT Type: ${udpNatTypeStrMap[udpNatType]}`,
|
|
||||||
icon: '',
|
|
||||||
} as Chip);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return chips;
|
|
||||||
});
|
|
||||||
|
|
||||||
const globalSumCommon = (field: string) => {
|
|
||||||
let sum = 0;
|
|
||||||
if (!peerRouteInfos.value) {
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
for (let info of peerRouteInfos.value) {
|
|
||||||
let tx = statsCommon(info, field);
|
|
||||||
if (tx) {
|
|
||||||
sum += tx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
};
|
|
||||||
|
|
||||||
const txGlobalSum = () => {
|
|
||||||
return globalSumCommon('stats.tx_bytes');
|
|
||||||
};
|
|
||||||
|
|
||||||
const rxGlobalSum = () => {
|
|
||||||
return globalSumCommon('stats.rx_bytes');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function txGlobalSum() {
|
||||||
|
return globalSumCommon('stats.tx_bytes')
|
||||||
|
}
|
||||||
|
|
||||||
|
function rxGlobalSum() {
|
||||||
|
return globalSumCommon('stats.rx_bytes')
|
||||||
|
}
|
||||||
|
|
||||||
const peerCount = computed(() => {
|
const peerCount = computed(() => {
|
||||||
if (!peerRouteInfos.value) {
|
if (!peerRouteInfos.value)
|
||||||
return 0;
|
return 0
|
||||||
}
|
|
||||||
return peerRouteInfos.value.length;
|
return peerRouteInfos.value.length
|
||||||
});
|
})
|
||||||
|
|
||||||
// calculate tx/rx rate every 2 seconds
|
// calculate tx/rx rate every 2 seconds
|
||||||
let rateIntervalId = 0;
|
let rateIntervalId = 0
|
||||||
let rateInterval = 2000;
|
const rateInterval = 2000
|
||||||
let prevTxSum = 0;
|
let prevTxSum = 0
|
||||||
let prevRxSum = 0;
|
let prevRxSum = 0
|
||||||
let txRate = ref('0');
|
const txRate = ref('0')
|
||||||
let rxRate = ref('0');
|
const rxRate = ref('0')
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
rateIntervalId = setInterval(() => {
|
rateIntervalId = window.setInterval(() => {
|
||||||
let curTxSum = txGlobalSum();
|
const curTxSum = txGlobalSum()
|
||||||
txRate.value = humanFileSize((curTxSum - prevTxSum) / (rateInterval / 1000));
|
txRate.value = humanFileSize((curTxSum - prevTxSum) / (rateInterval / 1000))
|
||||||
prevTxSum = curTxSum;
|
prevTxSum = curTxSum
|
||||||
|
|
||||||
let curRxSum = rxGlobalSum();
|
const curRxSum = rxGlobalSum()
|
||||||
rxRate.value = humanFileSize((curRxSum - prevRxSum) / (rateInterval / 1000));
|
rxRate.value = humanFileSize((curRxSum - prevRxSum) / (rateInterval / 1000))
|
||||||
prevRxSum = curRxSum;
|
prevRxSum = curRxSum
|
||||||
}, rateInterval);
|
}, rateInterval)
|
||||||
});
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearInterval(rateIntervalId);
|
clearInterval(rateIntervalId)
|
||||||
});
|
})
|
||||||
|
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false)
|
||||||
const dialogContent = ref('');
|
const dialogContent = ref('')
|
||||||
|
|
||||||
const showVpnPortalConfig = () => {
|
function showVpnPortalConfig() {
|
||||||
let my_node_info = myNodeInfo.value;
|
const my_node_info = myNodeInfo.value
|
||||||
if (!my_node_info) {
|
if (!my_node_info)
|
||||||
return;
|
return
|
||||||
}
|
|
||||||
const url = "https://www.wireguardconfig.com/qrcode";
|
const url = 'https://www.wireguardconfig.com/qrcode'
|
||||||
dialogContent.value = `${my_node_info.vpn_portal_cfg}\n\n # can generate QR code: ${url}`;
|
dialogContent.value = `${my_node_info.vpn_portal_cfg}\n\n # can generate QR code: ${url}`
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const showEventLogs = () => {
|
function showEventLogs() {
|
||||||
let detail = curNetworkInst.value?.detail;
|
const detail = curNetworkInst.value?.detail
|
||||||
if (!detail) {
|
if (!detail)
|
||||||
return;
|
return
|
||||||
}
|
|
||||||
dialogContent.value = detail.events;
|
|
||||||
dialogVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
dialogContent.value = detail.events
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Dialog v-model:visible="dialogVisible" modal header="Dialog" :style="{ width: '70%' }">
|
<Dialog v-model:visible="dialogVisible" modal header="Dialog" :style="{ width: '70%' }">
|
||||||
<Panel>
|
<Panel>
|
||||||
<ScrollPanel style="width: 100%; height: 400px">
|
<ScrollPanel style="width: 100%; height: 400px">
|
||||||
<pre>{{ dialogContent }}</pre>
|
<pre>{{ dialogContent }}</pre>
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
</Panel>
|
</Panel>
|
||||||
<Divider />
|
<Divider />
|
||||||
<div class="flex justify-content-end gap-2">
|
<div class="flex justify-content-end gap-2">
|
||||||
<Button type="button" label="Close" @click="dialogVisible = false"></Button>
|
<Button type="button" label="Close" @click="dialogVisible = false" />
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<Card v-if="!curNetworkInst?.error_msg">
|
||||||
|
<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-36 h-36 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>
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
<Card v-if="curNetworkInst?.error_msg">
|
<div
|
||||||
<template #title>Run Network Error</template>
|
class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
|
||||||
<template #content>
|
style="border: 1px solid purple"
|
||||||
<div class="flex flex-column gap-y-5">
|
>
|
||||||
<div class="text-red-500">
|
<div class="font-bold">
|
||||||
{{ curNetworkInst.error_msg }}
|
{{ $t('upload') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="text-xl mt-2">
|
||||||
</template>
|
{{ txRate }}/s
|
||||||
</Card>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card v-if="!curNetworkInst?.error_msg">
|
<div
|
||||||
<template #title>{{ $t('my_node_info') }}</template>
|
class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
|
||||||
<template #content>
|
style="border: 1px solid fuchsia"
|
||||||
<div class="flex w-full flex-column gap-y-5">
|
>
|
||||||
<div class="m-0 flex flex-row justify-center gap-x-5">
|
<div class="font-bold">
|
||||||
<div class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
|
{{ $t('download') }}
|
||||||
style="border: 1px solid green">
|
</div>
|
||||||
<div class="font-bold">
|
<div class="text-xl mt-2">
|
||||||
{{ $t('peer_count') }}
|
{{ rxRate }}/s
|
||||||
</div>
|
</div>
|
||||||
<div class="text-5xl mt-1">{{ peerCount }}</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
|
<div class="flex flex-row align-items-center flex-wrap w-full">
|
||||||
style="border: 1px solid purple">
|
<Chip
|
||||||
<div class="font-bold">
|
v-for="(chip, i) in myNodeInfoChips" :key="i" :label="chip.label" :icon="chip.icon"
|
||||||
{{ $t('upload') }}
|
class="mr-2 mt-2"
|
||||||
</div>
|
/>
|
||||||
<div class="text-xl mt-2">{{ txRate }}/s</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
|
<div v-if="myNodeInfo" class="m-0 flex flex-row justify-center gap-x-5 text-sm">
|
||||||
style="border: 1px solid fuchsia">
|
<Button severity="info" :label="$t('show_vpn_portal_config')" @click="showVpnPortalConfig" />
|
||||||
<div class="font-bold">
|
<Button severity="info" :label="$t('show_event_log')" @click="showEventLogs" />
|
||||||
{{ $t('download') }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xl mt-2">{{ rxRate }}/s</div>
|
</template>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row align-items-center flex-wrap w-full">
|
<Divider />
|
||||||
<Chip v-for="chip in myNodeInfoChips" :label="chip.label" :icon="chip.icon" class="mr-2 mt-2">
|
|
||||||
</Chip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="m-0 flex flex-row justify-center gap-x-5 text-sm" v-if="myNodeInfo">
|
<Card v-if="!curNetworkInst?.error_msg">
|
||||||
<Button severity="info" :label="$t('show_vpn_portal_config')" @click="showVpnPortalConfig" />
|
<template #title>
|
||||||
<Button severity="info" :label="$t('show_event_log')" @click="showEventLogs" />
|
{{ $t('peer_info') }}
|
||||||
</div>
|
</template>
|
||||||
</div>
|
<template #content>
|
||||||
</template>
|
<DataTable :value="peerRouteInfos" table-style="min-width: 50rem">
|
||||||
</Card>
|
<Column field="route.ipv4_addr" :header="$t('virtual_ipv4')" />
|
||||||
|
<Column field="route.hostname" :header="$t('hostname')" />
|
||||||
<Divider />
|
<Column :field="routeCost" :header="$t('route_cost')" />
|
||||||
|
<Column :field="latencyMs" :header="$t('latency')" />
|
||||||
<Card v-if="!curNetworkInst?.error_msg">
|
<Column :field="txBytes" :header="$t('upload_bytes')" />
|
||||||
<template #title>{{ $t('peer_info') }}</template>
|
<Column :field="rxBytes" :header="$t('download_bytes')" />
|
||||||
<template #content>
|
<Column :field="lossRate" :header="$t('loss_rate')" />
|
||||||
<DataTable :value="peerRouteInfos" tableStyle="min-width: 50rem">
|
</DataTable>
|
||||||
<Column field="route.ipv4_addr" :header="$t('virtual_ipv4')"></Column>
|
</template>
|
||||||
<Column field="route.hostname" :header="$t('hostname')"></Column>
|
</Card>
|
||||||
<Column :field="routeCost" :header="$t('route_cost')"></Column>
|
</div>
|
||||||
<Column :field="latencyMs" :header="$t('latency')"></Column>
|
</template>
|
||||||
<Column :field="txBytes" :header="$t('upload_bytes')"></Column>
|
|
||||||
<Column :field="rxBytes" :header="$t('download_bytes')"></Column>
|
|
||||||
<Column :field="lossRate" :header="$t('loss_rate')"></Column>
|
|
||||||
</DataTable>
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/tauri'
|
||||||
|
import type { NetworkConfig } from '~/types/network'
|
||||||
|
|
||||||
|
export async function parseNetworkConfig(cfg: NetworkConfig): Promise<string> {
|
||||||
|
const ret: string = await invoke('parse_network_config', { cfg: JSON.stringify(cfg) })
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runNetworkInstance(cfg: NetworkConfig) {
|
||||||
|
const ret: string = await invoke('run_network_instance', { cfg: JSON.stringify(cfg) })
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function retainNetworkInstance(instanceIds: Array<string>) {
|
||||||
|
const ret: string = await invoke('retain_network_instance', { instanceIds: JSON.stringify(instanceIds) })
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function collectNetworkInfos() {
|
||||||
|
const ret: string = await invoke('collect_network_infos', {})
|
||||||
|
return JSON.parse(ret)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
+36
-351
@@ -1,362 +1,47 @@
|
|||||||
import "./styles.css";
|
import { setupLayouts } from 'virtual:generated-layouts'
|
||||||
import "primevue/resources/themes/aura-light-green/theme.css";
|
import { createRouter, createWebHistory } from 'vue-router/auto'
|
||||||
import "primeicons/primeicons.css";
|
|
||||||
import "primeflex/primeflex.css";
|
|
||||||
|
|
||||||
import { createPinia, defineStore } from 'pinia'
|
import PrimeVue from 'primevue/config'
|
||||||
|
import ToastService from 'primevue/toastservice'
|
||||||
|
import App from '~/App.vue'
|
||||||
|
|
||||||
import { createMemoryHistory, createRouter } from 'vue-router'
|
import '~/styles.css'
|
||||||
|
import 'primevue/resources/themes/aura-light-green/theme.css'
|
||||||
|
import 'primeicons/primeicons.css'
|
||||||
|
import 'primeflex/primeflex.css'
|
||||||
|
import { i18n, loadLanguageAsync } from '~/modules/i18n'
|
||||||
|
|
||||||
import { createApp } from "vue";
|
if (import.meta.env.PROD) {
|
||||||
import PrimeVue from 'primevue/config';
|
document.addEventListener('keydown', (event) => {
|
||||||
import App from "./App.vue";
|
if (
|
||||||
import { invoke } from "@tauri-apps/api/tauri";
|
event.key === 'F5'
|
||||||
|
|| (event.ctrlKey && event.key === 'r')
|
||||||
|
|| (event.metaKey && event.key === 'r')
|
||||||
|
)
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
document.addEventListener('contextmenu', (event) => {
|
||||||
|
event.preventDefault()
|
||||||
import ToastService from 'primevue/toastservice';
|
})
|
||||||
|
|
||||||
|
|
||||||
const pinia = createPinia()
|
|
||||||
|
|
||||||
export enum NetworkingMethod {
|
|
||||||
PublicServer = "PublicServer",
|
|
||||||
Manual = "Manual",
|
|
||||||
Standalone = "Standalone",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkConfig {
|
async function main() {
|
||||||
instance_id: string,
|
await loadLanguageAsync(localStorage.getItem('lang') || 'en')
|
||||||
|
|
||||||
virtual_ipv4: string
|
const app = createApp(App)
|
||||||
network_name: string
|
|
||||||
network_secret: string
|
|
||||||
|
|
||||||
networking_method: NetworkingMethod,
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
extendRoutes: routes => setupLayouts(routes),
|
||||||
|
})
|
||||||
|
|
||||||
public_server_url: string,
|
app.use(router)
|
||||||
peer_urls: Array<string>,
|
app.use(createPinia())
|
||||||
|
app.use(i18n, { useScope: 'global' })
|
||||||
proxy_cidrs: Array<string>,
|
app.use(PrimeVue)
|
||||||
|
app.use(ToastService)
|
||||||
enable_vpn_portal: boolean,
|
app.mount('#app')
|
||||||
vpn_portal_listne_port: number,
|
|
||||||
vpn_portal_client_network_addr: string,
|
|
||||||
vpn_portal_client_network_len: number,
|
|
||||||
|
|
||||||
advanced_settings: boolean,
|
|
||||||
|
|
||||||
listener_urls: Array<string>,
|
|
||||||
rpc_port: number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function default_network(): NetworkConfig {
|
main()
|
||||||
return {
|
|
||||||
instance_id: uuidv4(),
|
|
||||||
|
|
||||||
virtual_ipv4: "",
|
|
||||||
network_name: "default",
|
|
||||||
network_secret: "",
|
|
||||||
|
|
||||||
networking_method: NetworkingMethod.PublicServer,
|
|
||||||
|
|
||||||
public_server_url: "tcp://easytier.public.kkrainbow.top:11010",
|
|
||||||
peer_urls: [],
|
|
||||||
|
|
||||||
proxy_cidrs: [],
|
|
||||||
|
|
||||||
enable_vpn_portal: false,
|
|
||||||
vpn_portal_listne_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: 15888,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NetworkInstance {
|
|
||||||
instance_id: string,
|
|
||||||
|
|
||||||
running: boolean,
|
|
||||||
error_msg: string,
|
|
||||||
|
|
||||||
detail: any,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useNetworkStore = defineStore('network', {
|
|
||||||
state: () => {
|
|
||||||
const networkList = [default_network()];
|
|
||||||
return {
|
|
||||||
// for initially empty lists
|
|
||||||
networkList: networkList as NetworkConfig[],
|
|
||||||
// for data that is not yet loaded
|
|
||||||
curNetwork: networkList[0],
|
|
||||||
|
|
||||||
// uuid -> instance
|
|
||||||
instances: {} as Record<string, NetworkInstance>,
|
|
||||||
|
|
||||||
networkInfos: {} as Record<string, any>,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
lastNetwork(): NetworkConfig {
|
|
||||||
return this.networkList[this.networkList.length - 1];
|
|
||||||
},
|
|
||||||
|
|
||||||
curNetworkId(): string {
|
|
||||||
return this.curNetwork.instance_id;
|
|
||||||
},
|
|
||||||
|
|
||||||
networkInstances(): Array<NetworkInstance> {
|
|
||||||
return Object.values(this.instances);
|
|
||||||
},
|
|
||||||
|
|
||||||
networkInstanceIds(): Array<string> {
|
|
||||||
return Object.keys(this.instances);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
addNewNetwork() {
|
|
||||||
this.networkList.push(default_network());
|
|
||||||
},
|
|
||||||
|
|
||||||
delCurNetwork() {
|
|
||||||
const curNetworkIdx = this.networkList.indexOf(this.curNetwork);
|
|
||||||
this.networkList.splice(curNetworkIdx, 1);
|
|
||||||
const nextCurNetworkIdx = Math.min(curNetworkIdx, this.networkList.length - 1);
|
|
||||||
this.curNetwork = this.networkList[nextCurNetworkIdx];
|
|
||||||
},
|
|
||||||
|
|
||||||
removeNetworkInstance(instanceId: string) {
|
|
||||||
delete this.instances[instanceId];
|
|
||||||
},
|
|
||||||
|
|
||||||
addNetworkInstance(instanceId: string) {
|
|
||||||
this.instances[instanceId] = {
|
|
||||||
instance_id: instanceId,
|
|
||||||
running: false,
|
|
||||||
error_msg: "",
|
|
||||||
detail: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
updateWithNetworkInfos(networkInfos: Record<string, any>) {
|
|
||||||
this.networkInfos = networkInfos;
|
|
||||||
for (const [instanceId, info] of Object.entries(networkInfos)) {
|
|
||||||
if (this.instances[instanceId] === undefined) {
|
|
||||||
this.addNetworkInstance(instanceId);
|
|
||||||
}
|
|
||||||
this.instances[instanceId].running = info["running"];
|
|
||||||
this.instances[instanceId].error_msg = info["error_msg"];
|
|
||||||
this.instances[instanceId].detail = info;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadFromLocalStorage() {
|
|
||||||
const networkList = JSON.parse(localStorage.getItem("networkList") || '[]');
|
|
||||||
let result = [];
|
|
||||||
for (const cfg of networkList) {
|
|
||||||
result.push({
|
|
||||||
...default_network(),
|
|
||||||
...cfg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (result.length === 0) {
|
|
||||||
result.push(default_network());
|
|
||||||
}
|
|
||||||
this.networkList = result;
|
|
||||||
this.curNetwork = this.networkList[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
saveToLocalStroage() {
|
|
||||||
localStorage.setItem("networkList", JSON.stringify(this.networkList));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export async function parseNetworkConfig(cfg: NetworkConfig): Promise<string> {
|
|
||||||
const ret: string = await invoke("parse_network_config", { cfg: JSON.stringify(cfg) });
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runNetworkInstance(cfg: NetworkConfig) {
|
|
||||||
const ret: string = await invoke("run_network_instance", { cfg: JSON.stringify(cfg) });
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function retainNetworkInstance(instanceIds: Array<string>) {
|
|
||||||
const ret: string = await invoke("retain_network_instance", { instanceIds: JSON.stringify(instanceIds) });
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function collectNetworkInfos() {
|
|
||||||
const ret: string = await invoke("collect_network_infos", {});
|
|
||||||
return JSON.parse(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
import { createI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
const messages = {
|
|
||||||
en: {
|
|
||||||
"network": "Network",
|
|
||||||
"networking_method": "Networking Method",
|
|
||||||
"public_server": "Public Server",
|
|
||||||
"manual": "Manual",
|
|
||||||
"standalone": "Standalone",
|
|
||||||
"virtual_ipv4": "Virtual IPv4",
|
|
||||||
"network_name": "Network Name",
|
|
||||||
"network_secret": "Network Secret",
|
|
||||||
"public_server_url": "Public Server URL",
|
|
||||||
"peer_urls": "Peer URLs",
|
|
||||||
"proxy_cidrs": "Subnet Proxy CIDRs",
|
|
||||||
"enable_vpn_portal": "Enable VPN Portal",
|
|
||||||
"vpn_portal_listen_port": "VPN Portal Listen Port",
|
|
||||||
"vpn_portal_client_network": "Client Sub Network",
|
|
||||||
"advanced_settings": "Advanced Settings",
|
|
||||||
"listener_urls": "Listener URLs",
|
|
||||||
"rpc_port": "RPC Port",
|
|
||||||
"config_network": "Config Network",
|
|
||||||
"running": "Running",
|
|
||||||
"error_msg": "Error Message",
|
|
||||||
"detail": "Detail",
|
|
||||||
"add_new_network": "Add New Network",
|
|
||||||
"del_cur_network": "Delete Current Network",
|
|
||||||
"select_network": "Select Network",
|
|
||||||
"network_instances": "Network Instances",
|
|
||||||
"instance_id": "Instance ID",
|
|
||||||
"network_infos": "Network Infos",
|
|
||||||
"parse_network_config": "Parse Network Config",
|
|
||||||
"retain_network_instance": "Retain Network Instance",
|
|
||||||
"collect_network_infos": "Collect Network Infos",
|
|
||||||
"settings": "Settings",
|
|
||||||
"exchange_language": "切换中文",
|
|
||||||
"exit": "Exit",
|
|
||||||
|
|
||||||
"chips_placeholder": "e.g: {0}, press Enter to add",
|
|
||||||
"off_text": "Press to disable",
|
|
||||||
"on_text": "Press to enable",
|
|
||||||
|
|
||||||
"show_config": "Show Config",
|
|
||||||
"close": "Close",
|
|
||||||
|
|
||||||
"my_node_info": "My Node Info",
|
|
||||||
"peer_count": "Connected",
|
|
||||||
"upload": "Upload",
|
|
||||||
"download": "Download",
|
|
||||||
"show_vpn_portal_config": "Show VPN Portal Config",
|
|
||||||
"show_event_log": "Show Event Log",
|
|
||||||
"peer_info": "Peer Info",
|
|
||||||
"route_cost": "Route Cost",
|
|
||||||
"hostname": "Hostname",
|
|
||||||
"latency": "Latency",
|
|
||||||
"upload_bytes": "Upload",
|
|
||||||
"download_bytes": "Download",
|
|
||||||
"loss_rate": "Loss Rate",
|
|
||||||
|
|
||||||
"run_network": "Run Network",
|
|
||||||
"stop_network": "Stop Network",
|
|
||||||
},
|
|
||||||
cn: {
|
|
||||||
"network": "网络",
|
|
||||||
"networking_method": "网络方式",
|
|
||||||
"public_server": "公共服务器",
|
|
||||||
"manual": "手动",
|
|
||||||
"standalone": "独立",
|
|
||||||
"virtual_ipv4": "虚拟IPv4地址",
|
|
||||||
"network_name": "网络名称",
|
|
||||||
"network_secret": "网络密码",
|
|
||||||
"public_server_url": "公共服务器地址",
|
|
||||||
"peer_urls": "对等节点地址",
|
|
||||||
"proxy_cidrs": "子网代理CIDR",
|
|
||||||
"enable_vpn_portal": "启用VPN门户",
|
|
||||||
"vpn_portal_listen_port": "监听端口",
|
|
||||||
"vpn_portal_client_network": "客户端子网",
|
|
||||||
"advanced_settings": "高级设置",
|
|
||||||
"listener_urls": "监听地址",
|
|
||||||
"rpc_port": "RPC端口",
|
|
||||||
"config_network": "配置网络",
|
|
||||||
"running": "运行中",
|
|
||||||
"error_msg": "错误信息",
|
|
||||||
"detail": "详情",
|
|
||||||
"add_new_network": "添加新网络",
|
|
||||||
"del_cur_network": "删除当前网络",
|
|
||||||
"select_network": "选择网络",
|
|
||||||
"network_instances": "网络实例",
|
|
||||||
"instance_id": "实例ID",
|
|
||||||
"network_infos": "网络信息",
|
|
||||||
"parse_network_config": "解析网络配置",
|
|
||||||
"retain_network_instance": "保留网络实例",
|
|
||||||
"collect_network_infos": "收集网络信息",
|
|
||||||
"settings": "设置",
|
|
||||||
"exchange_language": "Switch to English",
|
|
||||||
"exit": "退出",
|
|
||||||
"chips_placeholder": "例如: {0}, 按回车添加",
|
|
||||||
"off_text": "点击关闭",
|
|
||||||
"on_text": "点击开启",
|
|
||||||
"show_config": "显示配置",
|
|
||||||
"close": "关闭",
|
|
||||||
|
|
||||||
"my_node_info": "当前节点信息",
|
|
||||||
"peer_count": "已连接",
|
|
||||||
"upload": "上传",
|
|
||||||
"download": "下载",
|
|
||||||
"show_vpn_portal_config": "显示VPN门户配置",
|
|
||||||
"show_event_log": "显示事件日志",
|
|
||||||
"peer_info": "节点信息",
|
|
||||||
"hostname": "主机名",
|
|
||||||
"route_cost": "路由",
|
|
||||||
"latency": "延迟",
|
|
||||||
"upload_bytes": "上传",
|
|
||||||
"download_bytes": "下载",
|
|
||||||
"loss_rate": "丢包率",
|
|
||||||
|
|
||||||
"run_network": "运行网络",
|
|
||||||
"stop_network": "停止网络",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveLocaleToLocalStorage(locale: string) {
|
|
||||||
localStorage.setItem("locale", locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadLocaleFromLocalStorage(): 'en' | 'cn' {
|
|
||||||
const v = localStorage.getItem("locale")
|
|
||||||
if (v === 'en' || v === 'cn') {
|
|
||||||
return v;
|
|
||||||
} else {
|
|
||||||
return 'en';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const i18n = createI18n({
|
|
||||||
legacy: false,
|
|
||||||
locale: 'en', // set locale
|
|
||||||
fallbackLocale: 'cn', // set fallback locale
|
|
||||||
messages,
|
|
||||||
})
|
|
||||||
|
|
||||||
export function changeLocale(locale: 'en' | 'cn') {
|
|
||||||
i18n.global.locale.value = locale;
|
|
||||||
saveLocaleToLocalStorage(locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = createApp(App);
|
|
||||||
app.use(i18n, { useScope: 'global' })
|
|
||||||
app.use(pinia)
|
|
||||||
app.use(PrimeVue);
|
|
||||||
app.use(ToastService);
|
|
||||||
app.mount("#app");
|
|
||||||
|
|
||||||
export const router = createRouter({
|
|
||||||
history: createMemoryHistory(),
|
|
||||||
routes: [{ path: "/", component: App }]
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Locale } from 'vue-i18n'
|
||||||
|
import { createI18n } 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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Stepper from 'primevue/stepper'
|
||||||
|
import StepperPanel from 'primevue/stepperpanel'
|
||||||
|
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
|
|
||||||
|
import { exit } from '@tauri-apps/api/process'
|
||||||
|
import Config from '~/components/Config.vue'
|
||||||
|
import Status from '~/components/Status.vue'
|
||||||
|
|
||||||
|
import type { NetworkConfig } from '~/types/network'
|
||||||
|
import { loadLanguageAsync } from '~/modules/i18n'
|
||||||
|
|
||||||
|
const { t, locale } = useI18n()
|
||||||
|
const visible = ref(false)
|
||||||
|
const tomlConfig = ref('')
|
||||||
|
|
||||||
|
const items = ref([
|
||||||
|
{
|
||||||
|
label: () => t('show_config'),
|
||||||
|
icon: 'pi pi-file-edit',
|
||||||
|
command: async () => {
|
||||||
|
try {
|
||||||
|
const ret = await parseNetworkConfig(networkStore.curNetwork)
|
||||||
|
tomlConfig.value = ret
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
tomlConfig.value = e
|
||||||
|
}
|
||||||
|
visible.value = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => t('del_cur_network'),
|
||||||
|
icon: 'pi pi-times',
|
||||||
|
command: async () => {
|
||||||
|
networkStore.removeNetworkInstance(networkStore.curNetwork.instance_id)
|
||||||
|
await retainNetworkInstance(networkStore.networkInstanceIds)
|
||||||
|
networkStore.delCurNetwork()
|
||||||
|
},
|
||||||
|
disabled: () => networkStore.networkList.length <= 1,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
enum Severity {
|
||||||
|
None = 'none',
|
||||||
|
Success = 'success',
|
||||||
|
Info = 'info',
|
||||||
|
Warn = 'warn',
|
||||||
|
Error = 'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageBarSeverity = ref(Severity.None)
|
||||||
|
const messageBarContent = ref('')
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const networkStore = useNetworkStore()
|
||||||
|
|
||||||
|
function addNewNetwork() {
|
||||||
|
networkStore.addNewNetwork()
|
||||||
|
networkStore.curNetwork = networkStore.lastNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkMenuName(network: NetworkConfig) {
|
||||||
|
return `${network.network_name} (${network.instance_id})`
|
||||||
|
}
|
||||||
|
|
||||||
|
networkStore.$subscribe(async () => {
|
||||||
|
networkStore.saveToLocalStorage()
|
||||||
|
try {
|
||||||
|
await parseNetworkConfig(networkStore.curNetwork)
|
||||||
|
messageBarSeverity.value = Severity.None
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
messageBarContent.value = e
|
||||||
|
messageBarSeverity.value = Severity.Error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function runNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) {
|
||||||
|
cb({} as MouseEvent)
|
||||||
|
networkStore.removeNetworkInstance(cfg.instance_id)
|
||||||
|
await retainNetworkInstance(networkStore.networkInstanceIds)
|
||||||
|
networkStore.addNetworkInstance(cfg.instance_id)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runNetworkInstance(cfg)
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
// console.error(e)
|
||||||
|
toast.add({ severity: 'info', detail: e })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) {
|
||||||
|
// console.log('stopNetworkCb', cfg, cb)
|
||||||
|
cb({} as MouseEvent)
|
||||||
|
networkStore.removeNetworkInstance(cfg.instance_id)
|
||||||
|
await retainNetworkInstance(networkStore.networkInstanceIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateNetworkInfos() {
|
||||||
|
networkStore.updateWithNetworkInfos(await collectNetworkInfos())
|
||||||
|
}
|
||||||
|
|
||||||
|
let intervalId = 0
|
||||||
|
onMounted(() => {
|
||||||
|
intervalId = window.setInterval(async () => {
|
||||||
|
await updateNetworkInfos()
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
onUnmounted(() => clearInterval(intervalId))
|
||||||
|
|
||||||
|
const curNetworkHasInstance = computed(() => {
|
||||||
|
return networkStore.networkInstanceIds.includes(networkStore.curNetworkId)
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeStep = computed(() => {
|
||||||
|
return curNetworkHasInstance.value ? 1 : 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const setting_menu = ref()
|
||||||
|
const setting_menu_items = ref([
|
||||||
|
{
|
||||||
|
label: () => t('settings'),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: () => t('exchange_language'),
|
||||||
|
icon: 'pi pi-language',
|
||||||
|
command: async () => {
|
||||||
|
await loadLanguageAsync((locale.value === 'en' ? 'cn' : 'en'))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => t('exit'),
|
||||||
|
icon: 'pi pi-times',
|
||||||
|
command: async () => {
|
||||||
|
await exit(1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
function toggle_setting_menu(event: any) {
|
||||||
|
setting_menu.value.toggle(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
networkStore.loadFromLocalStorage()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="root" class="flex flex-column">
|
||||||
|
<Dialog v-model:visible="visible" modal header="Config File" :style="{ width: '70%' }">
|
||||||
|
<Panel>
|
||||||
|
<ScrollPanel style="width: 100%; height: 300px">
|
||||||
|
<pre>{{ tomlConfig }}</pre>
|
||||||
|
</ScrollPanel>
|
||||||
|
</Panel>
|
||||||
|
<Divider />
|
||||||
|
<div class="flex justify-content-end gap-2">
|
||||||
|
<Button type="button" :label="$t('close')" @click="visible = false" />
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Toolbar>
|
||||||
|
<template #start>
|
||||||
|
<div class="flex align-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-plus" class="mr-2" severity="primary" :label="$t('add_new_network')"
|
||||||
|
@click="addNewNetwork"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #center>
|
||||||
|
<div class="min-w-80 mr-20">
|
||||||
|
<Dropdown
|
||||||
|
v-model="networkStore.curNetwork" :options="networkStore.networkList"
|
||||||
|
:option-label="networkMenuName" :placeholder="$t('select_network')" :highlight-on-select="true"
|
||||||
|
:checkmark="true" class="w-full md:w-32rem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #end>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" :label="$t('settings')"
|
||||||
|
aria-controls="overlay_setting_menu" @click="toggle_setting_menu"
|
||||||
|
/>
|
||||||
|
<Menu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
|
||||||
|
</template>
|
||||||
|
</Toolbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Stepper class="h-full overflow-y-auto" :active-step="activeStep">
|
||||||
|
<StepperPanel :header="$t('config_network')" class="w">
|
||||||
|
<template #content="{ nextCallback }">
|
||||||
|
<Config
|
||||||
|
:instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
|
||||||
|
@run-network="runNetworkCb($event, nextCallback)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</StepperPanel>
|
||||||
|
<StepperPanel :header="$t('running')">
|
||||||
|
<template #content="{ prevCallback }">
|
||||||
|
<div class="flex flex-column">
|
||||||
|
<Status :instance-id="networkStore.curNetworkId" />
|
||||||
|
</div>
|
||||||
|
<div class="flex pt-4 justify-content-center">
|
||||||
|
<Button
|
||||||
|
:label="$t('stop_network')" severity="danger" icon="pi pi-arrow-left"
|
||||||
|
@click="stopNetworkCb(networkStore.curNetwork, prevCallback)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</StepperPanel>
|
||||||
|
</Stepper>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Menubar :model="items" breakpoint="300px" />
|
||||||
|
<InlineMessage v-if="messageBarSeverity !== Severity.None" class="absolute bottom-0 right-0" severity="error">
|
||||||
|
{{ messageBarContent }}
|
||||||
|
</InlineMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#root {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-menubar .p-menuitem {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
.p-tabview-panel {
|
||||||
|
height: 100%;
|
||||||
|
} */
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import type { NetworkConfig, NetworkInstance } from '~/types/network'
|
||||||
|
import { DEFAULT_NETWORK_CONFIG } from '~/types/network'
|
||||||
|
|
||||||
|
export const useNetworkStore = defineStore('networkStore', {
|
||||||
|
state: () => {
|
||||||
|
const networkList = [DEFAULT_NETWORK_CONFIG()]
|
||||||
|
return {
|
||||||
|
// for initially empty lists
|
||||||
|
networkList: networkList as NetworkConfig[],
|
||||||
|
// for data that is not yet loaded
|
||||||
|
curNetwork: networkList[0],
|
||||||
|
|
||||||
|
// uuid -> instance
|
||||||
|
instances: {} as Record<string, NetworkInstance>,
|
||||||
|
|
||||||
|
networkInfos: {} as Record<string, any>,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
lastNetwork(): NetworkConfig {
|
||||||
|
return this.networkList[this.networkList.length - 1]
|
||||||
|
},
|
||||||
|
|
||||||
|
curNetworkId(): string {
|
||||||
|
return this.curNetwork.instance_id
|
||||||
|
},
|
||||||
|
|
||||||
|
networkInstances(): Array<NetworkInstance> {
|
||||||
|
return Object.values(this.instances)
|
||||||
|
},
|
||||||
|
|
||||||
|
networkInstanceIds(): Array<string> {
|
||||||
|
return Object.keys(this.instances)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
addNewNetwork() {
|
||||||
|
this.networkList.push(DEFAULT_NETWORK_CONFIG())
|
||||||
|
},
|
||||||
|
|
||||||
|
delCurNetwork() {
|
||||||
|
const curNetworkIdx = this.networkList.indexOf(this.curNetwork)
|
||||||
|
this.networkList.splice(curNetworkIdx, 1)
|
||||||
|
const nextCurNetworkIdx = Math.min(curNetworkIdx, this.networkList.length - 1)
|
||||||
|
this.curNetwork = this.networkList[nextCurNetworkIdx]
|
||||||
|
},
|
||||||
|
|
||||||
|
removeNetworkInstance(instanceId: string) {
|
||||||
|
delete this.instances[instanceId]
|
||||||
|
},
|
||||||
|
|
||||||
|
addNetworkInstance(instanceId: string) {
|
||||||
|
this.instances[instanceId] = {
|
||||||
|
instance_id: instanceId,
|
||||||
|
running: false,
|
||||||
|
error_msg: '',
|
||||||
|
detail: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWithNetworkInfos(networkInfos: Record<string, any>) {
|
||||||
|
this.networkInfos = networkInfos
|
||||||
|
for (const [instanceId, info] of Object.entries(networkInfos)) {
|
||||||
|
if (this.instances[instanceId] === undefined)
|
||||||
|
this.addNetworkInstance(instanceId)
|
||||||
|
|
||||||
|
this.instances[instanceId].running = info.running
|
||||||
|
this.instances[instanceId].error_msg = info.error_msg
|
||||||
|
this.instances[instanceId].detail = info
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadFromLocalStorage() {
|
||||||
|
const networkList = JSON.parse(localStorage.getItem('networkList') || '[]')
|
||||||
|
const result = []
|
||||||
|
for (const cfg of networkList) {
|
||||||
|
result.push({
|
||||||
|
...DEFAULT_NETWORK_CONFIG,
|
||||||
|
...cfg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (result.length === 0)
|
||||||
|
result.push(DEFAULT_NETWORK_CONFIG)
|
||||||
|
|
||||||
|
this.networkList = result
|
||||||
|
this.curNetwork = this.networkList[0]
|
||||||
|
},
|
||||||
|
|
||||||
|
saveToLocalStorage() {
|
||||||
|
localStorage.setItem('networkList', JSON.stringify(this.networkList))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot)
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useNetworkStore as any, import.meta.hot))
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
@layer tailwind-base, primevue, tailwind-utilities;
|
@layer tailwind-base, primevue, tailwind-utilities;
|
||||||
|
|
||||||
@layer tailwind-base {
|
@layer tailwind-base {
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer tailwind-utilities {
|
@layer tailwind-utilities {
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background: var(--surface-card);
|
background: var(--surface-card);
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
|
||||||
|
// It's recommended to commit this file.
|
||||||
|
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||||
|
|
||||||
|
declare module 'vue-router/auto-routes' {
|
||||||
|
import type {
|
||||||
|
RouteRecordInfo,
|
||||||
|
ParamValue,
|
||||||
|
ParamValueOneOrMore,
|
||||||
|
ParamValueZeroOrMore,
|
||||||
|
ParamValueZeroOrOne,
|
||||||
|
} from 'unplugin-vue-router/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route name map generated by unplugin-vue-router
|
||||||
|
*/
|
||||||
|
export interface RouteNamedMap {
|
||||||
|
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
export enum NetworkingMethod {
|
||||||
|
PublicServer = 'PublicServer',
|
||||||
|
Manual = 'Manual',
|
||||||
|
Standalone = 'Standalone',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetworkConfig {
|
||||||
|
instance_id: string
|
||||||
|
|
||||||
|
virtual_ipv4: string
|
||||||
|
network_name: string
|
||||||
|
network_secret: string
|
||||||
|
|
||||||
|
networking_method: NetworkingMethod
|
||||||
|
|
||||||
|
public_server_url: string
|
||||||
|
peer_urls: Array<string>
|
||||||
|
|
||||||
|
proxy_cidrs: Array<string>
|
||||||
|
|
||||||
|
enable_vpn_portal: boolean
|
||||||
|
vpn_portal_listne_port: number
|
||||||
|
vpn_portal_client_network_addr: string
|
||||||
|
vpn_portal_client_network_len: number
|
||||||
|
|
||||||
|
advanced_settings: boolean
|
||||||
|
|
||||||
|
listener_urls: Array<string>
|
||||||
|
rpc_port: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||||
|
return {
|
||||||
|
instance_id: uuidv4(),
|
||||||
|
|
||||||
|
virtual_ipv4: '',
|
||||||
|
network_name: 'default',
|
||||||
|
network_secret: '',
|
||||||
|
|
||||||
|
networking_method: NetworkingMethod.PublicServer,
|
||||||
|
|
||||||
|
public_server_url: 'tcp://easytier.public.kkrainbow.top:11010',
|
||||||
|
peer_urls: [],
|
||||||
|
|
||||||
|
proxy_cidrs: [],
|
||||||
|
|
||||||
|
enable_vpn_portal: false,
|
||||||
|
vpn_portal_listne_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: 15888,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetworkInstance {
|
||||||
|
instance_id: string
|
||||||
|
|
||||||
|
running: boolean
|
||||||
|
error_msg: string
|
||||||
|
|
||||||
|
detail: any
|
||||||
|
}
|
||||||
Vendored
+5
-4
@@ -1,7 +1,8 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
declare module "*.vue" {
|
declare module '*.vue' {
|
||||||
import type { DefineComponent } from "vue";
|
import type { DefineComponent } from 'vue'
|
||||||
const component: DefineComponent<{}, {}, any>;
|
|
||||||
export default component;
|
const component: DefineComponent<object, object, any>
|
||||||
|
export default component
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
'./index.html',
|
||||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-18
@@ -1,25 +1,45 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ESNext",
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"lib": [
|
||||||
/* Linting */
|
"DOM",
|
||||||
|
"ESNext"
|
||||||
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"~/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"types": [
|
||||||
|
"vite/client",
|
||||||
|
"vite-plugin-vue-layouts/client",
|
||||||
|
"unplugin-vue-macros/macros-global",
|
||||||
|
"unplugin-vue-router/client"
|
||||||
|
],
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"allowJs": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noEmit": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
"vueCompilerOptions": {
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"plugins": [
|
||||||
|
"@vue-macros/volar/define-models",
|
||||||
|
"@vue-macros/volar/define-slots"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
+77
-12
@@ -1,19 +1,84 @@
|
|||||||
import { defineConfig } from "vite";
|
import path from 'node:path'
|
||||||
import vue from "@vitejs/plugin-vue";
|
import { defineConfig } from 'vite'
|
||||||
|
import Vue from '@vitejs/plugin-vue'
|
||||||
import Components from 'unplugin-vue-components/vite';
|
import Layouts from 'vite-plugin-vue-layouts'
|
||||||
import { PrimeVueResolver } from 'unplugin-vue-components/resolvers';
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import VueMacros from 'unplugin-vue-macros/vite'
|
||||||
|
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
|
||||||
|
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
import VueRouter from 'unplugin-vue-router/vite'
|
||||||
|
import { VueRouterAutoImports } from 'unplugin-vue-router'
|
||||||
|
import { PrimeVueResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(async () => ({
|
export default defineConfig(async () => ({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~/': `${path.resolve(__dirname, 'src')}/`,
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
VueMacros({
|
||||||
|
plugins: {
|
||||||
|
vue: Vue({
|
||||||
|
include: [/\.vue$/, /\.md$/],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/posva/unplugin-vue-router
|
||||||
|
VueRouter({
|
||||||
|
extensions: ['.vue', '.md'],
|
||||||
|
dts: 'src/typed-router.d.ts',
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
|
||||||
|
Layouts(),
|
||||||
|
|
||||||
|
// https://github.com/antfu/unplugin-auto-import
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
'vue-i18n',
|
||||||
|
'pinia',
|
||||||
|
VueRouterAutoImports,
|
||||||
|
{
|
||||||
|
// add any other imports you were relying on
|
||||||
|
'vue-router/auto': ['useLink'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dts: 'src/auto-imports.d.ts',
|
||||||
|
dirs: [
|
||||||
|
'src/composables',
|
||||||
|
'src/stores',
|
||||||
|
],
|
||||||
|
vueTemplate: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/antfu/unplugin-vue-components
|
||||||
Components({
|
Components({
|
||||||
dts: true,
|
// allow auto load markdown components under `./src/components/`
|
||||||
|
extensions: ['vue', 'md'],
|
||||||
|
// allow auto import and register components used in markdown
|
||||||
|
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||||
|
dts: 'src/components.d.ts',
|
||||||
resolvers: [
|
resolvers: [
|
||||||
PrimeVueResolver()
|
PrimeVueResolver(),
|
||||||
]
|
],
|
||||||
})],
|
}),
|
||||||
|
|
||||||
|
// https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
|
||||||
|
VueI18n({
|
||||||
|
runtimeOnly: true,
|
||||||
|
compositionOnly: true,
|
||||||
|
fullInstall: true,
|
||||||
|
include: [path.resolve(__dirname, 'locales/**')],
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/webfansplz/vite-plugin-vue-devtools
|
||||||
|
VueDevTools(),
|
||||||
|
],
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
//
|
//
|
||||||
@@ -25,7 +90,7 @@ export default defineConfig(async () => ({
|
|||||||
strictPort: true,
|
strictPort: true,
|
||||||
watch: {
|
watch: {
|
||||||
// 3. tell vite to ignore watching `src-tauri`
|
// 3. tell vite to ignore watching `src-tauri`
|
||||||
ignored: ["**/src-tauri/**"],
|
ignored: ['**/src-tauri/**'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}))
|
||||||
|
|||||||
Reference in New Issue
Block a user