Merge pull request #209 from Moonlight-Panel/NewVisualConfigEditor
Added a new visual config editor
This commit is contained in:
commit
d1c9009e9f
9 changed files with 442 additions and 42 deletions
|
@ -1,19 +1,25 @@
|
||||||
namespace Moonlight.App.Configuration;
|
using System.ComponentModel;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Configuration;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
public class ConfigV1
|
public class ConfigV1
|
||||||
{
|
{
|
||||||
[JsonProperty("Moonlight")] public MoonlightData Moonlight { get; set; } = new();
|
[JsonProperty("Moonlight")]
|
||||||
|
public MoonlightData Moonlight { get; set; } = new();
|
||||||
|
|
||||||
public class MoonlightData
|
public class MoonlightData
|
||||||
{
|
{
|
||||||
[JsonProperty("AppUrl")] public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
|
[JsonProperty("AppUrl")]
|
||||||
|
[Description("The url moonlight is accesible with from the internet")]
|
||||||
|
public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
|
||||||
|
|
||||||
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
|
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty("DiscordBotApi")] public DiscordBotData DiscordBotApi { get; set; } = new();
|
[JsonProperty("DiscordBotApi")] public DiscordBotApiData DiscordBotApi { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty("DiscordBot")] public DiscordBotData DiscordBot { get; set; } = new();
|
[JsonProperty("DiscordBot")] public DiscordBotData DiscordBot { get; set; } = new();
|
||||||
|
|
||||||
|
@ -47,17 +53,29 @@ public class ConfigV1
|
||||||
|
|
||||||
public class CleanupData
|
public class CleanupData
|
||||||
{
|
{
|
||||||
[JsonProperty("Cpu")] public long Cpu { get; set; } = 90;
|
[JsonProperty("Cpu")]
|
||||||
|
[Description("The maximum amount of cpu usage in percent a node is allowed to use before the cleanup starts")]
|
||||||
|
public long Cpu { get; set; } = 90;
|
||||||
|
|
||||||
[JsonProperty("Memory")] public long Memory { get; set; } = 8192;
|
[JsonProperty("Memory")]
|
||||||
|
[Description("The minumum amount of memory in megabytes avaliable before the cleanup starts")]
|
||||||
|
public long Memory { get; set; } = 8192;
|
||||||
|
|
||||||
[JsonProperty("Wait")] public long Wait { get; set; } = 15;
|
[JsonProperty("Wait")]
|
||||||
|
[Description("The delay between every cleanup check in minutes")]
|
||||||
|
public long Wait { get; set; } = 15;
|
||||||
|
|
||||||
[JsonProperty("Uptime")] public long Uptime { get; set; } = 6;
|
[JsonProperty("Uptime")]
|
||||||
|
[Description("The maximum uptime of any server in hours before it the server restarted by the cleanup system")]
|
||||||
|
public long Uptime { get; set; } = 6;
|
||||||
|
|
||||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
[JsonProperty("Enable")]
|
||||||
|
[Description("The cleanup system provides a fair way for stopping unused servers and staying stable even with overallocation. A detailed explanation: docs.endelon-hosting.de/erklaerungen/cleanup")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty("MinUptime")] public long MinUptime { get; set; } = 10;
|
[JsonProperty("MinUptime")]
|
||||||
|
[Description("The minumum uptime of a server in minutes to prevent stopping servers which just started")]
|
||||||
|
public long MinUptime { get; set; } = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DatabaseData
|
public class DatabaseData
|
||||||
|
@ -65,39 +83,77 @@ public class ConfigV1
|
||||||
[JsonProperty("Database")] public string Database { get; set; } = "moonlight_db";
|
[JsonProperty("Database")] public string Database { get; set; } = "moonlight_db";
|
||||||
|
|
||||||
[JsonProperty("Host")] public string Host { get; set; } = "your.database.host";
|
[JsonProperty("Host")] public string Host { get; set; } = "your.database.host";
|
||||||
|
|
||||||
[JsonProperty("Password")] public string Password { get; set; } = "secret";
|
[JsonProperty("Password")]
|
||||||
|
[Blur]
|
||||||
|
public string Password { get; set; } = "secret";
|
||||||
|
|
||||||
[JsonProperty("Port")] public long Port { get; set; } = 3306;
|
[JsonProperty("Port")] public long Port { get; set; } = 3306;
|
||||||
|
|
||||||
[JsonProperty("Username")] public string Username { get; set; } = "moonlight_user";
|
[JsonProperty("Username")] public string Username { get; set; } = "moonlight_user";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DiscordBotApiData
|
||||||
|
{
|
||||||
|
[JsonProperty("Enable")]
|
||||||
|
[Description("Enable the discord bot api. Currently only DatBot is using this api")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("Token")]
|
||||||
|
[Description("Specify the token the api client needs to provide")]
|
||||||
|
[Blur]
|
||||||
|
public string Token { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
public class DiscordBotData
|
public class DiscordBotData
|
||||||
{
|
{
|
||||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
[JsonProperty("Enable")]
|
||||||
|
[Description("The discord bot can be used to allow customers to manage their servers via discord")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty("Token")] public string Token { get; set; } = "discord token here";
|
[JsonProperty("Token")]
|
||||||
|
[Description("Your discord bot token goes here")]
|
||||||
|
[Blur]
|
||||||
|
public string Token { get; set; } = "discord token here";
|
||||||
|
|
||||||
[JsonProperty("PowerActions")] public bool PowerActions { get; set; } = false;
|
[JsonProperty("PowerActions")]
|
||||||
[JsonProperty("SendCommands")] public bool SendCommands { get; set; } = false;
|
[Description("Enable actions like starting and stopping servers")]
|
||||||
|
public bool PowerActions { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("SendCommands")]
|
||||||
|
[Description("Allow users to send commands to their servers")]
|
||||||
|
public bool SendCommands { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DiscordNotificationsData
|
public class DiscordNotificationsData
|
||||||
{
|
{
|
||||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
[JsonProperty("Enable")]
|
||||||
|
[Description("The discord notification system sends you a message everytime a event like a new support chat message is triggered with usefull data describing the event")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty("WebHook")] public string WebHook { get; set; } = "http://your-discord-webhook-url";
|
[JsonProperty("WebHook")]
|
||||||
|
[Description("The discord webhook the notifications are being sent to")]
|
||||||
|
[Blur]
|
||||||
|
public string WebHook { get; set; } = "http://your-discord-webhook-url";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DomainsData
|
public class DomainsData
|
||||||
{
|
{
|
||||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
[JsonProperty("Enable")]
|
||||||
[JsonProperty("AccountId")] public string AccountId { get; set; } = "cloudflare acc id";
|
[Description("This enables the domain system")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("AccountId")]
|
||||||
|
[Description("This option specifies the cloudflare account id")]
|
||||||
|
public string AccountId { get; set; } = "cloudflare acc id";
|
||||||
|
|
||||||
[JsonProperty("Email")] public string Email { get; set; } = "cloudflare@acc.email";
|
[JsonProperty("Email")]
|
||||||
|
[Description("This specifies the cloudflare email to use for communicating with the cloudflare api")]
|
||||||
|
public string Email { get; set; } = "cloudflare@acc.email";
|
||||||
|
|
||||||
[JsonProperty("Key")] public string Key { get; set; } = "secret";
|
[JsonProperty("Key")]
|
||||||
|
[Description("Your cloudflare api key goes here")]
|
||||||
|
[Blur]
|
||||||
|
public string Key { get; set; } = "secret";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HtmlData
|
public class HtmlData
|
||||||
|
@ -107,13 +163,21 @@ public class ConfigV1
|
||||||
|
|
||||||
public class HeadersData
|
public class HeadersData
|
||||||
{
|
{
|
||||||
[JsonProperty("Color")] public string Color { get; set; } = "#4b27e8";
|
[JsonProperty("Color")]
|
||||||
|
[Description("This specifies the color of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
|
||||||
|
public string Color { get; set; } = "#4b27e8";
|
||||||
|
|
||||||
[JsonProperty("Description")] public string Description { get; set; } = "the next generation hosting panel";
|
[JsonProperty("Description")]
|
||||||
|
[Description("This specifies the description text of the embed generated by platforms like discord when someone posts a link to your moonlight instance and can also help google to index your moonlight instance correctly")]
|
||||||
|
public string Description { get; set; } = "the next generation hosting panel";
|
||||||
|
|
||||||
[JsonProperty("Keywords")] public string Keywords { get; set; } = "moonlight";
|
[JsonProperty("Keywords")]
|
||||||
|
[Description("To help search engines like google to index your moonlight instance correctly you can specify keywords seperated by a comma here")]
|
||||||
|
public string Keywords { get; set; } = "moonlight";
|
||||||
|
|
||||||
[JsonProperty("Title")] public string Title { get; set; } = "Moonlight - endelon.link";
|
[JsonProperty("Title")]
|
||||||
|
[Description("This specifies the title of the embed generated by platforms like discord when someone posts a link to your moonlight instance")]
|
||||||
|
public string Title { get; set; } = "Moonlight - endelon.link";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MailData
|
public class MailData
|
||||||
|
@ -122,7 +186,9 @@ public class ConfigV1
|
||||||
|
|
||||||
[JsonProperty("Server")] public string Server { get; set; } = "your.mail.host";
|
[JsonProperty("Server")] public string Server { get; set; } = "your.mail.host";
|
||||||
|
|
||||||
[JsonProperty("Password")] public string Password { get; set; } = "secret";
|
[JsonProperty("Password")]
|
||||||
|
[Blur]
|
||||||
|
public string Password { get; set; } = "secret";
|
||||||
|
|
||||||
[JsonProperty("Port")] public int Port { get; set; } = 465;
|
[JsonProperty("Port")] public int Port { get; set; } = 465;
|
||||||
|
|
||||||
|
@ -142,9 +208,13 @@ public class ConfigV1
|
||||||
|
|
||||||
public class OAuth2Data
|
public class OAuth2Data
|
||||||
{
|
{
|
||||||
[JsonProperty("OverrideUrl")] public string OverrideUrl { get; set; } = "https://only-for-development.cases";
|
[JsonProperty("OverrideUrl")]
|
||||||
|
[Description("This overrides the redirect url which would be typicaly the app url")]
|
||||||
|
public string OverrideUrl { get; set; } = "https://only-for-development.cases";
|
||||||
|
|
||||||
[JsonProperty("EnableOverrideUrl")] public bool EnableOverrideUrl { get; set; } = false;
|
[JsonProperty("EnableOverrideUrl")]
|
||||||
|
[Description("This enables the url override")]
|
||||||
|
public bool EnableOverrideUrl { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty("Providers")]
|
[JsonProperty("Providers")]
|
||||||
public OAuth2ProviderData[] Providers { get; set; } = Array.Empty<OAuth2ProviderData>();
|
public OAuth2ProviderData[] Providers { get; set; } = Array.Empty<OAuth2ProviderData>();
|
||||||
|
@ -156,41 +226,65 @@ public class ConfigV1
|
||||||
|
|
||||||
[JsonProperty("ClientId")] public string ClientId { get; set; }
|
[JsonProperty("ClientId")] public string ClientId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("ClientSecret")] public string ClientSecret { get; set; }
|
[JsonProperty("ClientSecret")]
|
||||||
|
[Blur]
|
||||||
|
public string ClientSecret { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RatingData
|
public class RatingData
|
||||||
{
|
{
|
||||||
[JsonProperty("Enabled")] public bool Enabled { get; set; } = false;
|
[JsonProperty("Enabled")]
|
||||||
|
[Description("The rating systems shows a user who is registered longer than the set amout of days a popup to rate this platform if he hasnt rated it before")]
|
||||||
|
public bool Enabled { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty("Url")] public string Url { get; set; } = "https://link-to-google-or-smth";
|
[JsonProperty("Url")]
|
||||||
|
[Description("This is the url a user who rated above a set limit is shown to rate you again. Its recommended to put your google or trustpilot rate link here")]
|
||||||
|
public string Url { get; set; } = "https://link-to-google-or-smth";
|
||||||
|
|
||||||
[JsonProperty("MinRating")] public int MinRating { get; set; } = 4;
|
[JsonProperty("MinRating")]
|
||||||
|
[Description("The minimum star count on the rating ranging from 1 to 5")]
|
||||||
|
public int MinRating { get; set; } = 4;
|
||||||
|
|
||||||
[JsonProperty("DaysSince")] public int DaysSince { get; set; } = 5;
|
[JsonProperty("DaysSince")]
|
||||||
|
[Description("The days a user has to be registered to even be able to get this popup")]
|
||||||
|
public int DaysSince { get; set; } = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SecurityData
|
public class SecurityData
|
||||||
{
|
{
|
||||||
[JsonProperty("Token")] public string Token { get; set; } = Guid.NewGuid().ToString();
|
[JsonProperty("Token")]
|
||||||
|
[Description("This is the moonlight app token. It is used to encrypt and decrypt data and validte tokens and sessions")]
|
||||||
|
[Blur]
|
||||||
|
public string Token { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
|
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ReCaptchaData
|
public class ReCaptchaData
|
||||||
{
|
{
|
||||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
[JsonProperty("Enable")]
|
||||||
|
[Description("Enables repatcha at places like the register page. For information how to get your recaptcha credentails go to google.com/recaptcha/about/")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty("SiteKey")] public string SiteKey { get; set; } = "recaptcha site key here";
|
[JsonProperty("SiteKey")]
|
||||||
|
[Blur]
|
||||||
|
public string SiteKey { get; set; } = "recaptcha site key here";
|
||||||
|
|
||||||
[JsonProperty("SecretKey")] public string SecretKey { get; set; } = "recaptcha secret here";
|
[JsonProperty("SecretKey")]
|
||||||
|
[Blur]
|
||||||
|
public string SecretKey { get; set; } = "recaptcha secret here";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SentryData
|
public class SentryData
|
||||||
{
|
{
|
||||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
[JsonProperty("Enable")]
|
||||||
|
[Description("Sentry is a way to monitor application crashes and performance issues in real time. Enable this option only if you set a sentry dsn")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
[JsonProperty("Dsn")] public string Dsn { get; set; } = "http://your-sentry-url-here";
|
[JsonProperty("Dsn")]
|
||||||
|
[Description("The dsn is the key moonlight needs to communicate with your sentry instance")]
|
||||||
|
[Blur]
|
||||||
|
public string Dsn { get; set; } = "http://your-sentry-url-here";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SmartDeployData
|
public class SmartDeployData
|
||||||
|
|
6
Moonlight/App/Helpers/BlurAttribute.cs
Normal file
6
Moonlight/App/Helpers/BlurAttribute.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class BlurAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,36 @@
|
||||||
using Moonlight.App.Services;
|
using System.Text;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
public static class Formatter
|
public static class Formatter
|
||||||
{
|
{
|
||||||
|
public static string ReplaceEnd(string input, string substringToReplace, string newSubstring)
|
||||||
|
{
|
||||||
|
int lastIndexOfSubstring = input.LastIndexOf(substringToReplace);
|
||||||
|
if (lastIndexOfSubstring >= 0)
|
||||||
|
{
|
||||||
|
input = input.Remove(lastIndexOfSubstring, substringToReplace.Length).Insert(lastIndexOfSubstring, newSubstring);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
public static string ConvertCamelCaseToSpaces(string input)
|
||||||
|
{
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (char c in input)
|
||||||
|
{
|
||||||
|
if (char.IsUpper(c))
|
||||||
|
{
|
||||||
|
output.Append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.ToString().Trim();
|
||||||
|
}
|
||||||
public static string FormatUptime(double uptime)
|
public static string FormatUptime(double uptime)
|
||||||
{
|
{
|
||||||
TimeSpan t = TimeSpan.FromMilliseconds(uptime);
|
TimeSpan t = TimeSpan.FromMilliseconds(uptime);
|
||||||
|
|
51
Moonlight/App/Helpers/PropBinder.cs
Normal file
51
Moonlight/App/Helpers/PropBinder.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class PropBinder
|
||||||
|
{
|
||||||
|
private PropertyInfo PropertyInfo;
|
||||||
|
private object DataObject;
|
||||||
|
|
||||||
|
public PropBinder(PropertyInfo propertyInfo, object dataObject)
|
||||||
|
{
|
||||||
|
PropertyInfo = propertyInfo;
|
||||||
|
DataObject = dataObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StringValue
|
||||||
|
{
|
||||||
|
get => (string)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IntValue
|
||||||
|
{
|
||||||
|
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)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime DateTimeValue
|
||||||
|
{
|
||||||
|
get => (DateTime)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double DoubleValue
|
||||||
|
{
|
||||||
|
get => (double)PropertyInfo.GetValue(DataObject)!;
|
||||||
|
set => PropertyInfo.SetValue(DataObject, value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,7 +51,27 @@ public class ConfigService
|
||||||
File.ReadAllText(path)
|
File.ReadAllText(path)
|
||||||
) ?? new ConfigV1();
|
) ?? new ConfigV1();
|
||||||
|
|
||||||
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration));
|
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(ConfigV1 configV1)
|
||||||
|
{
|
||||||
|
Configuration = configV1;
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
var path = PathBuilder.File("storage", "configs", "config.json");
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
|
|
||||||
|
Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigV1 Get()
|
public ConfigV1 Get()
|
||||||
|
|
65
Moonlight/Shared/Components/Forms/SmartFormClass.razor
Normal file
65
Moonlight/Shared/Components/Forms/SmartFormClass.razor
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
@using System.Reflection
|
||||||
|
@using System.Collections
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
|
<div class="accordion my-3" id="configSetting@(Model.GetHashCode())">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="configSetting-header@(Model.GetHashCode())">
|
||||||
|
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#configSetting-body@(Model.GetHashCode())" aria-expanded="false" aria-controls="configSetting-body@(Model.GetHashCode())">
|
||||||
|
@{
|
||||||
|
var name = Formatter.ReplaceEnd(Model.GetType().Name, "Data", "");
|
||||||
|
name = Formatter.ConvertCamelCaseToSpaces(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@(name)
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="configSetting-body@(Model.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="configSetting-header@(Model.GetHashCode())" data-bs-parent="#configSetting">
|
||||||
|
<div class="accordion-body">
|
||||||
|
@foreach (var property in Model.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
@BindAndRenderProperty(property)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public object Model { get; set; }
|
||||||
|
|
||||||
|
private RenderFragment BindAndRenderProperty(PropertyInfo property)
|
||||||
|
{
|
||||||
|
if (property.PropertyType.IsClass && !property.PropertyType.IsPrimitive && !typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
|
||||||
|
{
|
||||||
|
return @<SmartFormClass Model="@property.GetValue(Model)"/>;
|
||||||
|
|
||||||
|
// If the property is a subclass, serialize and generate form for it
|
||||||
|
/*
|
||||||
|
foreach (var subProperty in property.PropertyType.GetProperties())
|
||||||
|
{
|
||||||
|
return BindAndRenderProperty(subProperty);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
else if (property.PropertyType == typeof(int) || property.PropertyType == typeof(string) || property.PropertyType == typeof(bool) || property.PropertyType == typeof(decimal) || property.PropertyType == typeof(long))
|
||||||
|
{
|
||||||
|
return @<SmartFormProperty Model="Model" PropertyInfo="property"/>;
|
||||||
|
}
|
||||||
|
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
|
||||||
|
{
|
||||||
|
// If the property is a collection, generate form for each element
|
||||||
|
var collection = property.GetValue(Model) as IEnumerable;
|
||||||
|
if (collection != null)
|
||||||
|
{
|
||||||
|
foreach (var element in collection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Additional property types could be handled here (e.g., DateTime, int, etc.)
|
||||||
|
|
||||||
|
return @<div></div>;
|
||||||
|
}
|
||||||
|
}
|
79
Moonlight/Shared/Components/Forms/SmartFormProperty.razor
Normal file
79
Moonlight/Shared/Components/Forms/SmartFormProperty.razor
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
@using System.Reflection
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using System.ComponentModel
|
||||||
|
|
||||||
|
<label class="form-label" for="@PropertyInfo.Name">
|
||||||
|
@(Formatter.ConvertCamelCaseToSpaces(PropertyInfo.Name))
|
||||||
|
</label>
|
||||||
|
@{
|
||||||
|
//TODO: Tidy up this code
|
||||||
|
|
||||||
|
var attrs = PropertyInfo.GetCustomAttributes(true);
|
||||||
|
|
||||||
|
var descAttr = attrs
|
||||||
|
.FirstOrDefault(x => x.GetType() == typeof(DescriptionAttribute));
|
||||||
|
|
||||||
|
var blurBool = attrs.Any(x => x.GetType() == typeof(BlurAttribute));
|
||||||
|
var blur = blurBool ? "blur-unless-hover" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (descAttr != null)
|
||||||
|
{
|
||||||
|
var a = descAttr as DescriptionAttribute;
|
||||||
|
|
||||||
|
<div class="form-text fs-5 mb-2 mt-0">
|
||||||
|
@(a.Description)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
@if (PropertyInfo.PropertyType == typeof(string))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<div class="@(blur) w-100">
|
||||||
|
<InputText id="@PropertyInfo.Name" @bind-Value="binder.StringValue" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(int))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputNumber id="@PropertyInfo.Name" @bind-Value="binder.IntValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(long))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputNumber id="@PropertyInfo.Name" @bind-Value="binder.LongValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(bool))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox id="@PropertyInfo.Name" @bind-Value="binder.BoolValue" class="form-check-input"/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(DateTime))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputDate id="@PropertyInfo.Name" @bind-Value="binder.DateTimeValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
else if (PropertyInfo.PropertyType == typeof(decimal))
|
||||||
|
{
|
||||||
|
var binder = new PropBinder(PropertyInfo, Model!);
|
||||||
|
|
||||||
|
<InputNumber id="@PropertyInfo.Name" step="0.01" @bind-Value="binder.DoubleValue" class="form-control"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public PropertyInfo PropertyInfo { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public object Model { get; set; }
|
||||||
|
}
|
|
@ -39,6 +39,11 @@
|
||||||
<TL>News</TL>
|
<TL>News</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 8 ? "active" : "")" href="/admin/system/configuration">
|
||||||
|
<TL>Configuration</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
53
Moonlight/Shared/Views/Admin/Sys/Configuration.razor
Normal file
53
Moonlight/Shared/Views/Admin/Sys/Configuration.razor
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
@page "/admin/system/configuration"
|
||||||
|
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using Moonlight.App.Configuration
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject ConfigService ConfigService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<AdminSystemNavigation Index="8"/>
|
||||||
|
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
<div class="card">
|
||||||
|
<SmartForm Model="Config" OnValidSubmit="OnSubmit">
|
||||||
|
<div class="card-body">
|
||||||
|
<SmartFormClass Model="Config"/>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="text-end">
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<TL>Save</TL>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SmartForm>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private ConfigV1 Config;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
Config = ConfigService.Get();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSubmit()
|
||||||
|
{
|
||||||
|
ConfigService.Save(Config);
|
||||||
|
await ToastService.Success(
|
||||||
|
SmartTranslateService.Translate(
|
||||||
|
"Successfully saved and reloaded configuration. Some changes may take affect after a restart of moonlight"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue