Upgraded mooncore versions. Cleaned up code, especially startup code. Changed versions

This commit is contained in:
2025-10-05 16:07:27 +00:00
parent d2ef59d171
commit 9ab69ffef5
43 changed files with 429 additions and 632 deletions

View File

@@ -18,7 +18,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.8" /> <PackageReference Include="MoonCore.PluginFramework" Version="1.0.9" />
</ItemGroup> </ItemGroup>
<Import Project="Plugins.props" /> <Import Project="Plugins.props" />

View File

@@ -1,28 +1,25 @@
using Moonlight.ApiServer.Runtime; using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Runtime;
using Moonlight.ApiServer.Startup; using Moonlight.ApiServer.Startup;
var pluginLoader = new PluginLoader(); var pluginLoader = new PluginLoader();
pluginLoader.Initialize(); pluginLoader.Initialize();
/*
await startup.Run(args, pluginLoader.Instances);
*/
var cs = new Startup();
await cs.InitializeAsync(args, pluginLoader.Instances);
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
await cs.AddMoonlightAsync(builder); builder.AddMoonlight(pluginLoader.Instances);
var app = builder.Build(); var app = builder.Build();
await cs.AddMoonlightAsync(app); app.UseMoonlight(pluginLoader.Instances);
// Add frontend
var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
// Handle setup of wasm app hosting in the runtime // Handle setup of wasm app hosting in the runtime
// so the Moonlight.ApiServer doesn't need the wasm package // so the Moonlight.ApiServer doesn't need the wasm package
if (cs.Configuration.Frontend.EnableHosting) if (configuration.Frontend.EnableHosting)
{ {
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
app.UseWebAssemblyDebugging(); app.UseWebAssemblyDebugging();
@@ -31,5 +28,7 @@ if (cs.Configuration.Frontend.EnableHosting)
app.UseStaticFiles(); app.UseStaticFiles();
} }
app.MapMoonlight(pluginLoader.Instances);
await app.RunAsync(); await app.RunAsync();

View File

@@ -1,8 +1,8 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MoonCore.Common;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Models;
using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Mappers; using Moonlight.ApiServer.Mappers;
using Moonlight.ApiServer.Services; using Moonlight.ApiServer.Services;
@@ -26,7 +26,7 @@ public class ApiKeysController : Controller
[HttpGet] [HttpGet]
[Authorize(Policy = "permissions:admin.apikeys.get")] [Authorize(Policy = "permissions:admin.apikeys.get")]
public async Task<ActionResult<ICountedData<ApiKeyResponse>>> GetAsync( public async Task<ActionResult<CountedData<ApiKeyResponse>>> GetAsync(
[FromQuery] int startIndex, [FromQuery] int startIndex,
[FromQuery] int count, [FromQuery] int count,
[FromQuery] string? orderBy, [FromQuery] string? orderBy,

View File

@@ -1,11 +1,8 @@
using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MoonCore.Exceptions; using MoonCore.Common;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Models;
using MoonCore.Models;
using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Mappers; using Moonlight.ApiServer.Mappers;
using Moonlight.Shared.Http.Requests.Admin.Sys.Theme; using Moonlight.Shared.Http.Requests.Admin.Sys.Theme;
@@ -26,7 +23,7 @@ public class ThemesController : Controller
[HttpGet] [HttpGet]
[Authorize(Policy = "permissions:admin.system.customisation.themes.read")] [Authorize(Policy = "permissions:admin.system.customisation.themes.read")]
public async Task<ActionResult<ICountedData<ThemeResponse>>> GetAsync( public async Task<ActionResult<CountedData<ThemeResponse>>> GetAsync(
[FromQuery] int startIndex, [FromQuery] int startIndex,
[FromQuery] int count, [FromQuery] int count,
[FromQuery] string? orderBy, [FromQuery] string? orderBy,

View File

@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Moonlight.ApiServer.Services; using Moonlight.ApiServer.Services;
using Moonlight.Shared.Http.Requests.Admin.Sys; using Moonlight.Shared.Http.Requests.Admin.Sys;

View File

@@ -2,10 +2,9 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MoonCore.Exceptions; using MoonCore.Common;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers; using MoonCore.Extended.Helpers;
using MoonCore.Models;
using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Services; using Moonlight.ApiServer.Services;
using Moonlight.ApiServer.Mappers; using Moonlight.ApiServer.Mappers;
@@ -27,7 +26,7 @@ public class UsersController : Controller
[HttpGet] [HttpGet]
[Authorize(Policy = "permissions:admin.users.get")] [Authorize(Policy = "permissions:admin.users.get")]
public async Task<ActionResult<ICountedData<UserResponse>>> GetAsync( public async Task<ActionResult<CountedData<UserResponse>>> GetAsync(
[FromQuery] int startIndex, [FromQuery] int startIndex,
[FromQuery] int count, [FromQuery] int count,
[FromQuery] string? orderBy, [FromQuery] string? orderBy,

View File

@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Implementations.LocalAuth;
using Moonlight.ApiServer.Interfaces; using Moonlight.ApiServer.Interfaces;
using Moonlight.Shared.Http.Responses.Auth; using Moonlight.Shared.Http.Responses.Auth;

View File

@@ -1,5 +1,4 @@
@using Moonlight.ApiServer.Database.Entities @using Moonlight.ApiServer.Database.Entities
@using Moonlight.Shared.Misc
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="bg-background text-base-content font-inter"> <html lang="en" class="bg-background text-base-content font-inter">

View File

@@ -1,11 +1,9 @@
using System.Security.Claims; using System.Security.Claims;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers; using MoonCore.Extended.Helpers;
using MoonCore.Helpers; using MoonCore.Helpers;

View File

@@ -0,0 +1,3 @@
namespace Moonlight.ApiServer;
public interface IAssemblyMarker;

View File

@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Configuration;
@@ -21,9 +20,10 @@ namespace Moonlight.ApiServer.Implementations.Startup;
public class CoreStartup : IPluginStartup public class CoreStartup : IPluginStartup
{ {
public Task BuildApplicationAsync(IServiceProvider serviceProvider, IHostApplicationBuilder builder) public void AddPlugin(WebApplicationBuilder builder)
{ {
var configuration = serviceProvider.GetRequiredService<AppConfiguration>(); var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
#region Api Docs #region Api Docs
@@ -138,13 +138,12 @@ public class CoreStartup : IPluginStartup
} }
#endregion #endregion
return Task.CompletedTask;
} }
public Task ConfigureApplicationAsync(IServiceProvider serviceProvider, IApplicationBuilder app) public void UsePlugin(WebApplication app)
{ {
var configuration = serviceProvider.GetRequiredService<AppConfiguration>(); var configuration = AppConfiguration.CreateEmpty();
app.Configuration.Bind(configuration);
#region Prometheus #region Prometheus
@@ -152,17 +151,14 @@ public class CoreStartup : IPluginStartup
app.UseOpenTelemetryPrometheusScrapingEndpoint(); app.UseOpenTelemetryPrometheusScrapingEndpoint();
#endregion #endregion
return Task.CompletedTask;
} }
public Task ConfigureEndpointsAsync(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder) public void MapPlugin(WebApplication app)
{ {
var configuration = serviceProvider.GetRequiredService<AppConfiguration>(); var configuration = AppConfiguration.CreateEmpty();
app.Configuration.Bind(configuration);
if (configuration.Development.EnableApiDocs) if (configuration.Development.EnableApiDocs)
routeBuilder.MapSwagger("/api/swagger/{documentName}"); app.MapSwagger("/api/swagger/{documentName}");
return Task.CompletedTask;
} }
} }

View File

@@ -13,7 +13,7 @@
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<PackageId>Moonlight.ApiServer</PackageId> <PackageId>Moonlight.ApiServer</PackageId>
<Version>2.1.11</Version> <Version>2.1.12</Version>
<Authors>Moonlight Panel</Authors> <Authors>Moonlight Panel</Authors>
<Description>A build of the api server for moonlight development</Description> <Description>A build of the api server for moonlight development</Description>
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl> <PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
@@ -27,9 +27,9 @@
<PackageReference Include="Hangfire.EntityFrameworkCore" Version="0.7.0"/> <PackageReference Include="Hangfire.EntityFrameworkCore" Version="0.7.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="9.0.9" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="9.0.9" />
<PackageReference Include="MoonCore" Version="2.0.1" /> <PackageReference Include="MoonCore" Version="2.0.4" />
<PackageReference Include="MoonCore.Extended" Version="1.4.0" /> <PackageReference Include="MoonCore.Extended" Version="1.4.2" />
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.2"/> <PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1"/> <PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1"/>

View File

@@ -1,12 +1,10 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Hosting;
namespace Moonlight.ApiServer.Plugins; namespace Moonlight.ApiServer.Plugins;
public interface IPluginStartup public interface IPluginStartup
{ {
public Task BuildApplicationAsync(IServiceProvider serviceProvider, IHostApplicationBuilder builder); public void AddPlugin(WebApplicationBuilder builder);
public Task ConfigureApplicationAsync(IServiceProvider serviceProvider, IApplicationBuilder app); public void UsePlugin(WebApplication app);
public Task ConfigureEndpointsAsync(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder); public void MapPlugin(WebApplication app);
} }

View File

@@ -3,19 +3,24 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using MoonCore.Permissions; using MoonCore.Permissions;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Implementations.LocalAuth; using Moonlight.ApiServer.Implementations.LocalAuth;
using Moonlight.ApiServer.Services; using Moonlight.ApiServer.Services;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private Task RegisterAuthAsync() private static void AddAuth(this WebApplicationBuilder builder)
{ {
WebApplicationBuilder.Services var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
builder.Services
.AddAuthentication(options => { options.DefaultScheme = "MainScheme"; }) .AddAuthentication(options => { options.DefaultScheme = "MainScheme"; })
.AddPolicyScheme("MainScheme", null, options => .AddPolicyScheme("MainScheme", null, options =>
{ {
@@ -42,15 +47,15 @@ public partial class Startup
options.TokenValidationParameters = new() options.TokenValidationParameters = new()
{ {
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
Configuration.Authentication.Secret configuration.Authentication.Secret
)), )),
ValidateIssuerSigningKey = true, ValidateIssuerSigningKey = true,
ValidateLifetime = true, ValidateLifetime = true,
ClockSkew = TimeSpan.Zero, ClockSkew = TimeSpan.Zero,
ValidateAudience = true, ValidateAudience = true,
ValidAudience = Configuration.PublicUrl, ValidAudience = configuration.PublicUrl,
ValidateIssuer = true, ValidateIssuer = true,
ValidIssuer = Configuration.PublicUrl ValidIssuer = configuration.PublicUrl
}; };
options.Events = new JwtBearerEvents() options.Events = new JwtBearerEvents()
@@ -81,11 +86,11 @@ public partial class Startup
}) })
.AddCookie("Session", null, options => .AddCookie("Session", null, options =>
{ {
options.ExpireTimeSpan = TimeSpan.FromDays(Configuration.Authentication.Sessions.ExpiresIn); options.ExpireTimeSpan = TimeSpan.FromDays(configuration.Authentication.Sessions.ExpiresIn);
options.Cookie = new CookieBuilder() options.Cookie = new CookieBuilder()
{ {
Name = Configuration.Authentication.Sessions.CookieName, Name = configuration.Authentication.Sessions.CookieName,
Path = "/", Path = "/",
IsEssential = true, IsEssential = true,
SecurePolicy = CookieSecurePolicy.SameAsRequest SecurePolicy = CookieSecurePolicy.SameAsRequest
@@ -150,16 +155,16 @@ public partial class Startup
options.SignInScheme = "Session"; options.SignInScheme = "Session";
}); });
WebApplicationBuilder.Services.AddAuthorization(); builder.Services.AddAuthorization();
WebApplicationBuilder.Services.AddAuthorizationPermissions(options => builder.Services.AddAuthorizationPermissions(options =>
{ {
options.ClaimName = "Permissions"; options.ClaimName = "Permissions";
options.Prefix = "permissions:"; options.Prefix = "permissions:";
}); });
WebApplicationBuilder.Services.AddScoped<UserAuthService>(); builder.Services.AddScoped<UserAuthService>();
WebApplicationBuilder.Services.AddScoped<ApiKeyAuthService>(); builder.Services.AddScoped<ApiKeyAuthService>();
// Setup data protection storage within storage folder // Setup data protection storage within storage folder
// so its persists in containers // so its persists in containers
@@ -167,23 +172,18 @@ public partial class Startup
Directory.CreateDirectory(dpKeyPath); Directory.CreateDirectory(dpKeyPath);
WebApplicationBuilder.Services builder.Services
.AddDataProtection() .AddDataProtection()
.PersistKeysToFileSystem( .PersistKeysToFileSystem(
new DirectoryInfo(dpKeyPath) new DirectoryInfo(dpKeyPath)
); );
WebApplicationBuilder.Services.AddScoped<UserDeletionService>(); builder.Services.AddScoped<UserDeletionService>();
return Task.CompletedTask;
} }
private Task UseAuthAsync() private static void UseAuth(this WebApplication application)
{ {
WebApplication.UseAuthentication(); application.UseAuthentication();
application.UseAuthorization();
WebApplication.UseAuthorization();
return Task.CompletedTask;
} }
} }

View File

@@ -1,63 +1,62 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MoonCore.Extended.Extensions; using MoonCore.Extended.Extensions;
using MoonCore.Extensions; using MoonCore.Extensions;
using MoonCore.Helpers; using MoonCore.Helpers;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Plugins;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private Task RegisterBaseAsync() private static void AddBase(this WebApplicationBuilder builder, IPluginStartup[] startups)
{ {
WebApplicationBuilder.Services.AutoAddServices<Startup>(); builder.Services.AutoAddServices<IAssemblyMarker>();
WebApplicationBuilder.Services.AddHttpClient(); builder.Services.AddHttpClient();
WebApplicationBuilder.Services.AddApiExceptionHandler(); builder.Services.AddApiExceptionHandler();
// Add pre-existing services
WebApplicationBuilder.Services.AddSingleton(Configuration);
// Configure controllers // Configure controllers
var mvcBuilder = WebApplicationBuilder.Services.AddControllers(); var mvcBuilder = builder.Services.AddControllers();
// Add plugin assemblies as application parts // Add plugin assemblies as application parts
foreach (var pluginStartup in PluginStartups.Select(x => x.GetType().Assembly).Distinct()) foreach (var pluginStartup in startups.Select(x => x.GetType().Assembly).Distinct())
mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly); mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly);
return Task.CompletedTask;
} }
private Task UseBaseAsync() private static void UseBase(this WebApplication application)
{ {
WebApplication.UseRouting(); application.UseRouting();
WebApplication.UseExceptionHandler(); application.UseExceptionHandler();
return Task.CompletedTask;
} }
private Task MapBaseAsync() private static void MapBase(this WebApplication application)
{ {
WebApplication.MapControllers(); application.MapControllers();
if (Configuration.Frontend.EnableHosting) // Frontend
WebApplication.MapFallbackToController("Index", "Frontend"); var configuration = AppConfiguration.CreateEmpty();
application.Configuration.Bind(configuration);
return Task.CompletedTask; if (configuration.Frontend.EnableHosting)
application.MapFallbackToController("Index", "Frontend");
} }
private Task ConfigureKestrelAsync() private static void ConfigureKestrel(this WebApplicationBuilder builder)
{ {
WebApplicationBuilder.WebHost.ConfigureKestrel(kestrelOptions => var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
builder.WebHost.ConfigureKestrel(kestrelOptions =>
{ {
var maxUploadInBytes = ByteConverter var maxUploadInBytes = ByteConverter
.FromMegaBytes(Configuration.Kestrel.UploadLimit) .FromMegaBytes(configuration.Kestrel.UploadLimit)
.Bytes; .Bytes;
kestrelOptions.Limits.MaxRequestBodySize = maxUploadInBytes; kestrelOptions.Limits.MaxRequestBodySize = maxUploadInBytes;
}); });
return Task.CompletedTask;
} }
} }

View File

@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MoonCore.EnvConfiguration; using MoonCore.EnvConfiguration;
@@ -6,30 +7,23 @@ using Moonlight.ApiServer.Configuration;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private async Task SetupAppConfigurationAsync() private static void AddConfiguration(this WebApplicationBuilder builder)
{ {
var configPath = Path.Combine("storage", "config.yml"); // Yaml
var yamlPath = Path.Combine("storage", "config.yml");
await YamlDefaultGenerator.GenerateAsync<AppConfiguration>(configPath); YamlDefaultGenerator.GenerateAsync<AppConfiguration>(yamlPath).Wait();
// Configure configuration (wow) builder.Configuration.AddYamlFile(yamlPath);
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddYamlFile(configPath); // Env
configurationBuilder.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_"); builder.Configuration.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_");
var configurationRoot = configurationBuilder.Build(); var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
// Retrieve configuration builder.Services.AddSingleton(configuration);
Configuration = AppConfiguration.CreateEmpty();
configurationRoot.Bind(Configuration);
}
private Task RegisterAppConfigurationAsync()
{
WebApplicationBuilder.Services.AddSingleton(Configuration);
return Task.CompletedTask;
} }
} }

View File

@@ -1,25 +1,17 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Extensions; using MoonCore.Extended.Extensions;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private Task RegisterDatabaseAsync() private static void AddDatabase(this WebApplicationBuilder builder)
{ {
WebApplicationBuilder.Services.AddDatabaseMappings(); builder.Services.AddDbAutoMigrations();
WebApplicationBuilder.Services.AddServiceCollectionAccessor(); builder.Services.AddDatabaseMappings();
WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>)); builder.Services.AddScoped(typeof(DatabaseRepository<>));
return Task.CompletedTask;
}
private async Task PrepareDatabaseAsync()
{
await WebApplication.Services.EnsureDatabaseMigratedAsync();
WebApplication.Services.GenerateDatabaseMappings();
} }
} }

View File

@@ -1,5 +1,6 @@
using Hangfire; using Hangfire;
using Hangfire.EntityFrameworkCore; using Hangfire.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -7,11 +8,11 @@ using Moonlight.ApiServer.Database;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private Task RegisterHangfireAsync() private static void AddMoonlightHangfire(this WebApplicationBuilder builder)
{ {
WebApplicationBuilder.Services.AddHangfire((provider, configuration) => builder.Services.AddHangfire((provider, configuration) =>
{ {
configuration.SetDataCompatibilityLevel(CompatibilityLevel.Version_180); configuration.SetDataCompatibilityLevel(CompatibilityLevel.Version_180);
configuration.UseSimpleAssemblyNameTypeSerializer(); configuration.UseSimpleAssemblyNameTypeSerializer();
@@ -23,26 +24,22 @@ public partial class Startup
}, new EFCoreStorageOptions()); }, new EFCoreStorageOptions());
}); });
WebApplicationBuilder.Services.AddHangfireServer(); builder.Services.AddHangfireServer();
WebApplicationBuilder.Logging.AddFilter( builder.Logging.AddFilter(
"Hangfire.Server.BackgroundServerProcess", "Hangfire.Server.BackgroundServerProcess",
LogLevel.Warning LogLevel.Warning
); );
WebApplicationBuilder.Logging.AddFilter( builder.Logging.AddFilter(
"Hangfire.BackgroundJobServer", "Hangfire.BackgroundJobServer",
LogLevel.Warning LogLevel.Warning
); );
return Task.CompletedTask;
} }
private Task UseHangfireAsync() private static void UseMoonlightHangfire(this WebApplication application)
{ {
if (WebApplication.Environment.IsDevelopment()) if (application.Environment.IsDevelopment())
WebApplication.UseHangfireDashboard(); application.UseHangfireDashboard();
return Task.CompletedTask;
} }
} }

View File

@@ -1,32 +1,24 @@
using System.Text.Json; using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MoonCore.Logging; using MoonCore.Logging;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private Task SetupLoggingAsync() private static void AddLogging(this WebApplicationBuilder builder)
{ {
var loggerFactory = new LoggerFactory(); // Logging providers
loggerFactory.AddAnsiConsole(); builder.Logging.ClearProviders();
Logger = loggerFactory.CreateLogger<Startup>(); builder.Logging.AddAnsiConsole();
builder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log"));
return Task.CompletedTask;
}
private async Task RegisterLoggingAsync()
{
// Configure application logging
WebApplicationBuilder.Logging.ClearProviders();
WebApplicationBuilder.Logging.AddAnsiConsole();
WebApplicationBuilder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log"));
// Logging levels // Logging levels
var logConfigPath = Path.Combine("storage", "logConfig.json"); var logConfigPath = Path.Combine("storage", "logConfig.json");
// Ensure logging config, add a default one is missing // Ensure default log levels exist
if (!File.Exists(logConfigPath)) if (!File.Exists(logConfigPath))
{ {
var defaultLogLevels = new Dictionary<string, string> var defaultLogLevels = new Dictionary<string, string>
@@ -38,25 +30,26 @@ public partial class Startup
}; };
var logLevelsJson = JsonSerializer.Serialize(defaultLogLevels); var logLevelsJson = JsonSerializer.Serialize(defaultLogLevels);
await File.WriteAllTextAsync(logConfigPath, logLevelsJson); File.WriteAllText(logConfigPath, logLevelsJson);
} }
// Add logging configuration // Read log levels
var logLevels = JsonSerializer.Deserialize<Dictionary<string, string>>( var logLevels = JsonSerializer.Deserialize<Dictionary<string, string>>(
await File.ReadAllTextAsync(logConfigPath) File.ReadAllText(logConfigPath)
)!; )!;
// Apply configured log levels
foreach (var level in logLevels) foreach (var level in logLevels)
WebApplicationBuilder.Logging.AddFilter(level.Key, Enum.Parse<LogLevel>(level.Value)); builder.Logging.AddFilter(level.Key, Enum.Parse<LogLevel>(level.Value));
// Mute exception handler middleware // Mute exception handler middleware
// https://github.com/dotnet/aspnetcore/issues/19740 // https://github.com/dotnet/aspnetcore/issues/19740
WebApplicationBuilder.Logging.AddFilter( builder.Logging.AddFilter(
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware",
LogLevel.Critical LogLevel.Critical
); );
WebApplicationBuilder.Logging.AddFilter( builder.Logging.AddFilter(
"Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware", "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware",
LogLevel.Critical LogLevel.Critical
); );

View File

@@ -1,12 +1,14 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moonlight.ApiServer.Configuration;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private Task PrintVersionAsync() private static void PrintVersionAsync()
{ {
// Fancy start console output... yes very fancy :> // Fancy start console output... yes very fancy :>
var rainbow = new Crayon.Rainbow(0.5); var rainbow = new Crayon.Rainbow(0.5);
@@ -21,23 +23,22 @@ public partial class Startup
} }
Console.WriteLine(); Console.WriteLine();
return Task.CompletedTask;
} }
private Task CreateStorageAsync() private static void CreateStorageAsync()
{ {
Directory.CreateDirectory("storage"); Directory.CreateDirectory("storage");
Directory.CreateDirectory(Path.Combine("storage", "logs")); Directory.CreateDirectory(Path.Combine("storage", "logs"));
return Task.CompletedTask;
} }
private Task RegisterCorsAsync() private static void AddMoonlightCors(this WebApplicationBuilder builder)
{ {
var allowedOrigins = Configuration.Kestrel.AllowedOrigins; var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
WebApplicationBuilder.Services.AddCors(options => var allowedOrigins = configuration.Kestrel.AllowedOrigins;
builder.Services.AddCors(options =>
{ {
var cors = new CorsPolicyBuilder(); var cors = new CorsPolicyBuilder();
@@ -60,14 +61,10 @@ public partial class Startup
cors.Build() cors.Build()
); );
}); });
return Task.CompletedTask;
} }
private Task UseCorsAsync() private static void UseMoonlightCors(this WebApplication application)
{ {
WebApplication.UseCors(); application.UseCors();
return Task.CompletedTask;
} }
} }

View File

@@ -1,87 +1,25 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using MoonCore.Logging;
using Moonlight.ApiServer.Plugins; using Moonlight.ApiServer.Plugins;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private IServiceProvider PluginLoadServiceProvider; private static void AddPlugins(this WebApplicationBuilder builder, IPluginStartup[] startups)
private IPluginStartup[] PluginStartups;
private Task InitializePluginsAsync()
{ {
// Create service provider for starting up foreach (var startup in startups)
var serviceCollection = new ServiceCollection(); startup.AddPlugin(builder);
serviceCollection.AddSingleton(Configuration);
serviceCollection.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddAnsiConsole();
});
PluginLoadServiceProvider = serviceCollection.BuildServiceProvider();
return Task.CompletedTask;
} }
private async Task HookPluginBuildAsync() private static void UsePlugins(this WebApplication application, IPluginStartup[] startups)
{ {
foreach (var pluginAppStartup in PluginStartups) foreach (var startup in startups)
{ startup.UsePlugin(application);
try
{
await pluginAppStartup.BuildApplicationAsync(PluginLoadServiceProvider, WebApplicationBuilder);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'BuildApp' for '{name}': {e}",
pluginAppStartup.GetType().FullName,
e
);
}
}
} }
private async Task HookPluginConfigureAsync() private static void MapPlugins(this WebApplication application, IPluginStartup[] startups)
{ {
foreach (var pluginAppStartup in PluginStartups) foreach (var startup in startups)
{ startup.MapPlugin(application);
try
{
await pluginAppStartup.ConfigureApplicationAsync(PluginLoadServiceProvider, WebApplication);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'ConfigureApp' for '{name}': {e}",
pluginAppStartup.GetType().FullName,
e
);
}
}
}
private async Task HookPluginEndpointsAsync()
{
foreach (var pluginEndpointStartup in PluginStartups)
{
try
{
await pluginEndpointStartup.ConfigureEndpointsAsync(PluginLoadServiceProvider, WebApplication);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'ConfigureEndpoints' for '{name}': {e}",
pluginEndpointStartup.GetType().FullName,
e
);
}
}
} }
} }

View File

@@ -1,25 +1,26 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Http.Hubs; using Moonlight.ApiServer.Http.Hubs;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
public Task RegisterSignalRAsync() private static void AddMoonlightSignalR(this WebApplicationBuilder builder)
{ {
var signalRBuilder = WebApplicationBuilder.Services.AddSignalR(); var configuration = AppConfiguration.CreateEmpty();
builder.Configuration.Bind(configuration);
if (Configuration.SignalR.UseRedis) var signalRBuilder = builder.Services.AddSignalR();
signalRBuilder.AddStackExchangeRedis(Configuration.SignalR.RedisConnectionString);
return Task.CompletedTask; if (configuration.SignalR.UseRedis)
signalRBuilder.AddStackExchangeRedis(configuration.SignalR.RedisConnectionString);
} }
public Task MapSignalRAsync() private static void MapMoonlightSignalR(this WebApplication application)
{ {
WebApplication.MapHub<DiagnoseHub>("/api/admin/system/diagnose/ws"); application.MapHub<DiagnoseHub>("/api/admin/system/diagnose/ws");
return Task.CompletedTask;
} }
} }

View File

@@ -1,69 +1,42 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Logging;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Plugins; using Moonlight.ApiServer.Plugins;
namespace Moonlight.ApiServer.Startup; namespace Moonlight.ApiServer.Startup;
public partial class Startup public static partial class Startup
{ {
private string[] Args; public static void AddMoonlight(this WebApplicationBuilder builder, IPluginStartup[] startups)
// Logger
public ILogger<Startup> Logger { get; private set; }
// Configuration
public AppConfiguration Configuration { get; private set; }
// WebApplication Stuff
public WebApplication WebApplication { get; private set; }
public WebApplicationBuilder WebApplicationBuilder { get; private set; }
public Task InitializeAsync(string[] args, IPluginStartup[]? plugins = null)
{ {
Args = args; PrintVersionAsync();
PluginStartups = plugins ?? []; CreateStorageAsync();
return Task.CompletedTask; builder.AddConfiguration();
builder.AddLogging();
builder.ConfigureKestrel();
builder.AddBase(startups);
builder.AddDatabase();
builder.AddAuth();
builder.AddMoonlightCors();
builder.AddMoonlightHangfire();
builder.AddMoonlightSignalR();
builder.AddPlugins(startups);
} }
public async Task AddMoonlightAsync(WebApplicationBuilder builder) public static void UseMoonlight(this WebApplication application, IPluginStartup[] startups)
{ {
WebApplicationBuilder = builder; application.UseBase();
application.UseMoonlightCors();
await PrintVersionAsync(); application.UseAuth();
application.UseMoonlightHangfire();
await CreateStorageAsync(); application.UsePlugins(startups);
await SetupAppConfigurationAsync();
await SetupLoggingAsync();
await InitializePluginsAsync();
await ConfigureKestrelAsync();
await RegisterAppConfigurationAsync();
await RegisterLoggingAsync();
await RegisterBaseAsync();
await RegisterDatabaseAsync();
await RegisterAuthAsync();
await RegisterCorsAsync();
await RegisterHangfireAsync();
await RegisterSignalRAsync();
await HookPluginBuildAsync();
} }
public async Task AddMoonlightAsync(WebApplication application) public static void MapMoonlight(this WebApplication application, IPluginStartup[] startups)
{ {
WebApplication = application; application.MapBase();
application.MapMoonlightSignalR();
await PrepareDatabaseAsync(); application.MapPlugins(startups);
await UseCorsAsync();
await UseBaseAsync();
await UseAuthAsync();
await UseHangfireAsync();
await HookPluginConfigureAsync();
await MapBaseAsync();
await MapSignalRAsync();
await HookPluginEndpointsAsync();
} }
} }

View File

@@ -12,8 +12,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.8" /> <PackageReference Include="MoonCore.PluginFramework" Version="1.0.9" />
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.2" /> <PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.9" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.9" PrivateAssets="all" />

View File

@@ -5,16 +5,12 @@ using Moonlight.Client.Startup;
var pluginLoader = new PluginLoader(); var pluginLoader = new PluginLoader();
pluginLoader.Initialize(); pluginLoader.Initialize();
var startup = new Startup(); var builder = WebAssemblyHostBuilder.CreateDefault(args);
await startup.InitializeAsync(pluginLoader.Instances); builder.AddMoonlight(pluginLoader.Instances);
var wasmHostBuilder = WebAssemblyHostBuilder.CreateDefault(args); var app = builder.Build();
await startup.AddMoonlightAsync(wasmHostBuilder); app.ConfigureMoonlight(pluginLoader.Instances);
var wasmApp = wasmHostBuilder.Build(); await app.RunAsync();
await startup.AddMoonlightAsync(wasmApp);
await wasmApp.RunAsync();

View File

@@ -0,0 +1,3 @@
namespace Moonlight.Client;
public interface IAssemblyMarker;

View File

@@ -7,14 +7,14 @@ namespace Moonlight.Client.Implementations;
public class CoreStartup : IPluginStartup public class CoreStartup : IPluginStartup
{ {
public Task BuildApplicationAsync(IServiceProvider serviceProvider, WebAssemblyHostBuilder builder) public void AddPlugin(WebAssemblyHostBuilder builder)
{ {
builder.Services.AddSingleton<ISidebarItemProvider, DefaultSidebarItemProvider>(); builder.Services.AddSingleton<ISidebarItemProvider, DefaultSidebarItemProvider>();
builder.Services.AddSingleton<IOverviewElementProvider, DefaultOverviewElementProvider>(); builder.Services.AddSingleton<IOverviewElementProvider, DefaultOverviewElementProvider>();
return Task.CompletedTask;
} }
public Task ConfigureApplicationAsync(IServiceProvider serviceProvider, WebAssemblyHost app) public void ConfigurePlugin(WebAssemblyHost app)
=> Task.CompletedTask; {
}
} }

View File

@@ -12,7 +12,7 @@
<PropertyGroup> <PropertyGroup>
<PackageTags>frontend</PackageTags> <PackageTags>frontend</PackageTags>
<PackageId>Moonlight.Client</PackageId> <PackageId>Moonlight.Client</PackageId>
<Version>2.1.11</Version> <Version>2.1.12</Version>
<Authors>Moonlight Panel</Authors> <Authors>Moonlight Panel</Authors>
<Description>A build of the client for moonlight development</Description> <Description>A build of the client for moonlight development</Description>
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl> <PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
@@ -25,8 +25,8 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.9" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.9" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.8" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.8" />
<PackageReference Include="MoonCore" Version="2.0.1" /> <PackageReference Include="MoonCore" Version="2.0.4" />
<PackageReference Include="MoonCore.Blazor.FlyonUi" Version="1.2.5" /> <PackageReference Include="MoonCore.Blazor.FlyonUi" Version="1.3.1" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" /> <PackageReference Include="System.Security.Claims" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -4,6 +4,6 @@ namespace Moonlight.Client.Plugins;
public interface IPluginStartup public interface IPluginStartup
{ {
public Task BuildApplicationAsync(IServiceProvider serviceProvider, WebAssemblyHostBuilder builder); public void AddPlugin(WebAssemblyHostBuilder builder);
public Task ConfigureApplicationAsync(IServiceProvider serviceProvider, WebAssemblyHost app); public void ConfigurePlugin(WebAssemblyHost app);
} }

View File

@@ -1,9 +1,7 @@
using MoonCore.Attributes; using MoonCore.Attributes;
using MoonCore.Helpers; using MoonCore.Helpers;
using MoonCore.Models;
using Moonlight.Shared.Http.Requests.Admin.Sys.Theme; using Moonlight.Shared.Http.Requests.Admin.Sys.Theme;
using Moonlight.Shared.Http.Responses.Admin; using Moonlight.Shared.Http.Responses.Admin;
using Moonlight.Shared.Misc;
namespace Moonlight.Client.Services; namespace Moonlight.Client.Services;

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MoonCore.Blazor.FlyonUi.Exceptions; using MoonCore.Blazor.FlyonUi.Exceptions;
using MoonCore.Permissions; using MoonCore.Permissions;
@@ -7,22 +8,20 @@ using Moonlight.Client.Services;
namespace Moonlight.Client.Startup; namespace Moonlight.Client.Startup;
public partial class Startup public static partial class Startup
{ {
private Task RegisterAuthenticationAsync() private static void AddAuth(this WebAssemblyHostBuilder builder)
{ {
WebAssemblyHostBuilder.Services.AddAuthorizationCore(); builder.Services.AddAuthorizationCore();
WebAssemblyHostBuilder.Services.AddCascadingAuthenticationState(); builder.Services.AddCascadingAuthenticationState();
WebAssemblyHostBuilder.Services.AddScoped<AuthenticationStateProvider, RemoteAuthStateProvider>(); builder.Services.AddScoped<AuthenticationStateProvider, RemoteAuthStateProvider>();
WebAssemblyHostBuilder.Services.AddScoped<IGlobalErrorFilter, UnauthenticatedErrorFilter>(); builder.Services.AddScoped<IGlobalErrorFilter, UnauthenticatedErrorFilter>();
WebAssemblyHostBuilder.Services.AddAuthorizationPermissions(options => builder.Services.AddAuthorizationPermissions(options =>
{ {
options.ClaimName = "Permissions"; options.ClaimName = "Permissions";
options.Prefix = "permissions:"; options.Prefix = "permissions:";
}); });
return Task.CompletedTask;
} }
} }

View File

@@ -1,39 +1,42 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MoonCore.Blazor.FlyonUi; using MoonCore.Blazor.FlyonUi;
using MoonCore.Blazor.FlyonUi.Exceptions;
using MoonCore.Extensions; using MoonCore.Extensions;
using MoonCore.Helpers; using MoonCore.Helpers;
using Moonlight.Client.Implementations;
using Moonlight.Client.Services; using Moonlight.Client.Services;
using Moonlight.Client.UI; using Moonlight.Client.UI;
namespace Moonlight.Client.Startup; namespace Moonlight.Client.Startup;
public partial class Startup public static partial class Startup
{ {
private Task RegisterBaseAsync() private static void AddBase(this WebAssemblyHostBuilder builder)
{ {
WebAssemblyHostBuilder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<App>("#app");
WebAssemblyHostBuilder.RootComponents.Add<HeadOutlet>("head::after"); builder.RootComponents.Add<HeadOutlet>("head::after");
WebAssemblyHostBuilder.Services.AddScoped(_ => builder.Services.AddScoped(_ =>
new HttpClient new HttpClient
{ {
BaseAddress = new Uri(Configuration.ApiUrl) BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
} }
); );
WebAssemblyHostBuilder.Services.AddScoped(sp => builder.Services.AddScoped(sp =>
{ {
var httpClient = sp.GetRequiredService<HttpClient>(); var httpClient = sp.GetRequiredService<HttpClient>();
return new HttpApiClient(httpClient); return new HttpApiClient(httpClient);
}); });
WebAssemblyHostBuilder.Services.AddFileManagerOperations(); builder.Services.AddFileManagerOperations();
WebAssemblyHostBuilder.Services.AddFlyonUiServices(); builder.Services.AddFlyonUiServices();
WebAssemblyHostBuilder.Services.AddScoped<ThemeService>(); builder.Services.AddScoped<ThemeService>();
WebAssemblyHostBuilder.Services.AutoAddServices<Startup>(); builder.Services.AutoAddServices<IAssemblyMarker>();
return Task.CompletedTask; builder.Services.AddScoped<IGlobalErrorFilter, LogErrorFilter>();
} }
} }

View File

@@ -1,30 +1,13 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MoonCore.Blazor.FlyonUi.Exceptions;
using MoonCore.Logging; using MoonCore.Logging;
using Moonlight.Client.Implementations;
namespace Moonlight.Client.Startup; namespace Moonlight.Client.Startup;
public partial class Startup public static partial class Startup
{ {
private Task SetupLoggingAsync() private static void AddLogging(this WebAssemblyHostBuilder builder)
{ {
var loggerFactory = new LoggerFactory(); builder.Logging.ClearProviders();
builder.Logging.AddAnsiConsole();
loggerFactory.AddAnsiConsole();
Logger = loggerFactory.CreateLogger<Startup>();
return Task.CompletedTask;
}
private Task RegisterLoggingAsync()
{
WebAssemblyHostBuilder.Logging.ClearProviders();
WebAssemblyHostBuilder.Logging.AddAnsiConsole();
WebAssemblyHostBuilder.Services.AddScoped<IGlobalErrorFilter, LogErrorFilter>();
return Task.CompletedTask;
} }
} }

View File

@@ -1,12 +1,8 @@
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Moonlight.Shared.Misc;
namespace Moonlight.Client.Startup; namespace Moonlight.Client.Startup;
public partial class Startup public static partial class Startup
{ {
private Task PrintVersionAsync() private static void PrintVersion()
{ {
// Fancy start console output... yes very fancy :> // Fancy start console output... yes very fancy :>
Console.Write("Running "); Console.Write("Running ");
@@ -23,30 +19,5 @@ public partial class Startup
} }
Console.WriteLine(); Console.WriteLine();
return Task.CompletedTask;
}
private async Task LoadConfigurationAsync()
{
try
{
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress);
var jsonText = await httpClient.GetStringAsync("frontend.json");
Configuration = JsonSerializer.Deserialize<FrontendConfiguration>(jsonText, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
})!;
WebAssemblyHostBuilder.Services.AddSingleton(Configuration);
}
catch (Exception e)
{
Logger.LogCritical("Unable to load configuration. Unable to continue: {e}", e);
throw;
}
} }
} }

View File

@@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MoonCore.Logging;
using Moonlight.Client.Plugins; using Moonlight.Client.Plugins;
using Moonlight.Client.Services; using Moonlight.Client.Services;
@@ -7,72 +7,25 @@ namespace Moonlight.Client.Startup;
public partial class Startup public partial class Startup
{ {
private IPluginStartup[] PluginStartups; private static void AddPlugins(this WebAssemblyHostBuilder builder, IPluginStartup[] startups)
private IServiceProvider PluginLoadServiceProvider;
private Task InitializePluginsAsync()
{ {
// Define minimal service collection foreach (var startup in startups)
var startupSc = new ServiceCollection(); startup.AddPlugin(builder);
// Create logging proxy // Get all assemblies and combine them into the application assembly service
startupSc.AddLogging(builder => // TODO: Consider rewriting this as it may not be that performant to do string checking to find distinct items
builder.Services.AddSingleton(new ApplicationAssemblyService()
{ {
builder.ClearProviders(); Assemblies = startups
builder.AddAnsiConsole();
});
PluginLoadServiceProvider = startupSc.BuildServiceProvider();
// Add application assembly service
var appAssemblyService = new ApplicationAssemblyService();
appAssemblyService.Assemblies.AddRange(
PluginStartups
.Select(x => x.GetType().Assembly) .Select(x => x.GetType().Assembly)
.Distinct() .DistinctBy(x => x.FullName)
); .ToList()
});
WebAssemblyHostBuilder.Services.AddSingleton(appAssemblyService);
return Task.CompletedTask;
} }
private async Task HookPluginBuildAsync() private static void ConfigurePlugins(this WebAssemblyHost app, IPluginStartup[] startups)
{ {
foreach (var pluginAppStartup in PluginStartups) foreach (var startup in startups)
{ startup.ConfigurePlugin(app);
try
{
await pluginAppStartup.BuildApplicationAsync(PluginLoadServiceProvider, WebAssemblyHostBuilder);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'BuildApp' for '{name}': {e}",
pluginAppStartup.GetType().FullName,
e
);
}
}
}
private async Task HookPluginConfigureAsync()
{
foreach (var pluginAppStartup in PluginStartups)
{
try
{
await pluginAppStartup.ConfigureApplicationAsync(PluginLoadServiceProvider, WebAssemblyHost);
}
catch (Exception e)
{
Logger.LogError(
"An error occured while processing 'ConfigureApp' for '{name}': {e}",
pluginAppStartup.GetType().FullName,
e
);
}
}
} }
} }

View File

@@ -1,48 +1,22 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Moonlight.Client.Plugins; using Moonlight.Client.Plugins;
using Moonlight.Shared.Misc;
namespace Moonlight.Client.Startup; namespace Moonlight.Client.Startup;
public partial class Startup public static partial class Startup
{ {
public ILogger<Startup> Logger { get; private set; } public static void AddMoonlight(this WebAssemblyHostBuilder builder, IPluginStartup[] startups)
// WebAssemblyHost
public WebAssemblyHostBuilder WebAssemblyHostBuilder { get; private set; }
public WebAssemblyHost WebAssemblyHost { get; private set; }
// Configuration
public FrontendConfiguration Configuration { get; private set; }
public Task InitializeAsync(IPluginStartup[]? plugins = null)
{ {
PluginStartups = plugins ?? []; PrintVersion();
return Task.CompletedTask; builder.AddLogging();
builder.AddBase();
builder.AddAuth();
builder.AddPlugins(startups);
} }
public async Task AddMoonlightAsync(WebAssemblyHostBuilder builder) public static void ConfigureMoonlight(this WebAssemblyHost app, IPluginStartup[] startups)
{ {
WebAssemblyHostBuilder = builder; app.ConfigurePlugins(startups);
await PrintVersionAsync();
await SetupLoggingAsync();
await LoadConfigurationAsync();
await InitializePluginsAsync();
await RegisterLoggingAsync();
await RegisterBaseAsync();
await RegisterAuthenticationAsync();
await HookPluginBuildAsync();
}
public async Task AddMoonlightAsync(WebAssemblyHost assemblyHost)
{
WebAssemblyHost = assemblyHost;
await HookPluginConfigureAsync();
} }
} }

View File

@@ -14,7 +14,6 @@
!rounded-xs !rounded-xs
!text-sm !text-sm
!w-2.5 !w-2.5
*:[grid-area:1/1]
*:first:rounded-tl-lg *:first:rounded-tl-lg
*:last:rounded-tr-lg *:last:rounded-tr-lg
-left-4 -left-4
@@ -43,18 +42,11 @@ alert-soft
align-bottom align-bottom
align-middle align-middle
animate-bounce animate-bounce
animate-ping
animate-spin animate-spin
aria-[current='page']:text-bg-soft-primary
avatar avatar
avatar-placeholder avatar-placeholder
badge badge
badge-error basis-full
badge-info
badge-outline
badge-primary
badge-soft
badge-success
bg-background bg-background
bg-background/60 bg-background/60
bg-base-100 bg-base-100
@@ -62,6 +54,7 @@ bg-base-150
bg-base-200 bg-base-200
bg-base-200! bg-base-200!
bg-base-200/50 bg-base-200/50
bg-base-250
bg-base-300 bg-base-300
bg-base-300/45 bg-base-300/45
bg-base-300/50 bg-base-300/50
@@ -157,7 +150,15 @@ disabled
divide-base-150/60 divide-base-150/60
divide-y divide-y
divider divider
drop-shadow drawer
drawer-body
drawer-bottom
drawer-end
drawer-footer
drawer-header
drawer-start
drawer-title
drawer-top
dropdown dropdown
dropdown-active dropdown-active
dropdown-disabled dropdown-disabled
@@ -217,14 +218,13 @@ gap-y-2.5
gap-y-3 gap-y-3
grid grid
grid-cols-1 grid-cols-1
grid-cols-4
grid-flow-col grid-flow-col
grow grow
grow-0 grow-0
h-10 h-10
h-2 h-2
h-3 h-3
h-32 h-36
h-64 h-64
h-8 h-8
h-auto h-auto
@@ -270,7 +270,6 @@ justify-center
justify-end justify-end
justify-start justify-start
label-text label-text
leading-3
leading-3.5 leading-3.5
leading-none leading-none
left-0 left-0
@@ -296,7 +295,6 @@ link-animated
link-hover link-hover
list-disc list-disc
list-inside list-inside
list-none
loading loading
loading-sm loading-sm
loading-spinner loading-spinner
@@ -310,9 +308,10 @@ max-lg:flex-col
max-lg:hidden max-lg:hidden
max-md:flex-wrap max-md:flex-wrap
max-md:justify-center max-md:justify-center
max-md:w-full
max-sm:hidden max-sm:hidden
max-w-64
max-w-7xl max-w-7xl
max-w-8
max-w-80 max-w-80
max-w-full max-w-full
max-w-lg max-w-lg
@@ -323,10 +322,16 @@ mb-1
mb-1.5 mb-1.5
mb-2 mb-2
mb-2.5 mb-2.5
mb-25
mb-3 mb-3
mb-4 mb-4
mb-5 mb-5
md:flex
md:gap-2
md:hidden!
md:items-center
md:min-w-md md:min-w-md
md:navbar-end
md:table-cell md:table-cell
md:text-3xl md:text-3xl
me-1 me-1
@@ -340,6 +345,7 @@ menu-disabled
menu-dropdown menu-dropdown
menu-dropdown-show menu-dropdown-show
menu-horizontal menu-horizontal
menu-sm
menu-title menu-title
min-h-0 min-h-0
min-h-svh min-h-svh
@@ -376,9 +382,12 @@ mt-5
mt-8 mt-8
mx-1 mx-1
mx-auto mx-auto
my-20
my-3 my-3
my-5 my-5
my-auto my-auto
navbar
navbar-start
object-cover object-cover
opacity-0 opacity-0
opacity-100 opacity-100
@@ -392,6 +401,9 @@ overflow-x-auto
overflow-y-auto overflow-y-auto
overlay-open:duration-50 overlay-open:duration-50
overlay-open:opacity-100 overlay-open:opacity-100
overlay-open:translate-x-0
overlay-open:translate-y-0
p-0
p-0.5 p-0.5
p-1 p-1
p-1.5 p-1.5
@@ -403,6 +415,7 @@ p-5
p-6 p-6
p-8 p-8
pb-1 pb-1
pe-1
pin-input pin-input
placeholder-base-content/60 placeholder-base-content/60
pointer-events-auto pointer-events-auto
@@ -427,7 +440,6 @@ py-2
py-2.5 py-2.5
py-6 py-6
radial-progress radial-progress
radio
range range
relative relative
resize resize
@@ -455,6 +467,8 @@ selected:select-active
shadow-base-300/20 shadow-base-300/20
shadow-lg shadow-lg
shadow-md shadow-md
shadow-none
shadow-sm
shadow-xs shadow-xs
shrink-0 shrink-0
size-10 size-10
@@ -462,8 +476,7 @@ size-12
size-4 size-4
size-5 size-5
size-8 size-8
skeleton size-8.5
skeleton-animated
sm:auto-cols-max sm:auto-cols-max
sm:flex sm:flex
sm:items-center sm:items-center
@@ -492,7 +505,6 @@ sr-only
stack stack
static static
status status
status-error
sticky sticky
success-message success-message
switch switch
@@ -548,26 +560,32 @@ tooltip
tooltip-content tooltip-content
top-0 top-0
top-1/2 top-1/2
top-3
top-full top-full
transform transform
transition transition
transition-all transition-all
transition-opacity transition-opacity
transition-transform
translate-x-0 translate-x-0
translate-x-full
truncate truncate
underline underline
uppercase uppercase
validate validate
w-0 w-0
w-0.5 w-0.5
w-12
w-13 w-13
w-20
w-4 w-4
w-56 w-56
w-6
w-64 w-64
w-fit w-fit
w-full w-full
whitespace-nowrap whitespace-nowrap
z-10 z-1
z-40 z-40
z-50 z-50
z-69
z-70

View File

@@ -64,6 +64,7 @@ badge-outline
badge-primary badge-primary
badge-soft badge-soft
badge-success badge-success
basis-full
bg-background bg-background
bg-background/60 bg-background/60
bg-base-100 bg-base-100
@@ -71,6 +72,7 @@ bg-base-150
bg-base-200 bg-base-200
bg-base-200! bg-base-200!
bg-base-200/50 bg-base-200/50
bg-base-250
bg-base-300 bg-base-300
bg-base-300/45 bg-base-300/45
bg-base-300/50 bg-base-300/50
@@ -189,6 +191,15 @@ disabled
divide-base-150/60 divide-base-150/60
divide-y divide-y
divider divider
drawer
drawer-body
drawer-bottom
drawer-end
drawer-footer
drawer-header
drawer-start
drawer-title
drawer-top
drop-shadow drop-shadow
dropdown dropdown
dropdown-active dropdown-active
@@ -275,6 +286,7 @@ h-14
h-2 h-2
h-3 h-3
h-32 h-32
h-36
h-6 h-6
h-64 h-64
h-8 h-8
@@ -372,7 +384,9 @@ max-lg:flex-col
max-lg:hidden max-lg:hidden
max-md:flex-wrap max-md:flex-wrap
max-md:justify-center max-md:justify-center
max-md:w-full
max-sm:hidden max-sm:hidden
max-w-64
max-w-7xl max-w-7xl
max-w-8 max-w-8
max-w-80 max-w-80
@@ -386,14 +400,20 @@ mb-1
mb-1.5 mb-1.5
mb-2 mb-2
mb-2.5 mb-2.5
mb-25
mb-3 mb-3
mb-4 mb-4
mb-5 mb-5
mb-8 mb-8
md:col-span-1 md:col-span-1
md:col-span-6 md:col-span-6
md:flex
md:gap-2
md:grid-cols-2 md:grid-cols-2
md:hidden!
md:items-center
md:min-w-md md:min-w-md
md:navbar-end
md:table-cell md:table-cell
md:text-3xl md:text-3xl
me-1 me-1
@@ -408,6 +428,7 @@ menu-dropdown
menu-dropdown-show menu-dropdown-show
menu-focus menu-focus
menu-horizontal menu-horizontal
menu-sm
menu-title menu-title
min-h-0 min-h-0
min-h-full min-h-full
@@ -452,10 +473,13 @@ mt-8
mx-1 mx-1
mx-auto mx-auto
my-2.5 my-2.5
my-20
my-3 my-3
my-5 my-5
my-8 my-8
my-auto my-auto
navbar
navbar-start
object-cover object-cover
opacity-0 opacity-0
opacity-100 opacity-100
@@ -470,6 +494,9 @@ overflow-x-hidden
overflow-y-auto overflow-y-auto
overlay-open:duration-50 overlay-open:duration-50
overlay-open:opacity-100 overlay-open:opacity-100
overlay-open:translate-x-0
overlay-open:translate-y-0
p-0
p-0.5 p-0.5
p-1 p-1
p-1.5 p-1.5
@@ -481,6 +508,7 @@ p-5
p-6 p-6
p-8 p-8
pb-1 pb-1
pe-1
pe-1.5 pe-1.5
pin-input pin-input
pin-input-underline pin-input-underline
@@ -548,6 +576,7 @@ shadow
shadow-base-300/20 shadow-base-300/20
shadow-lg shadow-lg
shadow-md shadow-md
shadow-none
shadow-sm shadow-sm
shadow-xs shadow-xs
shrink-0 shrink-0
@@ -557,6 +586,7 @@ size-4
size-5 size-5
size-7 size-7
size-8 size-8
size-8.5
skeleton skeleton
skeleton-animated skeleton-animated
sm:auto-cols-max sm:auto-cols-max
@@ -686,6 +716,7 @@ tooltip
tooltip-content tooltip-content
top-0 top-0
top-1/2 top-1/2
top-3
top-full top-full
tracking-tight tracking-tight
tracking-wide tracking-wide
@@ -693,7 +724,9 @@ transform
transition transition
transition-all transition-all
transition-opacity transition-opacity
transition-transform
translate-x-0 translate-x-0
translate-x-full
truncate truncate
underline underline
uppercase uppercase
@@ -703,9 +736,11 @@ w-0
w-0.5 w-0.5
w-12 w-12
w-13 w-13
w-20
w-32 w-32
w-4 w-4
w-56 w-56
w-6
w-64 w-64
w-8 w-8
w-auto w-auto
@@ -718,3 +753,5 @@ z-1
z-10 z-10
z-40 z-40
z-50 z-50
z-69
z-70

View File

@@ -1,4 +1,5 @@
@using Moonlight.Client.UI.Partials @using MoonCore.Blazor.FlyonUi.Drawers
@using Moonlight.Client.UI.Partials
@using MoonCore.Blazor.FlyonUi.Files.Drop @using MoonCore.Blazor.FlyonUi.Files.Drop
@inherits LayoutComponentBase @inherits LayoutComponentBase
@@ -20,6 +21,7 @@
<ToastLauncher/> <ToastLauncher/>
<ModalLauncher/> <ModalLauncher/>
<DrawerLauncher />
<DropHandler /> <DropHandler />

View File

@@ -1,11 +1,12 @@
@page "/admin/api" @page "/admin/api"
@using MoonCore.Blazor.FlyonUi.Common
@using MoonCore.Helpers @using MoonCore.Helpers
@using MoonCore.Models
@using Moonlight.Shared.Http.Responses.Admin.ApiKeys @using Moonlight.Shared.Http.Responses.Admin.ApiKeys
@using MoonCore.Blazor.FlyonUi.Grid @using MoonCore.Blazor.FlyonUi.Grid
@using MoonCore.Blazor.FlyonUi.Grid.Columns @using MoonCore.Blazor.FlyonUi.Grid.Columns
@using MoonCore.Blazor.FlyonUi.Grid.ToolbarItems @using MoonCore.Blazor.FlyonUi.Grid.ToolbarItems
@using MoonCore.Common
@inject HttpApiClient ApiClient @inject HttpApiClient ApiClient
@inject AlertService AlertService @inject AlertService AlertService
@@ -51,9 +52,7 @@
<DataGrid @ref="Grid" <DataGrid @ref="Grid"
TGridItem="ApiKeyResponse" TGridItem="ApiKeyResponse"
ItemsProvider="ItemsProviderAsync" ItemSource="ItemSource">
EnableFiltering="true"
EnablePagination="true">
<PropertyColumn Field="x => x.Id" Sortable="true" /> <PropertyColumn Field="x => x.Id" Sortable="true" />
<PropertyColumn Field="x => x.Description" /> <PropertyColumn Field="x => x.Description" />
<TemplateColumn Sortable="true" Title="Expires At"> <TemplateColumn Sortable="true" Title="Expires At">
@@ -92,26 +91,24 @@
{ {
private DataGrid<ApiKeyResponse> Grid; private DataGrid<ApiKeyResponse> Grid;
private async Task<DataGridItemResult<ApiKeyResponse>> ItemsProviderAsync(DataGridItemRequest request) private ItemSource<ApiKeyResponse> ItemSource => ItemSourceFactory.From(LoadItemsAsync);
{
var query = $"?startIndex={request.StartIndex}&count={request.Count}";
if (!string.IsNullOrEmpty(request.SortColumn)) private async Task<IEnumerable<ApiKeyResponse>> LoadItemsAsync(
int startIndex, int count, string? filter, SortOption? sortOption
)
{ {
var dir = request.SortDirection == SortState.Descending ? "desc" : "asc"; var query = $"?startIndex={startIndex}&count={count}";
query += $"&orderBy={request.SortColumn}&orderByDir={dir}";
if (sortOption != null)
{
var dir = sortOption.Direction == SortDirection.Descending ? "desc" : "asc";
query += $"&orderBy={sortOption.Column}&orderByDir={dir}";
} }
if (!string.IsNullOrEmpty(request.Filter)) if (!string.IsNullOrEmpty(filter))
query += $"&filter={request.Filter}"; query += $"&filter={filter}";
var data = await ApiClient.GetJson<CountedData<ApiKeyResponse>>($"api/admin/apikeys{query}"); return await ApiClient.GetJson<CountedData<ApiKeyResponse>>($"api/admin/apikeys{query}");
return new()
{
Items = data.Items,
TotalCount = data.TotalCount
};
} }
private async Task DeleteAsync(ApiKeyResponse apiKeyResponse) private async Task DeleteAsync(ApiKeyResponse apiKeyResponse)

View File

@@ -2,12 +2,13 @@
@using System.Text.Json @using System.Text.Json
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@using MoonCore.Blazor.FlyonUi.Common
@using MoonCore.Blazor.FlyonUi.Grid @using MoonCore.Blazor.FlyonUi.Grid
@using MoonCore.Blazor.FlyonUi.Grid.Columns @using MoonCore.Blazor.FlyonUi.Grid.Columns
@using MoonCore.Blazor.FlyonUi.Grid.ToolbarItems @using MoonCore.Blazor.FlyonUi.Grid.ToolbarItems
@using MoonCore.Blazor.FlyonUi.Helpers @using MoonCore.Blazor.FlyonUi.Helpers
@using MoonCore.Common
@using MoonCore.Helpers @using MoonCore.Helpers
@using MoonCore.Models
@using Moonlight.Client.Models @using Moonlight.Client.Models
@using Moonlight.Client.Services @using Moonlight.Client.Services
@using Moonlight.Shared.Http.Requests.Admin.Sys.Theme @using Moonlight.Shared.Http.Requests.Admin.Sys.Theme
@@ -30,9 +31,7 @@
<div class="my-8"> <div class="my-8">
<DataGrid TGridItem="ThemeResponse" <DataGrid TGridItem="ThemeResponse"
ItemsProvider="ItemsProviderAsync" ItemSource="ItemSource">
EnableFiltering="true"
EnablePagination="true">
<PropertyColumn Field="x => x.Id" Sortable="true"/> <PropertyColumn Field="x => x.Id" Sortable="true"/>
<TemplateColumn Title="Name" Sortable="true"> <TemplateColumn Title="Name" Sortable="true">
@@ -69,7 +68,8 @@
</a> </a>
} }
<a @onclick="() => ExportAsync(context)" @onclick:preventDefault href="#" class="flex items-center mr-2 sm:mr-3"> <a @onclick="() => ExportAsync(context)" @onclick:preventDefault href="#"
class="flex items-center mr-2 sm:mr-3">
<i class="text-success icon-download me-1"></i> <i class="text-success icon-download me-1"></i>
<span class="text-success">Export</span> <span class="text-success">Export</span>
</a> </a>
@@ -104,27 +104,24 @@
@code @code
{ {
private DataGrid<ThemeResponse> Grid; private DataGrid<ThemeResponse> Grid;
private ItemSource<ThemeResponse> ItemSource => ItemSourceFactory.From(LoadItemsAsync);
private async Task<DataGridItemResult<ThemeResponse>> ItemsProviderAsync(DataGridItemRequest request) private async Task<IEnumerable<ThemeResponse>> LoadItemsAsync(
int startIndex, int count, string? filter, SortOption? sortOption
)
{ {
var query = $"?startIndex={request.StartIndex}&count={request.Count}"; var query = $"?startIndex={startIndex}&count={count}";
if (!string.IsNullOrEmpty(request.SortColumn)) if (sortOption != null)
{ {
var dir = request.SortDirection == SortState.Descending ? "desc" : "asc"; var dir = sortOption.Direction == SortDirection.Descending ? "desc" : "asc";
query += $"&orderBy={request.SortColumn}&orderByDir={dir}"; query += $"&orderBy={sortOption.Column}&orderByDir={dir}";
} }
if (!string.IsNullOrEmpty(request.Filter)) if (!string.IsNullOrEmpty(filter))
query += $"&filter={request.Filter}"; query += $"&filter={filter}";
var data = await ApiClient.GetJson<CountedData<ThemeResponse>>($"api/admin/system/customisation/themes{query}"); return await ApiClient.GetJson<CountedData<ThemeResponse>>($"api/admin/system/customisation/themes{query}");
return new()
{
Items = data.Items,
TotalCount = data.TotalCount
};
} }
private async Task ImportAsync(InputFileChangeEventArgs eventArgs) private async Task ImportAsync(InputFileChangeEventArgs eventArgs)

View File

@@ -1,10 +1,11 @@
@page "/admin/users" @page "/admin/users"
@using MoonCore.Blazor.FlyonUi.Common
@using MoonCore.Helpers @using MoonCore.Helpers
@using MoonCore.Models
@using Moonlight.Shared.Http.Responses.Admin.Users @using Moonlight.Shared.Http.Responses.Admin.Users
@using MoonCore.Blazor.FlyonUi.Grid @using MoonCore.Blazor.FlyonUi.Grid
@using MoonCore.Blazor.FlyonUi.Grid.Columns @using MoonCore.Blazor.FlyonUi.Grid.Columns
@using MoonCore.Common
@inject HttpApiClient ApiClient @inject HttpApiClient ApiClient
@inject AlertService AlertService @inject AlertService AlertService
@@ -19,10 +20,7 @@
</div> </div>
<DataGrid @ref="Grid" <DataGrid @ref="Grid"
TGridItem="UserResponse" ItemSource="ItemSource">
ItemsProvider="ItemsProviderAsync"
EnableFiltering="true"
EnablePagination="true">
<PropertyColumn Field="x => x.Id" Sortable="true"/> <PropertyColumn Field="x => x.Id" Sortable="true"/>
<PropertyColumn Field="x => x.Username" Sortable="true"/> <PropertyColumn Field="x => x.Username" Sortable="true"/>
<PropertyColumn Field="x => x.Email" Sortable="true"/> <PropertyColumn Field="x => x.Email" Sortable="true"/>
@@ -45,27 +43,24 @@
@code @code
{ {
private DataGrid<UserResponse> Grid; private DataGrid<UserResponse> Grid;
private ItemSource<UserResponse> ItemSource => ItemSourceFactory.From(LoadItemsAsync);
private async Task<DataGridItemResult<UserResponse>> ItemsProviderAsync(DataGridItemRequest request) private async Task<IEnumerable<UserResponse>> LoadItemsAsync(
int startIndex, int count, string? filter, SortOption? sortOption
)
{ {
var query = $"?startIndex={request.StartIndex}&count={request.Count}"; var query = $"?startIndex={startIndex}&count={count}";
if (!string.IsNullOrEmpty(request.SortColumn)) if (sortOption != null)
{ {
var dir = request.SortDirection == SortState.Descending ? "desc" : "asc"; var dir = sortOption.Direction == SortDirection.Descending ? "desc" : "asc";
query += $"&orderBy={request.SortColumn}&orderByDir={dir}"; query += $"&orderBy={sortOption.Column}&orderByDir={dir}";
} }
if (!string.IsNullOrEmpty(request.Filter)) if (!string.IsNullOrEmpty(filter))
query += $"&filter={request.Filter}"; query += $"&filter={filter}";
var data = await ApiClient.GetJson<CountedData<UserResponse>>($"api/admin/users{query}"); return await ApiClient.GetJson<CountedData<UserResponse>>($"api/admin/users{query}");
return new()
{
Items = data.Items,
TotalCount = data.TotalCount
};
} }
private async Task DeleteAsync(UserResponse response) private async Task DeleteAsync(UserResponse response)

View File

@@ -9,7 +9,7 @@
<Title>Moonlight.Shared</Title> <Title>Moonlight.Shared</Title>
<PackageTags>shared</PackageTags> <PackageTags>shared</PackageTags>
<PackageId>Moonlight.Shared</PackageId> <PackageId>Moonlight.Shared</PackageId>
<Version>2.1.11</Version> <Version>2.1.12</Version>
<Authors>Moonlight Panel</Authors> <Authors>Moonlight Panel</Authors>
<Description>A build of the shared classes for moonlight development</Description> <Description>A build of the shared classes for moonlight development</Description>
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl> <PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>