This commit is contained in:
snltty
2025-03-29 23:06:39 +08:00
parent f0833eaa19
commit 4fcb274971
39 changed files with 371 additions and 158 deletions

View File

@@ -35,7 +35,7 @@ jobs:
release_name: v1.7.1.${{ steps.date.outputs.today }} release_name: v1.7.1.${{ steps.date.outputs.today }}
draft: false draft: false
prerelease: false prerelease: false
body: "1. 优化数据同步\r\n2. 优化linux的tun网卡网卡读写分离提高性能\r\n3. 优化windows网卡的禁用自动启用\r\n4. 增加TCP包合并。网卡IP包多个合并一起发送\r\n5. 建议更新" body: "1. 优化数据同步\r\n2. 优化linux的tun网卡网卡读写分离提高性能\r\n3. 优化windows网卡的禁用自动启用\r\n4. 增加TCP包合并。网卡IP包多个合并一起发送\r\n5. 内网穿透的计划任务\r\n6. 建议更新"
- name: publish projects - name: publish projects
run: ./publish.bat run: ./publish.bat
- name: upload-win-x86-oss - name: upload-win-x86-oss

View File

@@ -54,19 +54,19 @@
## 已有功能 ## 已有功能
- [x] 配置加密,配置文件加密 - [x] 配置加密,配置文件加密
- [x] 通信加密,所有通信均`ssl加密` - [x] 通信加密,所有通信均`ssl加密`
- [x] 打洞连接TCP(支持IPV6)打洞、UDP打洞 - [x] 打洞连接,`TCP打洞、UDP打洞(支持IPV6)`
- [x] 打洞类库,你可以使用`linker.tunnel`打洞库到你的项目中 - [x] 打洞类库,你可以使用`linker.tunnel`打洞库到你的项目中
- [x] 中继连接,先中继连接,然后偷偷打洞,打洞成功则无缝切换线路 - [x] 中继连接,先中继连接,然后偷偷打洞,打洞成功则无缝切换线路
- [x] 异地组网,使用虚拟网卡,将各个客户端组建为局域网络,`点对点``点对网``网对网` - [x] 异地组网,使用虚拟网卡,将各个客户端组建为局域网络,`点对点``点对网``网对网`
- [x] 网卡类库,你可以使用`linker.tun` tun网卡库到你的项目中 - [x] 网卡类库,你可以使用`linker.tun` tun网卡库到你的项目中
- [x] 端口转发,将客户端的端口转发到其它客户端的端口 - [x] 端口转发,将客户端的端口转发到其它客户端的端口
- [x] 服务器穿透,在服务器注册端口或域名,通过访问服务器端口或域名,访问内网服务(支持计划任务,定时定长自动开启关闭) - [x] 服务器穿透,在服务器注册端口或域名,通过访问服务器端口或域名,访问内网服务(支持`计划任务`,定时定长自动开启关闭)
- [x] 权限管理,主客户端拥有完全权限,可导出、配置子客户端配置,分配其管理权限 - [x] 权限管理,主客户端拥有完全权限,可导出、配置子客户端配置,分配其管理权限
- [x] 自定义验证,通过`HTTP POST`让你可以自定义认证是否允许`连接信标``中继``内网穿透` - [x] 自定义验证,通过`HTTP POST`让你可以自定义认证是否允许`连接信标``中继``内网穿透`
- [x] 流量统计,统计服务器`信标``中继``内网穿透` 的流量情况 - [x] 流量统计,统计服务器`信标``中继``内网穿透` 的流量情况
- [x] 网络配置主客户端设置网络所有客户端自动分配IP - [x] 网络配置主客户端设置网络所有客户端自动分配IP
- [x] 分布式,多中继服务器节点,承载海量设备 - [x] 分布式,多中继服务器节点,承载海量设备
- [x] socks5代理端口转发需要指定端口而socks5代理可以代理所有端口 - [x] socks5代理端口转发需要指定端口`socks5代理`可以代理所有端口
- [x] 集成linker使用`linker.messenger.entry`入口库,轻松集成到你的项目中 - [x] 集成linker使用`linker.messenger.entry`入口库,轻松集成到你的项目中
- [x] CDKEY可以临时解锁一些限制中继内外穿透什么的 - [x] CDKEY可以临时解锁一些限制中继内外穿透什么的

View File

@@ -49,6 +49,15 @@ namespace linker.messenger.plan
return result; return result;
} }
public void Trigger(string category,string key,string handle)
{
PlanExecCacheInfo trigger = caches.Values.FirstOrDefault(c => c.Plan.Category == category && c.Plan.Key == key && c.Plan.TriggerHandle == handle && c.Plan.TriggerHandle != c.Plan.Handle && c.Plan.Method == PlanMethod.Trigger);
if (trigger != null)
{
trigger.Active = UpdateNextTime(trigger);
}
}
private void PlanTask() private void PlanTask()
{ {
Load(); Load();

View File

@@ -44,8 +44,6 @@ namespace linker.messenger.sforward
/// </summary> /// </summary>
public string LocalMsg { get; set; } public string LocalMsg { get; set; }
public bool Proxy { get; set; }
/// <summary> /// <summary>
/// 端口范围 /// 端口范围
/// </summary> /// </summary>

View File

@@ -62,15 +62,6 @@
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="id"></param>
/// <param name="started"></param> /// <param name="started"></param>
/// <param name="proxy"></param>
/// <param name="msg"></param>
/// <returns></returns>
public bool Update(long id,bool started,bool proxy,string msg);
/// <summary>
/// 更新
/// </summary>
/// <param name="id"></param>
/// <param name="started"></param>
/// <returns></returns> /// <returns></returns>
public bool Update(long id,bool started); public bool Update(long id,bool started);
/// <summary> /// <summary>

View File

@@ -18,8 +18,9 @@ namespace linker.messenger.sforward.client
private readonly ISForwardClientStore sForwardClientStore; private readonly ISForwardClientStore sForwardClientStore;
private readonly ISerializer serializer; private readonly ISerializer serializer;
private readonly IAccessStore accessStore; private readonly IAccessStore accessStore;
private readonly SForwardPlanHandle sForwardPlanHandle;
public SForwardApiController(SForwardClientTransfer forwardTransfer, IMessengerSender messengerSender, SignInClientState signInClientState, ISignInClientStore signInClientStore, SForwardDecenter sForwardDecenter, ISForwardClientStore sForwardClientStore, ISerializer serializer, IAccessStore accessStore) public SForwardApiController(SForwardClientTransfer forwardTransfer, IMessengerSender messengerSender, SignInClientState signInClientState, ISignInClientStore signInClientStore, SForwardDecenter sForwardDecenter, ISForwardClientStore sForwardClientStore, ISerializer serializer, IAccessStore accessStore, SForwardPlanHandle sForwardPlanHandle)
{ {
this.forwardTransfer = forwardTransfer; this.forwardTransfer = forwardTransfer;
this.messengerSender = messengerSender; this.messengerSender = messengerSender;
@@ -29,6 +30,7 @@ namespace linker.messenger.sforward.client
this.sForwardClientStore = sForwardClientStore; this.sForwardClientStore = sForwardClientStore;
this.serializer = serializer; this.serializer = serializer;
this.accessStore = accessStore; this.accessStore = accessStore;
this.sForwardPlanHandle = sForwardPlanHandle;
} }
/// <summary> /// <summary>
@@ -146,6 +148,42 @@ namespace linker.messenger.sforward.client
}).ConfigureAwait(false); }).ConfigureAwait(false);
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray); return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
} }
public async Task<bool> Start(ApiControllerParamsInfo param)
{
SForwardRemoveForwardInfo info = param.Content.DeJson<SForwardRemoveForwardInfo>();
if (info.MachineId == signInClientStore.Id)
{
if (accessStore.HasAccess(AccessValue.ForwardSelf) == false) return false;
forwardTransfer.Start(info.Id);
return true;
}
if (accessStore.HasAccess(AccessValue.ForwardOther) == false) return false;
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)SForwardMessengerIds.StartClientForward,
Payload = serializer.Serialize(info)
}).ConfigureAwait(false);
return true;
}
public async Task<bool> Stop(ApiControllerParamsInfo param)
{
SForwardRemoveForwardInfo info = param.Content.DeJson<SForwardRemoveForwardInfo>();
if (info.MachineId == signInClientStore.Id)
{
if (accessStore.HasAccess(AccessValue.ForwardSelf) == false) return false;
forwardTransfer.Stop(info.Id);
return true;
}
if (accessStore.HasAccess(AccessValue.ForwardOther) == false) return false;
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)SForwardMessengerIds.StopClientForward,
Payload = serializer.Serialize(info)
}).ConfigureAwait(false);
return true;
}
/// <summary> /// <summary>
/// 测试服务 /// 测试服务

View File

@@ -9,8 +9,8 @@ namespace linker.messenger.sforward.client
public sealed class SForwardClientTransfer public sealed class SForwardClientTransfer
{ {
public Action OnChanged { get; set; } = () => { }; public Action OnChanged { get; set; } = () => { };
public Action<long> OnOpen = (id) => { }; public Action<long, string> OnOpen = (id,flag) => { };
public Action<long> OnClose = (id) => { }; public Action<long, string> OnClose = (id, flag) => { };
private readonly SignInClientState signInClientState; private readonly SignInClientState signInClientState;
private readonly IMessengerSender messengerSender; private readonly IMessengerSender messengerSender;
@@ -25,50 +25,32 @@ namespace linker.messenger.sforward.client
this.signInClientStore = signInClientStore; this.signInClientStore = signInClientStore;
this.sForwardClientStore = sForwardClientStore; this.sForwardClientStore = sForwardClientStore;
//也有可能是服务端重启导致重新上线,所以不能在首次登录启动,要每次登录都尝试添加一下
signInClientState.OnSignInSuccess += (i) => Start();
this.serializer = serializer; this.serializer = serializer;
} }
public void Start(int id) public void Start(long id,string flag = "")
{
SForwardInfo forwardInfo = sForwardClientStore.Get(id);
if(forwardInfo != null)
{
Start(forwardInfo);
}
}
public void Stop(int id)
{ {
SForwardInfo forwardInfo = sForwardClientStore.Get(id); SForwardInfo forwardInfo = sForwardClientStore.Get(id);
if (forwardInfo != null) if (forwardInfo != null)
{ {
Stop(forwardInfo); Start(forwardInfo, flag);
} }
} }
private void Start() public void Stop(long id, string flag = "")
{ {
foreach (var item in sForwardClientStore.Get()) SForwardInfo forwardInfo = sForwardClientStore.Get(id);
if (forwardInfo != null)
{ {
if (item.Started) Stop(forwardInfo, flag);
{
Start(item);
}
else
{
Stop(item);
}
} }
} }
private void Start(SForwardInfo forwardInfo) private void Start(SForwardInfo forwardInfo, string flag = "")
{ {
if (forwardInfo.Proxy) return;
if (forwardInfo.RemotePort == 0 && string.IsNullOrWhiteSpace(forwardInfo.Domain)) if (forwardInfo.RemotePort == 0 && string.IsNullOrWhiteSpace(forwardInfo.Domain))
{ {
sForwardClientStore.Update(forwardInfo.Id, false, forwardInfo.Proxy, $"Please use port or domain"); sForwardClientStore.Update(forwardInfo.Id, false, $"Please use port or domain");
return; return;
} }
try try
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
@@ -86,16 +68,21 @@ namespace linker.messenger.sforward.client
forwardInfo.BufferSize = sForwardAddResultInfo.BufferSize; forwardInfo.BufferSize = sForwardAddResultInfo.BufferSize;
if (sForwardAddResultInfo.Success) if (sForwardAddResultInfo.Success)
{ {
sForwardClientStore.Update(forwardInfo.Id, forwardInfo.Started, true, string.Empty); sForwardClientStore.Update(forwardInfo.Id, true, string.Empty);
LoggerHelper.Instance.Debug(sForwardAddResultInfo.Message); LoggerHelper.Instance.Debug(sForwardAddResultInfo.Message);
OnOpen(forwardInfo.Id); OnOpen(forwardInfo.Id, flag);
} }
else else
{ {
sForwardClientStore.Update(forwardInfo.Id, false, forwardInfo.Proxy, sForwardAddResultInfo.Message); sForwardClientStore.Update(forwardInfo.Id, false, sForwardAddResultInfo.Message);
LoggerHelper.Instance.Error(sForwardAddResultInfo.Message); LoggerHelper.Instance.Error(sForwardAddResultInfo.Message);
} }
} }
else
{
sForwardClientStore.Update(forwardInfo.Id, false, string.Empty);
}
OnChanged(); OnChanged();
}); });
} }
@@ -107,39 +94,23 @@ namespace linker.messenger.sforward.client
} }
} }
private void Stop(SForwardInfo forwardInfo) private void Stop(SForwardInfo forwardInfo, string flag = "")
{ {
try try
{ {
if (forwardInfo.Proxy) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"stop sforward {forwardInfo.ToJson()}");
messengerSender.SendReply(new MessageRequestWrap
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) Connection = signInClientState.Connection,
LoggerHelper.Instance.Info($"stop sforward {forwardInfo.ToJson()}"); MessengerId = (ushort)SForwardMessengerIds.Remove,
messengerSender.SendReply(new MessageRequestWrap Payload = serializer.Serialize(new SForwardAddInfo { Domain = forwardInfo.Domain, RemotePort = forwardInfo.RemotePort, SecretKey = sForwardClientStore.SecretKey })
{ }).ContinueWith((result) =>
Connection = signInClientState.Connection, {
MessengerId = (ushort)SForwardMessengerIds.Remove, OnClose(forwardInfo.Id, flag);
Payload = serializer.Serialize(new SForwardAddInfo { Domain = forwardInfo.Domain, RemotePort = forwardInfo.RemotePort, SecretKey = sForwardClientStore.SecretKey }) sForwardClientStore.Update(forwardInfo.Id, false, string.Empty);
}).ContinueWith((result) => OnChanged();
{ });
if (result.Result.Code == MessageResponeCodes.OK)
{
SForwardAddResultInfo sForwardAddResultInfo = serializer.Deserialize<SForwardAddResultInfo>(result.Result.Data.Span);
if (sForwardAddResultInfo.Success)
{
sForwardClientStore.Update(forwardInfo.Id, forwardInfo.Started, false, string.Empty);
LoggerHelper.Instance.Debug(sForwardAddResultInfo.Message);
OnClose(forwardInfo.Id);
}
else
{
sForwardClientStore.Update(forwardInfo.Id, forwardInfo.Started, forwardInfo.Proxy, string.Empty);
LoggerHelper.Instance.Error(sForwardAddResultInfo.Message);
}
}
OnChanged();
});
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -151,14 +122,11 @@ namespace linker.messenger.sforward.client
public bool Add(SForwardInfo forwardInfo) public bool Add(SForwardInfo forwardInfo)
{ {
sForwardClientStore.Add(forwardInfo); sForwardClientStore.Add(forwardInfo);
Start();
return true; return true;
} }
public bool Remove(int id) public bool Remove(int id)
{ {
sForwardClientStore.Update(id, false); Stop(id);
Start();
sForwardClientStore.Remove(id); sForwardClientStore.Remove(id);
return true; return true;
} }

View File

@@ -6,12 +6,19 @@ namespace linker.messenger.sforward.client
{ {
public string CategoryName => "sforward"; public string CategoryName => "sforward";
public string flag = "plan";
private readonly SForwardClientTransfer sForwardClientTransfer; private readonly SForwardClientTransfer sForwardClientTransfer;
public SForwardPlanHandle(SForwardClientTransfer sForwardClientTransfer) private readonly PlanTransfer planTransfer;
public SForwardPlanHandle(SForwardClientTransfer sForwardClientTransfer, PlanTransfer planTransfer)
{ {
this.sForwardClientTransfer = sForwardClientTransfer; this.sForwardClientTransfer = sForwardClientTransfer;
this.planTransfer = planTransfer;
sForwardClientTransfer.OnOpen += (id, _flag) => { if (_flag != flag) planTransfer.Trigger(CategoryName, id.ToString(), "start"); };
sForwardClientTransfer.OnClose += (id, _flag) => { if (_flag != flag) planTransfer.Trigger(CategoryName, id.ToString(), "stop"); };
} }
public async Task HandleAsync(string handle, string key, string value) public async Task HandleAsync(string handle, string key, string value)
{ {
if (int.TryParse(key, out int id) == false) return; if (int.TryParse(key, out int id) == false) return;
@@ -19,10 +26,10 @@ namespace linker.messenger.sforward.client
switch (handle) switch (handle)
{ {
case "start": case "start":
sForwardClientTransfer.Start(id); sForwardClientTransfer.Start(id, flag);
break; break;
case "stop": case "stop":
sForwardClientTransfer.Stop(id); sForwardClientTransfer.Stop(id, flag);
break; break;
default: default:
break; break;

View File

@@ -302,6 +302,35 @@ namespace linker.plugins.sforward.messenger
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
} }
[MessengerId((ushort)SForwardMessengerIds.StartClientForward)]
public async Task StartClientForward(IConnection connection)
{
SForwardRemoveForwardInfo info = serializer.Deserialize<SForwardRemoveForwardInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(info.MachineId, out SignCacheInfo cacheTo) && signCaching.TryGet(connection.Id, out SignCacheInfo cacheFrom) && cacheFrom.GroupId == cacheTo.GroupId)
{
await sender.SendOnly(new MessageRequestWrap
{
Connection = cacheTo.Connection,
MessengerId = (ushort)SForwardMessengerIds.StartClient,
Payload = serializer.Serialize(info.Id)
}).ConfigureAwait(false);
}
}
[MessengerId((ushort)SForwardMessengerIds.StopClientForward)]
public async Task StopClientForward(IConnection connection)
{
SForwardRemoveForwardInfo info = serializer.Deserialize<SForwardRemoveForwardInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(info.MachineId, out SignCacheInfo cacheTo) && signCaching.TryGet(connection.Id, out SignCacheInfo cacheFrom) && cacheFrom.GroupId == cacheTo.GroupId)
{
await sender.SendOnly(new MessageRequestWrap
{
Connection = cacheTo.Connection,
MessengerId = (ushort)SForwardMessengerIds.StopClient,
Payload = serializer.Serialize(info.Id)
}).ConfigureAwait(false);
}
}
/// <summary> /// <summary>
/// 测试对端的穿透记录 /// 测试对端的穿透记录
@@ -401,12 +430,14 @@ namespace linker.plugins.sforward.messenger
private readonly SForwardClientTransfer sForwardTransfer; private readonly SForwardClientTransfer sForwardTransfer;
private readonly ISForwardClientStore sForwardClientStore; private readonly ISForwardClientStore sForwardClientStore;
private readonly ISerializer serializer; private readonly ISerializer serializer;
public SForwardClientMessenger(SForwardProxy proxy, SForwardClientTransfer sForwardTransfer, ISForwardClientStore sForwardClientStore, ISerializer serializer) private readonly SForwardPlanHandle sForwardPlanHandle;
public SForwardClientMessenger(SForwardProxy proxy, SForwardClientTransfer sForwardTransfer, ISForwardClientStore sForwardClientStore, ISerializer serializer, SForwardPlanHandle sForwardPlanHandle)
{ {
this.proxy = proxy; this.proxy = proxy;
this.sForwardTransfer = sForwardTransfer; this.sForwardTransfer = sForwardTransfer;
this.sForwardClientStore = sForwardClientStore; this.sForwardClientStore = sForwardClientStore;
this.serializer = serializer; this.serializer = serializer;
this.sForwardPlanHandle = sForwardPlanHandle;
} }
/// <summary> /// <summary>
@@ -441,6 +472,19 @@ namespace linker.plugins.sforward.messenger
sForwardTransfer.Remove(id); sForwardTransfer.Remove(id);
connection.Write(Helper.TrueArray); connection.Write(Helper.TrueArray);
} }
[MessengerId((ushort)SForwardMessengerIds.StartClient)]
public void StartClient(IConnection connection)
{
int id = serializer.Deserialize<int>(connection.ReceiveRequestWrap.Payload.Span);
sForwardTransfer.Start(id);
}
[MessengerId((ushort)SForwardMessengerIds.StopClient)]
public void StopClient(IConnection connection)
{
int id = serializer.Deserialize<int>(connection.ReceiveRequestWrap.Payload.Span);
sForwardTransfer.Stop(id);
}
// <summary> // <summary>
/// 测试 /// 测试
/// </summary> /// </summary>

View File

@@ -20,6 +20,12 @@
TestClient = 2311, TestClient = 2311,
TestClientForward = 2312, TestClientForward = 2312,
StartClient = 2313,
StartClientForward = 2314,
StopClient = 2315,
StopClientForward = 2316,
Max = 2399 Max = 2399
} }
} }

View File

@@ -6,25 +6,13 @@ namespace linker.messenger.store.file.forward
{ {
public sealed class ForwardClientStore : IForwardClientStore public sealed class ForwardClientStore : IForwardClientStore
{ {
private readonly RunningConfig runningConfig;
private readonly Storefactory dBfactory; private readonly Storefactory dBfactory;
private readonly ILiteCollection<ForwardInfo> liteCollection; private readonly ILiteCollection<ForwardInfo> liteCollection;
public ForwardClientStore(RunningConfig runningConfig, Storefactory dBfactory) public ForwardClientStore(Storefactory dBfactory)
{ {
this.dBfactory = dBfactory; this.dBfactory = dBfactory;
liteCollection = dBfactory.GetCollection<ForwardInfo>("forward"); liteCollection = dBfactory.GetCollection<ForwardInfo>("forward");
this.runningConfig = runningConfig; liteCollection.UpdateMany(c => new ForwardInfo { Proxy = false }, c => c.Proxy == true);
foreach (var item in runningConfig.Data.Forwards)
{
item.Proxy = false;
item.Id = 0;
liteCollection.Insert(item);
}
runningConfig.Data.Forwards = new List<ForwardInfo>();
runningConfig.Data.Update();
liteCollection.UpdateMany(c => new ForwardInfo { Proxy = false },c=>c.Proxy==true);
} }
public int Count() public int Count()
{ {

View File

@@ -8,7 +8,7 @@ namespace linker.messenger.store.file
/// <summary> /// <summary>
/// 服务器穿透列表 /// 服务器穿透列表
/// </summary> /// </summary>
public List<SForwardInfo> SForwards { get; set; } = new List<SForwardInfo>(); //public List<SForwardInfo> SForwards { get; set; } = new List<SForwardInfo>();
} }
public sealed partial class ConfigClientInfo public sealed partial class ConfigClientInfo
{ {

View File

@@ -11,28 +11,16 @@ namespace linker.messenger.store.file.sforward
public string SecretKey => fileConfig.Data.Client.SForward.SecretKey; public string SecretKey => fileConfig.Data.Client.SForward.SecretKey;
private readonly FileConfig fileConfig; private readonly FileConfig fileConfig;
private readonly RunningConfig runningConfig;
private readonly Storefactory dBfactory; private readonly Storefactory dBfactory;
private readonly ILiteCollection<SForwardInfo> liteCollection; private readonly ILiteCollection<SForwardInfo> liteCollection;
public SForwardClientStore(FileConfig fileConfig, RunningConfig runningConfig, Storefactory dBfactory) public SForwardClientStore(FileConfig fileConfig, Storefactory dBfactory)
{ {
this.fileConfig = fileConfig;
this.dBfactory = dBfactory; this.dBfactory = dBfactory;
liteCollection = dBfactory.GetCollection<SForwardInfo>("sforward"); liteCollection = dBfactory.GetCollection<SForwardInfo>("sforward");
liteCollection.UpdateMany(c => new SForwardInfo { Started = false }, c => c.Started == true);
this.fileConfig = fileConfig;
this.runningConfig = runningConfig;
foreach (var item in runningConfig.Data.SForwards)
{
item.Proxy = false;
item.Id = 0;
liteCollection.Insert(item);
}
runningConfig.Data.SForwards = new List<SForwardInfo>();
runningConfig.Data.Update();
liteCollection.UpdateMany(c => new SForwardInfo { Proxy = false }, c => c.Proxy == true);
} }
public bool SetSecretKey(string key) public bool SetSecretKey(string key)
{ {
@@ -104,10 +92,6 @@ namespace linker.messenger.store.file.sforward
{ {
return liteCollection.UpdateMany(c => new SForwardInfo { Started = started, Msg = msg }, c => c.Id == id) > 0; return liteCollection.UpdateMany(c => new SForwardInfo { Started = started, Msg = msg }, c => c.Id == id) > 0;
} }
public bool Update(long id, bool started, bool proxy, string msg)
{
return liteCollection.UpdateMany(c => new SForwardInfo { Started = started, Proxy = proxy, Msg = msg }, c => c.Id == id) > 0;
}
public bool Update(long id, bool started) public bool Update(long id, bool started)
{ {
return liteCollection.UpdateMany(c => new SForwardInfo { Started = started }, c => c.Id == id) > 0; return liteCollection.UpdateMany(c => new SForwardInfo { Started = started }, c => c.Id == id) > 0;

View File

@@ -132,7 +132,7 @@ namespace linker.messenger.updater
MachineId = string.Empty, MachineId = string.Empty,
Current = info.Current, Current = info.Current,
Length = info.Length, Length = info.Length,
Status = info.Status, Status = info.Status,
Version = info.Version Version = info.Version
}; };
connection.Write(serializer.Serialize(result)); connection.Write(serializer.Serialize(result));

View File

@@ -6,7 +6,6 @@ using System.Reflection;
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using linker.libs.extends; using linker.libs.extends;
using System.Diagnostics;
namespace linker.messenger namespace linker.messenger
{ {

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.231" ProductVersion="0.0.0.231" publishDir="/dist/" dstrip="false" local="false" ignored="false"> <project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.232" ProductVersion="0.0.0.232" publishDir="/dist/" dstrip="false" local="false" ignored="false">
<file name="main.aardio" path="main.aardio" comment="main.aardio"/> <file name="main.aardio" path="main.aardio" comment="main.aardio"/>
<folder name="资源文件" path="res" embed="true" local="false" ignored="false"> <folder name="资源文件" path="res" embed="true" local="false" ignored="false">
<file name="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/> <file name="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/>

Binary file not shown.

View File

@@ -1 +1 @@
.table-sort th[data-v-754b053a]{border-bottom:0}.dropdown[data-v-2f0ed5e0]{border:1px solid #ddd;padding:.4rem;font-size:1.3rem;border-radius:.4rem;position:relative}.dropdown .el-icon[data-v-2f0ed5e0]{vertical-align:middle}.dropdown .badge[data-v-2f0ed5e0]{position:absolute;right:-1rem;top:-50%;border-radius:10px;background-color:#f1ae05;color:#fff;padding:.2rem .6rem;font-size:1.2rem}a[data-v-56c0e8be]{color:#666;text-decoration:underline}a.green[data-v-56c0e8be]{color:green;font-weight:700}a.download[data-v-56c0e8be]{margin-left:.6rem}a.download .el-icon[data-v-56c0e8be]{vertical-align:middle;font-weight:700;margin-left:.3rem}a.download .el-icon.loading[data-v-56c0e8be]{animation:loading-56c0e8be 1s linear infinite}a.download+a.download[data-v-56c0e8be]{margin-left:.2rem}@keyframes loading-56c0e8be{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}img.system[data-v-9f58a72e]{height:1.6rem;vertical-align:middle;margin-right:.4rem}.self[data-v-9f58a72e]{color:#d400ff}.self .el-icon[data-v-9f58a72e]{vertical-align:text-bottom}.ipaddress span[data-v-5db71b03]{vertical-align:middle}.el-input[data-v-5db71b03]{width:12rem;margin-right:.6rem}.el-col[data-v-7a697708]{text-align:left}div.point[data-v-41d1beca]{margin:-.2rem .3rem 0 -1.3rem;position:absolute}span.point[data-v-41d1beca]{width:.8rem;height:.8rem;border-radius:50%;display:inline-block;vertical-align:middle;background-color:#eee;border:1px solid #ddd;cursor:pointer;transition:.3s}span.point[data-v-41d1beca]:hover{transform:scale(2)}span.point.p2p[data-v-41d1beca]{background-color:#01c901;border:1px solid #049538}span.point.relay[data-v-41d1beca]{background-color:#e3e811;border:1px solid #b3c410}span.point.node[data-v-41d1beca]{background-color:#09dda9;border:1px solid #0cac90}.el-icon.loading[data-v-5ce8d590],a.loading[data-v-5ce8d590]{vertical-align:middle;font-weight:700;animation:loading-5ce8d590 1s linear infinite}.el-switch.is-disabled[data-v-5ce8d590]{opacity:1}.el-input[data-v-5ce8d590]{width:8rem}.delay[data-v-5ce8d590]{position:absolute;right:0;bottom:0;line-height:normal}.switch-btn[data-v-5ce8d590]{font-size:1.5rem}.any[data-v-5ce8d590]{position:absolute;left:-7px;top:-2px;line-height:normal}.any.green[data-v-5ce8d590]{background:linear-gradient(270deg,#caff00,green,#0d6d23,#e38a00,green);background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:hsla(0,0%,100%,0)}@keyframes loading-5ce8d590{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.wrap[data-v-786fe646]{padding-right:1rem}.remark[data-v-786fe646]{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wrap[data-v-286c7cac]{padding-right:1rem}.el-switch.is-disabled[data-v-d52cdcd0]{opacity:1}.upgrade-wrap[data-v-d52cdcd0]{border:1px solid #ddd;margin-bottom:2rem;padding:0 0 1rem 0}.el-switch.is-disabled[data-v-67ed3552]{opacity:1}.calc span[data-v-67ed3552]{display:inline-block}.calc span.label[data-v-67ed3552]{width:6rem}.el-icon.loading[data-v-3a4bfe6c],a.loading[data-v-3a4bfe6c]{vertical-align:middle;font-weight:700;animation:loading-3a4bfe6c 1s linear infinite}.el-switch.is-disabled[data-v-3a4bfe6c]{opacity:1}.el-input[data-v-3a4bfe6c]{width:8rem}.switch-btn[data-v-3a4bfe6c]{font-size:1.5rem}@keyframes loading-3a4bfe6c{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.el-switch.is-disabled[data-v-022e3781]{opacity:1}.upgrade-wrap[data-v-022e3781]{border:1px solid #ddd;margin-bottom:2rem;padding:1rem 0 1rem 0}.lan-item[data-v-022e3781]{margin-bottom:0}.el-switch.is-disabled[data-v-64b81c5b]{opacity:1}.green[data-v-64b81c5b]{font-weight:700}img.system[data-v-64b81c5b]{height:1.4rem;margin-right:.4rem;border:1px solid #eee}.el-switch.is-disabled[data-v-6941c158]{opacity:1}ul li[data-v-6941c158]{padding-left:2rem}a[data-v-2ee190a4]{text-decoration:underline}a+a[data-v-2ee190a4]{margin-left:1rem}a.green[data-v-2ee190a4]{font-weight:700}.head[data-v-6897ed85]{padding-bottom:1rem}.green[data-v-6897ed85]{color:green;font-weight:700}.error[data-v-6897ed85]{font-weight:700}.error .el-icon[data-v-6897ed85]{vertical-align:text-bottom}.head[data-v-7d65167d]{padding-bottom:1rem}.error[data-v-7d65167d]{font-weight:700}.error .el-icon[data-v-7d65167d]{vertical-align:text-bottom}.head[data-v-8c388c86]{padding-bottom:1rem}.blue[data-v-8c388c86]{color:#409eff}.dropdown[data-v-8c388c86]{border:1px solid #ddd;padding:.4rem;font-size:1.3rem;border-radius:.4rem;position:relative}.dropdown .el-icon[data-v-8c388c86]{vertical-align:middle}.dropdown .badge[data-v-8c388c86]{position:absolute;right:-1rem;top:-50%;border-radius:10px;background-color:#f1ae05;color:#fff;padding:.2rem .6rem;font-size:1.2rem}.table-sort.el-table th.el-table__cell.is-leaf{border-bottom:0}.table-sort.el-table .el-table__inner-wrapper:before{height:0}.home-list-wrap[data-v-4766ad40]{padding:1rem}.home-list-wrap .page[data-v-4766ad40]{padding-top:1rem}.home-list-wrap .page-wrap[data-v-4766ad40]{display:inline-block} .table-sort th[data-v-754b053a]{border-bottom:0}.dropdown[data-v-2f0ed5e0]{border:1px solid #ddd;padding:.4rem;font-size:1.3rem;border-radius:.4rem;position:relative}.dropdown .el-icon[data-v-2f0ed5e0]{vertical-align:middle}.dropdown .badge[data-v-2f0ed5e0]{position:absolute;right:-1rem;top:-50%;border-radius:10px;background-color:#f1ae05;color:#fff;padding:.2rem .6rem;font-size:1.2rem}a[data-v-56c0e8be]{color:#666;text-decoration:underline}a.green[data-v-56c0e8be]{color:green;font-weight:700}a.download[data-v-56c0e8be]{margin-left:.6rem}a.download .el-icon[data-v-56c0e8be]{vertical-align:middle;font-weight:700;margin-left:.3rem}a.download .el-icon.loading[data-v-56c0e8be]{animation:loading-56c0e8be 1s linear infinite}a.download+a.download[data-v-56c0e8be]{margin-left:.2rem}@keyframes loading-56c0e8be{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}img.system[data-v-9f58a72e]{height:1.6rem;vertical-align:middle;margin-right:.4rem}.self[data-v-9f58a72e]{color:#d400ff}.self .el-icon[data-v-9f58a72e]{vertical-align:text-bottom}.ipaddress span[data-v-5db71b03]{vertical-align:middle}.el-input[data-v-5db71b03]{width:12rem;margin-right:.6rem}.el-col[data-v-7a697708]{text-align:left}div.point[data-v-41d1beca]{margin:-.2rem .3rem 0 -1.3rem;position:absolute}span.point[data-v-41d1beca]{width:.8rem;height:.8rem;border-radius:50%;display:inline-block;vertical-align:middle;background-color:#eee;border:1px solid #ddd;cursor:pointer;transition:.3s}span.point[data-v-41d1beca]:hover{transform:scale(2)}span.point.p2p[data-v-41d1beca]{background-color:#01c901;border:1px solid #049538}span.point.relay[data-v-41d1beca]{background-color:#e3e811;border:1px solid #b3c410}span.point.node[data-v-41d1beca]{background-color:#09dda9;border:1px solid #0cac90}.el-icon.loading[data-v-5ce8d590],a.loading[data-v-5ce8d590]{vertical-align:middle;font-weight:700;animation:loading-5ce8d590 1s linear infinite}.el-switch.is-disabled[data-v-5ce8d590]{opacity:1}.el-input[data-v-5ce8d590]{width:8rem}.delay[data-v-5ce8d590]{position:absolute;right:0;bottom:0;line-height:normal}.switch-btn[data-v-5ce8d590]{font-size:1.5rem}.any[data-v-5ce8d590]{position:absolute;left:-7px;top:-2px;line-height:normal}.any.green[data-v-5ce8d590]{background:linear-gradient(270deg,#caff00,green,#0d6d23,#e38a00,green);background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:hsla(0,0%,100%,0)}@keyframes loading-5ce8d590{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.wrap[data-v-786fe646]{padding-right:1rem}.remark[data-v-786fe646]{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wrap[data-v-286c7cac]{padding-right:1rem}.el-switch.is-disabled[data-v-d52cdcd0]{opacity:1}.upgrade-wrap[data-v-d52cdcd0]{border:1px solid #ddd;margin-bottom:2rem;padding:0 0 1rem 0}.el-switch.is-disabled[data-v-67ed3552]{opacity:1}.calc span[data-v-67ed3552]{display:inline-block}.calc span.label[data-v-67ed3552]{width:6rem}.el-icon.loading[data-v-3a4bfe6c],a.loading[data-v-3a4bfe6c]{vertical-align:middle;font-weight:700;animation:loading-3a4bfe6c 1s linear infinite}.el-switch.is-disabled[data-v-3a4bfe6c]{opacity:1}.el-input[data-v-3a4bfe6c]{width:8rem}.switch-btn[data-v-3a4bfe6c]{font-size:1.5rem}@keyframes loading-3a4bfe6c{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.el-switch.is-disabled[data-v-022e3781]{opacity:1}.upgrade-wrap[data-v-022e3781]{border:1px solid #ddd;margin-bottom:2rem;padding:1rem 0 1rem 0}.lan-item[data-v-022e3781]{margin-bottom:0}.el-switch.is-disabled[data-v-64b81c5b]{opacity:1}.green[data-v-64b81c5b]{font-weight:700}img.system[data-v-64b81c5b]{height:1.4rem;margin-right:.4rem;border:1px solid #eee}.el-switch.is-disabled[data-v-6941c158]{opacity:1}ul li[data-v-6941c158]{padding-left:2rem}a[data-v-2ee190a4]{text-decoration:underline}a+a[data-v-2ee190a4]{margin-left:1rem}a.green[data-v-2ee190a4]{font-weight:700}.head[data-v-190226d8]{padding-bottom:1rem}.green[data-v-190226d8]{color:green;font-weight:700}.error[data-v-190226d8]{font-weight:700}.error .el-icon[data-v-190226d8]{vertical-align:text-bottom}.head[data-v-380ee730]{padding-bottom:1rem}.error[data-v-380ee730]{font-weight:700}.error .el-icon[data-v-380ee730]{vertical-align:text-bottom}.plan .el-icon[data-v-380ee730]{vertical-align:middle;margin-right:.4rem}.head[data-v-8c388c86]{padding-bottom:1rem}.blue[data-v-8c388c86]{color:#409eff}.dropdown[data-v-8c388c86]{border:1px solid #ddd;padding:.4rem;font-size:1.3rem;border-radius:.4rem;position:relative}.dropdown .el-icon[data-v-8c388c86]{vertical-align:middle}.dropdown .badge[data-v-8c388c86]{position:absolute;right:-1rem;top:-50%;border-radius:10px;background-color:#f1ae05;color:#fff;padding:.2rem .6rem;font-size:1.2rem}.table-sort.el-table th.el-table__cell.is-leaf{border-bottom:0}.table-sort.el-table .el-table__inner-wrapper:before{height:0}.home-list-wrap[data-v-4766ad40]{padding:1rem}.home-list-wrap .page[data-v-4766ad40]{padding-top:1rem}.home-list-wrap .page-wrap[data-v-4766ad40]{display:inline-block}

View File

@@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.3be25225.js"></script><script defer="defer" src="js/app.3d2b2fb0.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.7bd6c330.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html> <!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.a0b07350.js"></script><script defer="defer" src="js/app.875e12ef.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.e96fbec3.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -24,4 +24,11 @@ export const addSForwardInfo = (data) => {
export const testLocalSForwardInfo = (data) => { export const testLocalSForwardInfo = (data) => {
return sendWebsocketMsg('sforward/TestLocal', data); return sendWebsocketMsg('sforward/TestLocal', data);
}
export const startSForwardInfo = (data) => {
return sendWebsocketMsg('sforward/start', data);
}
export const stopSForwardInfo = (data) => {
return sendWebsocketMsg('sforward/start', data);
} }

View File

@@ -2,7 +2,8 @@
<el-dialog v-model="state.show" @open="handleOnShowList" append-to=".app-wrap" :title="`【${state.machineName}】的端口转发`" top="1vh" width="780"> <el-dialog v-model="state.show" @open="handleOnShowList" append-to=".app-wrap" :title="`【${state.machineName}】的端口转发`" top="1vh" width="780">
<div> <div>
<div class="t-c head"> <div class="t-c head">
<el-button type="success" size="small" @click="handleAdd">添加</el-button> <el-button type="success" size="small" @click="handleAdd" :loading="state.loading">添加</el-button>
<el-button size="small" @click="handleRefresh">刷新</el-button>
</div> </div>
<el-table :data="state.data" size="small" border height="500" @cell-dblclick="handleCellClick"> <el-table :data="state.data" size="small" border height="500" @cell-dblclick="handleCellClick">
<el-table-column property="Name" label="名称" width="100"> <el-table-column property="Name" label="名称" width="100">
@@ -199,6 +200,10 @@ export default {
state.timer1 = setTimeout(_getForwardInfo,1000); state.timer1 = setTimeout(_getForwardInfo,1000);
} }
} }
const handleRefresh = () => {
_getForwardInfo();
ElMessage.success('已刷新')
}
const _getSignInNames = ()=>{ const _getSignInNames = ()=>{
getSignInNames().then((res)=>{ getSignInNames().then((res)=>{
@@ -278,10 +283,13 @@ export default {
saveRow(row); saveRow(row);
} }
const saveRow = (row) => { const saveRow = (row) => {
state.loading = true;
row.Port = parseInt(row.Port); row.Port = parseInt(row.Port);
addForwardInfo({machineId:state.machineId,data:row}).then(() => { addForwardInfo({machineId:state.machineId,data:row}).then(() => {
state.loading = false;
_getForwardInfo(); _getForwardInfo();
}).catch((err) => { }).catch((err) => {
state.loading = false;
ElMessage.error(err); ElMessage.error(err);
}); });
} }
@@ -298,7 +306,7 @@ export default {
}); });
return { return {
state, handleOnShowList, handleCellClick, handleAdd, handleEdit, handleEditBlur, handleDel, handleStartChange, state, handleOnShowList, handleCellClick,handleRefresh, handleAdd, handleEdit, handleEditBlur, handleDel, handleStartChange,
handleSearch,handlePageChange handleSearch,handlePageChange
} }
} }

View File

@@ -15,7 +15,6 @@
{{ state.ruleForm.Rule }} {{ state.ruleForm.Rule }}
</strong> </strong>
</el-form-item> </el-form-item>
<el-form-item label="在" prop="Rule" v-if="state.ruleForm.Method == 100"> <el-form-item label="在" prop="Rule" v-if="state.ruleForm.Method == 100">
<div class="w-100"> <div class="w-100">
<el-select v-model="state.ruleAt.type" style="width:10rem" @change="handleChange"> <el-select v-model="state.ruleAt.type" style="width:10rem" @change="handleChange">
@@ -75,6 +74,9 @@
<span></span> <span></span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="内容" prop="Value">
<el-input type="textarea" resize="none" rows="5" v-model="state.ruleForm.Value"></el-input>
</el-form-item>
<el-form-item label="禁用" prop="Disabled"> <el-form-item label="禁用" prop="Disabled">
<el-switch v-model="state.ruleForm.Disabled" /> <el-switch v-model="state.ruleForm.Disabled" />
</el-form-item> </el-form-item>
@@ -82,7 +84,7 @@
<el-form-item label="" prop="Btns"> <el-form-item label="" prop="Btns">
<div class="t-c w-100"> <div class="t-c w-100">
<el-button @click="state.show = false">取消</el-button> <el-button @click="state.show = false">取消</el-button>
<el-button type="primary" @click="handleSave">确认</el-button> <el-button type="primary" @click="handleSave" :loading="state.loading">确认</el-button>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -90,19 +92,26 @@
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import { inject, reactive, ref, watch } from 'vue'; import { addPlan } from '@/apis/plan';
import { inject, onMounted, reactive, ref, watch } from 'vue';
export default { export default {
props: ['data','modelValue'], props: ['data','modelValue'],
emits: ['change','update:modelValue'], emits: ['change','update:modelValue'],
setup(props, { emit }) { setup(props, { emit }) {
const regex = /(\d+|\*)-(\d+|\*)-(\d+|\*)\s+(\d+|\*):(\d+|\*):(\d+|\*)/;
const regexNumber = /(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)/;
const regexCorn = /(.+)\s+(.+)\s+(.+)\s+(.+)\s+(.+)\s+(.+)/;
const ruleFormRef = ref(null); const ruleFormRef = ref(null);
const plan = inject('plan'); const plan = inject('plan');
if(!plan.value.current.TriggerHandle && plan.value.triggers.length > 0){ if(!plan.value.current.TriggerHandle && plan.value.triggers.length > 0){
plan.value.current.TriggerHandle = plan.value.triggers[0].value; plan.value.current.TriggerHandle = plan.value.triggers[0].value;
} }
const state = reactive({ const state = reactive({
show: true, show: true,
loading: false,
ruleCron:{ ruleCron:{
week:'*', week:'*',
month:'*', month:'*',
@@ -112,9 +121,9 @@ export default {
sec:'30', sec:'30',
}, },
ruleAt:{ ruleAt:{
type:2, type:3,
month:1, month:'*',
day:1, day:'*',
hour:0, hour:0,
min:0, min:0,
sec:0, sec:0,
@@ -155,8 +164,74 @@ export default {
}, 300); }, 300);
} }
}); });
const decodeRuleJson = {
100:(rule)=>{
rule = rule || `*-*-* 0:0:0`;
if(regex.test(rule) == false){
return;
}
console.log(rule.match(regex));
const [,year,month,day,hour,minute,second] = rule.match(regex);
if(minute == '*') state.ruleAt.type = 5;
else if(hour == '*') state.ruleAt.type = 4;
else if(day == '*') state.ruleAt.type = 3;
else if(month == '*') state.ruleAt.type = 2;
state.ruleAt.year = year;
state.ruleAt.month = month;
state.ruleAt.day = day;
state.ruleAt.hour = hour;
state.ruleAt.min = minute;
state.ruleAt.sec = second;
},
101:(rule)=>{
rule = rule || `0-0-0 0:0:30`;
if(regexNumber.test(rule) == false){
return;
}
const [,year,month,day,hour,minute,second] = rule.match(regexNumber);
state.ruleTimer.year = year;
state.ruleTimer.month = month;
state.ruleTimer.day = day;
state.ruleTimer.hour = hour;
state.ruleTimer.min = minute;
state.ruleTimer.sec = second;
},
102:(rule)=>{
rule = rule || `30 * * * * ?`;
if(regexCorn.test(rule) == false){
return;
}
const [,second,minute,hour,day,month,week] = rule.match(regexCorn);
state.ruleCron.sec = second;
state.ruleCron.min = minute;
state.ruleCron.hour = hour;
state.ruleCron.day = day;
state.ruleCron.month = month;
state.ruleCron.week = week;
console.log(rule.match(regexCorn));
},
103:(rule)=>{
rule = rule || `0-0-0 0:0:30`;
if(regexNumber.test(rule) == false){
return;
}
const [,year,month,day,hour,minute,second] = rule.match(regexNumber);
state.ruleTrigger.year = year;
state.ruleTrigger.month = month;
state.ruleTrigger.day = day;
state.ruleTrigger.hour = hour;
state.ruleTrigger.min = minute;
state.ruleTrigger.sec = second;
}
};
const decodeRule = ()=>{
if(state.ruleForm.Method in decodeRuleJson){
decodeRuleJson[state.ruleForm.Method](state.ruleForm.Rule);
}
}
const buildRule = { const buildRuleJson = {
100:()=>{ 100:()=>{
switch (state.ruleAt.type) { switch (state.ruleAt.type) {
case 2: case 2:
@@ -178,18 +253,34 @@ export default {
102:()=>`${state.ruleCron.sec} ${state.ruleCron.min} ${state.ruleCron.hour} ${state.ruleCron.day} ${state.ruleCron.month} ${state.ruleCron.week}`, 102:()=>`${state.ruleCron.sec} ${state.ruleCron.min} ${state.ruleCron.hour} ${state.ruleCron.day} ${state.ruleCron.month} ${state.ruleCron.week}`,
103:()=>`${state.ruleTrigger.year}-${state.ruleTrigger.month}-${state.ruleTrigger.day} ${state.ruleTrigger.hour}:${state.ruleTrigger.min}:${state.ruleTrigger.sec}`, 103:()=>`${state.ruleTrigger.year}-${state.ruleTrigger.month}-${state.ruleTrigger.day} ${state.ruleTrigger.hour}:${state.ruleTrigger.min}:${state.ruleTrigger.sec}`,
} }
const handleChange = () => { const buildRule= ()=>{
if(state.ruleForm.Method in buildRule){ if(state.ruleForm.Method in buildRuleJson){
state.ruleForm.Rule = buildRule[state.ruleForm.Method](); state.ruleForm.Rule = buildRuleJson[state.ruleForm.Method]();
} }
} }
const handleChange = () => {
buildRule();
}
const handleSave = () => { const handleSave = () => {
const json = JSON.parse(JSON.stringify(state.ruleForm));
state.loading = true;
addPlan(plan.value.machineid,json).then((res)=>{
state.loading = false;
state.show = false;
}).catch(()=>{
state.loading = false;
})
} }
return { onMounted(()=>{
decodeRule();
handleChange();
});
return {
state, ruleFormRef,plan,handleChange, handleSave state, ruleFormRef,plan,handleChange, handleSave
} }
} }
} }
</script> </script>

View File

@@ -15,12 +15,14 @@ export default {
setup (props) { setup (props) {
const plan = ref({ const plan = ref({
machineid:props.machineid,
timer:0, timer:0,
list:{}, list:{},
current:{}, current:{},
showEdit:false, showEdit:false,
category:props.category||'', category:props.category||'',
handles:props.handles||[], handles:props.handles||[],
handleJson:(props.handles||[]).reduce((json,item,index)=>{ json[item.value] = item.label; return json; },{}),
triggers:[], triggers:[],
methods:[ methods:[
{label:'手动',value:0}, {label:'手动',value:0},
@@ -34,8 +36,11 @@ export default {
provide('plan',plan); provide('plan',plan);
const _getPlans = () => { const _getPlans = () => {
clearTimeout(plan.value.timer); clearTimeout(plan.value.timer);
getPlans(props.machineid,props.category).then((res) => { getPlans(plan.value.machineid,props.category).then((res) => {
console.log(res); plan.value.list = res.reduce((json,item,index)=>{
json[`${item.Key}-${item.Handle}`] = item;
return json;
},{});
plan.value.timer = setTimeout(_getPlans,1000); plan.value.timer = setTimeout(_getPlans,1000);
}).catch(()=>{ }).catch(()=>{
@@ -44,7 +49,6 @@ export default {
} }
onMounted(()=>{ onMounted(()=>{
_getPlans(); _getPlans();
console.log(props);
}); });
onUnmounted(()=>{ onUnmounted(()=>{
clearTimeout(plan.value.timer); clearTimeout(plan.value.timer);

View File

@@ -1,6 +1,6 @@
<template> <template>
<a href="javascript:;" class="a-line" @click="handleEdit"> <a href="javascript:;" class="a-line" @click="handleEdit">
<span v-if="item">{{ item.Rule }}</span> <span v-if="item">{{ rule }}</span>
<span v-else>未设置</span> <span v-else>未设置</span>
</a> </a>
</template> </template>
@@ -12,8 +12,65 @@ export default {
props: ['keyid','handle'], props: ['keyid','handle'],
setup (props) { setup (props) {
const regex = /(\d+|\*)-(\d+|\*)-(\d+|\*)\s+(\d+|\*):(\d+|\*):(\d+|\*)/;
const regexNumber = /(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)/;
const ruleTrans = {
0:()=>`手动`,
1:()=>`网络启动后`,
100:(item,rule)=>{
if(regex.test(rule) == false){
return rule;
}
const [,year,month,day,hour,minute,second] = rule.match(regex);
if(minute == '*') return `每分钟的${second}`;
if(hour == '*') return `每小时的${minute}${second}`;
if(day == '*') return `每天的${hour}${minute}${second}`;
if(month == '*') return `每月的${day}${hour}${minute}${second}`;
if(year == '*') return `每年的${month}${day}${hour}${minute}${second}`;
},
101:(item,rule)=>{
if(regexNumber.test(rule) == false){
return rule;
}
const [,year,month,day,hour,minute,second] = rule.match(regexNumber);
const arr = [];
if(year != '0') arr.push(`${year}`);
if(month != '0') arr.push(`${month}`);
if(day != '0') arr.push(`${day}`);
if(hour != '0') arr.push(`${hour}`);
if(minute != '0') arr.push(`${minute}`);
if(second != '0') arr.push(`${second}`);
return `${arr.join('')}`
},
102:(item,rule)=>{
return `Cron : ${rule}`;
},
103:(item,rule)=>{
if(regexNumber.test(rule) == false){
return rule;
}
const [,year,month,day,hour,minute,second] = rule.match(regexNumber);
const arr = [];
if(year != '0') arr.push(`${year}`);
if(month != '0') arr.push(`${month}`);
if(day != '0') arr.push(`${day}`);
if(hour != '0') arr.push(`${hour}`);
if(minute != '0') arr.push(`${minute}`);
if(second != '0') arr.push(`${second}`);
return `在【${plan.value.handleJson[item.TriggerHandle]}】的${arr.join('')}`
},
}
const plan = inject('plan'); const plan = inject('plan');
const item = computed(()=>plan.value.list[`${props.keyid}-${props.handle}`]); const item = computed(()=>plan.value.list[`${props.keyid}-${props.handle}`]);
const rule = computed(()=>{
if(!item.value) return '';
const method = item.value.Method;
if(ruleTrans[method]){
return ruleTrans[method](item.value,item.value.Rule);
}
return item.value.Rule;
});
const handleEdit = () => { const handleEdit = () => {
plan.value.current = item.value || { plan.value.current = item.value || {
Id:0, Id:0,
@@ -23,14 +80,14 @@ export default {
Value:'', Value:'',
Disabled:false, Disabled:false,
TriggerHandle:'', TriggerHandle:'',
Method:103, Method:100,
Rule:'' Rule:''
}; };
plan.value.triggers = JSON.parse(JSON.stringify(plan.value.handles.filter(c=>c.value != props.handle))); plan.value.triggers = JSON.parse(JSON.stringify(plan.value.handles.filter(c=>c.value != props.handle)));
plan.value.showEdit = true; plan.value.showEdit = true;
} }
return {item,handleEdit} return {item,rule,handleEdit}
} }
} }
</script> </script>

View File

@@ -18,7 +18,7 @@
</template> </template>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="Plan" label="计划" width="200"> <el-table-column prop="Plan" label="开启和关闭计划" width="200">
<template #default="scope"> <template #default="scope">
<div class="plan"> <div class="plan">
<p><el-icon><Select /></el-icon><PlanShow handle="start" :keyid="scope.row.Id"></PlanShow></p> <p><el-icon><Select /></el-icon><PlanShow handle="start" :keyid="scope.row.Id"></PlanShow></p>
@@ -64,8 +64,8 @@
</el-table-column> </el-table-column>
<el-table-column property="Started" label="状态" width="60"> <el-table-column property="Started" label="状态" width="60">
<template #default="scope"> <template #default="scope">
<el-switch v-model="scope.row.Started" @change="handleStartChange(scope.row)" inline-prompt <el-switch disabled v-model="scope.row.Started" inline-prompt
active-text="" inactive-text="" /> active-text="" inactive-text="" @click="handleStartChange(scope.row)" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="54"> <el-table-column label="操作" width="54">
@@ -85,7 +85,7 @@
</template> </template>
<script> <script>
import { onMounted, onUnmounted, reactive, watch } from 'vue'; import { onMounted, onUnmounted, reactive, watch } from 'vue';
import { getSForwardInfo, removeSForwardInfo, addSForwardInfo,testLocalSForwardInfo } from '@/apis/sforward' import { getSForwardInfo, removeSForwardInfo, addSForwardInfo,testLocalSForwardInfo, stopSForwardInfo, startSForwardInfo } from '@/apis/sforward'
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import {WarnTriangleFilled,Delete,Select,CloseBold} from '@element-plus/icons-vue' import {WarnTriangleFilled,Delete,Select,CloseBold} from '@element-plus/icons-vue'
import { injectGlobalData } from '@/provide'; import { injectGlobalData } from '@/provide';
@@ -206,7 +206,18 @@ export default {
}); });
} }
const handleStartChange = (row) => { const handleStartChange = (row) => {
saveRow(row); state.loading = true;
const func = row.Started
? stopSForwardInfo({machineid:sforward.value.machineid,id:row.Id})
: startSForwardInfo({machineid:sforward.value.machineid,id:row.Id});
func.then(() => {
state.loading = false;
_getSForwardInfo();
}).catch((err) => {
state.loading = false;
ElMessage.error(err);
});
} }
const saveRow = (row) => { const saveRow = (row) => {
if(!row.Temp) return; if(!row.Temp) return;
@@ -259,7 +270,8 @@ export default {
} }
.plan{ .plan{
.el-icon{ .el-icon{
vertical-align:middle vertical-align:middle;
margin-right:0.4rem;
} }
} }
</style> </style>

View File

@@ -24,7 +24,8 @@
2. 优化linux的tun网卡网卡读写分离提高性能 2. 优化linux的tun网卡网卡读写分离提高性能
3. 优化windows网卡的禁用自动启用 3. 优化windows网卡的禁用自动启用
4. 增加TCP包合并。网卡IP包多个合并一起发送 4. 增加TCP包合并。网卡IP包多个合并一起发送
5. 建议更新</Description> 5. 内网穿透的计划任务
6. 建议更新</Description>
<Copyright>snltty</Copyright> <Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>

View File

@@ -1,7 +1,8 @@
v1.7.1 v1.7.1
2025-03-28 17:28:46 2025-03-29 23:06:39
1. 优化数据同步 1. 优化数据同步
2. 优化linux的tun网卡网卡读写分离提高性能 2. 优化linux的tun网卡网卡读写分离提高性能
3. 优化windows网卡的禁用自动启用 3. 优化windows网卡的禁用自动启用
4. 增加TCP包合并。网卡IP包多个合并一起发送 4. 增加TCP包合并。网卡IP包多个合并一起发送
5. 建议更新 5. 内网穿透的计划任务
6. 建议更新