Compare commits
7 Commits
31cf34ed04
...
v2.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 3dff8c8f6d | |||
| 95a848e571 | |||
| 9d557eea4e | |||
| 94c1aac0ac | |||
| 3bddd64d91 | |||
| 5ad7a6db7b | |||
| 9b9272cd6e |
@@ -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
|
||||
|
||||
6
Moonlight.Api/Constants/FrontendSettingConstants.cs
Normal file
6
Moonlight.Api/Constants/FrontendSettingConstants.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Moonlight.Api.Constants;
|
||||
|
||||
public class FrontendSettingConstants
|
||||
{
|
||||
public const string Name = "Moonlight.Frontend.Name";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
26
Moonlight.Frontend/Configuration/LayoutMiddlewareOptions.cs
Normal file
26
Moonlight.Frontend/Configuration/LayoutMiddlewareOptions.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
33
Moonlight.Frontend/Configuration/LayoutPageOptions.cs
Normal file
33
Moonlight.Frontend/Configuration/LayoutPageOptions.cs
Normal 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
|
||||
}
|
||||
35
Moonlight.Frontend/Configuration/SystemSettingsOptions.cs
Normal file
35
Moonlight.Frontend/Configuration/SystemSettingsOptions.cs
Normal 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
|
||||
);
|
||||
@@ -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"),
|
||||
|
||||
8
Moonlight.Frontend/Interfaces/LayoutMiddlewareBase.cs
Normal file
8
Moonlight.Frontend/Interfaces/LayoutMiddlewareBase.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Moonlight.Frontend.Interfaces;
|
||||
|
||||
public abstract class LayoutMiddlewareBase : ComponentBase
|
||||
{
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
52
Moonlight.Frontend/UI/Admin/Views/Sys/Settings.razor
Normal file
52
Moonlight.Frontend/UI/Admin/Views/Sys/Settings.razor
Normal 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;
|
||||
}
|
||||
@@ -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/>
|
||||
|
||||
34
Moonlight.Frontend/UI/Shared/LayoutMiddleware.razor
Normal file
34
Moonlight.Frontend/UI/Shared/LayoutMiddleware.razor
Normal 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();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.Shared.Http.Requests.Admin.Settings;
|
||||
|
||||
public class SetWhiteLabelingDto
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Moonlight.Shared.Http.Responses.Admin.Settings;
|
||||
|
||||
public class WhiteLabelingDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user