Merge pull request #333 from Moonlight-Panel/AddConfigEditor
Added config editor
This commit is contained in:
@@ -21,7 +21,12 @@ public class ConfigService
|
|||||||
|
|
||||||
var text = File.ReadAllText(Path);
|
var text = File.ReadAllText(Path);
|
||||||
Data = JsonConvert.DeserializeObject<ConfigV1>(text) ?? new();
|
Data = JsonConvert.DeserializeObject<ConfigV1>(text) ?? new();
|
||||||
text = JsonConvert.SerializeObject(Data, Formatting.Indented);
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
var text = JsonConvert.SerializeObject(Data, Formatting.Indented);
|
||||||
File.WriteAllText(Path, text);
|
File.WriteAllText(Path, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,11 @@
|
|||||||
Binder = new(Property, Data);
|
Binder = new(Property, Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
Binder = new(Property, Data);
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<div class="card">
|
||||||
|
<div class="card-body pt-0 pb-0">
|
||||||
|
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/sys">
|
||||||
|
<i class="bx bx-sm bxs-dashboard me-2"></i> Overview
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/sys/settings">
|
||||||
|
<i class="bx bx-sm bx-cog me-2"></i> Settings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public int Index { get; set; }
|
||||||
|
}
|
||||||
@@ -105,6 +105,17 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="menu-item">
|
||||||
|
<a class="menu-link " href="/admin/sys">
|
||||||
|
<span class="menu-icon">
|
||||||
|
<i class="bx bx-sm bxs-component"></i>
|
||||||
|
</span>
|
||||||
|
<span class="menu-title">
|
||||||
|
System
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
8
Moonlight/Shared/Views/Admin/Sys/Index.razor
Normal file
8
Moonlight/Shared/Views/Admin/Sys/Index.razor
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
@page "/admin/sys"
|
||||||
|
|
||||||
|
@using Moonlight.App.Extensions.Attributes
|
||||||
|
@using Moonlight.App.Models.Enums
|
||||||
|
|
||||||
|
@attribute [RequirePermission(Permission.AdminRoot)]
|
||||||
|
|
||||||
|
<AdminSysNavigation Index="0" />
|
||||||
190
Moonlight/Shared/Views/Admin/Sys/Settings.razor
Normal file
190
Moonlight/Shared/Views/Admin/Sys/Settings.razor
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
@page "/admin/sys/settings"
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using System.Reflection
|
||||||
|
@using Moonlight.App.Extensions.Attributes
|
||||||
|
@using Moonlight.App.Models.Enums
|
||||||
|
|
||||||
|
@attribute [RequirePermission(Permission.AdminRoot)]
|
||||||
|
|
||||||
|
@inject ConfigService ConfigService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
|
||||||
|
<AdminSysNavigation Index="1" />
|
||||||
|
|
||||||
|
@if (ModelToShow == null)
|
||||||
|
{
|
||||||
|
<NotFoundAlert />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-header">
|
||||||
|
@{
|
||||||
|
string title;
|
||||||
|
|
||||||
|
if (Path.Length == 0)
|
||||||
|
title = "Configuration";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
title = "Configuration - " + string.Join(" - ", Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3 class="card-title">@(title)</h3>
|
||||||
|
<div class="card-toolbar">
|
||||||
|
<WButton OnClick="Reload" CssClasses="btn btn-icon btn-warning me-3">
|
||||||
|
<i class="bx bx-sm bx-revision"></i>
|
||||||
|
</WButton>
|
||||||
|
<WButton OnClick="Save" CssClasses="btn btn-icon btn-success">
|
||||||
|
<i class="bx bx-sm bx-save"></i>
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
Changes to these settings are live applied. The save button only make the changes persistently saved to disk
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-md-3 col-12 mb-5">
|
||||||
|
<div class="card card-body">
|
||||||
|
@{
|
||||||
|
var props = ModelToShow
|
||||||
|
.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.Where(x => x.PropertyType.Assembly.FullName!.Contains("Moonlight") && x.PropertyType.IsClass)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@foreach (var prop in props)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-stack">
|
||||||
|
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
||||||
|
<a href="/admin/sys/settings?section=@(Section + "/" + prop.Name)" class="fs-4 text-primary">@(prop.Name)</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Path.Length != 0)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-stack">
|
||||||
|
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
||||||
|
<a href="/admin/sys/@(GetBackPath())" class="fs-4 text-primary">Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9 col-12">
|
||||||
|
<div class="card card-body">
|
||||||
|
<div class="row">
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
@foreach (var prop in Properties)
|
||||||
|
{
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<CascadingValue Name="Property" Value="prop">
|
||||||
|
<CascadingValue Name="Data" Value="ModelToShow">
|
||||||
|
@{
|
||||||
|
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ComponentHelper.FromType(typeToCreate)
|
||||||
|
</CascadingValue>
|
||||||
|
</CascadingValue>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
[SupplyParameterFromQuery]
|
||||||
|
public string? Section { get; set; } = "";
|
||||||
|
|
||||||
|
private object? ModelToShow;
|
||||||
|
private PropertyInfo[] Properties = Array.Empty<PropertyInfo>();
|
||||||
|
private string[] Path = Array.Empty<string>();
|
||||||
|
|
||||||
|
private LazyLoader? LazyLoader;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
if (Section != null && Section.StartsWith("/"))
|
||||||
|
Section = Section.TrimStart('/');
|
||||||
|
|
||||||
|
Path = Section != null ? Section.Split("/") : Array.Empty<string>();
|
||||||
|
|
||||||
|
ModelToShow = Resolve(ConfigService.Get(), Path, 0);
|
||||||
|
|
||||||
|
if (ModelToShow != null)
|
||||||
|
{
|
||||||
|
Properties = ModelToShow
|
||||||
|
.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.Where(x => !x.PropertyType.Assembly.FullName!.Contains("Moonlight"))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Properties = Array.Empty<PropertyInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
if (LazyLoader != null)
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBackPath()
|
||||||
|
{
|
||||||
|
if (Path.Length == 1)
|
||||||
|
return "settings";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var path = string.Join('/', Path.Take(Path.Length - 1)).TrimEnd('/');
|
||||||
|
return $"settings?section={path}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? Resolve(object model, string[] path, int index)
|
||||||
|
{
|
||||||
|
if (path.Length == 0)
|
||||||
|
return model;
|
||||||
|
|
||||||
|
if (path.Length == index)
|
||||||
|
return model;
|
||||||
|
|
||||||
|
var prop = model
|
||||||
|
.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.FirstOrDefault(x => x.PropertyType.Assembly.FullName!.Contains("Moonlight") && x.Name == path[index]);
|
||||||
|
|
||||||
|
if (prop == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Resolve(prop.GetValue(model)!, path, index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
ConfigService.Save();
|
||||||
|
await ToastService.Success("Successfully saved config to disk");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Reload()
|
||||||
|
{
|
||||||
|
ConfigService.Reload();
|
||||||
|
await ToastService.Info("Reloaded configuration from disk");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user