Add remote LANCommander server installs as viable server engine
This commit is contained in:
parent
8b3a63e9b5
commit
de38e609a5
14 changed files with 612 additions and 38 deletions
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Server.Data.MySQL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddRemoteServerEngine : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RemoteHostId",
|
||||
table: "Servers",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RemoteServerId",
|
||||
table: "Servers",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RemoteHostId",
|
||||
table: "Servers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RemoteServerId",
|
||||
table: "Servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1204,6 +1204,12 @@ namespace LANCommander.Server.Data.MySQL.Migrations
|
|||
b.Property<int>("ProcessTerminationMethod")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid?>("RemoteHostId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("RemoteServerId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("UpdatedById")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Server.Data.PostgreSQL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddRemoteServerEngine : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RemoteHostId",
|
||||
table: "Servers",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RemoteServerId",
|
||||
table: "Servers",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RemoteHostId",
|
||||
table: "Servers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RemoteServerId",
|
||||
table: "Servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1204,6 +1204,12 @@ namespace LANCommander.Server.Data.PostgreSQL.Migrations
|
|||
b.Property<int>("ProcessTerminationMethod")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid?>("RemoteHostId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("RemoteServerId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("UpdatedById")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddRemoteServerEngine : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RemoteHostId",
|
||||
table: "Servers",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RemoteServerId",
|
||||
table: "Servers",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RemoteHostId",
|
||||
table: "Servers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RemoteServerId",
|
||||
table: "Servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1370,6 +1370,12 @@ namespace LANCommander.Migrations
|
|||
b.Property<int>("ProcessTerminationMethod")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("RemoteHostId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("RemoteServerId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("UpdatedById")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
|
|||
|
|
@ -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; } = "";
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ public static class IServiceCollectionExtensions
|
|||
|
||||
services.AddSingleton<DockerServerEngine>();
|
||||
services.AddSingleton<IServerEngine>(provider => provider.GetService<DockerServerEngine>());
|
||||
|
||||
services.AddSingleton<RemoteServerEngine>();
|
||||
services.AddSingleton<IServerEngine>(provider => provider.GetService<RemoteServerEngine>());
|
||||
|
||||
services.AddSingleton<ScriptDebugger>();
|
||||
services.AddSingleton<IScriptDebugger>(sp =>
|
||||
|
|
|
|||
273
LANCommander.Server.Services/ServerEngines/RemoteServerEngine.cs
Normal file
273
LANCommander.Server.Services/ServerEngines/RemoteServerEngine.cs
Normal file
|
|
@ -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<RemoteServerEngine> logger,
|
||||
SettingsProvider<Settings.Settings> settingsProvider,
|
||||
IServiceProvider serviceProvider) : IServerEngine
|
||||
{
|
||||
private record RemoteServerInfo(Guid HostId, Guid RemoteServerId);
|
||||
|
||||
private readonly Dictionary<Guid, RemoteServerInfo> _tracked = new();
|
||||
private readonly Dictionary<Guid, HttpClient> _clients = new();
|
||||
private readonly Dictionary<Guid, ServerProcessStatus> _status = new();
|
||||
private Timer _pollTimer;
|
||||
|
||||
public event EventHandler<ServerStatusUpdateEventArgs>? OnServerStatusUpdate;
|
||||
public event EventHandler<ServerLogEventArgs>? 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<ServerService>();
|
||||
|
||||
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<ServerProcessStatus> 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<ServerProcessStatus>();
|
||||
return status;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogWarning(ex, "Could not retrieve status for remote server {ServerId}", serverId);
|
||||
return ServerProcessStatus.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SDK.Models.Server>> GetRemoteServersAsync(Guid hostId)
|
||||
{
|
||||
if (!_clients.TryGetValue(hostId, out var client))
|
||||
return [];
|
||||
|
||||
try
|
||||
{
|
||||
await EnsureTokenValidAsync(hostId);
|
||||
|
||||
var servers = await client.GetFromJsonAsync<IEnumerable<SDK.Models.Server>>("api/Server/");
|
||||
|
||||
return servers ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogWarning(ex, "Could not retrieve servers from remote host {HostId}", hostId);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> 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<AuthToken>();
|
||||
|
||||
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<AuthToken>();
|
||||
|
||||
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<ServerService>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,4 +4,5 @@ public enum ServerEngine
|
|||
{
|
||||
Local,
|
||||
Docker,
|
||||
Remote = 2,
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.Settings> _settings;
|
||||
|
||||
public ServerController(
|
||||
ILogger<ServerController> logger,
|
||||
SettingsProvider<Settings.Settings> settingsProvider,
|
||||
IMapper mapper,
|
||||
ServerService serverService) : base(logger, settingsProvider)
|
||||
ServerService serverService,
|
||||
IOptions<Settings.Settings> 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
@inject ServerService ServerService
|
||||
@inject GameService GameService
|
||||
@inject DockerServerEngine DockerServerEngine
|
||||
@inject RemoteServerEngine RemoteServerEngine
|
||||
@inject IOptions<Settings> Settings
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IMessageService MessageService
|
||||
|
|
@ -83,7 +84,7 @@
|
|||
@bind-Value="context.DockerHostId"
|
||||
OnSelectedItemChanged="ChangeDockerHost"></Select>
|
||||
</FormItem>
|
||||
|
||||
|
||||
<FormItem Label="Container">
|
||||
<Select
|
||||
TItem="DockerContainer"
|
||||
|
|
@ -95,36 +96,65 @@
|
|||
@bind-Value="context.ContainerId"></Select>
|
||||
</FormItem>
|
||||
}
|
||||
|
||||
<FormItem Label="Executable Path">
|
||||
<FilePicker Root="@RootPath" EntrySelectable="x => x is FileManagerFile" @bind-Value="@context.Path" OnSelected="(path) => context.WorkingDirectory = Path.GetDirectoryName(path)" />
|
||||
</FormItem>
|
||||
<FormItem Label="Arguments">
|
||||
<Input @bind-Value="@context.Arguments" />
|
||||
</FormItem>
|
||||
<FormItem Label="Working Directory">
|
||||
<FilePicker Root="@RootPath" Title="Choose Working Directory" OkText="Select Directory" EntrySelectable="x => x is FileManagerDirectory" @bind-Value="@context.WorkingDirectory" />
|
||||
</FormItem>
|
||||
|
||||
@if (context.Engine == ServerEngine.Remote)
|
||||
{
|
||||
<FormItem Label="Remote Host">
|
||||
<Select TItem="ServerEngineConfiguration"
|
||||
TItemValue="Guid?"
|
||||
DataSource="@Settings.Value.Server.GameServers.ServerEngines.Where(e => e.Type == ServerEngine.Remote)"
|
||||
LabelName="Name"
|
||||
ValueName="Id"
|
||||
@bind-Value="context.RemoteHostId"
|
||||
OnSelectedItemChanged="ChangeRemoteHost" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Remote Server">
|
||||
<Select TItem="SDK.Models.Server"
|
||||
TItemValue="Guid?"
|
||||
DataSource="RemoteServers"
|
||||
Disabled="!RemoteServers.Any()"
|
||||
LabelName="Name"
|
||||
ValueName="Id"
|
||||
@bind-Value="context.RemoteServerId" />
|
||||
</FormItem>
|
||||
}
|
||||
|
||||
@if (context.Engine != ServerEngine.Remote)
|
||||
{
|
||||
<FormItem Label="Executable Path">
|
||||
<FilePicker Root="@RootPath" EntrySelectable="x => x is FileManagerFile" @bind-Value="@context.Path" OnSelected="(path) => context.WorkingDirectory = Path.GetDirectoryName(path)" />
|
||||
</FormItem>
|
||||
<FormItem Label="Arguments">
|
||||
<Input @bind-Value="@context.Arguments" />
|
||||
</FormItem>
|
||||
<FormItem Label="Working Directory">
|
||||
<FilePicker Root="@RootPath" Title="Choose Working Directory" OkText="Select Directory" EntrySelectable="x => x is FileManagerDirectory" @bind-Value="@context.WorkingDirectory" />
|
||||
</FormItem>
|
||||
}
|
||||
<FormItem Label="Host">
|
||||
<Input @bind-Value="@context.Host" />
|
||||
</FormItem>
|
||||
<FormItem Label="Port" HasFeedback="@PortInUse" Help="@(PortInUse ? "Another server may already be bound to this port" : "")" ValidateStatus="@(PortInUse ? FormValidateStatus.Warning : FormValidateStatus.Default)">
|
||||
<ServerPortInput @bind-Value="@context.Port" @bind-PortInUse="PortInUse" ServerId="context.Id" />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<LabelTemplate>
|
||||
Use Shell Execute
|
||||
<Tooltip Title="This option specifies whether you would like to run the server using the shell. Some servers may require this as they will have a UI or won't output logs to stdout">
|
||||
<Icon Type="@IconType.Outline.QuestionCircle" Theme="@IconThemeType.Outline" />
|
||||
</Tooltip>
|
||||
</LabelTemplate>
|
||||
<ChildContent>
|
||||
<Switch @bind-Checked="context.UseShellExecute" />
|
||||
</ChildContent>
|
||||
</FormItem>
|
||||
<FormItem Label="Termination Method">
|
||||
<Select @bind-Value="context.ProcessTerminationMethod" TItem="ProcessTerminationMethod" TItemValue="ProcessTerminationMethod" DataSource="AllowedProcessTerminationMethods" />
|
||||
</FormItem>
|
||||
@if (context.Engine != ServerEngine.Remote)
|
||||
{
|
||||
<FormItem>
|
||||
<LabelTemplate>
|
||||
Use Shell Execute
|
||||
<Tooltip Title="This option specifies whether you would like to run the server using the shell. Some servers may require this as they will have a UI or won't output logs to stdout">
|
||||
<Icon Type="@IconType.Outline.QuestionCircle" Theme="@IconThemeType.Outline" />
|
||||
</Tooltip>
|
||||
</LabelTemplate>
|
||||
<ChildContent>
|
||||
<Switch @bind-Checked="context.UseShellExecute" />
|
||||
</ChildContent>
|
||||
</FormItem>
|
||||
<FormItem Label="Termination Method">
|
||||
<Select @bind-Value="context.ProcessTerminationMethod" TItem="ProcessTerminationMethod" TItemValue="ProcessTerminationMethod" DataSource="AllowedProcessTerminationMethods" />
|
||||
</FormItem>
|
||||
}
|
||||
</Form>
|
||||
</ChildContent>
|
||||
</ServerEditView>
|
||||
|
|
@ -134,6 +164,7 @@
|
|||
|
||||
IEnumerable<Game> Games = new List<Game>();
|
||||
IEnumerable<DockerContainer> Containers = new List<DockerContainer>();
|
||||
IEnumerable<SDK.Models.Server> RemoteServers = new List<SDK.Models.Server>();
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
@using LANCommander.Server.Services.ServerEngines
|
||||
@using LANCommander.Server.Settings.Enums
|
||||
@using LANCommander.Server.Settings.Models
|
||||
@inject RemoteServerEngine RemoteServerEngine
|
||||
|
||||
<Flex Direction="FlexDirection.Vertical" Gap="FlexGap.Large">
|
||||
@if (!ServerEngines.Any())
|
||||
{
|
||||
<Empty Description="@("No Docker hosts have been configured")" />
|
||||
<Empty Description="@("No engines have been configured")" />
|
||||
}
|
||||
|
||||
|
||||
<Collapse>
|
||||
@foreach (var serverEngine in ServerEngines)
|
||||
{
|
||||
|
|
@ -23,9 +25,9 @@
|
|||
</FormItem>
|
||||
</Form>
|
||||
</ChildContent>
|
||||
</Panel>
|
||||
</Panel>
|
||||
}
|
||||
|
||||
|
||||
@if (serverEngine.Type == ServerEngine.Docker)
|
||||
{
|
||||
<Panel Header="@serverEngine.Name">
|
||||
|
|
@ -37,7 +39,7 @@
|
|||
<FormItem Label="Name">
|
||||
<Input @bind-Value="context.Name" BindOnInput />
|
||||
</FormItem>
|
||||
|
||||
|
||||
<FormItem Label="Address">
|
||||
<Input @bind-Value="context.Address" />
|
||||
</FormItem>
|
||||
|
|
@ -45,11 +47,61 @@
|
|||
</ChildContent>
|
||||
</Panel>
|
||||
}
|
||||
|
||||
@if (serverEngine.Type == ServerEngine.Remote)
|
||||
{
|
||||
<Panel Header="@serverEngine.Name">
|
||||
<ExtraTemplate>
|
||||
<Button Type="ButtonType.Text" Icon="@IconType.Outline.Close" Size="ButtonSize.Small" Danger OnClick="() => Remove(serverEngine)"/>
|
||||
</ExtraTemplate>
|
||||
<ChildContent>
|
||||
<Form Model="serverEngine" Layout="FormLayout.Vertical">
|
||||
<FormItem Label="Name">
|
||||
<Input @bind-Value="context.Name" BindOnInput />
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Address">
|
||||
<Input @bind-Value="context.Address" Placeholder="http://other-server:1337" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Status">
|
||||
@if (!String.IsNullOrWhiteSpace(context.AccessToken) && context.TokenExpiration > DateTime.UtcNow)
|
||||
{
|
||||
<Tag Color="success">Connected</Tag>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Tag Color="default">Not Connected</Tag>
|
||||
}
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Username">
|
||||
<Input @bind-Value="_remoteUsernames[serverEngine.Id]" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem Label="Password">
|
||||
<InputPassword @bind-Value="_remotePasswords[serverEngine.Id]" />
|
||||
</FormItem>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_remoteErrors[serverEngine.Id]))
|
||||
{
|
||||
<Alert Type="@AlertType.Error" Message="@_remoteErrors[serverEngine.Id]" ShowIcon />
|
||||
}
|
||||
|
||||
<FormItem>
|
||||
<Button Type="ButtonType.Primary" Loading="_remoteConnecting[serverEngine.Id]" OnClick="() => Connect(serverEngine)">Connect</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</ChildContent>
|
||||
</Panel>
|
||||
}
|
||||
}
|
||||
</Collapse>
|
||||
|
||||
<Flex Justify="FlexJustify.Center">
|
||||
<Button OnClick="Add" Type="ButtonType.Primary">Add Engine</Button>
|
||||
<Flex Justify="FlexJustify.Center" Gap="FlexGap.Small">
|
||||
<Button OnClick="() => Add(ServerEngine.Local)" Type="ButtonType.Default">Add Local</Button>
|
||||
<Button OnClick="() => Add(ServerEngine.Docker)" Type="ButtonType.Default">Add Docker</Button>
|
||||
<Button OnClick="() => Add(ServerEngine.Remote)" Type="ButtonType.Primary">Add Remote</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
|
|
@ -59,24 +111,76 @@
|
|||
|
||||
List<ServerEngineConfiguration> ServerEngines = new();
|
||||
|
||||
Dictionary<Guid, string> _remoteUsernames = new();
|
||||
Dictionary<Guid, string> _remotePasswords = new();
|
||||
Dictionary<Guid, string> _remoteErrors = new();
|
||||
Dictionary<Guid, bool> _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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue