1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2024-11-25 07:42:58 +01:00

Compare commits

..

No commits in common. "d591318340f8bbf0b05fb9b314c94e3b96d66815" and "94148add2d642ca5d514f76ef59a8c76548d7d5f" have entirely different histories.

41 changed files with 269 additions and 146 deletions

View File

@ -6,6 +6,7 @@ using Phantom.Agent.Services.Instances;
using Phantom.Common.Data.Agent; using Phantom.Common.Data.Agent;
using Phantom.Utils.Actor; using Phantom.Utils.Actor;
using Phantom.Utils.Logging; using Phantom.Utils.Logging;
using Phantom.Utils.Tasks;
using Serilog; using Serilog;
namespace Phantom.Agent.Services; namespace Phantom.Agent.Services;
@ -17,6 +18,7 @@ public sealed class AgentServices {
private AgentFolders AgentFolders { get; } private AgentFolders AgentFolders { get; }
private AgentState AgentState { get; } private AgentState AgentState { get; }
private TaskManager TaskManager { get; }
private BackupManager BackupManager { get; } private BackupManager BackupManager { get; }
internal JavaRuntimeRepository JavaRuntimeRepository { get; } internal JavaRuntimeRepository JavaRuntimeRepository { get; }
@ -28,12 +30,13 @@ public sealed class AgentServices {
this.AgentFolders = agentFolders; this.AgentFolders = agentFolders;
this.AgentState = new AgentState(); this.AgentState = new AgentState();
this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, AgentServices>());
this.BackupManager = new BackupManager(agentFolders, serviceConfiguration.MaxConcurrentCompressionTasks); this.BackupManager = new BackupManager(agentFolders, serviceConfiguration.MaxConcurrentCompressionTasks);
this.JavaRuntimeRepository = new JavaRuntimeRepository(); this.JavaRuntimeRepository = new JavaRuntimeRepository();
this.InstanceTicketManager = new InstanceTicketManager(agentInfo, controllerConnection); this.InstanceTicketManager = new InstanceTicketManager(agentInfo, controllerConnection);
var instanceManagerInit = new InstanceManagerActor.Init(controllerConnection, agentFolders, AgentState, JavaRuntimeRepository, InstanceTicketManager, BackupManager); var instanceManagerInit = new InstanceManagerActor.Init(controllerConnection, agentFolders, AgentState, JavaRuntimeRepository, InstanceTicketManager, TaskManager, BackupManager);
this.InstanceManager = ActorSystem.ActorOf(InstanceManagerActor.Factory(instanceManagerInit), "InstanceManager"); this.InstanceManager = ActorSystem.ActorOf(InstanceManagerActor.Factory(instanceManagerInit), "InstanceManager");
} }
@ -47,6 +50,7 @@ public sealed class AgentServices {
Logger.Information("Stopping services..."); Logger.Information("Stopping services...");
await InstanceManager.Stop(new InstanceManagerActor.ShutdownCommand()); await InstanceManager.Stop(new InstanceManagerActor.ShutdownCommand());
await TaskManager.Stop();
BackupManager.Dispose(); BackupManager.Dispose();

View File

@ -24,7 +24,7 @@ sealed class BackupScheduler : CancellableBackgroundTask {
public event EventHandler<BackupCreationResult>? BackupCompleted; public event EventHandler<BackupCreationResult>? BackupCompleted;
public BackupScheduler(InstanceContext context, InstanceProcess process, int serverPort) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName)) { public BackupScheduler(InstanceContext context, InstanceProcess process, int serverPort) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName), context.Services.TaskManager, "Backup scheduler for " + context.ShortName) {
this.backupManager = context.Services.BackupManager; this.backupManager = context.Services.BackupManager;
this.context = context; this.context = context;
this.process = process; this.process = process;

View File

@ -20,7 +20,7 @@ namespace Phantom.Agent.Services.Instances;
sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand> { sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand> {
private static readonly ILogger Logger = PhantomLogger.Create<InstanceManagerActor>(); private static readonly ILogger Logger = PhantomLogger.Create<InstanceManagerActor>();
public readonly record struct Init(ControllerConnection ControllerConnection, AgentFolders AgentFolders, AgentState AgentState, JavaRuntimeRepository JavaRuntimeRepository, InstanceTicketManager InstanceTicketManager, BackupManager BackupManager); public readonly record struct Init(ControllerConnection ControllerConnection, AgentFolders AgentFolders, AgentState AgentState, JavaRuntimeRepository JavaRuntimeRepository, InstanceTicketManager InstanceTicketManager, TaskManager TaskManager, BackupManager BackupManager);
public static Props<ICommand> Factory(Init init) { public static Props<ICommand> Factory(Init init) {
return Props<ICommand>.Create(() => new InstanceManagerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); return Props<ICommand>.Create(() => new InstanceManagerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
@ -47,7 +47,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
var minecraftServerExecutables = new MinecraftServerExecutables(init.AgentFolders.ServerExecutableFolderPath); var minecraftServerExecutables = new MinecraftServerExecutables(init.AgentFolders.ServerExecutableFolderPath);
var launchServices = new LaunchServices(minecraftServerExecutables, init.JavaRuntimeRepository); var launchServices = new LaunchServices(minecraftServerExecutables, init.JavaRuntimeRepository);
this.instanceServices = new InstanceServices(init.ControllerConnection, init.BackupManager, launchServices); this.instanceServices = new InstanceServices(init.ControllerConnection, init.TaskManager, init.BackupManager, launchServices);
ReceiveAndReply<ConfigureInstanceCommand, InstanceActionResult<ConfigureInstanceResult>>(ConfigureInstance); ReceiveAndReply<ConfigureInstanceCommand, InstanceActionResult<ConfigureInstanceResult>>(ConfigureInstance);
ReceiveAndReply<LaunchInstanceCommand, InstanceActionResult<LaunchInstanceResult>>(LaunchInstance); ReceiveAndReply<LaunchInstanceCommand, InstanceActionResult<LaunchInstanceResult>>(LaunchInstance);

View File

@ -1,7 +1,8 @@
using Phantom.Agent.Minecraft.Launcher; using Phantom.Agent.Minecraft.Launcher;
using Phantom.Agent.Rpc; using Phantom.Agent.Rpc;
using Phantom.Agent.Services.Backups; using Phantom.Agent.Services.Backups;
using Phantom.Utils.Tasks;
namespace Phantom.Agent.Services.Instances; namespace Phantom.Agent.Services.Instances;
sealed record InstanceServices(ControllerConnection ControllerConnection, BackupManager BackupManager, LaunchServices LaunchServices); sealed record InstanceServices(ControllerConnection ControllerConnection, TaskManager TaskManager, BackupManager BackupManager, LaunchServices LaunchServices);

View File

@ -22,7 +22,7 @@ sealed class InstanceLogSender : CancellableBackgroundTask {
private int droppedLinesSinceLastSend; private int droppedLinesSinceLastSend;
public InstanceLogSender(ControllerConnection controllerConnection, Guid instanceGuid, string loggerName) : base(PhantomLogger.Create<InstanceLogSender>(loggerName)) { public InstanceLogSender(ControllerConnection controllerConnection, TaskManager taskManager, Guid instanceGuid, string loggerName) : base(PhantomLogger.Create<InstanceLogSender>(loggerName), taskManager, "Instance log sender for " + loggerName) {
this.controllerConnection = controllerConnection; this.controllerConnection = controllerConnection;
this.instanceGuid = instanceGuid; this.instanceGuid = instanceGuid;
this.outputChannel = Channel.CreateBounded<string>(BufferOptions, OnLineDropped); this.outputChannel = Channel.CreateBounded<string>(BufferOptions, OnLineDropped);

View File

@ -31,7 +31,7 @@ sealed class InstanceRunningState : IDisposable {
this.Process = process; this.Process = process;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.InstanceGuid, context.ShortName); this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.Services.TaskManager, context.InstanceGuid, context.ShortName);
this.backupScheduler = new BackupScheduler(context, process, configuration.ServerPort); this.backupScheduler = new BackupScheduler(context, process, configuration.ServerPort);
this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted; this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;

View File

@ -7,7 +7,7 @@ public enum EventLogEventType {
InstanceStopped, InstanceStopped,
InstanceBackupSucceeded, InstanceBackupSucceeded,
InstanceBackupSucceededWithWarnings, InstanceBackupSucceededWithWarnings,
InstanceBackupFailed InstanceBackupFailed,
} }
public static class EventLogEventTypeExtensions { public static class EventLogEventTypeExtensions {
@ -18,7 +18,7 @@ public static class EventLogEventTypeExtensions {
{ EventLogEventType.InstanceStopped, EventLogSubjectType.Instance }, { EventLogEventType.InstanceStopped, EventLogSubjectType.Instance },
{ EventLogEventType.InstanceBackupSucceeded, EventLogSubjectType.Instance }, { EventLogEventType.InstanceBackupSucceeded, EventLogSubjectType.Instance },
{ EventLogEventType.InstanceBackupSucceededWithWarnings, EventLogSubjectType.Instance }, { EventLogEventType.InstanceBackupSucceededWithWarnings, EventLogSubjectType.Instance },
{ EventLogEventType.InstanceBackupFailed, EventLogSubjectType.Instance } { EventLogEventType.InstanceBackupFailed, EventLogSubjectType.Instance },
}; };
static EventLogEventTypeExtensions() { static EventLogEventTypeExtensions() {

View File

@ -4,7 +4,7 @@ using MemoryPack;
namespace Phantom.Common.Data.Web.Users; namespace Phantom.Common.Data.Web.Users;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record LogInSuccess( public sealed partial record LogInSuccess (
[property: MemoryPackOrder(0)] Guid UserGuid, [property: MemoryPackOrder(0)] Guid UserGuid,
[property: MemoryPackOrder(1)] PermissionSet Permissions, [property: MemoryPackOrder(1)] PermissionSet Permissions,
[property: MemoryPackOrder(2)] ImmutableArray<byte> Token [property: MemoryPackOrder(2)] ImmutableArray<byte> Token

View File

@ -14,7 +14,9 @@ public sealed partial class AuthToken {
private readonly byte[] bytes; private readonly byte[] bytes;
internal AuthToken(byte[]? bytes) { internal AuthToken(byte[]? bytes) {
ArgumentNullException.ThrowIfNull(bytes); if (bytes == null) {
throw new ArgumentNullException(nameof(bytes));
}
if (bytes.Length != Length) { if (bytes.Length != Length) {
throw new ArgumentOutOfRangeException(nameof(bytes), "Invalid token length: " + bytes.Length + ". Token length must be exactly " + Length + " bytes."); throw new ArgumentOutOfRangeException(nameof(bytes), "Invalid token length: " + bytes.Length + ". Token length must be exactly " + Length + " bytes.");

View File

@ -1,10 +0,0 @@
using System.Collections.Immutable;
using MemoryPack;
namespace Phantom.Common.Messages.Web.ToController;
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record LogOutMessage(
[property: MemoryPackOrder(0)] Guid UserGuid,
[property: MemoryPackOrder(1)] ImmutableArray<byte> SessionToken
) : IMessageToController;

View File

@ -24,22 +24,21 @@ public static class WebMessageRegistries {
ToController.Add<RegisterWebMessage>(0); ToController.Add<RegisterWebMessage>(0);
ToController.Add<UnregisterWebMessage>(1); ToController.Add<UnregisterWebMessage>(1);
ToController.Add<LogInMessage, LogInSuccess?>(2); ToController.Add<LogInMessage, LogInSuccess?>(2);
ToController.Add<LogOutMessage>(3); ToController.Add<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(3);
ToController.Add<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(4); ToController.Add<CreateUserMessage, CreateUserResult>(4);
ToController.Add<CreateUserMessage, CreateUserResult>(5); ToController.Add<DeleteUserMessage, DeleteUserResult>(5);
ToController.Add<DeleteUserMessage, DeleteUserResult>(6); ToController.Add<GetUsersMessage, ImmutableArray<UserInfo>>(6);
ToController.Add<GetUsersMessage, ImmutableArray<UserInfo>>(7); ToController.Add<GetRolesMessage, ImmutableArray<RoleInfo>>(7);
ToController.Add<GetRolesMessage, ImmutableArray<RoleInfo>>(8); ToController.Add<GetUserRolesMessage, ImmutableDictionary<Guid, ImmutableArray<Guid>>>(8);
ToController.Add<GetUserRolesMessage, ImmutableDictionary<Guid, ImmutableArray<Guid>>>(9); ToController.Add<ChangeUserRolesMessage, ChangeUserRolesResult>(9);
ToController.Add<ChangeUserRolesMessage, ChangeUserRolesResult>(10); ToController.Add<CreateOrUpdateInstanceMessage, InstanceActionResult<CreateOrUpdateInstanceResult>>(10);
ToController.Add<CreateOrUpdateInstanceMessage, InstanceActionResult<CreateOrUpdateInstanceResult>>(11); ToController.Add<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(11);
ToController.Add<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(12); ToController.Add<StopInstanceMessage, InstanceActionResult<StopInstanceResult>>(12);
ToController.Add<StopInstanceMessage, InstanceActionResult<StopInstanceResult>>(13); ToController.Add<SendCommandToInstanceMessage, InstanceActionResult<SendCommandToInstanceResult>>(13);
ToController.Add<SendCommandToInstanceMessage, InstanceActionResult<SendCommandToInstanceResult>>(14); ToController.Add<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(14);
ToController.Add<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(15); ToController.Add<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(15);
ToController.Add<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(16); ToController.Add<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(16);
ToController.Add<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(17); ToController.Add<GetEventLogMessage, ImmutableArray<EventLogItem>>(17);
ToController.Add<GetEventLogMessage, ImmutableArray<EventLogItem>>(18);
ToController.Add<ReplyMessage>(127); ToController.Add<ReplyMessage>(127);
ToWeb.Add<RegisterWebResultMessage>(0); ToWeb.Add<RegisterWebResultMessage>(0);

View File

@ -13,18 +13,18 @@ namespace Phantom.Controller.Database;
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
public class ApplicationDbContext : DbContext { public class ApplicationDbContext : DbContext {
public DbSet<UserEntity> Users { get; init; } = null!; public DbSet<UserEntity> Users { get; set; } = null!;
public DbSet<RoleEntity> Roles { get; init; } = null!; public DbSet<RoleEntity> Roles { get; set; } = null!;
public DbSet<PermissionEntity> Permissions { get; init; } = null!; public DbSet<PermissionEntity> Permissions { get; set; } = null!;
public DbSet<UserRoleEntity> UserRoles { get; init; } = null!; public DbSet<UserRoleEntity> UserRoles { get; set; } = null!;
public DbSet<UserPermissionEntity> UserPermissions { get; init; } = null!; public DbSet<UserPermissionEntity> UserPermissions { get; set; } = null!;
public DbSet<RolePermissionEntity> RolePermissions { get; init; } = null!; public DbSet<RolePermissionEntity> RolePermissions { get; set; } = null!;
public DbSet<AgentEntity> Agents { get; init; } = null!; public DbSet<AgentEntity> Agents { get; set; } = null!;
public DbSet<InstanceEntity> Instances { get; init; } = null!; public DbSet<InstanceEntity> Instances { get; set; } = null!;
public DbSet<AuditLogEntity> AuditLog { get; init; } = null!; public DbSet<AuditLogEntity> AuditLog { get; set; } = null!;
public DbSet<EventLogEntity> EventLog { get; init; } = null!; public DbSet<EventLogEntity> EventLog { get; set; } = null!;
public AgentEntityUpsert AgentUpsert { get; } public AgentEntityUpsert AgentUpsert { get; }
public InstanceEntityUpsert InstanceUpsert { get; } public InstanceEntityUpsert InstanceUpsert { get; }

View File

@ -9,7 +9,7 @@ namespace Phantom.Controller.Database.Entities;
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
public sealed class AgentEntity { public sealed class AgentEntity {
[Key] [Key]
public Guid AgentGuid { get; init; } public Guid AgentGuid { get; set; }
public string Name { get; set; } public string Name { get; set; }
public ushort ProtocolVersion { get; set; } public ushort ProtocolVersion { get; set; }

View File

@ -13,16 +13,16 @@ public class AuditLogEntity : IDisposable {
[Key] [Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
public long Id { get; init; } public long Id { get; set; }
public Guid? UserGuid { get; init; } public Guid? UserGuid { get; set; }
public DateTime UtcTime { get; init; } // Note: Converting to UTC is not best practice, but for historical records it's good enough. public DateTime UtcTime { get; set; } // Note: Converting to UTC is not best practice, but for historical records it's good enough.
public AuditLogEventType EventType { get; init; } public AuditLogEventType EventType { get; set; }
public AuditLogSubjectType SubjectType { get; init; } public AuditLogSubjectType SubjectType { get; set; }
public string SubjectId { get; init; } public string SubjectId { get; set; }
public JsonDocument? Data { get; init; } public JsonDocument? Data { get; set; }
public virtual UserEntity? User { get; init; } public virtual UserEntity? User { get; set; }
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
internal AuditLogEntity() { internal AuditLogEntity() {

View File

@ -11,14 +11,14 @@ namespace Phantom.Controller.Database.Entities;
[SuppressMessage("ReSharper", "ClassWithVirtualMembersNeverInherited.Global")] [SuppressMessage("ReSharper", "ClassWithVirtualMembersNeverInherited.Global")]
public sealed class EventLogEntity : IDisposable { public sealed class EventLogEntity : IDisposable {
[Key] [Key]
public Guid EventGuid { get; init; } public Guid EventGuid { get; set; }
public DateTime UtcTime { get; init; } // Note: Converting to UTC is not best practice, but for historical records it's good enough. public DateTime UtcTime { get; set; } // Note: Converting to UTC is not best practice, but for historical records it's good enough.
public Guid? AgentGuid { get; init; } public Guid? AgentGuid { get; set; }
public EventLogEventType EventType { get; init; } public EventLogEventType EventType { get; set; }
public EventLogSubjectType SubjectType { get; init; } public EventLogSubjectType SubjectType { get; set; }
public string SubjectId { get; init; } public string SubjectId { get; set; }
public JsonDocument? Data { get; init; } public JsonDocument? Data { get; set; }
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
internal EventLogEntity() { internal EventLogEntity() {

View File

@ -11,7 +11,7 @@ namespace Phantom.Controller.Database.Entities;
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public sealed class InstanceEntity { public sealed class InstanceEntity {
[Key] [Key]
public Guid InstanceGuid { get; init; } public Guid InstanceGuid { get; set; }
public Guid AgentGuid { get; set; } public Guid AgentGuid { get; set; }

View File

@ -6,7 +6,7 @@ namespace Phantom.Controller.Database.Entities;
[Table("Permissions", Schema = "identity")] [Table("Permissions", Schema = "identity")]
public sealed class PermissionEntity { public sealed class PermissionEntity {
[Key] [Key]
public string Id { get; init; } public string Id { get; set; }
public PermissionEntity(string id) { public PermissionEntity(string id) {
Id = id; Id = id;

View File

@ -7,9 +7,9 @@ namespace Phantom.Controller.Database.Entities;
[Table("Roles", Schema = "identity")] [Table("Roles", Schema = "identity")]
public sealed class RoleEntity { public sealed class RoleEntity {
[Key] [Key]
public Guid RoleGuid { get; init; } public Guid RoleGuid { get; set; }
public string Name { get; init; } public string Name { get; set; }
public RoleEntity(Guid roleGuid, string name) { public RoleEntity(Guid roleGuid, string name) {
RoleGuid = roleGuid; RoleGuid = roleGuid;

View File

@ -4,8 +4,8 @@ namespace Phantom.Controller.Database.Entities;
[Table("RolePermissions", Schema = "identity")] [Table("RolePermissions", Schema = "identity")]
public sealed class RolePermissionEntity { public sealed class RolePermissionEntity {
public Guid RoleGuid { get; init; } public Guid RoleGuid { get; set; }
public string PermissionId { get; init; } public string PermissionId { get; set; }
public RolePermissionEntity(Guid roleGuid, string permissionId) { public RolePermissionEntity(Guid roleGuid, string permissionId) {
RoleGuid = roleGuid; RoleGuid = roleGuid;

View File

@ -7,9 +7,9 @@ namespace Phantom.Controller.Database.Entities;
[Table("Users", Schema = "identity")] [Table("Users", Schema = "identity")]
public sealed class UserEntity { public sealed class UserEntity {
[Key] [Key]
public Guid UserGuid { get; init; } public Guid UserGuid { get; set; }
public string Name { get; init; } public string Name { get; set; }
public string PasswordHash { get; set; } public string PasswordHash { get; set; }
public UserEntity(Guid userGuid, string name, string passwordHash) { public UserEntity(Guid userGuid, string name, string passwordHash) {

View File

@ -4,8 +4,8 @@ namespace Phantom.Controller.Database.Entities;
[Table("UserPermissions", Schema = "identity")] [Table("UserPermissions", Schema = "identity")]
public sealed class UserPermissionEntity { public sealed class UserPermissionEntity {
public Guid UserGuid { get; init; } public Guid UserGuid { get; set; }
public string PermissionId { get; init; } public string PermissionId { get; set; }
public UserPermissionEntity(Guid userGuid, string permissionId) { public UserPermissionEntity(Guid userGuid, string permissionId) {
UserGuid = userGuid; UserGuid = userGuid;

View File

@ -4,11 +4,11 @@ namespace Phantom.Controller.Database.Entities;
[Table("UserRoles", Schema = "identity")] [Table("UserRoles", Schema = "identity")]
public sealed class UserRoleEntity { public sealed class UserRoleEntity {
public Guid UserGuid { get; init; } public Guid UserGuid { get; set; }
public Guid RoleGuid { get; init; } public Guid RoleGuid { get; set; }
public UserEntity User { get; init; } public UserEntity User { get; set; }
public RoleEntity Role { get; init; } public RoleEntity Role { get; set; }
public UserRoleEntity(Guid userGuid, Guid roleGuid) { public UserRoleEntity(Guid userGuid, Guid roleGuid) {
UserGuid = userGuid; UserGuid = userGuid;

View File

@ -59,7 +59,7 @@ public sealed class ControllerServices : IDisposable {
this.PermissionManager = new PermissionManager(dbProvider); this.PermissionManager = new PermissionManager(dbProvider);
this.UserRoleManager = new UserRoleManager(dbProvider); this.UserRoleManager = new UserRoleManager(dbProvider);
this.UserLoginManager = new UserLoginManager(UserManager, PermissionManager, dbProvider); this.UserLoginManager = new UserLoginManager(UserManager, PermissionManager);
this.AuditLogManager = new AuditLogManager(dbProvider); this.AuditLogManager = new AuditLogManager(dbProvider);
this.EventLogManager = new EventLogManager(ActorSystem, dbProvider, shutdownCancellationToken); this.EventLogManager = new EventLogManager(ActorSystem, dbProvider, shutdownCancellationToken);

View File

@ -1,4 +1,5 @@
using Phantom.Common.Data.Replies; using Akka.Actor;
using Phantom.Common.Data.Replies;
using Phantom.Common.Messages.Agent; using Phantom.Common.Messages.Agent;
using Phantom.Common.Messages.Agent.BiDirectional; using Phantom.Common.Messages.Agent.BiDirectional;
using Phantom.Common.Messages.Agent.ToAgent; using Phantom.Common.Messages.Agent.ToAgent;
@ -18,6 +19,8 @@ sealed class AgentMessageHandlerActor : ReceiveActor<IMessageToController> {
return Props<IMessageToController>.Create(() => new AgentMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); return Props<IMessageToController>.Create(() => new AgentMessageHandlerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
} }
public IStash Stash { get; set; } = null!;
private readonly Guid agentGuid; private readonly Guid agentGuid;
private readonly RpcConnectionToClient<IMessageToAgent> connection; private readonly RpcConnectionToClient<IMessageToAgent> connection;
private readonly AgentRegistrationHandler agentRegistrationHandler; private readonly AgentRegistrationHandler agentRegistrationHandler;

View File

@ -69,8 +69,6 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
ReceiveAsync<RegisterWebMessage>(HandleRegisterWeb); ReceiveAsync<RegisterWebMessage>(HandleRegisterWeb);
Receive<UnregisterWebMessage>(HandleUnregisterWeb); Receive<UnregisterWebMessage>(HandleUnregisterWeb);
ReceiveAndReplyLater<LogInMessage, LogInSuccess?>(HandleLogIn);
Receive<LogOutMessage>(HandleLogOut);
ReceiveAndReplyLater<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(HandleCreateOrUpdateAdministratorUser); ReceiveAndReplyLater<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(HandleCreateOrUpdateAdministratorUser);
ReceiveAndReplyLater<CreateUserMessage, CreateUserResult>(HandleCreateUser); ReceiveAndReplyLater<CreateUserMessage, CreateUserResult>(HandleCreateUser);
ReceiveAndReplyLater<GetUsersMessage, ImmutableArray<UserInfo>>(HandleGetUsers); ReceiveAndReplyLater<GetUsersMessage, ImmutableArray<UserInfo>>(HandleGetUsers);
@ -86,6 +84,7 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
ReceiveAndReply<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(HandleGetAgentJavaRuntimes); ReceiveAndReply<GetAgentJavaRuntimesMessage, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(HandleGetAgentJavaRuntimes);
ReceiveAndReplyLater<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(HandleGetAuditLog); ReceiveAndReplyLater<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(HandleGetAuditLog);
ReceiveAndReplyLater<GetEventLogMessage, ImmutableArray<EventLogItem>>(HandleGetEventLog); ReceiveAndReplyLater<GetEventLogMessage, ImmutableArray<EventLogItem>>(HandleGetEventLog);
ReceiveAndReplyLater<LogInMessage, LogInSuccess?>(HandleLogIn);
Receive<ReplyMessage>(HandleReply); Receive<ReplyMessage>(HandleReply);
} }
@ -97,14 +96,6 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
connection.Close(); connection.Close();
} }
private Task<LogInSuccess?> HandleLogIn(LogInMessage message) {
return userLoginManager.LogIn(message.Username, message.Password);
}
private void HandleLogOut(LogOutMessage message) {
_ = userLoginManager.LogOut(message.UserGuid, message.SessionToken);
}
private Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message) { private Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message) {
return userManager.CreateOrUpdateAdministrator(message.Username, message.Password); return userManager.CreateOrUpdateAdministrator(message.Username, message.Password);
} }
@ -165,6 +156,10 @@ sealed class WebMessageHandlerActor : ReceiveActor<IMessageToController> {
return eventLogManager.GetMostRecentItems(message.Count); return eventLogManager.GetMostRecentItems(message.Count);
} }
private Task<LogInSuccess?> HandleLogIn(LogInMessage message) {
return userLoginManager.LogIn(message.Username, message.Password);
}
private void HandleReply(ReplyMessage message) { private void HandleReply(ReplyMessage message) {
connection.Receive(message); connection.Receive(message);
} }

View File

@ -2,23 +2,19 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Security.Cryptography; using System.Security.Cryptography;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users;
using Phantom.Controller.Database;
using Phantom.Controller.Database.Repositories;
namespace Phantom.Controller.Services.Users; namespace Phantom.Controller.Services.Users;
sealed class UserLoginManager { sealed class UserLoginManager {
private const int SessionIdBytes = 20; private const int SessionIdBytes = 20;
private readonly ConcurrentDictionary<Guid, List<ImmutableArray<byte>>> sessionTokensByUserGuid = new (); private readonly ConcurrentDictionary<string, List<ImmutableArray<byte>>> sessionTokensByUsername = new ();
private readonly UserManager userManager; private readonly UserManager userManager;
private readonly PermissionManager permissionManager; private readonly PermissionManager permissionManager;
private readonly IDbContextProvider dbProvider;
public UserLoginManager(UserManager userManager, PermissionManager permissionManager, IDbContextProvider dbProvider) { public UserLoginManager(UserManager userManager, PermissionManager permissionManager) {
this.userManager = userManager; this.userManager = userManager;
this.permissionManager = permissionManager; this.permissionManager = permissionManager;
this.dbProvider = dbProvider;
} }
public async Task<LogInSuccess?> LogIn(string username, string password) { public async Task<LogInSuccess?> LogIn(string username, string password) {
@ -28,37 +24,11 @@ sealed class UserLoginManager {
} }
var token = ImmutableArray.Create(RandomNumberGenerator.GetBytes(SessionIdBytes)); var token = ImmutableArray.Create(RandomNumberGenerator.GetBytes(SessionIdBytes));
var sessionTokens = sessionTokensByUserGuid.GetOrAdd(user.UserGuid, static _ => new List<ImmutableArray<byte>>()); var sessionTokens = sessionTokensByUsername.GetOrAdd(username, static _ => new List<ImmutableArray<byte>>());
lock (sessionTokens) { lock (sessionTokens) {
sessionTokens.Add(token); sessionTokens.Add(token);
} }
await using (var db = dbProvider.Lazy()) {
var auditLogWriter = new AuditLogRepository(db).Writer(user.UserGuid);
auditLogWriter.UserLoggedIn(user);
await db.Ctx.SaveChangesAsync();
}
return new LogInSuccess(user.UserGuid, await permissionManager.FetchPermissionsForUserId(user.UserGuid), token); return new LogInSuccess(user.UserGuid, await permissionManager.FetchPermissionsForUserId(user.UserGuid), token);
} }
public async Task LogOut(Guid userGuid, ImmutableArray<byte> sessionToken) {
if (!sessionTokensByUserGuid.TryGetValue(userGuid, out var sessionTokens)) {
return;
}
lock (sessionTokens) {
if (sessionTokens.RemoveAll(token => token.SequenceEqual(sessionToken)) == 0) {
return;
}
}
await using var db = dbProvider.Lazy();
var auditLogWriter = new AuditLogRepository(db).Writer(userGuid);
auditLogWriter.UserLoggedOut(userGuid);
await db.Ctx.SaveChangesAsync();
}
} }

View File

@ -10,6 +10,7 @@ using Phantom.Utils.Logging;
using Phantom.Utils.Rpc; using Phantom.Utils.Rpc;
using Phantom.Utils.Rpc.Runtime; using Phantom.Utils.Rpc.Runtime;
using Phantom.Utils.Runtime; using Phantom.Utils.Runtime;
using Phantom.Utils.Tasks;
var shutdownCancellationTokenSource = new CancellationTokenSource(); var shutdownCancellationTokenSource = new CancellationTokenSource();
var shutdownCancellationToken = shutdownCancellationTokenSource.Token; var shutdownCancellationToken = shutdownCancellationTokenSource.Token;
@ -63,12 +64,14 @@ try {
return new RpcConfiguration(serviceName, host, port, connectionKey.Certificate); return new RpcConfiguration(serviceName, host, port, connectionKey.Certificate);
} }
var rpcTaskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Rpc"));
try { try {
await Task.WhenAll( await Task.WhenAll(
RpcServerRuntime.Launch(ConfigureRpc("Agent", agentRpcServerHost, agentRpcServerPort, agentKeyData), AgentMessageRegistries.Definitions, controllerServices.AgentRegistrationHandler, controllerServices.ActorSystem, shutdownCancellationToken), RpcServerRuntime.Launch(ConfigureRpc("Agent", agentRpcServerHost, agentRpcServerPort, agentKeyData), AgentMessageRegistries.Definitions, controllerServices.AgentRegistrationHandler, controllerServices.ActorSystem, shutdownCancellationToken),
RpcServerRuntime.Launch(ConfigureRpc("Web", webRpcServerHost, webRpcServerPort, webKeyData), WebMessageRegistries.Definitions, controllerServices.WebRegistrationHandler, controllerServices.ActorSystem, shutdownCancellationToken) RpcServerRuntime.Launch(ConfigureRpc("Web", webRpcServerHost, webRpcServerPort, webKeyData), WebMessageRegistries.Definitions, controllerServices.WebRegistrationHandler, controllerServices.ActorSystem, shutdownCancellationToken)
); );
} finally { } finally {
await rpcTaskManager.Stop();
NetMQConfig.Cleanup(); NetMQConfig.Cleanup();
} }

View File

@ -1,12 +1,10 @@
using System.Diagnostics.CodeAnalysis; using Akka.Actor;
using Akka.Actor;
using Akka.Configuration; using Akka.Configuration;
using Akka.Dispatch; using Akka.Dispatch;
using Akka.Dispatch.MessageQueues; using Akka.Dispatch.MessageQueues;
namespace Phantom.Utils.Actor.Mailbox; namespace Phantom.Utils.Actor.Mailbox;
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public sealed class UnboundedJumpAheadMailbox : MailboxType, IProducesMessageQueue<UnboundedJumpAheadMessageQueue> { public sealed class UnboundedJumpAheadMailbox : MailboxType, IProducesMessageQueue<UnboundedJumpAheadMessageQueue> {
public const string Name = "unbounded-jump-ahead-mailbox"; public const string Name = "unbounded-jump-ahead-mailbox";

View File

@ -9,7 +9,7 @@ public sealed class RpcConnectionToClient<TMessageBase> : RpcConnection<TMessage
private readonly uint routingId; private readonly uint routingId;
internal event EventHandler<RpcClientConnectionClosedEventArgs>? Closed; internal event EventHandler<RpcClientConnectionClosedEventArgs>? Closed;
private bool isClosed; public bool IsClosed { get; private set; }
internal RpcConnectionToClient(ServerSocket socket, uint routingId, MessageRegistry<TMessageBase> messageRegistry, MessageReplyTracker replyTracker) : base(messageRegistry, replyTracker) { internal RpcConnectionToClient(ServerSocket socket, uint routingId, MessageRegistry<TMessageBase> messageRegistry, MessageReplyTracker replyTracker) : base(messageRegistry, replyTracker) {
this.socket = socket; this.socket = socket;
@ -24,8 +24,8 @@ public sealed class RpcConnectionToClient<TMessageBase> : RpcConnection<TMessage
bool hasClosed = false; bool hasClosed = false;
lock (this) { lock (this) {
if (!isClosed) { if (!IsClosed) {
isClosed = true; IsClosed = true;
hasClosed = true; hasClosed = true;
} }
} }

View File

@ -12,7 +12,7 @@ public class EnvironmentVariablesTests {
private readonly HashSet<string> createdVariables = new (); private readonly HashSet<string> createdVariables = new ();
private static void Discard<T>(T value) { private static void Discard<T>(T value) {
_ = value; var _ = value;
} }
private string CreateVariable(string value) { private string CreateVariable(string value) {

View File

@ -1,8 +1,18 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace Phantom.Utils.Collections; namespace Phantom.Utils.Collections;
public static class EnumerableExtensions { public static class EnumerableExtensions {
[SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")]
public static IEnumerable<TSource> WhereNotNull<TSource>(this IEnumerable<TSource?> items) {
foreach (var item in items) {
if (item is not null) {
yield return item;
}
}
}
public static async Task<ImmutableArray<TSource>> ToImmutableArrayAsync<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken = default) { public static async Task<ImmutableArray<TSource>> ToImmutableArrayAsync<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken = default) {
var builder = ImmutableArray.CreateBuilder<TSource>(); var builder = ImmutableArray.CreateBuilder<TSource>();

View File

@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis;
namespace Phantom.Utils.Collections; namespace Phantom.Utils.Collections;
public sealed class TableData<TRow, TKey> : IReadOnlyList<TRow>, IReadOnlyDictionary<TKey, TRow> where TRow : notnull where TKey : notnull { public sealed class TableData<TRow, TKey> : IReadOnlyList<TRow>, IReadOnlyDictionary<TKey, TRow> where TRow : notnull where TKey : notnull {
private readonly List<TRow> rowList = new (); private readonly List<TRow> rowList = new();
private readonly Dictionary<TKey, TRow> rowDictionary = new (); private readonly Dictionary<TKey, TRow> rowDictionary = new ();
public TRow this[int index] => rowList[index]; public TRow this[int index] => rowList[index];

View File

@ -8,13 +8,19 @@ public abstract class CancellableBackgroundTask {
protected ILogger Logger { get; } protected ILogger Logger { get; }
protected CancellationToken CancellationToken { get; } protected CancellationToken CancellationToken { get; }
protected CancellableBackgroundTask(ILogger logger) { private readonly TaskManager taskManager;
private readonly string taskName;
protected CancellableBackgroundTask(ILogger logger, TaskManager taskManager, string taskName) {
this.Logger = logger; this.Logger = logger;
this.CancellationToken = cancellationTokenSource.Token; this.CancellationToken = cancellationTokenSource.Token;
this.taskManager = taskManager;
this.taskName = taskName;
} }
protected void Start() { protected void Start() {
Task.Run(Run, CancellationToken.None); taskManager.Run(taskName, Run);
} }
private async Task Run() { private async Task Run() {

View File

@ -1,6 +1,4 @@
using System.Diagnostics.CodeAnalysis; namespace Phantom.Utils.Tasks;
namespace Phantom.Utils.Tasks;
public abstract record Result<TValue, TError> { public abstract record Result<TValue, TError> {
private Result() {} private Result() {}
@ -44,7 +42,7 @@ public abstract record Result<TError> {
return new Fail(error); return new Fail(error);
} }
public static implicit operator Result<TError>([SuppressMessage("ReSharper", "UnusedParameter.Global")] Result.OkType _) { public static implicit operator Result<TError>(Result.OkType _) {
return new Ok(); return new Ok();
} }

View File

@ -0,0 +1,76 @@
using System.Collections.Concurrent;
using Phantom.Utils.Collections;
using Serilog;
namespace Phantom.Utils.Tasks;
public sealed class TaskManager {
private readonly ILogger logger;
private readonly CancellationTokenSource cancellationTokenSource = new ();
private readonly CancellationToken cancellationToken;
private readonly ConcurrentDictionary<Task, string> runningTasks = new (ReferenceEqualityComparer<Task>.Instance);
public TaskManager(ILogger logger) {
this.logger = logger;
this.cancellationToken = cancellationTokenSource.Token;
}
private T Add<T>(string name, T task) where T : Task {
cancellationToken.ThrowIfCancellationRequested();
runningTasks.TryAdd(task, name);
task.ContinueWith(OnFinished, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default);
return task;
}
private void OnFinished(Task task) {
runningTasks.TryRemove(task, out _);
}
public Task Run(string name, Action action) {
return Add(name, Task.Run(action, cancellationToken));
}
public Task Run(string name, Func<Task> taskFunc) {
return Add(name, Task.Run(taskFunc, cancellationToken));
}
public Task<T> Run<T>(string name, Func<Task<T>> taskFunc) {
return Add(name, Task.Run(taskFunc, cancellationToken));
}
public async Task Stop() {
logger.Information("Stopping task manager...");
cancellationTokenSource.Cancel();
var remainingTasksAwaiterTask = WaitForRemainingTasks();
while (true) {
var logStateTimeoutTask = Task.Delay(TimeSpan.FromSeconds(10), CancellationToken.None);
var completedTask = await Task.WhenAny(remainingTasksAwaiterTask, logStateTimeoutTask);
if (completedTask == logStateTimeoutTask) {
var remainingTaskNames = runningTasks.Values.Order().ToList();
var remainingTaskNameList = string.Join('\n', remainingTaskNames.Select(static name => "- " + name));
logger.Warning("Waiting for {TaskCount} task(s) to finish:\n{TaskNames}", remainingTaskNames.Count, remainingTaskNameList);
}
else {
break;
}
}
runningTasks.Clear();
cancellationTokenSource.Dispose();
logger.Information("Task manager stopped.");
}
private async Task WaitForRemainingTasks() {
foreach (var task in runningTasks.Keys) {
try {
await task;
} catch (Exception) {
// ignored
}
}
}
}

View File

@ -0,0 +1,39 @@
namespace Phantom.Utils.Threading;
public sealed class ThreadSafeLinkedList<T> : IDisposable {
private readonly LinkedList<T> list = new ();
private readonly SemaphoreSlim semaphore = new (1, 1);
public async Task Add(T item, bool toFront, CancellationToken cancellationToken) {
await semaphore.WaitAsync(cancellationToken);
try {
if (toFront) {
list.AddFirst(item);
}
else {
list.AddLast(item);
}
} finally {
semaphore.Release();
}
}
public async Task<T?> TryTakeFromFront(CancellationToken cancellationToken) {
await semaphore.WaitAsync(cancellationToken);
try {
var firstNode = list.First;
if (firstNode == null) {
return default;
}
list.RemoveFirst();
return firstNode.Value;
} finally {
semaphore.Release();
}
}
public void Dispose() {
semaphore.Dispose();
}
}

View File

@ -0,0 +1,28 @@
namespace Phantom.Utils.Threading;
public sealed class ThreadSafeStructRef<T> : IDisposable where T : struct {
private T? value;
private readonly SemaphoreSlim semaphore = new (1, 1);
public async Task<T?> Get(CancellationToken cancellationToken) {
await semaphore.WaitAsync(cancellationToken);
try {
return value;
} finally {
semaphore.Release();
}
}
public async Task Set(T? value, CancellationToken cancellationToken) {
await semaphore.WaitAsync(cancellationToken);
try {
this.value = value;
} finally {
semaphore.Release();
}
}
public void Dispose() {
semaphore.Dispose();
}
}

View File

@ -53,8 +53,8 @@ public sealed class UserLoginManager {
public async Task LogOut() { public async Task LogOut() {
var stored = await sessionBrowserStorage.Delete(); var stored = await sessionBrowserStorage.Delete();
if (stored != null && sessionManager.Remove(stored.UserGuid, stored.Token)) { if (stored != null) {
await controllerConnection.Send(new LogOutMessage(stored.UserGuid, stored.Token)); sessionManager.Remove(stored.UserGuid, stored.Token);
} }
await navigation.NavigateTo(string.Empty); await navigation.NavigateTo(string.Empty);

View File

@ -23,13 +23,9 @@ public sealed class UserSessionManager {
return userSessions.TryGetValue(userGuid, out var sessions) && sessions.HasToken(token) ? sessions.UserInfo : null; return userSessions.TryGetValue(userGuid, out var sessions) && sessions.HasToken(token) ? sessions.UserInfo : null;
} }
internal bool Remove(Guid userGuid, ImmutableArray<byte> token) { internal void Remove(Guid userGuid, ImmutableArray<byte> token) {
if (userSessions.TryGetValue(userGuid, out var sessions)) { if (userSessions.TryGetValue(userGuid, out var sessions)) {
sessions.RemoveToken(token); sessions.RemoveToken(token);
return true;
}
else {
return false;
} }
} }
} }

View File

@ -9,6 +9,7 @@ using Phantom.Utils.Logging;
using Phantom.Utils.Rpc; using Phantom.Utils.Rpc;
using Phantom.Utils.Rpc.Sockets; using Phantom.Utils.Rpc.Sockets;
using Phantom.Utils.Runtime; using Phantom.Utils.Runtime;
using Phantom.Utils.Tasks;
using Phantom.Web; using Phantom.Web;
using Phantom.Web.Services; using Phantom.Web.Services;
using Phantom.Web.Services.Rpc; using Phantom.Web.Services.Rpc;
@ -58,7 +59,8 @@ try {
var rpcSocket = RpcClientSocket.Connect(rpcConfiguration, WebMessageRegistries.Definitions, new RegisterWebMessage(webToken)); var rpcSocket = RpcClientSocket.Connect(rpcConfiguration, WebMessageRegistries.Definitions, new RegisterWebMessage(webToken));
var webConfiguration = new WebLauncher.Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, dataProtectionKeysPath, shutdownCancellationToken); var webConfiguration = new WebLauncher.Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, dataProtectionKeysPath, shutdownCancellationToken);
var webApplication = WebLauncher.CreateApplication(webConfiguration, applicationProperties, rpcSocket.Connection); var taskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Web"));
var webApplication = WebLauncher.CreateApplication(webConfiguration, taskManager, applicationProperties, rpcSocket.Connection);
using var actorSystem = ActorSystemFactory.Create("Web"); using var actorSystem = ActorSystemFactory.Create("Web");
@ -86,6 +88,7 @@ try {
await WebLauncher.Launch(webConfiguration, webApplication); await WebLauncher.Launch(webConfiguration, webApplication);
} finally { } finally {
shutdownCancellationTokenSource.Cancel(); shutdownCancellationTokenSource.Cancel();
await taskManager.Stop();
rpcDisconnectSemaphore.Release(); rpcDisconnectSemaphore.Release();
await rpcTask; await rpcTask;

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Phantom.Common.Messages.Web; using Phantom.Common.Messages.Web;
using Phantom.Utils.Rpc.Runtime; using Phantom.Utils.Rpc.Runtime;
using Phantom.Utils.Tasks;
using Phantom.Web.Services; using Phantom.Web.Services;
using Serilog; using Serilog;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -12,7 +13,7 @@ static class WebLauncher {
public string HttpUrl => "http://" + Host + ":" + Port; public string HttpUrl => "http://" + Host + ":" + Port;
} }
internal static WebApplication CreateApplication(Configuration config, ApplicationProperties applicationProperties, RpcConnectionToServer<IMessageToController> controllerConnection) { internal static WebApplication CreateApplication(Configuration config, TaskManager taskManager, ApplicationProperties applicationProperties, RpcConnectionToServer<IMessageToController> controllerConnection) {
var assembly = typeof(WebLauncher).Assembly; var assembly = typeof(WebLauncher).Assembly;
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
ApplicationName = assembly.GetName().Name, ApplicationName = assembly.GetName().Name,
@ -28,6 +29,7 @@ static class WebLauncher {
builder.WebHost.UseStaticWebAssets(); builder.WebHost.UseStaticWebAssets();
} }
builder.Services.AddSingleton(taskManager);
builder.Services.AddSingleton(applicationProperties); builder.Services.AddSingleton(applicationProperties);
builder.Services.AddSingleton(controllerConnection); builder.Services.AddSingleton(controllerConnection);
builder.Services.AddPhantomServices(); builder.Services.AddPhantomServices();