feat: add tray remote connection settings

This commit is contained in:
2026-09-20 22:40:42 +08:00
parent baf7a47418
commit 1d5c472e88
5 changed files with 165 additions and 17 deletions
+123 -14
View File
@@ -5,6 +5,7 @@ using System.Text.Json;
using System.Windows.Forms;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using WxAgent.Core;
using WxAgent.Host;
using WxAgent.Service;
@@ -279,7 +280,14 @@ internal sealed class TrayApplicationContext : ApplicationContext
try
{
EnsureConfiguration();
if (ServiceSettingsEditor.Show(options!) is not { } edited) return;
var settings = options!;
if (!string.IsNullOrWhiteSpace(settings.RemoteConfigurationFile))
{
var remotePath = ResolvePath(settings.RemoteConfigurationFile, Path.GetDirectoryName(configPath)!, "remote.json");
var remoteConfiguration = RemoteNodeConfigurationStore.LoadAsync(remotePath).GetAwaiter().GetResult();
settings = WithRemoteForSettings(settings, remoteConfiguration.Remote, remoteConfiguration.Reporting);
}
if (ServiceSettingsEditor.Show(settings) is not { } edited) return;
WriteCredentials(edited.CredentialFile, edited.AccessToken!, edited.EnableValidationOperations);
WriteJson(configPath, edited);
_ = ReloadAsync();
@@ -291,6 +299,23 @@ internal sealed class TrayApplicationContext : ApplicationContext
}
}
private static ServiceOptions WithRemoteForSettings(ServiceOptions source, RemoteAgentOptions? remote, ReportingConfig reporting) => new()
{
ListenUrl = source.ListenUrl,
AllowExternal = source.AllowExternal,
AllowedHosts = source.AllowedHosts ?? [],
AllowedOrigins = source.AllowedOrigins ?? [],
AccessToken = source.AccessToken,
CredentialFile = source.CredentialFile,
DataDirectory = source.DataDirectory,
Remote = remote,
Reporting = reporting,
RemoteConfigurationFile = null,
EnableValidationOperations = source.EnableValidationOperations,
EnableListenerEvents = source.EnableListenerEvents,
PreventAutoLock = source.PreventAutoLock
};
private void SetStatus(string message)
{
StatusItem.Text = message.Length > 60 ? message[..60] : message;
@@ -352,57 +377,124 @@ internal static class ServiceSettingsEditor
{
if (!Uri.TryCreate(current.ListenUrl, UriKind.Absolute, out var uri))
throw new InvalidDataException("ListenUrl is invalid.");
static string? Optional(TextBox box) => string.IsNullOrWhiteSpace(box.Text) ? null : box.Text.Trim();
var host = uri.Host.Trim('[', ']');
var token = current.AccessToken ?? ServiceOptions.GenerateToken();
var remote = current.Remote;
ServiceOptions? result = null;
using var form = new Form
{
Text = "WxAgent 服务设置",
Width = 620,
Height = 435,
Width = 720,
Height = 535,
StartPosition = FormStartPosition.CenterScreen,
MinimizeBox = false,
MaximizeBox = false,
FormBorderStyle = FormBorderStyle.FixedDialog
};
var tabs = new TabControl { Left = 12, Top = 12, Width = 680, Height = 450 };
var localTab = new TabPage("本地服务");
var remoteTab = new TabPage("远程连接");
tabs.TabPages.Add(localTab);
tabs.TabPages.Add(remoteTab);
var listenLabel = new Label { Left = 18, Top = 18, Width = 120, Text = "监听地址" };
var hostBox = new TextBox { Left = 145, Top = 14, Width = 245, Text = host };
var portBox = new NumericUpDown { Left = 400, Top = 14, Width = 90, Minimum = 1, Maximum = 65535, Value = uri.Port };
var external = new CheckBox { Left = 145, Top = 52, Width = 430, Text = "允许远程机器连接(仅在可信内网启用)", Checked = current.AllowExternal };
var external = new CheckBox { Left = 145, Top = 52, Width = 500, Text = "允许远程机器连接(仅在可信内网启用)", Checked = current.AllowExternal };
var tokenLabel = new Label { Left = 18, Top = 91, Width = 120, Text = "访问凭据" };
var tokenBox = new TextBox { Left = 145, Top = 87, Width = 345, Text = token, MaxLength = 0 };
var copy = new Button { Left = 500, Top = 85, Width = 88, Text = "复制" };
var regenerate = new Button { Left = 145, Top = 123, Width = 105, Text = "重新生成" };
var validation = new CheckBox
{
Left = 145, Top = 158, Width = 430,
Left = 145, Top = 158, Width = 500,
Text = "启用本机验证写操作(仅授权测试机)",
Checked = current.EnableValidationOperations
};
var preventAutoLock = new CheckBox
{
Left = 145, Top = 187, Width = 430,
Left = 145, Top = 187, Width = 500,
Text = "防止自动息屏、睡眠和锁屏",
Checked = current.PreventAutoLock
};
var listenerEvents = new CheckBox
{
Left = 145, Top = 216, Width = 430,
Left = 145, Top = 216, Width = 500,
Text = "启用后台消息监听(会操作微信界面,默认关闭)",
Checked = current.EnableListenerEvents
};
var note = new Label
{
Left = 18, Top = 250, Width = 570, Height = 48,
Left = 18, Top = 250, Width = 620, Height = 48,
Text = "仅保留一个访问凭据,可直接输入自定义内容(不能为空或包含空白字符)。\n验证写操作只应在明确授权的测试机启用;本机回环访问不需要凭据。"
};
var data = new Label
{
Left = 18, Top = 308, Width = 570, Height = 24,
Left = 18, Top = 308, Width = 620, Height = 24,
Text = $"数据目录:{current.DataDirectory}", AutoEllipsis = true
};
var save = new Button { Left = 370, Top = 360, Width = 105, Text = "保存" };
var cancel = new Button { Left = 485, Top = 360, Width = 105, Text = "取消" };
localTab.Controls.AddRange([listenLabel, hostBox, portBox, external, tokenLabel, tokenBox, copy, regenerate, validation, preventAutoLock, listenerEvents, note, data]);
var remoteEnabled = new CheckBox
{
Left = 18, Top = 16, Width = 620,
Text = "启用远程连接(连接控制面)",
Checked = remote is not null
};
var remoteAddressLabel = new Label { Left = 18, Top = 54, Width = 125, Text = "控制面地址" };
var remoteAddressBox = new TextBox { Left = 150, Top = 50, Width = 485, Text = remote?.AuthAddress ?? "" };
var remoteNodeLabel = new Label { Left = 18, Top = 90, Width = 125, Text = "节点 ID" };
var remoteNodeBox = new TextBox { Left = 150, Top = 86, Width = 485, Text = remote?.NodeId ?? "" };
var remoteAccountLabel = new Label { Left = 18, Top = 126, Width = 125, Text = "活动账号 ID" };
var remoteAccountBox = new TextBox { Left = 150, Top = 122, Width = 485, Text = remote?.ActiveAccountId ?? "" };
var remoteTokenLabel = new Label { Left = 18, Top = 162, Width = 125, Text = "控制面令牌" };
var remoteTokenBox = new TextBox
{
Left = 150, Top = 158, Width = 485,
Text = remote?.Token ?? "",
UseSystemPasswordChar = true
};
var remoteTokenFileLabel = new Label { Left = 18, Top = 198, Width = 125, Text = "令牌文件(可选)" };
var remoteTokenFileBox = new TextBox { Left = 150, Top = 194, Width = 485, Text = remote?.TokenFile ?? "" };
var allowInsecureHttp = new CheckBox
{
Left = 150, Top = 230, Width = 485,
Text = "允许私有网络 HTTP(仅 10/172.16-31/192.168 网段)",
Checked = remote?.AllowInsecureHttp == true
};
var remoteServerCaLabel = new Label { Left = 18, Top = 266, Width = 125, Text = "服务端 CA(可选)" };
var remoteServerCaBox = new TextBox { Left = 150, Top = 262, Width = 485, Text = remote?.ServerCaFile ?? "" };
var remoteClientCertLabel = new Label { Left = 18, Top = 302, Width = 125, Text = "客户端证书(可选)" };
var remoteClientCertBox = new TextBox { Left = 150, Top = 298, Width = 485, Text = remote?.ClientCertificateFile ?? "" };
var remoteClientKeyLabel = new Label { Left = 18, Top = 338, Width = 125, Text = "客户端密钥(可选)" };
var remoteClientKeyBox = new TextBox { Left = 150, Top = 334, Width = 485, Text = remote?.ClientCertificateKeyFile ?? "" };
var remoteNote = new Label
{
Left = 18, Top = 372, Width = 620, Height = 40,
Text = "控制面令牌与控制面 WXAGENT_NODE_TOKEN 一致;HTTP 仅允许私有 IP,公网或域名请使用 HTTPS。"
};
remoteTab.Controls.AddRange([
remoteEnabled, remoteAddressLabel, remoteAddressBox, remoteNodeLabel, remoteNodeBox,
remoteAccountLabel, remoteAccountBox, remoteTokenLabel, remoteTokenBox,
remoteTokenFileLabel, remoteTokenFileBox, allowInsecureHttp, remoteServerCaLabel,
remoteServerCaBox, remoteClientCertLabel, remoteClientCertBox, remoteClientKeyLabel,
remoteClientKeyBox, remoteNote
]);
var remoteInputs = new Control[]
{
remoteAddressBox, remoteNodeBox, remoteAccountBox, remoteTokenBox, remoteTokenFileBox,
allowInsecureHttp, remoteServerCaBox, remoteClientCertBox, remoteClientKeyBox
};
void SetRemoteEnabled() { foreach (var control in remoteInputs) control.Enabled = remoteEnabled.Checked; }
remoteEnabled.CheckedChanged += (_, _) => SetRemoteEnabled();
SetRemoteEnabled();
var save = new Button { Left = 470, Top = 470, Width = 105, Text = "保存" };
var cancel = new Button { Left = 585, Top = 470, Width = 105, Text = "取消" };
copy.Click += (_, _) => { Clipboard.SetText(tokenBox.Text); copy.Text = "已复制"; };
regenerate.Click += (_, _) =>
{
@@ -418,6 +510,21 @@ internal static class ServiceSettingsEditor
}
var formattedHost = address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6
? $"[{address}]" : address.ToString();
var remoteToken = Optional(remoteTokenBox);
var remoteOptions = remoteEnabled.Checked
? new RemoteAgentOptions
{
AuthAddress = Optional(remoteAddressBox),
Token = remoteToken,
TokenFile = remoteToken is null ? Optional(remoteTokenFileBox) : null,
ServerCaFile = Optional(remoteServerCaBox),
ClientCertificateFile = Optional(remoteClientCertBox),
ClientCertificateKeyFile = Optional(remoteClientKeyBox),
NodeId = Optional(remoteNodeBox),
ActiveAccountId = Optional(remoteAccountBox),
AllowInsecureHttp = allowInsecureHttp.Checked
}
: null;
var edited = new ServiceOptions
{
ListenUrl = $"http://{formattedHost}:{portBox.Value}",
@@ -427,9 +534,9 @@ internal static class ServiceSettingsEditor
AccessToken = tokenBox.Text,
CredentialFile = current.CredentialFile,
DataDirectory = current.DataDirectory,
Remote = current.Remote,
Remote = remoteOptions,
Reporting = current.Reporting,
RemoteConfigurationFile = current.RemoteConfigurationFile,
RemoteConfigurationFile = null,
EnableValidationOperations = validation.Checked,
EnableListenerEvents = listenerEvents.Checked,
PreventAutoLock = preventAutoLock.Checked
@@ -444,7 +551,9 @@ internal static class ServiceSettingsEditor
form.Close();
};
cancel.Click += (_, _) => form.Close();
form.Controls.AddRange([listenLabel, hostBox, portBox, external, tokenLabel, tokenBox, copy, regenerate, validation, preventAutoLock, listenerEvents, note, data, save, cancel]);
form.Controls.Add(tabs);
form.Controls.Add(save);
form.Controls.Add(cancel);
form.AcceptButton = save;
form.CancelButton = cancel;
form.ShowDialog();