Implemented config editor. Fixed auto property
This commit is contained in:
parent
93cfe7cd1a
commit
11dace2617
8 changed files with 245 additions and 99 deletions
|
@ -24,13 +24,13 @@ public class PropBinder<T>
|
|||
get => (int)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
|
||||
public long LongValue
|
||||
{
|
||||
get => (long)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
|
||||
public bool BoolValue
|
||||
{
|
||||
get => (bool)PropertyInfo.GetValue(DataObject)!;
|
||||
|
@ -48,7 +48,7 @@ public class PropBinder<T>
|
|||
get => (T)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
|
||||
public double DoubleValue
|
||||
{
|
||||
get => (double)PropertyInfo.GetValue(DataObject)!;
|
||||
|
|
|
@ -21,7 +21,12 @@ public class ConfigService
|
|||
|
||||
var text = File.ReadAllText(Path);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,11 @@
|
|||
Binder = new(Property, Data);
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
Binder = new(Property, Data);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool 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>
|
||||
</a>
|
||||
</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>
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
@page "/admin/settings"
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject ConfigService ConfigService
|
||||
|
||||
@if (ModelToShow == null)
|
||||
{
|
||||
<h1>Nope</h1>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
@{
|
||||
string title;
|
||||
|
||||
if (Path.Length == 0)
|
||||
title = "Configuration";
|
||||
else
|
||||
title = string.Join(
|
||||
" > ",
|
||||
Path.Select(x =>
|
||||
x.EndsWith("Data") ? Formatter.ReplaceEnd(x, "Data", "") : x));
|
||||
}
|
||||
|
||||
<h3 class="card-title">@(title)</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-12">
|
||||
@{
|
||||
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/settings?section=@(Section + "/" + prop.Name)" class="fs-5 text-primary">@(prop.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-9 col-12">
|
||||
<AutoForm Model="ModelToShow" Columns="6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
[SupplyParameterFromQuery]
|
||||
public string? Section { get; set; } = "";
|
||||
|
||||
private object? ModelToShow = new();
|
||||
private string[] Path = Array.Empty<string>();
|
||||
|
||||
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);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
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.PropertyType.IsClass && x.Name == path[index]);
|
||||
|
||||
if (prop == null)
|
||||
return null;
|
||||
|
||||
return Resolve(prop.GetValue(model)!, path, index + 1);
|
||||
}
|
||||
}
|
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");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue