This commit is contained in:
snltty
2025-06-05 17:13:06 +08:00
parent 9a346f1362
commit e809be2f1a
77 changed files with 460 additions and 530 deletions

View File

@@ -37,7 +37,7 @@ jobs:
release_name: v1.8.2.${{ steps.date.outputs.today }}
draft: false
prerelease: false
body: "1. 一些累计更新\r\n2. 测试发布请使用1.8.2"
body: "1. 一些累计更新\r\n2. 重建权限存储ulong改为BitArray同组所有客户端需保持版本一致\r\n3. 增加唤醒功能支持WOLCOM继电器HID继电器\r\n4. 基于One-KVM包装docker镜像集成linker\r\n5. 管理端口改为1804一个端口托管Web+Websocket"
- name: publish projects
run: ./publish.bat "C:\\Android\\android-sdk"
- name: upload-win-x86-oss

View File

@@ -20,7 +20,7 @@ namespace linker.app
MainThread.BeginInvokeOnMainThread(() =>
{
IApiStore apiStore = LinkerMessengerEntry.GetService<IApiStore>();
webview.Source = new Uri($"http://127.0.0.1:{apiStore.Info.WebPort}?t={DateTime.Now.Ticks}/#/?api={apiStore.Info.ApiPort}&psd={Helper.GlobalString}");
webview.Source = new Uri($"http://127.0.0.1:{apiStore.Info.WebPort}?t={DateTime.Now.Ticks}/#/?api={apiStore.Info.WebPort}&psd={Helper.GlobalString}");
});
};
}

View File

@@ -9,7 +9,7 @@ sidebar_position: 0
:::
:::tip[客户端]
1. 客户端模式 监听端口 `1803(TCP)`websocket管理接口`1804(TCP)`管理UI端口
1. 客户端模式 管理接口`1804(TCP)`
2. 运行起来后可以使用`http://127.0.0.1:1804`打开管理页面如果是windows也可以在`linker.tray.win.exe`托盘程序里打开管理页面
:::

View File

@@ -31,7 +31,7 @@ sc delete 服务名
1. 由于linker使用当前目录下的`configs`文件夹里的配置文件,所以不能使用同一程序多开,需要使用多份程序,**不要把`configs`文件夹也复制过去**
2. 客户端托盘无法多开,所以需要客户端多开时,**需要你手动安装服务**,不能使用托盘安装服务
3. 客户端会监听两个端口,默认为`1803` 管理接口,`1804`网页服务,所以,在初始化时,你需要各个客户端**使用不同的端口**
3. 客户端会监听`1804` 管理接口,所以,在初始化时,你需要各个客户端**使用不同的端口**
#### 在初始化时,`接口端口`,和`网页端口`,需要各个客户端设置为不一样的端口
![](./img/client.png)

View File

@@ -25,19 +25,16 @@ chmod +x linker-install.sh
:::tip[说明]
1. 服务端端口 `1802` TCP+UDP
2. 客户端端口 `1804``1803` TCP
3. 配置文件夹 `./configs`
4. 日志文件夹 `./logs`
5. 镜像清单 `snltty/linker-debian``snltty/linker-musl``snltty/linker-kvm`
6. 特定平台 `snltty/linker-debian-{arch}``snltty/linker-musl-{arch}``snltty/linker-kvm-{arch}``{arch}`可以是`x64``arm64``arm`
7. `bridge`模式,需要映射一些端口比如`-p 18000-18010:18000-18010`,用于端口转发
8. `host`模式或者直接使用host`--network host`**建议使用host点对网网对网端口转发都方便**
1. 服务端端口 `1802` TCP+UDP、客户端端口 `1804` TCP
2. 配置文件夹 `./configs`、日志文件夹 `./logs`
3. 镜像清单 `snltty/linker-debian``snltty/linker-musl``snltty/linker-kvm` 或者 `snltty/linker-debian-{arch}``snltty/linker-musl-{arch}``snltty/linker-kvm-{arch}``{arch}`可以是`x64``arm64``arm`
4. `bridge`模式,需要映射一些端口比如`-p 18000-18010:18000-18010`用于端口转发或者直接使用host`--network host`**建议使用host点对网网对网端口转发都方便**
5. 如果你使用的是KVM镜像那么会多需要几个端口`1806 http``1807 https``5900 VNC``623 ipmi`
#### 客户端-bridge
```
docker run -it -d --name linker \
-p 1804:1804/tcp -p 1803:1803/tcp \
-p 1804:1804/tcp \
-p 18000-18010:18000-18010/tcp \
-p 18000-18010:18000-18010/udp \
-v /usr/local/linker-docker/configs:/app/configs \
@@ -47,6 +44,41 @@ docker run -it -d --name linker \
--privileged=true \
snltty/linker-musl
```
#### 客户端-bridge-kvm
1. 基于[One-KVM](https://one-kvm.mofeng.run/start_install/docker_install/)的docker镜像包装集成linker
2. 在linker参数的基础上再加上KVM的参数根据自己的需求修改一下参数即可
3. 使用 1806 端口打开KVM管理页面
```
docker run --name linker-kvm -itd \
-p 1804:1804/tcp \
-p 18000-18010:18000-18010/tcp \
-p 18000-18010:18000-18010/udp \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--device /dev/net/tun \
--restart=always \
--privileged=true \
--network host \
-p 1806:1806/tcp -p 1806:1806/udp \ #网页http端口
-p 1807:1807/tcp -p 1807:1807/udp \ #网页https端口
-p 5900:5900/tcp -p 5900:5900/udp \ #vnc端口
-p 623:623/tcp -p 623:623/udp \ #ipmi端口
--device /dev/video0:/dev/video0 \ #采集卡
--device /dev/snd:/dev/snd -e AUDIONUM=0 \ #声卡
--device /dev/hidraw1:/dev/hidraw1 -e ATX=USBRELAY_HID \ #HID继电器
--device /dev/ttyUSB0:/dev/ttyUSB0 -e CH9329SPEED=9600 \ #CH9329键盘鼠标
-v /usr/local/linker-docker/kvmd:/etc/kvmd \ #映射配置目录
-e USERNAME=snltty -e PASSWORD=snltty -e NOAUTH=1 \ #账号密码,禁用认证
-e NOVNC=1 \ #禁用vnc
-e NOIPMI=1 \ #禁用ipmi
-e NOWEBTERM=1 -e NOWEBTERMWRITE=1 \ #禁用webterm
-e NOSSL=1 \ #禁用ssl
snltty/linker-kvm
```
#### 客户端-host
```
docker run -it -d --name linker \
@@ -58,6 +90,32 @@ docker run -it -d --name linker \
--network host \
snltty/linker-musl
```
#### 客户端-host-kvm
1. 基于[One-KVM](https://one-kvm.mofeng.run/start_install/docker_install/)的docker镜像包装集成linker
2. 在linker参数的基础上再加上KVM的参数根据自己的需求修改一下参数即可
3. 使用 1806 端口打开KVM管理页面
```
docker run --name linker-kvm -itd \
-v /usr/local/linker-docker/configs:/app/configs \
-v /usr/local/linker-docker/logs:/app/logs \
--device /dev/net/tun \
--restart=always \
--privileged=true \
--network host \
--device /dev/video0:/dev/video0 \ #采集卡
--device /dev/snd:/dev/snd -e AUDIONUM=0 \ #声卡
--device /dev/hidraw1:/dev/hidraw1 -e ATX=USBRELAY_HID \ #HID继电器
--device /dev/ttyUSB0:/dev/ttyUSB0 -e CH9329SPEED=9600 \ #CH9329键盘鼠标
-v /usr/local/linker-docker/kvmd:/etc/kvmd \ #映射配置目录
-e USERNAME=snltty -e PASSWORD=snltty -e NOAUTH=1 \ #账号密码,禁用认证
-e NOVNC=1 \ #禁用vnc
-e NOIPMI=1 \ #禁用ipmi
-e NOWEBTERM=1 -e NOWEBTERMWRITE=1 \ #禁用webterm
-e NOSSL=1 \ #禁用ssl
snltty/linker-kvm
```
#### 服务端-bridge
```

View File

@@ -19,9 +19,8 @@ sidebar_position: 8
```
{
"Name": "A", //客户端名
"Access": 4503599627370495, //权限值,可以看`src/linker.messenger.api/Config.cs`里的`AccessValue`枚举
"AccessBits": '11111111111', //按下标1有权限0无权限,可以看`src/linker.messenger.api/Config.cs`里的`AccessValue`枚举
"CApi": {
"ApiPort": 1803, //api接口
"ApiPassword": "snltty", //api密码
"WebPort": 1804, //web端口
"WebRoot": "./web/" //web根目录

View File

@@ -17,7 +17,7 @@ sidebar_position: 6
### 1.2、动态配置示例
以javascript 为例,设置参数方法:
```
const ws = new WebSocket(`ws://127.0.0.1:1803`, ['接口密钥']);
const ws = new WebSocket(`ws://127.0.0.1:1804`, ['接口密钥']);
ws.onopen = () => {
const json = {Key:'token',Value:'snltty'}; //你的参数内容json格式任意内容将原样post到ActionUrl
ws.send(JSON.stringify({

View File

@@ -1,202 +0,0 @@
using linker.libs.extends;
using linker.libs.websocket;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace linker.libs.api
{
/// <summary>
/// 前段接口服务
/// </summary>
public class ApiServer : IApiServer
{
protected readonly Dictionary<string, PluginPathCacheInfo> plugins = new();
protected readonly ConcurrentDictionary<uint, ConnectionTimeInfo> connectionTimes = new();
public uint OnlineNum = 0;
private string password = string.Empty;
private WebSocketServer server;
public ApiServer()
{
}
/// <summary>
/// 开启websockt
/// </summary>
public void Websocket(int port, string password = "")
{
this.password = password;
server = new WebSocketServer();
try
{
server.Start(port);
}
catch (Exception ex)
{
LoggerHelper.Instance.Error(ex);
}
server.OnConnecting = (connection, header) =>
{
bool res = string.IsNullOrWhiteSpace(this.password) || (header.TryGetHeaderValue(WebsocketHeaderKey.SecWebSocketProtocol, out string _password) && string.Equals(_password, this.password));
if (res)
{
header.SetHeaderValue(WebsocketHeaderKey.SecWebSocketExtensions, string.Empty);
}
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
header.TryGetHeaderValue(WebsocketHeaderKey.SecWebSocketProtocol, out string _password1);
LoggerHelper.Instance.Info($"websocket client password {_password1} eq {password}");
}
return res;
};
server.OnOpen = (connection) =>
{
Interlocked.Increment(ref OnlineNum);
connectionTimes.TryAdd(connection.Id, new ConnectionTimeInfo());
};
server.OnDisConnectd = (connection) =>
{
Interlocked.Decrement(ref OnlineNum);
if (OnlineNum < 0) Interlocked.Exchange(ref OnlineNum, 0);
connectionTimes.TryRemove(connection.Id, out _);
};
server.OnMessage = (connection, frame, message) =>
{
if (connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo))
{
timeInfo.DateTime = DateTime.Now;
}
var req = message.DeJson<ApiControllerRequestInfo>();
req.Connection = connection;
OnMessage(req).ContinueWith((result) =>
{
var resp = result.Result.ToJson().ToBytes();
connection.SendFrameText(resp);
});
};
}
public void SetPassword(string password)
{
this.password = password;
}
/// <summary>
/// 收到消息
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<ApiControllerResponseInfo> OnMessage(ApiControllerRequestInfo model)
{
model.Path = model.Path.ToLower();
if (plugins.TryGetValue(model.Path, out PluginPathCacheInfo plugin) == false)
{
return new ApiControllerResponseInfo
{
Content = $"{model.Path} not exists",
RequestId = model.RequestId,
Path = model.Path,
Code = ApiControllerResponseCodes.NotFound
};
}
if (plugin.HasAccess(plugin.Access) == false)
{
return new ApiControllerResponseInfo
{
Content = "no permission",
RequestId = model.RequestId,
Path = model.Path,
Code = ApiControllerResponseCodes.Error
};
}
try
{
ApiControllerParamsInfo param = new ApiControllerParamsInfo
{
RequestId = model.RequestId,
Content = model.Content,
Connection = model.Connection
};
dynamic resultAsync = plugin.Method.Invoke(plugin.Target, new object[] { param });
object resultObject = null;
if (plugin.IsVoid == false)
{
if (plugin.IsTask)
{
await resultAsync.ConfigureAwait(false);
if (plugin.IsTaskResult)
{
resultObject = resultAsync.Result;
}
}
else
{
resultObject = resultAsync;
}
}
return new ApiControllerResponseInfo
{
Code = param.Code,
Content = param.Code != ApiControllerResponseCodes.Error ? resultObject : param.ErrorMessage,
RequestId = model.RequestId,
Path = model.Path,
};
}
catch (Exception ex)
{
LoggerHelper.Instance.Error($"{model.Path} -> {ex.Message}");
return new ApiControllerResponseInfo
{
Content = ex.Message,
RequestId = model.RequestId,
Path = model.Path,
Code = ApiControllerResponseCodes.Error
};
}
}
}
public sealed class ConnectionTimeInfo
{
public DateTime DateTime { get; set; } = DateTime.Now;
}
/// <summary>
/// 前段接口缓存
/// </summary>
public struct PluginPathCacheInfo
{
/// <summary>
/// 对象
/// </summary>
public object Target { get; set; }
/// <summary>
/// 方法
/// </summary>
public MethodInfo Method { get; set; }
/// <summary>
/// 是否void
/// </summary>
public bool IsVoid { get; set; }
/// <summary>
/// 是否task
/// </summary>
public bool IsTask { get; set; }
/// <summary>
/// 是否task result
/// </summary>
public bool IsTaskResult { get; set; }
public int Access { get; set; }
public Func<int, bool> HasAccess { get; set; }
}
}

View File

@@ -1,15 +0,0 @@
namespace linker.libs.api
{
/// <summary>
/// 前端接口服务
/// </summary>
public interface IApiServer
{
/// <summary>
/// websocket
/// </summary>
public void Websocket(int port,string password);
public void SetPassword(string password);
}
}

View File

@@ -1,8 +1,9 @@
using linker.libs.websocket;
using System;
using System.Net.WebSockets;
using System.Text.Json.Serialization;
namespace linker.libs.api
namespace linker.libs.web
{
/// <summary>
/// 前段接口
@@ -38,7 +39,7 @@ namespace linker.libs.api
public sealed class ApiControllerRequestInfo
{
[JsonIgnore]
public WebsocketConnection Connection { get; set; }
public WebSocket Connection { get; set; }
/// <summary>
/// 路径
/// </summary>
@@ -58,7 +59,7 @@ namespace linker.libs.api
/// </summary>
public sealed class ApiControllerParamsInfo
{
public WebsocketConnection Connection { get; set; }
public WebSocket Connection { get; set; }
/// <summary>
/// 请求id
/// </summary>

View File

@@ -1,4 +1,6 @@
namespace linker.libs.web
using System.Collections.Generic;
namespace linker.libs.web
{
/// <summary>
/// web服务
@@ -8,7 +10,8 @@
/// <summary>
/// 开始
/// </summary>
public void Start(int port, string root);
public void Start(int port, string root,string password);
public void SetPassword(string password);
}
}

View File

@@ -1,7 +1,12 @@
using System;
using linker.libs.extends;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.WebSockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace linker.libs.web
@@ -12,6 +17,8 @@ namespace linker.libs.web
public class WebServer : IWebServer
{
private string root = "";
private string password = Helper.GlobalString;
protected readonly Dictionary<string, PluginPathCacheInfo> plugins = new();
private readonly IWebServerFileReader fileReader;
public WebServer(IWebServerFileReader fileReader)
@@ -22,10 +29,11 @@ namespace linker.libs.web
/// <summary>
/// 开启web
/// </summary>
public void Start(int port, string root)
public void Start(int port, string root, string password)
{
this.root = root;
Task.Factory.StartNew(() =>
this.password = password;
Task.Factory.StartNew(async () =>
{
try
{
@@ -34,7 +42,25 @@ namespace linker.libs.web
http.Prefixes.Add($"http://+:{port}/");
http.Start();
http.BeginGetContext(Callback, http);
while (true)
{
try
{
HttpListenerContext context = await http.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
HttpListenerWebSocketContext wsContext = await context.AcceptWebSocketAsync(null);
HandleWs(wsContext.WebSocket);
}
else
{
HandleWeb(context);
}
}
catch (Exception)
{
}
}
}
catch (Exception ex)
{
@@ -42,13 +68,11 @@ namespace linker.libs.web
}
}, TaskCreationOptions.LongRunning);
}
private void Callback(IAsyncResult result)
private void HandleWeb(HttpListenerContext context)
{
HttpListener http = result.AsyncState as HttpListener;
HttpListenerContext context = http.EndGetContext(result);
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
try
{
response.Headers.Set("Server", Helper.GlobalString);
@@ -99,11 +123,7 @@ namespace linker.libs.web
}
response.Close();
http.BeginGetContext(Callback, http);
}
private Dictionary<string, string> types = new Dictionary<string, string> {
{ ".webp","image/webp"},
{ ".png","image/png"},
@@ -127,6 +147,133 @@ namespace linker.libs.web
}
return "application/octet-stream";
}
public void SetPassword(string password)
{
this.password = password;
}
private async void HandleWs(WebSocket websocket)
{
byte[] buffer = new byte[8 * 1024];
try
{
WebSocketReceiveResult result = await websocket.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType != WebSocketMessageType.Text)
{
await websocket.CloseAsync(WebSocketCloseStatus.ProtocolError, "password fail", CancellationToken.None);
return;
}
ApiControllerRequestInfo req = Encoding.UTF8.GetString(buffer.AsMemory(0, result.Count).Span).DeJson<ApiControllerRequestInfo>();
if (req.Path != "password" || req.Content != this.password)
{
await websocket.CloseAsync(WebSocketCloseStatus.ProtocolError, "password fail", CancellationToken.None);
return;
}
await websocket.SendAsync(new ApiControllerResponseInfo
{
Code = ApiControllerResponseCodes.Success,
Path = req.Path,
RequestId = req.RequestId,
Content = "password ok",
}.ToJson().ToBytes(), WebSocketMessageType.Text, true, CancellationToken.None);
while (websocket.State == WebSocketState.Open)
{
result = await websocket.ReceiveAsync(buffer, CancellationToken.None);
switch (result.MessageType)
{
case WebSocketMessageType.Text:
{
req = Encoding.UTF8.GetString(buffer.AsMemory(0, result.Count).Span).DeJson<ApiControllerRequestInfo>();
req.Connection = websocket;
ApiControllerResponseInfo resp = await OnMessage(req);
await websocket.SendAsync(resp.ToJson().ToBytes(), WebSocketMessageType.Text, true, CancellationToken.None);
}
break;
case WebSocketMessageType.Binary:
break;
case WebSocketMessageType.Close:
await websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", CancellationToken.None);
break;
default:
break;
}
}
}
catch (Exception ex)
{
LoggerHelper.Instance.Error(ex);
}
}
private async Task<ApiControllerResponseInfo> OnMessage(ApiControllerRequestInfo model)
{
model.Path = model.Path.ToLower();
if (plugins.TryGetValue(model.Path, out PluginPathCacheInfo plugin) == false)
{
return new ApiControllerResponseInfo
{
Content = $"{model.Path} not exists",
RequestId = model.RequestId,
Path = model.Path,
Code = ApiControllerResponseCodes.NotFound
};
}
if (plugin.HasAccess(plugin.Access) == false)
{
return new ApiControllerResponseInfo
{
Content = "no permission",
RequestId = model.RequestId,
Path = model.Path,
Code = ApiControllerResponseCodes.Error
};
}
try
{
ApiControllerParamsInfo param = new ApiControllerParamsInfo
{
RequestId = model.RequestId,
Content = model.Content,
Connection = model.Connection
};
dynamic resultAsync = plugin.Method.Invoke(plugin.Target, new object[] { param });
object resultObject = null;
if (plugin.IsVoid == false)
{
if (plugin.IsTask)
{
await resultAsync.ConfigureAwait(false);
if (plugin.IsTaskResult)
{
resultObject = resultAsync.Result;
}
}
else
{
resultObject = resultAsync;
}
}
return new ApiControllerResponseInfo
{
Code = param.Code,
Content = param.Code != ApiControllerResponseCodes.Error ? resultObject : param.ErrorMessage,
RequestId = model.RequestId,
Path = model.Path,
};
}
catch (Exception ex)
{
LoggerHelper.Instance.Error($"{model.Path} -> {ex.Message}");
return new ApiControllerResponseInfo
{
Content = ex.Message,
RequestId = model.RequestId,
Path = model.Path,
Code = ApiControllerResponseCodes.Error
};
}
}
}
@@ -143,4 +290,31 @@ namespace linker.libs.web
return File.ReadAllBytes(fileName);
}
}
public struct PluginPathCacheInfo
{
/// <summary>
/// 对象
/// </summary>
public object Target { get; set; }
/// <summary>
/// 方法
/// </summary>
public MethodInfo Method { get; set; }
/// <summary>
/// 是否void
/// </summary>
public bool IsVoid { get; set; }
/// <summary>
/// 是否task
/// </summary>
public bool IsTask { get; set; }
/// <summary>
/// 是否task result
/// </summary>
public bool IsTaskResult { get; set; }
public int Access { get; set; }
public Func<int, bool> HasAccess { get; set; }
}
}

View File

@@ -1,11 +1,10 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using linker.libs;
using linker.messenger.signin;
using linker.messenger.api;
using IApiServer = linker.messenger.api.IApiServer;
using System.Collections.Concurrent;
using System.Collections;
using linker.libs.web;
namespace linker.messenger.access
{
@@ -18,9 +17,10 @@ namespace linker.messenger.access
private readonly ISerializer serializer;
private readonly IAccessStore accessStore;
private readonly IApiStore apiStore;
private readonly IApiServer apiServer;
private readonly linker.messenger.api.IWebServer apiServer;
public AccessApiController(IMessengerSender sender, SignInClientState signInClientState, AccessDecenter accessDecenter, ISignInClientStore signInClientStore, ISerializer serializer, IAccessStore accessStore, IApiStore apiStore, IApiServer apiServer)
public AccessApiController(IMessengerSender sender, SignInClientState signInClientState, AccessDecenter accessDecenter,
ISignInClientStore signInClientStore, ISerializer serializer, IAccessStore accessStore, IApiStore apiStore, linker.messenger.api.IWebServer apiServer)
{
this.sender = sender;
this.signInClientState = signInClientState;

View File

@@ -1,8 +1,6 @@
using linker.libs;
using linker.libs.api;
using linker.messenger.api;
using linker.messenger.signin;
using IApiServer = linker.messenger.api.IApiServer;
namespace linker.messenger.access
{
@@ -96,8 +94,8 @@ namespace linker.messenger.access
private readonly IAccessStore accessStore;
private readonly ISerializer serializer;
private readonly IApiStore apiStore;
private readonly IApiServer apiServer;
public AccessClientMessenger(IAccessStore accessStore, ISerializer serializer, IApiStore apiStore, IApiServer apiServer)
private readonly linker.messenger.api.IWebServer apiServer;
public AccessClientMessenger(IAccessStore accessStore, ISerializer serializer, IApiStore apiStore, linker.messenger.api.IWebServer apiServer)
{
this.accessStore = accessStore;
this.serializer = serializer;

View File

@@ -1,4 +1,5 @@
using linker.messenger.access;
using linker.libs.web;
using linker.messenger.access;
using linker.messenger.decenter;
using Microsoft.Extensions.DependencyInjection;
namespace linker.messenger.api
@@ -14,8 +15,8 @@ namespace linker.messenger.api
}
public static ServiceProvider UseAccessClient(this ServiceProvider serviceProvider)
{
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<AccessApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<AccessApiController>() });
DecenterClientTransfer decenterClientTransfer = serviceProvider.GetService<DecenterClientTransfer>();
decenterClientTransfer.AddDecenters(new List<IDecenter> { serviceProvider.GetService<AccessDecenter>() });

View File

@@ -1,5 +1,5 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using linker.libs.web;
using linker.messenger.api;
namespace linker.messenger.action

View File

@@ -1,4 +1,5 @@
using linker.messenger.action;
using linker.libs.web;
using linker.messenger.action;
using linker.messenger.relay.server.validator;
using linker.messenger.sforward.server.validator;
using linker.messenger.signin.args;
@@ -17,8 +18,8 @@ namespace linker.messenger.api
}
public static ServiceProvider UseActionClient(this ServiceProvider serviceProvider)
{
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<ActionApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<ActionApiController>() });
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> { serviceProvider.GetService<SignInArgsAction>() });

View File

@@ -1,62 +0,0 @@
using linker.libs;
using linker.libs.api;
using System.Reflection;
namespace linker.messenger.api
{
/// <summary>
/// 前段接口服务
/// </summary>
public sealed partial class ApiServer : libs.api.ApiServer, IApiServer
{
private readonly IAccessStore accessStore;
public ApiServer(IAccessStore accessStore)
{
this.accessStore = accessStore;
}
/// <summary>
/// 加载插件
/// </summary>
public void AddPlugins(List<IApiController> list)
{
Type voidType = typeof(void);
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"add api {string.Join(",", list.Select(c => c.GetType().Name))}");
foreach (IApiController obj in list)
{
Type type = obj.GetType();
string path = type.Name.Replace("ApiController", "").Replace("ApiController", "");
foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
string key = $"{path}/{method.Name}".ToLower();
if (plugins.ContainsKey(key) == false)
{
bool istask = method.ReturnType.GetProperty("IsCompleted") != null && method.ReturnType.GetMethod("GetAwaiter") != null;
bool isTaskResult = method.ReturnType.GetProperty("Result") != null;
AccessAttribute accessAttr = method.GetCustomAttribute<AccessAttribute>();
int access = (int)(accessAttr?.Value ?? 0);
plugins.TryAdd(key, new PluginPathCacheInfo
{
IsVoid = method.ReturnType == voidType,
Method = method,
Target = obj,
IsTask = istask,
IsTaskResult = isTaskResult,
Access = access,
HasAccess = HasAccess,
});
}
}
}
}
private bool HasAccess(int access)
{
return accessStore.HasAccess((AccessValue)access);
}
}
}

View File

@@ -7,7 +7,6 @@ namespace linker.messenger.api
{
public static ServiceCollection AddApiClient(this ServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IApiServer, ApiServer>();
serviceCollection.AddSingleton<IWebServer, WebServer>();
serviceCollection.AddSingleton<IWebServerFileReader, WebServerFileReader>();
@@ -18,21 +17,12 @@ namespace linker.messenger.api
{
IApiStore apiStore = serviceProvider.GetService<IApiStore>();
IAccessStore accessStore = serviceProvider.GetService<IAccessStore>();
if (apiStore.Info.ApiPort > 0 && accessStore.HasAccess(AccessValue.Api))
{
LoggerHelper.Instance.Info($"start client api");
IApiServer server = serviceProvider.GetService<IApiServer>();
server.Websocket(apiStore.Info.ApiPort, apiStore.Info.ApiPassword);
LoggerHelper.Instance.Warning($"client api listen:{apiStore.Info.ApiPort}");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Warning($"client api password:{apiStore.Info.ApiPassword}");
}
if (apiStore.Info.WebPort > 0 && accessStore.HasAccess(AccessValue.Web))
{
LoggerHelper.Instance.Info($"start client web");
IWebServer webServer = serviceProvider.GetService<IWebServer>();
webServer.Start(apiStore.Info.WebPort, apiStore.Info.WebRoot);
webServer.Start(apiStore.Info.WebPort, apiStore.Info.WebRoot, apiStore.Info.ApiPassword);
LoggerHelper.Instance.Warning($"client web listen:{apiStore.Info.WebPort}");
}
return serviceProvider;

View File

@@ -1,10 +0,0 @@
using linker.libs.api;
namespace linker.messenger.api
{
public interface IApiServer : libs.api.IApiServer
{
public void AddPlugins(List<IApiController> list);
}
}

View File

@@ -3,15 +3,10 @@ namespace linker.messenger.api
{
public sealed class ApiClientInfo
{
/// <summary>
/// 管理接口端口
/// </summary>
public int ApiPort { get; set; } = 1803;
/// <summary>
/// 管理接口密码
/// </summary>
public string ApiPassword { get; set; } = Helper.GlobalString;
/// <summary>
/// 网站端口
/// </summary>
@@ -33,12 +28,6 @@ namespace linker.messenger.api
/// <returns></returns>
public bool Set(ApiClientInfo info);
/// <summary>
/// 设置接口端口
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
public bool SetApiPort(int port);
/// <summary>
/// 设置接口密码
/// </summary>
/// <param name="password"></param>

View File

@@ -1,15 +1,69 @@
using linker.libs.web;
using linker.libs;
using linker.libs.web;
using System.Reflection;
namespace linker.messenger.api
{
public interface IWebServer : libs.web.IWebServer
{
/// <summary>
/// 加载插件
/// </summary>
void AddPlugins(List<IApiController> list);
}
/// <summary>
/// 本地web管理端服务器
/// </summary>
public sealed class WebServer : libs.web.WebServer, IWebServer
{
public WebServer(IWebServerFileReader webServerFileReader) : base(webServerFileReader)
private readonly IAccessStore accessStore;
public WebServer(IWebServerFileReader webServerFileReader, IAccessStore accessStore) : base(webServerFileReader)
{
this.accessStore = accessStore;
}
/// <summary>
/// 加载插件
/// </summary>
public void AddPlugins(List<IApiController> list)
{
Type voidType = typeof(void);
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"add api {string.Join(",", list.Select(c => c.GetType().Name))}");
foreach (IApiController obj in list)
{
Type type = obj.GetType();
string path = type.Name.Replace("ApiController", "").Replace("ApiController", "");
foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
string key = $"{path}/{method.Name}".ToLower();
if (plugins.ContainsKey(key) == false)
{
bool istask = method.ReturnType.GetProperty("IsCompleted") != null && method.ReturnType.GetMethod("GetAwaiter") != null;
bool isTaskResult = method.ReturnType.GetProperty("Result") != null;
AccessAttribute accessAttr = method.GetCustomAttribute<AccessAttribute>();
int access = (int)(accessAttr?.Value ?? 0);
plugins.TryAdd(key, new PluginPathCacheInfo
{
IsVoid = method.ReturnType == voidType,
Method = method,
Target = obj,
IsTask = istask,
IsTaskResult = isTaskResult,
Access = access,
HasAccess = HasAccess,
});
}
}
}
}
private bool HasAccess(int access)
{
return accessStore.HasAccess(AccessValue.Api) && accessStore.HasAccess((AccessValue)access);
}
}

View File

@@ -1,5 +1,5 @@

using linker.messenger.api;
using linker.libs.web;
using linker.snat;
using Microsoft.Extensions.DependencyInjection;
@@ -23,8 +23,8 @@ namespace linker.messenger.firewall
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<FirewallClientMessenger>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<FirewallApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<FirewallApiController>() });
return serviceProvider;
}

View File

@@ -1,8 +1,8 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using linker.libs;
using linker.messenger.signin;
using linker.messenger.api;
using linker.libs.web;
namespace linker.messenger.firewall
{

View File

@@ -1,4 +1,5 @@
using linker.messenger.api;
using linker.libs.web;
using linker.messenger.api;
using linker.messenger.flow.messenger;
using linker.messenger.relay.server;
using linker.messenger.tunnel;
@@ -22,8 +23,8 @@ namespace linker.messenger.flow
}
public static ServiceProvider UseFlowClient(this ServiceProvider serviceProvider)
{
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<FlowApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<FlowApiController>() });
FlowTransfer flowTransfer = serviceProvider.GetService<FlowTransfer>();
flowTransfer.AddFlows(new List<IFlow> { serviceProvider.GetService<MessengerFlow>() });

View File

@@ -1,6 +1,6 @@
using linker.libs;
using linker.libs.api;
using linker.libs.extends;
using linker.libs.web;
using linker.messenger.api;
using linker.messenger.flow.messenger;
using linker.messenger.relay.client;

View File

@@ -1,4 +1,5 @@
using linker.messenger.api;
using linker.libs.web;
using linker.messenger.api;
using linker.messenger.decenter;
using linker.messenger.forward.proxy;
using Microsoft.Extensions.DependencyInjection;
@@ -23,8 +24,8 @@ namespace linker.messenger.forward
}
public static ServiceProvider UseForwardClient(this ServiceProvider serviceProvider)
{
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<ForwardApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<ForwardApiController>() });
ForwardTransfer forwardTransfer = serviceProvider.GetService<ForwardTransfer>();

View File

@@ -1,5 +1,4 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using System.Net;
using linker.libs;
using linker.tunnel.connection;
@@ -7,6 +6,7 @@ using System.Collections.Concurrent;
using linker.messenger.signin;
using linker.messenger.forward.proxy;
using linker.messenger.api;
using linker.libs.web;
namespace linker.messenger.forward
{

View File

@@ -1,5 +1,6 @@
using linker.libs;
using linker.libs.timer;
using linker.libs.web;
using linker.messenger.api;
using Microsoft.Extensions.DependencyInjection;
namespace linker.messenger.logger
@@ -25,8 +26,8 @@ namespace linker.messenger.logger
}
public static ServiceProvider UseLoggerClient(this ServiceProvider serviceProvider)
{
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<LoggerApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<LoggerApiController>() });
IAccessStore accessStore= serviceProvider.GetService<IAccessStore>();
ILoggerStore loggerStore= serviceProvider.GetService<ILoggerStore>();

View File

@@ -1,7 +1,7 @@
using linker.libs.extends;
using linker.libs.api;
using linker.libs;
using linker.messenger.api;
using linker.libs.web;
namespace linker.messenger.logger
{

View File

@@ -1,4 +1,5 @@

using linker.libs.web;
using linker.messenger.api;
using Microsoft.Extensions.DependencyInjection;
@@ -21,8 +22,8 @@ namespace linker.messenger.plan
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<PlanClientMessenger>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<PlanApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<PlanApiController>() });
return serviceProvider;
}

View File

@@ -1,6 +1,6 @@
using linker.libs;
using linker.libs.api;
using linker.libs.extends;
using linker.libs.web;
using linker.messenger.signin;
namespace linker.messenger.plan

View File

@@ -1,4 +1,4 @@
using linker.messenger.api;
using linker.libs.web;
using linker.messenger.relay.client;
using linker.messenger.relay.messenger;
using linker.messenger.relay.server;
@@ -31,8 +31,8 @@ namespace linker.messenger.relay
SyncTreansfer syncTreansfer = serviceProvider.GetService<SyncTreansfer>();
syncTreansfer.AddSyncs(new List<ISync> { serviceProvider.GetService<RelaySyncSecretKey>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<RelayApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<RelayApiController>() });
RelayClientTestTransfer relayClientTestTransfer = serviceProvider.GetService<RelayClientTestTransfer>();

View File

@@ -1,6 +1,6 @@
using linker.libs;
using linker.libs.api;
using linker.libs.extends;
using linker.libs.web;
using linker.messenger.api;
using linker.messenger.relay.client;
using linker.messenger.relay.client.transport;

View File

@@ -1,4 +1,5 @@
using linker.messenger.api;
using linker.libs.web;
using linker.messenger.api;
using linker.messenger.decenter;
using linker.messenger.plan;
using linker.messenger.sforward.client;
@@ -33,8 +34,8 @@ namespace linker.messenger.sforward
}
public static ServiceProvider UseSForwardClient(this ServiceProvider serviceProvider)
{
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<SForwardApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<SForwardApiController>() });
SForwardClientTransfer sForwardClientTransfer = serviceProvider.GetService<SForwardClientTransfer>();

View File

@@ -1,10 +1,10 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using linker.plugins.sforward.messenger;
using System.Collections.Concurrent;
using linker.messenger.signin;
using linker.libs;
using linker.messenger.api;
using linker.libs.web;
namespace linker.messenger.sforward.client
{

View File

@@ -1,5 +1,5 @@
using linker.libs;
using linker.libs.api;
using linker.libs.web;
using linker.messenger.exroute;
using linker.messenger.signin.args;
using Microsoft.Extensions.DependencyInjection;
@@ -36,7 +36,7 @@ namespace linker.messenger.signin
serviceProvider.GetService<SignInArgsVersionClient>(),
});
linker.messenger.api.IApiServer apiServer = serviceProvider.GetService<linker.messenger.api.IApiServer>();
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> {
serviceProvider.GetService<SignInApiController>()
});

View File

@@ -1,6 +1,6 @@
using linker.libs;
using linker.libs.api;
using linker.libs.extends;
using linker.libs.web;
using linker.messenger.api;
namespace linker.messenger.signin

View File

@@ -1,4 +1,5 @@
using linker.messenger.api;
using linker.libs.web;
using linker.messenger.api;
using linker.messenger.decenter;
using linker.messenger.exroute;
using Microsoft.Extensions.DependencyInjection;
@@ -28,8 +29,8 @@ namespace linker.messenger.socks5
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<Socks5ClientMessenger>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<Socks5ApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<Socks5ApiController>() });
DecenterClientTransfer decenterClientTransfer = serviceProvider.GetService<DecenterClientTransfer>();
decenterClientTransfer.AddDecenters(new List<IDecenter> { serviceProvider.GetService<Socks5Decenter>() });

View File

@@ -1,10 +1,10 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using System.Collections.Concurrent;
using linker.tunnel.connection;
using linker.messenger.signin;
using linker.libs;
using linker.messenger.api;
using linker.libs.web;
namespace linker.messenger.socks5
{

View File

@@ -1,5 +1,4 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using System.IO.Compression;
using linker.libs;
using linker.messenger.signin;
@@ -8,6 +7,7 @@ using System.Text;
using linker.messenger.relay.client.transport;
using System.Text.Json;
using System.Collections;
using linker.libs.web;
namespace linker.messenger.store.file
{
public sealed class ConfigApiController : IApiController
@@ -48,7 +48,6 @@ namespace linker.messenger.store.file
config.Data.Client.Name = info.Client.Name;
config.Data.Client.Groups = new SignInClientGroupInfo[] { new SignInClientGroupInfo { Id = info.Client.GroupId, Name = info.Client.GroupId, Password = info.Client.GroupPassword } };
config.Data.Client.CApi.WebPort = info.Client.Web;
config.Data.Client.CApi.ApiPort = info.Client.Api;
config.Data.Client.CApi.ApiPassword = info.Client.Password;
if (info.Client.HasServer)
@@ -258,7 +257,6 @@ namespace linker.messenger.store.file
client.CApi.ApiPassword = configExportInfo.ApiPassword;
}
client.CApi.WebPort = configExportInfo.WebPort;
client.CApi.ApiPort = configExportInfo.ApiPort;
client.AccessBits = accessStore.AssignAccess(configExportInfo.Access);
@@ -321,7 +319,6 @@ namespace linker.messenger.store.file
public string GroupId { get; set; }
public string GroupPassword { get; set; }
public int Api { get; set; }
public int Web { get; set; }
public string Password { get; set; }
@@ -370,7 +367,6 @@ namespace linker.messenger.store.file
public string Name { get; set; }
public string ApiPassword { get; set; }
public int WebPort { get; set; }
public int ApiPort { get; set; }
public bool Single { get; set; }
public BitArray Access { get; set; }

View File

@@ -1,4 +1,5 @@
using linker.libs;
using linker.libs.web;
using linker.messenger.action;
using linker.messenger.api;
using linker.messenger.firewall;
@@ -126,8 +127,8 @@ namespace linker.messenger.store.file
fileConfig.Save(config);
RunningConfig runningConfig = serviceProvider.GetService<RunningConfig>();
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<ConfigApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<ConfigApiController>() });
SyncTreansfer syncTreansfer = serviceProvider.GetService<SyncTreansfer>();
syncTreansfer.AddSyncs(new List<ISync> {

View File

@@ -24,12 +24,6 @@ namespace linker.messenger.store.file.api
return true;
}
public bool SetApiPort(int port)
{
fileConfig.Data.Client.CApi.ApiPort = port;
return true;
}
public bool SetApiPassword(string password)
{
fileConfig.Data.Client.CApi.ApiPassword = password;

View File

@@ -1,6 +1,5 @@
using linker.libs.api;
using linker.libs.web;
using Microsoft.Extensions.DependencyInjection;
using IApiServer = linker.messenger.api.IApiServer;
namespace linker.messenger.sync
{
public static class Entry
@@ -17,7 +16,7 @@ namespace linker.messenger.sync
IMessengerResolver messengerResolver= serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<SyncClientMessenger>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<SyncApiController>() });
return serviceProvider;

View File

@@ -1,5 +1,5 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using linker.libs.web;
using linker.messenger.api;
namespace linker.messenger.sync
{

View File

@@ -8,6 +8,7 @@ using System.Text.Json;
using linker.tunnel.connection;
using linker.messenger.signin.args;
using linker.messenger.sync;
using linker.libs.web;
namespace linker.messenger.tunnel
{
public static class Entry
@@ -55,8 +56,8 @@ namespace linker.messenger.tunnel
DecenterClientTransfer decenterClientTransfer = serviceProvider.GetService<DecenterClientTransfer>();
decenterClientTransfer.AddDecenters(new List<IDecenter> { serviceProvider.GetService<TunnelDecenter>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<TunnelApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<TunnelApiController>() });
ExRouteTransfer exRouteTransfer = serviceProvider.GetService<ExRouteTransfer>();

View File

@@ -1,5 +1,4 @@
using linker.tunnel.transport;
using linker.libs.api;
using linker.libs.extends;
using System.Collections.Concurrent;
using linker.messenger.signin;
@@ -7,6 +6,7 @@ using linker.libs;
using linker.messenger.api;
using linker.tunnel.connection;
using linker.tunnel;
using linker.libs.web;
namespace linker.messenger.tunnel
{

View File

@@ -1,5 +1,6 @@
using linker.libs;
using linker.libs.extends;
using linker.libs.web;
using linker.messenger.api;
using linker.messenger.decenter;
using linker.messenger.exroute;
@@ -61,8 +62,8 @@ namespace linker.messenger.tuntap
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<TuntapClientMessenger>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<TuntapApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<TuntapApiController>() });
ExRouteTransfer exRouteTransfer = serviceProvider.GetService<ExRouteTransfer>();
exRouteTransfer.AddExRoutes(new List<IExRoute> { serviceProvider.GetService<TuntapExRoute>() });

View File

@@ -1,5 +1,4 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using System.Collections.Concurrent;
using linker.tunnel.connection;
using System.Net;
@@ -8,6 +7,7 @@ using linker.messenger.signin;
using linker.messenger.tuntap.lease;
using linker.messenger.api;
using linker.messenger.tuntap.messenger;
using linker.libs.web;
namespace linker.messenger.tuntap
{

View File

@@ -1,4 +1,4 @@
using linker.messenger.api;
using linker.libs.web;
using linker.messenger.sync;
using Microsoft.Extensions.DependencyInjection;
namespace linker.messenger.updater
@@ -30,8 +30,8 @@ namespace linker.messenger.updater
SyncTreansfer syncTransfer = serviceProvider.GetService<SyncTreansfer>();
syncTransfer.AddSyncs(new List<ISync> { serviceProvider.GetService<UpdaterConfigSyncSecretKey>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<UpdaterApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<UpdaterApiController>() });
return serviceProvider;
}

View File

@@ -1,7 +1,7 @@
using linker.libs.api;
using linker.libs.extends;
using linker.libs.extends;
using linker.libs;
using linker.messenger.signin;
using linker.libs.web;
namespace linker.messenger.updater
{

View File

@@ -1,4 +1,5 @@

using linker.libs.web;
using linker.messenger.api;
using Microsoft.Extensions.DependencyInjection;
namespace linker.messenger.wakeup
@@ -21,8 +22,8 @@ namespace linker.messenger.wakeup
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<WakeupClientMessenger>() });
IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<WakeupApiController>() });
linker.messenger.api.IWebServer apiServer = serviceProvider.GetService<linker.messenger.api.IWebServer>();
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<WakeupApiController>() });
return serviceProvider;
}

View File

@@ -1,8 +1,8 @@
using linker.libs.api;
using linker.libs;
using linker.libs;
using linker.messenger.signin;
using linker.messenger.api;
using linker.libs.extends;
using linker.libs.web;
namespace linker.messenger.wakeup
{

View File

@@ -1,5 +1,5 @@
<?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.281" ProductVersion="0.0.0.281" 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.283" ProductVersion="0.0.0.283" publishDir="/dist/" dstrip="false" local="false" ignored="false">
<file name="main.aardio" path="main.aardio" comment="main.aardio"/>
<folder name="资源文件" path="res" embed="true" local="false" ignored="false">
<file name="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/>
@@ -51,5 +51,6 @@
<file name="app.842662e0.js" path="web\js\app.842662e0.js" comment="web\js\app.842662e0.js"/>
<file name="chunk-vendors.710dc716.js" path="web\js\chunk-vendors.710dc716.js" comment="web\js\chunk-vendors.710dc716.js"/>
</folder>
<file name="test.html" path="web\test.html" comment="web\test.html"/>
</folder>
</project>

Binary file not shown.

View File

@@ -1,6 +1,6 @@
import win.ui;
/*DSG{{*/
var winform = win.form(text="linker 管理";right=849;bottom=739;max=false;min=1;topmost=1)
var winform = win.form(text="linker 管理";right=849;bottom=739;max=false;topmost=1)
winform.add()
/*}}*/

View File

@@ -1,6 +1,6 @@
import win.ui;
/*DSG{{*/
var winform = win.form(text="linker 管理(简单)";right=399;bottom=639;border="thin";max=false;min=1;topmost=1)
var winform = win.form(text="linker 管理(简单)";right=399;bottom=639;border="thin";max=false;topmost=1)
winform.add()
/*}}*/

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.81af7d4e.js"></script><script defer="defer" src="js/app.2201faad.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.bb3197c7.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.81af7d4e.js"></script><script defer="defer" src="js/app.ca5c5474.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.af897554.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

@@ -1 +1 @@
"use strict";(self["webpackChunklinker_web"]=self["webpackChunklinker_web"]||[]).push([[663],{7332:function(e,n,a){a.r(n),a.d(n,{default:function(){return O}});var t=a(56768);const s={class:"net-wrap app-wrap"},l={class:"inner absolute flex flex-column flex-nowrap"},i={class:"head"},o={class:"body flex-1 relative"},c={class:"status"};function r(e,n,a,r,u,d){const g=(0,t.g2)("Head"),p=(0,t.g2)("List"),v=(0,t.g2)("Status");return(0,t.uX)(),(0,t.CE)("div",s,[(0,t.Lk)("div",l,[(0,t.Lk)("div",i,[(0,t.bF)(g)]),(0,t.Lk)("div",o,[(0,t.bF)(p)]),(0,t.Lk)("div",c,[(0,t.bF)(v,{config:!1})])])])}a(44114);var u=a(24232);const d=e=>((0,t.Qi)("data-v-6bfe19a3"),e=e(),(0,t.jt)(),e),g={class:"head-wrap"},p={class:"tools flex"},v={class:"label"},h=d((()=>(0,t.Lk)("span",{class:"flex-1"},null,-1))),f={style:{"margin-left":"1rem"}};function k(e,n,a,s,l,i){const o=(0,t.g2)("Refresh"),c=(0,t.g2)("el-icon"),r=(0,t.g2)("el-button"),d=(0,t.g2)("Background");return(0,t.uX)(),(0,t.CE)("div",g,[(0,t.Lk)("div",p,[(0,t.Lk)("span",v,"分组 : "+(0,u.v_)(s.state.group),1),h,(0,t.bF)(r,{size:"small",onClick:s.handleRefresh},{default:(0,t.k6)((()=>[(0,t.eW)(" 刷新(F5)"),(0,t.bF)(c,null,{default:(0,t.k6)((()=>[(0,t.bF)(o)])),_:1})])),_:1},8,["onClick"]),(0,t.Lk)("div",f,[(0,t.bF)(d,{name:"net"})])])])}var C=a(53830),m=a(90144),b=a(57477),L=a(5096),_={components:{Edit:b.ffu,Refresh:b.C42,Background:L.A},setup(){const e=(0,C.B)(),n=(0,m.Kh)({server:(0,t.EW)((()=>e.value.config.Client.Server.Host)),group:(0,t.EW)((()=>e.value.config.Client.Group.Name))}),a=()=>{window.location.reload()};return{state:n,handleRefresh:a}}},w=a(71241);const S=(0,w.A)(_,[["render",k],["__scopeId","data-v-6bfe19a3"]]);var F=S;const x=e=>((0,t.Qi)("data-v-68d1c30a"),e=e(),(0,t.jt)(),e),T={class:"net-list-wrap flex flex-column absolute"},z={class:"flex-1 scrollbar"},E={class:"flex"},A=x((()=>(0,t.Lk)("div",{class:"flex-1"},null,-1))),I={class:"tuntap"},P={class:"page t-c"},B={class:"page-wrap t-c"};function R(e,n,a,s,l,i){const o=(0,t.g2)("DeviceName"),c=(0,t.g2)("UpdaterBtn"),r=(0,t.g2)("TuntapShow"),u=(0,t.g2)("el-pagination");return(0,t.uX)(),(0,t.CE)("div",T,[(0,t.Lk)("div",z,[(0,t.Lk)("ul",null,[((0,t.uX)(!0),(0,t.CE)(t.FK,null,(0,t.pI)(s.devices.page.List,((e,n)=>((0,t.uX)(),(0,t.CE)("li",{key:n},[(0,t.Lk)("dl",null,[(0,t.Lk)("dt",E,[(0,t.Lk)("div",null,[(0,t.bF)(o,{item:e},null,8,["item"])]),A,(0,t.Lk)("div",null,[(0,t.bF)(c,{config:!1,item:e},null,8,["item"])])]),(0,t.Lk)("dd",I,[s.tuntap.list[e.MachineId]?((0,t.uX)(),(0,t.Wv)(r,{key:0,item:e},null,8,["item"])):(0,t.Q3)("",!0)])])])))),128))])]),(0,t.Lk)("div",P,[(0,t.Lk)("div",B,[(0,t.bF)(u,{size:"small",background:"",layout:"prev,pager, next","pager-count":5,total:s.devices.page.Count,"page-size":s.devices.page.Request.Size,"current-page":s.devices.page.Request.Page,onCurrentChange:s.handlePageChange,onSizeChange:s.handlePageSizeChange,"page-sizes":[10,20,50,100,255]},null,8,["total","page-size","current-page","onCurrentChange","onSizeChange"])])])])}var D=a(98104),X=a(27985),y=a(39383),N=a(62956),U=a(27569),Q=a(57671),W=a(99983),H={components:{StarFilled:b.BQ2,UpdaterBtn:N.A,DeviceName:U.A,TuntapShow:Q.A},setup(e){(0,C.B)();const n=(0,m.Kh)({}),{devices:a,machineId:s,_getSignList:l,_getSignList1:i,handleDeviceEdit:o,handlePageChange:c,handlePageSizeChange:r,handleDel:u,clearDevicesTimeout:d}=(0,X.r)(),{tuntap:g,_getTuntapInfo:p,handleTuntapRefresh:v,clearTuntapTimeout:h,handleTuntapEdit:f,sortTuntapIP:k}=(0,D.O)(),{_getUpdater:b,_subscribeUpdater:L,clearUpdaterTimeout:_}=(0,y.d)(),{connections:w,forwardConnections:S,_getForwardConnections:F,tuntapConnections:x,_getTuntapConnections:T,socks5Connections:z,_getSocks5Connections:E,handleTunnelConnections:A,clearConnectionsTimeout:I}=(0,W.L2)();return(0,t.sV)((()=>{c(),v(),l(),i(),p(),b(),L()})),(0,t.hi)((()=>{d(),h(),_()})),{state:n,devices:a,machineId:s,handlePageChange:c,handlePageSizeChange:r,tuntap:g}}};const K=(0,w.A)(H,[["render",R],["__scopeId","data-v-68d1c30a"]]);var j=K,q=a(24722),M=a(81387),V={components:{Head:F,List:j,Status:q.A},setup(){document.addEventListener("contextmenu",(function(e){e.preventDefault()}));const e=(0,C.B)(),n=(0,M.rd)();return(0,t.sV)((()=>{0==e.value.hasAccess("NetManager")&&n.push({name:"NoPermission"})})),{}}};const G=(0,w.A)(V,[["render",r],["__scopeId","data-v-6a3f3b43"]]);var O=G}}]);
"use strict";(self["webpackChunklinker_web"]=self["webpackChunklinker_web"]||[]).push([[519],{7332:function(e,n,a){a.r(n),a.d(n,{default:function(){return O}});var t=a(56768);const s={class:"net-wrap app-wrap"},l={class:"inner absolute flex flex-column flex-nowrap"},i={class:"head"},o={class:"body flex-1 relative"},c={class:"status"};function r(e,n,a,r,u,d){const g=(0,t.g2)("Head"),p=(0,t.g2)("List"),v=(0,t.g2)("Status");return(0,t.uX)(),(0,t.CE)("div",s,[(0,t.Lk)("div",l,[(0,t.Lk)("div",i,[(0,t.bF)(g)]),(0,t.Lk)("div",o,[(0,t.bF)(p)]),(0,t.Lk)("div",c,[(0,t.bF)(v,{config:!1})])])])}a(44114);var u=a(24232);const d=e=>((0,t.Qi)("data-v-6bfe19a3"),e=e(),(0,t.jt)(),e),g={class:"head-wrap"},p={class:"tools flex"},v={class:"label"},h=d((()=>(0,t.Lk)("span",{class:"flex-1"},null,-1))),f={style:{"margin-left":"1rem"}};function k(e,n,a,s,l,i){const o=(0,t.g2)("Refresh"),c=(0,t.g2)("el-icon"),r=(0,t.g2)("el-button"),d=(0,t.g2)("Background");return(0,t.uX)(),(0,t.CE)("div",g,[(0,t.Lk)("div",p,[(0,t.Lk)("span",v,"分组 : "+(0,u.v_)(s.state.group),1),h,(0,t.bF)(r,{size:"small",onClick:s.handleRefresh},{default:(0,t.k6)((()=>[(0,t.eW)(" 刷新(F5)"),(0,t.bF)(c,null,{default:(0,t.k6)((()=>[(0,t.bF)(o)])),_:1})])),_:1},8,["onClick"]),(0,t.Lk)("div",f,[(0,t.bF)(d,{name:"net"})])])])}var C=a(53830),m=a(90144),b=a(57477),L=a(5096),_={components:{Edit:b.ffu,Refresh:b.C42,Background:L.A},setup(){const e=(0,C.B)(),n=(0,m.Kh)({server:(0,t.EW)((()=>e.value.config.Client.Server.Host)),group:(0,t.EW)((()=>e.value.config.Client.Group.Name))}),a=()=>{window.location.reload()};return{state:n,handleRefresh:a}}},w=a(71241);const S=(0,w.A)(_,[["render",k],["__scopeId","data-v-6bfe19a3"]]);var F=S;const x=e=>((0,t.Qi)("data-v-68d1c30a"),e=e(),(0,t.jt)(),e),T={class:"net-list-wrap flex flex-column absolute"},z={class:"flex-1 scrollbar"},E={class:"flex"},A=x((()=>(0,t.Lk)("div",{class:"flex-1"},null,-1))),I={class:"tuntap"},P={class:"page t-c"},B={class:"page-wrap t-c"};function R(e,n,a,s,l,i){const o=(0,t.g2)("DeviceName"),c=(0,t.g2)("UpdaterBtn"),r=(0,t.g2)("TuntapShow"),u=(0,t.g2)("el-pagination");return(0,t.uX)(),(0,t.CE)("div",T,[(0,t.Lk)("div",z,[(0,t.Lk)("ul",null,[((0,t.uX)(!0),(0,t.CE)(t.FK,null,(0,t.pI)(s.devices.page.List,((e,n)=>((0,t.uX)(),(0,t.CE)("li",{key:n},[(0,t.Lk)("dl",null,[(0,t.Lk)("dt",E,[(0,t.Lk)("div",null,[(0,t.bF)(o,{item:e},null,8,["item"])]),A,(0,t.Lk)("div",null,[(0,t.bF)(c,{config:!1,item:e},null,8,["item"])])]),(0,t.Lk)("dd",I,[s.tuntap.list[e.MachineId]?((0,t.uX)(),(0,t.Wv)(r,{key:0,item:e},null,8,["item"])):(0,t.Q3)("",!0)])])])))),128))])]),(0,t.Lk)("div",P,[(0,t.Lk)("div",B,[(0,t.bF)(u,{size:"small",background:"",layout:"prev,pager, next","pager-count":5,total:s.devices.page.Count,"page-size":s.devices.page.Request.Size,"current-page":s.devices.page.Request.Page,onCurrentChange:s.handlePageChange,onSizeChange:s.handlePageSizeChange,"page-sizes":[10,20,50,100,255]},null,8,["total","page-size","current-page","onCurrentChange","onSizeChange"])])])])}var D=a(98104),X=a(27985),y=a(39383),N=a(62956),U=a(27569),Q=a(57671),W=a(99983),H={components:{StarFilled:b.BQ2,UpdaterBtn:N.A,DeviceName:U.A,TuntapShow:Q.A},setup(e){(0,C.B)();const n=(0,m.Kh)({}),{devices:a,machineId:s,_getSignList:l,_getSignList1:i,handleDeviceEdit:o,handlePageChange:c,handlePageSizeChange:r,handleDel:u,clearDevicesTimeout:d}=(0,X.r)(),{tuntap:g,_getTuntapInfo:p,handleTuntapRefresh:v,clearTuntapTimeout:h,handleTuntapEdit:f,sortTuntapIP:k}=(0,D.O)(),{_getUpdater:b,_subscribeUpdater:L,clearUpdaterTimeout:_}=(0,y.d)(),{connections:w,forwardConnections:S,_getForwardConnections:F,tuntapConnections:x,_getTuntapConnections:T,socks5Connections:z,_getSocks5Connections:E,handleTunnelConnections:A,clearConnectionsTimeout:I}=(0,W.L2)();return(0,t.sV)((()=>{c(),v(),l(),i(),p(),b(),L()})),(0,t.hi)((()=>{d(),h(),_()})),{state:n,devices:a,machineId:s,handlePageChange:c,handlePageSizeChange:r,tuntap:g}}};const K=(0,w.A)(H,[["render",R],["__scopeId","data-v-68d1c30a"]]);var j=K,q=a(44453),M=a(81387),V={components:{Head:F,List:j,Status:q.A},setup(){document.addEventListener("contextmenu",(function(e){e.preventDefault()}));const e=(0,C.B)(),n=(0,M.rd)();return(0,t.sV)((()=>{0==e.value.hasAccess("NetManager")&&n.push({name:"NoPermission"})})),{}}};const G=(0,w.A)(V,[["render",r],["__scopeId","data-v-6a3f3b43"]]);var O=G}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +1,15 @@
import { ElMessage } from 'element-plus'
let requestId = 0, ws = null, wsUrl = '', index = 1, apiPassword = '';
//请求缓存,等待回调
let requestId = 0, ws = null, wsUrl = '', index = 1, apiPassword = 'snltty';
const requests = {};
const queues = [];
export const websocketState = { connected: false, connecting: false };
const sendQueueMsg = () => {
if (queues.length > 0 && websocketState.connected && ws && ws.readyState == 1) {
try {
ws.send(queues.shift());
} catch (e) { }
}
setTimeout(sendQueueMsg, 1000 / 60);
}
//sendQueueMsg();
const sendTimeout = () => {
const time = Date.now();
for (let j in requests) {
const item = requests[j];
if (time - item.time > item.timeout) {
item.reject('超时~');
item.reject(`超时:${JSON.stringify(item)}`);
delete requests[j];
}
}
@@ -30,8 +17,6 @@ const sendTimeout = () => {
}
sendTimeout();
//发布订阅
export const pushListener = {
subs: {
},
@@ -59,10 +44,10 @@ export const pushListener = {
}
}
//消息处理
const onWebsocketOpen = () => {
websocketState.connected = true;
websocketState.connecting = false;
sendWebsocketMsg('password',apiPassword || 'snltty');
pushListener.push(websocketStateChangeKey, websocketState.connected);
}
const onWebsocketClose = (e) => {
@@ -125,8 +110,7 @@ export const initWebsocket = (url = wsUrl, password = apiPassword) => {
ws.close();
}
websocketState.connecting = true;
const protocol = password || 'snltty';
ws = new WebSocket(wsUrl, [protocol]);
ws = new WebSocket(wsUrl);
ws.iddd = ++index;
ws.onopen = onWebsocketOpen;
ws.onclose = onWebsocketClose
@@ -140,8 +124,6 @@ export const closeWebsocket = () => {
ws.close();
}
}
//发送消息
export const sendWebsocketMsg = (path, msg = {}, errHandle = false, timeout = 15000) => {
return new Promise((resolve, reject) => {
let id = ++requestId;
@@ -165,7 +147,6 @@ export const sendWebsocketMsg = (path, msg = {}, errHandle = false, timeout = 15
});
}
const websocketStateChangeKey = Symbol();
export const subWebsocketState = (callback) => {
pushListener.add(websocketStateChangeKey, callback);

View File

@@ -66,9 +66,9 @@ export default {
'status.exportSingle': '单设备',
'status.exportName': '设备名',
'status.exportNamePlease': '请输入设备名',
'status.exportApiPassword': '接口密码',
'status.exportApiPassword': '管理密码',
'status.exportApiPasswordPlease': '请输入接口密码',
'status.exportWebport': '网页端口',
'status.exportWebport': '管理端口',
'status.exportWebportPlease': '请输入网页端口',
'status.exportApiport': '接口端口',
'status.exportApiportPlease': '请输入接口端口',

View File

@@ -33,7 +33,8 @@ export default {
const router = useRouter();
const route = useRoute();
const defaultInfo = {api:`${window.location.hostname}:1803`,psd:'snltty'};
const api = process.env.NODE_ENV == 'development' ? `${window.location.hostname}:1804` : window.location.host;
const defaultInfo = {api:api,psd:'snltty'};
const queryCache = JSON.parse(sessionStorage.getItem('api-cache') || localStorage.getItem('api-cache') || JSON.stringify(defaultInfo));
const state = reactive({
api:queryCache.api,

View File

@@ -7,35 +7,30 @@
</div>
</el-form-item>
<el-form-item label="" label-width="0">
<el-row>
<el-row class="w-100">
<el-col :sm="12" :xs="24">
<el-form-item label="机器名" prop="name">
<el-input v-model="state.form.name" maxlength="32" show-word-limit />
</el-form-item>
</el-col>
<el-col :sm="12" :xs="24" v-if="globalData.isPc">
<el-form-item label="网页端口" prop="web">
<el-input v-model="state.form.web" />
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="" label-width="0" v-if="globalData.isPc">
<el-row>
<el-col :sm="12" :xs="24">
<el-form-item label="接口端口" prop="api">
<el-input v-model="state.form.api" />
<el-row class="w-100">
<el-col :sm="12" :xs="24" v-if="globalData.isPc">
<el-form-item label="管理端口" prop="web">
<el-input v-model="state.form.web" />
</el-form-item>
</el-col>
<el-col :sm="12" :xs="24">
<el-form-item label="接口密码" prop="password">
<el-form-item label="管理密码" prop="password">
<el-input type="password" v-model="state.form.password" show-password maxlength="36" show-word-limit/>
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="" label-width="0">
<el-row>
<el-row class="w-100">
<el-col :sm="12" :xs="24">
<el-form-item label="分组名" prop="groupid">
<el-input v-model="state.form.groupid" maxlength="36" show-word-limit />
@@ -49,7 +44,7 @@
</el-row>
</el-form-item>
<el-form-item label="" label-width="0">
<el-row>
<el-row class="w-100">
<el-col :span="24">
<el-form-item label-width="8rem" prop="hasServer">
<el-checkbox v-model="state.form.hasServer" label="我有服务器(私有部署)" size="large" />
@@ -59,7 +54,7 @@
</el-form-item>
<el-form-item label="" label-width="0" v-if="state.form.hasServer">
<el-row>
<el-row class="w-100">
<el-col :sm="12" :xs="24">
<el-form-item label="信标服务" prop="server">
<el-input v-model="state.form.server"/>
@@ -74,7 +69,7 @@
</el-form-item>
<el-form-item label="" label-width="0" v-if="state.form.hasServer">
<el-row>
<el-row class="w-100">
<el-col :sm="12" :xs="24">
<el-form-item label="穿透密钥" prop="sForwardSecretKey">
<el-input v-model="state.form.sForwardSecretKey" maxlength="36" show-word-limit />
@@ -88,7 +83,7 @@
</el-row>
</el-form-item>
<el-form-item label="" label-width="0" v-if="state.form.hasServer">
<el-row>
<el-row class="w-100">
<el-col :sm="12" :xs="24">
<el-form-item label="更新密钥" prop="updaterSecretKey">
<el-input v-model="state.form.updaterSecretKey" maxlength="36" show-word-limit />
@@ -118,7 +113,6 @@ export default {
name:step.value.form.client.name || globalData.value.config.Client.Name,
groupid: step.value.form.client.groupid ||globalData.value.config.Client.Group.Id,
groupPassword: step.value.form.client.groupPassword ||globalData.value.config.Client.Group.Password,
api: step.value.form.client.api ||globalData.value.config.Client.CApi.ApiPort,
web: step.value.form.client.web ||globalData.value.config.Client.CApi.WebPort,
password:step.value.form.client.password || globalData.value.config.Client.CApi.ApiPassword,
@@ -134,19 +128,6 @@ export default {
groupid: [{ required: true, message: "必填", trigger: "blur" }],
groupPassword: [{ required: true, message: "必填", trigger: "blur" }],
password: [{ required: true, message: "必填", trigger: "blur" }],
api: [
{ required: true, message: "必填", trigger: "blur" },
{
type: "number",
min: 0,
max: 65535,
message: "数字 0-65535",
trigger: "blur",
transform(value) {
return Number(value);
},
},
],
web: [
{ required: true, message: "必填", trigger: "blur" },
{
@@ -173,7 +154,6 @@ export default {
name: state.form.name,
groupid: state.form.groupid,
groupPassword: state.form.groupPassword,
api: +state.form.api,
web: +state.form.web,
password: state.form.password,

View File

@@ -15,21 +15,16 @@
<div class="card-header">
<div>
<el-row>
<el-col :span="24"><el-checkbox :disabled="onlyNode" v-model="state.single" :label="$t('status.exportSingle')" /></el-col>
</el-row>
</div>
<div>
<el-row>
<el-col :span="12"><el-checkbox :disabled="onlyNode" v-model="state.single" :label="$t('status.exportSingle')" /></el-col>
<el-col :span="12">
<div class="flex flex-nowrap">
<span style="width: 11rem;">{{$t('status.exportName')}} : </span><el-input :disabled="!state.single" v-model="state.name" maxlength="32" show-word-limit></el-input>
</div>
</el-col>
<el-col :span="12">
<div class="flex flex-nowrap">
<span style="width: 11rem;">{{$t('status.exportApiPassword')}} : </span><el-input type="password" show-password :disabled="onlyNode" v-model="state.apipassword" maxlength="36" show-word-limit></el-input>
</div>
</el-col>
</el-row>
</div>
<div>
<el-row>
<el-col :span="12">
<div class="flex flex-nowrap mgt-1">
<span style="width: 11rem;">{{$t('status.exportWebport')}} : </span><el-input :disabled="onlyNode" v-model="state.webport"></el-input>
@@ -37,7 +32,7 @@
</el-col>
<el-col :span="12">
<div class="flex flex-nowrap mgt-1">
<span style="width: 11rem;">{{$t('status.exportApiport')}} : </span><el-input :disabled="onlyNode" v-model="state.apiport"></el-input>
<span style="width: 11rem;">{{$t('status.exportApiPassword')}} : </span><el-input type="password" show-password :disabled="onlyNode" v-model="state.apipassword" maxlength="36" show-word-limit></el-input>
</div>
</el-col>
</el-row>
@@ -112,7 +107,6 @@ export default {
single:true,
name:'',
apipassword:onlyNode.value? globalData.value.config.Client.CApi.ApiPassword :'',
apiport: globalData.value.config.Client.CApi.ApiPort,
webport: globalData.value.config.Client.CApi.WebPort,
relay:true,
@@ -143,7 +137,6 @@ export default {
name:state.name,
apipassword:state.apipassword,
webport:+state.webport,
apiport:+state.apiport,
relay:state.relay,
sforward:state.sforward,
updater:state.updater,
@@ -168,10 +161,6 @@ export default {
ElMessage.error(t('status.exportWebportPlease'));
return;
}
if(!json.apiport || isNaN(json.apiport) || json.apiport<=0 || json.apiport>65535){
ElMessage.error(t('status.exportApiportPlease'));
return;
}
return json;
}
const download = ()=>{

View File

@@ -20,8 +20,6 @@ RUN apt update \
EXPOSE 1802/tcp
EXPOSE 1802/udp
EXPOSE 1803/tcp
EXPOSE 1803/udp
EXPOSE 1804/tcp
EXPOSE 1804/udp

View File

@@ -20,8 +20,6 @@ RUN apt update \
EXPOSE 1802/tcp
EXPOSE 1802/udp
EXPOSE 1803/tcp
EXPOSE 1803/udp
EXPOSE 1804/tcp
EXPOSE 1804/udp
EXPOSE 1806/tcp

View File

@@ -10,8 +10,6 @@ RUN echo "https://mirrors.ustc.edu.cn/alpine/latest-stable/main/" > /etc/apk/rep
EXPOSE 1802/tcp
EXPOSE 1802/udp
EXPOSE 1803/tcp
EXPOSE 1803/udp
EXPOSE 1804/tcp
EXPOSE 1804/udp

View File

@@ -21,7 +21,10 @@
<Authors>snltty</Authors>
<Company>snltty</Company>
<Description>1. 一些累计更新
2. 测试发布请使用1.8.2</Description>
2. 重建权限存储ulong改为BitArray同组所有客户端需保持版本一致
3. 增加唤醒功能支持WOLCOM继电器HID继电器
4. 基于One-KVM包装docker镜像集成linker
5. 管理端口改为1804一个端口托管Web+Websocket</Description>
<Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>

View File

@@ -1,4 +1,7 @@
v1.8.2
2025-06-05 10:48:49
2025-06-05 17:13:06
1. 一些累计更新
2. 测试发布请使用1.8.2
2. 重建权限存储ulong改为BitArray同组所有客户端需保持版本一致
3. 增加唤醒功能支持WOLCOM继电器HID继电器
4. 基于One-KVM包装docker镜像集成linker
5. 管理端口改为1804一个端口托管Web+Websocket