Added event system, soft error handler and added some things from helio
This commit is contained in:
parent
afb3a7f3a3
commit
3bb4e7daab
22 changed files with 699 additions and 9 deletions
64
Moonlight/App/Configuration/ConfigV1.cs
Normal file
64
Moonlight/App/Configuration/ConfigV1.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System.ComponentModel;
|
||||
using Moonlight.App.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Configuration;
|
||||
|
||||
public class ConfigV1
|
||||
{
|
||||
[JsonProperty("AppUrl")]
|
||||
[Description("The url with which moonlight is accessible from the internet. It must not end with a /")]
|
||||
public string AppUrl { get; set; } = "http://your-moonlight-instance-without-slash.owo";
|
||||
|
||||
[JsonProperty("Security")] public SecurityData Security { get; set; } = new();
|
||||
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
|
||||
[JsonProperty("MailServer")] public MailServerData MailServer { get; set; } = new();
|
||||
|
||||
public class SecurityData
|
||||
{
|
||||
[JsonProperty("Token")]
|
||||
[Description("The security token helio will use to encrypt various things like tokens")]
|
||||
public string Token { get; set; } = Guid.NewGuid().ToString().Replace("-", "");
|
||||
|
||||
[JsonProperty("EnableEmailVerify")]
|
||||
[Description("This will users force to verify their email address if they havent already")]
|
||||
public bool EnableEmailVerify { get; set; } = false;
|
||||
}
|
||||
|
||||
public class DatabaseData
|
||||
{
|
||||
[JsonProperty("UseSqlite")]
|
||||
public bool UseSqlite { get; set; } = false;
|
||||
|
||||
[JsonProperty("SqlitePath")]
|
||||
public string SqlitePath { get; set; } = PathBuilder.File("storage", "data.sqlite");
|
||||
|
||||
[JsonProperty("Host")]
|
||||
public string Host { get; set; } = "your.db.host";
|
||||
|
||||
[JsonProperty("Port")]
|
||||
public int Port { get; set; } = 3306;
|
||||
|
||||
[JsonProperty("Username")]
|
||||
public string Username { get; set; } = "moonlight_user";
|
||||
|
||||
[JsonProperty("Password")]
|
||||
public string Password { get; set; } = "s3cr3t";
|
||||
|
||||
[JsonProperty("Database")]
|
||||
public string Database { get; set; } = "moonlight_db";
|
||||
}
|
||||
|
||||
public class MailServerData
|
||||
{
|
||||
[JsonProperty("Host")] public string Host { get; set; } = "your.email.host";
|
||||
|
||||
[JsonProperty("Port")] public int Port { get; set; } = 465;
|
||||
|
||||
[JsonProperty("Email")] public string Email { get; set; } = "noreply@your.email.host";
|
||||
|
||||
[JsonProperty("Password")] public string Password { get; set; } = "s3cr3t";
|
||||
|
||||
[JsonProperty("UseSsl")] public bool UseSsl { get; set; } = true;
|
||||
}
|
||||
}
|
42
Moonlight/App/Database/DataContext.cs
Normal file
42
Moonlight/App/Database/DataContext.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Database;
|
||||
|
||||
public class DataContext : DbContext
|
||||
{
|
||||
private readonly ConfigService ConfigService;
|
||||
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
public DataContext(ConfigService configService)
|
||||
{
|
||||
ConfigService = configService;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
{
|
||||
var config = ConfigService.Get().Database;
|
||||
|
||||
if (config.UseSqlite)
|
||||
optionsBuilder.UseSqlite($"Data Source={config.SqlitePath}");
|
||||
else
|
||||
{
|
||||
var connectionString = $"host={config.Host};" +
|
||||
$"port={config.Port};" +
|
||||
$"database={config.Database};" +
|
||||
$"uid={config.Username};" +
|
||||
$"pwd={config.Password}";
|
||||
|
||||
optionsBuilder.UseMySql(
|
||||
connectionString,
|
||||
ServerVersion.AutoDetect(connectionString),
|
||||
builder => builder.EnableRetryOnFailure(5)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
Moonlight/App/Database/Entities/User.cs
Normal file
19
Moonlight/App/Database/Entities/User.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string? Avatar { get; set; } = null;
|
||||
public string? TotpKey { get; set; } = null;
|
||||
|
||||
// Meta data
|
||||
public string Flags { get; set; } = "";
|
||||
public int Permissions { get; set; } = 0;
|
||||
|
||||
// Timestamps
|
||||
public DateTime TokenValidTimestamp { get; set; } = DateTime.UtcNow.AddMinutes(-10);
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
16
Moonlight/App/Exceptions/DisplayException.cs
Normal file
16
Moonlight/App/Exceptions/DisplayException.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace Moonlight.App.Exceptions;
|
||||
|
||||
public class DisplayException : Exception
|
||||
{
|
||||
public DisplayException()
|
||||
{
|
||||
}
|
||||
|
||||
public DisplayException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DisplayException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
140
Moonlight/App/Helpers/EventSystem.cs
Normal file
140
Moonlight/App/Helpers/EventSystem.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
using System.Diagnostics;
|
||||
using Moonlight.App.Models.Abstractions;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class EventSystem
|
||||
{
|
||||
private readonly List<Subscriber> Subscribers = new();
|
||||
|
||||
private readonly bool Debug = false;
|
||||
private readonly bool DisableWarning = false;
|
||||
private readonly TimeSpan TookToLongTime = TimeSpan.FromSeconds(1);
|
||||
|
||||
public Task On<T>(string id, object handle, Func<T, Task> action)
|
||||
{
|
||||
if (Debug)
|
||||
Logger.Debug($"{handle} subscribed to '{id}'");
|
||||
|
||||
lock (Subscribers)
|
||||
{
|
||||
if (!Subscribers.Any(x => x.Id == id && x.Handle == handle))
|
||||
{
|
||||
Subscribers.Add(new()
|
||||
{
|
||||
Action = action,
|
||||
Handle = handle,
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Emit(string id, object? data = null)
|
||||
{
|
||||
Subscriber[] subscribers;
|
||||
|
||||
lock (Subscribers)
|
||||
{
|
||||
subscribers = Subscribers
|
||||
.Where(x => x.Id == id)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
foreach (var subscriber in subscribers)
|
||||
{
|
||||
tasks.Add(new Task(() =>
|
||||
{
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
|
||||
var del = (Delegate)subscriber.Action;
|
||||
|
||||
try
|
||||
{
|
||||
((Task)del.DynamicInvoke(data)!).Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn($"Error emitting '{subscriber.Id} on {subscriber.Handle}'");
|
||||
Logger.Warn(e);
|
||||
}
|
||||
|
||||
stopWatch.Stop();
|
||||
|
||||
if (!DisableWarning)
|
||||
{
|
||||
if (stopWatch.Elapsed.TotalMilliseconds > TookToLongTime.TotalMilliseconds)
|
||||
{
|
||||
Logger.Warn(
|
||||
$"Subscriber {subscriber.Handle} for event '{subscriber.Id}' took long to process. {stopWatch.Elapsed.TotalMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug)
|
||||
{
|
||||
Logger.Debug(
|
||||
$"Subscriber {subscriber.Handle} for event '{subscriber.Id}' took {stopWatch.Elapsed.TotalMilliseconds}ms");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
task.Start();
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
if (Debug)
|
||||
Logger.Debug($"Completed all event tasks for '{id}' and removed object from storage");
|
||||
});
|
||||
|
||||
if (Debug)
|
||||
Logger.Debug($"Completed event emit '{id}'");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Off(string id, object handle)
|
||||
{
|
||||
if (Debug)
|
||||
Logger.Debug($"{handle} unsubscribed to '{id}'");
|
||||
|
||||
lock (Subscribers)
|
||||
{
|
||||
Subscribers.RemoveAll(x => x.Id == id && x.Handle == handle);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<T> WaitForEvent<T>(string id, object handle, Func<T, bool>? filter = null)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource<T>();
|
||||
|
||||
Func<T, Task> action = async data =>
|
||||
{
|
||||
if (filter == null)
|
||||
{
|
||||
taskCompletionSource.SetResult(data);
|
||||
await Off(id, handle);
|
||||
}
|
||||
else if (filter.Invoke(data))
|
||||
{
|
||||
taskCompletionSource.SetResult(data);
|
||||
await Off(id, handle);
|
||||
}
|
||||
};
|
||||
|
||||
On<T>(id, handle, action);
|
||||
|
||||
return taskCompletionSource.Task;
|
||||
}
|
||||
}
|
50
Moonlight/App/Models/Abstractions/FlagStorage.cs
Normal file
50
Moonlight/App/Models/Abstractions/FlagStorage.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
using Moonlight.App.Models.Enums;
|
||||
|
||||
namespace Moonlight.App.Models.Abstractions;
|
||||
|
||||
public class FlagStorage
|
||||
{
|
||||
private readonly List<string> FlagList;
|
||||
|
||||
public UserFlag[] Flags => FlagList
|
||||
.Select(x => Enum.Parse(typeof(UserFlag), x))
|
||||
.Select(x => (UserFlag)x)
|
||||
.ToArray();
|
||||
|
||||
public string[] RawFlags => FlagList.ToArray();
|
||||
public string RawFlagString => string.Join(";", FlagList);
|
||||
|
||||
public bool this[UserFlag flag]
|
||||
{
|
||||
get => Flags.Contains(flag);
|
||||
set => Set(flag.ToString(), value);
|
||||
}
|
||||
|
||||
public bool this[string flagName]
|
||||
{
|
||||
get => FlagList.Contains(flagName);
|
||||
set => Set(flagName, value);
|
||||
}
|
||||
|
||||
public FlagStorage(string flagString)
|
||||
{
|
||||
FlagList = flagString
|
||||
.Split(";")
|
||||
.Where(x => !string.IsNullOrEmpty(x))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public void Set(string flagName, bool shouldAdd)
|
||||
{
|
||||
if (shouldAdd)
|
||||
{
|
||||
if(!FlagList.Contains(flagName))
|
||||
FlagList.Add(flagName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FlagList.Contains(flagName))
|
||||
FlagList.Remove(flagName);
|
||||
}
|
||||
}
|
||||
}
|
34
Moonlight/App/Models/Abstractions/PermissionStorage.cs
Normal file
34
Moonlight/App/Models/Abstractions/PermissionStorage.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using Moonlight.App.Models.Enums;
|
||||
|
||||
namespace Moonlight.App.Models.Abstractions;
|
||||
|
||||
public class PermissionStorage
|
||||
{
|
||||
public readonly int PermissionInteger;
|
||||
|
||||
public PermissionStorage(int permissionInteger)
|
||||
{
|
||||
PermissionInteger = permissionInteger;
|
||||
}
|
||||
|
||||
public Permission[] Permissions => GetPermissions();
|
||||
|
||||
public Permission[] GetPermissions()
|
||||
{
|
||||
return GetAllPermissions()
|
||||
.Where(x => (int)x <= PermissionInteger)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static Permission[] GetAllPermissions()
|
||||
{
|
||||
return Enum.GetValues<Permission>();
|
||||
}
|
||||
|
||||
public static Permission GetFromInteger(int id)
|
||||
{
|
||||
return GetAllPermissions().First(x => (int)x == id);
|
||||
}
|
||||
|
||||
public bool this[Permission permission] => Permissions.Contains(permission);
|
||||
}
|
12
Moonlight/App/Models/Abstractions/Session.cs
Normal file
12
Moonlight/App/Models/Abstractions/Session.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Models.Abstractions;
|
||||
|
||||
public class Session
|
||||
{
|
||||
public string Ip { get; set; } = "N/A";
|
||||
public string Url { get; set; } = "N/A";
|
||||
public User? User { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; // To remove inactive sessions
|
||||
}
|
8
Moonlight/App/Models/Abstractions/Subscriber.cs
Normal file
8
Moonlight/App/Models/Abstractions/Subscriber.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Models.Abstractions;
|
||||
|
||||
public class Subscriber
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public object Action { get; set; }
|
||||
public object Handle { get; set; }
|
||||
}
|
14
Moonlight/App/Models/Enums/Permission.cs
Normal file
14
Moonlight/App/Models/Enums/Permission.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace Moonlight.App.Models.Enums;
|
||||
|
||||
public enum Permission
|
||||
{
|
||||
Default = 0,
|
||||
AdminMenu = 999,
|
||||
AdminOverview = 1000,
|
||||
AdminUsers = 1001,
|
||||
AdminSessions = 1002,
|
||||
AdminUsersEdit = 1003,
|
||||
AdminTickets = 1004,
|
||||
AdminViewExceptions = 1999,
|
||||
AdminRoot = 2000
|
||||
}
|
8
Moonlight/App/Models/Enums/UserFlag.cs
Normal file
8
Moonlight/App/Models/Enums/UserFlag.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Models.Enums;
|
||||
|
||||
public enum UserFlag
|
||||
{
|
||||
MailVerified,
|
||||
PasswordPending,
|
||||
TotpEnabled
|
||||
}
|
32
Moonlight/App/Services/ConfigService.cs
Normal file
32
Moonlight/App/Services/ConfigService.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using Moonlight.App.Configuration;
|
||||
using Moonlight.App.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class ConfigService
|
||||
{
|
||||
private readonly string Path = PathBuilder.File("storage", "config.json");
|
||||
private ConfigV1 Data;
|
||||
|
||||
public ConfigService()
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if(!File.Exists(Path))
|
||||
File.WriteAllText(Path, "{}");
|
||||
|
||||
var text = File.ReadAllText(Path);
|
||||
Data = JsonConvert.DeserializeObject<ConfigV1>(text) ?? new();
|
||||
text = JsonConvert.SerializeObject(Data, Formatting.Indented);
|
||||
File.WriteAllText(Path, text);
|
||||
}
|
||||
|
||||
public ConfigV1 Get()
|
||||
{
|
||||
return Data;
|
||||
}
|
||||
}
|
38
Moonlight/App/Services/SessionService.cs
Normal file
38
Moonlight/App/Services/SessionService.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using Moonlight.App.Models.Abstractions;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class SessionService
|
||||
{
|
||||
private readonly List<Session> AllSessions = new();
|
||||
|
||||
public Session[] Sessions => GetSessions();
|
||||
|
||||
public Task Register(Session session)
|
||||
{
|
||||
lock (AllSessions)
|
||||
{
|
||||
AllSessions.Add(session);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Unregister(Session session)
|
||||
{
|
||||
lock (AllSessions)
|
||||
{
|
||||
AllSessions.Remove(session);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Session[] GetSessions()
|
||||
{
|
||||
lock (AllSessions)
|
||||
{
|
||||
return AllSessions.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,21 +14,22 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="App\Configuration\" />
|
||||
<Folder Include="App\Database\Entities\" />
|
||||
<Folder Include="App\Database\Enums\" />
|
||||
<Folder Include="App\Database\Migrations\" />
|
||||
<Folder Include="App\Exceptions\" />
|
||||
<Folder Include="App\Http\" />
|
||||
<Folder Include="App\Models\Abstractions\" />
|
||||
<Folder Include="App\Models\Enums\" />
|
||||
<Folder Include="App\Models\Forms\" />
|
||||
<Folder Include="App\Repositories\" />
|
||||
<Folder Include="App\Services\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
||||
<PackageReference Include="Serilog" Version="3.1.0-dev-02078" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0-dev-00923" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Extensions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Helpers.LogMigrator;
|
||||
using Moonlight.App.Services;
|
||||
using Serilog;
|
||||
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage"));
|
||||
|
@ -17,6 +19,11 @@ Log.Logger = logConfig.CreateLogger();
|
|||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddDbContext<DataContext>();
|
||||
|
||||
builder.Services.AddSingleton<ConfigService>();
|
||||
builder.Services.AddSingleton<SessionService>();
|
||||
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddServerSideBlazor();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
|
66
Moonlight/Shared/Components/Forms/ConfirmButton.razor
Normal file
66
Moonlight/Shared/Components/Forms/ConfirmButton.razor
Normal file
|
@ -0,0 +1,66 @@
|
|||
@if (ShowConfirm)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success me-2 rounded-end" @onclick="Do">Confirm</button>
|
||||
<button class="btn btn-danger rounded-start" @onclick="() => SetConfirm(false)">Cancel</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Working)
|
||||
{
|
||||
<button class="btn @(CssClasses) disabled" disabled="">
|
||||
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
|
||||
@WorkingText
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn @(CssClasses)" @onclick="() => SetConfirm(true)">
|
||||
@Text
|
||||
@ChildContent
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
private bool Working { get; set; } = false;
|
||||
private bool ShowConfirm = false;
|
||||
|
||||
[Parameter]
|
||||
public string CssClasses { get; set; } = "btn-primary";
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public string WorkingText { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
private async Task SetConfirm(bool b)
|
||||
{
|
||||
ShowConfirm = b;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Do()
|
||||
{
|
||||
Working = true;
|
||||
ShowConfirm = false;
|
||||
StateHasChanged();
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
if (OnClick != null)
|
||||
await OnClick.Invoke();
|
||||
|
||||
Working = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
}
|
48
Moonlight/Shared/Components/Forms/WButton.razor
Normal file
48
Moonlight/Shared/Components/Forms/WButton.razor
Normal file
|
@ -0,0 +1,48 @@
|
|||
@if (!Working)
|
||||
{
|
||||
<button class="btn @(CssClasses)" @onclick="Do">
|
||||
@Text
|
||||
@ChildContent
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn @(CssClasses) disabled" disabled="">
|
||||
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
|
||||
@WorkingText
|
||||
</button>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
private bool Working { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public string CssClasses { get; set; } = "btn-primary";
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public string WorkingText { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
private async Task Do()
|
||||
{
|
||||
Working = true;
|
||||
StateHasChanged();
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
if (OnClick != null)
|
||||
await OnClick.Invoke();
|
||||
|
||||
Working = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
@using Moonlight.Shared.Layouts
|
||||
|
||||
<div class="app-sidebar flex-column @(Layout.ShowMobileSidebar ? "drawer drawer-start drawer-on" : "")">
|
||||
<div class="app-sidebar-header d-flex flex-stack d-none d-lg-flex pt-8 pb-2">
|
||||
<a href="/metronic8/demo38/../demo38/index.html" class="app-sidebar-logo">
|
||||
|
|
70
Moonlight/Shared/Components/Partials/SoftErrorHandler.razor
Normal file
70
Moonlight/Shared/Components/Partials/SoftErrorHandler.razor
Normal file
|
@ -0,0 +1,70 @@
|
|||
@using System.Diagnostics
|
||||
@using Moonlight.App.Exceptions
|
||||
@inherits ErrorBoundaryBase
|
||||
|
||||
@if (Crashed)
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") // TODO: Add check for admin perms to show exceptions to admins
|
||||
{
|
||||
if (Exception != null)
|
||||
{
|
||||
<div class="card border border-danger">
|
||||
<div class="card-header">
|
||||
<span class="card-title text-danger fw-bold fs-3">An unhandled exception occured</span>
|
||||
</div>
|
||||
<div class="card-body fw-bold">
|
||||
@(Formatter.FormatLineBreaks(Exception.ToStringDemystified()))
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>Crashed lol :c</h1>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ErrorMessages.Any())
|
||||
{
|
||||
foreach (var errorMessage in ErrorMessages)
|
||||
{
|
||||
<div class="alert alert-danger bg-danger text-white p-3 mb-5 fw-bold">
|
||||
@errorMessage
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@ChildContent
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
private bool Crashed = false;
|
||||
private List<string> ErrorMessages = new();
|
||||
private Exception? Exception;
|
||||
|
||||
protected override Task OnErrorAsync(Exception exception)
|
||||
{
|
||||
if (exception is DisplayException displayException)
|
||||
{
|
||||
ErrorMessages.Add(displayException.Message);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
ErrorMessages.Remove(displayException.Message);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Exception = exception;
|
||||
Crashed = true;
|
||||
}
|
||||
|
||||
Recover();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
@inherits LayoutComponentBase
|
||||
|
||||
<DefaultLayout>
|
||||
<SoftErrorHandler>
|
||||
@Body
|
||||
</SoftErrorHandler>
|
||||
</DefaultLayout>
|
|
@ -1,3 +1,20 @@
|
|||
@page "/"
|
||||
@using Moonlight.App.Exceptions
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
<ConfirmButton Text="Crash" WorkingText="Crashing" CssClasses="btn-danger" OnClick="Do" />
|
||||
<ConfirmButton Text="Crash" WorkingText="Crashing" CssClasses="btn-danger" OnClick="Do2" />
|
||||
|
||||
@code
|
||||
{
|
||||
private async Task Do()
|
||||
{
|
||||
throw new DisplayException("LOL");
|
||||
}
|
||||
|
||||
private async Task Do2()
|
||||
{
|
||||
throw new FormatException("LOL");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
@using Moonlight
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
@using Moonlight.Shared.Components.Forms
|
Loading…
Reference in a new issue