Merge pull request #208 from Moonlight-Panel/RewriteSessionSystem

Rewritten session system to match new standarts and be more performant
This commit is contained in:
Marcel Baumgartner 2023-07-04 17:51:09 +02:00 committed by GitHub
commit 92705837ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 183 additions and 188 deletions

View file

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Database.Entities;
using Moonlight.App.Services.Interop;
namespace Moonlight.App.Models.Misc;
public class Session
{
public string Ip { get; set; } = "N/A";
public string Url { get; set; } = "N/A";
public string Device { get; set; } = "N/A";
public User? User { get; set; }
public DateTime CreatedAt { get; set; }
public NavigationManager Navigation { get; set; }
public AlertService AlertService { get; set; }
}

View file

@ -1,37 +0,0 @@
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Repositories;
public class SessionRepository
{
private readonly List<Session> Sessions;
public SessionRepository()
{
Sessions = new();
}
public Session[] Get()
{
lock (Sessions)
{
return Sessions.ToArray();
}
}
public void Add(Session session)
{
lock (Sessions)
{
Sessions.Add(session);
}
}
public void Delete(Session session)
{
lock (Sessions)
{
Sessions.Remove(session);
}
}
}

View file

@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Moonlight.App.Database.Entities;
using Moonlight.App.Repositories;
using Moonlight.App.Services.Interop;
namespace Moonlight.App.Services.Sessions;
public class SessionClientService
{
public readonly Guid Uuid = Guid.NewGuid();
public readonly DateTime CreateTimestamp = DateTime.UtcNow;
public User? User { get; private set; }
public readonly IdentityService IdentityService;
public readonly AlertService AlertService;
public readonly NavigationManager NavigationManager;
public readonly IJSRuntime JsRuntime;
private readonly SessionServerService SessionServerService;
private readonly Repository<User> UserRepository;
public SessionClientService(
IdentityService identityService,
AlertService alertService,
NavigationManager navigationManager,
IJSRuntime jsRuntime,
SessionServerService sessionServerService,
Repository<User> userRepository)
{
IdentityService = identityService;
AlertService = alertService;
NavigationManager = navigationManager;
JsRuntime = jsRuntime;
SessionServerService = sessionServerService;
UserRepository = userRepository;
}
public async Task Start()
{
User = await IdentityService.Get();
if (User != null) // Track users last visit
{
User.LastVisitedAt = DateTime.UtcNow;
UserRepository.Update(User);
}
await SessionServerService.Register(this);
}
public async Task Stop()
{
await SessionServerService.UnRegister(this);
}
}

View file

@ -0,0 +1,64 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
namespace Moonlight.App.Services.Sessions;
public class SessionServerService
{
private readonly List<SessionClientService> Sessions = new();
private readonly EventSystem Event;
public SessionServerService(EventSystem eventSystem)
{
Event = eventSystem;
}
public async Task Register(SessionClientService sessionClientService)
{
lock (Sessions)
{
if(!Sessions.Contains(sessionClientService))
Sessions.Add(sessionClientService);
}
await Event.Emit("sessions.add", sessionClientService);
}
public async Task UnRegister(SessionClientService sessionClientService)
{
lock (Sessions)
{
if(Sessions.Contains(sessionClientService))
Sessions.Remove(sessionClientService);
}
await Event.Emit("sessions.remove", sessionClientService);
}
public Task<SessionClientService[]> GetSessions()
{
lock (Sessions)
{
return Task.FromResult(Sessions.ToArray());
}
}
public async Task ReloadUserSessions(User user)
{
var sessions = await GetSessions();
foreach (var session in sessions)
{
if (session.User != null && session.User.Id == user.Id)
{
try
{
session.NavigationManager.NavigateTo(session.NavigationManager.Uri, true);
}
catch (Exception)
{
// ignore
}
}
}
}
}

View file

@ -1,83 +0,0 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.Interop;
namespace Moonlight.App.Services.Sessions;
public class SessionService
{
private readonly SessionRepository SessionRepository;
private Repository<User> UserRepository;
private readonly IdentityService IdentityService;
private readonly NavigationManager NavigationManager;
private readonly AlertService AlertService;
private readonly DateTimeService DateTimeService;
private Session? OwnSession;
public SessionService(
SessionRepository sessionRepository,
IdentityService identityService,
NavigationManager navigationManager,
AlertService alertService,
DateTimeService dateTimeService,
Repository<User> userRepository)
{
SessionRepository = sessionRepository;
IdentityService = identityService;
NavigationManager = navigationManager;
AlertService = alertService;
DateTimeService = dateTimeService;
UserRepository = userRepository;
}
public async Task Register()
{
var user = await IdentityService.Get();
OwnSession = new Session()
{
Ip = IdentityService.GetIp(),
Url = NavigationManager.Uri,
Device = IdentityService.GetDevice(),
CreatedAt = DateTimeService.GetCurrent(),
User = user,
Navigation = NavigationManager,
AlertService = AlertService
};
SessionRepository.Add(OwnSession);
if (user != null) // Track last session init of user as last visited timestamp
{
user.LastVisitedAt = DateTimeService.GetCurrent();
UserRepository.Update(user);
}
}
public void Refresh()
{
OwnSession.Url = NavigationManager.Uri;
}
public void Close()
{
SessionRepository.Delete(OwnSession);
}
public Session[] GetAll()
{
return SessionRepository.Get();
}
public void ReloadUserSessions(User user)
{
foreach (var session in SessionRepository.Get())
{
if(session.User != null && session.User.Id == user.Id)
session.Navigation.NavigateTo(session.Navigation.Uri, true);
}
}
}

View file

@ -44,7 +44,7 @@ public class StatisticsCaptureService
var domainsRepo = scope.ServiceProvider.GetRequiredService<Repository<Domain>>();
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
var databasesRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
var sessionService = scope.ServiceProvider.GetRequiredService<SessionService>();
var sessionService = scope.ServiceProvider.GetRequiredService<SessionServerService>();
void AddEntry(string chart, int value)
{
@ -61,7 +61,7 @@ public class StatisticsCaptureService
AddEntry("domainsCount", domainsRepo.Get().Count());
AddEntry("webspacesCount", webspacesRepo.Get().Count());
AddEntry("databasesCount", databasesRepo.Get().Count());
AddEntry("sessionsCount", sessionService.GetAll().Length);
AddEntry("sessionsCount", (await sessionService.GetSessions()).Length);
}
}
catch (Exception e)

View file

@ -152,7 +152,6 @@ namespace Moonlight
builder.Services.AddDbContext<DataContext>();
// Repositories
builder.Services.AddSingleton<SessionRepository>();
builder.Services.AddScoped<UserRepository>();
builder.Services.AddScoped<NodeRepository>();
builder.Services.AddScoped<ServerRepository>();
@ -179,7 +178,6 @@ namespace Moonlight
builder.Services.AddScoped<CookieService>();
builder.Services.AddScoped<IdentityService>();
builder.Services.AddScoped<IpLocateService>();
builder.Services.AddScoped<SessionService>();
builder.Services.AddScoped<AlertService>();
builder.Services.AddScoped<SmartTranslateService>();
builder.Services.AddScoped<UserService>();
@ -215,6 +213,9 @@ namespace Moonlight
builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>();
builder.Services.AddScoped<SessionClientService>();
builder.Services.AddSingleton<SessionServerService>();
// Loggers
builder.Services.AddScoped<MailService>();
builder.Services.AddSingleton<TrashMailDetectorService>();

View file

@ -14,7 +14,7 @@
@inject IJSRuntime JsRuntime
@inject IdentityService IdentityService
@inject SessionService SessionService
@inject SessionClientService SessionClientService
@inject NavigationManager NavigationManager
@inject EventSystem Event
@inject ToastService ToastService
@ -215,12 +215,10 @@
}
catch (Exception){ /* ignore errors to make sure that the session call is executed */ }
await SessionService.Register();
await SessionClientService.Start();
NavigationManager.LocationChanged += async (_, _) =>
{
SessionService.Refresh();
if (!NavigationManager.Uri.Contains("/server/"))
await DynamicBackgroundService.Reset();
};
@ -255,7 +253,7 @@
public async void Dispose()
{
SessionService.Close();
await SessionClientService.Stop();
await KeyListenerService.DisposeAsync();

View file

@ -9,7 +9,7 @@
@inject IJSRuntime JsRuntime
@inject IdentityService IdentityService
@inject SessionService SessionService
@inject SessionClientService SessionClientService
@inject NavigationManager NavigationManager
<GlobalErrorBoundary>
@ -106,9 +106,7 @@
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
await JsRuntime.InvokeVoidAsync("createSnow");
await SessionService.Register();
NavigationManager.LocationChanged += (sender, args) => { SessionService.Refresh(); };
await SessionClientService.Start();
}
catch (Exception)
{
@ -117,9 +115,9 @@
}
}
public void Dispose()
public async void Dispose()
{
SessionService.Close();
await SessionClientService.Stop();
}
private void AddBodyAttribute(string attribute, string value)

View file

@ -8,7 +8,7 @@
@inject UserRepository UserRepository
@inject UserService UserService
@inject SessionService SessionService
@inject SessionServerService SessionServerService
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
@ -174,7 +174,7 @@
user.Status = User!.Status;
UserRepository.Update(user);
SessionService.ReloadUserSessions(User);
await SessionServerService.ReloadUserSessions(User);
await ToastService.Success(SmartTranslateService.Translate("Successfully updated user"));
}
@ -191,7 +191,7 @@
await UserService.ChangePassword(User!, NewPassword, true);
NewPassword = "";
SessionService.ReloadUserSessions(User);
await SessionServerService.ReloadUserSessions(User!);
await ToastService.Success(SmartTranslateService.Translate("Successfully updated password"));
}

View file

@ -8,9 +8,10 @@
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject SessionService SessionService
@inject SessionServerService SessionServerService
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
@inject ToastService ToastService
<OnlyAdmin>
<AdminSessionNavigation Index="1"/>
@ -44,8 +45,8 @@
}
else
{
<Table TableItem="Session" Items="AllSessions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Email)" Sortable="true" Filterable="true" Width="20%">
<Table TableItem="SessionClientService" Items="AllSessions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Email)" Sortable="true" Filterable="true" Width="20%">
<Template>
@if (context.User == null)
{
@ -57,25 +58,33 @@
}
</Template>
</Column>
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Ip)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.Url)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Device)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true" Width="10%">
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
<Template>
@(context.IdentityService.GetIp())
</Template>
</Column>
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.NavigationManager.Uri)" Sortable="true" Filterable="true" Width="10%"/>
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
<Template>
@(context.IdentityService.GetDevice())
</Template>
</Column>
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreateTimestamp)" Sortable="true" Filterable="true" Width="10%">
<Template>
@{
var time = Formatter.FormatUptime((DateTime.UtcNow - context.CreatedAt).TotalMilliseconds);
var time = Formatter.FormatUptime((DateTime.UtcNow - context.CreateTimestamp).TotalMilliseconds);
}
<span>@(time)</span>
</Template>
</Column>
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
<Template>
<button @onclick="() => Navigate(context)" class="btn btn-sm btn-primary">
<TL>Change url</TL>
</button>
</Template>
</Column>
<Column TableItem="Session" Title="" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
<Column TableItem="SessionClientService" Title="" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
<Template>
<button @onclick="() => Message(context)" class="btn btn-sm btn-warning">
<TL>Message</TL>
@ -92,11 +101,11 @@
@code
{
private Session[]? AllSessions;
private SessionClientService[]? AllSessions;
private Task Load(LazyLoader arg)
private async Task Load(LazyLoader arg)
{
AllSessions = SessionService.GetAll();
AllSessions = await SessionServerService.GetSessions();
Task.Run(async () =>
{
@ -109,35 +118,30 @@
}
catch (Exception e)
{
Logger.Warn("Error autorefreshing sessions");
Logger.Warn("Error auto refreshing sessions");
Logger.Warn(e);
}
}
});
return Task.CompletedTask;
}
private async Task Refresh()
{
AllSessions = SessionService.GetAll();
AllSessions = await SessionServerService.GetSessions();
await InvokeAsync(StateHasChanged);
}
private async Task Navigate(Session session)
private async Task Navigate(SessionClientService session)
{
var url = await AlertService.Text("URL", SmartTranslateService.Translate("Enter url"), "");
if (url == null)
return;
if (url == "")
if (string.IsNullOrEmpty(url))
return;
if (url == "null")
return;
session.Navigation.NavigateTo(url, true);
session.NavigationManager.NavigateTo(url, true);
}
private async Task MessageAll()
@ -157,22 +161,30 @@
if (b)
{
foreach (var session in SessionService.GetAll())
foreach (var session in AllSessions!)
{
try
Task.Run(async () =>
{
await session.AlertService.Warning("Admin Message", message);
}
catch (Exception e)
{
Logger.Warn("Error sending user a alert");
Logger.Warn(e);
}
try
{
await session.AlertService.Warning("Admin Message", message);
}
catch (Exception e)
{
Logger.Warn("Error sending user a alert");
Logger.Warn(e);
var translation = SmartTranslateService.Translate("An unknown error occured while sending admin message to user: ");
var identifier = session.User != null ? session.User.Email : session.Uuid.ToString();
await ToastService.Warning(translation + identifier);
}
});
}
}
}
private async Task Message(Session session)
private async Task Message(SessionClientService session)
{
var message = await AlertService.Text(
SmartTranslateService.Translate("Enter message"),
@ -191,12 +203,14 @@
{
try
{
await session.AlertService.Warning("Admin Message", message);
await session.AlertService.Warning("Admin Message", message!);
}
catch (Exception e)
{
Logger.Warn("Error sending user a alert");
Logger.Warn(e);
await ToastService.Warning(SmartTranslateService.Translate("An unknown error occured while sending admin message"));
}
}
}