This commit is contained in:
linmeideli committed 2026-09-06 14:00:49 +08:00
1 parent a5dd48a5ed
commit f72a8aa59f
11 files changed
+323 -208

No files matched your search

Binary file not shown.
Binary file not shown.
@@ -1,142 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Empostor.Api.Games.Managers;
using Empostor.Api.Net.Manager;
using Microsoft.AspNetCore.Mvc;
namespace Empostor.Plugins.Monitor;
[ApiController]
public sealed class MonitorController : ControllerBase
{
private static readonly DateTime StartTime = Process.GetCurrentProcess().StartTime.ToUniversalTime();
private readonly IGameManager _gameManager;
private readonly IClientManager _clientManager;
public MonitorController(IGameManager gameManager, IClientManager clientManager)
{
_gameManager = gameManager;
_clientManager = clientManager;
}
[HttpGet("/api/monitor/status")]
public IActionResult GetStatus()
{
var process = Process.GetCurrentProcess();
var uptime = DateTime.UtcNow - StartTime;
var games = _gameManager.Games.ToList();
var clients = _clientManager.Clients.ToList();
var status = new MonitorStatus
{
Status = "running",
Timestamp = DateTime.UtcNow.ToString("o"),
UptimeSeconds = (long)uptime.TotalSeconds,
UptimeDisplay = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m",
Version = GetVersion(),
Runtime = RuntimeInformation.FrameworkDescription,
Platform = RuntimeInformation.OSDescription,
Processors = Environment.ProcessorCount,
MemoryMb = process.WorkingSet64 / 1024 / 1024,
GameCount = games.Count,
PlayerCount = clients.Count(c => c.Player != null),
ActiveConnections = clients.Count,
Games = games.Select(g => new MonitorGame
{
Code = g.Code.ToString(),
State = g.GameState.ToString(),
Map = g.Options.Map.ToString(),
PlayerCount = g.PlayerCount,
Host = g.Host?.Character?.PlayerInfo?.PlayerName ?? "(none)",
}).ToList(),
};
return new JsonResult(status);
}
[HttpGet("/api/monitor/health")]
public IActionResult GetHealth()
{
return Ok(new { status = "healthy", timestamp = DateTime.UtcNow.ToString("o") });
}
private static string GetVersion()
{
try
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var info = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location);
return info.ProductVersion ?? "unknown";
}
catch
{
return "unknown";
}
}
}
public sealed class MonitorStatus
{
[JsonPropertyName("status")]
public string Status { get; set; } = string.Empty;
[JsonPropertyName("timestamp")]
public string Timestamp { get; set; } = string.Empty;
[JsonPropertyName("uptime_seconds")]
public long UptimeSeconds { get; set; }
[JsonPropertyName("uptime_display")]
public string UptimeDisplay { get; set; } = string.Empty;
[JsonPropertyName("version")]
public string Version { get; set; } = string.Empty;
[JsonPropertyName("runtime")]
public string Runtime { get; set; } = string.Empty;
[JsonPropertyName("platform")]
public string Platform { get; set; } = string.Empty;
[JsonPropertyName("processors")]
public int Processors { get; set; }
[JsonPropertyName("memory_mb")]
public long MemoryMb { get; set; }
[JsonPropertyName("game_count")]
public int GameCount { get; set; }
[JsonPropertyName("player_count")]
public int PlayerCount { get; set; }
[JsonPropertyName("active_connections")]
public int ActiveConnections { get; set; }
[JsonPropertyName("games")]
public List<MonitorGame> Games { get; set; } = new();
}
public sealed class MonitorGame
{
[JsonPropertyName("code")]
public string Code { get; set; } = string.Empty;
[JsonPropertyName("state")]
public string State { get; set; } = string.Empty;
[JsonPropertyName("map")]
public string Map { get; set; } = string.Empty;
[JsonPropertyName("player_count")]
public int PlayerCount { get; set; }
[JsonPropertyName("host")]
public string Host { get; set; } = string.Empty;
}
@@ -0,0 +1,106 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Empostor.Api.Games.Managers;
using Empostor.Api.Net.Manager;
using Microsoft.AspNetCore.Http;
namespace Empostor.Plugins.Monitor;
public sealed class MonitorMiddleware
{
private static readonly DateTime StartTime = Process.GetCurrentProcess().StartTime.ToUniversalTime();
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = null,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
};
private readonly RequestDelegate _next;
public MonitorMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, IGameManager gameManager, IClientManager clientManager)
{
var path = context.Request.Path;
if (path.Equals("/api/monitor/status", StringComparison.OrdinalIgnoreCase)
&& HttpMethods.IsGet(context.Request.Method))
{
await WriteJsonAsync(context, BuildStatus(gameManager, clientManager));
return;
}
if (path.Equals("/api/monitor/health", StringComparison.OrdinalIgnoreCase)
&& HttpMethods.IsGet(context.Request.Method))
{
await WriteJsonAsync(context, new
{
status = "healthy",
timestamp = DateTime.UtcNow.ToString("o"),
});
return;
}
await _next(context);
}
private static MonitorStatus BuildStatus(IGameManager gameManager, IClientManager clientManager)
{
var process = Process.GetCurrentProcess();
var uptime = DateTime.UtcNow - StartTime;
var games = gameManager.Games.ToList();
var clients = clientManager.Clients.ToList();
return new MonitorStatus
{
Status = "running",
Timestamp = DateTime.UtcNow.ToString("o"),
UptimeSeconds = (long)uptime.TotalSeconds,
UptimeDisplay = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m",
Version = GetVersion(),
Runtime = RuntimeInformation.FrameworkDescription,
Platform = RuntimeInformation.OSDescription,
Processors = Environment.ProcessorCount,
MemoryMb = process.WorkingSet64 / 1024 / 1024,
GameCount = games.Count,
PlayerCount = clients.Count(c => c.Player != null),
ActiveConnections = clients.Count,
Games = games.Select(g => new MonitorGame
{
Code = g.Code.ToString(),
State = g.GameState.ToString(),
Map = g.Options.Map.ToString(),
PlayerCount = g.PlayerCount,
Host = g.Host?.Character?.PlayerInfo?.PlayerName ?? "(none)",
}).ToList(),
};
}
private static async Task WriteJsonAsync(HttpContext context, object value)
{
context.Response.ContentType = "application/json; charset=utf-8";
await context.Response.WriteAsync(JsonSerializer.Serialize(value, JsonOptions));
}
private static string GetVersion()
{
try
{
var assembly = typeof(MonitorMiddleware).Assembly;
var info = FileVersionInfo.GetVersionInfo(assembly.Location);
return info.ProductVersion ?? "unknown";
}
catch
{
return "unknown";
}
}
}
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Empostor.Plugins.Monitor;
public sealed class MonitorStatus
{
[JsonPropertyName("status")]
public string Status { get; set; } = string.Empty;
[JsonPropertyName("timestamp")]
public string Timestamp { get; set; } = string.Empty;
[JsonPropertyName("uptime_seconds")]
public long UptimeSeconds { get; set; }
[JsonPropertyName("uptime_display")]
public string UptimeDisplay { get; set; } = string.Empty;
[JsonPropertyName("version")]
public string Version { get; set; } = string.Empty;
[JsonPropertyName("runtime")]
public string Runtime { get; set; } = string.Empty;
[JsonPropertyName("platform")]
public string Platform { get; set; } = string.Empty;
[JsonPropertyName("processors")]
public int Processors { get; set; }
[JsonPropertyName("memory_mb")]
public long MemoryMb { get; set; }
[JsonPropertyName("game_count")]
public int GameCount { get; set; }
[JsonPropertyName("player_count")]
public int PlayerCount { get; set; }
[JsonPropertyName("active_connections")]
public int ActiveConnections { get; set; }
[JsonPropertyName("games")]
public List<MonitorGame> Games { get; set; } = new();
}
public sealed class MonitorGame
{
[JsonPropertyName("code")]
public string Code { get; set; } = string.Empty;
[JsonPropertyName("state")]
public string State { get; set; } = string.Empty;
[JsonPropertyName("map")]
public string Map { get; set; } = string.Empty;
[JsonPropertyName("player_count")]
public int PlayerCount { get; set; }
[JsonPropertyName("host")]
public string Host { get; set; } = string.Empty;
}
@@ -1,6 +1,5 @@
using Empostor.Api.Plugins;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -12,11 +11,10 @@ public sealed class MonitorPluginStartup : IPluginStartup, IPluginHttpStartup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddApplicationPart(typeof(MonitorPluginStartup).Assembly);
}
public void ConfigureWebApplication(IApplicationBuilder app)
{
app.UseMiddleware<MonitorMiddleware>();
}
}
@@ -1,60 +0,0 @@
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.Privacy;
[ApiController]
[Route("/privacy")]
public sealed class PrivacyController : ControllerBase
{
private readonly PrivacyStore _store;
private readonly ILogger<PrivacyController> _logger;
public PrivacyController(PrivacyStore store, ILogger<PrivacyController> logger)
{
_store = store;
_logger = logger;
}
[HttpGet]
public IActionResult GetPrivacy()
{
var html = _store.GetContent();
return Content(html, "text/html; charset=utf-8");
}
[HttpPost]
[Route("/admin/api/privacy")]
public async Task<IActionResult> UpdatePrivacy()
{
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
try
{
var doc = JsonDocument.Parse(body);
var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty;
var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty;
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
?? "empostor";
if (token != adminToken)
{
return Unauthorized(new { error = "Invalid token." });
}
_store.SaveContent(content);
return Ok(new { success = true });
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}
}
@@ -0,0 +1,75 @@
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Empostor.Plugins.Privacy;
public sealed class PrivacyMiddleware
{
private readonly RequestDelegate _next;
public PrivacyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, PrivacyStore store)
{
var path = context.Request.Path;
if (path.Equals("/privacy", StringComparison.OrdinalIgnoreCase)
&& HttpMethods.IsGet(context.Request.Method))
{
context.Response.ContentType = "text/html; charset=utf-8";
await context.Response.WriteAsync(store.GetContent());
return;
}
if (path.Equals("/admin/api/privacy", StringComparison.OrdinalIgnoreCase)
&& HttpMethods.IsPost(context.Request.Method))
{
await HandleUpdateAsync(context, store);
return;
}
await _next(context);
}
private static async Task HandleUpdateAsync(HttpContext context, PrivacyStore store)
{
using var reader = new StreamReader(context.Request.Body);
var body = await reader.ReadToEndAsync();
try
{
var doc = JsonDocument.Parse(body);
var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty;
var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty;
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
?? "empostor";
if (token != adminToken)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"error\":\"Invalid token.\"}");
return;
}
store.SaveContent(content);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"success\":true}");
}
catch (Exception ex)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
context.Response.ContentType = "application/json";
var error = JsonSerializer.Serialize(new { error = ex.Message });
await context.Response.WriteAsync(error);
}
}
}
@@ -14,11 +14,10 @@ public sealed class PrivacyPluginStartup : IPluginStartup, IPluginHttpStartup
{
services.AddSingleton<PrivacyStore>();
services.AddSingleton<IAdminExtension, PrivacyAdminExtension>();
services.AddControllers()
.AddApplicationPart(typeof(PrivacyPluginStartup).Assembly);
}
public void ConfigureWebApplication(IApplicationBuilder app)
{
app.UseMiddleware<PrivacyMiddleware>();
}
}
@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Empostor.Api.Plugins;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Hosting;
@@ -16,6 +17,8 @@ namespace Empostor.Server.Plugins
{
private static readonly ILogger Logger = Log.ForContext(typeof(PluginLoader));
internal static IReadOnlyList<Assembly> LoadedPluginAssemblies { get; private set; } = Array.Empty<Assembly>();
public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConfig config)
{
var assemblyInfos = new List<IAssemblyInformation>();
@@ -105,6 +108,10 @@ namespace Empostor.Server.Plugins
}
var orderedPlugins = LoadOrderPlugins(plugins);
LoadedPluginAssemblies = orderedPlugins
.Select(p => p.PluginType.Assembly)
.Distinct()
.ToList();
foreach (var plugin in orderedPlugins)
{
@@ -120,11 +127,50 @@ namespace Empostor.Server.Plugins
{
plugin.Startup?.ConfigureServices(services);
}
EnsureMvcApplicationParts(services, orderedPlugins);
});
return builder;
}
private static void EnsureMvcApplicationParts(IServiceCollection services, IReadOnlyList<PluginInformation> plugins)
{
var manager = services
.LastOrDefault(d => d.ServiceType == typeof(ApplicationPartManager))
?.ImplementationInstance as ApplicationPartManager;
if (manager == null)
{
return;
}
EnsureAssemblyPart(manager, typeof(PluginLoader).Assembly, insertFirst: true);
foreach (var plugin in plugins)
{
EnsureAssemblyPart(manager, plugin.PluginType.Assembly, insertFirst: false);
}
}
private static void EnsureAssemblyPart(ApplicationPartManager manager, Assembly assembly, bool insertFirst)
{
if (manager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == assembly))
{
return;
}
var part = new AssemblyPart(assembly);
if (insertFirst)
{
manager.ApplicationParts.Insert(0, part);
}
else
{
manager.ApplicationParts.Add(part);
}
Logger.Information("Registered MVC controllers from {Assembly}.", assembly.GetName().Name);
}
private static void CheckPaths(IEnumerable<string> paths)
{
foreach (var path in paths)
+30 -1
View File
@@ -33,6 +33,7 @@ using Empostor.Server.Service.Firewall;
using Empostor.Server.Utils;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -269,7 +270,8 @@ namespace Empostor.Server
{
builder.ConfigureServices(s =>
{
s.AddControllers();
s.AddControllers()
.ConfigureApplicationPartManager(EnsureServerApplicationPart);
});
builder.Configure(app =>
{
@@ -289,5 +291,32 @@ namespace Empostor.Server
return hostBuilder;
}
private static void EnsureServerApplicationPart(ApplicationPartManager manager)
{
EnsureAssemblyPart(manager, typeof(Program).Assembly, insertFirst: true);
foreach (var assembly in PluginLoader.LoadedPluginAssemblies)
{
EnsureAssemblyPart(manager, assembly, insertFirst: false);
}
}
private static void EnsureAssemblyPart(ApplicationPartManager manager, Assembly assembly, bool insertFirst)
{
if (manager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == assembly))
{
return;
}
var part = new AssemblyPart(assembly);
if (insertFirst)
{
manager.ApplicationParts.Insert(0, part);
}
else
{
manager.ApplicationParts.Add(part);
}
}
}
}