Added event system, soft error handler and added some things from helio
This commit is contained in:
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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="App\Configuration\" />
|
|
||||||
<Folder Include="App\Database\Entities\" />
|
|
||||||
<Folder Include="App\Database\Enums\" />
|
<Folder Include="App\Database\Enums\" />
|
||||||
<Folder Include="App\Database\Migrations\" />
|
<Folder Include="App\Database\Migrations\" />
|
||||||
<Folder Include="App\Exceptions\" />
|
|
||||||
<Folder Include="App\Http\" />
|
<Folder Include="App\Http\" />
|
||||||
<Folder Include="App\Models\Abstractions\" />
|
|
||||||
<Folder Include="App\Models\Enums\" />
|
|
||||||
<Folder Include="App\Models\Forms\" />
|
<Folder Include="App\Models\Forms\" />
|
||||||
<Folder Include="App\Repositories\" />
|
<Folder Include="App\Repositories\" />
|
||||||
<Folder Include="App\Services\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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="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" Version="3.1.0-dev-02078" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0-dev-00923" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0-dev-00923" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
using Moonlight.App.Database;
|
||||||
using Moonlight.App.Extensions;
|
using Moonlight.App.Extensions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Helpers.LogMigrator;
|
using Moonlight.App.Helpers.LogMigrator;
|
||||||
|
using Moonlight.App.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
Directory.CreateDirectory(PathBuilder.Dir("storage"));
|
Directory.CreateDirectory(PathBuilder.Dir("storage"));
|
||||||
@@ -17,6 +19,11 @@ Log.Logger = logConfig.CreateLogger();
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<DataContext>();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<ConfigService>();
|
||||||
|
builder.Services.AddSingleton<SessionService>();
|
||||||
|
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
builder.Services.AddHttpContextAccessor();
|
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
|
@using Moonlight.Shared.Layouts
|
||||||
|
|
||||||
<div class="app-sidebar flex-column @(Layout.ShowMobileSidebar ? "drawer drawer-start drawer-on" : "")">
|
<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">
|
<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">
|
<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
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
@Body
|
<SoftErrorHandler>
|
||||||
|
@Body
|
||||||
|
</SoftErrorHandler>
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@using Moonlight.App.Exceptions
|
||||||
|
|
||||||
<h1>Hello, world!</h1>
|
<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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using Moonlight
|
@using Moonlight
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.Shared.Components.Partials
|
@using Moonlight.Shared.Components.Partials
|
||||||
|
@using Moonlight.Shared.Components.Forms
|
||||||
Reference in New Issue
Block a user