#Update 增加对成员列表kcp和quic代理显示 增加部分配置到configJson #23

This commit is contained in:
黑先森
2025-11-06 18:31:10 +08:00
parent 6b373a3ed6
commit 86adc2c40b
5 changed files with 177 additions and 48 deletions
-5
View File
@@ -30,7 +30,6 @@ export const updateConfigJson = async (configJsonSeverUrl?: ConfigServerUrlType)
const {
proxyNetworks,
autoStart,
connectAfterStart,
saveErrorLog,
serverUrl,
enableCustomListener,
@@ -39,10 +38,6 @@ export const updateConfigJson = async (configJsonSeverUrl?: ConfigServerUrlType)
acceptDNS,
enablePortForward,
portForwardData,
tcpWhitelist,
udpWhitelist,
tcpWhitelistEnable,
udpWhitelistEnable,
...otherConfig
} = mainStore.config;
let writeServerUrl: Array<string> = serverUrl;
+65 -5
View File
@@ -10,7 +10,7 @@
<ElTable
stripe
border
:data="data.member"
:data="computedMember"
v-else
>
<ElTableColumn
@@ -65,7 +65,23 @@
label="虚拟网IP"
:sort-method="sortIpv4"
:sort-orders="['ascending', 'descending', null]"
></ElTableColumn>
>
<template #default="{ row }">
<div class="space-y-[5px]">
<div>
{{ row.ipv4 || "-" }}
</div>
<div v-if="row.proxy?.[0]">
<ElTag
size="small"
:type="row.proxy?.[1] == 'Connected' ? 'success' : 'danger'"
>
{{ row.proxy?.[0] || "-" }}
</ElTag>
</div>
</div>
</template>
</ElTableColumn>
<ElTableColumn
sortable
prop="lat_ms"
@@ -137,7 +153,7 @@
</template>
<script setup lang="ts">
import { invoke } from "@tauri-apps/api/core";
import { reactive, onMounted, onBeforeUnmount } from "vue";
import { reactive, onMounted, onBeforeUnmount, computed } from "vue";
import { ATJ, parseCliInfo } from "@/utils";
import { ElConfirmDanger } from "~/utils/element";
import { getCurrentWindow } from "@tauri-apps/api/window";
@@ -157,6 +173,7 @@
id: string;
version: string;
connections_addrs: string[];
proxy?: [string, "Connected" | "Closed"];
};
type ConnectionsType = {
@@ -209,10 +226,35 @@
symmetriceasyinc: "nat4-easyinc",
symUdpfirewall: "nat4-udpfirewall"
};
type Proxy = {
src: string;
dst: string;
start_time: string;
state: "Connected" | "Closed";
transport_type: string;
};
type natKyes = keyof typeof natMaps;
const data = reactive<{ member: Member[] }>({
member: []
const data = reactive<{ member: Member[]; proxy: Proxy[] }>({
member: [],
proxy: []
});
const computedMember = computed(() => {
const localMember = data.member.find(member => member?.cost == "Local");
if (!localMember) return data.member;
const ipv4 = localMember.ipv4;
const proxyMember = data.proxy?.filter(proxy => proxy.src.startsWith(`${ipv4}:`));
const proxyObj: Record<string, [string, "Connected" | "Closed"]> = {};
for (const proxy of proxyMember) {
proxyObj[proxy.src.split(":")[0]] = [proxy.transport_type, proxy.state];
}
return data.member.map(m => {
m.proxy = proxyObj?.[m.ipv4.split(":")?.[0]] || m?.proxy || ["", "Closed"];
return m;
});
});
// 延迟排序函数
@@ -395,9 +437,27 @@
data.member = JSON.parse(member) as Member[];
}
// console.log(data.member);
getMembersProxy();
startTimer();
};
const getMembersProxy = async () => {
const [error, proxy] = await ATJ(invoke<string>("get_members_proxy"));
if (error) {
data.proxy = [];
return "";
}
if (proxy === "_EasytierGameCliFailedToGetProxy_") {
data.proxy = [];
return "";
}
try {
data.proxy = JSON.parse(proxy) as Proxy[];
} catch (error) {
data.proxy = [];
}
};
const startTimer = () => {
timer && clearTimeout(timer);
timer = setTimeout(() => {
+61 -36
View File
@@ -1,46 +1,53 @@
<template>
<div class="relative flex h-full flex-col flex-wrap gap-[0] overflow-hidden">
<div class="flex h-[40px] w-full flex-nowrap items-center gap-[0_5px]">
<div class="ml-[5px] mr-auto flex flex-1">
<div class="flex h-[40px] w-full flex-nowrap items-center gap-[0_8px]">
<div class="ml-[5px] mr-auto flex">
<ElText class="text-lg font-semibold">公共服务器节点列表</ElText>
</div>
<div>
<ElButton
:loading="loading"
size="small"
type="primary"
class="mr-[5px]"
@click="refreshNodes"
>
刷新
</ElButton>
<ElButton
:disabled="selectedNodes.length === 0"
size="small"
type="success"
class="mr-[5px]"
@click="addSelectedToServerList"
>
添加选中 ({{ selectedNodes.length }})
</ElButton>
<ElTag
v-if="lastUpdateTime"
size="small"
type="info"
>
最后更新: {{ lastUpdateTime }}
</ElTag>
<div class="flex w-fit flex-nowrap items-center gap-[0_8px]">
<div>
<ElInput
placeholder="搜索名称/地址"
v-model="searchName"
clearable
></ElInput>
</div>
<div>
<ElButton
size="small"
type="primary"
@click="refreshNodes"
>
刷新
</ElButton>
<ElButton
:disabled="selectedNodes.length === 0"
size="small"
type="success"
class="mr-[8px]"
@click="addSelectedToServerList"
>
添加选中 ({{ selectedNodes.length }})
</ElButton>
<ElTag
size="small"
type="info"
>
最后更新: {{ lastUpdateTime || "00:00:00" }}
</ElTag>
</div>
</div>
</div>
<div class="flex w-full flex-1 flex-wrap gap-[0] overflow-auto">
<ElTable
:data="nodes"
:data="computedNodes"
stripe
height="100%"
ref="tableRef"
:empty-text="loading ? '加载中...' : '暂无可用服务器'"
:default-sort="{ prop: 'last_response_time', order: 'ascending' }"
@selection-change="handleSelectionChange"
@select-all="handleSelectChange"
@select="handleSelectChange"
@row-click="handleRowClick"
>
<ElTableColumn
@@ -151,10 +158,11 @@
<script setup lang="ts">
import useMainStore from "@/stores/index";
import { invoke } from "@tauri-apps/api/core";
import { reactive, onMounted, onBeforeUnmount, ref, useTemplateRef } from "vue";
import { reactive, onMounted, onBeforeUnmount, ref, useTemplateRef, computed, nextTick } from "vue";
import { ElMessage, type TableInstance } from "element-plus";
import { dataSubscribe } from "~/composables/windows";
import { uniq } from "lodash-es";
import { inputBounceRef } from "@/utils";
import { uniq } from "lodash-es";
const mainStore = useMainStore();
// 节点数据
@@ -162,6 +170,20 @@ import { uniq } from "lodash-es";
const loading = ref(false);
const lastUpdateTime = ref("");
const selectedNodes = ref<NodeItem[]>([]);
const searchName = ref<string>("");
const computedNodes = computed(() => {
const v = searchName.value.trim();
let result = nodes;
if (v) {
result = nodes.filter(node => node.name.includes(v) || node.address.includes(v));
}
nextTick(() => {
for (const node of selectedNodes.value) {
tableRef.value?.toggleRowSelection(node, true);
}
});
return result;
});
const tableRef = useTemplateRef<TableInstance>("tableRef");
// 定时器
@@ -191,14 +213,15 @@ import { uniq } from "lodash-es";
// 刷新节点数据
const refreshNodes = async () => {
loading.value = true;
let nodesList = await invoke<string>("fetch_nodes_list", { page: 1, limit: 1000 });
try {
let nodesList = await invoke<string>("fetch_nodes_list", { page: 1, limit: 1000 });
if (nodesList === "_EasytierGameFetchNodesError_") {
nodesList = "";
return;
}
if (nodesList) {
const nodesListData: NodesResponse = JSON.parse(nodesList);
console.log(nodesListData);
// console.log(nodesListData);
if (nodesListData.success && nodesListData.data.items) {
// 过滤可用节点
const availableNodes = filterAvailableNodes(nodesListData.data.items);
@@ -215,6 +238,7 @@ import { uniq } from "lodash-es";
}
}
} catch (err) {
console.log(nodesList);
console.error("获取节点数据失败:", err);
ElMessage.error("获取节点数据失败");
} finally {
@@ -303,7 +327,7 @@ import { uniq } from "lodash-es";
const startRefreshTimer = () => {
refreshTimer = setInterval(() => {
refreshNodes();
}, 10000); // 10秒刷新一次
}, 60000); // 10秒刷新一次
};
// 停止定时器
@@ -315,12 +339,13 @@ import { uniq } from "lodash-es";
};
// 处理选择变化
const handleSelectionChange = (selection: NodeItem[]) => {
const handleSelectChange = (selection: NodeItem[]) => {
selectedNodes.value = selection;
};
const handleRowClick = (row: NodeItem) => {
tableRef.value?.toggleRowSelection(row);
selectedNodes.value = tableRef.value?.getSelectionRows() || [];
};
// 添加选中的服务器到主页面服务器列表
+28
View File
@@ -299,6 +299,34 @@ async fn get_members_by_cli() -> String {
}
}
#[tauri::command(rename_all = "snake_case")]
async fn get_members_proxy() -> String {
let cli_path = get_tool_exe_path("\\easytier\\easytier-cli.exe");
match tokioCommand::new(&cli_path)
.arg("-o")
.arg("json")
.arg("proxy")
.creation_flags(0x08000000)
.output()
.await
{
Ok(output) => {
if output.status.success() {
let output_str = String::from_utf8_lossy(&output.stdout);
return output_str.trim().to_string();
} else {
let output_str = String::from_utf8_lossy(&output.stderr);
log::error!("{}", output_str.trim().to_string());
return "_EasytierGameCliFailedToGetProxy_".to_string();
}
}
Err(_e) => {
log::error!("get member list error");
return "".to_string();
}
}
}
#[tauri::command(rename_all = "snake_case")]
async fn get_members_connections_cli() -> String {
let cli_path = get_tool_exe_path("\\easytier\\easytier-cli.exe");
+23 -2
View File
@@ -3,10 +3,11 @@ import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { open } from "@tauri-apps/plugin-shell";
import { ElMessage } from "element-plus";
import { flatMap, map } from "lodash-es";
import { customRef, ref } from "vue";
export const ENV = import.meta.env;
export const mixedArray = (a: Array<string>, b: Array<string>, split:string="") => {
export const mixedArray = (a: Array<string>, b: Array<string>, split: string = "") => {
return flatMap(a, protocol => map(b, port => `${protocol}${split}${port}`));
};
@@ -21,6 +22,26 @@ export const bounce = (time = 3000) => {
};
};
export const inputBounceRef = (value: string, cb?: Function, delay = 650) => {
let timeout: number | null = null;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
value = newValue;
trigger();
timeout && clearTimeout(timeout);
timeout = setTimeout(() => {
cb?.(newValue);
}, delay);
}
};
});
};
/**
*
* @param {number} size byte number
@@ -73,7 +94,7 @@ export const ATJ = (promise: Promise<any>, errorExt: string | undefined = undefi
});
};
const _supportProtocols = ["tcp", "udp", "ws", "wss", 'srv', 'schema'];
const _supportProtocols = ["tcp", "udp", "ws", "wss", "srv", "schema"];
export const supportProtocols = () => {
return _supportProtocols.slice();