├── .gitattributes
├── .gitignore
├── Directory.Build.props
├── Directory.Packages.props
├── FrpGUI.Avalonia.Browser
├── FrpGUI.Avalonia.Browser.csproj
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ └── launchSettings.json
├── runtimeconfig.template.json
├── uiconfig.json
├── web.config
└── wwwroot
│ ├── app.css
│ ├── favicon.ico
│ ├── index.html
│ ├── main.js
│ └── utils.js
├── FrpGUI.Avalonia.Desktop
├── FrpGUI.Avalonia.Desktop.csproj
├── Program.cs
└── app.manifest
├── FrpGUI.Avalonia
├── App.axaml
├── App.axaml.cs
├── Assets
│ ├── WRYH.ttf
│ ├── WRYH_Bold.ttf
│ └── icon.ico
├── Brushes.axaml
├── Converters
│ ├── NullableValueConverter.cs
│ ├── ProcessStatus2BrushConverter.cs
│ └── RuleParameterEnableConverter.cs
├── DataProviders
│ ├── HttpRequester.cs
│ ├── IDataProvider.cs
│ ├── LocalDataProvider.cs
│ └── WebDataProvider.cs
├── FrpGUI.Avalonia.csproj
├── JsInterop.cs
├── LocalAppLifetimeService.cs
├── LocalLogger.cs
├── Models
│ └── FrpAvaloniaSourceGenerationContext.cs
├── RunningMode.cs
├── UIConfig.cs
├── ViewModels
│ ├── FrpConfigViewModel.cs
│ ├── FrpStatusInfo.cs
│ ├── LogInfo.cs
│ ├── LogViewModel.cs
│ ├── MainViewModel.cs
│ ├── RuleViewModel.cs
│ ├── SettingViewModel.cs
│ └── ViewModelBase.cs
└── Views
│ ├── ClientPanel.axaml
│ ├── ClientPanel.axaml.cs
│ ├── ConfigPanelBase.cs
│ ├── ControlBar.axaml
│ ├── ControlBar.axaml.cs
│ ├── LogPanel.axaml
│ ├── LogPanel.axaml.cs
│ ├── MainView.axaml
│ ├── MainView.axaml.cs
│ ├── MainWindow.axaml
│ ├── MainWindow.axaml.cs
│ ├── ProgressRingOverlay.axaml
│ ├── ProgressRingOverlay.axaml.cs
│ ├── RuleDialog.axaml
│ ├── RuleDialog.axaml.cs
│ ├── ServerPanel.axaml
│ ├── ServerPanel.axaml.cs
│ ├── SettingsDialog.axaml
│ └── SettingsDialog.axaml.cs
├── FrpGUI.Core
├── Enums
│ ├── NetType.cs
│ ├── ProcessStatus.cs
│ └── TokenVerification.cs
├── FrpGUI.Core.csproj
├── Models
│ ├── ClientConfig.cs
│ ├── FrpConfigBase.cs
│ ├── FrpConfigJsonConverter.cs
│ ├── IFrpProcess.cs
│ ├── IToFrpConfig.cs
│ ├── LogEntity.cs
│ ├── ProcessInfo.cs
│ ├── Rule.cs
│ └── ServerConfig.cs
└── StatusBasedException.cs
├── FrpGUI.Service
├── Configs
│ ├── AppConfig.cs
│ ├── AppConfigBase.cs
│ └── AppConfigSourceGenerationContext.cs
├── FrpGUI.Service.csproj
├── Models
│ ├── FrpProcess.cs
│ └── FrpProcessCollection.cs
└── Services
│ ├── AppLifetimeService.cs
│ ├── Logger.cs
│ └── ProcessService.cs
├── FrpGUI.SimpleWeb
└── index.html
├── FrpGUI.Tests
├── FrpGUI.Tests.csproj
├── GlobalUsings.cs
└── UnitTest.cs
├── FrpGUI.WPF
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── FrpGUI.WPF.csproj
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Panel
│ ├── AddRulePanel.xaml
│ ├── AddRulePanel.xaml.cs
│ ├── ClientPanel.xaml
│ ├── ClientPanel.xaml.cs
│ ├── PanelBase.cs
│ ├── RuleParameterEnableConverter.cs
│ ├── ServerPanel.xaml
│ └── ServerPanel.xaml.cs
├── app.manifest
├── icon.ico
└── icon.svg
├── FrpGUI.WebAPI
├── Controllers
│ ├── ConfigController.cs
│ ├── FrpControllerBase.cs
│ ├── LogController.cs
│ ├── NeedTokenAttribute.cs
│ ├── ProcessController.cs
│ └── TokenController.cs
├── CreateWindowsService.bat
├── DeleteWindowsService.bat
├── FrpGUI.Service.http
├── FrpGUI.WebAPI.csproj
├── FrpGUIActionFilter.cs
├── Logger.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Services
│ └── WebConfigService.cs
├── WebAppLifetimeService.cs
├── appsettings.Development.json
└── appsettings.json
├── FrpGUI.sln
├── README.md
├── build.ps1
├── clean.ps1
├── config.json
└── img
├── browser.png
├── linux.png
├── structure.png
├── swagger.png
├── windows.png
└── 软件架构图.vsdx
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 3.0.0
5 |
6 |
7 | 11.2.6
8 |
9 | 2.3.0
10 |
11 |
12 | T
13 |
14 |
15 |
16 |
17 |
18 |
19 | CS0168,CS0169;MSB3539
20 |
21 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/FrpGUI.Avalonia.Browser.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-browser
4 | Exe
5 | disable
6 | latest
7 | ../Generation/bin
8 | false
9 | true
10 | false
11 | false
12 | true
13 | $(AppVersion)
14 | $(Temp)\$(SolutionName)\$(Configuration)\$(AssemblyName)
15 | $(Temp)\$(SolutionName)\obj\$(Configuration)\$(AssemblyName)
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Always
39 |
40 |
41 | Always
42 |
43 |
44 | Always
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Browser;
3 | using FrpGUI.Avalonia;
4 | using FrpGUI.Avalonia.Models;
5 | using System;
6 | using System.IO;
7 | using System.Net;
8 | using System.Net.Http;
9 | using System.Runtime.InteropServices.JavaScript;
10 | using System.Runtime.Versioning;
11 | using System.Text.Json;
12 | using System.Threading.Tasks;
13 |
14 | [assembly: SupportedOSPlatform("browser")]
15 |
16 | internal sealed partial class Program
17 | {
18 | public static AppBuilder BuildAvaloniaApp()
19 | {
20 | return AppBuilder.Configure();
21 | }
22 |
23 | private static async Task Main(string[] args)
24 | {
25 | await JSHost.ImportAsync("utils.js", "../utils.js");
26 | try
27 | {
28 | await ProcessDefaultUIConfigAsync();
29 | }
30 | catch (Exception ex)
31 | {
32 | JsInterop.Alert("获取默认UI配置失败:" + ex.Message);
33 | }
34 | await BuildAvaloniaApp().StartBrowserAppAsync("out");
35 | }
36 |
37 | private static async Task ProcessDefaultUIConfigAsync()
38 | {
39 | UIConfig config = new UIConfig();
40 | string url = $"{JsInterop.GetCurrentUrl().TrimEnd('/')}/{Path.GetFileName(config.ConfigPath)}";
41 |
42 | HttpClient httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) };
43 | var response = await httpClient.GetAsync(url);
44 | if (response.StatusCode == HttpStatusCode.NotFound)
45 | {
46 | return;
47 | }
48 | var s = await response.Content.ReadAsStreamAsync();
49 | UIConfig.DefaultConfig = JsonSerializer.Deserialize(s, FrpAvaloniaSourceGenerationContext.Default.UIConfig);
50 | }
51 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: System.Runtime.Versioning.SupportedOSPlatform("browser")]
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "FrpGUI.Browser": {
4 | "commandName": "Project",
5 | "launchBrowser": false,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "applicationUrl": "http://localhost:7169",
10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "wasmHostProperties": {
3 | "perHostConfig": [
4 | {
5 | "name": "browser",
6 | "host": "browser"
7 | }
8 | ]
9 | }
10 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/uiconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "RunningMode": 1,
3 | "ServerAddress": "http://localhost:5113",
4 | "ServerToken": ""
5 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/wwwroot/app.css:
--------------------------------------------------------------------------------
1 | /* HTML styles for the splash screen */
2 | .avalonia-splash {
3 | position: absolute;
4 | height: 100%;
5 | width: 100%;
6 | background: white;
7 | font-family: 'Outfit', sans-serif;
8 | justify-content: center;
9 | align-items: center;
10 | display: flex;
11 | pointer-events: none;
12 | }
13 |
14 | /* Light theme styles */
15 | @media (prefers-color-scheme: light) {
16 | .avalonia-splash {
17 | background: white;
18 | }
19 |
20 | .avalonia-splash h2 {
21 | color: #1b2a4e;
22 | }
23 |
24 | .avalonia-splash a {
25 | color: #0D6EFD;
26 | }
27 | }
28 |
29 | @media (prefers-color-scheme: dark) {
30 | .avalonia-splash {
31 | background: #1b2a4e;
32 | }
33 |
34 | .avalonia-splash h2 {
35 | color: white;
36 | }
37 |
38 | .avalonia-splash a {
39 | color: white;
40 | }
41 | }
42 |
43 | .avalonia-splash h2 {
44 | font-weight: 400;
45 | font-size: 1.5rem;
46 | }
47 |
48 | .avalonia-splash a {
49 | text-decoration: none;
50 | font-size: 2.5rem;
51 | display: block;
52 | }
53 |
54 | .avalonia-splash.splash-close {
55 | transition: opacity 200ms, display 200ms;
56 | display: none;
57 | opacity: 0;
58 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/f-shake/FrpGUI/17ad5ab9a92941bde1625b861b75f16ce6c858c2/FrpGUI.Avalonia.Browser/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FrpGUI
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |

17 |
18 | FrpGUI
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/wwwroot/main.js:
--------------------------------------------------------------------------------
1 | import { dotnet } from './_framework/dotnet.js'
2 |
3 | const is_browser = typeof window != "undefined";
4 | if (!is_browser) throw new Error(`Expected to be running in a browser`);
5 |
6 | const dotnetRuntime = await dotnet
7 | .withDiagnosticTracing(false)
8 | .withApplicationArgumentsFromQuery()
9 | .create();
10 |
11 | const config = dotnetRuntime.getConfig();
12 |
13 | await dotnetRuntime.runMain(config.mainAssemblyName, [globalThis.location.href]);
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Browser/wwwroot/utils.js:
--------------------------------------------------------------------------------
1 | export function setLocalStorage(key, value) {
2 | localStorage.setItem(key, value);
3 | }
4 |
5 | export function getLocalStorage(key) {
6 | return localStorage.getItem(key);
7 | }
8 |
9 | export function showAlert(message) {
10 | alert(message);
11 | }
12 |
13 | export function reload() {
14 | location.reload(true)
15 | }
16 |
17 | export function getCurrentUrl() {
18 | return window.location.href
19 | }
20 | export function openUrl() {
21 | return window.open("url", "_blank")
22 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Desktop/FrpGUI.Avalonia.Desktop.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 |
6 | net8.0
7 | disable
8 | true
9 | app.manifest
10 | ../Generation/bin
11 | zh-CN
12 | false
13 | $(Temp)\$(SolutionName)\$(Configuration)\$(AssemblyName)
14 | $(Temp)\$(SolutionName)\obj\$(Configuration)\$(AssemblyName)
15 | ../FrpGUI.Avalonia/Assets/icon.ico
16 | $(AppVersion)
17 | true
18 | partial
19 |
20 | true
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Desktop/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Serilog;
3 | using System;
4 | using System.IO;
5 | using System.Threading.Tasks;
6 |
7 | namespace FrpGUI.Avalonia.Desktop;
8 |
9 |
10 | class Program
11 | {
12 | public static AppBuilder BuildAvaloniaApp()
13 | => AppBuilder.Configure()
14 | .LogToTrace()
15 | .UsePlatformDetect();
16 | //.UseDesktopWebView();
17 |
18 | [STAThread]
19 | public static void Main(string[] args)
20 | {
21 | Log.Logger = new LoggerConfiguration()
22 | .MinimumLevel.Debug()
23 | .WriteTo.File("logs/logs.txt", rollingInterval: RollingInterval.Day)
24 | .CreateLogger();
25 | Log.Information("程序启动");
26 | #if !DEBUG
27 | TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
28 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
29 |
30 | try
31 | {
32 | #endif
33 | BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
34 | #if !DEBUG
35 | }
36 | catch (Exception ex)
37 | {
38 | Log.Fatal(ex, "未捕获的主线程错误");
39 | }
40 | finally
41 | {
42 | Log.CloseAndFlush();
43 | }
44 | #endif
45 | }
46 |
47 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
48 | {
49 | Log.Fatal(e.ExceptionObject as Exception, "未捕获的AppDomain异常");
50 | }
51 |
52 | private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
53 | {
54 | Log.Fatal(e.Exception, "未捕获的TaskScheduler异常");
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia.Desktop/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/App.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
63 |
64 |
65 |
66 |
67 |
71 | -->
72 |
73 |
74 |
75 |
76 |
77 |
78 |
83 |
84 |
85 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Assets/WRYH.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/f-shake/FrpGUI/17ad5ab9a92941bde1625b861b75f16ce6c858c2/FrpGUI.Avalonia/Assets/WRYH.ttf
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Assets/WRYH_Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/f-shake/FrpGUI/17ad5ab9a92941bde1625b861b75f16ce6c858c2/FrpGUI.Avalonia/Assets/WRYH_Bold.ttf
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Assets/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/f-shake/FrpGUI/17ad5ab9a92941bde1625b861b75f16ce6c858c2/FrpGUI.Avalonia/Assets/icon.ico
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Brushes.axaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
11 |
12 |
16 |
17 |
21 |
25 |
29 |
33 |
37 |
41 |
42 |
46 |
50 |
54 |
58 |
59 |
60 |
61 |
64 |
67 |
70 |
73 |
74 |
78 |
82 |
86 |
90 |
91 |
92 |
93 |
96 |
99 |
102 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Converters/NullableValueConverter.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Data;
3 | using Avalonia.Data.Converters;
4 | using System;
5 | using System.Diagnostics;
6 | using System.Globalization;
7 |
8 | namespace FrpGUI.Avalonia.Converters
9 | {
10 | public class NullableValueConverter : IValueConverter
11 | {
12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
13 | {
14 | return value?.ToString();
15 | }
16 |
17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
18 | {
19 | if (Nullable.GetUnderlyingType(targetType) == null)
20 | {
21 | throw new ArgumentException("目标类型不可为空", nameof(targetType));
22 | }
23 | if (value == null)
24 | {
25 | return null;
26 | }
27 | if (value is not string str)
28 | {
29 | throw new ArgumentException("值不是字符串", nameof(value));
30 | }
31 | if (string.IsNullOrWhiteSpace(str))
32 | {
33 | return null;
34 | }
35 | Type underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
36 | try
37 | {
38 | return underlyingType switch
39 | {
40 | // 基本数值类型
41 | Type t when t == typeof(int) => int.Parse(str, culture),
42 | Type t when t == typeof(double) => double.Parse(str, culture),
43 | Type t when t == typeof(float) => float.Parse(str, culture),
44 | Type t when t == typeof(decimal) => decimal.Parse(str, culture),
45 | Type t when t == typeof(long) => long.Parse(str, culture),
46 | Type t when t == typeof(short) => short.Parse(str, culture),
47 | Type t when t == typeof(byte) => byte.Parse(str, culture),
48 | Type t when t == typeof(sbyte) => sbyte.Parse(str, culture),
49 | Type t when t == typeof(uint) => uint.Parse(str, culture),
50 | Type t when t == typeof(ulong) => ulong.Parse(str, culture),
51 | Type t when t == typeof(ushort) => ushort.Parse(str, culture),
52 |
53 | // 布尔类型
54 | Type t when t == typeof(bool) => bool.Parse(str),
55 |
56 | // 日期时间
57 | Type t when t == typeof(DateTime) => DateTime.Parse(str, culture),
58 | Type t when t == typeof(DateTimeOffset) => DateTimeOffset.Parse(str, culture),
59 | Type t when t == typeof(TimeSpan) => TimeSpan.Parse(str, culture),
60 |
61 | // 其他类型(如 Guid)
62 | Type t when t == typeof(Guid) => Guid.Parse(str),
63 |
64 | // 枚举类型(AOT 需要确保枚举被编译)
65 | Type t when t.IsEnum => Enum.Parse(t, str, ignoreCase: true),
66 |
67 | // 默认情况(可能不支持)
68 | _ => throw new NotSupportedException($"不支持转换到类型 {underlyingType.Name}")
69 | };
70 | }
71 | catch (FormatException ex)
72 | {
73 | //return new BindingNotification(new ArgumentException($"无法将值 '{str}' 转换为类型 {underlyingType.Name}", nameof(targetType)),BindingErrorType.DataValidationError)
74 | throw new ArgumentException($"无法将值 '{str}' 转换为类型 {underlyingType.Name}", nameof(targetType));
75 | }
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Converters/ProcessStatus2BrushConverter.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Data.Converters;
2 | using Avalonia.Media;
3 | using FrpGUI.Enums;
4 | using System;
5 | using System.Globalization;
6 |
7 | namespace FrpGUI.Avalonia.Converters
8 | {
9 | public class ProcessStatus2BrushConverter : IValueConverter
10 | {
11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
12 | {
13 | if (value == null)
14 | {
15 | return null;
16 | }
17 | ProcessStatus status = (ProcessStatus)value;
18 | if (status == ProcessStatus.Stopped)
19 | {
20 | return Brushes.Red;
21 | }
22 | return Brushes.Green;
23 | }
24 |
25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
26 | {
27 | throw new NotImplementedException();
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Converters/RuleParameterEnableConverter.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Data.Converters;
2 |
3 | using FrpGUI.Enums;
4 | using FrpGUI.Models;
5 | using System;
6 | using System.Globalization;
7 |
8 | namespace FrpGUI.Avalonia.Converters
9 | {
10 | public class RuleParameterEnableConverter : IValueConverter
11 | {
12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
13 | {
14 | NetType type = (NetType)value;
15 | return (parameter as string) switch
16 | {
17 | nameof(Rule.Domains) => type is NetType.HTTP or NetType.HTTPS,
18 | nameof(Rule.StcpKey) => type is NetType.STCP or NetType.STCP_Visitor,
19 | nameof(Rule.StcpServerName) => type is NetType.STCP_Visitor,
20 | nameof(Rule.RemotePort) => type is NetType.TCP or NetType.UDP,
21 | _ => throw new ArgumentException(),
22 | };
23 | }
24 |
25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
26 | {
27 | throw new NotImplementedException();
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/DataProviders/HttpRequester.cs:
--------------------------------------------------------------------------------
1 | using FrpGUI.Avalonia.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization.Metadata;
9 | using System.Threading.Tasks;
10 |
11 | namespace FrpGUI.Avalonia.DataProviders
12 | {
13 | public class HttpRequester(UIConfig config)
14 | {
15 | private const string AuthorizationKey = "Authorization";
16 | private readonly HttpClient httpClient = new HttpClient();
17 | public string Token { get; private set; }
18 | protected string BaseApiUrl => config.ServerAddress;
19 |
20 | public void Dispose()
21 | {
22 | httpClient.Dispose();
23 | }
24 |
25 | public async Task GetAsync(string endpoint)
26 | {
27 | WriteAuthorizationHeader();
28 | var response = await httpClient.GetAsync($"{BaseApiUrl}/{endpoint}");
29 |
30 | if (response.IsSuccessStatusCode)
31 | {
32 | return response.Content;
33 | }
34 | if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
35 | {
36 | return null;
37 | }
38 | await ProcessError(response);
39 | throw new Exception();
40 | }
41 |
42 | public Task GetObjectAsync(string endpoint, JsonTypeInfo jsonTypeInfo, params (string Key, string Value)[] query) where T : class
43 | {
44 | var querys = query.Select(p => $"{p.Key}={p.Value}");
45 | return GetObjectAsync(endpoint + "?" + string.Join('&', querys), jsonTypeInfo);
46 | }
47 |
48 | public async Task GetObjectAsync(string endpoint, JsonTypeInfo jsonTypeInfo)
49 | {
50 | using var responseStream = await (await GetAsync(endpoint)).ReadAsStreamAsync();
51 | return await JsonSerializer.DeserializeAsync(responseStream, jsonTypeInfo);
52 | }
53 |
54 | public async Task PostAsync(string endpoint, object data, JsonTypeInfo jsonTypeInfo)
55 | {
56 | WriteAuthorizationHeader();
57 | ArgumentNullException.ThrowIfNullOrWhiteSpace(endpoint);
58 | ArgumentNullException.ThrowIfNull(data);
59 | ArgumentNullException.ThrowIfNull(jsonTypeInfo);
60 | var jsonContent = data == null ? null : new StringContent(JsonSerializer.Serialize(data, jsonTypeInfo), Encoding.UTF8, "application/json");
61 | var response = await httpClient.PostAsync($"{BaseApiUrl}/{endpoint}", jsonContent);
62 | await ProcessError(response);
63 | }
64 |
65 | public async Task PostAsync(string endpoint)
66 | {
67 | WriteAuthorizationHeader();
68 | var response = await httpClient.PostAsync($"{BaseApiUrl}/{endpoint}", null);
69 | await ProcessError(response);
70 | }
71 |
72 | public async Task PostAsync(string endpoint, JsonTypeInfo jsonResultTypeInfo, object data, JsonTypeInfo jsonDataTypeInfo)
73 | {
74 | WriteAuthorizationHeader();
75 | ArgumentNullException.ThrowIfNullOrWhiteSpace(endpoint);
76 | ArgumentNullException.ThrowIfNull(data);
77 | ArgumentNullException.ThrowIfNull(jsonResultTypeInfo);
78 | ArgumentNullException.ThrowIfNull(jsonDataTypeInfo);
79 | var jsonContent = data == null ? null : new StringContent(JsonSerializer.Serialize(data, jsonResultTypeInfo), Encoding.UTF8, "application/json");
80 | var response = await httpClient.PostAsync($"{BaseApiUrl}/{endpoint}", jsonContent);
81 |
82 | await ProcessError(response);
83 | if (response.Content.Headers.ContentLength == 0)
84 | {
85 | return default;
86 | }
87 | return await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), jsonResultTypeInfo);
88 | }
89 |
90 | public async Task PostAsync(string endpoint, JsonTypeInfo jsonResultTypeInfo)
91 | {
92 | WriteAuthorizationHeader();
93 | ArgumentNullException.ThrowIfNullOrWhiteSpace(endpoint);
94 | ArgumentNullException.ThrowIfNull(jsonResultTypeInfo);
95 | var response = await httpClient.PostAsync($"{BaseApiUrl}/{endpoint}", null);
96 |
97 | await ProcessError(response);
98 | if (response.Content.Headers.ContentLength == 0)
99 | {
100 | return default;
101 | }
102 | return await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), jsonResultTypeInfo);
103 | }
104 |
105 | public void WriteAuthorizationHeader()
106 | {
107 | if (string.IsNullOrWhiteSpace(config.ServerToken))
108 | {
109 | return;
110 | }
111 | if (httpClient.DefaultRequestHeaders.TryGetValues(AuthorizationKey, out IEnumerable values))
112 | {
113 | var count = values.Count();
114 | if (count >= 1)
115 | {
116 | if (values.First() == config.ServerToken)
117 | {
118 | return;
119 | }
120 | httpClient.DefaultRequestHeaders.Remove(AuthorizationKey);
121 | httpClient.DefaultRequestHeaders.Add(AuthorizationKey, config.ServerToken);
122 | }
123 | else
124 | {
125 | throw new Exception();
126 | }
127 | }
128 | else
129 | {
130 | httpClient.DefaultRequestHeaders.Add(AuthorizationKey, config.ServerToken);
131 | }
132 | }
133 | protected static async Task ProcessError(HttpResponseMessage response)
134 | {
135 | if (!response.IsSuccessStatusCode)
136 | {
137 | if (response == null)
138 | {
139 | throw new Exception($"API请求失败({(int)response.StatusCode}{response.StatusCode})");
140 | }
141 | var message = await response.Content.ReadAsStringAsync();
142 | message = message.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)[0];
143 | if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
144 | {
145 | throw new Exception($"服务器处理错误(500):{Environment.NewLine}{message}");
146 | }
147 | throw new Exception($"API请求失败({(int)response.StatusCode}{response.StatusCode}):{Environment.NewLine}{message}");
148 | }
149 | }
150 | }
151 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/DataProviders/IDataProvider.cs:
--------------------------------------------------------------------------------
1 | using FrpGUI.Avalonia.ViewModels;
2 | using FrpGUI.Enums;
3 | using FrpGUI.Models;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Threading.Tasks;
7 |
8 | namespace FrpGUI.Avalonia.DataProviders
9 | {
10 | public interface IDataProvider
11 | {
12 | Task AddClientAsync();
13 |
14 | Task AddServerAsync();
15 |
16 | Task DeleteFrpConfigAsync(string id);
17 | Task> GetConfigsAsync();
18 |
19 | Task GetFrpStatusAsync(string id);
20 |
21 | Task> GetFrpStatusesAsync();
22 |
23 | Task> GetLogsAsync(DateTime timeAfter);
24 |
25 | Task> GetSystemProcesses();
26 |
27 | Task KillProcess(int id);
28 |
29 | Task ModifyConfigAsync(FrpConfigBase config);
30 |
31 | Task RestartFrpAsync(string id);
32 |
33 | Task SetTokenAsync(string oldToken, string newToken);
34 |
35 | Task StartFrpAsync(string id);
36 |
37 | Task StopFrpAsync(string id);
38 |
39 | Task VerifyTokenAsync();
40 | }
41 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/DataProviders/LocalDataProvider.cs:
--------------------------------------------------------------------------------
1 | using FrpGUI.Avalonia.ViewModels;
2 | using FrpGUI.Configs;
3 | using FrpGUI.Enums;
4 | using FrpGUI.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 |
10 | namespace FrpGUI.Avalonia.DataProviders
11 | {
12 | public class LocalDataProvider : IDataProvider
13 | {
14 | private readonly AppConfig configs;
15 | private readonly LocalLogger logger;
16 | private readonly FrpProcessCollection processes;
17 |
18 | public LocalDataProvider(AppConfig configs, FrpProcessCollection processes)
19 | {
20 | this.configs = configs;
21 | this.processes = processes;
22 | }
23 |
24 | public Task AddClientAsync()
25 | {
26 | ClientConfig client = new ClientConfig();
27 | configs.FrpConfigs.Add(client);
28 | configs.Save();
29 | return Task.FromResult(client);
30 | }
31 |
32 | public Task AddServerAsync()
33 | {
34 | ServerConfig server = new ServerConfig();
35 | configs.FrpConfigs.Add(server);
36 | configs.Save();
37 |
38 | return Task.FromResult(server);
39 | }
40 |
41 | public Task DeleteFrpConfigAsync(string id)
42 | {
43 | return processes.RemoveFrpAsync(id);
44 | }
45 |
46 | public Task> GetConfigsAsync()
47 | {
48 | return Task.FromResult(configs.FrpConfigs);
49 | }
50 |
51 | public Task GetFrpStatusAsync(string id)
52 | {
53 | return Task.FromResult(new FrpStatusInfo(processes.GetOrCreateProcess(id)));
54 | }
55 |
56 | public Task> GetFrpStatusesAsync()
57 | {
58 | return Task.FromResult(processes.GetAll().Select(p => new FrpStatusInfo(p)).ToList());
59 | }
60 |
61 | public Task> GetLogsAsync(DateTime timeAfter)
62 | {
63 | throw new NotSupportedException();
64 | }
65 |
66 | public Task> GetSystemProcesses()
67 | {
68 | return Task.FromResult(ProcessInfo.GetFrpProcesses());
69 | }
70 |
71 | public Task KillProcess(int id)
72 | {
73 | ProcessInfo.KillProcess(id);
74 | return Task.CompletedTask;
75 | }
76 |
77 | public Task ModifyConfigAsync(FrpConfigBase config)
78 | {
79 | var p = processes.GetOrCreateProcess(config.ID);
80 | if (p.Config.GetType() != config.GetType())
81 | {
82 | throw new ArgumentException("提供的配置与已有配置类型不同");
83 | }
84 | config.Adapt(p.Config);
85 | configs.Save();
86 | return Task.CompletedTask;
87 | }
88 |
89 | public Task RestartFrpAsync(string id)
90 | {
91 | configs.Save();
92 | return processes.GetOrCreateProcess(id).RestartAsync();
93 | }
94 |
95 | public Task SetTokenAsync(string oldToken, string newToken)
96 | {
97 | throw new NotImplementedException();
98 | }
99 |
100 | public Task StartFrpAsync(string id)
101 | {
102 | configs.Save();
103 | return processes.GetOrCreateProcess(id).StartAsync();
104 | }
105 |
106 | public Task StopFrpAsync(string id)
107 | {
108 | return processes.GetOrCreateProcess(id).StopAsync();
109 | }
110 |
111 | public Task VerifyTokenAsync()
112 | {
113 | throw new NotSupportedException();
114 | }
115 | }
116 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/DataProviders/WebDataProvider.cs:
--------------------------------------------------------------------------------
1 | using FrpGUI.Avalonia.Models;
2 | using FrpGUI.Avalonia.ViewModels;
3 | using FrpGUI.Enums;
4 | using FrpGUI.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Net;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace FrpGUI.Avalonia.DataProviders
13 | {
14 | public class WebDataProvider : HttpRequester, IDataProvider
15 | {
16 | private const string AddClientEndpoint = "Config/FrpConfigs/Add/Client";
17 | private const string AddServerEndpoint = "Config/FrpConfigs/Add/Server";
18 | private const string DeleteFrpConfigsEndpoint = "Config/FrpConfigs/Delete";
19 | private const string ConfigsEndpoint = "Config/Configs";
20 | private const string FrpStatusEndpoint = "Process/Status";
21 | private const string KillProcessEndpoint = "Process/Kill";
22 | private const string LogsEndpoint = "Log/List";
23 | private const string ModifyConfigEndpoint = "Config/FrpConfigs/Modify";
24 | private const string RestartFrpEndpoint = "Process/Restart";
25 | private const string StartFrpEndpoint = "Process/Start";
26 | private const string StopFrpEndpoint = "Process/Stop";
27 | private const string SystemProcessesEndpoint = "Process/All";
28 | private const string TokenEndpoint = "Token";
29 | private readonly UIConfig config;
30 | private readonly LocalLogger logger;
31 | private PeriodicTimer timer;
32 |
33 | private List<(string Name, Func task)> timerTasks = new List<(string Name, Func task)>();
34 |
35 | public WebDataProvider(UIConfig config, LocalLogger logger) : base(config)
36 | {
37 | this.config = config;
38 | this.logger = logger;
39 | StartTimer();
40 | }
41 |
42 | public Task AddClientAsync()
43 | {
44 | return PostAsync(AddClientEndpoint, JContext.ClientConfig);
45 | }
46 |
47 | public Task AddServerAsync()
48 | {
49 | return PostAsync(AddServerEndpoint, JContext.ServerConfig);
50 | }
51 |
52 | public void AddTimerTask(string name, Func task)
53 | {
54 | ArgumentException.ThrowIfNullOrEmpty(name, nameof(name));
55 | ArgumentNullException.ThrowIfNull(task, nameof(task));
56 | timerTasks.Add((name, task));
57 | }
58 |
59 | public Task DeleteFrpConfigAsync(string id)
60 | {
61 | return PostAsync($"{DeleteFrpConfigsEndpoint}/{id}");
62 | }
63 |
64 | public Task> GetConfigsAsync()
65 | {
66 | return GetObjectAsync(ConfigsEndpoint, JContext.ListFrpConfigBase);
67 | }
68 |
69 | public Task GetFrpStatusAsync(string id)
70 | {
71 | return PostAsync($"{FrpStatusEndpoint}/{id}", JContext.FrpStatusInfo);
72 | }
73 |
74 | public async Task> GetFrpStatusesAsync()
75 | {
76 | var result = await GetObjectAsync(FrpStatusEndpoint, JContext.ListFrpStatusInfo);
77 | return result;//.Select(p => new FrpStatusInfo(p)).ToList();
78 | }
79 |
80 | private FrpAvaloniaSourceGenerationContext JContext => FrpAvaloniaSourceGenerationContext.Get();
81 |
82 | public Task> GetLogsAsync(DateTime timeAfter)
83 | {
84 | return GetObjectAsync(LogsEndpoint, JContext.ListLogEntity, ("timeAfter", timeAfter.ToString("yyyy-MM-ddTHH:mm:ss.fffffff")));
85 | }
86 |
87 | public Task> GetSystemProcesses()
88 | {
89 | return GetObjectAsync(SystemProcessesEndpoint, JContext.ListProcessInfo);
90 | }
91 |
92 | public Task KillProcess(int id)
93 | {
94 | return PostAsync($"{KillProcessEndpoint}/{id}");
95 | }
96 |
97 | public Task ModifyConfigAsync(FrpConfigBase config)
98 | {
99 | switch (config)
100 | {
101 | case ClientConfig c:
102 | return PostAsync(ModifyConfigEndpoint, config, JContext.ClientConfig);
103 | case ServerConfig s:
104 | return PostAsync(ModifyConfigEndpoint, config, JContext.ServerConfig);
105 | default:
106 | throw new ArgumentOutOfRangeException();
107 | }
108 | }
109 |
110 | public Task RestartFrpAsync(string id)
111 | {
112 | return PostAsync($"{RestartFrpEndpoint}/{id}");
113 | }
114 |
115 | public Task SetTokenAsync(string oldToken, string newToken)
116 | {
117 | return PostAsync($"{TokenEndpoint}?oldToken={WebUtility.UrlEncode(oldToken ?? "")}&newToken={WebUtility.UrlEncode(newToken)}", JContext.TokenVerification);
118 | }
119 |
120 | public Task StartFrpAsync(string id)
121 | {
122 | return PostAsync($"{StartFrpEndpoint}/{id}");
123 | }
124 |
125 | public Task StopFrpAsync(string id)
126 | {
127 | return PostAsync($"{StopFrpEndpoint}/{id}");
128 | }
129 |
130 | public Task VerifyTokenAsync()
131 | {
132 | return GetObjectAsync(TokenEndpoint, JContext.TokenVerification);
133 | }
134 |
135 | private async void StartTimer()
136 | {
137 | var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));
138 | while (await timer.WaitForNextTickAsync())
139 | {
140 | foreach (var (name, task) in timerTasks.ToList())
141 | {
142 | try
143 | {
144 | await task.Invoke();
145 | }
146 | catch (Exception ex)
147 | {
148 | logger.Error($"执行定时任务“{name}”失败", null, ex);
149 | }
150 | }
151 | }
152 | }
153 | }
154 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/FrpGUI.Avalonia.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | disable
5 | latest
6 | ../Generation/bin
7 | false
8 | $(Temp)\$(SolutionName)\$(Configuration)\$(AssemblyName)
9 | $(Temp)\$(SolutionName)\obj\$(Configuration)\$(AssemblyName)
10 | true
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | ..\..\FzLib\Publish\Release\net8.0\FzLib.dll
44 |
45 |
46 | ..\..\FzLib\Publish\Release\net8.0\FzLib.Avalonia.dll
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | Designer
65 |
66 |
67 |
68 |
69 |
70 | LogPanel.axaml
71 |
72 |
73 | ControlBar.axaml
74 |
75 |
76 | ProgressRingOverlay.axaml
77 |
78 |
79 | SettingsDialog.axaml
80 |
81 |
82 | Code
83 | RuleDialog.axaml
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/JsInterop.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices.JavaScript;
2 | using System.Runtime.Versioning;
3 |
4 | namespace FrpGUI.Avalonia;
5 |
6 | [SupportedOSPlatform("browser")]
7 | public partial class JsInterop
8 | {
9 | [JSImport("showAlert", "utils.js")]
10 | public static partial string Alert(string message);
11 |
12 | [JSImport("getCurrentUrl", "utils.js")]
13 | public static partial string GetCurrentUrl();
14 |
15 | [JSImport("getLocalStorage", "utils.js")]
16 | public static partial string GetLocalStorage(string key);
17 |
18 | [JSImport("openUrl", "utils.js")]
19 | public static partial void OpenUrl(string url);
20 |
21 | [JSImport("reload", "utils.js")]
22 | public static partial void Reload();
23 |
24 | [JSImport("setLocalStorage", "utils.js")]
25 | public static partial void SetLocalStorage(string key, string value);
26 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/LocalAppLifetimeService.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using FrpGUI.Configs;
4 | using FrpGUI.Models;
5 | using FrpGUI.Services;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace FrpGUI.Avalonia;
10 |
11 | public partial class App
12 | {
13 | public class LocalAppLifetimeService(AppConfig config, UIConfig uiconfig, LoggerBase logger, FrpProcessCollection processes)
14 | : AppLifetimeService(config, logger, processes)
15 | {
16 | public override Task StopAsync(CancellationToken cancellationToken)
17 | {
18 | uiconfig.Save();
19 | return base.StopAsync(cancellationToken);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/LocalLogger.cs:
--------------------------------------------------------------------------------
1 | using FrpGUI.Models;
2 | using FrpGUI.Services;
3 | using System;
4 | using System.Collections.Concurrent;
5 | using System.Collections.Generic;
6 |
7 | namespace FrpGUI.Avalonia
8 | {
9 | public class LocalLogger : LoggerBase
10 | {
11 | public ConcurrentBag savedLogs = new ConcurrentBag();
12 |
13 | public event EventHandler NewLog;
14 |
15 | public bool SaveLogs { get; set; } = true;
16 |
17 | public LogEntity[] GetSavedLogs() => [.. savedLogs];
18 |
19 | protected override void AddLog(LogEntity logEntity)
20 | {
21 | NewLog?.Invoke(this, new NewLogEventArgs(logEntity));
22 | if (SaveLogs)
23 | {
24 | savedLogs.Add(logEntity);
25 | }
26 | }
27 |
28 | public class NewLogEventArgs : EventArgs
29 | {
30 | public NewLogEventArgs(LogEntity log)
31 | {
32 | ArgumentNullException.ThrowIfNull(log);
33 | Log = log;
34 | }
35 |
36 | public LogEntity Log { get; }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Models/FrpAvaloniaSourceGenerationContext.cs:
--------------------------------------------------------------------------------
1 | using FrpGUI.Avalonia.ViewModels;
2 | using FrpGUI.Configs;
3 | using FrpGUI.Enums;
4 | using FrpGUI.Models;
5 | using System.Collections.Generic;
6 | using System.Text.Encodings.Web;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using System.Text.Unicode;
10 |
11 | namespace FrpGUI.Avalonia.Models;
12 |
13 | [JsonSourceGenerationOptions(WriteIndented = true, PropertyNameCaseInsensitive = true)]
14 | [JsonSerializable(typeof(FrpStatusInfo))]
15 | [JsonSerializable(typeof(FrpProcess))]
16 | [JsonSerializable(typeof(UIConfig))]
17 | [JsonSerializable(typeof(LogEntity))]
18 | [JsonSerializable(typeof(TokenVerification))]
19 | [JsonSerializable(typeof(List))]
20 | [JsonSerializable(typeof(List))]
21 | [JsonSerializable(typeof(List))]
22 | [JsonSerializable(typeof(List))]
23 | [JsonSerializable(typeof(List))]
24 | [JsonSerializable(typeof(List))]
25 | [JsonSerializable(typeof(List))]
26 | public partial class FrpAvaloniaSourceGenerationContext : JsonSerializerContext
27 | {
28 | public static FrpAvaloniaSourceGenerationContext Get()
29 | {
30 | return new FrpAvaloniaSourceGenerationContext(new JsonSerializerOptions()
31 | {
32 | WriteIndented = true,
33 | PropertyNameCaseInsensitive = true,
34 | Converters = { new FrpConfigJsonConverter() }
35 | });
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/RunningMode.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace FrpGUI.Avalonia;
4 |
5 | public enum RunningMode
6 | {
7 | [Description("单机模式")]
8 | Singleton,
9 |
10 | [Description("服务模式")]
11 | Service
12 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/UIConfig.cs:
--------------------------------------------------------------------------------
1 | using FrpGUI.Avalonia.Models;
2 | using FrpGUI.Configs;
3 | using FzLib;
4 | using System;
5 | using System.ComponentModel;
6 | using System.IO;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using CommunityToolkit.Mvvm.ComponentModel;
10 | using System.Text.Json.Serialization.Metadata;
11 |
12 | namespace FrpGUI.Avalonia;
13 |
14 | public class UIConfig : AppConfigBase, INotifyPropertyChanged
15 | {
16 | private RunningMode runningMode;
17 |
18 | private bool showTrayIcon;
19 |
20 | public UIConfig() : base()
21 | {
22 | }
23 |
24 | public event PropertyChangedEventHandler PropertyChanged;
25 |
26 | public static UIConfig DefaultConfig { get; set; }
27 |
28 | [JsonIgnore]
29 | public override string ConfigPath => Path.Combine(AppContext.BaseDirectory, "uiconfig.json");
30 |
31 | public RunningMode RunningMode
32 | {
33 | get => runningMode;
34 | set
35 | {
36 | runningMode = value;
37 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RunningMode)));
38 | }
39 | }
40 | public string ServerAddress { get; set; } = "http://localhost:5113";
41 |
42 | public string ServerToken { get; set; } = "";
43 |
44 | public bool ShowTrayIcon
45 | {
46 | get => showTrayIcon;
47 | set
48 | {
49 | showTrayIcon = value;
50 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ShowTrayIcon)));
51 | }
52 | }
53 |
54 | private static JsonTypeInfo JsonTypeInfo { get; } = FrpAvaloniaSourceGenerationContext.Get().UIConfig;
55 |
56 | public static UIConfig Get()
57 | {
58 | return Get(JsonTypeInfo);
59 | }
60 |
61 | public void Save()
62 | {
63 | if (OperatingSystem.IsBrowser())
64 | {
65 | var json = JsonSerializer.Serialize(this, JsonTypeInfo);
66 | JsInterop.SetLocalStorage("config", json);
67 | }
68 | else
69 | {
70 | Save(JsonTypeInfo);
71 | }
72 | }
73 |
74 | protected override T GetImpl(JsonTypeInfo jsonTypeInfo)
75 | {
76 | if (OperatingSystem.IsBrowser())
77 | {
78 | try
79 | {
80 | var json = JsInterop.GetLocalStorage("config");
81 | if (string.IsNullOrEmpty(json))
82 | {
83 | if (DefaultConfig != null)
84 | {
85 | return DefaultConfig as T; //优先级2:默认配置。由于HttpClient不支持同步,所以DefaultConfig在Browser项目中进行了赋值
86 | }
87 |
88 | return new UIConfig() as T; //优先级3:新配置
89 | }
90 |
91 | //优先级1:LocalStorage配置
92 | return JsonSerializer.Deserialize(JsInterop.GetLocalStorage("config"), JsonTypeInfo) as T;
93 | }
94 | catch (Exception ex)
95 | {
96 | JsInterop.Alert("读取配置文件错误:" + ex.ToString());
97 | throw;
98 | }
99 | }
100 | else
101 | {
102 | return base.GetImpl(jsonTypeInfo);
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/ViewModels/FrpConfigViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 | using CommunityToolkit.Mvvm.Input;
3 | using FrpGUI.Avalonia.DataProviders;
4 | using FrpGUI.Avalonia.Views;
5 |
6 | using FrpGUI.Models;
7 | using FzLib.Avalonia.Messages;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using System;
10 | using System.Collections.ObjectModel;
11 | using System.Threading.Tasks;
12 |
13 | namespace FrpGUI.Avalonia.ViewModels;
14 |
15 | public partial class FrpConfigViewModel(IDataProvider provider, IServiceProvider services) : ViewModelBase(provider)
16 | {
17 | [ObservableProperty]
18 | private IFrpProcess frp;
19 |
20 | [ObservableProperty]
21 | private ObservableCollection rules;
22 |
23 | public async Task AddRuleAsync()
24 | {
25 | var dialog = services.GetRequiredService();
26 | var message = SendMessage(new DialogHostMessage(dialog));
27 | var result = await message.Task;
28 | if (result is Rule newRule)
29 | {
30 | Rules.Add(newRule);
31 | }
32 | }
33 |
34 | public void LoadConfig(IFrpProcess frp)
35 | {
36 | Frp = frp;
37 | if (frp?.Config is ClientConfig cc)
38 | {
39 | Rules = new ObservableCollection(cc.Rules);
40 | Rules.CollectionChanged += (s, e) => cc.Rules = [.. Rules];
41 | }
42 | }
43 |
44 | [RelayCommand]
45 | private void DisableRule(Rule rule)
46 | {
47 | rule.Enable = false;
48 | }
49 |
50 | [RelayCommand]
51 | private void EnableRule(Rule rule)
52 | {
53 | rule.Enable = true;
54 | }
55 |
56 | [RelayCommand]
57 | private async Task ModifyRuleAsync(Rule rule)
58 | {
59 | var dialog = services.GetRequiredService();
60 | dialog.SetRule(rule);
61 | var message = SendMessage(new DialogHostMessage(dialog));
62 | var result = await message.Task;
63 | if (result is Rule newRule)
64 | {
65 | Rules[Rules.IndexOf(rule)] = newRule;
66 | }
67 | }
68 |
69 | [RelayCommand]
70 | private void RemoveRule(Rule rule)
71 | {
72 | Rules.Remove(rule);
73 | }
74 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/ViewModels/FrpStatusInfo.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | using FrpGUI.Enums;
4 | using FrpGUI.Models;
5 | using System;
6 | using System.Threading.Tasks;
7 |
8 | namespace FrpGUI.Avalonia.ViewModels;
9 |
10 | public partial class FrpStatusInfo : ObservableObject, IFrpProcess
11 | {
12 | private FrpConfigBase config;
13 |
14 | private ProcessStatus processStatus;
15 |
16 | public FrpStatusInfo()
17 | {
18 | }
19 |
20 | public FrpStatusInfo(FrpConfigBase config)
21 | {
22 | Config = config;
23 | }
24 |
25 | public FrpStatusInfo(IFrpProcess fp)
26 | {
27 | Config = fp.Config;
28 | ProcessStatus = fp.ProcessStatus;
29 | fp.StatusChanged += (s, e) =>
30 | {
31 | ProcessStatus = (s as IFrpProcess).ProcessStatus;
32 | StatusChanged?.Invoke(s, e);
33 | };
34 | }
35 |
36 | public event EventHandler StatusChanged;
37 |
38 | public FrpConfigBase Config
39 | {
40 | get => config;
41 | set => SetProperty(ref config, value, nameof(Config));
42 | }
43 |
44 | public ProcessStatus ProcessStatus
45 | {
46 | get => processStatus;
47 | set => SetProperty(ref processStatus, value, nameof(ProcessStatus));
48 | }
49 |
50 | public Task RestartAsync()
51 | {
52 | throw new NotImplementedException();
53 | }
54 |
55 | public Task StartAsync()
56 | {
57 | throw new NotImplementedException();
58 | }
59 |
60 | public Task StopAsync()
61 | {
62 | throw new NotImplementedException();
63 | }
64 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/ViewModels/LogInfo.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Media;
2 | using CommunityToolkit.Mvvm.ComponentModel;
3 | using FrpGUI.Models;
4 | using System;
5 | using System.Diagnostics;
6 |
7 | namespace FrpGUI.Avalonia.ViewModels;
8 |
9 | [DebuggerDisplay("{Message}")]
10 | public partial class LogInfo(LogEntity e) : ObservableObject
11 | {
12 | [ObservableProperty]
13 | [NotifyPropertyChangedFor(nameof(HasUpdated))]
14 | public int updateTimes;
15 |
16 | [ObservableProperty]
17 | private bool fromFrp = e.FromFrp;
18 |
19 | [ObservableProperty]
20 | private string instanceName = e.InstanceName;
21 |
22 | [ObservableProperty]
23 | private string message = e.Message;
24 |
25 | [ObservableProperty]
26 | private DateTime time = e.Time;
27 |
28 | [ObservableProperty]
29 | private char type = e.Type;
30 |
31 | [ObservableProperty]
32 | private IBrush typeBrush;
33 |
34 | public bool HasUpdated => UpdateTimes > 0;
35 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/ViewModels/LogViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Media;
2 | using CommunityToolkit.Mvvm.ComponentModel;
3 | using CommunityToolkit.Mvvm.Input;
4 | using FrpGUI.Avalonia.DataProviders;
5 | using FrpGUI.Models;
6 | using FzLib.Avalonia.Messages;
7 | using System;
8 | using System.Collections.ObjectModel;
9 |
10 | namespace FrpGUI.Avalonia.ViewModels;
11 |
12 | public partial class LogViewModel : ViewModelBase
13 | {
14 | private readonly UIConfig config;
15 | private readonly LocalLogger logger;
16 |
17 | [ObservableProperty]
18 | private LogInfo selectedLog;
19 |
20 | public LogViewModel(IDataProvider provider, UIConfig config, LocalLogger logger) : base(provider)
21 | {
22 | this.config = config;
23 | this.logger = logger;
24 | StartTimer();
25 | }
26 |
27 | public ObservableCollection Logs { get; } = new ObservableCollection();
28 |
29 | public void AddLog(LogEntity e)
30 | {
31 | try
32 | {
33 | //不知道为什么,加了内置浏览器后,Logs会报莫名其妙的错误,有时候Logs最后一个是null,
34 | //但是CollectionChanged里也不触发。所以加了最后一个null的判断以及try-catch
35 | while (Logs.Count > 0 && Logs[^1] == null)
36 | {
37 | Logs.RemoveAt(Logs.Count - 1);
38 | }
39 | IBrush brush = Brushes.Transparent;
40 | if (e.Type == 'W')
41 | {
42 | brush = Brushes.Orange;
43 | }
44 | else if (e.Type == 'E')
45 | {
46 | brush = Brushes.Red;
47 | }
48 |
49 | if (Logs.Count >= 2)
50 | {
51 | for (int i = 1; i <= 2; i++)
52 | {
53 | if (Logs[^i].Message == e.Message)
54 | {
55 | Logs[^i].UpdateTimes++;
56 | return;
57 | }
58 | }
59 | }
60 | var log = new LogInfo(e)
61 | {
62 | TypeBrush = brush,
63 | };
64 |
65 | Logs.Add(log);
66 | SelectedLog = log;
67 | }
68 | catch (Exception ex)
69 | {
70 |
71 | }
72 | }
73 |
74 | [RelayCommand]
75 | private void CopyLog(LogInfo log)
76 | {
77 | SendMessage(new GetClipboardMessage()).Clipboard.SetTextAsync(log.Message);
78 | }
79 |
80 | private void StartTimer()
81 | {
82 | if (DataProvider is WebDataProvider webDataProvider)
83 | {
84 | DateTime lastRequestTime = DateTime.MinValue;
85 | webDataProvider.AddTimerTask("获取日志", async () =>
86 | {
87 | var logs = await DataProvider.GetLogsAsync(lastRequestTime);
88 | if (logs.Count > 0)
89 | {
90 | lastRequestTime = logs[^1].Time;
91 | foreach (var log in logs)
92 | {
93 | AddLog(log);
94 | }
95 | }
96 | });
97 | }
98 | logger.NewLog += (s, e) => AddLog(e.Log);
99 | logger.SaveLogs = false;
100 | foreach (var log in logger.GetSavedLogs())
101 | {
102 | AddLog(log);
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/ViewModels/RuleViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 | using FrpGUI.Avalonia.DataProviders;
3 | using FrpGUI.Enums;
4 | using FrpGUI.Models;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 |
9 | namespace FrpGUI.Avalonia.ViewModels;
10 |
11 | public partial class RuleViewModel(IDataProvider provider) : ViewModelBase(provider)
12 | {
13 | [ObservableProperty]
14 | public Rule rule = new Rule();
15 |
16 | [ObservableProperty]
17 | public string errorMessage;
18 |
19 | private ushort[] GetPorts(string port)
20 | {
21 | if (ushort.TryParse(port, out ushort result))
22 | {
23 | return [result];
24 | }
25 | HashSet ports = new HashSet();
26 | foreach (var part in port.Split(','))
27 | {
28 | if (ushort.TryParse(part, out ushort r))
29 | {
30 | Add(r);
31 | }
32 | var range = part.Split('-');
33 | if (range.Length != 2)
34 | {
35 | T("范围数量错误");
36 | }
37 | if (ushort.TryParse(range[0], out ushort from))
38 | {
39 | if (ushort.TryParse(range[1], out ushort to))
40 | {
41 | if (from >= to)
42 | {
43 | T("范围起始大于结束");
44 | }
45 | for (ushort i = from; i <= to; i++)
46 | {
47 | Add(i);
48 | }
49 | }
50 | else
51 | {
52 | T("范围解析错误");
53 | }
54 | }
55 | else
56 | {
57 | T("范围解析错误");
58 | }
59 | }
60 | return ports.ToArray();
61 | void Add(ushort p)
62 | {
63 | if (ports.Contains(p))
64 | {
65 | if (!ports.Add(p))
66 | {
67 | T("端口号重复:" + p);
68 | }
69 | }
70 | }
71 | }
72 |
73 | private void T(string message)
74 | {
75 | throw new ArgumentException(message);
76 | }
77 |
78 | public bool Check()
79 | {
80 | try
81 | {
82 | if (Rule.Name.Length == 0) T("名称为空");
83 | if (Rule.Name.Length > 10) T("名称长度不可超过10");
84 | if (Rule.LocalPort.Length == 0) T("本地端口为空");
85 | if (Rule.LocalAddress.Length == 0) T("本地地址为空");
86 |
87 | ushort[] localPort = null;
88 | ushort[] remotePort = null;
89 | switch (Rule.Type)
90 | {
91 | case NetType.TCP:
92 | case NetType.UDP:
93 | if (Rule.RemotePort.Length == 0) T("远程端口为空");
94 | try
95 | {
96 | localPort = GetPorts(Rule.LocalPort);
97 | }
98 | catch (FormatException ex)
99 | {
100 | T("本地端口" + ex.Message);
101 | }
102 | try
103 | {
104 | remotePort = GetPorts(Rule.RemotePort);
105 | }
106 | catch (FormatException ex)
107 | {
108 | T("远程端口" + ex.Message);
109 | }
110 | if (localPort.Length != remotePort.Length)
111 | {
112 | T("本地端口和远程端口数量不同");
113 | }
114 | break;
115 |
116 | case NetType.HTTP:
117 | case NetType.HTTPS:
118 | case NetType.STCP:
119 | case NetType.STCP_Visitor:
120 | break;
121 | }
122 | if (Rule.Type is NetType.STCP or NetType.STCP_Visitor && string.IsNullOrWhiteSpace(Rule.StcpKey)) T("STCP密钥为空");
123 | if (Rule.Type is NetType.STCP_Visitor && string.IsNullOrWhiteSpace(Rule.StcpServerName)) T("STCP服务名为空");
124 |
125 | //暂不考虑端口不对应
126 |
127 | return true;
128 | }
129 | catch (Exception ex)
130 | {
131 | ErrorMessage = ex.Message;
132 | return false;
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/ViewModels/SettingViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 | using CommunityToolkit.Mvvm.Input;
3 | using FrpGUI.Avalonia.DataProviders;
4 | using FrpGUI.Models;
5 | using FzLib.Avalonia.Messages;
6 |
7 | using System;
8 | using System.Collections.ObjectModel;
9 | using System.Diagnostics;
10 | using System.Threading.Tasks;
11 | using FzLib.Program.Startup;
12 | using Microsoft.Extensions.DependencyInjection;
13 |
14 | namespace FrpGUI.Avalonia.ViewModels
15 | {
16 | public partial class SettingViewModel : ViewModelBase
17 | {
18 | [ObservableProperty]
19 | private string newToken;
20 |
21 | [ObservableProperty]
22 | private string oldToken;
23 |
24 | [ObservableProperty]
25 | private ObservableCollection processes;
26 |
27 | [ObservableProperty]
28 | private string serverAddress;
29 |
30 | [ObservableProperty]
31 | private bool startup;
32 |
33 | [ObservableProperty]
34 | private string token;
35 | public SettingViewModel(IDataProvider provider, UIConfig config) : base(provider)
36 | {
37 | if (!OperatingSystem.IsBrowser())
38 | {
39 | startup = App.Services.GetRequiredService().IsStartupEnabled();
40 | }
41 | Config = config;
42 | ServerAddress = config.ServerAddress;
43 | FillProcesses();
44 | Config.PropertyChanged += (s, e) =>
45 | {
46 | if (e.PropertyName == nameof(Config.RunningMode) && Config.RunningMode == RunningMode.Service)
47 | {
48 | Startup = false;
49 | }
50 | };
51 | }
52 |
53 | public UIConfig Config { get; }
54 |
55 | private async void FillProcesses()
56 | {
57 | try
58 | {
59 | Processes = new ObservableCollection(await DataProvider.GetSystemProcesses());
60 | }
61 | catch (Exception ex)
62 | { }
63 | }
64 |
65 | [RelayCommand]
66 | private async Task KillProcessAsync(ProcessInfo p)
67 | {
68 | Debug.Assert(p != null);
69 | try
70 | {
71 | await DataProvider.KillProcess(p.Id);
72 | Processes.Remove(p);
73 | }
74 | catch (Exception ex)
75 | {
76 | await SendMessage(new CommonDialogMessage()
77 | {
78 | Type = CommonDialogMessage.CommonDialogType.Error,
79 | Title = "结束进程失败",
80 | Exception = ex,
81 | }).Task;
82 | }
83 | }
84 |
85 | partial void OnStartupChanged(bool value)
86 | {
87 | if (!OperatingSystem.IsBrowser())
88 | {
89 | var startupManager = App.Services.GetRequiredService();
90 |
91 | if (value)
92 | {
93 | startupManager.EnableStartup("s");
94 | Config.ShowTrayIcon = true;
95 | }
96 | else
97 | {
98 | startupManager.DisableStartup();
99 | }
100 | }
101 | }
102 | [RelayCommand]
103 | private async Task RestartAsync()
104 | {
105 | Config.ServerAddress = ServerAddress;
106 | if (!string.IsNullOrEmpty(Token))
107 | {
108 | Config.ServerToken = Token;
109 | }
110 | Config.Save();
111 |
112 | if (OperatingSystem.IsBrowser())
113 | {
114 | JsInterop.Reload();
115 | }
116 | else
117 | {
118 | string exePath = Environment.ProcessPath;
119 | Process.Start(new ProcessStartInfo(exePath)
120 | {
121 | UseShellExecute = true
122 | });
123 | await (App.Current as App).ShutdownAsync();
124 | }
125 | }
126 |
127 | [RelayCommand]
128 | private async Task SetTokenAsync()
129 | {
130 | try
131 | {
132 | Config.ServerAddress = ServerAddress;
133 | await DataProvider.SetTokenAsync(OldToken, NewToken);
134 | Config.ServerToken = NewToken;
135 | Config.Save();
136 | await SendMessage(new CommonDialogMessage()
137 | {
138 | Type = CommonDialogMessage.CommonDialogType.Ok,
139 | Title = "修改密码",
140 | Message = "修改密码成功"
141 | }).Task;
142 | }
143 | catch (Exception ex)
144 | {
145 | await ShowErrorAsync(ex, "修改密码失败");
146 | }
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 | using CommunityToolkit.Mvvm.Messaging;
3 | using FrpGUI.Avalonia.DataProviders;
4 | using FzLib.Avalonia.Messages;
5 | using System;
6 | using System.Threading.Tasks;
7 | using static CommunityToolkit.Mvvm.Messaging.IMessengerExtensions;
8 | using static FzLib.Avalonia.Messages.CommonDialogMessage;
9 |
10 | namespace FrpGUI.Avalonia.ViewModels;
11 |
12 | public class ViewModelBase : ObservableObject
13 | {
14 | public ViewModelBase(IDataProvider provider)
15 | {
16 | DataProvider = provider;
17 | }
18 |
19 | protected IDataProvider DataProvider { get; }
20 |
21 | protected TMessage SendMessage(TMessage message) where TMessage : class
22 | {
23 | return WeakReferenceMessenger.Default.Send(message);
24 | }
25 |
26 | protected Task ShowErrorAsync(Exception ex, string title)
27 | {
28 | return SendMessage(new CommonDialogMessage()
29 | {
30 | Type = CommonDialogType.Error,
31 | Title = title,
32 | Exception = ex
33 | }).Task;
34 | }
35 |
36 | protected Task ShowErrorAsync(string message, string title)
37 | {
38 | return SendMessage(new CommonDialogMessage()
39 | {
40 | Type = CommonDialogType.Error,
41 | Title = title,
42 | Message = message
43 | }).Task;
44 | }
45 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Views/ClientPanel.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace FrpGUI.Avalonia.Views;
4 |
5 | public partial class ClientPanel : ConfigPanelBase
6 | {
7 | public ClientPanel()
8 | {
9 | InitializeComponent();
10 | }
11 |
12 | private void PanelBase_SizeChanged(object sender, SizeChangedEventArgs e)
13 | {
14 | Resources["RuleWidth"] = lstRules.Bounds.Width switch
15 | {
16 | < 840 => lstRules.Bounds.Width - 0,
17 | _ => 420d
18 | };
19 | }
20 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Views/ConfigPanelBase.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace FrpGUI.Avalonia.Views
4 | {
5 | public class ConfigPanelBase : UserControl
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Views/ControlBar.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Interactivity;
3 | using FzLib.Avalonia.Controls;
4 |
5 | namespace FrpGUI.Avalonia.Views;
6 |
7 | public partial class ControlBar : UserControl
8 | {
9 | //private MainViewModel viewModel;
10 |
11 | public ControlBar()
12 | {
13 | InitializeComponent();
14 | }
15 |
16 | protected override void OnLoaded(RoutedEventArgs e)
17 | {
18 | base.OnLoaded(e);
19 | if (TopLevel.GetTopLevel(this) is Window)
20 | {
21 | new WindowDragHelper(thumb).EnableDrag();
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Views/LogPanel.axaml:
--------------------------------------------------------------------------------
1 |
13 |
21 |
22 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
56 |
60 |
61 |
69 |
77 |
87 |
88 |
89 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Views/LogPanel.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Markup.Xaml;
3 | using FrpGUI.Avalonia.ViewModels;
4 |
5 | using Microsoft.Extensions.DependencyInjection;
6 |
7 | namespace FrpGUI.Avalonia.Views;
8 |
9 | public partial class LogPanel : UserControl
10 | {
11 | public LogPanel()
12 | {
13 | DataContext = App.Services.GetRequiredService();
14 | InitializeComponent();
15 | }
16 | }
--------------------------------------------------------------------------------
/FrpGUI.Avalonia/Views/MainView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Interactivity;
4 | using CommunityToolkit.Mvvm.Messaging;
5 | using FrpGUI.Avalonia.ViewModels;
6 |
7 | using FrpGUI.Models;
8 | using FzLib.Avalonia.Controls;
9 | using FzLib.Avalonia.Dialogs;
10 | using FzLib.Avalonia.Messages;
11 | using Microsoft.Extensions.DependencyInjection;
12 | using System;
13 |
14 | namespace FrpGUI.Avalonia.Views;
15 |
16 | public partial class MainView : UserControl
17 | {
18 | public MainView()
19 | {
20 | DataContext = App.Services.GetRequiredService();
21 | InitializeComponent();
22 | RegisterMessages();
23 | }
24 |
25 | protected override void OnLoaded(RoutedEventArgs e)
26 | {
27 | base.OnLoaded(e);
28 | if (TopLevel.GetTopLevel(this) is Window)
29 | {
30 | //new WindowDragHelper(controlBar).EnableDrag();
31 | new WindowDragHelper(tbkLogo).EnableDrag();
32 | }
33 | }
34 |
35 | private void RegisterDialogHostMessage()
36 | {
37 | WeakReferenceMessenger.Default.Register(this, async delegate (object _, DialogHostMessage m)
38 | {
39 | try
40 | {
41 | m.SetResult(await m.Dialog.ShowDialog