mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 16:42:54 +01:00
Compare commits
2 Commits
e796a364f4
...
5e11a83d1b
Author | SHA1 | Date | |
---|---|---|---|
5e11a83d1b | |||
4df279a249 |
@ -1,5 +1,14 @@
|
|||||||
using System.Text.Json;
|
using MemoryPack;
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.AuditLog;
|
namespace Phantom.Common.Data.Web.AuditLog;
|
||||||
|
|
||||||
public sealed record AuditLogItem(DateTime UtcTime, Guid? UserGuid, string? UserName, AuditLogEventType EventType, AuditLogSubjectType SubjectType, string? SubjectId, JsonDocument? Data);
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record AuditLogItem(
|
||||||
|
[property: MemoryPackOrder(0)] DateTime UtcTime,
|
||||||
|
[property: MemoryPackOrder(1)] Guid? UserGuid,
|
||||||
|
[property: MemoryPackOrder(2)] string? UserName,
|
||||||
|
[property: MemoryPackOrder(3)] AuditLogEventType EventType,
|
||||||
|
[property: MemoryPackOrder(4)] AuditLogSubjectType SubjectType,
|
||||||
|
[property: MemoryPackOrder(5)] string? SubjectId,
|
||||||
|
[property: MemoryPackOrder(6)] string? JsonData
|
||||||
|
);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Phantom.Common.Data.Java;
|
using Phantom.Common.Data.Java;
|
||||||
using Phantom.Common.Data.Minecraft;
|
using Phantom.Common.Data.Minecraft;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
using Phantom.Common.Data.Web.Instance;
|
using Phantom.Common.Data.Web.Instance;
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Common.Messages.Web.BiDirectional;
|
using Phantom.Common.Messages.Web.BiDirectional;
|
||||||
@ -16,7 +17,11 @@ public interface IMessageToControllerListener {
|
|||||||
Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message);
|
Task<CreateOrUpdateAdministratorUserResult> HandleCreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUserMessage message);
|
||||||
Task<InstanceActionResult<CreateOrUpdateInstanceResult>> HandleCreateOrUpdateInstance(CreateOrUpdateInstanceMessage message);
|
Task<InstanceActionResult<CreateOrUpdateInstanceResult>> HandleCreateOrUpdateInstance(CreateOrUpdateInstanceMessage message);
|
||||||
Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message);
|
Task<InstanceActionResult<LaunchInstanceResult>> HandleLaunchInstance(LaunchInstanceMessage message);
|
||||||
|
Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message);
|
||||||
|
Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message);
|
||||||
Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message);
|
Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message);
|
||||||
Task<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> HandleGetAgentJavaRuntimes(GetAgentJavaRuntimes message);
|
Task<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> HandleGetAgentJavaRuntimes(GetAgentJavaRuntimes message);
|
||||||
|
Task<ImmutableArray<UserInfo>> HandleGetUsers(GetUsersMessage message);
|
||||||
|
Task<ImmutableArray<AuditLogItem>> HandleGetAuditLog(GetAuditLogMessage message);
|
||||||
Task<NoReply> HandleReply(ReplyMessage message);
|
Task<NoReply> HandleReply(ReplyMessage message);
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,4 @@ public sealed partial record GetAgentJavaRuntimes : IMessageToController<Immutab
|
|||||||
public Task<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> Accept(IMessageToControllerListener listener) {
|
public Task<ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>> Accept(IMessageToControllerListener listener) {
|
||||||
return listener.HandleGetAgentJavaRuntimes(this);
|
return listener.HandleGetAgentJavaRuntimes(this);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record GetAuditLogMessage(
|
||||||
|
[property: MemoryPackOrder(0)] int Count
|
||||||
|
) : IMessageToController<ImmutableArray<AuditLogItem>> {
|
||||||
|
public Task<ImmutableArray<AuditLogItem>> Accept(IMessageToControllerListener listener) {
|
||||||
|
return listener.HandleGetAuditLog(this);
|
||||||
|
}
|
||||||
|
}
|
@ -9,4 +9,4 @@ public sealed partial record GetMinecraftVersionsMessage : IMessageToController<
|
|||||||
public Task<ImmutableArray<MinecraftVersion>> Accept(IMessageToControllerListener listener) {
|
public Task<ImmutableArray<MinecraftVersion>> Accept(IMessageToControllerListener listener) {
|
||||||
return listener.HandleGetMinecraftVersions(this);
|
return listener.HandleGetMinecraftVersions(this);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Common.Data.Web.Users;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record GetUsersMessage : IMessageToController<ImmutableArray<UserInfo>> {
|
||||||
|
public Task<ImmutableArray<UserInfo>> Accept(IMessageToControllerListener listener) {
|
||||||
|
return listener.HandleGetUsers(this);
|
||||||
|
}
|
||||||
|
}
|
@ -11,4 +11,4 @@ public sealed partial record LogInMessage(
|
|||||||
public Task<LogInSuccess?> Accept(IMessageToControllerListener listener) {
|
public Task<LogInSuccess?> Accept(IMessageToControllerListener listener) {
|
||||||
return listener.HandleLogIn(this);
|
return listener.HandleLogIn(this);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Common.Data.Replies;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record SendCommandToInstanceMessage(
|
||||||
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
|
[property: MemoryPackOrder(1)] Guid InstanceGuid,
|
||||||
|
[property: MemoryPackOrder(2)] string Command
|
||||||
|
) : IMessageToController<InstanceActionResult<SendCommandToInstanceResult>> {
|
||||||
|
public Task<InstanceActionResult<SendCommandToInstanceResult>> Accept(IMessageToControllerListener listener) {
|
||||||
|
return listener.HandleSendCommandToInstance(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
using MemoryPack;
|
||||||
|
using Phantom.Common.Data.Minecraft;
|
||||||
|
using Phantom.Common.Data.Replies;
|
||||||
|
|
||||||
|
namespace Phantom.Common.Messages.Web.ToController;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record StopInstanceMessage(
|
||||||
|
[property: MemoryPackOrder(0)] Guid LoggedInUserGuid,
|
||||||
|
[property: MemoryPackOrder(1)] Guid InstanceGuid,
|
||||||
|
[property: MemoryPackOrder(2)] MinecraftStopStrategy StopStrategy
|
||||||
|
) : IMessageToController<InstanceActionResult<StopInstanceResult>> {
|
||||||
|
public Task<InstanceActionResult<StopInstanceResult>> Accept(IMessageToControllerListener listener) {
|
||||||
|
return listener.HandleStopInstance(this);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using Phantom.Common.Data.Java;
|
using Phantom.Common.Data.Java;
|
||||||
using Phantom.Common.Data.Minecraft;
|
using Phantom.Common.Data.Minecraft;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
using Phantom.Common.Data.Web.Instance;
|
using Phantom.Common.Data.Web.Instance;
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Common.Logging;
|
using Phantom.Common.Logging;
|
||||||
@ -24,8 +25,11 @@ public static class WebMessageRegistries {
|
|||||||
ToController.Add<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(2);
|
ToController.Add<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(2);
|
||||||
ToController.Add<CreateOrUpdateInstanceMessage, InstanceActionResult<CreateOrUpdateInstanceResult>>(3);
|
ToController.Add<CreateOrUpdateInstanceMessage, InstanceActionResult<CreateOrUpdateInstanceResult>>(3);
|
||||||
ToController.Add<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(4);
|
ToController.Add<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(4);
|
||||||
ToController.Add<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(5);
|
ToController.Add<StopInstanceMessage, InstanceActionResult<StopInstanceResult>>(5);
|
||||||
ToController.Add<GetAgentJavaRuntimes, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(6);
|
ToController.Add<GetMinecraftVersionsMessage, ImmutableArray<MinecraftVersion>>(6);
|
||||||
|
ToController.Add<GetAgentJavaRuntimes, ImmutableDictionary<Guid, ImmutableArray<TaggedJavaRuntime>>>(7);
|
||||||
|
ToController.Add<GetUsersMessage, ImmutableArray<UserInfo>>(8);
|
||||||
|
ToController.Add<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(9);
|
||||||
ToController.Add<ReplyMessage>(127);
|
ToController.Add<ReplyMessage>(127);
|
||||||
|
|
||||||
ToWeb.Add<RegisterWebResultMessage>(0);
|
ToWeb.Add<RegisterWebResultMessage>(0);
|
||||||
|
@ -3,7 +3,19 @@ using Phantom.Controller.Database.Entities;
|
|||||||
|
|
||||||
namespace Phantom.Controller.Database.Repositories;
|
namespace Phantom.Controller.Database.Repositories;
|
||||||
|
|
||||||
sealed partial class AuditLogRepository {
|
public sealed class AuditLogRepositoryWriter {
|
||||||
|
private readonly ILazyDbContext db;
|
||||||
|
private readonly Guid? currentUserGuid;
|
||||||
|
|
||||||
|
public AuditLogRepositoryWriter(ILazyDbContext db, Guid? currentUserGuid) {
|
||||||
|
this.db = db;
|
||||||
|
this.currentUserGuid = currentUserGuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddItem(AuditLogEventType eventType, string subjectId, Dictionary<string, object?>? extra = null) {
|
||||||
|
db.Ctx.AuditLog.Add(new AuditLogEntity(currentUserGuid, eventType, subjectId, extra));
|
||||||
|
}
|
||||||
|
|
||||||
public void AddUserLoggedInEvent(UserEntity user) {
|
public void AddUserLoggedInEvent(UserEntity user) {
|
||||||
AddItem(AuditLogEventType.UserLoggedIn, user.UserGuid.ToString());
|
AddItem(AuditLogEventType.UserLoggedIn, user.UserGuid.ToString());
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Phantom.Common.Data.Web.AuditLog;
|
|
||||||
using Phantom.Controller.Database.Entities;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Database.Repositories;
|
|
||||||
|
|
||||||
public sealed partial class AuditLogRepository {
|
|
||||||
private readonly ILazyDbContext db;
|
|
||||||
private readonly Guid? currentUserGuid;
|
|
||||||
|
|
||||||
public AuditLogRepository(ILazyDbContext db, Guid? currentUserGuid) {
|
|
||||||
this.db = db;
|
|
||||||
this.currentUserGuid = currentUserGuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddItem(AuditLogEventType eventType, string subjectId, Dictionary<string, object?>? extra = null) {
|
|
||||||
db.Ctx.AuditLog.Add(new AuditLogEntity(currentUserGuid, eventType, subjectId, extra));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<AuditLogItem[]> GetItems(int count, CancellationToken cancellationToken) {
|
|
||||||
return db.Ctx
|
|
||||||
.AuditLog
|
|
||||||
.Include(static entity => entity.User)
|
|
||||||
.AsQueryable()
|
|
||||||
.OrderByDescending(static entity => entity.UtcTime)
|
|
||||||
.Take(count)
|
|
||||||
.Select(static entity => new AuditLogItem(entity.UtcTime, entity.UserGuid, entity.User == null ? null : entity.User.Name, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data))
|
|
||||||
.ToArrayAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,26 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
|
using Phantom.Utils.Collections;
|
||||||
|
|
||||||
|
namespace Phantom.Controller.Database.Repositories;
|
||||||
|
|
||||||
|
public sealed class AuditLogRepositoryReader {
|
||||||
|
private readonly ILazyDbContext db;
|
||||||
|
|
||||||
|
public AuditLogRepositoryReader(ILazyDbContext db) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ImmutableArray<AuditLogItem>> GetMostRecentItems(int count, CancellationToken cancellationToken) {
|
||||||
|
return db.Ctx
|
||||||
|
.AuditLog
|
||||||
|
.Include(static entity => entity.User)
|
||||||
|
.AsQueryable()
|
||||||
|
.OrderByDescending(static entity => entity.UtcTime)
|
||||||
|
.Take(count)
|
||||||
|
.Select(static entity => new AuditLogItem(entity.UtcTime, entity.UserGuid, entity.User == null ? null : entity.User.Name, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data == null ? null : entity.Data.RootElement.ToString()))
|
||||||
|
.AsAsyncEnumerable()
|
||||||
|
.ToImmutableArrayAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
@ -50,8 +50,8 @@ public sealed class UserRepository {
|
|||||||
|
|
||||||
private readonly ILazyDbContext db;
|
private readonly ILazyDbContext db;
|
||||||
|
|
||||||
private AuditLogRepository? auditLog;
|
private AuditLogRepositoryWriter? auditLogWriter;
|
||||||
private AuditLogRepository AuditLogRepository => this.auditLog ??= new AuditLogRepository(db, null);
|
private AuditLogRepositoryWriter AuditLogWriter => this.auditLogWriter ??= new AuditLogRepositoryWriter(db, null);
|
||||||
|
|
||||||
public UserRepository(ILazyDbContext db) {
|
public UserRepository(ILazyDbContext db) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@ -91,7 +91,7 @@ public sealed class UserRepository {
|
|||||||
var user = new UserEntity(Guid.NewGuid(), username, UserPasswords.Hash(password));
|
var user = new UserEntity(Guid.NewGuid(), username, UserPasswords.Hash(password));
|
||||||
|
|
||||||
db.Ctx.Users.Add(user);
|
db.Ctx.Users.Add(user);
|
||||||
AuditLogRepository.AddUserCreatedEvent(user);
|
AuditLogWriter.AddUserCreatedEvent(user);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@ -103,13 +103,13 @@ public sealed class UserRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.PasswordHash = UserPasswords.Hash(password);
|
user.PasswordHash = UserPasswords.Hash(password);
|
||||||
AuditLogRepository.AddUserPasswordChangedEvent(user);
|
AuditLogWriter.AddUserPasswordChangedEvent(user);
|
||||||
|
|
||||||
return Result.Ok<SetUserPasswordError>();
|
return Result.Ok<SetUserPasswordError>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteUser(UserEntity user) {
|
public void DeleteUser(UserEntity user) {
|
||||||
db.Ctx.Users.Remove(user);
|
db.Ctx.Users.Remove(user);
|
||||||
AuditLogRepository.AddUserDeletedEvent(user);
|
AuditLogWriter.AddUserDeletedEvent(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ public sealed class ControllerServices {
|
|||||||
private PermissionManager PermissionManager { get; }
|
private PermissionManager PermissionManager { get; }
|
||||||
|
|
||||||
private UserLoginManager UserLoginManager { get; }
|
private UserLoginManager UserLoginManager { get; }
|
||||||
|
private AuditLogManager AuditLogManager { get; }
|
||||||
|
|
||||||
private readonly IDbContextProvider dbProvider;
|
private readonly IDbContextProvider dbProvider;
|
||||||
private readonly AuthToken webAuthToken;
|
private readonly AuthToken webAuthToken;
|
||||||
@ -49,6 +50,7 @@ public sealed class ControllerServices {
|
|||||||
this.PermissionManager = new PermissionManager(dbProvider);
|
this.PermissionManager = new PermissionManager(dbProvider);
|
||||||
|
|
||||||
this.UserLoginManager = new UserLoginManager(UserManager, PermissionManager);
|
this.UserLoginManager = new UserLoginManager(UserManager, PermissionManager);
|
||||||
|
this.AuditLogManager = new AuditLogManager(dbProvider);
|
||||||
|
|
||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
this.webAuthToken = webAuthToken;
|
this.webAuthToken = webAuthToken;
|
||||||
@ -60,7 +62,7 @@ public sealed class ControllerServices {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public WebMessageListener CreateWebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection) {
|
public WebMessageListener CreateWebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection) {
|
||||||
return new WebMessageListener(connection, webAuthToken, UserManager, UserLoginManager, AgentManager, AgentJavaRuntimesManager, InstanceManager, MinecraftVersions, TaskManager);
|
return new WebMessageListener(connection, webAuthToken, UserManager, UserLoginManager, AuditLogManager, AgentManager, AgentJavaRuntimesManager, InstanceManager, MinecraftVersions, TaskManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize() {
|
public async Task Initialize() {
|
||||||
|
@ -114,12 +114,12 @@ sealed class InstanceManager {
|
|||||||
entity.JavaRuntimeGuid = configuration.JavaRuntimeGuid;
|
entity.JavaRuntimeGuid = configuration.JavaRuntimeGuid;
|
||||||
entity.JvmArguments = JvmArgumentsHelper.Join(configuration.JvmArguments);
|
entity.JvmArguments = JvmArgumentsHelper.Join(configuration.JvmArguments);
|
||||||
|
|
||||||
var auditLogRepository = new AuditLogRepository(db, auditLogUserGuid);
|
var auditLogWriter = new AuditLogRepositoryWriter(db, auditLogUserGuid);
|
||||||
if (isNewInstance) {
|
if (isNewInstance) {
|
||||||
auditLogRepository.AddInstanceCreatedEvent(configuration.InstanceGuid);
|
auditLogWriter.AddInstanceCreatedEvent(configuration.InstanceGuid);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auditLogRepository.AddInstanceEditedEvent(configuration.InstanceGuid);
|
auditLogWriter.AddInstanceEditedEvent(configuration.InstanceGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.Ctx.SaveChangesAsync(cancellationToken);
|
await db.Ctx.SaveChangesAsync(cancellationToken);
|
||||||
@ -188,13 +188,13 @@ sealed class InstanceManager {
|
|||||||
public async Task<InstanceActionResult<StopInstanceResult>> StopInstance(Guid auditLogUserGuid, Guid instanceGuid, MinecraftStopStrategy stopStrategy) {
|
public async Task<InstanceActionResult<StopInstanceResult>> StopInstance(Guid auditLogUserGuid, Guid instanceGuid, MinecraftStopStrategy stopStrategy) {
|
||||||
var result = await SendInstanceActionMessage<StopInstanceMessage, StopInstanceResult>(instanceGuid, new StopInstanceMessage(instanceGuid, stopStrategy));
|
var result = await SendInstanceActionMessage<StopInstanceMessage, StopInstanceResult>(instanceGuid, new StopInstanceMessage(instanceGuid, stopStrategy));
|
||||||
if (result.Is(StopInstanceResult.StopInitiated)) {
|
if (result.Is(StopInstanceResult.StopInitiated)) {
|
||||||
await HandleInstanceManuallyLaunchedOrStopped(instanceGuid, false, auditLogUserGuid, auditLogRepository => auditLogRepository.AddInstanceLaunchedEvent(instanceGuid));
|
await HandleInstanceManuallyLaunchedOrStopped(instanceGuid, false, auditLogUserGuid, auditLogRepository => auditLogRepository.AddInstanceStoppedEvent(instanceGuid, stopStrategy.Seconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleInstanceManuallyLaunchedOrStopped(Guid instanceGuid, bool wasLaunched, Guid auditLogUserGuid, Action<AuditLogRepository> addAuditEvent) {
|
private async Task HandleInstanceManuallyLaunchedOrStopped(Guid instanceGuid, bool wasLaunched, Guid auditLogUserGuid, Action<AuditLogRepositoryWriter> addAuditEvent) {
|
||||||
await modifyInstancesSemaphore.WaitAsync(cancellationToken);
|
await modifyInstancesSemaphore.WaitAsync(cancellationToken);
|
||||||
try {
|
try {
|
||||||
instances.ByGuid.TryReplace(instanceGuid, instance => instance with { LaunchAutomatically = wasLaunched });
|
instances.ByGuid.TryReplace(instanceGuid, instance => instance with { LaunchAutomatically = wasLaunched });
|
||||||
@ -203,10 +203,7 @@ sealed class InstanceManager {
|
|||||||
var entity = await db.Ctx.Instances.FindAsync(new object[] { instanceGuid }, cancellationToken);
|
var entity = await db.Ctx.Instances.FindAsync(new object[] { instanceGuid }, cancellationToken);
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
entity.LaunchAutomatically = wasLaunched;
|
entity.LaunchAutomatically = wasLaunched;
|
||||||
|
addAuditEvent(new AuditLogRepositoryWriter(db, auditLogUserGuid));
|
||||||
var auditLogRepository = new AuditLogRepository(db, auditLogUserGuid);
|
|
||||||
addAuditEvent(auditLogRepository);
|
|
||||||
|
|
||||||
await db.Ctx.SaveChangesAsync(cancellationToken);
|
await db.Ctx.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -214,10 +211,12 @@ sealed class InstanceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<InstanceActionResult<SendCommandToInstanceResult>> SendCommand(Guid instanceGuid, string command) {
|
public async Task<InstanceActionResult<SendCommandToInstanceResult>> SendCommand(Guid auditLogUserId, Guid instanceGuid, string command) {
|
||||||
var result = await SendInstanceActionMessage<SendCommandToInstanceMessage, SendCommandToInstanceResult>(instanceGuid, new SendCommandToInstanceMessage(instanceGuid, command));
|
var result = await SendInstanceActionMessage<SendCommandToInstanceMessage, SendCommandToInstanceResult>(instanceGuid, new SendCommandToInstanceMessage(instanceGuid, command));
|
||||||
if (result.Is(SendCommandToInstanceResult.Success)) {
|
if (result.Is(SendCommandToInstanceResult.Success)) {
|
||||||
// TODO audit log
|
await using var db = dbProvider.Lazy();
|
||||||
|
new AuditLogRepositoryWriter(db, auditLogUserId).AddInstanceCommandExecutedEvent(instanceGuid, command);
|
||||||
|
await db.Ctx.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -4,6 +4,7 @@ using Phantom.Common.Data.Java;
|
|||||||
using Phantom.Common.Data.Minecraft;
|
using Phantom.Common.Data.Minecraft;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Data.Web.Agent;
|
using Phantom.Common.Data.Web.Agent;
|
||||||
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
using Phantom.Common.Data.Web.Instance;
|
using Phantom.Common.Data.Web.Instance;
|
||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Common.Logging;
|
using Phantom.Common.Logging;
|
||||||
@ -25,21 +26,34 @@ namespace Phantom.Controller.Services.Rpc;
|
|||||||
public sealed class WebMessageListener : IMessageToControllerListener {
|
public sealed class WebMessageListener : IMessageToControllerListener {
|
||||||
private static readonly ILogger Logger = PhantomLogger.Create<WebMessageListener>();
|
private static readonly ILogger Logger = PhantomLogger.Create<WebMessageListener>();
|
||||||
|
|
||||||
private readonly RpcConnectionToClient<IMessageToWebListener> connection;
|
private readonly RpcConnectionToClient<IMessageToWebListener> connection; // TODO use single queue
|
||||||
private readonly AuthToken authToken;
|
private readonly AuthToken authToken;
|
||||||
private readonly UserManager userManager;
|
private readonly UserManager userManager;
|
||||||
private readonly UserLoginManager userLoginManager;
|
private readonly UserLoginManager userLoginManager;
|
||||||
|
private readonly AuditLogManager auditLogManager;
|
||||||
private readonly AgentManager agentManager;
|
private readonly AgentManager agentManager;
|
||||||
private readonly AgentJavaRuntimesManager agentJavaRuntimesManager;
|
private readonly AgentJavaRuntimesManager agentJavaRuntimesManager;
|
||||||
private readonly InstanceManager instanceManager;
|
private readonly InstanceManager instanceManager;
|
||||||
private readonly MinecraftVersions minecraftVersions;
|
private readonly MinecraftVersions minecraftVersions;
|
||||||
private readonly TaskManager taskManager;
|
private readonly TaskManager taskManager;
|
||||||
|
|
||||||
internal WebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection, AuthToken authToken, UserManager userManager, UserLoginManager userLoginManager, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, MinecraftVersions minecraftVersions, TaskManager taskManager) {
|
internal WebMessageListener(
|
||||||
|
RpcConnectionToClient<IMessageToWebListener> connection,
|
||||||
|
AuthToken authToken,
|
||||||
|
UserManager userManager,
|
||||||
|
UserLoginManager userLoginManager,
|
||||||
|
AuditLogManager auditLogManager,
|
||||||
|
AgentManager agentManager,
|
||||||
|
AgentJavaRuntimesManager agentJavaRuntimesManager,
|
||||||
|
InstanceManager instanceManager,
|
||||||
|
MinecraftVersions minecraftVersions,
|
||||||
|
TaskManager taskManager
|
||||||
|
) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
this.userManager = userManager;
|
this.userManager = userManager;
|
||||||
this.userLoginManager = userLoginManager;
|
this.userLoginManager = userLoginManager;
|
||||||
|
this.auditLogManager = auditLogManager;
|
||||||
this.agentManager = agentManager;
|
this.agentManager = agentManager;
|
||||||
this.agentJavaRuntimesManager = agentJavaRuntimesManager;
|
this.agentJavaRuntimesManager = agentJavaRuntimesManager;
|
||||||
this.instanceManager = instanceManager;
|
this.instanceManager = instanceManager;
|
||||||
@ -61,6 +75,7 @@ public sealed class WebMessageListener : IMessageToControllerListener {
|
|||||||
|
|
||||||
agentManager.AgentsChanged.Subscribe(this, HandleAgentsChanged);
|
agentManager.AgentsChanged.Subscribe(this, HandleAgentsChanged);
|
||||||
instanceManager.InstancesChanged.Subscribe(this, HandleInstancesChanged);
|
instanceManager.InstancesChanged.Subscribe(this, HandleInstancesChanged);
|
||||||
|
// TODO unsubscribe on closed
|
||||||
return NoReply.Instance;
|
return NoReply.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +101,14 @@ public sealed class WebMessageListener : IMessageToControllerListener {
|
|||||||
return instanceManager.LaunchInstance(message.LoggedInUserGuid, message.InstanceGuid);
|
return instanceManager.LaunchInstance(message.LoggedInUserGuid, message.InstanceGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<InstanceActionResult<StopInstanceResult>> HandleStopInstance(StopInstanceMessage message) {
|
||||||
|
return instanceManager.StopInstance(message.LoggedInUserGuid, message.InstanceGuid, message.StopStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<InstanceActionResult<SendCommandToInstanceResult>> HandleSendCommandToInstance(SendCommandToInstanceMessage message) {
|
||||||
|
return instanceManager.SendCommand(message.LoggedInUserGuid, message.InstanceGuid, message.Command);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message) {
|
public Task<ImmutableArray<MinecraftVersion>> HandleGetMinecraftVersions(GetMinecraftVersionsMessage message) {
|
||||||
return minecraftVersions.GetVersions(CancellationToken.None);
|
return minecraftVersions.GetVersions(CancellationToken.None);
|
||||||
}
|
}
|
||||||
@ -94,6 +117,14 @@ public sealed class WebMessageListener : IMessageToControllerListener {
|
|||||||
return Task.FromResult(agentJavaRuntimesManager.All);
|
return Task.FromResult(agentJavaRuntimesManager.All);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<ImmutableArray<UserInfo>> HandleGetUsers(GetUsersMessage message) {
|
||||||
|
return userManager.GetAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ImmutableArray<AuditLogItem>> HandleGetAuditLog(GetAuditLogMessage message) {
|
||||||
|
return auditLogManager.GetMostRecentItems(message.Count);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<LogInSuccess?> HandleLogIn(LogInMessage message) {
|
public Task<LogInSuccess?> HandleLogIn(LogInMessage message) {
|
||||||
return userLoginManager.LogIn(message.Username, message.Password);
|
return userLoginManager.LogIn(message.Username, message.Password);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
|
using Phantom.Controller.Database;
|
||||||
|
using Phantom.Controller.Database.Repositories;
|
||||||
|
|
||||||
|
namespace Phantom.Controller.Services.Users;
|
||||||
|
|
||||||
|
sealed class AuditLogManager {
|
||||||
|
private readonly IDbContextProvider dbProvider;
|
||||||
|
|
||||||
|
public AuditLogManager(IDbContextProvider dbProvider) {
|
||||||
|
this.dbProvider = dbProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ImmutableArray<AuditLogItem>> GetMostRecentItems(int count) {
|
||||||
|
await using var db = dbProvider.Lazy();
|
||||||
|
return await new AuditLogRepositoryReader(db).GetMostRecentItems(count, CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,20 @@ sealed class PermissionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PermissionSet> FetchPermissionsForAllUsers(Guid userId) {
|
||||||
|
await using var ctx = dbProvider.Eager();
|
||||||
|
|
||||||
|
var userPermissions = ctx.UserPermissions
|
||||||
|
.Where(up => up.UserGuid == userId)
|
||||||
|
.Select(static up => up.PermissionId);
|
||||||
|
|
||||||
|
var rolePermissions = ctx.UserRoles
|
||||||
|
.Where(ur => ur.UserGuid == userId)
|
||||||
|
.Join(ctx.RolePermissions, static ur => ur.RoleGuid, static rp => rp.RoleGuid, static (ur, rp) => rp.PermissionId);
|
||||||
|
|
||||||
|
return new PermissionSet(await userPermissions.Union(rolePermissions).AsAsyncEnumerable().ToImmutableSetAsync());
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<PermissionSet> FetchPermissionsForUserId(Guid userId) {
|
public async Task<PermissionSet> FetchPermissionsForUserId(Guid userId) {
|
||||||
await using var ctx = dbProvider.Eager();
|
await using var ctx = dbProvider.Eager();
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Phantom.Common.Data.Web.Users;
|
using System.Collections.Immutable;
|
||||||
|
using Phantom.Common.Data.Web.Users;
|
||||||
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
|
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
|
||||||
using Phantom.Common.Logging;
|
using Phantom.Common.Logging;
|
||||||
using Phantom.Controller.Database;
|
using Phantom.Controller.Database;
|
||||||
@ -17,24 +18,32 @@ sealed class UserManager {
|
|||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ImmutableArray<UserInfo>> GetAll() {
|
||||||
|
await using var db = dbProvider.Lazy();
|
||||||
|
var userRepository = new UserRepository(db);
|
||||||
|
|
||||||
|
var allUsers = await userRepository.GetAll();
|
||||||
|
return allUsers.Select(static user => user.ToUserInfo()).ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<UserEntity?> GetAuthenticated(string username, string password) {
|
public async Task<UserEntity?> GetAuthenticated(string username, string password) {
|
||||||
await using var db = dbProvider.Lazy();
|
await using var db = dbProvider.Lazy();
|
||||||
var repository = new UserRepository(db);
|
var userRepository = new UserRepository(db);
|
||||||
|
|
||||||
var user = await repository.GetByName(username);
|
var user = await userRepository.GetByName(username);
|
||||||
return user != null && UserPasswords.Verify(password, user.PasswordHash) ? user : null;
|
return user != null && UserPasswords.Verify(password, user.PasswordHash) ? user : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministrator(string username, string password) {
|
public async Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministrator(string username, string password) {
|
||||||
await using var db = dbProvider.Lazy();
|
await using var db = dbProvider.Lazy();
|
||||||
var repository = new UserRepository(db);
|
var userRepository = new UserRepository(db);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bool wasCreated;
|
bool wasCreated;
|
||||||
|
|
||||||
var user = await repository.GetByName(username);
|
var user = await userRepository.GetByName(username);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
var result = await repository.CreateUser(username, password);
|
var result = await userRepository.CreateUser(username, password);
|
||||||
if (result) {
|
if (result) {
|
||||||
user = result.Value;
|
user = result.Value;
|
||||||
wasCreated = true;
|
wasCreated = true;
|
||||||
@ -44,7 +53,7 @@ sealed class UserManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var result = repository.SetUserPassword(user, password);
|
var result = userRepository.SetUserPassword(user, password);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return new UpdatingFailed(result.Error);
|
return new UpdatingFailed(result.Error);
|
||||||
}
|
}
|
||||||
@ -77,15 +86,15 @@ sealed class UserManager {
|
|||||||
|
|
||||||
public async Task<DeleteUserResult> DeleteByGuid(Guid guid) {
|
public async Task<DeleteUserResult> DeleteByGuid(Guid guid) {
|
||||||
await using var db = dbProvider.Lazy();
|
await using var db = dbProvider.Lazy();
|
||||||
var repository = new UserRepository(db);
|
var userRepository = new UserRepository(db);
|
||||||
|
|
||||||
var user = await repository.GetByGuid(guid);
|
var user = await userRepository.GetByGuid(guid);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return DeleteUserResult.NotFound;
|
return DeleteUserResult.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
repository.DeleteUser(user);
|
userRepository.DeleteUser(user);
|
||||||
await db.Ctx.SaveChangesAsync();
|
await db.Ctx.SaveChangesAsync();
|
||||||
|
|
||||||
Logger.Information("Deleted user \"{Username}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
Logger.Information("Deleted user \"{Username}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Phantom.Common.Data.Instance;
|
using Phantom.Common.Data.Instance;
|
||||||
|
using Phantom.Common.Data.Minecraft;
|
||||||
using Phantom.Common.Data.Replies;
|
using Phantom.Common.Data.Replies;
|
||||||
using Phantom.Common.Data.Web.Instance;
|
using Phantom.Common.Data.Web.Instance;
|
||||||
using Phantom.Common.Logging;
|
using Phantom.Common.Logging;
|
||||||
@ -25,17 +26,31 @@ public sealed class InstanceManager {
|
|||||||
instances.SetTo(newInstances.ToImmutableDictionary(static instance => instance.Configuration.InstanceGuid));
|
instances.SetTo(newInstances.ToImmutableDictionary(static instance => instance.Configuration.InstanceGuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InstanceDictionary GetAll() {
|
||||||
|
return instances.Value;
|
||||||
|
}
|
||||||
|
|
||||||
public Instance? GetByGuid(Guid instanceGuid) {
|
public Instance? GetByGuid(Guid instanceGuid) {
|
||||||
return instances.Value.GetValueOrDefault(instanceGuid);
|
return instances.Value.GetValueOrDefault(instanceGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<InstanceActionResult<CreateOrUpdateInstanceResult>> CreateOrUpdateInstance(Guid loggedInUserGuid, InstanceConfiguration configuration) {
|
public Task<InstanceActionResult<CreateOrUpdateInstanceResult>> CreateOrUpdateInstance(Guid loggedInUserGuid, InstanceConfiguration configuration, CancellationToken cancellationToken) {
|
||||||
var message = new CreateOrUpdateInstanceMessage(loggedInUserGuid, configuration);
|
var message = new CreateOrUpdateInstanceMessage(loggedInUserGuid, configuration);
|
||||||
return controllerConnection.Send<CreateOrUpdateInstanceMessage, InstanceActionResult<CreateOrUpdateInstanceResult>>(message, TimeSpan.FromSeconds(30));
|
return controllerConnection.Send<CreateOrUpdateInstanceMessage, InstanceActionResult<CreateOrUpdateInstanceResult>>(message, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<InstanceActionResult<LaunchInstanceResult>> LaunchInstance(Guid loggedInUserGuid, Guid instanceGuid) {
|
public Task<InstanceActionResult<LaunchInstanceResult>> LaunchInstance(Guid loggedInUserGuid, Guid instanceGuid, CancellationToken cancellationToken) {
|
||||||
var message = new LaunchInstanceMessage(loggedInUserGuid, instanceGuid);
|
var message = new LaunchInstanceMessage(loggedInUserGuid, instanceGuid);
|
||||||
return controllerConnection.Send<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(message, TimeSpan.FromSeconds(30));
|
return controllerConnection.Send<LaunchInstanceMessage, InstanceActionResult<LaunchInstanceResult>>(message, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<InstanceActionResult<StopInstanceResult>> StopInstance(Guid loggedInUserGuid, Guid instanceGuid, MinecraftStopStrategy stopStrategy, CancellationToken cancellationToken) {
|
||||||
|
var message = new StopInstanceMessage(loggedInUserGuid, instanceGuid, stopStrategy);
|
||||||
|
return controllerConnection.Send<StopInstanceMessage, InstanceActionResult<StopInstanceResult>>(message, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<InstanceActionResult<SendCommandToInstanceResult>> SendCommandToInstance(Guid loggedInUserGuid, Guid instanceGuid, string command, CancellationToken cancellationToken) {
|
||||||
|
var message = new SendCommandToInstanceMessage(loggedInUserGuid, instanceGuid, command);
|
||||||
|
return controllerConnection.Send<SendCommandToInstanceMessage, InstanceActionResult<SendCommandToInstanceResult>>(message, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using Phantom.Web.Services.Authentication;
|
|||||||
using Phantom.Web.Services.Authorization;
|
using Phantom.Web.Services.Authorization;
|
||||||
using Phantom.Web.Services.Instances;
|
using Phantom.Web.Services.Instances;
|
||||||
using Phantom.Web.Services.Rpc;
|
using Phantom.Web.Services.Rpc;
|
||||||
|
using Phantom.Web.Services.Users;
|
||||||
|
|
||||||
namespace Phantom.Web.Services;
|
namespace Phantom.Web.Services;
|
||||||
|
|
||||||
@ -18,9 +19,11 @@ public static class PhantomWebServices {
|
|||||||
services.AddSingleton<InstanceManager>();
|
services.AddSingleton<InstanceManager>();
|
||||||
services.AddSingleton<PermissionManager>();
|
services.AddSingleton<PermissionManager>();
|
||||||
|
|
||||||
|
services.AddSingleton<UserManager>();
|
||||||
services.AddSingleton<UserSessionManager>();
|
services.AddSingleton<UserSessionManager>();
|
||||||
services.AddScoped<UserSessionBrowserStorage>();
|
services.AddSingleton<AuditLogManager>();
|
||||||
services.AddScoped<UserLoginManager>();
|
services.AddScoped<UserLoginManager>();
|
||||||
|
services.AddScoped<UserSessionBrowserStorage>();
|
||||||
|
|
||||||
services.AddScoped<CustomAuthenticationStateProvider>();
|
services.AddScoped<CustomAuthenticationStateProvider>();
|
||||||
services.AddScoped<AuthenticationStateProvider>(static services => services.GetRequiredService<CustomAuthenticationStateProvider>());
|
services.AddScoped<AuthenticationStateProvider>(static services => services.GetRequiredService<CustomAuthenticationStateProvider>());
|
||||||
|
@ -17,4 +17,8 @@ public sealed class ControllerConnection {
|
|||||||
public Task<TReply> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken = default) where TMessage : IMessageToController<TReply> {
|
public Task<TReply> Send<TMessage, TReply>(TMessage message, TimeSpan waitForReplyTime, CancellationToken waitForReplyCancellationToken = default) where TMessage : IMessageToController<TReply> {
|
||||||
return connection.Send<TMessage, TReply>(message, waitForReplyTime, waitForReplyCancellationToken);
|
return connection.Send<TMessage, TReply>(message, waitForReplyTime, waitForReplyCancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<TReply> Send<TMessage, TReply>(TMessage message, CancellationToken waitForReplyCancellationToken) where TMessage : IMessageToController<TReply> {
|
||||||
|
return connection.Send<TMessage, TReply>(message, Timeout.InfiniteTimeSpan, waitForReplyCancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
19
Web/Phantom.Web.Services/Users/AuditLogManager.cs
Normal file
19
Web/Phantom.Web.Services/Users/AuditLogManager.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using Phantom.Common.Data.Web.AuditLog;
|
||||||
|
using Phantom.Common.Messages.Web.ToController;
|
||||||
|
using Phantom.Web.Services.Rpc;
|
||||||
|
|
||||||
|
namespace Phantom.Web.Services.Users;
|
||||||
|
|
||||||
|
public sealed class AuditLogManager {
|
||||||
|
private readonly ControllerConnection controllerConnection;
|
||||||
|
|
||||||
|
public AuditLogManager(ControllerConnection controllerConnection) {
|
||||||
|
this.controllerConnection = controllerConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ImmutableArray<AuditLogItem>> GetMostRecentItems(int count, CancellationToken cancellationToken) {
|
||||||
|
var message = new GetAuditLogMessage(count);
|
||||||
|
return controllerConnection.Send<GetAuditLogMessage, ImmutableArray<AuditLogItem>>(message, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,18 @@
|
|||||||
namespace Phantom.Web.Services.Users;
|
using System.Collections.Immutable;
|
||||||
|
using Phantom.Common.Data.Web.Users;
|
||||||
|
using Phantom.Common.Messages.Web.ToController;
|
||||||
|
using Phantom.Web.Services.Rpc;
|
||||||
|
|
||||||
|
namespace Phantom.Web.Services.Users;
|
||||||
|
|
||||||
public sealed class UserManager {
|
public sealed class UserManager {
|
||||||
|
private readonly ControllerConnection controllerConnection;
|
||||||
|
|
||||||
|
public UserManager(ControllerConnection controllerConnection) {
|
||||||
|
this.controllerConnection = controllerConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ImmutableArray<UserInfo>> GetAll(CancellationToken cancellationToken) {
|
||||||
|
return controllerConnection.Send<GetUsersMessage, ImmutableArray<UserInfo>>(new GetUsersMessage(), cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ using UserInfo = Phantom.Web.Services.Authentication.UserInfo;
|
|||||||
|
|
||||||
namespace Phantom.Web.Base;
|
namespace Phantom.Web.Base;
|
||||||
|
|
||||||
public abstract class PhantomComponent : ComponentBase {
|
public abstract class PhantomComponent : ComponentBase, IDisposable {
|
||||||
private static readonly ILogger Logger = PhantomLogger.Create<PhantomComponent>();
|
private static readonly ILogger Logger = PhantomLogger.Create<PhantomComponent>();
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
@ -17,6 +17,10 @@ public abstract class PhantomComponent : ComponentBase {
|
|||||||
[Inject]
|
[Inject]
|
||||||
public PermissionManager PermissionManager { get; set; } = null!;
|
public PermissionManager PermissionManager { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource = new ();
|
||||||
|
|
||||||
|
public CancellationToken CancellationToken => cancellationTokenSource.Token;
|
||||||
|
|
||||||
public async Task<Guid?> GetUserGuid() {
|
public async Task<Guid?> GetUserGuid() {
|
||||||
var authenticationState = await AuthenticationStateTask;
|
var authenticationState = await AuthenticationStateTask;
|
||||||
return UserInfo.TryGetGuid(authenticationState.User);
|
return UserInfo.TryGetGuid(authenticationState.User);
|
||||||
@ -30,4 +34,13 @@ public abstract class PhantomComponent : ComponentBase {
|
|||||||
protected void InvokeAsyncChecked(Func<Task> task) {
|
protected void InvokeAsyncChecked(Func<Task> task) {
|
||||||
InvokeAsync(task).ContinueWith(static t => Logger.Error(t.Exception, "Caught exception in async task."), TaskContinuationOptions.OnlyOnFaulted);
|
InvokeAsync(task).ContinueWith(static t => Logger.Error(t.Exception, "Caught exception in async task."), TaskContinuationOptions.OnlyOnFaulted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
cancellationTokenSource.Dispose();
|
||||||
|
OnDisposed();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnDisposed() {}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
@using Phantom.Common.Data.Web.Agent
|
@using Phantom.Common.Data.Web.Agent
|
||||||
@using Phantom.Utils.Collections
|
@using Phantom.Utils.Collections
|
||||||
@using Phantom.Web.Services.Agents
|
@using Phantom.Web.Services.Agents
|
||||||
@implements IDisposable
|
@inherits PhantomComponent
|
||||||
@inject AgentManager AgentManager
|
@inject AgentManager AgentManager
|
||||||
|
|
||||||
<h1>Agents</h1>
|
<h1>Agents</h1>
|
||||||
@ -85,7 +85,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDisposable.Dispose() {
|
protected override void OnDisposed() {
|
||||||
AgentManager.AgentsChanged.Unsubscribe(this);
|
AgentManager.AgentsChanged.Unsubscribe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
@page "/audit"
|
@page "/audit"
|
||||||
@attribute [Authorize(Permission.ViewAuditPolicy)]
|
@attribute [Authorize(Permission.ViewAuditPolicy)]
|
||||||
|
@using System.Collections.Immutable
|
||||||
@using Phantom.Common.Data.Web.AuditLog
|
@using Phantom.Common.Data.Web.AuditLog
|
||||||
@using Phantom.Common.Data.Web.Users
|
@using Phantom.Common.Data.Web.Users
|
||||||
@using System.Collections.Immutable
|
@using Phantom.Web.Services.Users
|
||||||
@using Phantom.Web.Services.Instances
|
@using Phantom.Web.Services.Instances
|
||||||
@implements IDisposable
|
@inherits PhantomComponent
|
||||||
@inject AuditLog AuditLog
|
@inject AuditLogManager AuditLogManager
|
||||||
@inject InstanceManager InstanceManager
|
@inject InstanceManager InstanceManager
|
||||||
@inject UserManager UserManager
|
@inject UserManager UserManager
|
||||||
|
|
||||||
@ -42,7 +43,7 @@
|
|||||||
<code class="text-uppercase">@(logItem.SubjectId ?? "-")</code>
|
<code class="text-uppercase">@(logItem.SubjectId ?? "-")</code>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<code>@logItem.Data?.RootElement.ToString()</code>
|
<code>@logItem.JsonData</code>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@ -52,8 +53,8 @@
|
|||||||
@code {
|
@code {
|
||||||
|
|
||||||
private CancellationTokenSource? initializationCancellationTokenSource;
|
private CancellationTokenSource? initializationCancellationTokenSource;
|
||||||
private AuditLogItem[] logItems = Array.Empty<AuditLogItem>();
|
private ImmutableArray<AuditLogItem> logItems = ImmutableArray<AuditLogItem>.Empty;
|
||||||
private Dictionary<Guid, string>? userNamesByGuid;
|
private ImmutableDictionary<Guid, string>? userNamesByGuid;
|
||||||
private ImmutableDictionary<Guid, string> instanceNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
private ImmutableDictionary<Guid, string> instanceNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
@ -61,9 +62,9 @@
|
|||||||
var cancellationToken = initializationCancellationTokenSource.Token;
|
var cancellationToken = initializationCancellationTokenSource.Token;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logItems = await AuditLog.GetItems(50, cancellationToken);
|
logItems = await AuditLogManager.GetMostRecentItems(50, cancellationToken);
|
||||||
userNamesByGuid = await UserManager.GetAllByGuid(static user => user.Name, cancellationToken);
|
userNamesByGuid = (await UserManager.GetAll(cancellationToken)).ToImmutableDictionary(static user => user.Guid, static user => user.Name);
|
||||||
instanceNamesByGuid = InstanceManager.GetInstanceNames();
|
instanceNamesByGuid = InstanceManager.GetAll().Values.ToImmutableDictionary(static instance => instance.Configuration.InstanceGuid, static instance => instance.Configuration.InstanceName);
|
||||||
} finally {
|
} finally {
|
||||||
initializationCancellationTokenSource.Dispose();
|
initializationCancellationTokenSource.Dispose();
|
||||||
}
|
}
|
||||||
@ -77,7 +78,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
protected override void OnDisposed() {
|
||||||
try {
|
try {
|
||||||
initializationCancellationTokenSource?.Cancel();
|
initializationCancellationTokenSource?.Cancel();
|
||||||
} catch (ObjectDisposedException) {}
|
} catch (ObjectDisposedException) {}
|
||||||
|
@ -1,95 +1,95 @@
|
|||||||
@page "/events"
|
@* @page "/events" *@
|
||||||
@attribute [Authorize(Permission.ViewEventsPolicy)]
|
@* @attribute [Authorize(Permission.ViewEventsPolicy)] *@
|
||||||
@using Phantom.Common.Data.Web.EventLog
|
@* @inherits PhantomComponent *@
|
||||||
@using Phantom.Common.Data.Web.Users
|
@* @using Phantom.Common.Data.Web.EventLog *@
|
||||||
@using System.Collections.Immutable
|
@* @using Phantom.Common.Data.Web.Users *@
|
||||||
@using System.Diagnostics
|
@* @using System.Collections.Immutable *@
|
||||||
@using Phantom.Web.Services.Instances
|
@* @using System.Diagnostics *@
|
||||||
@implements IDisposable
|
@* @using Phantom.Web.Services.Instances *@
|
||||||
@inject AgentManager AgentManager
|
@* @inject AgentManager AgentManager *@
|
||||||
@inject EventLog EventLog
|
@* @inject EventLog EventLog *@
|
||||||
@inject InstanceManager InstanceManager
|
@* @inject InstanceManager InstanceManager *@
|
||||||
|
@* *@
|
||||||
<h1>Event Log</h1>
|
@* <h1>Event Log</h1> *@
|
||||||
|
@* *@
|
||||||
<table class="table">
|
@* <table class="table"> *@
|
||||||
<thead>
|
@* <thead> *@
|
||||||
<tr>
|
@* <tr> *@
|
||||||
<Column Width="165px" Class="text-end">Time</Column>
|
@* <Column Width="165px" Class="text-end">Time</Column> *@
|
||||||
<Column Width="320px; 20%">Agent</Column>
|
@* <Column Width="320px; 20%">Agent</Column> *@
|
||||||
<Column Width="160px">Event Type</Column>
|
@* <Column Width="160px">Event Type</Column> *@
|
||||||
<Column Width="320px; 20%">Subject</Column>
|
@* <Column Width="320px; 20%">Subject</Column> *@
|
||||||
<Column Width="100px; 60%">Data</Column>
|
@* <Column Width="100px; 60%">Data</Column> *@
|
||||||
</tr>
|
@* </tr> *@
|
||||||
</thead>
|
@* </thead> *@
|
||||||
<tbody>
|
@* <tbody> *@
|
||||||
@foreach (var logItem in logItems) {
|
@* @foreach (var logItem in logItems) { *@
|
||||||
DateTimeOffset time = logItem.UtcTime.ToLocalTime();
|
@* DateTimeOffset time = logItem.UtcTime.ToLocalTime(); *@
|
||||||
<tr>
|
@* <tr> *@
|
||||||
<td class="text-end">
|
@* <td class="text-end"> *@
|
||||||
<time datetime="@time.ToString("o")">@time.ToString()</time>
|
@* <time datetime="@time.ToString("o")">@time.ToString()</time> *@
|
||||||
</td>
|
@* </td> *@
|
||||||
<td>
|
@* <td> *@
|
||||||
@if (logItem.AgentGuid is {} agentGuid) {
|
@* @if (logItem.AgentGuid is {} agentGuid) { *@
|
||||||
@(GetAgentName(agentGuid))
|
@* @(GetAgentName(agentGuid)) *@
|
||||||
<br>
|
@* <br> *@
|
||||||
<code class="text-uppercase">@agentGuid</code>
|
@* <code class="text-uppercase">@agentGuid</code> *@
|
||||||
}
|
@* } *@
|
||||||
else {
|
@* else { *@
|
||||||
<text>-</text>
|
@* <text>-</text> *@
|
||||||
}
|
@* } *@
|
||||||
</td>
|
@* </td> *@
|
||||||
<td>@logItem.EventType.ToNiceString()</td>
|
@* <td>@logItem.EventType.ToNiceString()</td> *@
|
||||||
<td>
|
@* <td> *@
|
||||||
@if (GetSubjectName(logItem.SubjectType, logItem.SubjectId) is {} subjectName) {
|
@* @if (GetSubjectName(logItem.SubjectType, logItem.SubjectId) is {} subjectName) { *@
|
||||||
@subjectName
|
@* @subjectName *@
|
||||||
<br>
|
@* <br> *@
|
||||||
}
|
@* } *@
|
||||||
<code class="text-uppercase">@logItem.SubjectId</code>
|
@* <code class="text-uppercase">@logItem.SubjectId</code> *@
|
||||||
</td>
|
@* </td> *@
|
||||||
<td>
|
@* <td> *@
|
||||||
<code>@logItem.Data?.RootElement.ToString()</code>
|
@* <code>@logItem.Data?.RootElement.ToString()</code> *@
|
||||||
</td>
|
@* </td> *@
|
||||||
</tr>
|
@* </tr> *@
|
||||||
}
|
@* } *@
|
||||||
</tbody>
|
@* </tbody> *@
|
||||||
</table>
|
@* </table> *@
|
||||||
|
@* *@
|
||||||
@code {
|
@* @code { *@
|
||||||
|
@* *@
|
||||||
private CancellationTokenSource? initializationCancellationTokenSource;
|
@* private CancellationTokenSource? initializationCancellationTokenSource; *@
|
||||||
private EventLogItem[] logItems = Array.Empty<EventLogItem>();
|
@* private EventLogItem[] logItems = Array.Empty<EventLogItem>(); *@
|
||||||
private ImmutableDictionary<Guid, string> agentNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
@* private ImmutableDictionary<Guid, string> agentNamesByGuid = ImmutableDictionary<Guid, string>.Empty; *@
|
||||||
private ImmutableDictionary<Guid, string> instanceNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
@* private ImmutableDictionary<Guid, string> instanceNamesByGuid = ImmutableDictionary<Guid, string>.Empty; *@
|
||||||
|
@* *@
|
||||||
protected override async Task OnInitializedAsync() {
|
@* protected override async Task OnInitializedAsync() { *@
|
||||||
initializationCancellationTokenSource = new CancellationTokenSource();
|
@* initializationCancellationTokenSource = new CancellationTokenSource(); *@
|
||||||
var cancellationToken = initializationCancellationTokenSource.Token;
|
@* var cancellationToken = initializationCancellationTokenSource.Token; *@
|
||||||
|
@* *@
|
||||||
try {
|
@* try { *@
|
||||||
logItems = await EventLog.GetItems(50, cancellationToken);
|
@* logItems = await EventLog.GetItems(50, cancellationToken); *@
|
||||||
agentNamesByGuid = AgentManager.GetAgents().ToImmutableDictionary(static kvp => kvp.Key, static kvp => kvp.Value.Name);
|
@* agentNamesByGuid = AgentManager.GetAgents().ToImmutableDictionary(static kvp => kvp.Key, static kvp => kvp.Value.Name); *@
|
||||||
instanceNamesByGuid = InstanceManager.GetInstanceNames();
|
@* instanceNamesByGuid = InstanceManager.GetInstanceNames(); *@
|
||||||
} finally {
|
@* } finally { *@
|
||||||
initializationCancellationTokenSource.Dispose();
|
@* initializationCancellationTokenSource.Dispose(); *@
|
||||||
}
|
@* } *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private string GetAgentName(Guid agentGuid) {
|
@* private string GetAgentName(Guid agentGuid) { *@
|
||||||
return agentNamesByGuid.TryGetValue(agentGuid, out var name) ? name : "?";
|
@* return agentNamesByGuid.TryGetValue(agentGuid, out var name) ? name : "?"; *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private string? GetSubjectName(EventLogSubjectType type, string id) {
|
@* private string? GetSubjectName(EventLogSubjectType type, string id) { *@
|
||||||
return type switch {
|
@* return type switch { *@
|
||||||
EventLogSubjectType.Instance => instanceNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null,
|
@* EventLogSubjectType.Instance => instanceNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null, *@
|
||||||
_ => null
|
@* _ => null *@
|
||||||
};
|
@* }; *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
public void Dispose() {
|
@* public void Dispose() { *@
|
||||||
try {
|
@* try { *@
|
||||||
initializationCancellationTokenSource?.Cancel();
|
@* initializationCancellationTokenSource?.Cancel(); *@
|
||||||
} catch (ObjectDisposedException) {}
|
@* } catch (ObjectDisposedException) {} *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
}
|
@* } *@
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
@page "/instances/{InstanceGuid:guid}"
|
@page "/instances/{InstanceGuid:guid}"
|
||||||
@attribute [Authorize(Permission.ViewInstancesPolicy)]
|
@attribute [Authorize(Permission.ViewInstancesPolicy)]
|
||||||
@inherits PhantomComponent
|
|
||||||
@using Phantom.Common.Data.Instance
|
@using Phantom.Common.Data.Instance
|
||||||
@using Phantom.Common.Data.Replies
|
@using Phantom.Common.Data.Replies
|
||||||
@using Phantom.Common.Data.Web.Instance
|
@using Phantom.Common.Data.Web.Instance
|
||||||
@using Phantom.Common.Data.Web.Users
|
@using Phantom.Common.Data.Web.Users
|
||||||
@using Phantom.Web.Services.Instances
|
@using Phantom.Web.Services.Instances
|
||||||
@implements IDisposable
|
@inherits PhantomComponent
|
||||||
@inject InstanceManager InstanceManager
|
@inject InstanceManager InstanceManager
|
||||||
|
|
||||||
@if (Instance == null) {
|
@if (Instance == null) {
|
||||||
@ -78,7 +77,7 @@ else {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await InstanceManager.LaunchInstance(loggedInUserGuid.Value, InstanceGuid);
|
var result = await InstanceManager.LaunchInstance(loggedInUserGuid.Value, InstanceGuid, CancellationToken);
|
||||||
if (!result.Is(LaunchInstanceResult.LaunchInitiated)) {
|
if (!result.Is(LaunchInstanceResult.LaunchInitiated)) {
|
||||||
lastError = result.ToSentence(Messages.ToSentence);
|
lastError = result.ToSentence(Messages.ToSentence);
|
||||||
}
|
}
|
||||||
@ -87,7 +86,7 @@ else {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
protected override void OnDisposed() {
|
||||||
InstanceManager.InstancesChanged.Unsubscribe(this);
|
InstanceManager.InstancesChanged.Unsubscribe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
@using Phantom.Common.Data.Web.Users
|
@using Phantom.Common.Data.Web.Users
|
||||||
@using Phantom.Web.Services.Agents
|
@using Phantom.Web.Services.Agents
|
||||||
@using Phantom.Web.Services.Instances
|
@using Phantom.Web.Services.Instances
|
||||||
@implements IDisposable
|
@inherits PhantomComponent
|
||||||
@inject AgentManager AgentManager
|
@inject AgentManager AgentManager
|
||||||
@inject InstanceManager InstanceManager
|
@inject InstanceManager InstanceManager
|
||||||
|
|
||||||
@ -91,7 +91,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDisposable.Dispose() {
|
protected override void OnDisposed() {
|
||||||
AgentManager.AgentsChanged.Unsubscribe(this);
|
AgentManager.AgentsChanged.Unsubscribe(this);
|
||||||
InstanceManager.InstancesChanged.Unsubscribe(this);
|
InstanceManager.InstancesChanged.Unsubscribe(this);
|
||||||
}
|
}
|
||||||
|
@ -1,110 +1,110 @@
|
|||||||
@page "/users"
|
@* @page "/users" *@
|
||||||
@using Phantom.Common.Data.Web.Users
|
@* @using Phantom.Common.Data.Web.Users *@
|
||||||
@using System.Collections.Immutable
|
@* @using System.Collections.Immutable *@
|
||||||
@using Phantom.Web.Services.Authorization
|
@* @using Phantom.Web.Services.Authorization *@
|
||||||
@attribute [Authorize(Permission.ViewUsersPolicy)]
|
@* @attribute [Authorize(Permission.ViewUsersPolicy)] *@
|
||||||
@inject UserManager UserManager
|
@* @inject UserManager UserManager *@
|
||||||
@inject UserRoleManager UserRoleManager
|
@* @inject UserRoleManager UserRoleManager *@
|
||||||
@inject PermissionManager PermissionManager
|
@* @inject PermissionManager PermissionManager *@
|
||||||
|
@* *@
|
||||||
<h1>Users</h1>
|
@* <h1>Users</h1> *@
|
||||||
|
@* *@
|
||||||
<PermissionView Permission="Permission.EditUsers">
|
@* <PermissionView Permission="Permission.EditUsers"> *@
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-user">Add User...</button>
|
@* <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-user">Add User...</button> *@
|
||||||
</PermissionView>
|
@* </PermissionView> *@
|
||||||
|
@* *@
|
||||||
<AuthorizeView>
|
@* <AuthorizeView> *@
|
||||||
<Authorized>
|
@* <Authorized> *@
|
||||||
@{ var canEdit = PermissionManager.CheckPermission(context.User, Permission.EditUsers); }
|
@* @{ var canEdit = PermissionManager.CheckPermission(context.User, Permission.EditUsers); } *@
|
||||||
<table class="table align-middle">
|
@* <table class="table align-middle"> *@
|
||||||
<thead>
|
@* <thead> *@
|
||||||
<tr>
|
@* <tr> *@
|
||||||
<Column Width="320px">Identifier</Column>
|
@* <Column Width="320px">Identifier</Column> *@
|
||||||
<Column Width="125px; 40%">Username</Column>
|
@* <Column Width="125px; 40%">Username</Column> *@
|
||||||
<Column Width="125px; 60%">Roles</Column>
|
@* <Column Width="125px; 60%">Roles</Column> *@
|
||||||
@if (canEdit) {
|
@* @if (canEdit) { *@
|
||||||
<Column Width="175px">Actions</Column>
|
@* <Column Width="175px">Actions</Column> *@
|
||||||
}
|
@* } *@
|
||||||
</tr>
|
@* </tr> *@
|
||||||
</thead>
|
@* </thead> *@
|
||||||
<tbody>
|
@* <tbody> *@
|
||||||
@{ var myUserId = UserManager.GetAuthenticatedUserId(context.User); }
|
@* @{ var myUserId = UserManager.GetAuthenticatedUserId(context.User); } *@
|
||||||
@foreach (var user in allUsers) {
|
@* @foreach (var user in allUsers) { *@
|
||||||
var isMe = myUserId == user.Guid;
|
@* var isMe = myUserId == user.Guid; *@
|
||||||
<tr>
|
@* <tr> *@
|
||||||
<td>
|
@* <td> *@
|
||||||
<code class="text-uppercase">@user.Guid</code>
|
@* <code class="text-uppercase">@user.Guid</code> *@
|
||||||
</td>
|
@* </td> *@
|
||||||
@if (isMe) {
|
@* @if (isMe) { *@
|
||||||
<td class="fw-semibold">@user.Name</td>
|
@* <td class="fw-semibold">@user.Name</td> *@
|
||||||
}
|
@* } *@
|
||||||
else {
|
@* else { *@
|
||||||
<td>@user.Name</td>
|
@* <td>@user.Name</td> *@
|
||||||
}
|
@* } *@
|
||||||
<td>@(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?")</td>
|
@* <td>@(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?")</td> *@
|
||||||
@if (canEdit) {
|
@* @if (canEdit) { *@
|
||||||
<td>
|
@* <td> *@
|
||||||
@if (!isMe) {
|
@* @if (!isMe) { *@
|
||||||
<button class="btn btn-primary btn-sm" @onclick="() => userRolesDialog.Show(user)">Edit Roles</button>
|
@* <button class="btn btn-primary btn-sm" @onclick="() => userRolesDialog.Show(user)">Edit Roles</button> *@
|
||||||
<button class="btn btn-danger btn-sm" @onclick="() => userDeleteDialog.Show(user)">Delete...</button>
|
@* <button class="btn btn-danger btn-sm" @onclick="() => userDeleteDialog.Show(user)">Delete...</button> *@
|
||||||
}
|
@* } *@
|
||||||
</td>
|
@* </td> *@
|
||||||
}
|
@* } *@
|
||||||
</tr>
|
@* </tr> *@
|
||||||
}
|
@* } *@
|
||||||
</tbody>
|
@* </tbody> *@
|
||||||
</table>
|
@* </table> *@
|
||||||
</Authorized>
|
@* </Authorized> *@
|
||||||
</AuthorizeView>
|
@* </AuthorizeView> *@
|
||||||
|
@* *@
|
||||||
<PermissionView Permission="Permission.EditUsers">
|
@* <PermissionView Permission="Permission.EditUsers"> *@
|
||||||
<UserAddDialog ModalId="add-user" UserAdded="OnUserAdded" />
|
@* <UserAddDialog ModalId="add-user" UserAdded="OnUserAdded" /> *@
|
||||||
<UserRolesDialog @ref="userRolesDialog" ModalId="manage-user-roles" UserModified="OnUserRolesChanged" />
|
@* <UserRolesDialog @ref="userRolesDialog" ModalId="manage-user-roles" UserModified="OnUserRolesChanged" /> *@
|
||||||
<UserDeleteDialog @ref="userDeleteDialog" ModalId="delete-user" UserModified="OnUserDeleted" />
|
@* <UserDeleteDialog @ref="userDeleteDialog" ModalId="delete-user" UserModified="OnUserDeleted" /> *@
|
||||||
</PermissionView>
|
@* </PermissionView> *@
|
||||||
|
@* *@
|
||||||
@code {
|
@* @code { *@
|
||||||
|
@* *@
|
||||||
private ImmutableArray<UserInfo> allUsers = ImmutableArray<UserInfo>.Empty;
|
@* private ImmutableArray<UserInfo> allUsers = ImmutableArray<UserInfo>.Empty; *@
|
||||||
private readonly Dictionary<Guid, string> userGuidToRoleDescription = new();
|
@* private readonly Dictionary<Guid, string> userGuidToRoleDescription = new(); *@
|
||||||
|
@* *@
|
||||||
private UserRolesDialog userRolesDialog = null!;
|
@* private UserRolesDialog userRolesDialog = null!; *@
|
||||||
private UserDeleteDialog userDeleteDialog = null!;
|
@* private UserDeleteDialog userDeleteDialog = null!; *@
|
||||||
|
@* *@
|
||||||
protected override async Task OnInitializedAsync() {
|
@* protected override async Task OnInitializedAsync() { *@
|
||||||
var unsortedUsers = await UserManager.GetAll();
|
@* var unsortedUsers = await UserManager.GetAll(); *@
|
||||||
allUsers = unsortedUsers.Sort(static (a, b) => a.Name.CompareTo(b.Name));
|
@* allUsers = unsortedUsers.Sort(static (a, b) => a.Name.CompareTo(b.Name)); *@
|
||||||
|
@* *@
|
||||||
foreach (var (userGuid, roles) in await UserRoleManager.GetAllByUserGuid()) {
|
@* foreach (var (userGuid, roles) in await UserRoleManager.GetAllByUserGuid()) { *@
|
||||||
userGuidToRoleDescription[userGuid] = StringifyRoles(roles);
|
@* userGuidToRoleDescription[userGuid] = StringifyRoles(roles); *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
foreach (var user in allUsers) {
|
@* foreach (var user in allUsers) { *@
|
||||||
await RefreshUserRoles(user);
|
@* await RefreshUserRoles(user); *@
|
||||||
}
|
@* } *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private async Task RefreshUserRoles(UserInfo user) {
|
@* private async Task RefreshUserRoles(UserInfo user) { *@
|
||||||
var roles = await UserRoleManager.GetUserRoles(user);
|
@* var roles = await UserRoleManager.GetUserRoles(user); *@
|
||||||
userGuidToRoleDescription[user.Guid] = StringifyRoles(roles);
|
@* userGuidToRoleDescription[user.Guid] = StringifyRoles(roles); *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private static string StringifyRoles(ImmutableArray<RoleInfo> roles) {
|
@* private static string StringifyRoles(ImmutableArray<RoleInfo> roles) { *@
|
||||||
return roles.IsEmpty ? "-" : string.Join(", ", roles.Select(static role => role.Name));
|
@* return roles.IsEmpty ? "-" : string.Join(", ", roles.Select(static role => role.Name)); *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private Task OnUserAdded(UserInfo user) {
|
@* private Task OnUserAdded(UserInfo user) { *@
|
||||||
allUsers = allUsers.Add(user);
|
@* allUsers = allUsers.Add(user); *@
|
||||||
return RefreshUserRoles(user);
|
@* return RefreshUserRoles(user); *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private Task OnUserRolesChanged(UserInfo user) {
|
@* private Task OnUserRolesChanged(UserInfo user) { *@
|
||||||
return RefreshUserRoles(user);
|
@* return RefreshUserRoles(user); *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private void OnUserDeleted(UserInfo user) {
|
@* private void OnUserDeleted(UserInfo user) { *@
|
||||||
allUsers = allUsers.Remove(user);
|
@* allUsers = allUsers.Remove(user); *@
|
||||||
userGuidToRoleDescription.Remove(user.Guid);
|
@* userGuidToRoleDescription.Remove(user.Guid); *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
}
|
@* } *@
|
||||||
|
@ -338,7 +338,7 @@
|
|||||||
JvmArgumentsHelper.Split(form.JvmArguments)
|
JvmArgumentsHelper.Split(form.JvmArguments)
|
||||||
);
|
);
|
||||||
|
|
||||||
var result = await InstanceManager.CreateOrUpdateInstance(loggedInUserGuid.Value, instance);
|
var result = await InstanceManager.CreateOrUpdateInstance(loggedInUserGuid.Value, instance, CancellationToken);
|
||||||
if (result.Is(CreateOrUpdateInstanceResult.Success)) {
|
if (result.Is(CreateOrUpdateInstanceResult.Success)) {
|
||||||
await Nav.NavigateTo("instances/" + instance.InstanceGuid);
|
await Nav.NavigateTo("instances/" + instance.InstanceGuid);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,11 @@
|
|||||||
private ElementReference commandInputElement;
|
private ElementReference commandInputElement;
|
||||||
|
|
||||||
private async Task ExecuteCommand(EditContext context) {
|
private async Task ExecuteCommand(EditContext context) {
|
||||||
|
var loggedInUserGuid = await GetUserGuid();
|
||||||
|
if (loggedInUserGuid == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await form.SubmitModel.StartSubmitting();
|
await form.SubmitModel.StartSubmitting();
|
||||||
|
|
||||||
if (!await CheckPermission(Permission.ControlInstances)) {
|
if (!await CheckPermission(Permission.ControlInstances)) {
|
||||||
@ -38,7 +43,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await InstanceManager.SendCommand(InstanceGuid, form.Command);
|
var result = await InstanceManager.SendCommandToInstance(loggedInUserGuid.Value, InstanceGuid, form.Command, CancellationToken);
|
||||||
if (result.Is(SendCommandToInstanceResult.Success)) {
|
if (result.Is(SendCommandToInstanceResult.Success)) {
|
||||||
form.Command = string.Empty;
|
form.Command = string.Empty;
|
||||||
form.SubmitModel.StopSubmitting();
|
form.SubmitModel.StopSubmitting();
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
@inherits PhantomComponent
|
@using Phantom.Utils.Collections
|
||||||
@using Phantom.Utils.Collections
|
@using Phantom.Utils.Events
|
||||||
@using System.Diagnostics
|
@using System.Diagnostics
|
||||||
@using Phantom.Common.Data.Web.Users
|
@using Phantom.Common.Data.Web.Users
|
||||||
@implements IDisposable
|
@inherits PhantomComponent
|
||||||
@inject IJSRuntime Js;
|
@inject IJSRuntime Js;
|
||||||
@inject InstanceLogManager InstanceLogManager
|
|
||||||
|
|
||||||
<div id="log" class="font-monospace mb-3">
|
<div id="log" class="font-monospace mb-3">
|
||||||
@foreach (var line in instanceLogs.EnumerateLast(uint.MaxValue)) {
|
@foreach (var line in instanceLogs.EnumerateLast(uint.MaxValue)) {
|
||||||
@ -15,7 +14,7 @@
|
|||||||
@code {
|
@code {
|
||||||
|
|
||||||
[Parameter, EditorRequired]
|
[Parameter, EditorRequired]
|
||||||
public Guid InstanceGuid { get; set; }
|
public Guid InstanceGuid { get; init; }
|
||||||
|
|
||||||
private IJSObjectReference? PageJs { get; set; }
|
private IJSObjectReference? PageJs { get; set; }
|
||||||
|
|
||||||
@ -25,11 +24,13 @@
|
|||||||
private readonly Stopwatch recheckPermissionsStopwatch = Stopwatch.StartNew();
|
private readonly Stopwatch recheckPermissionsStopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
protected override void OnInitialized() {
|
protected override void OnInitialized() {
|
||||||
|
/*
|
||||||
instanceLogsSubs = InstanceLogManager.GetSubs(InstanceGuid);
|
instanceLogsSubs = InstanceLogManager.GetSubs(InstanceGuid);
|
||||||
instanceLogsSubs.Subscribe(this, buffer => {
|
instanceLogsSubs.Subscribe(this, buffer => {
|
||||||
instanceLogs = buffer;
|
instanceLogs = buffer;
|
||||||
InvokeAsyncChecked(RefreshLog);
|
InvokeAsyncChecked(RefreshLog);
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
||||||
@ -64,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
protected override void OnDisposed() {
|
||||||
instanceLogsSubs.Unsubscribe(this);
|
instanceLogsSubs.Unsubscribe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
@inherits PhantomComponent
|
@inherits PhantomComponent
|
||||||
@inject IJSRuntime Js;
|
@inject IJSRuntime Js;
|
||||||
@inject InstanceManager InstanceManager;
|
@inject InstanceManager InstanceManager;
|
||||||
@inject AuditLog AuditLog
|
|
||||||
|
|
||||||
<Form Model="form" OnSubmit="StopInstance">
|
<Form Model="form" OnSubmit="StopInstance">
|
||||||
<Modal Id="@ModalId" TitleText="Stop Instance">
|
<Modal Id="@ModalId" TitleText="Stop Instance">
|
||||||
@ -33,13 +32,13 @@
|
|||||||
@code {
|
@code {
|
||||||
|
|
||||||
[Parameter, EditorRequired]
|
[Parameter, EditorRequired]
|
||||||
public Guid InstanceGuid { get; set; }
|
public Guid InstanceGuid { get; init; }
|
||||||
|
|
||||||
[Parameter, EditorRequired]
|
[Parameter, EditorRequired]
|
||||||
public string ModalId { get; set; } = string.Empty;
|
public string ModalId { get; init; } = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; init; }
|
||||||
|
|
||||||
private readonly StopInstanceFormModel form = new ();
|
private readonly StopInstanceFormModel form = new ();
|
||||||
|
|
||||||
@ -49,6 +48,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task StopInstance(EditContext context) {
|
private async Task StopInstance(EditContext context) {
|
||||||
|
var loggedInUserGuid = await GetUserGuid();
|
||||||
|
if (loggedInUserGuid == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await form.SubmitModel.StartSubmitting();
|
await form.SubmitModel.StartSubmitting();
|
||||||
|
|
||||||
if (!await CheckPermission(Permission.ControlInstances)) {
|
if (!await CheckPermission(Permission.ControlInstances)) {
|
||||||
@ -56,9 +60,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await InstanceManager.StopInstance(InstanceGuid, new MinecraftStopStrategy(form.StopInSeconds));
|
var result = await InstanceManager.StopInstance(loggedInUserGuid.Value, InstanceGuid, new MinecraftStopStrategy(form.StopInSeconds), CancellationToken);
|
||||||
if (result.Is(StopInstanceResult.StopInitiated)) {
|
if (result.Is(StopInstanceResult.StopInitiated)) {
|
||||||
await AuditLog.AddInstanceStoppedEvent(InstanceGuid, form.StopInSeconds);
|
|
||||||
await Js.InvokeVoidAsync("closeModal", ModalId);
|
await Js.InvokeVoidAsync("closeModal", ModalId);
|
||||||
form.SubmitModel.StopSubmitting();
|
form.SubmitModel.StopSubmitting();
|
||||||
}
|
}
|
||||||
|
@ -1,72 +1,72 @@
|
|||||||
@using Phantom.Common.Data.Web.Users
|
@* @using Phantom.Common.Data.Web.Users *@
|
||||||
@using Phantom.Utils.Tasks
|
@* @using Phantom.Utils.Tasks *@
|
||||||
@using System.ComponentModel.DataAnnotations
|
@* @using System.ComponentModel.DataAnnotations *@
|
||||||
@inherits PhantomComponent
|
@* @inherits PhantomComponent *@
|
||||||
@inject IJSRuntime Js;
|
@* @inject IJSRuntime Js; *@
|
||||||
|
@* *@
|
||||||
<Form Model="form" OnSubmit="AddUser">
|
@* <Form Model="form" OnSubmit="AddUser"> *@
|
||||||
<Modal Id="@ModalId" TitleText="Add User">
|
@* <Modal Id="@ModalId" TitleText="Add User"> *@
|
||||||
<Body>
|
@* <Body> *@
|
||||||
|
@* *@
|
||||||
<div class="row">
|
@* <div class="row"> *@
|
||||||
<div class="mb-3">
|
@* <div class="mb-3"> *@
|
||||||
<FormTextInput Id="account-username" Label="Username" @bind-Value="form.Username" autocomplete="off" />
|
@* <FormTextInput Id="account-username" Label="Username" @bind-Value="form.Username" autocomplete="off" /> *@
|
||||||
</div>
|
@* </div> *@
|
||||||
</div>
|
@* </div> *@
|
||||||
|
@* *@
|
||||||
<div class="row">
|
@* <div class="row"> *@
|
||||||
<div class="mb-3">
|
@* <div class="mb-3"> *@
|
||||||
<FormTextInput Id="account-password" Label="Password" Type="FormTextInputType.Password" autocomplete="new-password" @bind-Value="form.Password" />
|
@* <FormTextInput Id="account-password" Label="Password" Type="FormTextInputType.Password" autocomplete="new-password" @bind-Value="form.Password" /> *@
|
||||||
</div>
|
@* </div> *@
|
||||||
</div>
|
@* </div> *@
|
||||||
|
@* *@
|
||||||
</Body>
|
@* </Body> *@
|
||||||
<Footer>
|
@* <Footer> *@
|
||||||
<FormSubmitError />
|
@* <FormSubmitError /> *@
|
||||||
<FormButtonSubmit Label="Add User" class="btn btn-primary" />
|
@* <FormButtonSubmit Label="Add User" class="btn btn-primary" /> *@
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
@* <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> *@
|
||||||
</Footer>
|
@* </Footer> *@
|
||||||
</Modal>
|
@* </Modal> *@
|
||||||
</Form>
|
@* </Form> *@
|
||||||
|
@* *@
|
||||||
@code {
|
@* @code { *@
|
||||||
|
@* *@
|
||||||
[Parameter, EditorRequired]
|
@* [Parameter, EditorRequired] *@
|
||||||
public string ModalId { get; set; } = string.Empty;
|
@* public string ModalId { get; set; } = string.Empty; *@
|
||||||
|
@* *@
|
||||||
[Parameter]
|
@* [Parameter] *@
|
||||||
public EventCallback<UserInfo> UserAdded { get; set; }
|
@* public EventCallback<UserInfo> UserAdded { get; set; } *@
|
||||||
|
@* *@
|
||||||
private readonly AddUserFormModel form = new();
|
@* private readonly AddUserFormModel form = new(); *@
|
||||||
|
@* *@
|
||||||
private sealed class AddUserFormModel : FormModel {
|
@* private sealed class AddUserFormModel : FormModel { *@
|
||||||
[Required]
|
@* [Required] *@
|
||||||
public string Username { get; set; } = string.Empty;
|
@* public string Username { get; set; } = string.Empty; *@
|
||||||
|
@* *@
|
||||||
[Required]
|
@* [Required] *@
|
||||||
public string Password { get; set; } = string.Empty;
|
@* public string Password { get; set; } = string.Empty; *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private async Task AddUser(EditContext context) {
|
@* private async Task AddUser(EditContext context) { *@
|
||||||
await form.SubmitModel.StartSubmitting();
|
@* await form.SubmitModel.StartSubmitting(); *@
|
||||||
|
@* *@
|
||||||
if (!await CheckPermission(Permission.EditUsers)) {
|
@* if (!await CheckPermission(Permission.EditUsers)) { *@
|
||||||
form.SubmitModel.StopSubmitting("You do not have permission to add users.");
|
@* form.SubmitModel.StopSubmitting("You do not have permission to add users."); *@
|
||||||
return;
|
@* return; *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
switch (await UserManager.CreateUser(form.Username, form.Password)) {
|
@* switch (await UserManager.CreateUser(form.Username, form.Password)) { *@
|
||||||
case Result<UserInfo, AddUserError>.Ok ok:
|
@* case Result<UserInfo, AddUserError>.Ok ok: *@
|
||||||
await AuditLog.AddUserCreatedEvent(ok.Value);
|
@* await AuditLog.AddUserCreatedEvent(ok.Value); *@
|
||||||
await UserAdded.InvokeAsync(ok.Value);
|
@* await UserAdded.InvokeAsync(ok.Value); *@
|
||||||
await Js.InvokeVoidAsync("closeModal", ModalId);
|
@* await Js.InvokeVoidAsync("closeModal", ModalId); *@
|
||||||
form.SubmitModel.StopSubmitting();
|
@* form.SubmitModel.StopSubmitting(); *@
|
||||||
break;
|
@* break; *@
|
||||||
|
@* *@
|
||||||
case Result<UserInfo, AddUserError>.Fail fail:
|
@* case Result<UserInfo, AddUserError>.Fail fail: *@
|
||||||
form.SubmitModel.StopSubmitting(fail.Error.ToSentences("\n"));
|
@* form.SubmitModel.StopSubmitting(fail.Error.ToSentences("\n")); *@
|
||||||
break;
|
@* break; *@
|
||||||
}
|
@* } *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
}
|
@* } *@
|
||||||
|
@ -1,37 +1,37 @@
|
|||||||
@using Phantom.Common.Data.Web.Users
|
@* @using Phantom.Common.Data.Web.Users *@
|
||||||
@inherits UserEditDialogBase
|
@* @inherits UserEditDialogBase *@
|
||||||
@inject UserManager UserManager
|
@* @inject UserManager UserManager *@
|
||||||
@inject AuditLog AuditLog
|
@* @inject AuditLog AuditLog *@
|
||||||
|
@* *@
|
||||||
<Modal Id="@ModalId" TitleText="Delete User">
|
@* <Modal Id="@ModalId" TitleText="Delete User"> *@
|
||||||
<Body>
|
@* <Body> *@
|
||||||
You are about to delete the user <strong class="fw-semibold">@EditedUserName</strong>.<br>
|
@* You are about to delete the user <strong class="fw-semibold">@EditedUserName</strong>.<br> *@
|
||||||
This action cannot be undone.
|
@* This action cannot be undone. *@
|
||||||
</Body>
|
@* </Body> *@
|
||||||
<Footer>
|
@* <Footer> *@
|
||||||
<FormSubmitError Model="SubmitModel" />
|
@* <FormSubmitError Model="SubmitModel" /> *@
|
||||||
<FormButtonSubmit Model="SubmitModel" Label="Delete User" type="button" class="btn btn-danger" @onclick="Submit" />
|
@* <FormButtonSubmit Model="SubmitModel" Label="Delete User" type="button" class="btn btn-danger" @onclick="Submit" /> *@
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="OnClosed">Cancel</button>
|
@* <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="OnClosed">Cancel</button> *@
|
||||||
</Footer>
|
@* </Footer> *@
|
||||||
</Modal>
|
@* </Modal> *@
|
||||||
|
@* *@
|
||||||
@code {
|
@* @code { *@
|
||||||
|
@* *@
|
||||||
protected override async Task DoEdit(UserInfo user) {
|
@* protected override async Task DoEdit(UserInfo user) { *@
|
||||||
switch (await UserManager.DeleteByGuid(user.Guid)) {
|
@* switch (await UserManager.DeleteByGuid(user.Guid)) { *@
|
||||||
case DeleteUserResult.Deleted:
|
@* case DeleteUserResult.Deleted: *@
|
||||||
await AuditLog.AddUserDeletedEvent(user);
|
@* await AuditLog.AddUserDeletedEvent(user); *@
|
||||||
await OnEditSuccess();
|
@* await OnEditSuccess(); *@
|
||||||
break;
|
@* break; *@
|
||||||
|
@* *@
|
||||||
case DeleteUserResult.NotFound:
|
@* case DeleteUserResult.NotFound: *@
|
||||||
await OnEditSuccess();
|
@* await OnEditSuccess(); *@
|
||||||
break;
|
@* break; *@
|
||||||
|
@* *@
|
||||||
case DeleteUserResult.Failed:
|
@* case DeleteUserResult.Failed: *@
|
||||||
OnEditFailure("Could not delete user.");
|
@* OnEditFailure("Could not delete user."); *@
|
||||||
break;
|
@* break; *@
|
||||||
}
|
@* } *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
}
|
@* } *@
|
||||||
|
@ -1,76 +1,76 @@
|
|||||||
@using Phantom.Common.Data.Web.Users
|
@* @using Phantom.Common.Data.Web.Users *@
|
||||||
@inherits UserEditDialogBase
|
@* @inherits UserEditDialogBase *@
|
||||||
|
@* *@
|
||||||
<Modal Id="@ModalId" TitleText="Manage User Roles">
|
@* <Modal Id="@ModalId" TitleText="Manage User Roles"> *@
|
||||||
<Body>
|
@* <Body> *@
|
||||||
Roles for user: <strong class="fw-semibold">@EditedUserName</strong><br>
|
@* Roles for user: <strong class="fw-semibold">@EditedUserName</strong><br> *@
|
||||||
@for (var index = 0; index < items.Count; index++) {
|
@* @for (var index = 0; index < items.Count; index++) { *@
|
||||||
var item = items[index];
|
@* var item = items[index]; *@
|
||||||
<div class="mt-1">
|
@* <div class="mt-1"> *@
|
||||||
<input id="role-@index" type="checkbox" class="form-check-input" @bind="@item.Checked" />
|
@* <input id="role-@index" type="checkbox" class="form-check-input" @bind="@item.Checked" /> *@
|
||||||
<label for="role-@index" class="form-check-label">@item.Role.Name</label>
|
@* <label for="role-@index" class="form-check-label">@item.Role.Name</label> *@
|
||||||
</div>
|
@* </div> *@
|
||||||
}
|
@* } *@
|
||||||
</Body>
|
@* </Body> *@
|
||||||
<Footer>
|
@* <Footer> *@
|
||||||
<FormSubmitError Model="SubmitModel" />
|
@* <FormSubmitError Model="SubmitModel" /> *@
|
||||||
<FormButtonSubmit Model="SubmitModel" Label="Save Roles" type="button" class="btn btn-success" @onclick="Submit" />
|
@* <FormButtonSubmit Model="SubmitModel" Label="Save Roles" type="button" class="btn btn-success" @onclick="Submit" /> *@
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="OnClosed">Cancel</button>
|
@* <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="OnClosed">Cancel</button> *@
|
||||||
</Footer>
|
@* </Footer> *@
|
||||||
</Modal>
|
@* </Modal> *@
|
||||||
|
@* *@
|
||||||
@code {
|
@* @code { *@
|
||||||
|
@* *@
|
||||||
private List<RoleItem> items = new();
|
@* private List<RoleItem> items = new(); *@
|
||||||
|
@* *@
|
||||||
protected override async Task BeforeShown(UserInfo user) {
|
@* protected override async Task BeforeShown(UserInfo user) { *@
|
||||||
var userRoles = await UserRoleManager.GetUserRoleGuids(user);
|
@* var userRoles = await UserRoleManager.GetUserRoleGuids(user); *@
|
||||||
var allRoles = await RoleManager.GetAll();
|
@* var allRoles = await RoleManager.GetAll(); *@
|
||||||
this.items = allRoles.Select(role => new RoleItem(role, userRoles.Contains(role.RoleGuid))).ToList();
|
@* this.items = allRoles.Select(role => new RoleItem(role, userRoles.Contains(role.RoleGuid))).ToList(); *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
protected override async Task DoEdit(UserInfo user) {
|
@* protected override async Task DoEdit(UserInfo user) { *@
|
||||||
var userRoles = await UserRoleManager.GetUserRoleGuids(user);
|
@* var userRoles = await UserRoleManager.GetUserRoleGuids(user); *@
|
||||||
var addedToRoles = new List<string>();
|
@* var addedToRoles = new List<string>(); *@
|
||||||
var removedFromRoles = new List<string>();
|
@* var removedFromRoles = new List<string>(); *@
|
||||||
var errors = new List<string>();
|
@* var errors = new List<string>(); *@
|
||||||
|
@* *@
|
||||||
foreach (var item in items) {
|
@* foreach (var item in items) { *@
|
||||||
var shouldHaveRole = item.Checked;
|
@* var shouldHaveRole = item.Checked; *@
|
||||||
if (shouldHaveRole == userRoles.Contains(item.Role.Guid)) {
|
@* if (shouldHaveRole == userRoles.Contains(item.Role.Guid)) { *@
|
||||||
continue;
|
@* continue; *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
bool success = shouldHaveRole ? await UserRoleManager.Add(user, item.Role) : await UserRoleManager.Remove(user, item.Role);
|
@* bool success = shouldHaveRole ? await UserRoleManager.Add(user, item.Role) : await UserRoleManager.Remove(user, item.Role); *@
|
||||||
if (success) {
|
@* if (success) { *@
|
||||||
var modifiedList = shouldHaveRole ? addedToRoles : removedFromRoles;
|
@* var modifiedList = shouldHaveRole ? addedToRoles : removedFromRoles; *@
|
||||||
modifiedList.Add(item.Role.Name);
|
@* modifiedList.Add(item.Role.Name); *@
|
||||||
}
|
@* } *@
|
||||||
else if (shouldHaveRole) {
|
@* else if (shouldHaveRole) { *@
|
||||||
errors.Add("Could not add role " + item.Role.Name + " to user.");
|
@* errors.Add("Could not add role " + item.Role.Name + " to user."); *@
|
||||||
}
|
@* } *@
|
||||||
else {
|
@* else { *@
|
||||||
errors.Add("Could not remove role " + item.Role.Name + " from user.");
|
@* errors.Add("Could not remove role " + item.Role.Name + " from user."); *@
|
||||||
}
|
@* } *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
if (errors.Count == 0) {
|
@* if (errors.Count == 0) { *@
|
||||||
await AuditLog.AddUserRolesChangedEvent(user, addedToRoles, removedFromRoles);
|
@* await AuditLog.AddUserRolesChangedEvent(user, addedToRoles, removedFromRoles); *@
|
||||||
await OnEditSuccess();
|
@* await OnEditSuccess(); *@
|
||||||
}
|
@* } *@
|
||||||
else {
|
@* else { *@
|
||||||
OnEditFailure(string.Join("\n", errors));
|
@* OnEditFailure(string.Join("\n", errors)); *@
|
||||||
}
|
@* } *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
private sealed class RoleItem {
|
@* private sealed class RoleItem { *@
|
||||||
public RoleInfo Role { get; }
|
@* public RoleInfo Role { get; } *@
|
||||||
public bool Checked { get; set; }
|
@* public bool Checked { get; set; } *@
|
||||||
|
@* *@
|
||||||
public RoleItem(RoleInfo role, bool @checked) {
|
@* public RoleItem(RoleInfo role, bool @checked) { *@
|
||||||
this.Role = role;
|
@* this.Role = role; *@
|
||||||
this.Checked = @checked;
|
@* this.Checked = @checked; *@
|
||||||
}
|
@* } *@
|
||||||
}
|
@* } *@
|
||||||
|
@* *@
|
||||||
}
|
@* } *@
|
||||||
|
Loading…
Reference in New Issue
Block a user