feat: add tray remote connection settings
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user