Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/Web/AdminPanel/Pages/Servers.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@page "/servers"

@using System.ComponentModel
@using Microsoft.Extensions.DependencyInjection
@using Blazored.Toast.Services
@using MUnique.OpenMU.Interfaces
@using MUnique.OpenMU.Web.AdminPanel.Properties

Expand All @@ -16,6 +18,35 @@
}
else
{
@if (_gameServers is not null)
{
<div class="card mb-3">
<div class="card-body">
<div class="row g-2 align-items-end">
<div class="col-md-5">
<label for="global-message" class="form-label mb-1">@Resources.GlobalMessage</label>
<input id="global-message" type="text" class="form-control" placeholder="@Resources.MessagePlaceholder" @bind="_message" @bind:event="oninput" />
</div>
<div class="col-md-4">
<label for="server-select" class="form-label mb-1">@Resources.Target</label>
<select id="server-select" class="form-select" @bind="_selectedServerId">
<option value="-1">@Resources.AllGameServers</option>
@foreach (var server in _servers.Where(s => s.Type == ServerType.GameServer && s.ServerState == ServerState.Started))
{
<option value="@server.Id">@server.Description (@(string.Format(Resources.OnlineCount, server.CurrentConnections)))</option>
}
</select>
</div>
<div class="col-md-3">
<button class="btn btn-primary w-100" @onclick="SendMessageAsync" disabled="@string.IsNullOrWhiteSpace(_message)">
@Resources.Send
</button>
</div>
</div>
</div>
</div>
}

<div>
<table>
<thead>
Expand Down Expand Up @@ -78,6 +109,10 @@ else

private bool _isRestarting;

private string _message = string.Empty;

private int _selectedServerId = -1;

/// <summary>
/// Gets or sets the <see cref="IServerProvider"/>.
/// </summary>
Expand All @@ -90,6 +125,25 @@ else
[Inject]
public IGameServerInstanceManager? ServerInstanceManager { get; set; }

/// <summary>
/// Gets or sets the service provider, used to optionally resolve the game server dictionary.
/// </summary>
[Inject]
public IServiceProvider ServiceProvider { get; set; } = null!;

/// <summary>
/// Gets or sets the toast service.
/// </summary>
[Inject]
public IToastService ToastService { get; set; } = null!;

/// <summary>
/// The game servers, resolved optionally: the dictionary is only registered in the
/// single-process host, so it is null on the distributed (Dapr) admin panel host. When
/// null, the global message card is not rendered (graceful degradation).
/// </summary>
private IDictionary<int, IGameServer>? _gameServers;

/// <inheritdoc />
public void Dispose()
{
Expand All @@ -101,6 +155,7 @@ else
{
base.OnInitialized();
this._servers = this.ServerProvider.Servers;
this._gameServers = this.ServiceProvider.GetService<IDictionary<int, IGameServer>>();
this.ServerProvider.PropertyChanged += this.OnServersChanged;
}

Expand All @@ -125,4 +180,57 @@ else
this._isRestarting = false;
}
}

private async Task SendMessageAsync()
{
var message = _message;
if (string.IsNullOrWhiteSpace(message) || this._gameServers is not { } gameServers)
{
return;
}

// Clear the input immediately (on the sync context) so the Send button gets disabled,
// preventing double-submission and preserving any text the user types while sending.
_message = string.Empty;

var targets = _selectedServerId == -1
? gameServers.Values.Where(s => s.ServerState == ServerState.Started).ToList()
: gameServers.TryGetValue(_selectedServerId, out var selected) && selected.ServerState == ServerState.Started
? new List<IGameServer> { selected }
: new List<IGameServer>();

if (targets.Count == 0)
{
// No running target (e.g. the selected server stopped meanwhile). Restore the text so
// the admin can pick another target and retry without retyping.
_message = message;
this.ToastService.ShowInfo(Resources.GlobalMessageNoTarget);
return;
}

var sent = 0;
foreach (var server in targets)
{
try
{
await server.SendGlobalMessageAsync(message, MessageType.GoldenCenter);
sent++;
}
catch (Exception ex)
{
// Per-server catch so one failing server does not abort the broadcast to the rest.
this.ToastService.ShowError(string.Format(Resources.GlobalMessageSendFailed, server.Description, ex.Message));
}
}

if (sent == targets.Count)
{
this.ToastService.ShowSuccess(Resources.GlobalMessageSent);
}
else if (sent == 0)
{
// Nothing got through: restore the text so the whole broadcast can be retried.
_message = message;
}
}
Comment thread
nolt marked this conversation as resolved.
}
Loading