diff --git a/LANCommander.Server.Data.MySQL/Migrations/20260308000001_AddRemoteServerEngine.cs b/LANCommander.Server.Data.MySQL/Migrations/20260308000001_AddRemoteServerEngine.cs
new file mode 100644
index 00000000..fad147cb
--- /dev/null
+++ b/LANCommander.Server.Data.MySQL/Migrations/20260308000001_AddRemoteServerEngine.cs
@@ -0,0 +1,41 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace LANCommander.Server.Data.MySQL.Migrations
+{
+ ///
+ public partial class AddRemoteServerEngine : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "RemoteHostId",
+ table: "Servers",
+ type: "char(36)",
+ nullable: true,
+ collation: "ascii_general_ci");
+
+ migrationBuilder.AddColumn(
+ name: "RemoteServerId",
+ table: "Servers",
+ type: "char(36)",
+ nullable: true,
+ collation: "ascii_general_ci");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "RemoteHostId",
+ table: "Servers");
+
+ migrationBuilder.DropColumn(
+ name: "RemoteServerId",
+ table: "Servers");
+ }
+ }
+}
diff --git a/LANCommander.Server.Data.MySQL/Migrations/DatabaseContextModelSnapshot.cs b/LANCommander.Server.Data.MySQL/Migrations/DatabaseContextModelSnapshot.cs
index 08efa71b..1875e91b 100644
--- a/LANCommander.Server.Data.MySQL/Migrations/DatabaseContextModelSnapshot.cs
+++ b/LANCommander.Server.Data.MySQL/Migrations/DatabaseContextModelSnapshot.cs
@@ -1204,6 +1204,12 @@ namespace LANCommander.Server.Data.MySQL.Migrations
b.Property("ProcessTerminationMethod")
.HasColumnType("int");
+ b.Property("RemoteHostId")
+ .HasColumnType("char(36)");
+
+ b.Property("RemoteServerId")
+ .HasColumnType("char(36)");
+
b.Property("UpdatedById")
.HasColumnType("char(36)");
diff --git a/LANCommander.Server.Data.PostgreSQL/Migrations/20260308000001_AddRemoteServerEngine.cs b/LANCommander.Server.Data.PostgreSQL/Migrations/20260308000001_AddRemoteServerEngine.cs
new file mode 100644
index 00000000..191d5da9
--- /dev/null
+++ b/LANCommander.Server.Data.PostgreSQL/Migrations/20260308000001_AddRemoteServerEngine.cs
@@ -0,0 +1,39 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace LANCommander.Server.Data.PostgreSQL.Migrations
+{
+ ///
+ public partial class AddRemoteServerEngine : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "RemoteHostId",
+ table: "Servers",
+ type: "uuid",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "RemoteServerId",
+ table: "Servers",
+ type: "uuid",
+ nullable: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "RemoteHostId",
+ table: "Servers");
+
+ migrationBuilder.DropColumn(
+ name: "RemoteServerId",
+ table: "Servers");
+ }
+ }
+}
diff --git a/LANCommander.Server.Data.PostgreSQL/Migrations/DatabaseContextModelSnapshot.cs b/LANCommander.Server.Data.PostgreSQL/Migrations/DatabaseContextModelSnapshot.cs
index f4fd3ef7..e9b62da2 100644
--- a/LANCommander.Server.Data.PostgreSQL/Migrations/DatabaseContextModelSnapshot.cs
+++ b/LANCommander.Server.Data.PostgreSQL/Migrations/DatabaseContextModelSnapshot.cs
@@ -1204,6 +1204,12 @@ namespace LANCommander.Server.Data.PostgreSQL.Migrations
b.Property("ProcessTerminationMethod")
.HasColumnType("integer");
+ b.Property("RemoteHostId")
+ .HasColumnType("uuid");
+
+ b.Property("RemoteServerId")
+ .HasColumnType("uuid");
+
b.Property("UpdatedById")
.HasColumnType("uuid");
diff --git a/LANCommander.Server.Data.SQLite/Migrations/20260308000001_AddRemoteServerEngine.cs b/LANCommander.Server.Data.SQLite/Migrations/20260308000001_AddRemoteServerEngine.cs
new file mode 100644
index 00000000..bed2ce8b
--- /dev/null
+++ b/LANCommander.Server.Data.SQLite/Migrations/20260308000001_AddRemoteServerEngine.cs
@@ -0,0 +1,39 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace LANCommander.Migrations
+{
+ ///
+ public partial class AddRemoteServerEngine : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "RemoteHostId",
+ table: "Servers",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "RemoteServerId",
+ table: "Servers",
+ type: "TEXT",
+ nullable: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "RemoteHostId",
+ table: "Servers");
+
+ migrationBuilder.DropColumn(
+ name: "RemoteServerId",
+ table: "Servers");
+ }
+ }
+}
diff --git a/LANCommander.Server.Data.SQLite/Migrations/DatabaseContextModelSnapshot.cs b/LANCommander.Server.Data.SQLite/Migrations/DatabaseContextModelSnapshot.cs
index 3733387f..a01d905b 100644
--- a/LANCommander.Server.Data.SQLite/Migrations/DatabaseContextModelSnapshot.cs
+++ b/LANCommander.Server.Data.SQLite/Migrations/DatabaseContextModelSnapshot.cs
@@ -1370,6 +1370,12 @@ namespace LANCommander.Migrations
b.Property("ProcessTerminationMethod")
.HasColumnType("INTEGER");
+ b.Property("RemoteHostId")
+ .HasColumnType("TEXT");
+
+ b.Property("RemoteServerId")
+ .HasColumnType("TEXT");
+
b.Property("UpdatedById")
.HasColumnType("TEXT");
diff --git a/LANCommander.Server.Data/Models/Server.cs b/LANCommander.Server.Data/Models/Server.cs
index b0666212..bf4642bb 100644
--- a/LANCommander.Server.Data/Models/Server.cs
+++ b/LANCommander.Server.Data/Models/Server.cs
@@ -19,6 +19,8 @@ namespace LANCommander.Server.Data.Models
public string OnStopScriptPath { get; set; } = "";
public Guid? DockerHostId { get; set; }
+ public Guid? RemoteHostId { get; set; }
+ public Guid? RemoteServerId { get; set; }
[MaxLength(64)]
public string ContainerId { get; set; } = "";
public string Host { get; set; } = "";
diff --git a/LANCommander.Server.Services/Extensions/IServiceCollectionExtensions.cs b/LANCommander.Server.Services/Extensions/IServiceCollectionExtensions.cs
index 51b53fbd..585a03ed 100644
--- a/LANCommander.Server.Services/Extensions/IServiceCollectionExtensions.cs
+++ b/LANCommander.Server.Services/Extensions/IServiceCollectionExtensions.cs
@@ -72,6 +72,9 @@ public static class IServiceCollectionExtensions
services.AddSingleton();
services.AddSingleton(provider => provider.GetService());
+
+ services.AddSingleton();
+ services.AddSingleton(provider => provider.GetService());
services.AddSingleton();
services.AddSingleton(sp =>
diff --git a/LANCommander.Server.Services/ServerEngines/RemoteServerEngine.cs b/LANCommander.Server.Services/ServerEngines/RemoteServerEngine.cs
new file mode 100644
index 00000000..9ad0f818
--- /dev/null
+++ b/LANCommander.Server.Services/ServerEngines/RemoteServerEngine.cs
@@ -0,0 +1,273 @@
+using System.Net.Http.Json;
+using System.Text.Json;
+using LANCommander.SDK.Models;
+using LANCommander.Server.Services.Abstractions;
+using LANCommander.Server.Services.Enums;
+using LANCommander.Server.Services.Models;
+using LANCommander.Server.Settings.Enums;
+using LANCommander.Server.Settings.Models;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace LANCommander.Server.Services.ServerEngines;
+
+public class RemoteServerEngine(
+ ILogger logger,
+ SettingsProvider settingsProvider,
+ IServiceProvider serviceProvider) : IServerEngine
+{
+ private record RemoteServerInfo(Guid HostId, Guid RemoteServerId);
+
+ private readonly Dictionary _tracked = new();
+ private readonly Dictionary _clients = new();
+ private readonly Dictionary _status = new();
+ private Timer _pollTimer;
+
+ public event EventHandler? OnServerStatusUpdate;
+ public event EventHandler? OnServerLog;
+
+ public async Task InitializeAsync()
+ {
+ foreach (var config in settingsProvider.CurrentValue.Server.GameServers.ServerEngines
+ .Where(e => e.Type == ServerEngine.Remote))
+ {
+ if (!string.IsNullOrWhiteSpace(config.Address) &&
+ Uri.TryCreate(config.Address, UriKind.Absolute, out _))
+ {
+ _clients[config.Id] = CreateHttpClient(config);
+ }
+ }
+
+ using var scope = serviceProvider.CreateScope();
+ var serverService = scope.ServiceProvider.GetRequiredService();
+
+ var servers = await serverService.GetAsync(s => s.Engine == ServerEngine.Remote);
+
+ foreach (var server in servers)
+ {
+ if (server.RemoteHostId.HasValue && server.RemoteServerId.HasValue &&
+ _clients.ContainsKey(server.RemoteHostId.Value))
+ {
+ _tracked[server.Id] = new RemoteServerInfo(server.RemoteHostId.Value, server.RemoteServerId.Value);
+ }
+ }
+
+ _pollTimer = new Timer(PollStatus, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
+ }
+
+ public bool IsManaging(Guid serverId) => _tracked.ContainsKey(serverId);
+
+ public async Task StartAsync(Guid serverId)
+ {
+ if (!_tracked.ContainsKey(serverId))
+ throw new Exception("Server is not being tracked by this engine.");
+
+ var info = _tracked[serverId];
+ await EnsureTokenValidAsync(info.HostId);
+
+ var client = _clients[info.HostId];
+ await client.PostAsync($"api/Server/{info.RemoteServerId}/Start", null);
+ }
+
+ public async Task StopAsync(Guid serverId)
+ {
+ if (!_tracked.ContainsKey(serverId))
+ throw new Exception("Server is not being tracked by this engine.");
+
+ var info = _tracked[serverId];
+ await EnsureTokenValidAsync(info.HostId);
+
+ var client = _clients[info.HostId];
+ await client.PostAsync($"api/Server/{info.RemoteServerId}/Stop", null);
+ }
+
+ public async Task GetStatusAsync(Guid serverId)
+ {
+ if (!_tracked.ContainsKey(serverId))
+ return ServerProcessStatus.Stopped;
+
+ var info = _tracked[serverId];
+
+ try
+ {
+ await EnsureTokenValidAsync(info.HostId);
+
+ var client = _clients[info.HostId];
+ var response = await client.GetAsync($"api/Server/{info.RemoteServerId}/Status");
+
+ if (!response.IsSuccessStatusCode)
+ return ServerProcessStatus.Stopped;
+
+ var status = await response.Content.ReadFromJsonAsync();
+ return status;
+ }
+ catch (Exception ex)
+ {
+ logger?.LogWarning(ex, "Could not retrieve status for remote server {ServerId}", serverId);
+ return ServerProcessStatus.Stopped;
+ }
+ }
+
+ public async Task> GetRemoteServersAsync(Guid hostId)
+ {
+ if (!_clients.TryGetValue(hostId, out var client))
+ return [];
+
+ try
+ {
+ await EnsureTokenValidAsync(hostId);
+
+ var servers = await client.GetFromJsonAsync>("api/Server/");
+
+ return servers ?? [];
+ }
+ catch (Exception ex)
+ {
+ logger?.LogWarning(ex, "Could not retrieve servers from remote host {HostId}", hostId);
+ return [];
+ }
+ }
+
+ public async Task AuthenticateAsync(Guid hostId, string username, string password)
+ {
+ var config = settingsProvider.CurrentValue.Server.GameServers.ServerEngines
+ .FirstOrDefault(e => e.Id == hostId);
+
+ if (config == null)
+ return "Remote host configuration not found.";
+
+ if (!Uri.TryCreate(config.Address, UriKind.Absolute, out _))
+ return "Invalid address configured for remote host.";
+
+ try
+ {
+ var tempClient = CreateHttpClient(config, includeAuth: false);
+ var loginModel = new { Username = username, Password = password };
+
+ var response = await tempClient.PostAsJsonAsync("api/Auth/Login", loginModel);
+
+ if (!response.IsSuccessStatusCode)
+ return $"Authentication failed: {response.StatusCode}";
+
+ var token = await response.Content.ReadFromJsonAsync();
+
+ if (token == null)
+ return "Authentication failed: empty response.";
+
+ settingsProvider.Update(s =>
+ {
+ var c = s.Server.GameServers.ServerEngines.FirstOrDefault(e => e.Id == hostId);
+
+ if (c == null)
+ return;
+
+ c.AccessToken = token.AccessToken;
+ c.RefreshToken = token.RefreshToken;
+ c.TokenExpiration = token.Expiration;
+ });
+
+ _clients[hostId] = CreateHttpClient(settingsProvider.CurrentValue.Server.GameServers.ServerEngines
+ .FirstOrDefault(e => e.Id == hostId) ?? config);
+
+ return string.Empty;
+ }
+ catch (Exception ex)
+ {
+ logger?.LogError(ex, "Error authenticating with remote host {HostId}", hostId);
+ return ex.Message;
+ }
+ }
+
+ private async Task EnsureTokenValidAsync(Guid hostId)
+ {
+ var config = settingsProvider.CurrentValue.Server.GameServers.ServerEngines
+ .FirstOrDefault(e => e.Id == hostId);
+
+ if (config == null)
+ return;
+
+ if (config.TokenExpiration > DateTime.UtcNow.AddMinutes(5))
+ return;
+
+ if (string.IsNullOrWhiteSpace(config.RefreshToken))
+ return;
+
+ try
+ {
+ var tempClient = CreateHttpClient(config, includeAuth: false);
+ var refreshPayload = new AuthToken
+ {
+ AccessToken = config.AccessToken,
+ RefreshToken = config.RefreshToken,
+ Expiration = config.TokenExpiration
+ };
+
+ var response = await tempClient.PostAsJsonAsync("api/Auth/Refresh", refreshPayload);
+
+ if (!response.IsSuccessStatusCode)
+ return;
+
+ var token = await response.Content.ReadFromJsonAsync();
+
+ if (token == null)
+ return;
+
+ settingsProvider.Update(s =>
+ {
+ var c = s.Server.GameServers.ServerEngines.FirstOrDefault(e => e.Id == hostId);
+ if (c != null)
+ {
+ c.AccessToken = token.AccessToken;
+ c.RefreshToken = token.RefreshToken;
+ c.TokenExpiration = token.Expiration;
+ }
+ });
+
+ _clients[hostId] = CreateHttpClient(settingsProvider.CurrentValue.Server.GameServers.ServerEngines
+ .FirstOrDefault(e => e.Id == hostId) ?? config);
+ }
+ catch (Exception ex)
+ {
+ logger?.LogWarning(ex, "Could not refresh token for remote host {HostId}", hostId);
+ }
+ }
+
+ private HttpClient CreateHttpClient(ServerEngineConfiguration config, bool includeAuth = true)
+ {
+ var client = new HttpClient();
+ client.BaseAddress = new Uri(config.Address.TrimEnd('/') + "/");
+
+ if (includeAuth && !string.IsNullOrWhiteSpace(config.AccessToken))
+ client.DefaultRequestHeaders.Authorization =
+ new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", config.AccessToken);
+
+ return client;
+ }
+
+ private async void PollStatus(object? state)
+ {
+ foreach (var (serverId, info) in _tracked.ToList())
+ {
+ try
+ {
+ var newStatus = await GetStatusAsync(serverId);
+
+ if (!_status.TryGetValue(serverId, out var oldStatus) || oldStatus != newStatus)
+ {
+ _status[serverId] = newStatus;
+
+ using var scope = serviceProvider.CreateScope();
+ var serverService = scope.ServiceProvider.GetRequiredService();
+ var server = await serverService.GetAsync(serverId);
+
+ if (server != null)
+ OnServerStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, newStatus));
+ }
+ }
+ catch (Exception ex)
+ {
+ logger?.LogWarning(ex, "Error polling status for remote server {ServerId}", serverId);
+ }
+ }
+ }
+}
diff --git a/LANCommander.Server.Settings/Enums/ServerEngine.cs b/LANCommander.Server.Settings/Enums/ServerEngine.cs
index 3e64ea0b..511a514a 100644
--- a/LANCommander.Server.Settings/Enums/ServerEngine.cs
+++ b/LANCommander.Server.Settings/Enums/ServerEngine.cs
@@ -4,4 +4,5 @@ public enum ServerEngine
{
Local,
Docker,
+ Remote = 2,
}
\ No newline at end of file
diff --git a/LANCommander.Server.Settings/Models/ServerEngineConfiguration.cs b/LANCommander.Server.Settings/Models/ServerEngineConfiguration.cs
index 2b475837..627bbec2 100644
--- a/LANCommander.Server.Settings/Models/ServerEngineConfiguration.cs
+++ b/LANCommander.Server.Settings/Models/ServerEngineConfiguration.cs
@@ -8,6 +8,9 @@ public class ServerEngineConfiguration
public string Name { get; set; } = "Local";
public ServerEngine Type { get; set; } = ServerEngine.Local;
public string Address { get; set; } = String.Empty;
+ public string AccessToken { get; set; } = String.Empty;
+ public string RefreshToken { get; set; } = String.Empty;
+ public DateTime TokenExpiration { get; set; }
public ServerEngineConfiguration()
{
diff --git a/LANCommander.Server/Controllers/ServerController.cs b/LANCommander.Server/Controllers/ServerController.cs
index 73f9366b..cc1b7bad 100644
--- a/LANCommander.Server/Controllers/ServerController.cs
+++ b/LANCommander.Server/Controllers/ServerController.cs
@@ -2,6 +2,7 @@
using LANCommander.SDK.Helpers;
using LANCommander.Server.Services;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
using System.IO.Compression;
namespace LANCommander.Server.Controllers
@@ -10,15 +11,18 @@ namespace LANCommander.Server.Controllers
{
private readonly ServerService ServerService;
private readonly IMapper Mapper;
+ private readonly IOptions _settings;
public ServerController(
ILogger logger,
SettingsProvider settingsProvider,
IMapper mapper,
- ServerService serverService) : base(logger, settingsProvider)
+ ServerService serverService,
+ IOptions settings) : base(logger, settingsProvider)
{
ServerService = serverService;
Mapper = mapper;
+ _settings = settings;
}
[HttpGet("/Server/{id:guid}/{*path}")]
@@ -31,6 +35,17 @@ namespace LANCommander.Server.Controllers
if (server == null)
return NotFound();
+ if (server.Engine == Settings.Enums.ServerEngine.Remote)
+ {
+ var config = _settings.Value.Server.GameServers.ServerEngines
+ .FirstOrDefault(e => e.Id == server.RemoteHostId);
+
+ if (config == null)
+ return NotFound();
+
+ return Redirect($"{config.Address.TrimEnd('/')}/Server/{server.RemoteServerId}/{path}");
+ }
+
if (server.HttpPaths == null || server.HttpPaths.Count == 0)
return NotFound();
diff --git a/LANCommander.Server/UI/Pages/Servers/Edit/General.razor b/LANCommander.Server/UI/Pages/Servers/Edit/General.razor
index 6f1e87e5..1e6b6ec2 100644
--- a/LANCommander.Server/UI/Pages/Servers/Edit/General.razor
+++ b/LANCommander.Server/UI/Pages/Servers/Edit/General.razor
@@ -13,6 +13,7 @@
@inject ServerService ServerService
@inject GameService GameService
@inject DockerServerEngine DockerServerEngine
+@inject RemoteServerEngine RemoteServerEngine
@inject IOptions Settings
@inject NavigationManager NavigationManager
@inject IMessageService MessageService
@@ -83,7 +84,7 @@
@bind-Value="context.DockerHostId"
OnSelectedItemChanged="ChangeDockerHost">
-
+
}
-
-
-
-
-
-
-
-
-
-
+
+ @if (context.Engine == ServerEngine.Remote)
+ {
+
+
+
+
+
+
+
+ }
+
+ @if (context.Engine != ServerEngine.Remote)
+ {
+
+
+
+
+
+
+
+
+
+ }
-
-
- Use Shell Execute
-
-
-
-
-
-
-
-
-
-
-
+ @if (context.Engine != ServerEngine.Remote)
+ {
+
+
+ Use Shell Execute
+
+
+
+
+
+
+
+
+
+
+
+ }
@@ -134,6 +164,7 @@
IEnumerable Games = new List();
IEnumerable Containers = new List();
+ IEnumerable RemoteServers = new List();
Guid GameId;
@@ -172,6 +203,11 @@
{
Containers = await DockerServerEngine.GetContainersAsync(hostConfiguration.Id);
}
+
+ async Task ChangeRemoteHost(ServerEngineConfiguration hostConfiguration)
+ {
+ RemoteServers = await RemoteServerEngine.GetRemoteServersAsync(hostConfiguration.Id);
+ }
async Task Save(Server server)
{
diff --git a/LANCommander.Server/UI/Pages/Settings/Components/ServerEngineEditor.razor b/LANCommander.Server/UI/Pages/Settings/Components/ServerEngineEditor.razor
index 0e0df0c5..3491f832 100644
--- a/LANCommander.Server/UI/Pages/Settings/Components/ServerEngineEditor.razor
+++ b/LANCommander.Server/UI/Pages/Settings/Components/ServerEngineEditor.razor
@@ -1,12 +1,14 @@
+@using LANCommander.Server.Services.ServerEngines
@using LANCommander.Server.Settings.Enums
@using LANCommander.Server.Settings.Models
+@inject RemoteServerEngine RemoteServerEngine
@if (!ServerEngines.Any())
{
-
+
}
-
+
@foreach (var serverEngine in ServerEngines)
{
@@ -23,9 +25,9 @@
-
+
}
-
+
@if (serverEngine.Type == ServerEngine.Docker)
{
@@ -37,7 +39,7 @@
-
+
@@ -45,11 +47,61 @@
}
+
+ @if (serverEngine.Type == ServerEngine.Remote)
+ {
+
+
+
+
+
+
+
+
+ }
}
-
-
+
+
+
+
@@ -59,24 +111,76 @@
List ServerEngines = new();
+ Dictionary _remoteUsernames = new();
+ Dictionary _remotePasswords = new();
+ Dictionary _remoteErrors = new();
+ Dictionary _remoteConnecting = new();
+
protected override void OnParametersSet()
{
ServerEngines = Values.OrderBy(e => e.Type).ThenBy(e => e.Name).ToList();
+
+ foreach (var engine in ServerEngines.Where(e => e.Type == ServerEngine.Remote))
+ {
+ _remoteUsernames.TryAdd(engine.Id, string.Empty);
+ _remotePasswords.TryAdd(engine.Id, string.Empty);
+ _remoteErrors.TryAdd(engine.Id, string.Empty);
+ _remoteConnecting.TryAdd(engine.Id, false);
+ }
}
-
- async Task Add()
+
+ async Task Add(ServerEngine type)
{
- ServerEngines.Add(new ServerEngineConfiguration());
+ var newEngine = new ServerEngineConfiguration
+ {
+ Type = type,
+ Name = type.ToString()
+ };
+
+ ServerEngines.Add(newEngine);
+
+ if (type == ServerEngine.Remote)
+ {
+ _remoteUsernames[newEngine.Id] = string.Empty;
+ _remotePasswords[newEngine.Id] = string.Empty;
+ _remoteErrors[newEngine.Id] = string.Empty;
+ _remoteConnecting[newEngine.Id] = false;
+ }
if (ValuesChanged.HasDelegate)
await ValuesChanged.InvokeAsync(ServerEngines);
}
-
+
async Task Remove(ServerEngineConfiguration serverEngine)
{
ServerEngines.Remove(serverEngine);
+ _remoteUsernames.Remove(serverEngine.Id);
+ _remotePasswords.Remove(serverEngine.Id);
+ _remoteErrors.Remove(serverEngine.Id);
+ _remoteConnecting.Remove(serverEngine.Id);
+
if (ValuesChanged.HasDelegate)
await ValuesChanged.InvokeAsync(ServerEngines);
}
-}
\ No newline at end of file
+
+ async Task Connect(ServerEngineConfiguration serverEngine)
+ {
+ _remoteConnecting[serverEngine.Id] = true;
+ _remoteErrors[serverEngine.Id] = string.Empty;
+
+ await InvokeAsync(StateHasChanged);
+
+ var username = _remoteUsernames.GetValueOrDefault(serverEngine.Id, string.Empty);
+ var password = _remotePasswords.GetValueOrDefault(serverEngine.Id, string.Empty);
+
+ var error = await RemoteServerEngine.AuthenticateAsync(serverEngine.Id, username, password);
+
+ if (!string.IsNullOrWhiteSpace(error))
+ _remoteErrors[serverEngine.Id] = error;
+
+ _remoteConnecting[serverEngine.Id] = false;
+
+ await InvokeAsync(StateHasChanged);
+ }
+}