Compare commits

...
2 Commits
12 changed files with 277 additions and 119 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ export const updateConfigJson = async (configJsonSeverUrl: Array<string> | strin
proxyNetworks, proxyNetworks,
autoStart, autoStart,
relayAllPeerrpc, relayAllPeerrpc,
coonectAfterStart, connectAfterStart,
multiThread, multiThread,
enablExitNode, enablExitNode,
useSmoltcp, useSmoltcp,
+2 -1
View File
@@ -3,7 +3,7 @@
"private": true, "private": true,
"author": "leizi97", "author": "leizi97",
"description": "A simple network initiator based on Easytier", "description": "A simple network initiator based on Easytier",
"version": "1.2.0", "version": "1.2.1",
"scripts": { "scripts": {
"dev": "nuxt dev --dotenv env/.env.dev --host 0.0.0.0", "dev": "nuxt dev --dotenv env/.env.dev --host 0.0.0.0",
"build": "nuxt generate --dotenv env/.env.prod" "build": "nuxt generate --dotenv env/.env.prod"
@@ -21,6 +21,7 @@
"@tauri-apps/plugin-fs": "~2", "@tauri-apps/plugin-fs": "~2",
"@tauri-apps/plugin-http": "^2.0.1", "@tauri-apps/plugin-http": "^2.0.1",
"@tauri-apps/plugin-shell": "~2", "@tauri-apps/plugin-shell": "~2",
"@tauri-apps/plugin-window-state": "~2",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@vitejs/plugin-vue-jsx": "^4.1.0", "@vitejs/plugin-vue-jsx": "^4.1.0",
"@vueuse/core": "^11.2.0", "@vueuse/core": "^11.2.0",
+1 -1
View File
@@ -26,7 +26,7 @@
打开日志目录 打开日志目录
</ElButton> </ElButton>
</div> </div>
<div><ElCheckbox v-model="mainStore.config.coonectAfterStart">软件启动后自动"启动联机"(搭配开机自启无感联机)</ElCheckbox></div> <div><ElCheckbox v-model="mainStore.config.connectAfterStart">软件启动后自动"启动联机"(搭配开机自启无感联机)</ElCheckbox></div>
<div class="flex items-center gap-[15px] flex-nowrap"> <div class="flex items-center gap-[15px] flex-nowrap">
<ElCheckbox v-model="mainStore.createConfigInEasytier">自动生成界面配置文件easytier/config.json</ElCheckbox> <ElCheckbox v-model="mainStore.createConfigInEasytier">自动生成界面配置文件easytier/config.json</ElCheckbox>
<ElButton <ElButton
+142 -81
View File
@@ -2,7 +2,8 @@
<ElForm <ElForm
size="small" size="small"
label-position="top" label-position="top"
:model="config"> :model="config"
>
<!-- element-loading-custom-class="config-start" <!-- element-loading-custom-class="config-start"
v-loading="mainStore.configStartEnable" v-loading="mainStore.configStartEnable"
:element-loading-spinner="'<path />'" :element-loading-spinner="'<path />'"
@@ -10,24 +11,28 @@
<!-- <div> --> <!-- <div> -->
<ElFormItem <ElFormItem
label="服务器" label="服务器"
prop="serverUrl"> prop="serverUrl"
>
<template #label> <template #label>
<div class="flex items-center gap-[0_5px]"> <div class="flex items-center gap-[0_5px]">
<div>服务器</div> <div>服务器</div>
<span>-</span> <span>-</span>
<ElTag <ElTag
effect="dark" effect="dark"
:type="data.isSuccessGetIp ? 'success' : 'info'"> :type="data.isSuccessGetIp ? 'success' : 'info'"
>
{{ data.isSuccessGetIp ? "联机成功" : data.isStart && !data.isSuccessGetIp ? "联机中" : "未联机" }} {{ data.isSuccessGetIp ? "联机成功" : data.isStart && !data.isSuccessGetIp ? "联机中" : "未联机" }}
</ElTag> </ElTag>
<ElButton <ElButton
v-if="!data.coreVersion" v-if="!data.coreVersion"
@click="getCoreVersion(true)"> @click="getCoreVersion(true)"
>
获取内核版本 获取内核版本
</ElButton> </ElButton>
<ElTag <ElTag
v-else v-else
type="info"> type="info"
>
{{ data.coreVersion }} {{ data.coreVersion }}
</ElTag> </ElTag>
<ElPopconfirm <ElPopconfirm
@@ -36,13 +41,15 @@
confirm-button-text="继续" confirm-button-text="继续"
title="内核从github下载,需要出国工具,可能下载缓慢或失败,是否继续? title="内核从github下载,需要出国工具,可能下载缓慢或失败,是否继续?
也可以从官方群里手动下载后解压到easytier-game.exe同级目录下的easytier目录里,全部覆盖即可" 也可以从官方群里手动下载后解压到easytier-game.exe同级目录下的easytier目录里,全部覆盖即可"
@confirm="handleUpdateCore"> @confirm="handleUpdateCore"
>
<template #reference> <template #reference>
<ElButton <ElButton
:disabled="data.isStart" :disabled="data.isStart"
:loading="data.update" :loading="data.update"
type="warning" type="warning"
size="small"> size="small"
>
{{ data.coreVersion ? "更新内核" : "下载内核" }} {{ data.coreVersion ? "更新内核" : "下载内核" }}
</ElButton> </ElButton>
</template> </template>
@@ -55,7 +62,8 @@
placeholder="请选择服务器地址" placeholder="请选择服务器地址"
default-first-option default-first-option
v-model="config.serverUrl" v-model="config.serverUrl"
@change="handleServerUrlChange"> @change="handleServerUrlChange"
>
<template #prefix> <template #prefix>
<div :class="config.protocol && config.protocol.length > 1 ? 'w-[120px]' : 'w-[80px]'"> <div :class="config.protocol && config.protocol.length > 1 ? 'w-[120px]' : 'w-[80px]'">
<ElSelect <ElSelect
@@ -64,12 +72,14 @@
collapse-tags collapse-tags
@click.stop @click.stop
v-model="config.protocol" v-model="config.protocol"
@change="handleServerUrlChange"> @change="handleServerUrlChange"
>
<ElOption <ElOption
v-for="item in protocols" v-for="item in protocols"
:key="item" :key="item"
:label="item" :label="item"
:value="item"></ElOption> :value="item"
></ElOption>
</ElSelect> </ElSelect>
</div> </div>
</template> </template>
@@ -77,7 +87,8 @@
v-for="item in mainStore.basePeers" v-for="item in mainStore.basePeers"
:key="item" :key="item"
:label="item" :label="item"
:value="item"> :value="item"
>
<div class="flex items-center gap-[20px] overflow-hidden flex-nowrap max-w-[calc(100vw-62px)]"> <div class="flex items-center gap-[20px] overflow-hidden flex-nowrap max-w-[calc(100vw-62px)]">
<ElTooltip :content="item"> <ElTooltip :content="item">
<p class="truncate">{{ item }}</p> <p class="truncate">{{ item }}</p>
@@ -87,7 +98,8 @@
@click.stop="handleDeleteServerUrl(item)" @click.stop="handleDeleteServerUrl(item)"
round round
:icon="Delete" :icon="Delete"
type="danger"></ElButton> type="danger"
></ElButton>
</div> </div>
</div> </div>
</ElOption> </ElOption>
@@ -107,7 +119,8 @@
<ElInput <ElInput
maxlength="100" maxlength="100"
placeholder="请输入网络名" placeholder="请输入网络名"
v-model="config.networkName"></ElInput> v-model="config.networkName"
></ElInput>
</ElFormItem> </ElFormItem>
</div> </div>
<div class="flex-1"> <div class="flex-1">
@@ -125,7 +138,8 @@
maxlength="100" maxlength="100"
placeholder="请输入网络密码" placeholder="请输入网络密码"
v-model="config.networkPassword" v-model="config.networkPassword"
type="password"></ElInput> type="password"
></ElInput>
</ElFormItem> </ElFormItem>
</div> </div>
</div> </div>
@@ -142,11 +156,13 @@
<ElInput <ElInput
maxlength="100" maxlength="100"
placeholder="例如: Player1" placeholder="例如: Player1"
v-model="config.hostname"></ElInput> v-model="config.hostname"
></ElInput>
</ElFormItem> </ElFormItem>
<ElFormItem <ElFormItem
class="w-[70%]" class="w-[70%]"
label="局域网IP"> label="局域网IP"
>
<template #label> <template #label>
<div class="flex items-center h-[20px]"> <div class="flex items-center h-[20px]">
虚拟网IP 虚拟网IP
@@ -159,14 +175,16 @@
inline-prompt inline-prompt
inactive-text="固定IP" inactive-text="固定IP"
active-text="动态获取IP" active-text="动态获取IP"
size="small"></ElSwitch> size="small"
></ElSwitch>
</div> </div>
</template> </template>
<ElInput <ElInput
maxlength="100" maxlength="100"
:disabled="config.dhcp" :disabled="config.dhcp"
:placeholder="data.isStart && config.dhcp ? '等待动态分配IP...' : '例如: 10.126.126.1'" :placeholder="data.isStart && config.dhcp ? '等待动态分配IP...' : '例如: 10.126.126.1'"
v-model="config.ipv4"></ElInput> v-model="config.ipv4"
></ElInput>
</ElFormItem> </ElFormItem>
</div> </div>
<!-- </div> --> <!-- </div> -->
@@ -180,24 +198,28 @@
size="default" size="default"
:type="!data.isStart ? 'primary' : 'danger'" :type="!data.isStart ? 'primary' : 'danger'"
:disabled="data.startLoading || !data.coreVersion || data.update" :disabled="data.startLoading || !data.coreVersion || data.update"
@click="handleConnection"> @click="handleConnection"
>
{{ !data.isStart ? "启动联机" : "停止联机" }} {{ !data.isStart ? "启动联机" : "停止联机" }}
<template #dropdown> <template #dropdown>
<ElDropdownMenu> <ElDropdownMenu>
<ElDropdownItem <ElDropdownItem
command="import_config" command="import_config"
:icon="Link"> :icon="Link"
>
导入配置 导入配置
</ElDropdownItem> </ElDropdownItem>
<ElDropdownItem <ElDropdownItem
command="share_config" command="share_config"
:icon="Share"> :icon="Share"
>
分享配置 分享配置
</ElDropdownItem> </ElDropdownItem>
<ElDropdownItem <ElDropdownItem
:icon="Tools" :icon="Tools"
command="toml" command="toml"
:disabled="data.isStart"> :disabled="data.isStart"
>
使用外部配置文件 使用外部配置文件
</ElDropdownItem> </ElDropdownItem>
</ElDropdownMenu> </ElDropdownMenu>
@@ -207,47 +229,57 @@
<div class="mt-[6px] pl-[2px]"> <div class="mt-[6px] pl-[2px]">
<ElTooltip <ElTooltip
placement="left" placement="left"
content="日志"> content="日志"
>
<ElButton <ElButton
:type="!data.logVisible ? 'info' : 'warning'" :type="!data.logVisible ? 'info' : 'warning'"
@click="handleShowLogDialog" @click="handleShowLogDialog"
:icon="List" :icon="List"
size="small" size="small"
plain></ElButton> plain
></ElButton>
</ElTooltip> </ElTooltip>
<ElTooltip <ElTooltip
placement="left" placement="left"
content="成员"> content="成员"
>
<ElButton <ElButton
@click="handleShowMemberDialog" @click="handleShowMemberDialog"
:icon="UserFilled" :icon="UserFilled"
plain plain
type="success" type="success"
size="small"></ElButton> size="small"
></ElButton>
</ElTooltip> </ElTooltip>
</div> </div>
</div> </div>
<div class="ml-auto"> <div class="ml-auto">
<ElCheckbox <ElCheckbox
v-model="config.disbleP2p" v-model="config.disbleP2p"
size="small"> size="small"
>
强制中转 强制中转
</ElCheckbox> </ElCheckbox>
<ElCheckbox <ElTooltip placement="top-start" content="自启后隐藏于托盘,不显示界面">
@change="handleAutoStartByTask" <ElCheckbox
:model-value="config.autoStart" @change="handleAutoStartByTask"
size="small"> :model-value="config.autoStart"
开机自启 size="small"
</ElCheckbox> >
开机自启
</ElCheckbox>
</ElTooltip>
<div> <div>
<ElButton <ElButton
@click="handleShowCidrDialog" @click="handleShowCidrDialog"
:icon="Share"> :icon="Share"
>
子网代理 子网代理
</ElButton> </ElButton>
<ElButton <ElButton
@click="handleShowAdvanceDialog" @click="handleShowAdvanceDialog"
:icon="Setting"> :icon="Setting"
>
高级选项 高级选项
</ElButton> </ElButton>
</div> </div>
@@ -258,7 +290,8 @@
type="primary" type="primary"
size="small" size="small"
:icon="MagicStick" :icon="MagicStick"
@click="handleShowToolDialog"> @click="handleShowToolDialog"
>
增强工具 增强工具
</ElButton> </ElButton>
</div> </div>
@@ -266,7 +299,8 @@
class="!text-[9px] pb-[2px] ml-[8px] truncate" class="!text-[9px] pb-[2px] ml-[8px] truncate"
type="info" type="info"
:underline="false" :underline="false"
@click="open('https://github.com/EasyTier/EasytierGame')"> @click="open('https://github.com/EasyTier/EasytierGame')"
>
EasytierGame主页 EasytierGame主页
</ElLink> </ElLink>
</div> </div>
@@ -281,12 +315,14 @@
class="!mb-0" class="!mb-0"
v-model="importConfigData.visible" v-model="importConfigData.visible"
:close-on-press-escape="false" :close-on-press-escape="false"
title="导入分享"> title="导入分享"
>
<ElInput <ElInput
type="textarea" type="textarea"
:rows="6" :rows="6"
placeholder="请粘贴分享的配置" placeholder="请粘贴分享的配置"
v-model="importConfigData.data"></ElInput> v-model="importConfigData.data"
></ElInput>
<template #footer> <template #footer>
<div> <div>
<el-text type="danger">导入成功后您当前的部分配置将被替换</el-text> <el-text type="danger">导入成功后您当前的部分配置将被替换</el-text>
@@ -295,7 +331,8 @@
<ElButton <ElButton
size="small" size="small"
@click="handleStartImport" @click="handleStartImport"
type="primary"> type="primary"
>
导入 导入
</ElButton> </ElButton>
</div> </div>
@@ -309,7 +346,8 @@
class="!mb-0" class="!mb-0"
v-model="configStart.visible" v-model="configStart.visible"
:close-on-press-escape="false" :close-on-press-escape="false"
title="配置文件启动"> title="配置文件启动"
>
<div class="flex items-center gap-[0_4px]"> <div class="flex items-center gap-[0_4px]">
<span>启用</span> <span>启用</span>
<ElSwitch v-model="mainStore.configStartEnable"></ElSwitch> <ElSwitch v-model="mainStore.configStartEnable"></ElSwitch>
@@ -318,14 +356,16 @@
</ElTooltip> </ElTooltip>
<ElButton <ElButton
@click="openConfigDir" @click="openConfigDir"
size="small"> size="small"
>
打开配置目录 打开配置目录
</ElButton> </ElButton>
<ElButton <ElButton
@click="handleStartCommand('toml')" @click="handleStartCommand('toml')"
type="primary" type="primary"
:icon="RefreshRight" :icon="RefreshRight"
size="small"> size="small"
>
刷新 刷新
</ElButton> </ElButton>
</div> </div>
@@ -333,12 +373,14 @@
<ElSelect <ElSelect
v-model="mainStore.configPath" v-model="mainStore.configPath"
no-data-text="目录没有配置文件" no-data-text="目录没有配置文件"
placeholder="选择配置文件"> placeholder="选择配置文件"
>
<ElOption <ElOption
v-for="item in configStart.list" v-for="item in configStart.list"
:key="item.path" :key="item.path"
:value="item.path" :value="item.path"
:label="item.name"></ElOption> :label="item.name"
></ElOption>
</ElSelect> </ElSelect>
</div> </div>
<template #footer> <template #footer>
@@ -346,7 +388,8 @@
<ElButton <ElButton
size="small" size="small"
@click="configStart.visible = false" @click="configStart.visible = false"
type="danger"> type="danger"
>
关闭 关闭
</ElButton> </ElButton>
</div> </div>
@@ -385,7 +428,7 @@
const mainStore = useMainStore(); const mainStore = useMainStore();
const config = mainStore.config; const config = mainStore.config;
// console.log(config); // console.log(config);
const protocols = ["tcp", "udp", "ws", "wss", "wg"]; const protocols = ["tcp", "udp", "ws", "wss", "wg", "quic"];
const data = reactive({ const data = reactive({
logVisible: false, logVisible: false,
cidrVisible: false, cidrVisible: false,
@@ -402,25 +445,25 @@
startLoading: false, startLoading: false,
isStart: false, isStart: false,
connectionSuccess: false, connectionSuccess: false,
configJsonSeverUrl: "", // 本地保存一次,用于回填config.json configJsonSeverUrl: "" // 本地保存一次,用于回填config.json
}); });
const configStart = reactive<{ list: Array<{ path: string; name: string }>; [key: string]: any }>({ const configStart = reactive<{ list: Array<{ path: string; name: string }>; [key: string]: any }>({
visible: false, visible: false,
loading: false, loading: false,
list: [], //配置文件列表 list: [] //配置文件列表
}); });
const importConfigData = reactive<{ data: ""; [key: string]: any }>({ const importConfigData = reactive<{ data: ""; [key: string]: any }>({
visible: false, visible: false,
loading: false, loading: false,
data: "", data: ""
}); });
const closePrevent = async () => { const closePrevent = async () => {
const appWindow = getCurrentWindow(); const appWindow = getCurrentWindow();
if (appWindow.label == "main") { if (appWindow.label == "main") {
appWindow.onCloseRequested(async (event) => { appWindow.onCloseRequested(async event => {
// console.log(appWindow.label); // console.log(appWindow.label);
if (!is_close) { if (!is_close) {
// console.log(1); // console.log(1);
@@ -441,10 +484,24 @@
newBasePeers.splice(idx, 1); newBasePeers.splice(idx, 1);
} }
mainStore.basePeers = uniq([...newBasePeers]); mainStore.basePeers = uniq([...newBasePeers]);
if (mainStore.basePeers.length > 0) {
mainStore.config.serverUrl = mainStore.basePeers[0];
}
}; };
const handleServerUrlChange = () => { const handleServerUrlChange = () => {
mainStore.basePeers = uniq([config.serverUrl, ...mainStore.basePeers]); let inputProtocols: string | null = null;
for (const p of protocols) {
if (config.serverUrl.toLowerCase().startsWith(`${p}://`)) {
inputProtocols = p;
break;
}
}
if (inputProtocols) {
mainStore.config.protocol = [inputProtocols];
mainStore.config.serverUrl = mainStore.config.serverUrl.slice(inputProtocols.length + 3);
}
mainStore.basePeers = uniq([mainStore.config.serverUrl, ...mainStore.basePeers]);
}; };
const listenObj: { [key: string]: any } = { const listenObj: { [key: string]: any } = {
@@ -454,7 +511,7 @@
thread_id: null, thread_id: null,
async listenOutput() { async listenOutput() {
// const appWindow = getCurrentWindow(); // const appWindow = getCurrentWindow();
const unListen = await listen("command-output", async (event) => { const unListen = await listen("command-output", async event => {
data.isStart = true; data.isStart = true;
// console.log(event.payload); // console.log(event.payload);
if (event.payload) { if (event.payload) {
@@ -487,7 +544,7 @@
"netsh", "netsh",
["interface", "ipv4", "set", "interface", devName, "metric=", `${mainStore.config.netCardMetricValue}`], ["interface", "ipv4", "set", "interface", devName, "metric=", `${mainStore.config.netCardMetricValue}`],
{ {
encoding: "gb2312", encoding: "gb2312"
} }
).execute(); ).execute();
} catch (err) { } catch (err) {
@@ -504,7 +561,7 @@
this.unListenOutPut = unListen; this.unListenOutPut = unListen;
}, },
async listenThreadId() { async listenThreadId() {
const unListen = await listen("thread-id", (event) => { const unListen = await listen("thread-id", event => {
if (event.payload) { if (event.payload) {
this.thread_id = event.payload; this.thread_id = event.payload;
} }
@@ -512,14 +569,14 @@
this.unListenThreadId = unListen; this.unListenThreadId = unListen;
}, },
async listenConfigStart() { async listenConfigStart() {
const unListen = await listen("config", (event) => { const unListen = await listen("config", event => {
// console.log("config", event.payload); console.log("config", event.payload);
const ipv4 = config.ipv4; const ipv4 = config.ipv4;
mainStore.$patch(event.payload as any); mainStore.$patch(event.payload as any);
config.ipv4 = ipv4; config.ipv4 = ipv4;
}); });
this.unListenConfigStart = unListen; this.unListenConfigStart = unListen;
}, }
}; };
const unListenAll = async () => { const unListenAll = async () => {
@@ -638,6 +695,7 @@
is_enable_by_task = (await invoke("autostart_is_enabled")) as boolean; is_enable_by_task = (await invoke("autostart_is_enabled")) as boolean;
config.autoStart = is_enable_by_task; config.autoStart = is_enable_by_task;
} catch (err) { } catch (err) {
console.log(err);
await invoke("spawn_autostart", { enabled: false }); await invoke("spawn_autostart", { enabled: false });
const is_enable_by_task = (await invoke("autostart_is_enabled")) as boolean; const is_enable_by_task = (await invoke("autostart_is_enabled")) as boolean;
config.autoStart = is_enable_by_task; config.autoStart = is_enable_by_task;
@@ -645,7 +703,7 @@
}; };
const initConnectAfterStart = async () => { const initConnectAfterStart = async () => {
if (config.coonectAfterStart && data.coreVersion) { if (config.connectAfterStart && data.coreVersion) {
await reset(); await reset();
await handleConnection(); await handleConnection();
} }
@@ -679,13 +737,14 @@
config: { config: {
...guiJson, ...guiJson,
...mainStore.config, ...mainStore.config,
serverUrl: saveServerUrl, serverUrl: saveServerUrl
}, }
}); });
if (mainStore.createConfigInEasytier) { if (mainStore.createConfigInEasytier) {
await updateConfigJson(data.configJsonSeverUrl); await updateConfigJson(data.configJsonSeverUrl);
} }
} catch (err) { } catch (err) {
console.log(err);
ElMessage.error(`config.json格式错误`); ElMessage.error(`config.json格式错误`);
} }
} }
@@ -751,7 +810,7 @@
} }
if (config.serverUrl) { if (config.serverUrl) {
const formatUrl = config.serverUrl.replace(/\\/g, "/"); const formatUrl = config.serverUrl.replace(/\\/g, "/");
args.push("--peers", ...config.protocol.map((protocol) => `${protocol}://${formatUrl}`)); args.push("--peers", ...config.protocol.map(protocol => `${protocol}://${formatUrl}`));
} }
if (config.disbleP2p) { if (config.disbleP2p) {
args.push("--disable-p2p"); args.push("--disable-p2p");
@@ -767,8 +826,8 @@
const reg = /\d+\.\d+\.\d+\.\d+\/\d+/g; const reg = /\d+\.\d+\.\d+\.\d+\/\d+/g;
const formatProxyNetworks = config.proxyNetworks const formatProxyNetworks = config.proxyNetworks
.split("\n") .split("\n")
.map((item) => item.trim()) .map(item => item.trim())
.filter((item) => item && reg.test(item)); .filter(item => item && reg.test(item));
args.push("--proxy-networks", ...formatProxyNetworks); args.push("--proxy-networks", ...formatProxyNetworks);
config.proxyNetworks = formatProxyNetworks.join("\n"); config.proxyNetworks = formatProxyNetworks.join("\n");
} }
@@ -812,7 +871,7 @@
config.ipv4 = ""; config.ipv4 = "";
} }
const memberDialog = await getAllWebviewWindows(); const memberDialog = await getAllWebviewWindows();
const memberDialogs = memberDialog.filter((item) => item.label === "member"); const memberDialogs = memberDialog.filter(item => item.label === "member");
if (memberDialogs && memberDialogs.length > 0) { if (memberDialogs && memberDialogs.length > 0) {
data.memberVisible = false; data.memberVisible = false;
for (const memberDialog of memberDialogs) { for (const memberDialog of memberDialogs) {
@@ -838,7 +897,7 @@
if (args[0] === "-c") { if (args[0] === "-c") {
ElMessage.warning({ ElMessage.warning({
message: "使用配置文件中.", message: "使用配置文件中.",
duration: 5000, duration: 5000
}); });
} }
data.log = ""; //清空日志 data.log = ""; //清空日志
@@ -847,7 +906,7 @@
await listenObj.listenOutput(); await listenObj.listenOutput();
await invoke("run_command", { await invoke("run_command", {
args, args
}); });
} }
}; };
@@ -861,10 +920,10 @@
if (isExists) { if (isExists) {
const entries = await readDir(path, { baseDir: BaseDirectory.Resource }); const entries = await readDir(path, { baseDir: BaseDirectory.Resource });
configStart.list = entries configStart.list = entries
.filter((item) => item.isFile) .filter(item => item.isFile)
.map((item) => ({ .map(item => ({
name: item.name, name: item.name,
path: `${path}${item.name}`, path: `${path}${item.name}`
})) as any; })) as any;
} else { } else {
mainStore.configStartEnable = false; mainStore.configStartEnable = false;
@@ -879,7 +938,7 @@
const { const {
proxyNetworks, proxyNetworks,
autoStart, autoStart,
coonectAfterStart, connectAfterStart,
multiThread, multiThread,
hostname, hostname,
enablExitNode, enablExitNode,
@@ -907,14 +966,14 @@
try { try {
await ElMessageBox.confirm("确定导入?", "提示", { await ElMessageBox.confirm("确定导入?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消"
}); });
const payload = JSON.parse(decodeURIComponent(atob(importConfigData.data))); const payload = JSON.parse(decodeURIComponent(atob(importConfigData.data)));
mainStore.$patch({ mainStore.$patch({
config: { config: {
...mainStore.config, ...mainStore.config,
...payload.config, ...payload.config
}, }
}); });
ElMessage.success("导入成功"); ElMessage.success("导入成功");
importConfigData.visible = false; importConfigData.visible = false;
@@ -935,7 +994,9 @@
mainStore.configPath = ""; mainStore.configPath = "";
try { try {
await mkdir(path, { baseDir: BaseDirectory.Resource }); await mkdir(path, { baseDir: BaseDirectory.Resource });
} catch (err) {} } catch (err) {
console.log(err);
}
} }
}; };
@@ -958,9 +1019,9 @@
"member", "member",
{ {
title: "成员列表", title: "成员列表",
width: 470, width: 875,
height: 380, height: 380,
url: "#/member", url: "#/member"
}, },
() => { () => {
data.memberVisible = true; data.memberVisible = true;
@@ -979,7 +1040,7 @@
width: 600, width: 600,
height: 380, height: 380,
resizable: false, resizable: false,
url: "#/log", url: "#/log"
}, },
(_, appWindow) => { (_, appWindow) => {
data.logVisible = true; data.logVisible = true;
@@ -1002,7 +1063,7 @@
width: 600, width: 600,
height: 380, height: 380,
resizable: false, resizable: false,
url: "#/cidr", url: "#/cidr"
}, },
(_, appWindow) => { (_, appWindow) => {
data.cidrVisible = true; data.cidrVisible = true;
@@ -1021,7 +1082,7 @@
width: 600, width: 600,
height: 380, height: 380,
resizable: false, resizable: false,
url: "#/advance", url: "#/advance"
}, },
(_, appWindow) => { (_, appWindow) => {
data.advanceVisible = true; data.advanceVisible = true;
@@ -1040,7 +1101,7 @@
width: 460, width: 460,
height: 480, height: 480,
resizable: true, resizable: true,
url: "#/tool", url: "#/tool"
}, },
(_, appWindow) => { (_, appWindow) => {
data.toolVisible = true; data.toolVisible = true;
+76 -20
View File
@@ -6,26 +6,66 @@
stripe stripe
border border
:data="data.member" :data="data.member"
v-else> v-else
>
<ElTableColumn <ElTableColumn
v-for="prop in showTableHeader"
sortable sortable
:prop="prop[0]" width="140"
:label="prop[1]" label="主机名"
:key="prop[0]"> prop="hostname"
></ElTableColumn>
<ElTableColumn
sortable
width="90"
prop="cost"
label="路由"
>
<template #default="{ row }"> <template #default="{ row }">
<span v-if="prop[0] == 'loss_rate'">
{{ row[prop[0]] && row[prop[0]] != "-" ? Number(row[prop[0]] * 100).toFixed(2) + "%" : row[prop[0]] }}
</span>
<span v-else-if="prop[0] != 'cost'">{{ row[prop[0]] }}</span>
<ElTag <ElTag
v-else
effect="dark" effect="dark"
:type="row[prop[0]] == 'p2p' ? 'success' : 'info'"> :type="row.cost == 'p2p' ? 'success' : 'info'"
{{ row[prop[0]] }} >
{{ row.cost }}
</ElTag> </ElTag>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn
sortable
width="120"
prop="ipv4"
label="虚拟网IP"
></ElTableColumn>
<ElTableColumn
sortable
prop="lat_ms"
width="130"
label="延迟/ms"
></ElTableColumn>
<ElTableColumn
sortable
width="120"
prop="loss_rate"
label="丢包率"
>
<template #default="{ row }">
{{ row.loss_rate && row.loss_rate != "-" ? Number(row.loss_rate * 100).toFixed(2) + "%" : row.loss_rate }}
</template>
</ElTableColumn>
<ElTableColumn
sortable
width="120"
label="nat_type"
prop="NAT类型"
>
<template #default="{ row }">
{{ natMaps[row.nat_type.toLowerCase() as natKyes] || row.nat_type || "-" }}
</template>
</ElTableColumn>
<ElTableColumn
sortable
prop="version"
label="版本"
></ElTableColumn>
</ElTable> </ElTable>
</div> </div>
</div> </div>
@@ -34,24 +74,40 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { reactive, onMounted, onBeforeUnmount } from "vue"; import { reactive, onMounted, onBeforeUnmount } from "vue";
import { parsePeerInfo } from "@/utils"; import { parsePeerInfo } from "@/utils";
const natMaps = {
unknown: "未知",
nopat: "nat0",
fullcone: "nat1",
restricted: "nat2",
addressrestricted: "nat2",
portrestricted: "nat3",
symmetric: "nat4"
};
type natKyes = keyof typeof natMaps;
// NAT1: Full Cone NAT,全锥形NAT,这是最宽松的网络环境,你想做什么,基本没啥限制IP和端口都不受限。
// NAT2: Address-Restricted Cone NAT,受限锥型NAT,相比NAT1,NAT2 增加了地址限制,也就是IP受限,而端口不受限。
// NAT3: Port-Restricted Cone NAT,端口受限锥型,相比NAT2,NAT3 又增加了端口限制,也就是说IP、端口都受限。
// NAT4: Symmetric NAT,对称型NAT,对称型NAT具有端口受限锥型的受限特性,内部地址每一次请求一个特定的外部地址,
// 都可能会绑定到一个新的端口号。也就是请求不同的外部地址映射的端口号是可能不同的。这种类型基本上就告别 P2P 了。
const data = reactive<{ member: any[] }>({ const data = reactive<{ member: any[] }>({
member: [], member: []
}); });
const showTableHeader = [ const _showTableHeader = [
["hostname", "主机名"], ["hostname", "主机名"],
["lat_ms", "延迟/ms"],
["ipv4", "虚拟网IP"],
["cost", "路由"], ["cost", "路由"],
["ipv4", "虚拟网IP"],
["lat_ms", "延迟/ms"],
["loss_rate", "丢包率"], ["loss_rate", "丢包率"],
["nat_type", "NAT类型"], ["nat_type", "NAT类型"],
["version", "版本"], ["version", "版本"]
]; ];
const listenOutput = async () => { const listenOutput = async () => {
const member = await invoke("get_members_by_cli"); const member = await invoke<string>("get_members_by_cli");
const peerInfo = parsePeerInfo(member as string); const peerInfo = parsePeerInfo(member);
peerInfo.forEach((value) => { peerInfo.forEach(value => {
if (value.cost === "Local") { if (value.cost === "Local") {
value.cost = "本机"; value.cost = "本机";
} }
+11
View File
@@ -7,6 +7,10 @@ settings:
importers: importers:
.: .:
dependencies:
'@tauri-apps/plugin-window-state':
specifier: ~2
version: 2.0.0
devDependencies: devDependencies:
'@element-plus/icons-vue': '@element-plus/icons-vue':
specifier: ^2.3.1 specifier: ^2.3.1
@@ -1047,6 +1051,9 @@ packages:
'@tauri-apps/plugin-shell@2.0.1': '@tauri-apps/plugin-shell@2.0.1':
resolution: {integrity: sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==} resolution: {integrity: sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==}
'@tauri-apps/plugin-window-state@2.0.0':
resolution: {integrity: sha512-O82iRlrh1BLgBI8CTc+NMTPxQhQo8II5admKq9mLvH45Us5i4Zcr74At6eM46nOflFd7R8bZsVNGy+PxOEqUmQ==}
'@trysound/sax@0.2.0': '@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@@ -4759,6 +4766,10 @@ snapshots:
dependencies: dependencies:
'@tauri-apps/api': 2.1.1 '@tauri-apps/api': 2.1.1
'@tauri-apps/plugin-window-state@2.0.0':
dependencies:
'@tauri-apps/api': 2.1.1
'@trysound/sax@0.2.0': {} '@trysound/sax@0.2.0': {}
'@types/estree@1.0.6': {} '@types/estree@1.0.6': {}
+17 -1
View File
@@ -1176,7 +1176,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]] [[package]]
name = "easytier-game" name = "easytier-game"
version = "1.2.0" version = "1.2.1"
dependencies = [ dependencies = [
"log", "log",
"planif", "planif",
@@ -1192,6 +1192,7 @@ dependencies = [
"tauri-plugin-log", "tauri-plugin-log",
"tauri-plugin-shell", "tauri-plugin-shell",
"tauri-plugin-single-instance", "tauri-plugin-single-instance",
"tauri-plugin-window-state",
"whoami", "whoami",
"windows 0.58.0", "windows 0.58.0",
"zip", "zip",
@@ -4543,6 +4544,21 @@ dependencies = [
"zbus", "zbus",
] ]
[[package]]
name = "tauri-plugin-window-state"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683c8764751fbbcebf3a594bcee24cf84c62773fa0080d1b40fc80698472421e"
dependencies = [
"bitflags 2.6.0",
"log",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror 1.0.64",
]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.2.0" version = "2.2.0"
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "easytier-game" name = "easytier-game"
version = "1.2.0" version = "1.2.1"
homepage = "https://github.com/EasyTier/EasyTier" homepage = "https://github.com/EasyTier/EasyTier"
repository = "https://github.com/EasyTier/EasytierGame" repository = "https://github.com/EasyTier/EasytierGame"
description = "A simple network initiator based on Easytier" description = "A simple network initiator based on Easytier"
@@ -50,3 +50,4 @@ features = ["Win32_System_TaskScheduler"]
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-autostart = "2.0.1" tauri-plugin-autostart = "2.0.1"
tauri-plugin-single-instance = "2.0.1" tauri-plugin-single-instance = "2.0.1"
tauri-plugin-window-state = "2"
+5 -1
View File
@@ -88,6 +88,10 @@
"fs:allow-resource-write-recursive", "fs:allow-resource-write-recursive",
"clipboard-manager:allow-clear", "clipboard-manager:allow-clear",
"clipboard-manager:allow-read-text", "clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text" "clipboard-manager:allow-write-text",
"window-state:default",
"window-state:allow-save-window-state",
"window-state:allow-restore-state"
] ]
} }
+14 -8
View File
@@ -518,12 +518,17 @@ fn autostart(enabled: bool) -> std::result::Result<(), Box<dyn std::error::Error
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
// 获取命令行参数 // 获取命令行参数
let _args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let stop_signal = Arc::new(AtomicBool::new(false)); // 创建一个原子布尔值,用于控制命令的停止 let stop_signal = Arc::new(AtomicBool::new(false)); // 创建一个原子布尔值,用于控制命令的停止
let stop_signal_clone = Arc::clone(&stop_signal); // 创建一个原子布尔值的克隆,用于传递给命令 let stop_signal_clone = Arc::clone(&stop_signal); // 创建一个原子布尔值的克隆,用于传递给命令
let context = tauri::generate_context!(); let context = tauri::generate_context!();
tauri::Builder::default() tauri::Builder::default()
.plugin(
tauri_plugin_window_state::Builder::new()
.with_state_flags(tauri_plugin_window_state::StateFlags::SIZE)
.build(),
)
.plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_autostart::init( .plugin(tauri_plugin_autostart::init(
@@ -539,7 +544,7 @@ pub fn run() {
.expect("failed to set focus"); .expect("failed to set focus");
})) }))
.setup(move |app| { .setup(move |app| {
// let args = args.clone(); let args = args.clone();
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
app.handle().plugin( app.handle().plugin(
tauri_plugin_log::Builder::default() tauri_plugin_log::Builder::default()
@@ -566,12 +571,13 @@ pub fn run() {
))?) ))?)
.icon_as_template(false) .icon_as_template(false)
.build(app)?; .build(app)?;
// 开机自启隐藏到托盘
// if args.contains(&String::from(TASKAUTOSTART_ARG)) { // 开机自启隐藏到托盘或者显示主窗口
// app.get_webview_window("main") if !args.contains(&String::from(TASKAUTOSTART_ARG)) {
// .expect("no main window to hide") let main_window = app.get_webview_window("main").unwrap();
// .hide()?; main_window.show().expect("failed to show window");
// } main_window.set_focus().expect("failed to set focus");
}
Ok(()) Ok(())
}) })
+4 -2
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "easytier-game", "productName": "easytier-game",
"version": "1.2.0", "version": "1.2.1",
"identifier": "com.tauri.easytier-game", "identifier": "com.tauri.easytier-game",
"build": { "build": {
@@ -13,7 +13,8 @@
"app": { "app": {
"windows": [ "windows": [
{ {
"title": "easytier-game 1.2.0", "title": "easytier-game 1.2.1",
"label": "main",
"minWidth": 340, "minWidth": 340,
"width": 340, "width": 340,
"height": 305, "height": 305,
@@ -24,6 +25,7 @@
"fullscreen": false, "fullscreen": false,
"decorations": true, "decorations": true,
"center": true, "center": true,
"visible": false,
"maximizable": false, "maximizable": false,
"transparent": false "transparent": false
} }
+2 -2
View File
@@ -11,7 +11,7 @@ export default defineStore("main", {
ipv4: "", ipv4: "",
proxyNetworks: "", // 子网代理 proxyNetworks: "", // 子网代理
autoStart: false, // 是否自动启动 autoStart: false, // 是否自动启动
coonectAfterStart: false, //软件打开后,是否自动连接 connectAfterStart: false, //软件打开后,是否自动连接
disableIpv6: false, // 是否禁用IPv6 disableIpv6: false, // 是否禁用IPv6
disbleListenner: false, // 是否禁用监听 disbleListenner: false, // 是否禁用监听
disableEncryption: false, // 是否禁用加密 disableEncryption: false, // 是否禁用加密
@@ -54,7 +54,7 @@ export default defineStore("main", {
"config.ipv4", "config.ipv4",
"config.proxyNetworks", "config.proxyNetworks",
"config.autoStart", "config.autoStart",
"config.coonectAfterStart", "config.connectAfterStart",
"config.disableIpv6", "config.disableIpv6",
"config.disbleListenner", "config.disbleListenner",
"config.disableEncryption", "config.disableEncryption",