重构中继

This commit is contained in:
snltty
2025-12-04 17:55:01 +08:00
parent 3b613a87de
commit 7acd88526c
29 changed files with 536 additions and 146 deletions

View File

@@ -1,5 +1,5 @@
v1.9.6 v1.9.6
2025-12-03 21:17:12 2025-12-04 17:55:01
1. 一些累计更新一些BUG修复 1. 一些累计更新一些BUG修复
2. 优化客户端数据同步,减少服务器流量 2. 优化客户端数据同步,减少服务器流量
3. 去除cdkey改为发电解锁中继速度 3. 去除cdkey改为发电解锁中继速度

View File

@@ -8,7 +8,7 @@ sidebar_position: 1
:::tip[说明] :::tip[说明]
#### 1.2、加入组织 #### 1.2、一起探讨
<a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">你可以加入QQ群1121552990</a> <a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">你可以加入QQ群1121552990</a>
@@ -16,6 +16,9 @@ sidebar_position: 1
简单视频说明:<a href="https://www.bilibili.com/video/BV1PDpxz9EcW" target="_blank">B站视频</a> 简单视频说明:<a href="https://www.bilibili.com/video/BV1PDpxz9EcW" target="_blank">B站视频</a>
<div style={{color:'red',fontSize:'20px'}}>郑重声明linker的目标是完全自由、受控不是傻瓜式工具需要具备一定的入手基础。</div>
-- --
::: :::

View File

@@ -162,6 +162,19 @@ namespace linker.messenger.api
Socks5Flow = 51, Socks5Flow = 51,
[AccessDisplay("查看隧道流量")] [AccessDisplay("查看隧道流量")]
TunnelFlow = 52, TunnelFlow = 52,
[AccessDisplay("导入中继节点")]
ImportRelayNode = 53,
[AccessDisplay("删除中继节点")]
RemoveRelayNode = 54,
[AccessDisplay("修改中继节点")]
UpdateRelayNode = 55,
[AccessDisplay("分享中继节点")]
ShareRelayNode = 56,
[AccessDisplay("重启中继节点")]
RebootRelayNode = 57,
[AccessDisplay("更新中继节点")]
UpgradeRelayNode = 58,
} }
public sealed class AccessTextInfo public sealed class AccessTextInfo

View File

@@ -61,6 +61,7 @@ namespace linker.messenger.relay.client
{ {
relayClientStore.SetDefaultNodeId(info.Data.Key); relayClientStore.SetDefaultNodeId(info.Data.Key);
relayClientStore.SetDefaultProtocol(info.Data.Value); relayClientStore.SetDefaultProtocol(info.Data.Value);
relayClientStore.Confirm();
} }
} }
@@ -70,7 +71,8 @@ namespace linker.messenger.relay.client
var resp = await messengerSender.SendReply(new MessageRequestWrap var resp = await messengerSender.SendReply(new MessageRequestWrap
{ {
Connection = signInClientState.Connection, Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.Share, MessengerId = (ushort)RelayMessengerIds.ShareForward,
Payload = serializer.Serialize(param.Content)
}); });
return resp.Code == MessageResponeCodes.OK ? serializer.Deserialize<string>(resp.Data.Span) : $"network error:{resp.Code}"; return resp.Code == MessageResponeCodes.OK ? serializer.Deserialize<string>(resp.Data.Span) : $"network error:{resp.Code}";
} }
@@ -90,10 +92,44 @@ namespace linker.messenger.relay.client
{ {
Connection = signInClientState.Connection, Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.Remove, MessengerId = (ushort)RelayMessengerIds.Remove,
Payload = serializer.Serialize(int.Parse(param.Content)) Payload = serializer.Serialize(param.Content)
}); });
return resp.Code == MessageResponeCodes.OK ? serializer.Deserialize<string>(resp.Data.Span) : $"network error:{resp.Code}"; return resp.Code == MessageResponeCodes.OK ? serializer.Deserialize<string>(resp.Data.Span) : $"network error:{resp.Code}";
} }
public async Task<bool> Update(ApiControllerParamsInfo param)
{
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.UpdateForward,
Payload = serializer.Serialize(param.Content.DeJson<RelayServerNodeStoreInfo>())
});
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
public async Task<bool> Upgrade(ApiControllerParamsInfo param)
{
KeyValueInfo<string, string> info = param.Content.DeJson<KeyValueInfo<string, string>>();
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.UpgradeForward,
Payload = serializer.Serialize(new KeyValuePair<string, string>(info.Key, info.Value))
});
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
public async Task<bool> Exit(ApiControllerParamsInfo param)
{
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.ExitForward,
Payload = serializer.Serialize(param.Content)
});
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
} }

View File

@@ -13,8 +13,39 @@ namespace linker.messenger.relay.messenger
/// </summary> /// </summary>
public class RelayClientMessenger : IMessenger public class RelayClientMessenger : IMessenger
{ {
public RelayClientMessenger() private readonly ISerializer serializer;
private readonly RelayServerNodeReportTransfer relayServerNodeReportTransfer;
public RelayClientMessenger(SignInServerCaching signCaching, ISerializer serializer, RelayServerNodeReportTransfer relayServerNodeReportTransfer)
{ {
this.serializer = serializer;
this.relayServerNodeReportTransfer = relayServerNodeReportTransfer;
}
[MessengerId((ushort)RelayMessengerIds.Share)]
public async Task Share(IConnection connection)
{
string masterKey = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
connection.Write(serializer.Serialize(await relayServerNodeReportTransfer.GetShareKey(masterKey)));
}
[MessengerId((ushort)RelayMessengerIds.Update)]
public async Task Update(IConnection connection)
{
RelayServerNodeStoreInfo info = serializer.Deserialize<RelayServerNodeStoreInfo>(connection.ReceiveRequestWrap.Payload.Span);
await relayServerNodeReportTransfer.Update(info).ConfigureAwait(false);
}
[MessengerId((ushort)RelayMessengerIds.Upgrade)]
public async Task Upgrade(IConnection connection)
{
KeyValuePair<string, string> info = serializer.Deserialize<KeyValuePair<string, string>>(connection.ReceiveRequestWrap.Payload.Span);
await relayServerNodeReportTransfer.Upgrade(info.Key, info.Value).ConfigureAwait(false);
}
[MessengerId((ushort)RelayMessengerIds.Exit)]
public async Task Exit(IConnection connection)
{
string masterKey = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
await relayServerNodeReportTransfer.Exit(masterKey).ConfigureAwait(false);
} }
} }
@@ -123,15 +154,16 @@ namespace linker.messenger.relay.messenger
} }
[MessengerId((ushort)RelayMessengerIds.Share)] [MessengerId((ushort)RelayMessengerIds.ShareForward)]
public async Task Share(IConnection connection) public async Task ShareForward(IConnection connection)
{ {
string id = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo from) == false || from.Super == false) if (signCaching.TryGet(connection.Id, out SignCacheInfo from) == false || from.Super == false)
{ {
connection.Write(serializer.Serialize("need super key")); connection.Write(serializer.Serialize("need super key"));
return; return;
} }
connection.Write(serializer.Serialize(relayServerNodeReportTransfer.Config.ShareKey)); connection.Write(serializer.Serialize(await relayServerNodeReportTransfer.GetShareKeyForward(id)));
} }
[MessengerId((ushort)RelayMessengerIds.Import)] [MessengerId((ushort)RelayMessengerIds.Import)]
public async Task Import(IConnection connection) public async Task Import(IConnection connection)
@@ -150,7 +182,7 @@ namespace linker.messenger.relay.messenger
[MessengerId((ushort)RelayMessengerIds.Remove)] [MessengerId((ushort)RelayMessengerIds.Remove)]
public async Task Remove(IConnection connection) public async Task Remove(IConnection connection)
{ {
int id = serializer.Deserialize<int>(connection.ReceiveRequestWrap.Payload.Span); string id = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo from) == false || from.Super == false) if (signCaching.TryGet(connection.Id, out SignCacheInfo from) == false || from.Super == false)
{ {
connection.Write(serializer.Serialize("need super key")); connection.Write(serializer.Serialize("need super key"));
@@ -160,7 +192,47 @@ namespace linker.messenger.relay.messenger
bool result = await relayServerNodeReportTransfer.Remove(id).ConfigureAwait(false); bool result = await relayServerNodeReportTransfer.Remove(id).ConfigureAwait(false);
connection.Write(serializer.Serialize(result ? string.Empty : "remove fail")); connection.Write(serializer.Serialize(result ? string.Empty : "remove fail"));
} }
[MessengerId((ushort)RelayMessengerIds.UpdateForward)]
public async Task UpdateForward(IConnection connection)
{
RelayServerNodeStoreInfo info = serializer.Deserialize<RelayServerNodeStoreInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo from) == false || from.Super == false)
{
connection.Write(Helper.FalseArray);
return;
}
bool result = await relayServerNodeReportTransfer.UpdateForward(info).ConfigureAwait(false);
connection.Write(result ? Helper.TrueArray : Helper.FalseArray);
}
[MessengerId((ushort)RelayMessengerIds.UpgradeForward)]
public async Task UpgradeForward(IConnection connection)
{
KeyValuePair<string, string> info = serializer.Deserialize<KeyValuePair<string, string>>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo from) == false || from.Super == false)
{
connection.Write(Helper.FalseArray);
return;
}
bool result = await relayServerNodeReportTransfer.UpgradeForward(info.Key, info.Value).ConfigureAwait(false);
connection.Write(result ? Helper.TrueArray : Helper.FalseArray);
}
[MessengerId((ushort)RelayMessengerIds.ExitForward)]
public async Task ExitForward(IConnection connection)
{
string nodeid = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo from) == false || from.Super == false)
{
connection.Write(Helper.FalseArray);
return;
}
bool result = await relayServerNodeReportTransfer.ExitForward(nodeid).ConfigureAwait(false);
connection.Write(result ? Helper.TrueArray : Helper.FalseArray);
}
[MessengerId((ushort)RelayMessengerIds.NodeReport)] [MessengerId((ushort)RelayMessengerIds.NodeReport)]

View File

@@ -15,9 +15,18 @@
Report = 2135, Report = 2135,
Share = 2136, Share = 2136,
Import = 2137, ShareForward = 2137,
Remove = 2138, Import = 2138,
Remove = 2139,
UpdateForward = 2140,
Update = 2141,
ExitForward = 2142,
Exit = 2143,
UpgradeForward = 2144,
Upgrade = 2145,
Max = 2199 Max = 2199
} }

View File

@@ -74,6 +74,8 @@ namespace linker.messenger.relay.server
public double BandwidthRatio { get; set; } public double BandwidthRatio { get; set; }
public IPEndPoint[] Masters { get; set; } = Array.Empty<IPEndPoint>(); public IPEndPoint[] Masters { get; set; } = Array.Empty<IPEndPoint>();
public string Domain { get; set; } = string.Empty;
} }
public sealed class RelayServerNodeStoreInfo : RelayServerNodeReportInfo public sealed class RelayServerNodeStoreInfo : RelayServerNodeReportInfo

View File

@@ -6,7 +6,8 @@
public Task<RelayServerNodeStoreInfo> GetByNodeId(string nodeId); public Task<RelayServerNodeStoreInfo> GetByNodeId(string nodeId);
public Task<bool> Add(RelayServerNodeStoreInfo info); public Task<bool> Add(RelayServerNodeStoreInfo info);
public Task<bool> Report(RelayServerNodeReportInfo info); public Task<bool> Report(RelayServerNodeReportInfo info);
public Task<bool> Delete(int id); public Task<bool> Delete(string nodeId);
public Task<bool> Update(RelayServerNodeStoreInfo info);
} }

View File

@@ -2,6 +2,7 @@
using linker.libs.extends; using linker.libs.extends;
using linker.libs.timer; using linker.libs.timer;
using linker.messenger.relay.messenger; using linker.messenger.relay.messenger;
using System;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
@@ -93,6 +94,32 @@ namespace linker.messenger.relay.server
return true; return true;
} }
public async Task<string> GetShareKeyForward(string nodeId)
{
RelayServerNodeStoreInfo store = await relayServerNodeStore.GetByNodeId(nodeId);
if (store != null && store.Manageable && relayServerConnectionTransfer.TryGet(ConnectionSideType.Node, nodeId, out var connection))
{
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = connection,
MessengerId = (ushort)RelayMessengerIds.Share,
Payload = serializer.Serialize(store.MasterKey)
});
if (resp.Code == MessageResponeCodes.OK)
{
return serializer.Deserialize<string>(resp.Data.Span);
}
}
return string.Empty;
}
public async Task<string> GetShareKey(string masterKey)
{
if (masterKey != Config.MasterKey) return string.Empty;
return Config.ShareKey;
}
public async Task<string> Import(string shareKey) public async Task<string> Import(string shareKey)
{ {
try try
@@ -117,16 +144,110 @@ namespace linker.messenger.relay.server
} }
return string.Empty; return string.Empty;
} }
public async Task<bool> Remove(int id) public async Task<bool> Remove(string nodeId)
{ {
return await relayServerNodeStore.Delete(id).ConfigureAwait(false); if (nodeId == Config.NodeId) return false;
return await relayServerNodeStore.Delete(nodeId).ConfigureAwait(false);
}
public async Task<bool> UpdateForward(RelayServerNodeStoreInfo info)
{
RelayServerNodeStoreInfo store = await relayServerNodeStore.GetByNodeId(info.NodeId);
if (store != null && store.Manageable && relayServerConnectionTransfer.TryGet(ConnectionSideType.Node, info.NodeId, out var connection))
{
info.MasterKey = store.MasterKey;
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = connection,
MessengerId = (ushort)RelayMessengerIds.Update,
Payload = serializer.Serialize(info)
});
}
return await relayServerNodeStore.Update(info).ConfigureAwait(false);
}
public async Task<bool> Update(RelayServerNodeStoreInfo info)
{
if (info.MasterKey != Config.MasterKey) return false;
Config.Connections = info.Connections;
Config.MasterKey = info.MasterKey;
Config.Bandwidth = info.Bandwidth;
Config.Protocol = info.Protocol;
Config.DataEachMonth = info.DataEachMonth;
Config.DataRemain = info.DataRemain;
Config.Logo = info.Logo;
Config.Name = info.Name;
Config.Url = info.Url;
Config.Domain = info.Domain;
relayServerConfigStore.Confirm();
return true;
}
public async Task<bool> UpgradeForward(string nodeId, string version)
{
RelayServerNodeStoreInfo store = await relayServerNodeStore.GetByNodeId(nodeId);
if (store != null && store.Manageable && relayServerConnectionTransfer.TryGet(ConnectionSideType.Node, nodeId, out var connection))
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = connection,
MessengerId = (ushort)RelayMessengerIds.Update,
Payload = serializer.Serialize(new KeyValuePair<string, string>(store.MasterKey, version))
});
return true;
}
return false;
}
public async Task<bool> Upgrade(string masterKey, string version)
{
if (masterKey != Config.MasterKey) return false;
Helper.AppUpdate(version);
return true;
}
public async Task<bool> ExitForward(string nodeId)
{
RelayServerNodeStoreInfo store = await relayServerNodeStore.GetByNodeId(nodeId);
if (store != null && store.Manageable && relayServerConnectionTransfer.TryGet(ConnectionSideType.Node, nodeId, out var connection))
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = connection,
MessengerId = (ushort)RelayMessengerIds.Update,
Payload = serializer.Serialize(new KeyValuePair<string, string>(store.MasterKey, nodeId))
});
return true;
}
return false;
}
public async Task<bool> Exit(string masterKey)
{
if (masterKey != Config.MasterKey) return false;
Helper.AppExit(-1);
return true;
} }
public async Task<List<RelayServerNodeStoreInfo>> GetNodes(bool validated, string userid, string machineId) public async Task<List<RelayServerNodeStoreInfo>> GetNodes(bool validated, string userid, string machineId)
{ {
var nodes = (await relayServerWhiteListStore.GetNodes(userid, machineId)).Where(c => c.Bandwidth >= 0).SelectMany(c => c.Nodes); var nodes = (await relayServerWhiteListStore.GetNodes(userid, machineId)).Where(c => c.Bandwidth >= 0).SelectMany(c => c.Nodes);
var result = (await relayServerNodeStore.GetAll()) var list = await relayServerNodeStore.GetAll();
list.ForEach(c =>
{
c.MasterKey = string.Empty;
});
var result = list
.Where(c => validated || Environment.TickCount64 - c.LastTicks < 15000) .Where(c => validated || Environment.TickCount64 - c.LastTicks < 15000)
.Where(c => .Where(c =>
{ {
@@ -146,7 +267,13 @@ namespace linker.messenger.relay.server
} }
public async Task<List<RelayServerNodeStoreInfo>> GetPublicNodes() public async Task<List<RelayServerNodeStoreInfo>> GetPublicNodes()
{ {
var result = (await relayServerNodeStore.GetAll()) var list = await relayServerNodeStore.GetAll();
list.ForEach(c =>
{
c.MasterKey = string.Empty;
});
var result = list
.Where(c => Environment.TickCount64 - c.LastTicks < 15000) .Where(c => Environment.TickCount64 - c.LastTicks < 15000)
.Where(c => c.Public) .Where(c => c.Public)
.OrderByDescending(c => c.LastTicks); .OrderByDescending(c => c.LastTicks);
@@ -227,7 +354,7 @@ namespace linker.messenger.relay.server
BandwidthRatio = Math.Round(diff / 5, 2), BandwidthRatio = Math.Round(diff / 5, 2),
Version = VersionHelper.Version, Version = VersionHelper.Version,
Masters = connections.Select(c => c.Address).ToArray(), Masters = connections.Select(c => c.Address).ToArray(),
MasterKey = config.MasterKey, MasterKey = config.MasterKey,
}; };
byte[] memory = serializer.Serialize(info); byte[] memory = serializer.Serialize(info);
var tasks = connections.Select(c => messengerSender.SendOnly(new MessageRequestWrap var tasks = connections.Select(c => messengerSender.SendOnly(new MessageRequestWrap

View File

@@ -63,8 +63,12 @@ namespace linker.messenger.relay.server
return serializer.Deserialize<RelayMessageInfo>(crypto.Decode(buffer, 0, length).Span); return serializer.Deserialize<RelayMessageInfo>(crypto.Decode(buffer, 0, length).Span);
} }
catch (Exception) catch (Exception ex)
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
} }
finally finally
{ {
@@ -93,7 +97,7 @@ namespace linker.messenger.relay.server
if (relayCache == null) if (relayCache == null)
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"relay {relayMessage.Type} get cache fail,flowid:{relayMessage.FlowId}"); LoggerHelper.Instance.Error($"relay server {relayMessage.Type} get cache fail,flowid:{relayMessage.FlowId}");
await socket.SendAsync(Helper.FalseArray).ConfigureAwait(false); await socket.SendAsync(Helper.FalseArray).ConfigureAwait(false);
socket.SafeClose(); socket.SafeClose();
return; return;
@@ -102,7 +106,7 @@ namespace linker.messenger.relay.server
if (relayMessage.Type == RelayMessengerType.Ask && relayServerNodeTransfer.Validate(relayCache) == false) if (relayMessage.Type == RelayMessengerType.Ask && relayServerNodeTransfer.Validate(relayCache) == false)
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"relay {relayMessage.Type} validate false,flowid:{relayMessage.FlowId}"); LoggerHelper.Instance.Error($"relay server {relayMessage.Type} validate false,flowid:{relayMessage.FlowId}");
await socket.SendAsync(Helper.FalseArray).ConfigureAwait(false); await socket.SendAsync(Helper.FalseArray).ConfigureAwait(false);
socket.SafeClose(); socket.SafeClose();
return; return;
@@ -122,14 +126,17 @@ namespace linker.messenger.relay.server
} }
TaskCompletionSource<Socket> tcs = new TaskCompletionSource<Socket>(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource<Socket> tcs = new TaskCompletionSource<Socket>(TaskCreationOptions.RunContinuationsAsynchronously);
Socket answerSocket = null;
IPEndPoint fromep = socket.RemoteEndPoint as IPEndPoint,toep = null;
try try
{ {
await socket.SendAsync(Helper.TrueArray).ConfigureAwait(false); await socket.SendAsync(Helper.TrueArray).ConfigureAwait(false);
relayDic.TryAdd(relayCache.FlowId, tcs); relayDic.TryAdd(relayCache.FlowId, tcs);
Socket answerSocket = await tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(15000)).ConfigureAwait(false); answerSocket = await tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(15000)).ConfigureAwait(false);
await answerSocket.SendAsync(Helper.TrueArray).ConfigureAwait(false); await answerSocket.SendAsync(Helper.TrueArray).ConfigureAwait(false);
toep = answerSocket.RemoteEndPoint as IPEndPoint;
LoggerHelper.Instance.Error($"relay start {socket.RemoteEndPoint} to {answerSocket.RemoteEndPoint}"); LoggerHelper.Instance.Info($"relay server start {fromep} to {toep}");
string flowKey = relayMessage.Type == RelayMessengerType.Ask ? $"{relayMessage.FromId}->{relayMessage.ToId}" : $"{relayMessage.ToId}->{relayMessage.FromId}"; string flowKey = relayMessage.Type == RelayMessengerType.Ask ? $"{relayMessage.FromId}->{relayMessage.ToId}" : $"{relayMessage.ToId}->{relayMessage.FromId}";
RelayTrafficCacheInfo trafficCacheInfo = new RelayTrafficCacheInfo { Cache = relayCache, Sendt = 0, Limit = new RelaySpeedLimit(), Key = flowKey }; RelayTrafficCacheInfo trafficCacheInfo = new RelayTrafficCacheInfo { Cache = relayCache, Sendt = 0, Limit = new RelaySpeedLimit(), Key = flowKey };
@@ -138,26 +145,26 @@ namespace linker.messenger.relay.server
await Task.WhenAll(CopyToAsync(trafficCacheInfo, socket, answerSocket), CopyToAsync(trafficCacheInfo, answerSocket, socket)).ConfigureAwait(false); await Task.WhenAll(CopyToAsync(trafficCacheInfo, socket, answerSocket), CopyToAsync(trafficCacheInfo, answerSocket, socket)).ConfigureAwait(false);
relayServerNodeTransfer.DecrementConnectionNum(); relayServerNodeTransfer.DecrementConnectionNum();
relayServerNodeTransfer.RemoveTrafficCache(trafficCacheInfo); relayServerNodeTransfer.RemoveTrafficCache(trafficCacheInfo);
LoggerHelper.Instance.Error($"relay end {socket.RemoteEndPoint} to {answerSocket.RemoteEndPoint}");
} }
catch (Exception ex) catch (Exception ex)
{ {
tcs.TrySetResult(null); tcs.TrySetResult(null);
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"{ex},flowid:{relayMessage.FlowId}"); LoggerHelper.Instance.Error($"relay server error {ex},flowid:{relayMessage.FlowId}");
} }
finally finally
{ {
LoggerHelper.Instance.Info($"relay server end {fromep} to {toep}");
relayDic.TryRemove(relayCache.FlowId, out _); relayDic.TryRemove(relayCache.FlowId, out _);
socket.SafeClose(); socket?.SafeClose();
answerSocket?.SafeClose();
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error(ex); LoggerHelper.Instance.Error(ex);
socket.SafeClose(); socket?.SafeClose();
} }
} }
private async Task CopyToAsync(RelayTrafficCacheInfo trafficCacheInfo, Socket source, Socket destination) private async Task CopyToAsync(RelayTrafficCacheInfo trafficCacheInfo, Socket source, Socket destination)

View File

@@ -31,7 +31,7 @@ namespace linker.tunnel.transport
public bool SSL => true; public bool SSL => true;
public bool DisableSSL => false; public bool DisableSSL => true;
public byte Order => 0; public byte Order => 0;
@@ -70,21 +70,21 @@ namespace linker.tunnel.transport
List<RelayServerNodeStoreInfo> nodes = ask.Nodes; List<RelayServerNodeStoreInfo> nodes = ask.Nodes;
if (ask.Nodes.Count == 0) if (ask.Nodes.Count == 0)
{ {
throw new Exception("relay ask fail,no relay nodes"); throw new Exception("relay client ask fail,no relay nodes");
} }
//连接中继节点服务器 //连接中继节点服务器
Socket socket = await ConnectNodeServer(tunnelTransportInfo, ask).ConfigureAwait(false); Socket socket = await ConnectNodeServer(tunnelTransportInfo, ask).ConfigureAwait(false);
if(socket == null) if(socket == null)
{ {
throw new Exception("connect relay node server fail"); throw new Exception("relay client connect node server fail");
} }
tunnelTransportInfo.TransactionTag = ask.Info.ToJson(); tunnelTransportInfo.TransactionTag = ask.Info.ToJson();
//让对方确认中继 //让对方确认中继
if (await tunnelMessengerAdapter.SendConnectBegin(tunnelTransportInfo).ConfigureAwait(false) == false) if (await tunnelMessengerAdapter.SendConnectBegin(tunnelTransportInfo).ConfigureAwait(false) == false)
{ {
throw new Exception("relay begin fail"); throw new Exception("relay client begin fail");
} }
//成功建立连接, //成功建立连接,
@@ -182,7 +182,7 @@ namespace linker.tunnel.transport
} }
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Debug($"connect relay server {ep}"); LoggerHelper.Instance.Debug($"relay client connect server {ep}");
//连接中继服务器 //连接中继服务器
Socket socket = new Socket(ep.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); Socket socket = new Socket(ep.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
@@ -191,7 +191,7 @@ namespace linker.tunnel.transport
await socket.ConnectAsync(ep).WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false); await socket.ConnectAsync(ep).WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false);
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{ {
LoggerHelper.Instance.Debug($"relay connected {ep}"); LoggerHelper.Instance.Debug($"relay client connected {ep}");
} }
//建立关联 //建立关联
@@ -254,10 +254,15 @@ namespace linker.tunnel.transport
await socket.SendAsync(buffer.Memory.Slice(0, sendBytes.Length + 5)).ConfigureAwait(false); await socket.SendAsync(buffer.Memory.Slice(0, sendBytes.Length + 5)).ConfigureAwait(false);
int length = await socket.ReceiveAsync(buffer.Memory.Slice(0, 1)).AsTask().WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false); int length = await socket.ReceiveAsync(buffer.Memory.Slice(0, 1)).AsTask().WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false);
return length == 1 && buffer.Memory.Span[0] == 0;
return length == 1 && buffer.Memory.Slice(0, 1).Span.SequenceEqual(Helper.TrueArray);
} }
catch (Exception) catch (Exception ex)
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
} }
return false; return false;
} }
@@ -268,7 +273,7 @@ namespace linker.tunnel.transport
{ {
if (tunnelTransportInfo.SSL && certificate == null) if (tunnelTransportInfo.SSL && certificate == null)
{ {
LoggerHelper.Instance.Error($"{Name}->ssl Certificate not found"); LoggerHelper.Instance.Error($"relay client {Name}->ssl Certificate not found");
await tunnelMessengerAdapter.SendConnectFail(tunnelTransportInfo).ConfigureAwait(false); await tunnelMessengerAdapter.SendConnectFail(tunnelTransportInfo).ConfigureAwait(false);
return; return;
} }
@@ -301,7 +306,7 @@ namespace linker.tunnel.transport
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{ {
LoggerHelper.Instance.Error($"relay connect server {ep} {ex}"); LoggerHelper.Instance.Error($"relay client connect server {ep} {ex}");
} }
} }
} }
@@ -349,7 +354,7 @@ namespace linker.tunnel.transport
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{ {
LoggerHelper.Instance.Error($"relay wait ssl {ex}"); LoggerHelper.Instance.Error($"relay client wait ssl {ex}");
} }
socket.SafeClose(); socket.SafeClose();
} }

View File

@@ -52,7 +52,7 @@ namespace linker.messenger.serializer.memorypack
value = null; value = null;
return; return;
} }
value = new AccessUpdateInfo() ; value = new AccessUpdateInfo();
reader.TryReadObjectHeader(out byte count); reader.TryReadObjectHeader(out byte count);
value.FromMachineId = reader.ReadValue<string>(); value.FromMachineId = reader.ReadValue<string>();
value.ToMachineId = reader.ReadValue<string>(); value.ToMachineId = reader.ReadValue<string>();
@@ -74,10 +74,13 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackInclude] [MemoryPackInclude]
BitArray Access => info.Access; BitArray Access => info.Access;
[MemoryPackInclude]
bool FullAccess => info.FullAccess;
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableAccessBitsUpdateInfo(string fromMachineId, string toMachineId, BitArray access) SerializableAccessBitsUpdateInfo(string fromMachineId, string toMachineId, BitArray access, bool fullAccess)
{ {
var info = new AccessBitsUpdateInfo { FromMachineId = fromMachineId, ToMachineId = toMachineId, Access = access }; var info = new AccessBitsUpdateInfo { FromMachineId = fromMachineId, ToMachineId = toMachineId, Access = access, FullAccess = fullAccess };
this.info = info; this.info = info;
} }
@@ -110,10 +113,13 @@ namespace linker.messenger.serializer.memorypack
value = new AccessBitsUpdateInfo(); value = new AccessBitsUpdateInfo();
reader.TryReadObjectHeader(out byte count); reader.TryReadObjectHeader(out byte count);
value.FromMachineId = reader.ReadValue<string>(); value.FromMachineId = reader.ReadValue<string>();
value.ToMachineId = reader.ReadValue<string>(); value.ToMachineId = reader.ReadValue<string>();
value.Access = reader.ReadValue<BitArray>(); value.Access = reader.ReadValue<BitArray>();
if (count > 3)
{
value.FullAccess = reader.ReadValue<bool>();
}
} }
} }

View File

@@ -251,10 +251,12 @@ namespace linker.messenger.serializer.memorypack
double BandwidthRatio => info.BandwidthRatio; double BandwidthRatio => info.BandwidthRatio;
[MemoryPackInclude] [MemoryPackInclude]
IPEndPoint[] Masters => info.Masters; IPEndPoint[] Masters => info.Masters;
[MemoryPackInclude]
string Domain => info.Domain;
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableRelayServerNodeReportInfo(string nodeId, string name, TunnelProtocolType protocol, int connections, int bandwidth, int dataEachMonth, SerializableRelayServerNodeReportInfo(string nodeId, string name, TunnelProtocolType protocol, int connections, int bandwidth, int dataEachMonth,
long dataRemain, string url, string logo, string masterKey, string version, int connectionsRatio, double bandwidthRatio, IPEndPoint[] masters) long dataRemain, string url, string logo, string masterKey, string version, int connectionsRatio, double bandwidthRatio, IPEndPoint[] masters, string domain)
{ {
var info = new RelayServerNodeReportInfo var info = new RelayServerNodeReportInfo
{ {
@@ -271,7 +273,8 @@ namespace linker.messenger.serializer.memorypack
Version = version, Version = version,
ConnectionsRatio = connectionsRatio, ConnectionsRatio = connectionsRatio,
BandwidthRatio = bandwidthRatio, BandwidthRatio = bandwidthRatio,
Masters = masters Masters = masters,
Domain = domain
}; };
this.info = info; this.info = info;
} }
@@ -319,6 +322,7 @@ namespace linker.messenger.serializer.memorypack
value.ConnectionsRatio = reader.ReadValue<int>(); value.ConnectionsRatio = reader.ReadValue<int>();
value.BandwidthRatio = reader.ReadValue<double>(); value.BandwidthRatio = reader.ReadValue<double>();
value.Masters = reader.ReadValue<IPEndPoint[]>(); value.Masters = reader.ReadValue<IPEndPoint[]>();
value.Domain = reader.ReadValue<string>();
} }
} }
@@ -357,7 +361,9 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackInclude] [MemoryPackInclude]
double BandwidthRatio => info.BandwidthRatio; double BandwidthRatio => info.BandwidthRatio;
[MemoryPackInclude] [MemoryPackInclude]
IPEndPoint[] Servers => info.Masters; IPEndPoint[] Masters => info.Masters;
[MemoryPackInclude]
string Domain => info.Domain;
[MemoryPackInclude] [MemoryPackInclude]
@@ -376,7 +382,7 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableRelayServerNodeStoreInfo(string nodeId, string name, TunnelProtocolType protocol, int connections, int bandwidth, int dataEachMonth, SerializableRelayServerNodeStoreInfo(string nodeId, string name, TunnelProtocolType protocol, int connections, int bandwidth, int dataEachMonth,
long dataRemain, string url, string logo, string masterKey, string version, int connectionsRatio, double bandwidthRatio, IPEndPoint[] servers, long dataRemain, string url, string logo, string masterKey, string version, int connectionsRatio, double bandwidthRatio, IPEndPoint[] masters, string domain,
int id, string host, int bandwidthEachConnection, bool Public, long lastTicks, bool manageable) int id, string host, int bandwidthEachConnection, bool Public, long lastTicks, bool manageable)
{ {
var info = new RelayServerNodeStoreInfo var info = new RelayServerNodeStoreInfo
@@ -394,7 +400,8 @@ namespace linker.messenger.serializer.memorypack
Version = version, Version = version,
ConnectionsRatio = connectionsRatio, ConnectionsRatio = connectionsRatio,
BandwidthRatio = bandwidthRatio, BandwidthRatio = bandwidthRatio,
Masters = servers, Masters = masters,
Domain = domain,
Id = id, Id = id,
Host = host, Host = host,
BandwidthEach = bandwidthEachConnection, BandwidthEach = bandwidthEachConnection,
@@ -448,6 +455,7 @@ namespace linker.messenger.serializer.memorypack
value.ConnectionsRatio = reader.ReadValue<int>(); value.ConnectionsRatio = reader.ReadValue<int>();
value.BandwidthRatio = reader.ReadValue<double>(); value.BandwidthRatio = reader.ReadValue<double>();
value.Masters = reader.ReadValue<IPEndPoint[]>(); value.Masters = reader.ReadValue<IPEndPoint[]>();
value.Domain = reader.ReadValue<string>();
value.Id = reader.ReadValue<int>(); value.Id = reader.ReadValue<int>();
value.Host = reader.ReadValue<string>(); value.Host = reader.ReadValue<string>();
value.BandwidthEach = reader.ReadValue<int>(); value.BandwidthEach = reader.ReadValue<int>();

View File

@@ -244,11 +244,12 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackInclude] [MemoryPackInclude]
uint FlowId => tunnelTransportInfo.FlowId; uint FlowId => tunnelTransportInfo.FlowId;
//[MemoryPackInclude] [MemoryPackInclude]
//string TransactionTag => tunnelTransportInfo.TransactionTag; string TransactionTag => tunnelTransportInfo.TransactionTag;
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableTunnelTransportInfo(TunnelTransportWanPortInfo local, TunnelTransportWanPortInfo remote, string transactionId, TunnelProtocolType transportType, string transportName, TunnelDirection direction, bool ssl, byte bufferSize, uint flowid/*, string transactionTag*/) SerializableTunnelTransportInfo(TunnelTransportWanPortInfo local, TunnelTransportWanPortInfo remote, string transactionId,
TunnelProtocolType transportType, string transportName, TunnelDirection direction, bool ssl, byte bufferSize, uint flowid, string transactionTag)
{ {
var tunnelTransportInfo = new TunnelTransportInfo var tunnelTransportInfo = new TunnelTransportInfo
{ {
@@ -261,7 +262,7 @@ namespace linker.messenger.serializer.memorypack
SSL = ssl, SSL = ssl,
BufferSize = bufferSize, BufferSize = bufferSize,
FlowId = flowid, FlowId = flowid,
// TransactionTag = transactionTag TransactionTag = transactionTag
}; };
this.tunnelTransportInfo = tunnelTransportInfo; this.tunnelTransportInfo = tunnelTransportInfo;
} }
@@ -293,8 +294,19 @@ namespace linker.messenger.serializer.memorypack
return; return;
} }
var wrapped = reader.ReadPackable<SerializableTunnelTransportInfo>(); value = new TunnelTransportInfo();
value = wrapped.tunnelTransportInfo; reader.TryReadObjectHeader(out byte count);
value.Local = reader.ReadValue<TunnelTransportWanPortInfo>();
value.Remote = reader.ReadValue<TunnelTransportWanPortInfo>();
value.TransactionId = reader.ReadValue<string>();
value.TransportType = reader.ReadValue<TunnelProtocolType>();
value.TransportName = reader.ReadValue<string>();
value.Direction = reader.ReadValue<TunnelDirection>();
value.SSL = reader.ReadValue<bool>();
value.BufferSize = reader.ReadValue<byte>();
value.FlowId = reader.ReadValue<uint>();
if (count > 9)
value.TransactionTag = reader.ReadValue<string>();
} }
} }

View File

@@ -25,9 +25,9 @@ namespace linker.messenger.store.file.relay
return await Task.FromResult(true).ConfigureAwait(false); return await Task.FromResult(true).ConfigureAwait(false);
} }
public async Task<bool> Delete(int id) public async Task<bool> Delete(string nodeId)
{ {
return await Task.FromResult(liteCollection.Delete(id)).ConfigureAwait(false); return await Task.FromResult(liteCollection.DeleteMany(c => c.NodeId == nodeId) > 0).ConfigureAwait(false);
} }
public async Task<List<RelayServerNodeStoreInfo>> GetAll() public async Task<List<RelayServerNodeStoreInfo>> GetAll()
@@ -59,7 +59,18 @@ namespace linker.messenger.store.file.relay
MasterKey = info.MasterKey, MasterKey = info.MasterKey,
Masters = info.Masters, Masters = info.Masters,
//是我初始化的,可以管理 //是我初始化的,可以管理
Manageable = false//info.MasterKey == md5 Manageable = info.MasterKey == md5
}, c => c.NodeId == info.NodeId);
return await Task.FromResult(length > 0).ConfigureAwait(false);
}
public async Task<bool> Update(RelayServerNodeStoreInfo info)
{
int length = liteCollection.UpdateMany(p => new RelayServerNodeStoreInfo
{
DataEachMonth = info.DataEachMonth,
Public = info.Public,
}, c => c.NodeId == info.NodeId); }, c => c.NodeId == info.NodeId);
return await Task.FromResult(length > 0).ConfigureAwait(false); ; return await Task.FromResult(length > 0).ConfigureAwait(false); ;

View File

@@ -8,9 +8,9 @@
"name": "linker.web", "name": "linker.web",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.2",
"core-js": "^3.38.0", "core-js": "^3.38.0",
"element-plus": "^2.8.0", "element-plus": "^2.11.9",
"moment": "^2.30.1", "moment": "^2.30.1",
"vue": "^3.4.38", "vue": "^3.4.38",
"vue-i18n": "^11.0.1", "vue-i18n": "^11.0.1",
@@ -1710,8 +1710,9 @@
} }
}, },
"node_modules/@element-plus/icons-vue": { "node_modules/@element-plus/icons-vue": {
"version": "2.3.1", "version": "2.3.2",
"license": "MIT", "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"peerDependencies": { "peerDependencies": {
"vue": "^3.2.0" "vue": "^3.2.0"
} }
@@ -2114,8 +2115,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/lodash": { "node_modules/@types/lodash": {
"version": "4.17.0", "version": "4.17.21",
"license": "MIT" "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ=="
}, },
"node_modules/@types/lodash-es": { "node_modules/@types/lodash-es": {
"version": "4.17.12", "version": "4.17.12",
@@ -4536,8 +4538,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/dayjs": { "node_modules/dayjs": {
"version": "1.11.10", "version": "1.11.19",
"license": "MIT" "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="
}, },
"node_modules/debounce": { "node_modules/debounce": {
"version": "1.2.1", "version": "1.2.1",
@@ -4896,22 +4899,22 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/element-plus": { "node_modules/element-plus": {
"version": "2.8.0", "version": "2.11.9",
"license": "MIT", "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.11.9.tgz",
"integrity": "sha512-yTckX+fMGDGiBHVL1gpwfmjEc8P8OwuyU5ZX3f4FhMy2OdC2Y+OwQYWUXmuB+jFMukuSdnGYXYgHq6muBjSxTg==",
"dependencies": { "dependencies": {
"@ctrl/tinycolor": "^3.4.1", "@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.2",
"@floating-ui/dom": "^1.0.1", "@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.14.182", "@types/lodash": "^4.17.20",
"@types/lodash-es": "^4.17.6", "@types/lodash-es": "^4.17.12",
"@vueuse/core": "^9.1.0", "@vueuse/core": "^9.1.0",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"dayjs": "^1.11.3", "dayjs": "^1.11.19",
"escape-html": "^1.0.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lodash-unified": "^1.0.2", "lodash-unified": "^1.0.3",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.2.0" "normalize-wheel-es": "^1.2.0"
}, },
@@ -5020,6 +5023,7 @@
}, },
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {

View File

@@ -7,9 +7,9 @@
"build": "vue-cli-service build" "build": "vue-cli-service build"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.2",
"core-js": "^3.38.0", "core-js": "^3.38.0",
"element-plus": "^2.8.0", "element-plus": "^2.11.9",
"moment": "^2.30.1", "moment": "^2.30.1",
"vue": "^3.4.38", "vue": "^3.4.38",
"vue-i18n": "^11.0.1", "vue-i18n": "^11.0.1",

View File

@@ -12,12 +12,21 @@ export const setRelaySubscribe = () => {
export const relayConnect = (data) => { export const relayConnect = (data) => {
return sendWebsocketMsg('relay/Connect', data); return sendWebsocketMsg('relay/Connect', data);
} }
export const relayEdit = (data) => { export const relayUpdate= (data) => {
return sendWebsocketMsg('relay/edit', data); return sendWebsocketMsg('relay/update', data);
}
export const relayUpgrade= (data) => {
return sendWebsocketMsg('relay/upgrade', data);
} }
export const relayExit = (id) => { export const relayExit = (id) => {
return sendWebsocketMsg('relay/Exit', id); return sendWebsocketMsg('relay/Exit', id);
} }
export const relayUpdate = (id) => { export const relayRemove = (id) => {
return sendWebsocketMsg('relay/Update', id); return sendWebsocketMsg('relay/Remove', id);
}
export const relayImport = (data) => {
return sendWebsocketMsg('relay/Import', data);
}
export const relayShare = (id) => {
return sendWebsocketMsg('relay/Share', id);
} }

View File

@@ -148,7 +148,9 @@ a.a-line {
.mgb-0 { .mgb-0 {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.mgb-1 {
margin-bottom: 1rem;
}
.mgb-3 { .mgb-3 {
margin-bottom: 3rem; margin-bottom: 3rem;
} }

View File

@@ -212,6 +212,9 @@ export default {
'server.relayExit': 'Restart', 'server.relayExit': 'Restart',
'server.relayUpdate': 'Update', 'server.relayUpdate': 'Update',
'server.relayEdit': 'Edit', 'server.relayEdit': 'Edit',
'server.relayRemove': 'Remove',
'server.relayImport': 'Import relay node',
'server.relayShare': 'Share relay node',
'server.cdkeySecretKey': 'Cdkey secretKey', 'server.cdkeySecretKey': 'Cdkey secretKey',
'server.cdkeyText': 'The cdkey manager can be used when the key is correct', 'server.cdkeyText': 'The cdkey manager can be used when the key is correct',

View File

@@ -290,7 +290,7 @@ export default {
'server.relayName': '名称', 'server.relayName': '名称',
'server.relayFlow': '月流量', 'server.relayFlow': '月流量',
'server.relayFlowLast': '剩余流量', 'server.relayFlowLast': '剩余流量',
'server.relaySpeed': '带宽', 'server.relaySpeed': '单链带宽',
'server.relaySpeed1': '总带宽', 'server.relaySpeed1': '总带宽',
'server.relaySpeed2': '速率', 'server.relaySpeed2': '速率',
'server.relayConnection': '连接数', 'server.relayConnection': '连接数',
@@ -308,6 +308,9 @@ export default {
'server.relayExit': '重启', 'server.relayExit': '重启',
'server.relayUpdate': '更新', 'server.relayUpdate': '更新',
'server.relayEdit': '编辑', 'server.relayEdit': '编辑',
'server.relayRemove': '删除',
'server.relayImport': '导入中继节点',
'server.relayShare': '分享中继节点',
'server.cdkeySecretKey': 'Cdkey密钥', 'server.cdkeySecretKey': 'Cdkey密钥',
'server.cdkeyText': '密钥正确时可管理cdkey', 'server.cdkeyText': '密钥正确时可管理cdkey',

View File

@@ -9,7 +9,7 @@ const routes = [
{ {
path: '/full/index.html', path: '/full/index.html',
name: 'FullIndex', name: 'FullIndex',
meta: { title: 'head.home',access:'FullIndex',icon:'home.svg' }, meta: { title: 'head.home',access:'FullManager',icon:'home.svg' },
component: () => import('@/views/layout/full/list/Index.vue') component: () => import('@/views/layout/full/list/Index.vue')
}, },
{ {

View File

@@ -6,6 +6,9 @@
<el-col :span="8" v-if="globalData.config.Client.FullAccess"> <el-col :span="8" v-if="globalData.config.Client.FullAccess">
<el-checkbox v-model="state.full" ><span class="red">满权限(顶级管理权)</span></el-checkbox> <el-checkbox v-model="state.full" ><span class="red">满权限(顶级管理权)</span></el-checkbox>
</el-col> </el-col>
<el-col :span="6">
<el-input size="small" v-model="state.search"></el-input>
</el-col>
</el-row> </el-row>
<div class="access-wrap scrollbar" :style="{height:`${state.height}rem`}"> <div class="access-wrap scrollbar" :style="{height:`${state.height}rem`}">
<el-checkbox-group v-model="state.checkList" @change="handleCheckedChange"> <el-checkbox-group v-model="state.checkList" @change="handleCheckedChange">
@@ -27,16 +30,17 @@ export default {
setup(props) { setup(props) {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const exclude = ['ExternalShow','Cdkey']
const access = computed(()=>{ const access = computed(()=>{
const json = globalData.value.config.Client.Accesss; const json = globalData.value.config.Client.Accesss;
return Object.keys(json).reduce((arr,key,index)=>{ return Object.keys(json).reduce((arr,key,index)=>{
if(globalData.value.hasAccess(key)){ if(globalData.value.hasAccess(key) && !exclude.includes(key)){
const value = json[key]; const value = json[key];
value.Key = key; value.Key = key;
arr.push(value); arr.push(value);
} }
return arr; return arr;
},[]); },[]).filter(c=>c.Text.includes(state.search));
}); });
const state = reactive({ const state = reactive({
@@ -63,7 +67,9 @@ export default {
], ],
checkAll:false, checkAll:false,
full:false, full:false,
isIndeterminate:false isIndeterminate:false,
search:''
}); });
const getValue = ()=>{ const getValue = ()=>{

View File

@@ -88,7 +88,6 @@ export const provideDevices = () => {
getSignInList(devices.page.Request).then((res) => { getSignInList(devices.page.Request).then((res) => {
devices.page.Request = res.Request; devices.page.Request = res.Request;
devices.page.Count = res.Count; devices.page.Count = res.Count;
console.log(res);
for (let j in res.List) { for (let j in res.List) {
Object.assign(res.List[j], { Object.assign(res.List[j], {
showDel: machineId.value != res.List[j].MachineId && res.List[j].Connected == false, showDel: machineId.value != res.List[j].MachineId && res.List[j].Connected == false,

View File

@@ -106,13 +106,14 @@ export default {
const handleShowAccess = (row,access)=>{ const handleShowAccess = (row,access)=>{
const rowAccess = row.hook_accesss || ''; const rowAccess = row.hook_accesss || '';
const myAccess = globalData.value.config.Client.AccessBits; const myAccess = globalData.value.config.Client.AccessBits;
let maxLength = Math.max(myAccess.length,rowAccess.length); let maxLength = Math.max(myAccess.length,rowAccess.length);
let myValue = myAccess.padEnd(maxLength,'0').split(''); let myValue = myAccess.padEnd(maxLength,'0').split('').map(c=>+c);
let rowValue = rowAccess.padEnd(maxLength,'0').split(''); let rowValue = rowAccess.padEnd(maxLength,'0').split('').map(c=>+c);
return row.showAccess && access.Access return row.showAccess && access.Access
&& myValue.map((v,i)=>{ && myValue.map((v,i)=>{
return (rowValue[i] == '1' && v == '1') || rowValue[i] == '0'; return (rowValue[i] + v >=1 && v == 1);
}).filter(c=>c).length > 0; }).filter(c=>!c).length == 0;
} }
const handleAccess = (row)=>{ const handleAccess = (row)=>{
devices.deviceInfo = row; devices.deviceInfo = row;

View File

@@ -49,7 +49,7 @@
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { reactive, ref, watch } from 'vue' import { reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { relayEdit } from '@/apis/relay'; import { relayUpdate } from '@/apis/relay';
import { Refresh } from '@element-plus/icons-vue' import { Refresh } from '@element-plus/icons-vue'
export default { export default {
props: ['data','modelValue'], props: ['data','modelValue'],
@@ -57,22 +57,12 @@ export default {
components:{Refresh}, components:{Refresh},
setup(props,{emit}) { setup(props,{emit}) {
const {t} = useI18n(); const {t} = useI18n();
const json = JSON.parse(JSON.stringify(props.data));
json.AllowTcp = (json.Protocol & 1) == 1;
json.AllowUdp = (json.Protocol & 2) == 2;
const state = reactive({ const state = reactive({
show:true, show:true,
ruleForm:{ ruleForm:json,
Id:props.data.Id,
Name:props.data.Name,
Connections:props.data.Connections,
BandwidthEach:props.data.BandwidthEach,
Bandwidth:props.data.Bandwidth,
DataEachMonth:props.data.DataEachMonth,
DataRemain:props.data.DataRemain,
Public:props.data.Public,
Url:props.data.Url,
Logo:props.data.Logo,
AllowTcp:(props.data.Protocol & 1) == 1,
AllowUdp:(props.data.Protocol & 2) == 2,
},
rules:{ rules:{
} }
}); });
@@ -95,7 +85,7 @@ export default {
const json = JSON.parse(JSON.stringify(state.ruleForm)); const json = JSON.parse(JSON.stringify(state.ruleForm));
json.Protocol = (json.AllowTcp ? 1 : 0) | (json.AllowUdp ? 2 : 0); json.Protocol = (json.AllowTcp ? 1 : 0) | (json.AllowUdp ? 2 : 0);
relayEdit(json).then((res)=>{ relayUpdate(json).then((res)=>{
if(res){ if(res){
ElMessage.success(t('common.oper')); ElMessage.success(t('common.oper'));
state.show = false; state.show = false;

View File

@@ -2,6 +2,17 @@
<div> <div>
<el-dialog v-model="state.show" :title="$t('server.relayTitle')" width="98%" top="2vh"> <el-dialog v-model="state.show" :title="$t('server.relayTitle')" width="98%" top="2vh">
<div> <div>
<AccessShow value="ImportRelayNode">
<div class="head mgb-1" v-if="state.super">
<div class="flex">
<span class="flex-1"></span>
<div>
<el-button type="success" size="small" @click="handleImport"><el-icon><Plus /></el-icon></el-button>
</div>
<span class="flex-1"></span>
</div>
</div>
</AccessShow>
<el-table :data="state.nodes" size="small" border height="500" stripe> <el-table :data="state.nodes" size="small" border height="500" stripe>
<el-table-column property="Name" :label="$t('server.relayName')"> <el-table-column property="Name" :label="$t('server.relayName')">
<template #default="scope"> <template #default="scope">
@@ -25,7 +36,7 @@
</p> </p>
<p class="flex"> <p class="flex">
<div> <div>
<template v-if="state.syncData.Key == scope.row.Id"> <template v-if="state.syncData.Key == scope.row.NodeId">
<el-checkbox size="small" disabled checked>{{ $t('server.relayDefault') }}</el-checkbox> <el-checkbox size="small" disabled checked>{{ $t('server.relayDefault') }}</el-checkbox>
</template> </template>
<template v-else> <template v-else>
@@ -33,8 +44,12 @@
</template> </template>
</div> </div>
<span class="flex-1"></span> <span class="flex-1"></span>
<a v-if="state.super" href="javascript:;" class="a-line a-edit" @click="handleUpdate(scope.row)"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a> <AccessBoolean value="UpgradeRelayNode">
<a v-else href="javascript:;" class="a-line a-edit"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a> <template #default="{values}">
<a v-if="state.super && values.UpgradeRelayNode" href="javascript:;" class="a-line a-edit" @click="handleUpgrade(scope.row)"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
<a v-else href="javascript:;" class="a-line a-edit"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
</template>
</AccessBoolean>
</p> </p>
</div> </div>
</div> </div>
@@ -79,11 +94,21 @@
<p>{{ scope.row.Delay }}ms</p> <p>{{ scope.row.Delay }}ms</p>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="state.super" property="Manageable" :label="$t('server.relayOper')" width="110"> <el-table-column property="Manageable" :label="$t('server.relayOper')" width="110">
<template #default="scope"> <template #default="scope">
<p> <p>
<el-button v-if="scope.row.Manageable" size="small" @click="handleExit(scope.row)"><el-icon><Refresh /></el-icon></el-button> <AccessBoolean v-if="state.super" value="RemoveRelayNode,UpdateRelayNode,ShareRelayNode,RebootRelayNode">
<el-button size="small" @click="handleEdit(scope.row)"><el-icon><Edit /></el-icon></el-button> <template #default="{values}">
<p>
<el-button v-if="scope.row.Manageable && values.RebootRelayNode" type="warning" size="small" @click="handleExit(scope.row)"><el-icon><Refresh /></el-icon></el-button>
<el-button v-if="values.UpdateRelayNode" size="small" @click="handleEdit(scope.row)"><el-icon><Edit /></el-icon></el-button>
</p>
<p>
<el-button v-if="values.RemoveRelayNode" type="danger" size="small" @click="handleRemove(scope.row)"><el-icon><CircleClose /></el-icon></el-button>
<el-button v-if="scope.row.Manageable && values.ShareRelayNode" type="info" size="small" @click="handleShare(scope.row)"><el-icon><Share /></el-icon></el-button>
</p>
</template>
</AccessBoolean>
</p> </p>
</template> </template>
</el-table-column> </el-table-column>
@@ -104,19 +129,19 @@
</div> </div>
</template> </template>
<script> <script>
import { getDefault,relayEdit,relayExit,relayUpdate,syncDefault } from '@/apis/relay'; import { getDefault,relayExit,relayImport,relayRemove,relayShare,relayUpgrade,syncDefault } from '@/apis/relay';
import { injectGlobalData } from '@/provide'; import { injectGlobalData } from '@/provide';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Ids from '../sync/Ids.vue'; import Ids from '../sync/Ids.vue';
import EditNode from './EditNode.vue'; import EditNode from './EditNode.vue';
import { Edit,ArrowDown,Refresh } from '@element-plus/icons-vue'; import { Edit,ArrowDown,Refresh,CircleClose,Plus,Share } from '@element-plus/icons-vue';
export default { export default {
props: ['modelValue','data'], props: ['modelValue','data'],
emits: ['update:modelValue','success'], emits: ['update:modelValue','success'],
components:{Ids,EditNode,Edit,ArrowDown,Refresh}, components:{Ids,EditNode,Edit,ArrowDown,Refresh,CircleClose,Plus,Share},
setup(props,{emit}) { setup(props,{emit}) {
const {t} = useI18n(); const {t} = useI18n();
const globalData = injectGlobalData(); const globalData = injectGlobalData();
@@ -154,7 +179,7 @@ export default {
} }
const domIds = ref(null); const domIds = ref(null);
const handleShowSync = (row,proto)=>{ const handleShowSync = (row,proto)=>{
state.syncData.Key = row.Id; state.syncData.Key = row.NodeId;
state.syncData.Value = proto; state.syncData.Value = proto;
state.showSync = true; state.showSync = true;
} }
@@ -176,39 +201,29 @@ export default {
_getDefault(); _getDefault();
} }
const handleSync2Server = (row)=>{
row.Sync2Server = !row.Sync2Server;
row.AllowTcp= (row.AllowProtocol & 1) == 1,
row.AllowUdp = (row.AllowProtocol & 2) == 2,
relayEdit(row).then(res => {
ElMessage.success(t('common.oper'));
}).catch(()=>{
ElMessage.error(t('common.operFail'));
});
}
const handleExit = (row)=>{ const handleExit = (row)=>{
ElMessageBox.confirm(t('server.relayExit'), t('common.confirm'), { ElMessageBox.confirm(t('server.relayExit'), t('common.confirm'), {
confirmButtonText: t('common.confirm'), confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'), cancelButtonText: t('common.cancel'),
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
relayExit(row.Id).then(res => { relayExit(row.NodeId).then(res => {
ElMessage.success(t('common.oper')); ElMessage.success(t('common.oper'));
}).catch(()=>{ }).catch(()=>{
ElMessage.error(t('common.operFail')); ElMessage.error(t('common.operFail'));
}); });
}).catch(() => { }).catch(() => {
ElMessage.error(t('common.operFail')); //ElMessage.error(t('common.operFail'));
}); });
} }
const handleUpdate = (row)=>{ const handleUpgrade = (row)=>{
if(row.Manageable == false) return; if(row.Manageable == false) return;
ElMessageBox.confirm(`${t('server.relayUpdate')} ${globalData.value.signin.Version}`,t('server.relayUpdate'), { ElMessageBox.confirm(`${t('server.relayUpdate')} ${globalData.value.signin.Version}`,t('server.relayUpdate'), {
confirmButtonText: t('common.confirm'), confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'), cancelButtonText: t('common.cancel'),
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
relayUpdate({Key:row.Id,Value:globalData.value.signin.Version}).then(res => { relayUpgrade({Key:row.NodeId,Value:globalData.value.signin.Version}).then(res => {
ElMessage.success(t('common.oper')); ElMessage.success(t('common.oper'));
}).catch(()=>{ }).catch(()=>{
ElMessage.error(t('common.operFail')); ElMessage.error(t('common.operFail'));
@@ -218,6 +233,52 @@ export default {
}); });
} }
const handleRemove = (row)=>{
ElMessageBox.confirm(t('server.relayRemove'), t('common.confirm'), {
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
type: 'warning',
}).then(() => {
relayRemove(row.NodeId).then(res => {
ElMessage.success(t('common.oper'));
}).catch(()=>{
ElMessage.error(t('common.operFail'));
});
}).catch(() => {
//ElMessage.error(t('common.operFail'));
});
}
const handleImport = ()=>{
ElMessageBox.prompt(t('server.relayImport'), t('common.confirm'), {
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel')
}).then(({ value }) => {
if(!value) return;
relayImport(value).then((res)=>{
if(res){
ElMessage.error(res);
}else{
ElMessage.success(t('common.oper'));
}
}).catch(()=>{})
}).catch(() => {
})
}
const handleShare = (row)=>{
relayShare(row.NodeId).then((res)=>{
ElMessageBox.prompt(t('server.relayShare'), t('common.tips'), {
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
inputValue:res
}).then(({ value }) => {
navigator.clipboard.writeText(value)
}).catch(() => {
})
}).catch(()=>{
ElMessage.error(t('common.operFail'));
});
}
onMounted(()=>{ onMounted(()=>{
_getDefault(); _getDefault();
}); });
@@ -227,7 +288,7 @@ export default {
return {globalData,state, return {globalData,state,
handleEdit,domIds,handleShowSync,handleSync,handleCancelSync, handleEdit,domIds,handleShowSync,handleSync,handleCancelSync,
handleExit,handleUpdate,handleSync2Server} handleExit,handleUpgrade,handleRemove,handleImport,handleShare}
} }
} }
</script> </script>

View File

@@ -1,29 +1,29 @@
<template> <template>
<el-dialog v-model="state.show" append-to=".app-wrap" :title="`与[${state.device.MachineName}]的链接`" top="1vh" width="350"> <el-dialog v-model="state.show" append-to=".app-wrap" :title="`与[${state.device.MachineName}]的链接`" top="1vh" width="350">
<div> <div>
<el-descriptions border size="small" :column="1" column-max-width="120px" overlength-control="wrap"> <el-descriptions border size="small" :column="1" label-width="6rem" overlength-control="wrap">
<el-descriptions-item label="目标">{{ state.connection.IPEndPoint || '0.0.0.0:0' }}</el-descriptions-item> <el-descriptions-item label="目标" >{{ state.connection.IPEndPoint }}</el-descriptions-item>
<el-descriptions-item label="事务">{{ state.transactions[state.connection.TransactionId] }}</el-descriptions-item> <el-descriptions-item label="事务" >{{ state.transactions[state.connection.TransactionId] }}</el-descriptions-item>
<el-descriptions-item label="协议"> <el-descriptions-item label="协议" >
<div> <div v-if="state.connection.Connected">
<p>{{ state.connection.TransportName }}({{ state.protocolTypes[state.connection.ProtocolType] }}) - {{ state.types[state.connection.Type] }}</p> <p>{{ state.connection.TransportName }}({{ state.protocolTypes[state.connection.ProtocolType] }}) - {{ state.types[state.connection.Type] }}</p>
<p>{{ state.connection.SendBufferRemainingText }} - {{ state.connection.RecvBufferRemainingText }}</p> <p>{{ state.connection.SendBufferRemainingText }} - {{ state.connection.RecvBufferRemainingText }}</p>
</div> </div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="SSL">{{ state.connection.SSL }}</el-descriptions-item> <el-descriptions-item label="SSL" >{{ state.connection.SSL }}</el-descriptions-item>
<el-descriptions-item label="上传"> <el-descriptions-item label="上传" >
<div> <div>
<p><span>{{ state.connection.SendBytesText }}</span></p> <p><span>{{ state.connection.SendBytesText }}</span></p>
</div> </div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="下载"> <el-descriptions-item label="下载" >
<div> <div>
<p><span>{{ state.connection.ReceiveBytesText }}</span></p> <p><span>{{ state.connection.ReceiveBytesText }}</span></p>
</div> </div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="中继"> <el-descriptions-item label="中继" >
<div> <div>
<a v-if="state.connecting" href="javascript:;" class="a-line"> <a v-if="state.connecting" href="javascript:;" class="a-line">
<span>操作中.</span><el-icon size="14" class="loading"><Loading /></el-icon> <span>操作中.</span><el-icon size="14" class="loading"><Loading /></el-icon>
@@ -31,7 +31,7 @@
<a v-else href="javascript:;" class="a-line" @click="handleNode">{{ state.nodesDic[state.connection.NodeId] || '选择节点' }}</a> <a v-else href="javascript:;" class="a-line" @click="handleNode">{{ state.nodesDic[state.connection.NodeId] || '选择节点' }}</a>
</div> </div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="打洞"> <el-descriptions-item label="打洞" >
<div> <div>
<a v-if="state.connecting" href="javascript:;" class="a-line"> <a v-if="state.connecting" href="javascript:;" class="a-line">
<span>操作中.</span><el-icon size="14" class="loading"><Loading /></el-icon> <span>操作中.</span><el-icon size="14" class="loading"><Loading /></el-icon>
@@ -39,8 +39,8 @@
<a v-else href="javascript:;" class="a-line" @click="handlep2p">尝试打洞</a> <a v-else href="javascript:;" class="a-line" @click="handlep2p">尝试打洞</a>
</div> </div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="延迟">{{ state.connection.Delay }}</el-descriptions-item> <el-descriptions-item label="延迟" >{{ state.connection.Delay }}</el-descriptions-item>
<el-descriptions-item label="操作"> <el-descriptions-item label="操作" >
<div> <div>
<AccessShow value="TunnelRemove"> <AccessShow value="TunnelRemove">
<el-popconfirm confirm-button-text="确认" cancel-button-text="取消" <el-popconfirm confirm-button-text="确认" cancel-button-text="取消"

View File

@@ -1,7 +1,7 @@
<template> <template>
<el-dialog v-model="state.show" append-to=".app-wrap" :title="state.title" top="1vh" width="370"> <el-dialog v-model="state.show" append-to=".app-wrap" :title="state.title" top="1vh" width="400">
<div> <div>
<el-descriptions border size="small" :column="1" column-max-width="120px" overlength-control="wrap"> <el-descriptions border size="small" :column="1" label-width="8rem" overlength-control="wrap">
<el-descriptions-item label="名称">{{ state.status.Info.Name }}</el-descriptions-item> <el-descriptions-item label="名称">{{ state.status.Info.Name }}</el-descriptions-item>
<el-descriptions-item label="带宽"> <el-descriptions-item label="带宽">
<div> <div>