10 Commits

Author SHA1 Message Date
3dff8c8f6d Fixed IL trimming removing used icons from build output in system settings tab 2026-02-21 22:46:34 +01:00
95a848e571 Merge pull request 'Implemented extendable system settings tab. Started implementing white labeling settings' (#21) from feat/SystemSettings into v2.1
Reviewed-on: #21
2026-02-21 21:22:06 +00:00
9d557eea4e Implemented extendable system settings tab. Started implementing white labeling settings 2026-02-21 22:20:51 +01:00
94c1aac0ac Merge pull request 'Added plugins hooks for layout related options' (#20) from feat/LayoutMiddleware into v2.1
Reviewed-on: #20
2026-02-20 15:28:24 +00:00
3bddd64d91 Added page hooks for main layout 2026-02-20 16:25:01 +01:00
5ad7a6db7b Added hook option for plugins to inject into the main layout before the router 2026-02-20 12:28:22 +01:00
9b9272cd6e Fixed nuget package build failing after changing shadcnblazor class list location 2026-02-20 09:41:35 +01:00
31cf34ed04 Merge pull request 'Improved css build and initialization of frontend plugins' (#19) from feat/PluginImprovements into v2.1
Some checks failed
Dev Publish: Nuget / Publish Dev Packages (push) Failing after 40s
Reviewed-on: #19
2026-02-20 08:38:27 +00:00
a9b0020131 Upgraded to shadcnblazor 1.0.13. Added transitive mapping copying and prefixed target to stop any collisions 2026-02-20 09:35:43 +01:00
e3b432aae6 Removed unused startup interface. Added plugin list to frontend plugin initialization 2026-02-20 09:20:29 +01:00
26 changed files with 455 additions and 58 deletions

View File

@@ -34,7 +34,7 @@ jobs:
# Publish frontend
# We need to build it first so the class list files generate
- name: Build project
run: dotnet build Moonlight.Frontend --configuration Debug
run: dotnet build Hosts/Moonlight.Frontend.Host --configuration Debug
- name: Build tailwind styles and extract class list
working-directory: Hosts/Moonlight.Frontend.Host/Styles

View File

@@ -1,11 +1,11 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "../../../Moonlight.Frontend/bin/ShadcnBlazor/scrollbar.css";
@import "../../../Moonlight.Frontend/bin/ShadcnBlazor/default-theme.css";
@import "../bin/ShadcnBlazor/scrollbar.css";
@import "../bin/ShadcnBlazor/default-theme.css";
@import "./theme.css";
@source "../../../Moonlight.Frontend/bin/ShadcnBlazor/ShadcnBlazor.map";
@source "../bin/ShadcnBlazor/ShadcnBlazor.map";
@source "../../../Moonlight.Api/**/*.razor";
@source "../../../Moonlight.Api/**/*.cs";

View File

@@ -0,0 +1,6 @@
namespace Moonlight.Api.Constants;
public class FrontendSettingConstants
{
public const string Name = "Moonlight.Frontend.Name";
}

View File

@@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Moonlight.Api.Constants;
using Moonlight.Api.Services;
using Moonlight.Shared;
using Moonlight.Shared.Http.Requests.Admin.Settings;
using Moonlight.Shared.Http.Responses.Admin.Settings;
namespace Moonlight.Api.Http.Controllers.Admin.Settings;
[ApiController]
[Authorize(Policy = Permissions.System.Settings)]
[Route("api/admin/system/settings/whiteLabeling")]
public class WhiteLabelingController : Controller
{
private readonly SettingsService SettingsService;
private readonly FrontendService FrontendService;
public WhiteLabelingController(SettingsService settingsService, FrontendService frontendService)
{
SettingsService = settingsService;
FrontendService = frontendService;
}
[HttpGet]
public async Task<ActionResult<WhiteLabelingDto>> GetAsync()
{
var dto = new WhiteLabelingDto
{
Name = await SettingsService.GetValueAsync<string>(FrontendSettingConstants.Name) ?? "Moonlight"
};
return dto;
}
[HttpPost]
public async Task<ActionResult<WhiteLabelingDto>> PostAsync([FromBody] SetWhiteLabelingDto request)
{
await SettingsService.SetValueAsync(FrontendSettingConstants.Name, request.Name);
await FrontendService.ResetCacheAsync();
var dto = new WhiteLabelingDto
{
Name = await SettingsService.GetValueAsync<string>(FrontendSettingConstants.Name) ?? "Moonlight"
};
return dto;
}
}

View File

@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Moonlight.Api.Configuration;
using Moonlight.Api.Constants;
using Moonlight.Api.Database;
using Moonlight.Api.Database.Entities;
using Moonlight.Api.Models;
@@ -13,14 +14,16 @@ public class FrontendService
private readonly IMemoryCache Cache;
private readonly DatabaseRepository<Theme> ThemeRepository;
private readonly IOptions<FrontendOptions> Options;
private readonly SettingsService SettingsService;
private const string CacheKey = $"Moonlight.{nameof(FrontendService)}.{nameof(GetConfigurationAsync)}";
public FrontendService(IMemoryCache cache, DatabaseRepository<Theme> themeRepository, IOptions<FrontendOptions> options)
public FrontendService(IMemoryCache cache, DatabaseRepository<Theme> themeRepository, IOptions<FrontendOptions> options, SettingsService settingsService)
{
Cache = cache;
ThemeRepository = themeRepository;
Options = options;
SettingsService = settingsService;
}
public async Task<FrontendConfiguration> GetConfigurationAsync()
@@ -35,7 +38,9 @@ public class FrontendService
.Query()
.FirstOrDefaultAsync(x => x.IsEnabled);
var config = new FrontendConfiguration("Moonlight", theme?.CssContent);
var name = await SettingsService.GetValueAsync<string>(FrontendSettingConstants.Name);
var config = new FrontendConfiguration(name ?? "Moonlight", theme?.CssContent);
Cache.Set(CacheKey, config, TimeSpan.FromMinutes(Options.Value.CacheMinutes));

View File

@@ -0,0 +1,26 @@
using System.Diagnostics.CodeAnalysis;
using Moonlight.Frontend.Interfaces;
namespace Moonlight.Frontend.Configuration;
public class LayoutMiddlewareOptions
{
public IReadOnlyList<Type> Components => InnerComponents;
private readonly List<Type> InnerComponents = new();
public void Add<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : LayoutMiddlewareBase
{
InnerComponents.Add(typeof(T));
}
public void Insert<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(int index) where T : LayoutMiddlewareBase
{
InnerComponents.Insert(index, typeof(T));
}
public void Remove<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : LayoutMiddlewareBase
{
InnerComponents.Remove(typeof(T));
}
}

View File

@@ -0,0 +1,33 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
namespace Moonlight.Frontend.Configuration;
public class LayoutPageOptions
{
public IReadOnlyList<LayoutPageComponent> Components => InnerComponents;
private readonly List<LayoutPageComponent> InnerComponents = new();
public void Add<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(LayoutPageSlot slot, int order)
where T : ComponentBase
=> Add(typeof(T), slot, order);
public void Add(Type componentType, LayoutPageSlot slot, int order)
=> InnerComponents.Add(new LayoutPageComponent(componentType, order, slot));
public void Remove<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
where T : ComponentBase
=> Remove(typeof(T));
public void Remove(Type componentType)
=> InnerComponents.RemoveAll(x => x.ComponentType == componentType);
}
public record LayoutPageComponent(Type ComponentType, int Order, LayoutPageSlot Slot);
public enum LayoutPageSlot
{
Header = 0,
Footer = 1
}

View File

@@ -0,0 +1,35 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
namespace Moonlight.Frontend.Configuration;
public class SystemSettingsOptions
{
public IReadOnlyList<SystemSettingsPage> Components => InnerComponents;
private readonly List<SystemSettingsPage> InnerComponents = new();
public void Add<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TIcon,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TComponent>(string name, string description,
int order)
where TIcon : ComponentBase where TComponent : ComponentBase
=> Add(name, description, order, typeof(TIcon), typeof(TComponent));
public void Add(string name, string description, int order, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type iconComponent, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type component)
=> InnerComponents.Add(new SystemSettingsPage(name, description, order, iconComponent, component));
public void Remove<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TComponent>()
where TComponent : ComponentBase
=> Remove(typeof(TComponent));
public void Remove([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type componentType)
=> InnerComponents.RemoveAll(x => x.ComponentType == componentType);
}
public record SystemSettingsPage(
string Name,
string Description,
int Order,
Type IconComponentType,
Type ComponentType
);

View File

@@ -29,6 +29,7 @@ public sealed class PermissionProvider : IPermissionProvider
new Permission(Permissions.System.Diagnose, "Diagnose", "Run diagnostics"),
new Permission(Permissions.System.Versions, "Versions", "Look at the available versions"),
new Permission(Permissions.System.Instance, "Instance", "Update the moonlight instance and add plugins"),
new Permission(Permissions.System.Settings, "Settings", "Change settings of the instance"),
]),
new PermissionCategory("API Keys", typeof(KeyIcon), [
new Permission(Permissions.ApiKeys.Create, "Create", "Create new API keys"),

View File

@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Components;
namespace Moonlight.Frontend.Interfaces;
public abstract class LayoutMiddlewareBase : ComponentBase
{
[Parameter] public RenderFragment ChildContent { get; set; }
}

View File

@@ -24,8 +24,8 @@
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1"/>
<PackageReference Include="Riok.Mapperly" Version="4.3.1-next.0"/>
<PackageReference Include="ShadcnBlazor" Version="1.0.11" />
<PackageReference Include="ShadcnBlazor.Extras" Version="1.0.11" />
<PackageReference Include="ShadcnBlazor" Version="1.0.13" />
<PackageReference Include="ShadcnBlazor.Extras" Version="1.0.13" />
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2" />
</ItemGroup>
@@ -36,5 +36,6 @@
<ItemGroup>
<None Include="Styles/*" Pack="true" PackagePath="Styles/" />
<None Include="Moonlight.Frontend.targets" Pack="true" PackagePath="build\Moonlight.Frontend.targets" />
<None Include="Moonlight.Frontend.targets" Pack="true" PackagePath="buildTransitive\Moonlight.Frontend.targets" />
</ItemGroup>
</Project>

View File

@@ -5,7 +5,7 @@
</MoonlightCssClassDir>
</PropertyGroup>
<Target Name="CopyContents" BeforeTargets="Build">
<Target Name="Moonlight_CopyContents" BeforeTargets="Build">
<ItemGroup>
<Files Include="$(MSBuildThisFileDirectory)..\Styles\**\*" />
</ItemGroup>

View File

@@ -5,6 +5,13 @@ namespace Moonlight.Frontend;
public abstract class MoonlightPlugin : IPluginModule
{
protected MoonlightPlugin[] Plugins { get; private set; }
public void Initialize(MoonlightPlugin[] plugins)
{
Plugins = plugins;
}
public virtual void PreBuild(WebAssemblyHostBuilder builder){}
public virtual void PostBuild(WebAssemblyHost application){}
}

View File

@@ -1,10 +0,0 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using SimplePlugin.Abstractions;
namespace Moonlight.Frontend.Startup;
public interface IAppStartup : IPluginModule
{
public void PreBuild(WebAssemblyHostBuilder builder);
public void PostBuild(WebAssemblyHost application);
}

View File

@@ -1,3 +1,4 @@
using LucideBlazor;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
@@ -6,6 +7,7 @@ using Moonlight.Frontend.Implementations;
using Moonlight.Frontend.Interfaces;
using Moonlight.Frontend.Services;
using Moonlight.Frontend.UI;
using Moonlight.Frontend.UI.Admin.Settings;
using ShadcnBlazor;
using ShadcnBlazor.Extras;
@@ -19,7 +21,7 @@ public partial class Startup
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddShadcnBlazor();
builder.Services.AddShadcnBlazorExtras();
@@ -31,5 +33,14 @@ public partial class Startup
{
options.Assemblies.Add(typeof(Startup).Assembly);
});
builder.Services.Configure<SystemSettingsOptions>(options =>
{
options.Add<TextCursorInputIcon, WhiteLabelingSetting>(
"White Labeling",
"Settings for white labeling your moonlight instance",
0
);
});
}
}

View File

@@ -10,11 +10,17 @@ public static class StartupHandler
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Setting up context
foreach (var plugin in plugins)
plugin.Initialize(plugins);
// Stage 1: Pre Build
foreach (var plugin in plugins)
plugin.PreBuild(builder);
var app = builder.Build();
// Stage 2: Post Build
foreach(var plugin in plugins)
plugin.PostBuild(app);

View File

@@ -0,0 +1,75 @@
@using LucideBlazor
@using Moonlight.Frontend.Helpers
@using Moonlight.Frontend.Services
@using Moonlight.Shared.Http
@using Moonlight.Shared.Http.Requests.Admin.Settings
@using Moonlight.Shared.Http.Responses.Admin.Settings
@using ShadcnBlazor.Extras.Common
@using ShadcnBlazor.Extras.Forms
@using ShadcnBlazor.Extras.Toasts
@using ShadcnBlazor.Fields
@using ShadcnBlazor.Inputs
@inject HttpClient HttpClient
@inject ToastService ToastService
@inject FrontendService FrontendService
<LazyLoader Load="LoadAsync">
<EnhancedEditForm Model="Request" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator />
<FieldSet>
<FormValidationSummary />
<FieldGroup>
<Field>
<FieldLabel>Name</FieldLabel>
<TextInputField @bind-Value="Request.Name"/>
</Field>
</FieldGroup>
</FieldSet>
<SubmitButton ClassName="mt-3">
<SaveIcon/>
Save
</SubmitButton>
</EnhancedEditForm>
</LazyLoader>
@code
{
private SetWhiteLabelingDto Request;
private async Task LoadAsync(LazyLoader _)
{
var dto = await HttpClient.GetFromJsonAsync<WhiteLabelingDto>(
"api/admin/system/settings/whiteLabeling",
SerializationContext.Default.Options
);
Request = new SetWhiteLabelingDto()
{
Name = dto!.Name
};
}
private async Task<bool> OnValidSubmit(EditContext editContext, ValidationMessageStore validationMessageStore)
{
var response = await HttpClient.PostAsJsonAsync(
"api/admin/system/settings/whiteLabeling",
Request,
SerializationContext.Default.Options
);
if (response.IsSuccessStatusCode)
{
await FrontendService.ReloadAsync();
await ToastService.SuccessAsync("Setting", "Successfully updated white labeling settings");
return true;
}
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
return false;
}
}

View File

@@ -4,18 +4,14 @@
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Moonlight.Shared
@using ShadcnBlazor.Buttons
@using ShadcnBlazor.Cards
@using ShadcnBlazor.Inputs
@using ShadcnBlazor.Tab
@using ShadcnBlazor.Labels
@inject NavigationManager Navigation
@inject IAuthorizationService AuthorizationService
<Tabs DefaultValue="@(Tab ?? "settings")" OnValueChanged="OnTabChanged">
<TabsList ClassName="inline-flex w-full lg:w-fit justify-start overflow-x-auto overflow-y-hidden">
<TabsTrigger Value="settings">
<TabsTrigger Value="settings" Disabled="@(!SettingsResult.Succeeded)">
<CogIcon />
Settings
</TabsTrigger>
@@ -27,7 +23,7 @@
<KeyIcon/>
API & API Keys
</TabsTrigger>
<TabsTrigger Value="diagnose">
<TabsTrigger Value="diagnose" Disabled="@(!DiagnoseResult.Succeeded)">
<HeartPulseIcon/>
Diagnose
</TabsTrigger>
@@ -36,19 +32,18 @@
Instance
</TabsTrigger>
</TabsList>
<TabsContent Value="settings">
<Card ClassName="mt-5">
<CardFooter ClassName="justify-end">
<Button>
<SaveIcon />
Save changes
</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent Value="diagnose">
<Diagnose />
</TabsContent>
@if (SettingsResult.Succeeded)
{
<TabsContent Value="settings">
<Settings />
</TabsContent>
}
@if (DiagnoseResult.Succeeded)
{
<TabsContent Value="diagnose">
<Diagnose />
</TabsContent>
}
@if (ApiKeyAccess.Succeeded)
{
<TabsContent Value="apiKeys">
@@ -81,6 +76,8 @@
private AuthorizationResult ThemesAccess;
private AuthorizationResult InstanceResult;
private AuthorizationResult VersionsResult;
private AuthorizationResult SettingsResult;
private AuthorizationResult DiagnoseResult;
protected override async Task OnInitializedAsync()
{
@@ -90,6 +87,8 @@
ThemesAccess = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.Themes.View);
InstanceResult = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.System.Versions);
VersionsResult = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.System.Instance);
SettingsResult = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.System.Settings);
DiagnoseResult = await AuthorizationService.AuthorizeAsync(authState.User, Permissions.System.Diagnose);
}
private void OnTabChanged(string name)

View File

@@ -0,0 +1,52 @@
@using Microsoft.Extensions.Options
@using Moonlight.Frontend.Configuration
@using ShadcnBlazor.Cards
@using ShadcnBlazor.Sidebars
@inject IOptions<SystemSettingsOptions> Options
<div class="mt-5 flex flex-col md:flex-row gap-5">
<Card ClassName="flex py-2 grow-0 min-w-56 max-h-[65vh] md:min-h-[65vh]">
<CardContent ClassName="px-2 flex flex-col gap-y-1 h-full max-h-[65vh] overflow-y-auto scrollbar-thin">
@foreach (var menuPage in Pages)
{
<SidebarMenuButton @onclick="() => Navigate(menuPage)" IsActive="@(CurrentPage == menuPage)" ClassName="overflow-visible">
<DynamicComponent Type="@menuPage.IconComponentType" />
<span>@menuPage.Name</span>
</SidebarMenuButton>
}
</CardContent>
</Card>
@if (CurrentPage != null)
{
<Card ClassName="flex grow">
<CardHeader>
<CardTitle>@CurrentPage.Name</CardTitle>
<CardDescription>@CurrentPage.Description</CardDescription>
</CardHeader>
<CardContent>
<DynamicComponent Type="@CurrentPage.ComponentType" />
</CardContent>
</Card>
}
</div>
@code
{
private SystemSettingsPage[] Pages;
private SystemSettingsPage? CurrentPage;
protected override void OnInitialized()
{
Pages = Options
.Value
.Components
.OrderBy(x => x.Order)
.ToArray();
CurrentPage = Pages.FirstOrDefault();
}
private void Navigate(SystemSettingsPage page)
=> CurrentPage = page;
}

View File

@@ -9,6 +9,10 @@
@using ShadcnBlazor.Emptys
@using Moonlight.Frontend.UI.Shared.Components.Auth
@using Moonlight.Frontend.UI.Shared.Partials
@using ShadcnBlazor.Extras.AlertDialogs
@using ShadcnBlazor.Extras.Dialogs
@using ShadcnBlazor.Extras.Toasts
@using ShadcnBlazor.Portals
@inject NavigationManager Navigation
@inject IOptions<NavigationAssemblyOptions> NavigationOptions
@@ -17,15 +21,23 @@
<ChildContent>
<AuthorizeView>
<ChildContent>
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="Assemblies" NotFoundPage="typeof(NotFound)">
<Found Context="routeData">
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
<NotAuthorized Context="authRouteViewContext">
<AccessDenied/>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
</Router>
<LayoutMiddleware>
<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="Assemblies" NotFoundPage="typeof(NotFound)">
<Found Context="routeData">
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
<NotAuthorized Context="authRouteViewContext">
<AccessDenied/>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
</Router>
</LayoutMiddleware>
<ToastLauncher/>
<DialogLauncher/>
<AlertDialogLauncher/>
<PortalOutlet />
</ChildContent>
<Authorizing>
<Authenticating/>

View File

@@ -0,0 +1,34 @@
@using Microsoft.Extensions.Options
@using Moonlight.Frontend.Configuration
@using Moonlight.Frontend.Interfaces
@inject IOptions<LayoutMiddlewareOptions> Options
@Chain
@code
{
[Parameter] public RenderFragment ChildContent { get; set; }
private RenderFragment Chain;
protected override void OnInitialized()
{
Chain = ChildContent;
foreach (var component in Options.Value.Components)
{
// Capture current values
var currentChain = Chain;
var currentComponent = component;
Chain = builder =>
{
builder.OpenComponent(0, currentComponent);
builder.SetKey(component);
builder.AddComponentParameter(1, nameof(LayoutMiddlewareBase.ChildContent), currentChain);
builder.CloseComponent();
};
}
}
}

View File

@@ -1,28 +1,53 @@
@using ShadcnBlazor.Extras.AlertDialogs
@using Microsoft.Extensions.Options
@using Moonlight.Frontend.Configuration
@using ShadcnBlazor.Extras.Alerts
@using ShadcnBlazor.Extras.Dialogs
@using ShadcnBlazor.Extras.Toasts
@using ShadcnBlazor.Portals
@using ShadcnBlazor.Sidebars
@inherits LayoutComponentBase
@inject IOptions<LayoutPageOptions> LayoutPageOptions
<SidebarProvider DefaultOpen="true">
<AppSidebar/>
<SidebarInset>
<AppHeader/>
@foreach (var headerComponent in HeaderComponents)
{
<DynamicComponent Type="headerComponent" />
}
<div class="mx-8 my-8 max-w-full">
<AlertLauncher/>
@Body
</div>
<ToastLauncher/>
<DialogLauncher/>
<AlertDialogLauncher/>
<PortalOutlet />
@foreach (var footerComponent in FooterComponents)
{
<DynamicComponent Type="footerComponent" />
}
</SidebarInset>
</SidebarProvider>
</SidebarProvider>
@code
{
private Type[] HeaderComponents;
private Type[] FooterComponents;
protected override void OnInitialized()
{
HeaderComponents = LayoutPageOptions.Value.Components
.Where(x => x.Slot == LayoutPageSlot.Header)
.OrderBy(x => x.Order)
.Select(x => x.ComponentType)
.ToArray();
FooterComponents = LayoutPageOptions.Value.Components
.Where(x => x.Slot == LayoutPageSlot.Footer)
.OrderBy(x => x.Order)
.Select(x => x.ComponentType)
.ToArray();
}
}

View File

@@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace Moonlight.Shared.Http.Requests.Admin.Settings;
public class SetWhiteLabelingDto
{
[Required]
public string Name { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Moonlight.Shared.Http.Responses.Admin.Settings;
public class WhiteLabelingDto
{
public string Name { get; set; }
}

View File

@@ -4,12 +4,14 @@ using Moonlight.Shared.Http.Events;
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
using Moonlight.Shared.Http.Requests.Admin.ContainerHelper;
using Moonlight.Shared.Http.Requests.Admin.Roles;
using Moonlight.Shared.Http.Requests.Admin.Settings;
using Moonlight.Shared.Http.Requests.Admin.Themes;
using Moonlight.Shared.Http.Requests.Admin.Users;
using Moonlight.Shared.Http.Responses;
using Moonlight.Shared.Http.Responses.Admin;
using Moonlight.Shared.Http.Responses.Admin.ApiKeys;
using Moonlight.Shared.Http.Responses.Admin.Auth;
using Moonlight.Shared.Http.Responses.Admin.Settings;
using Moonlight.Shared.Http.Responses.Admin.Themes;
using Moonlight.Shared.Http.Responses.Admin.Users;
@@ -59,6 +61,10 @@ namespace Moonlight.Shared.Http;
[JsonSerializable(typeof(VersionDto))]
[JsonSerializable(typeof(ProblemDetails))]
// Settings - White Labeling
[JsonSerializable(typeof(WhiteLabelingDto))]
[JsonSerializable(typeof(SetWhiteLabelingDto))]
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
public partial class SerializationContext : JsonSerializerContext
{

View File

@@ -55,5 +55,6 @@ public static class Permissions
public const string Diagnose = $"{Prefix}{Section}.{nameof(Diagnose)}";
public const string Versions = $"{Prefix}{Section}.{nameof(Versions)}";
public const string Instance = $"{Prefix}{Section}.{nameof(Instance)}";
public const string Settings = $"{Prefix}{Section}.{nameof(Settings)}";
}
}