修改客户端UI连接显示逻辑
This commit is contained in:
parent
6ed3118df1
commit
09d5c69072
|
|
@ -59,6 +59,10 @@ export const GetVPNServerOnlineListHandler = () => {
|
|||
return request.get('/vpn/get_server_online')
|
||||
}
|
||||
|
||||
export const GetSurppotVPNServerOnlineListHandler = () => {
|
||||
return request.get('/vpn/get_support_vpn_server')
|
||||
}
|
||||
|
||||
export const DeleteVPNTunnelHandler = (Data) => {
|
||||
return request.delete('/vpn/delete_vpn_tunnel', { data: Data })
|
||||
}
|
||||
|
|
@ -78,6 +82,18 @@ export const DeleteVPNServerHandler = (Data) => {
|
|||
return request.delete('/vpn/delete_vpn_server', { data: Data })
|
||||
}
|
||||
|
||||
export const GetClientDownloadURLHandler = () => {
|
||||
return request.get("/vpn/clients_url")
|
||||
}
|
||||
|
||||
export const LocalClientConnectHandler = (Data) => {
|
||||
return local_request.post('/vpn/connect', Data)
|
||||
}
|
||||
export const LocalClientDisConnectHandler = (Data) => {
|
||||
let url = '/vpn/disconnect?server_id=' + Data.server_id;
|
||||
return local_request.get(url)
|
||||
}
|
||||
//获取本地客户端连接状态
|
||||
export const LocalClientStatusHandler = () => {
|
||||
return local_request.get('/vpn/get_status')
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ request.interceptors.response.use(
|
|||
}
|
||||
|
||||
if(result.data.code == 1) {
|
||||
ElMessage.error('请求失败,请稍后重试!');
|
||||
ElMessage.error('请求失败,请运行客户端!');
|
||||
} else {
|
||||
return result.data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,34 +14,35 @@
|
|||
<el-empty v-if="!loading && vpnServers.length === 0" description="暂无在线VPN服务器" />
|
||||
|
||||
<el-row :gutter="20" v-else>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="server in vpnServers" :key="server.id">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="server in vpnServers" :key="server.server_id">
|
||||
<el-card class="vpn-server-card" shadow="hover">
|
||||
<div class="server-info">
|
||||
<div class="server-header">
|
||||
<el-icon class="server-icon"><Monitor /></el-icon>
|
||||
<h3>{{ server.name || server.server_name || 'VPN服务器' }}</h3>
|
||||
<h3>{{ server.name || server.name || 'VPN服务器' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="server-details">
|
||||
<div class="detail-item">
|
||||
<span class="label">IP地址:</span>
|
||||
<span class="value">{{ server.ip || server.server_ip || 'N/A' }}</span>
|
||||
<span class="value">{{ server.server_ip || server.server_ip || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="server.protocol">
|
||||
<span class="label">协议:</span>
|
||||
<span class="value">{{ server.protocol === 1 ? 'TCP' : 'UDP'}}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">端口:</span>
|
||||
<span class="value">{{ server.port || 'N/A' }}</span>
|
||||
<span class="value" v-if="server.protocol == 1">{{ server.tcp_port || 'N/A' }}</span>
|
||||
<span class="value" v-if="server.protocol == 2">{{ server.udp_port || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">状态:</span>
|
||||
<el-tag type="success" size="small">在线</el-tag>
|
||||
</div>
|
||||
<div class="detail-item" v-if="server.location">
|
||||
<span class="label">位置:</span>
|
||||
<span class="value">{{ server.location }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="server.protocol">
|
||||
<span class="label">协议:</span>
|
||||
<span class="value">{{ server.protocol }}</span>
|
||||
<div class="detail-item" v-if="server.server_info">
|
||||
<span class="label">描述信息:</span>
|
||||
<span class="value">{{ server.server_info }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -51,11 +52,32 @@
|
|||
type="primary"
|
||||
size="small"
|
||||
@click="connectToServer(server)"
|
||||
:loading="connectingServers.includes(server.id)"
|
||||
:loading="connectingServers.includes(server.server_id)"
|
||||
v-if = "clientIsConnectServerID == ''"
|
||||
>
|
||||
<el-icon><Connection /></el-icon>
|
||||
连接
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="disConnectToServer(server)"
|
||||
:loading="connectingServers.includes(server.server_id)"
|
||||
v-if = "clientIsConnectServerID == server.server_id"
|
||||
>
|
||||
<el-icon><Connection /></el-icon>
|
||||
断开
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showOnlineInfo(server)"
|
||||
:loading="connectingServers.includes(server.server_id)"
|
||||
v-if = "clientIsConnectServerID == server.server_id"
|
||||
>
|
||||
<el-icon><Connection /></el-icon>
|
||||
显示在线信息
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
|
@ -70,28 +92,15 @@
|
|||
:before-close="handleDialogClose"
|
||||
>
|
||||
<div class="download-options">
|
||||
<p>请选择您的操作系统类型:</p>
|
||||
<div class="client-options">
|
||||
<p>{{ clientUrls.length ? '请选择您的操作系统类型:' : '暂不支持下载客户端' }}</p>
|
||||
|
||||
<div v-for="clientUrl in clientUrls" :key="clientUrl.platform">
|
||||
<el-button
|
||||
class="download-btn"
|
||||
@click="downloadClient('windows')"
|
||||
@click="downloadClient(clientUrl.platform)"
|
||||
:icon="Monitor"
|
||||
>
|
||||
Windows 客户端
|
||||
</el-button>
|
||||
<el-button
|
||||
class="download-btn"
|
||||
@click="downloadClient('linux')"
|
||||
:icon="Monitor"
|
||||
>
|
||||
Linux 客户端
|
||||
</el-button>
|
||||
<el-button
|
||||
class="download-btn"
|
||||
@click="downloadClient('macos')"
|
||||
:icon="Monitor"
|
||||
>
|
||||
macOS 客户端
|
||||
{{ clientUrl.platform }} 客户端
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -100,37 +109,215 @@
|
|||
<el-button @click="showDownloadDialog = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 在线信息对话框 -->
|
||||
<el-dialog
|
||||
v-model="showOnlineInfoDialog"
|
||||
title="VPN连接详细信息"
|
||||
width="800px"
|
||||
:before-close="handleOnlineInfoDialogClose"
|
||||
>
|
||||
<div class="online-info-content" v-loading="onlineInfoLoading">
|
||||
<el-tabs v-model="activeOnlineInfoTab" type="card">
|
||||
<!-- 基本信息标签页 -->
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<el-descriptions :column="2" border v-if="onlineInfoData.online_info">
|
||||
<el-descriptions-item label="服务器ID">{{ onlineInfoData.online_info.server_id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="服务器IP">{{ onlineInfoData.online_info.server_ip }}</el-descriptions-item>
|
||||
<el-descriptions-item label="TCP端口">{{ onlineInfoData.online_info.tcp_port }}</el-descriptions-item>
|
||||
<el-descriptions-item label="UDP端口">{{ onlineInfoData.online_info.udp_port }}</el-descriptions-item>
|
||||
<el-descriptions-item label="协议">{{ onlineInfoData.online_info.protocol === 1 ? 'TCP' : 'UDP' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="IP类型">{{ onlineInfoData.online_info.ip_type === 46 ? 'IPv4/IPv6' : 'IPv4' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="私有IPv4">{{ onlineInfoData.online_info.private_ipv4 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="IPv4前缀">{{ onlineInfoData.online_info.ipv4_prefix }}</el-descriptions-item>
|
||||
<el-descriptions-item label="网关">{{ onlineInfoData.online_info.gateway }}</el-descriptions-item>
|
||||
<el-descriptions-item label="加密方式">{{ onlineInfoData.online_info.encryption }}</el-descriptions-item>
|
||||
<el-descriptions-item label="哈希算法">{{ onlineInfoData.online_info.hash }}</el-descriptions-item>
|
||||
<el-descriptions-item label="连接时间" v-if="onlineInfoData.connect_status">
|
||||
{{ onlineInfoData.connect_status.connect_time }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流量统计标签页 -->
|
||||
<el-tab-pane label="流量统计" name="traffic">
|
||||
<el-descriptions :column="2" border v-if="onlineInfoData.connect_status">
|
||||
<el-descriptions-item label="发送字节">
|
||||
{{ formatBytes(onlineInfoData.connect_status.send_bytes) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="接收字节">
|
||||
{{ formatBytes(onlineInfoData.connect_status.receive_byes) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发送数据包">{{ onlineInfoData.connect_status.send_packets }}</el-descriptions-item>
|
||||
<el-descriptions-item label="接收数据包">{{ onlineInfoData.connect_status.receive_packets }}</el-descriptions-item>
|
||||
<el-descriptions-item label="私有IP">{{ onlineInfoData.connect_status.private_ip }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 路由信息标签页 -->
|
||||
<el-tab-pane label="路由信息" name="routing">
|
||||
<div v-if="onlineInfoData.connect_status?.router?.length > 0">
|
||||
<h4>IPv4路由表</h4>
|
||||
<el-table :data="onlineInfoData.connect_status.router" style="width: 100%">
|
||||
<el-table-column prop="type" label="类型" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.type === 4 ? 'IPv4' : 'IPv6' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ip" label="目标网络" />
|
||||
<el-table-column prop="prefix" label="前缀长度" width="100" />
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-empty description="暂无路由信息" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showOnlineInfoDialog = false">关闭</el-button>
|
||||
<el-button type="primary" @click="refreshOnlineInfo">刷新</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Download, Monitor, Connection } from '@element-plus/icons-vue'
|
||||
import { GetVPNServerOnlineListHandler, LocalClientConnectHandler } from '@/api/vpn'
|
||||
import { GetSurppotVPNServerOnlineListHandler, LocalClientConnectHandler, LocalClientStatusHandler,LocalClientDisConnectHandler,GetClientDownloadURLHandler } from '@/api/vpn'
|
||||
|
||||
interface VPNServer {
|
||||
id: string | number
|
||||
name?: string
|
||||
server_name?: string
|
||||
ip?: string
|
||||
server_ip?: string
|
||||
port?: string | number
|
||||
location?: string
|
||||
protocol?: string
|
||||
[key: string]: any
|
||||
interface ServerInfo {
|
||||
/** 服务器名称 */
|
||||
name: string;
|
||||
/** 服务器唯一标识ID */
|
||||
server_id: string;
|
||||
/** 服务器IPv4地址 */
|
||||
server_ip: string;
|
||||
/** 服务器附加信息(预留字段) */
|
||||
server_info: string;
|
||||
/** 服务器IPv6地址 */
|
||||
server_ipv6: string;
|
||||
/** UDP协议端口号 */
|
||||
udp_port: number;
|
||||
/** TCP协议端口号 */
|
||||
tcp_port: number;
|
||||
/** 协议类型标识(2可能代表TCP/UDP双协议等) */
|
||||
protocol: number;
|
||||
}
|
||||
|
||||
const vpnServers = ref<VPNServer[]>([])
|
||||
interface ClientUrl {
|
||||
platform: string;
|
||||
download_url: string;
|
||||
}
|
||||
|
||||
const vpnServers = ref<ServerInfo[]>([])
|
||||
const loading = ref(false)
|
||||
const connectingServers = ref<(string | number)[]>([])
|
||||
const clientIsConnectServerID = ref('')
|
||||
const showDownloadDialog = ref(false)
|
||||
let statusTimerId: number | null = null
|
||||
let serverListTimerId: number | null = null
|
||||
const clientUrls = ref<ClientUrl[]>([])
|
||||
const showOnlineInfoDialog = ref(false)
|
||||
const onlineInfoLoading = ref(false)
|
||||
const activeOnlineInfoTab = ref('basic')
|
||||
const onlineInfoData = ref({
|
||||
online_info: null,
|
||||
connect_status: null,
|
||||
status: 0
|
||||
})
|
||||
|
||||
// 格式化字节数显示
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (!bytes || bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
// 显示在线信息
|
||||
const showOnlineInfo = async (server: ServerInfo) => {
|
||||
showOnlineInfoDialog.value = true
|
||||
activeOnlineInfoTab.value = 'basic'
|
||||
await refreshOnlineInfo()
|
||||
}
|
||||
|
||||
// 刷新在线信息
|
||||
const refreshOnlineInfo = async () => {
|
||||
onlineInfoLoading.value = true
|
||||
try {
|
||||
const response = await LocalClientStatusHandler()
|
||||
if (response && response["code"] === 0) {
|
||||
onlineInfoData.value = response["data"] || {}
|
||||
} else {
|
||||
ElMessage.error(response["message"] || '获取在线信息失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取在线信息错误:', error)
|
||||
ElMessage.error('获取在线信息失败,请稍后重试')
|
||||
} finally {
|
||||
onlineInfoLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭在线信息对话框
|
||||
const handleOnlineInfoDialogClose = () => {
|
||||
showOnlineInfoDialog.value = false
|
||||
}
|
||||
|
||||
const LocalClientStatus = async () => {
|
||||
const response = await LocalClientStatusHandler()
|
||||
if (response && response["code"] === 0) {
|
||||
let data = response["data"]
|
||||
if (data && data["status"] == 2001) {
|
||||
clientIsConnectServerID.value = data["online_info"]["server_id"]
|
||||
onlineInfoData.value = response["data"] || {}
|
||||
console.log('clientIsConnectServerID:', clientIsConnectServerID.value)
|
||||
}else{
|
||||
clientIsConnectServerID.value = ''
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(response["message"] || '获取VPN客户端状态失败')
|
||||
}
|
||||
}
|
||||
|
||||
const disConnectToServer = async (server: ServerInfo) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要断开连接到服务器 ${server.name || server.name || server.server_id} 吗?`,
|
||||
'确认断开',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
}
|
||||
)
|
||||
connectingServers.value.push(server.server_id)
|
||||
const requestData = {
|
||||
server_id: server.server_id,
|
||||
}
|
||||
|
||||
const response = await LocalClientDisConnectHandler(requestData)
|
||||
if (response && response["code"] === 0) {
|
||||
ElMessage.success('断开连接成功')
|
||||
} else {
|
||||
ElMessage.error(response["message"] || '断开连接失败')
|
||||
}
|
||||
} catch (error) {
|
||||
}finally {
|
||||
connectingServers.value = connectingServers.value.filter(id => id !== server.server_id)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取在线VPN服务器列表
|
||||
const fetchVPNServers = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await GetVPNServerOnlineListHandler()
|
||||
const response = await GetSurppotVPNServerOnlineListHandler()
|
||||
if (response && response["code"] === 0) {
|
||||
vpnServers.value = response.data || []
|
||||
} else {
|
||||
|
|
@ -145,10 +332,10 @@ const fetchVPNServers = async () => {
|
|||
}
|
||||
|
||||
// 连接到VPN服务器
|
||||
const connectToServer = async (server: VPNServer) => {
|
||||
const connectToServer = async (server: ServerInfo) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要连接到服务器 ${server.name || server.server_name || server.id} 吗?`,
|
||||
`确定要连接到服务器 ${server.name || server.name || server.server_id} 吗?`,
|
||||
'确认连接',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
|
|
@ -173,6 +360,7 @@ const connectToServer = async (server: VPNServer) => {
|
|||
} else if (response && response["code"] === 20){
|
||||
//设置已在线
|
||||
ElMessage.error(response["message"])
|
||||
clientIsConnectServerID.value = server.server_id
|
||||
} else {
|
||||
ElMessage.error(response["message"])
|
||||
}
|
||||
|
|
@ -182,7 +370,7 @@ const connectToServer = async (server: VPNServer) => {
|
|||
ElMessage.error('VPN连接失败,请稍后重试')
|
||||
}
|
||||
} finally {
|
||||
connectingServers.value = connectingServers.value.filter(id => id !== server.id)
|
||||
connectingServers.value = connectingServers.value.filter(id => id !== server.server_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,13 +381,13 @@ const downloadClient = (clientType: string) => {
|
|||
|
||||
// 模拟下载,实际应用中应该提供真实的下载链接
|
||||
setTimeout(() => {
|
||||
const downloadUrls = {
|
||||
windows: 'https://gitee.com/junleea/my-vpn-client/releases/download/0.1/myvpn-client-windows_amd_x64.exe',
|
||||
linux: 'https://gitee.com/junleea/my-vpn-client/releases/download/0.1/myvpn-client-windows_amd_x64.exe',
|
||||
macos: 'https://gitee.com/junleea/my-vpn-client/releases/download/0.1/myvpn-client-windows_amd_x64.exe'
|
||||
let url = "";
|
||||
for (let client_url of clientUrls.value) {
|
||||
if (client_url.platform == clientType) {
|
||||
url = client_url.download_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const url = downloadUrls[clientType as keyof typeof downloadUrls]
|
||||
if (url) {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
|
|
@ -214,6 +402,19 @@ const downloadClient = (clientType: string) => {
|
|||
}, 1000)
|
||||
}
|
||||
|
||||
const GetClientDownloadUrl =async () =>{
|
||||
try{
|
||||
let resp =await GetClientDownloadURLHandler()
|
||||
if (resp && resp["code"] == 0){
|
||||
if (resp["data"]){
|
||||
clientUrls.value = resp["data"]
|
||||
}
|
||||
}
|
||||
}catch(error){
|
||||
console.error('获取客户端下载URL失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
const handleDialogClose = () => {
|
||||
showDownloadDialog.value = false
|
||||
|
|
@ -221,7 +422,21 @@ const handleDialogClose = () => {
|
|||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
GetClientDownloadUrl()
|
||||
// 立即获取一次
|
||||
fetchVPNServers()
|
||||
LocalClientStatus()
|
||||
|
||||
// 每2秒获取客户端状态
|
||||
statusTimerId = setInterval(LocalClientStatus, 2000)
|
||||
// 每10秒获取服务器列表
|
||||
serverListTimerId = setInterval(fetchVPNServers, 10000)
|
||||
})
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
if (statusTimerId) clearInterval(statusTimerId)
|
||||
if (serverListTimerId) clearInterval(serverListTimerId)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -372,4 +587,57 @@ onMounted(() => {
|
|||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
.online-info-content {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.online-info-content h4 {
|
||||
margin: 20px 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.online-info-content h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* 自定义标签页样式 */
|
||||
.el-tabs--card .el-tabs__item {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
/* 描述列表样式调整 */
|
||||
.el-descriptions__body .el-descriptions__table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.el-descriptions__label {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.el-table .el-table__header th {
|
||||
background-color: #f8f9fa;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 响应式设计补充 */
|
||||
@media (max-width: 768px) {
|
||||
.online-info-content {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.el-descriptions :deep(.el-descriptions__body) .el-descriptions__table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-descriptions :deep(.el-descriptions__body) .el-descriptions__cell {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
IPv6: {{ tunnel.config.auto_ipv6 ? '自动' : tunnel.config.ipv6_address }}
|
||||
</div>
|
||||
<div class="tunnel-limits">
|
||||
上行: {{ tunnel.config.upload_limit }} Mbps | 下行: {{ tunnel.config.download_limit }} Mbps
|
||||
上行: {{ tunnel.config.upload_limit }} Kbps | 下行: {{ tunnel.config.download_limit }} Kbps
|
||||
</div>
|
||||
</div>
|
||||
<div class="tunnel-actions">
|
||||
|
|
|
|||
Loading…
Reference in New Issue