From d024a834f96ea2dfae33a227e2c977d4bfb34649 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Thu, 6 Jul 2023 16:46:01 +0200 Subject: [PATCH 01/20] Added a new visual config editor --- Moonlight/App/Configuration/ConfigV1.cs | 174 ++++++++++++++---- Moonlight/App/Helpers/BlurAttribute.cs | 6 + Moonlight/App/Helpers/Formatter.cs | 29 ++- Moonlight/App/Helpers/PropBinder.cs | 51 +++++ Moonlight/App/Services/ConfigService.cs | 22 ++- .../Components/Forms/SmartFormClass.razor | 65 +++++++ .../Components/Forms/SmartFormProperty.razor | 79 ++++++++ .../Navigations/AdminSystemNavigation.razor | 5 + .../Views/Admin/Sys/Configuration.razor | 53 ++++++ 9 files changed, 442 insertions(+), 42 deletions(-) create mode 100644 Moonlight/App/Helpers/BlurAttribute.cs create mode 100644 Moonlight/App/Helpers/PropBinder.cs create mode 100644 Moonlight/Shared/Components/Forms/SmartFormClass.razor create mode 100644 Moonlight/Shared/Components/Forms/SmartFormProperty.razor create mode 100644 Moonlight/Shared/Views/Admin/Sys/Configuration.razor diff --git a/Moonlight/App/Configuration/ConfigV1.cs b/Moonlight/App/Configuration/ConfigV1.cs index 67224fd2..053aa1c1 100644 --- a/Moonlight/App/Configuration/ConfigV1.cs +++ b/Moonlight/App/Configuration/ConfigV1.cs @@ -1,19 +1,25 @@ -namespace Moonlight.App.Configuration; +using System.ComponentModel; +using Moonlight.App.Helpers; + +namespace Moonlight.App.Configuration; using System; using Newtonsoft.Json; public class ConfigV1 { - [JsonProperty("Moonlight")] public MoonlightData Moonlight { get; set; } = new(); + [JsonProperty("Moonlight")] + public MoonlightData Moonlight { get; set; } = new(); 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("DiscordBotApi")] public DiscordBotData DiscordBotApi { get; set; } = new(); + [JsonProperty("DiscordBotApi")] public DiscordBotApiData DiscordBotApi { get; set; } = new(); [JsonProperty("DiscordBot")] public DiscordBotData DiscordBot { get; set; } = new(); @@ -47,17 +53,29 @@ public class ConfigV1 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 @@ -65,39 +83,77 @@ public class ConfigV1 [JsonProperty("Database")] public string Database { get; set; } = "moonlight_db"; [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("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 { - [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("SendCommands")] public bool SendCommands { get; set; } = false; + [JsonProperty("PowerActions")] + [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 { - [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 { - [JsonProperty("Enable")] public bool Enable { get; set; } = false; - [JsonProperty("AccountId")] public string AccountId { get; set; } = "cloudflare acc id"; + [JsonProperty("Enable")] + [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 @@ -107,13 +163,21 @@ public class ConfigV1 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 @@ -122,7 +186,9 @@ public class ConfigV1 [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; @@ -142,9 +208,13 @@ public class ConfigV1 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")] public OAuth2ProviderData[] Providers { get; set; } = Array.Empty(); @@ -156,41 +226,65 @@ public class ConfigV1 [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 { - [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 { - [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(); } 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 { - [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 diff --git a/Moonlight/App/Helpers/BlurAttribute.cs b/Moonlight/App/Helpers/BlurAttribute.cs new file mode 100644 index 00000000..170c2b4f --- /dev/null +++ b/Moonlight/App/Helpers/BlurAttribute.cs @@ -0,0 +1,6 @@ +namespace Moonlight.App.Helpers; + +public class BlurAttribute : Attribute +{ + +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/Formatter.cs b/Moonlight/App/Helpers/Formatter.cs index 31834ccd..44d08f50 100644 --- a/Moonlight/App/Helpers/Formatter.cs +++ b/Moonlight/App/Helpers/Formatter.cs @@ -1,9 +1,36 @@ -using Moonlight.App.Services; +using System.Text; +using Moonlight.App.Services; namespace Moonlight.App.Helpers; 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) { TimeSpan t = TimeSpan.FromMilliseconds(uptime); diff --git a/Moonlight/App/Helpers/PropBinder.cs b/Moonlight/App/Helpers/PropBinder.cs new file mode 100644 index 00000000..ddb5a601 --- /dev/null +++ b/Moonlight/App/Helpers/PropBinder.cs @@ -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); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/ConfigService.cs b/Moonlight/App/Services/ConfigService.cs index f6573e19..4a9c0d7e 100644 --- a/Moonlight/App/Services/ConfigService.cs +++ b/Moonlight/App/Services/ConfigService.cs @@ -51,7 +51,27 @@ public class ConfigService File.ReadAllText(path) ) ?? 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() diff --git a/Moonlight/Shared/Components/Forms/SmartFormClass.razor b/Moonlight/Shared/Components/Forms/SmartFormClass.razor new file mode 100644 index 00000000..2eef37e3 --- /dev/null +++ b/Moonlight/Shared/Components/Forms/SmartFormClass.razor @@ -0,0 +1,65 @@ +@using System.Reflection +@using System.Collections +@using Moonlight.App.Helpers + +
+
+

+ +

+
+
+ @foreach (var property in Model.GetType().GetProperties()) + { + @BindAndRenderProperty(property) + } +
+
+
+
+ +@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 @; + + // 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 @; + } + 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 @
; + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Forms/SmartFormProperty.razor b/Moonlight/Shared/Components/Forms/SmartFormProperty.razor new file mode 100644 index 00000000..d398e88f --- /dev/null +++ b/Moonlight/Shared/Components/Forms/SmartFormProperty.razor @@ -0,0 +1,79 @@ +@using System.Reflection +@using Moonlight.App.Helpers +@using System.ComponentModel + + +@{ + //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; + +
+ @(a.Description) +
+} + +
+ @if (PropertyInfo.PropertyType == typeof(string)) + { + var binder = new PropBinder(PropertyInfo, Model!); + +
+ +
+ } + else if (PropertyInfo.PropertyType == typeof(int)) + { + var binder = new PropBinder(PropertyInfo, Model!); + + + } + else if (PropertyInfo.PropertyType == typeof(long)) + { + var binder = new PropBinder(PropertyInfo, Model!); + + + } + else if (PropertyInfo.PropertyType == typeof(bool)) + { + var binder = new PropBinder(PropertyInfo, Model!); + +
+ +
+ } + else if (PropertyInfo.PropertyType == typeof(DateTime)) + { + var binder = new PropBinder(PropertyInfo, Model!); + + + } + else if (PropertyInfo.PropertyType == typeof(decimal)) + { + var binder = new PropBinder(PropertyInfo, Model!); + + + } +
+ +@code +{ + [Parameter] + public PropertyInfo PropertyInfo { get; set; } + + [Parameter] + public object Model { get; set; } +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor index f4dcab1f..e7f74242 100644 --- a/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor +++ b/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor @@ -39,6 +39,11 @@ News + diff --git a/Moonlight/Shared/Views/Admin/Sys/Configuration.razor b/Moonlight/Shared/Views/Admin/Sys/Configuration.razor new file mode 100644 index 00000000..067fc902 --- /dev/null +++ b/Moonlight/Shared/Views/Admin/Sys/Configuration.razor @@ -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 + + + + + +
+ +
+ +
+ +
+
+
+
+ +@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" + ) + ); + } +} \ No newline at end of file From d2dbb68967c9edb5a7483f5fadbee0faa3348ef7 Mon Sep 17 00:00:00 2001 From: Dannyx Date: Thu, 6 Jul 2023 23:28:08 +0200 Subject: [PATCH 02/20] I got bored again (de_de.lang) --- .../defaultstorage/resources/lang/de_de.lang | 192 +++++++++--------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/Moonlight/defaultstorage/resources/lang/de_de.lang b/Moonlight/defaultstorage/resources/lang/de_de.lang index 0eb61394..3f498c23 100644 --- a/Moonlight/defaultstorage/resources/lang/de_de.lang +++ b/Moonlight/defaultstorage/resources/lang/de_de.lang @@ -19,7 +19,7 @@ Add admin accounts;Admin Konto hinzufügen First name;Vorname Last name;Nachname Email address;E-Mail-Adresse -Enter password;Password eingeben +Enter password;Passwort eingeben Next;Weiter Back;Zurück Configure features;Features konfigurieren @@ -60,10 +60,10 @@ Servers;Server Websites;Websiten Databases;Datenbanken Domains;Domains -Changelog;Anderungen +Changelog;Änderungen Firstname;Vorname Lastname;Nachname -Repeat password;Password wiederholen +Repeat password;Passwort wiederholen Sign Up;Anmelden Sign up to start with moonlight;Registrieren um mit Moonlight zu starten Sign up with Discord;Mit Discord Registrieren @@ -71,11 +71,11 @@ Sign up with Google;Mit Google Registrieren Sign-up;Registrieren Already registered?;Schon Registriert? Sign in;Registrieren -Create something new;Etwas neues erstellen +Create something new;Etwas Neues erstellen Create a gameserver;Einen Gameserver erstellen A new gameserver in just a few minutes;Ein neuer Gameserver in wenigen Minuten Create a database;Eine Datenbank erstellen -A quick way to store your data and manage it from all around the world;Eine schnelle Möglichkeit, um deine Daten von überall auf der Welt zu verwalten +A quick way to store your data and manage it from all around the world;Eine schnelle Möglichkeit, deine Daten von überall auf der Welt zu verwalten Manage your services;Deine Dienste verwalten Manage your gameservers;Gameserver verwalten Adjust your gameservers;Deine Gameserver anpassen @@ -100,7 +100,7 @@ aaPanel;aaPanel Users;Benutzer Support;Hilfe Statistics;Statistiken -No nodes found. Start with adding a new node;Keine Nodes gefunden. Ein neues Node hinzufügen +No nodes found. Start with adding a new node;Keine Nodes gefunden. Eine neue Node hinzufügen Nodename;Nodename FQDN;FQDN Create;Erstellen @@ -115,25 +115,25 @@ Memory;Arbeitsspeicher Used / Available memory;Benutzter / Verfügbarer Arbeitsspeicher Storage;Speicherplatz Available storage;Verfügbarer Speicherplatz -Add a new node;Ein neues Node hinzufügen +Add a new node;Eine neue Node hinzufügen Delete;Löschen -Deleting;Wirt gelöscht... +Deleting;Wird gelöscht... Edit;Bearbeiten -Token Id;Token Id +Token Id;Token ID Token;Token Save;Speichern Setup;Aufsetzen -Open a ssh connection to your node and enter;Eine SSH verbindung zum Node hinzufügen und öffnen -and paste the config below. Then press STRG+O and STRG+X to save;und die Config darunter einfügern. Dann STRG+O und STRG+X um zu Speichern +Open a ssh connection to your node and enter;Eine SSH verbindung zu der Node hinzufügen und öffnen +and paste the config below. Then press STRG+O and STRG+X to save;und die Config darunter einfügen. Dann STRG+O und STRG+X drücken um zu Speichern Before configuring this node, install the daemon;Installiere den Daemon bevor du dieses Node konfigurierst -Delete this node?;Dieses Node löschen? -Do you really want to delete this node;Möchtest du dieses Node wirklich löschen? +Delete this node?;Diese Node löschen? +Do you really want to delete this node;Möchtest du diese Node wirklich löschen? Yes;Ja No;Nein Status;Status Adding;Hinzufügen Port;Port -Id;Id +Id;ID Manage;Verwalten Create new server;Neuen Server erstellen No servers found;Keine Server gefunden @@ -150,20 +150,20 @@ Cores;Kerne Owner;Besitzer Value;Wert An unknown error occured;Ein unbekannter Fehler ist aufgetreten -No allocation found;Keine Zuweisung gefunden +No allocation found;Keine Allocation gefunden Identifier;Identifier -UuidIdentifier;UuidIdentifier +UuidIdentifier;UUIDIdentifier Override startup command;Startup Befehl überschreiben Loading;Wird geladen... Offline;Offline Connecting;Verbiden... Start;Start -Restart;Neu Starten +Restart;Neustarten Stop;Stoppen Shared IP;Geteilte IP Server ID;Server ID Cpu;CPU -Console;Console +Console;Konsole Files;Dateien Backups;Backups Network;Netzwerk @@ -184,11 +184,11 @@ Search files and folders;Ordner und Dateien durchsuchen Launch WinSCP;WinSCP starten New folder;Neuer Ordner Upload;Hochladen -File name;Datei-Name -File size;Datei-Größe -Last modified;Zuletz geändert +File name;Dateiname +File size;Dateigröße +Last modified;Zuletzt geändert Cancel;Abbrechen -Canceling;Wird Abbgebrochen +Canceling;Wird Abgebrochen Running;Läuft Loading backups;Backups werden geladen Started backup creation;Backup wird erstellt @@ -198,35 +198,35 @@ Move;Bewegen Archive;Archivieren Unarchive;Archivieren rückgängig machen Download;Herunterladen -Starting download;Herunterladen wird gestartet +Starting download;Download wird gestartet Backup successfully created;Backup wurde erfolgreich erstellt Restore;Wiederherstellen Copy url;URL Kopieren -Backup deletion started;Backup löschung wird gestartet +Backup deletion started;Backup Löschung wird gestartet Backup successfully deleted;Backup wurde erfolgreich gelöscht Primary;Primärer -This feature is currently not available;Diese Funktion ist zur Zeit nicht verfügbar +This feature is currently not available;Diese Funktion ist zurzeit leider nicht verfügbar Send;Senden Sending;Wird gesendet -Welcome to the support chat. Ask your question here and we will help you;Willkommen in der Chat Hilfe. Stelle hier deine Frage und wir helfen dir. - minutes ago; Minuten +Welcome to the support chat. Ask your question here and we will help you;Willkommen im Support Chat. Stelle hier deine Frage und wir helfen dir. + minutes ago; Minuten her just now;gerade eben -less than a minute ago;weniger als eine Minute +less than a minute ago;vor weniger als einer Minute 1 hour ago;vor einer Stunde 1 minute ago;vor einer Minute Failed;Fehlgeschlagen - hours ago; Stunden + hours ago; Stunden her Open tickets;Tickets öffnen Actions;Aktionen No support ticket is currently open;Kein Support Ticket ist zurzeit offen. User information;Benutzer-Information Close ticket;Ticket schließen Closing;Wird geschlossen... -The support team has been notified. Please be patient;Das Support-Team wurde benachrichtigt. Habe etwas gedult +The support team has been notified. Please be patient;Das Support-Team wurde benachrichtigt. Habe etwas Geduld The ticket is now closed. Type a message to open it again;Das Ticket wurde geschlossen. Schreibe etwas, um es wieder zu öffnen 1 day ago;vor einem Tag is typing;schreibt... -are typing;schreiben +are typing;schreiben... No domains available;Keine Domains verfügbar Shared domains;Geteilte Domains Shared domain;Geteilte Domain @@ -238,16 +238,16 @@ Fetching dns records;Es wird nach DNS-Records gesucht No dns records found;Keine DNS-Records gefunden Content;Inhalt Priority;Priorität -Ttl;Ttl +Ttl;TTL Enable cloudflare proxy;Cloudflare-Proxy benutzen CF Proxy;CF Proxy - days ago; Tage + days ago; Tage her Cancle;Abbrechen An unexpected error occured;Ein unbekannter Fehler ist aufgetreten Testy;Testy Error from cloudflare api;Fehler von der Cloudflare-API Profile;Profil -No subscription available;Kein Abo verfügbar +No subscription available;Kein Abonnement verfügbar Buy;Kaufen Redirecting;Weiterleiten Apply;Anwenden @@ -255,7 +255,7 @@ Applying code;Code Anwenden Invalid subscription code;Unbekannter Abo-Code Cancel Subscription;Abo beenden Active until;Aktiv bis -We will send you a notification upon subscription expiration;Wenn dein Abo endet, senden wir dir eine E-Mail +We will send you a notification upon subscription expiration;Wenn dein Abonnement endet, senden wir dir eine E-Mail This token has been already used;Dieser Token wurde schon benutzt New login for;Neue Anmeldung für No records found for this day;Für diesen Tag wurden keine Records gefunden @@ -265,7 +265,7 @@ Minecraft version;Minecraft Version Build version;Build Version Server installation is currently running;Der Server wird installiert. Selected;Ausgewählt -Move deleted;Gelöschtest Bewegen +Move deleted;Gelöschtes Bewegen Delete selected;Ausgewähltes löschen Log level;Log Level Log message;Log Message @@ -274,11 +274,11 @@ Version;Version You are running moonlight version;Du benutzt die Moonlight-Version Operating system;Betriebssystem Moonlight is running on;Moonlight läuft auf -Memory usage;Arbeitsspeicher-Auslastung +Memory usage;Arbeitsspeicher Auslastung Moonlight is using;Moonlight benutzt of memory;des Arbeitsspeichers Cpu usage;CPU Auslastung -Refresh;Neu Laden +Refresh;Neuladen Send a message to all users;Eine Nachricht an alle Benutzer senden IP;IP URL;URL @@ -286,9 +286,9 @@ Device;Gerät Change url;URL Ändern Message;Nachricht Enter message;Nachricht eingeben -Enter the message to send;Eine Nachricht zum senden eingeben +Enter the message to send;Eine Nachricht zum Senden eingeben Confirm;Bestätigen -Are you sure?;Bist du dir sicher +Are you sure?;Bist du dir sicher? Enter url;URL eingeben An unknown error occured while starting backup deletion;Ein unbekannter Fehler ist während der Backuplöschung aufgetreten Success;erfolgreich @@ -298,9 +298,9 @@ Backup successfully restored;Das Backup wurde erfolgreich wiedergeherstellt Register for;Registrieren für Core;Kern Logs;Logs -AuditLog;AuditLog -SecurityLog;SecurityLog -ErrorLog;ErrorLog +AuditLog;Audit Log +SecurityLog;Security Log +ErrorLog;Error Log Resources;Resourcen WinSCP cannot be launched here;WinSCP kann nicht gestartet werden Create a new folder;Neuen Ordner erstellen @@ -311,24 +311,24 @@ Sessions;Sitzungen New user;Neuer Benutzer Created at;Erstellt am Mail template not found;E-Mail template wurde nicht gefunden -Missing admin permissions. This attempt has been logged ;Fehlende Admin-Rechte. Dieser Versuch wurde aufgezeichnet +Missing admin permissions. This attempt has been logged ;Fehlende Admin-Rechte. Dieser Versuch wurde aufgezeichnet und ist für das ganze Admin Team sichtbar Address;Addresse City;Stadt State;Land Country;Staat -Totp;Totp +Totp;TOTP Discord;Discord -Subscription;Abonament +Subscription;Abonnement None;None No user with this id found;Kein Benutzer mit dieser ID gefunden -Back to list;Zurück zur liste -New domain;Neue domain +Back to list;Zurück zur Liste +New domain;Neue Domain Reset password;Password wiederherstellen Password reset;Password wiederherstellung Reset the password of your account;Password deines Accounts zurücksetzen Wrong here?;Falsch hier? A user with this email can not be found;Ein Benutzer mit dieser E-Mail konnte nicht gefunden werden -Passwort reset successfull. Check your mail;Password wiederherstellung erfolgreich. Überprüfe deine Email +Passwort reset successfull. Check your mail;Password wiederherstellung erfolgreich. Überprüfe dein Email Postfach Discord bot;Discord Bot New image;Neues Image Description;Beschreibung @@ -348,15 +348,15 @@ Startup detection;Startuperkennung Stop command;Stopp-Befehl Successfully saved image;Das Image wurde erfolgreich gespeichert No docker images found;Keine Docker Images gefunden -Key;Schlüssel +Key;Key Default value;Standardwert Allocations;Zuweisung No variables found;Keine Variablen gefunden Successfully added image;Das Image wurde erfolgreich hinzugefügt Password change for;Password ändern für of;von -New node;Neues Node -Fqdn;Fqdn +New node;Neue Node +Fqdn;FQDN Cores used;Kerne genutzt used;benutzt 5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64 @@ -367,7 +367,7 @@ details;Details 1;1 2;2 DDos;DDos -No ddos attacks found;Keine DDoS gefunden +No ddos attacks found;Keine DDos Attacken gefunden Node;Node Date;Datum DDos attack started;DDos Attacke gestartet @@ -383,25 +383,25 @@ Do you really want to kill all running servers?;Möchtest du wirklich alle laufe Change power state for;Power-State ändern für to;zu Stop all servers;Alle Server stoppen -Do you really want to stop all running servers?;Möchtest du wirklich alle laufenden Server Killen? +Do you really want to stop all running servers?;Möchtest du wirklich alle laufenden Server stoppen? Manage ;Verwalten Manage user ;Benutzer verwalten -Reloading;Neu Laden... +Reloading;Lade neu... Update;Aktualisieren Updating;Wird Aktualisiert... Successfully updated user;Benutzer erfolgreich aktualisiert -Discord id;Discord ID +Discord id;Discord User ID Discord username;Discord Benutzername Discord discriminator;Discord Tag The Name field is required.;Der Name dieses Feldes ist erforderlich -An error occured while logging you in;Ein Fehler ist aufgetreten, als du dich angemeldet hast +An error occured while logging you in;Ein Fehler ist aufgetreten, während du dich angemeldet hast You need to enter an email address;Du musst eine E-Mail-Adresse angeben You need to enter a password;Du musst ein Password eingeben You need to enter a password with minimum 8 characters in lenght;Du musst ein Password eingeben, das mindestens 8 Buchstaben lang ist Proccessing;Weid verarbeitet... The FirstName field is required.;Das Vorname-Feld ist erforderlich The LastName field is required.;Das Nachname-Feld ist erforderlich -The Address field is required.;Das Address-Feld ist erforderlich +The Address field is required.;Das Addresse-Feld ist erforderlich The City field is required.;Das Stadt-Feld ist erforderlich The State field is required.;Das Staat-Feld ist erforderlich The Country field is required.;Das Land-Feld ist erforderlich @@ -417,7 +417,7 @@ Disable;Deaktivieren Addons;Add-ons Javascript version;Javascript Version Javascript file;Javascript Datei -Select javascript file to execute on start;Javascript Datei zum starten auswählen +Select javascript file to execute on start;Javascript Datei zum Starten auswählen Submit;Einreichen Processing;Wird verarbeitet... Go up;Nach oben gehen @@ -433,15 +433,15 @@ Are you sure you want to reset this server?;Möchtest du diesen Server wirklich Are you sure? This cannot be undone;Bist du dir sicher? Dies kann nicht rückgängig gemacht werden Resetting server;Server wird zurückgesetzt... Deleted file;Datei gelöscht -Reinstalling server;Server wird reinstalliert +Reinstalling server;Server wird neuinstalliert Uploading files;Dateien wurden hochgeladen complete;vollständig Upload complete;Upload komplett Security;Sicherheit -Subscriptions;Abonaments +Subscriptions;Abonnements 2fa Code;2FA Code Your account is secured with 2fa;Dein Account wird mit 2-FA gesichert -anyone write a fancy text here?;hier einen schönen Text schreiben? +anyone write a fancy text here?;hier einen schönen Text schreiben? -Nö. Activate 2fa;2-FA Aktivieren 2fa apps;2-FA Apps Use an app like ;Benutze eine App wie @@ -453,34 +453,34 @@ Finish activation;Aktivierung fertig New password;Neues Password Secure your account;Deinen Account sichern 2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.;2-FA fügt eine weitere Sicherheits-Schicht hinzu. Du musst einen 6-Ziffern-Code eingeben, um dich anzumelden. -New subscription;Neues Abonament +New subscription;Neues Abonnement You need to enter a name;Du musst einen Namen eingeben You need to enter a description;Du musst eine Beschreibung eigeben Add new limit;Ein neues Limit hinzufügen -Create subscription;Abonament erstellen +Create subscription;Abonnement erstellen Options;Optionen Amount;Betrag Do you really want to delete it?;Möchtes du es wirklich löschen? -Loading your subscription;Dein Abonament wird geladen -Searching for deploy node;#Empty# +Loading your subscription;Dein Abonnement wird geladen +Searching for deploy node;Suche nach einer verfügbaren Node Searching for available images;Nach verfügbaren Images wird gesucht Server details;Server Details Configure your server;Deinen Server konfigurieren Default;Standart -You reached the maximum amount of servers for every image of your subscription;Du hast die maximale Anzahl an Images von deinem Abonament erreicht. -Personal information;Prsönliche Informationen +You reached the maximum amount of servers for every image of your subscription;Du hast die maximale Anzahl an Images von deinem Abonnement erreicht. +Personal information;Persönliche Informationen Enter code;Code eingeben Server rename;Server Umbenennen Create code;Code erstellen -Save subscription;Abonament speichern +Save subscription;Abonnement speichern Enter your information;Informationen eingeben You need to enter your full name in order to use moonlight;Du musst deinen ganzen Namen eingeben, um Moonlight zu nutzen No node found;Kein Node gefunden -No node found to deploy to found;#Empty# +No node found to deploy to found;Keine Node für die Bereitstellung gefunden Node offline;Node offline -The node the server is running on is currently offline;Das Node, auf dem der Server grat läuft, ist offline +The node the server is running on is currently offline;Das Node, auf dem der Server grade läuft, ist offline Server not found;Server konnte nicht gefunden werden -A server with that id cannot be found or you have no access for this server;Ein Server mit dieser ID konnte nicht gefunden werden +A server with that id cannot be found or you have no access for this server;Ein Server mit dieser ID konnte nicht gefunden werden oder du hast keinen Zugriff auf ihn Compress;Komprimieren Decompress;De-Komprimieren Moving;Bewegen... @@ -495,11 +495,11 @@ No SSL certificate found;Keine SSL-Zertifikate gefunden Ftp Host;FTP Host Ftp Port;FTP Port Ftp Username;FTP Username -Ftp Password;FTP Password +Ftp Password;FTP Passwort Use;Benutzen SSL Certificates;SSL Zertifikate SSL certificates;SSL Zertifikate -Issue certificate;SSL-Zertifikat Ausgeben +Issue certificate;SSL-Zertifikat erstellen lassen New plesk server;Neuer Plesk Server Api url;API URL Host system offline;Host System Offline @@ -514,14 +514,14 @@ Username;Benutzername SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one;SRV Records können aufgrund von Cloudflare nicht geupdatet werden. Bitte lösche den Record und erstelle einen neuen. The User field is required.;Das Benutzer-Feld ist erforderlich You need to specify a owner;Du musst einen Server-Besitzer angeben -You need to specify a image;You need to specify a image +You need to specify a image;Du musst ein Image angeben Api Url;API URL Api Key;Api Key Duration;Dauer -Enter duration of subscription;Dauer des Abonaments eingeben +Enter duration of subscription;Dauer des Abonnements eingeben Copied code to clipboard;Code in die Zwischenablage kopiert Invalid or expired subscription code;Ungültiger oder Abgelaufener Abo-Code -Current subscription;Dein Abonament +Current subscription;Dein Abonnement You need to specify a server image;Du musst ein Image angeben CPU;CPU Hour;Stunde @@ -532,14 +532,14 @@ All time;Für immer This function is not implemented;Diese Funktion wurde noch nicht hinzugefügt Domain details;Domain Details Configure your domain;Deine Domain konfigurieren -You reached the maximum amount of domains in your subscription;Du hast das Maximum an Domains in deinem Abonament erreicht +You reached the maximum amount of domains in your subscription;Du hast das Maximum an Domains in deinem Abonnement erreicht You need to specify a shared domain;Du musst eine Shared-Domain angeben A domain with this name does already exist for this shared domain;Eine Domain mit diesem Name existiert bereits in dieser Shared-Domain The Email field is required.;Das E-Mail-Feld ist erforderlich The Password field is required.;Das Password-Feld ist erforderlich The ConfirmPassword field is required.;Das Password-Bestätigen-Feld ist erforderlich Passwords need to match;Die Passwörter müssen übereinstimmen -Cleanup exception;Cleanup ausnahme +Cleanup exception;Cleanup Ausnahme No shared domain found;Keine Shared-Domain gefunden Searching for deploy plesk server;Suchen um den Plesk Server aufzusetzen No plesk server found;Kein Plesk Server gefunden @@ -561,7 +561,7 @@ We were not able to find any domains associated with your account;Wir haben kein You have no websites;Du hast keine Websites We were not able to find any websites associated with your account;Wir haben keine Websiten, die mit deinem Account verbunden sind, gefunden Guest;Gast -You need a domain;Du brauchts eine Domain +You need a domain;Du brauchst eine Domain New post;Neuer Post New entry;Neuer Eintrag You have no servers;Du hast keine Server @@ -572,27 +572,27 @@ Error from daemon;Fehler vom Daemon End;Ende Cloud panel;Cloud Panel Cloud panels;Cloud Panels -New cloud panel;Neues cloud Panel +New cloud panel;Neues Cloud Panel You need to enter an api key;Du musst einen API-Key eigeben Webspaces;Webspaces New webspace;Neuer Webspace -The uploaded file should not be bigger than 100MB;DIe Datei sollte nicht größer als 100MB sein -An unknown error occured while uploading a file;Ein unbekannter Fehler ist während dem Datei-Hochladen aufgetreten +The uploaded file should not be bigger than 100MB;Die Datei sollte nicht größer als 100MB sein +An unknown error occured while uploading a file;Ein unbekannter Fehler ist während dem Datei Upload aufgetreten No databases found for this webspace;Keine Datenbanken für diesen Webspace gefunden Sftp;SFTP -Sftp Host;Sftp Host -Sftp Port;Sftp Port -Sftp Username;Sftp Benutzername -Sftp Password;Sftp Password +Sftp Host;SFTP Host +Sftp Port;SFTP Port +Sftp Username;SFTP Benutzername +Sftp Password;SFTP Password Lets Encrypt certificate successfully issued;Lets Encrypt Zertifikat erfolgreich erstellt Add shared domain;Shared Domain Hinzufügen Webspace;Webspace -You reached the maximum amount of websites in your subscription;Du hast das Maximum an Websiten in deinem Abonament erreicht +You reached the maximum amount of websites in your subscription;Du hast das Maximum an Websiten in deinem Abonnement erreicht Searching for deploy web host;Suchen um den Webhost aufzusetzen Webspace details;Webspace Details Web host;Web host Configure your webspaces;Konfiguriere deine Webspaces -You reached the maximum amount of webspaces in your subscription;Du hast das Maximum an Webspaces in deinem Abonament erreicht +You reached the maximum amount of webspaces in your subscription;Du hast das Maximum an Webspaces in deinem Abonnement erreicht Create a webspace;Einen Webspace erstellen Manage your webspaces;Deine Webspaces verwalten Modify the content of your webspaces;Den Inhalt deiner Webspaces verwalten @@ -607,11 +607,11 @@ We paused your connection because of inactivity. The resume just focus the tab a Failed to reconnect to the moonlight servers;Die Wiederverbindung zu den Moonlight-Servern ist gescheitert We were unable to reconnect to moonlight. Please refresh the page;Verbindung zu Moonlight fehlgeschlagen. Bitte aktualisiere die Seite Failed to reconnect to the moonlight servers. The connection has been rejected;Die Wiederverbindung zu den Moonlight-Servern ist fehlgeschlagen. Die Verbindung wurde abgelehnt -We were unable to reconnect to moonlight. Most of the time this is caused by an update of moonlight. Please refresh the page;Verbindung zu Moonlight fehlgeschlagen. Meistens wird dies durch eine Aktualisierung von Moonlight verursacht. Bitte aktualisieren Sie die Seite -Verifying token, loading user data;Token verifizieren, Benutzer-Daten laden -Reload config;Konfiguration neu laden -Successfully reloading configuration;Konfiguration wird neu geladen... -Successfully reloaded configuration;Die Konfiguration wurde erfolgreich neu geladen +We were unable to reconnect to moonlight. Most of the time this is caused by an update of moonlight. Please refresh the page;Verbindung zu Moonlight fehlgeschlagen. Meistens wird dies durch eine Aktualisierung von Moonlight verursacht. Bitte aktualisiere die Seite +Verifying token, loading user data;Token verifizieren, lade Benutzerdaten +Reload config;Konfiguration neuladen +Successfully reloading configuration;Konfiguration wird neugeladen... +Successfully reloaded configuration;Die Konfiguration wurde erfolgreich neugeladen Flows;Flows Add node;Node Hinzufügen Web system;Web System @@ -627,10 +627,10 @@ Fabric loader version;Fabric Loader Version Rate;Rate Hey, can i borrow you for a second?;Hey, kann ich dich mal kurz ausleihen? We want to improve our services and get a little bit of feedback how we are currently doing. Please leave us a rating;Da wir unsere Dienste ständig verbessern, möchten wir dich um Feedback bitten. Bitte Bewerte uns: -Thanks for your rating;Danke für deine Bewertun +Thanks for your rating;Danke für deine Bewertung It would be really kind of you rating us on a external platform as it will help our project very much;Es wäre wirklich nett, wenn du uns auf einer externen Plattform bewerten würdest, denn das würde unserem Projekt sehr helfen Close;Schließen -Rating saved;Bewretung gespeichert +Rating saved;Bewertung gespeichert Group;Gruppe Beta;Beta Create a new group;Eine neue Gruppe erstellen From f52b9e295138dbb423b2728846f107109c2d8ef7 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Fri, 7 Jul 2023 03:06:16 +0200 Subject: [PATCH 03/20] Added telemetry reporter --- .../Telemetry/Requests/TelemetryData.cs | 11 ++++ .../Telemetry/TelemetryApiHelper.cs | 52 ++++++++++++++++ .../Telemetry/TelemetryException.cs | 32 ++++++++++ .../Services/Background/TelemetryService.cs | 62 +++++++++++++++++++ Moonlight/Program.cs | 4 ++ 5 files changed, 161 insertions(+) create mode 100644 Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs create mode 100644 Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs create mode 100644 Moonlight/App/ApiClients/Telemetry/TelemetryException.cs create mode 100644 Moonlight/App/Services/Background/TelemetryService.cs diff --git a/Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs b/Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs new file mode 100644 index 00000000..c8fafe75 --- /dev/null +++ b/Moonlight/App/ApiClients/Telemetry/Requests/TelemetryData.cs @@ -0,0 +1,11 @@ +namespace Moonlight.App.ApiClients.Telemetry.Requests; + +public class TelemetryData +{ + public string AppUrl { get; set; } = ""; + public int Servers { get; set; } + public int Nodes { get; set; } + public int Users { get; set; } + public int Databases { get; set; } + public int Webspaces { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs b/Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs new file mode 100644 index 00000000..d9b07450 --- /dev/null +++ b/Moonlight/App/ApiClients/Telemetry/TelemetryApiHelper.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json; +using RestSharp; + +namespace Moonlight.App.ApiClients.Telemetry; + +public class TelemetryApiHelper +{ + private readonly RestClient Client; + + public TelemetryApiHelper() + { + Client = new(); + } + + public async Task Post(string resource, object? body) + { + var request = CreateRequest(resource); + + request.Method = Method.Post; + + request.AddParameter("application/json", JsonConvert.SerializeObject(body), ParameterType.RequestBody); + + var response = await Client.ExecuteAsync(request); + + if (!response.IsSuccessful) + { + if (response.StatusCode != 0) + { + throw new TelemetryException( + $"An error occured: ({response.StatusCode}) {response.Content}", + (int)response.StatusCode + ); + } + else + { + throw new Exception($"An internal error occured: {response.ErrorMessage}"); + } + } + } + + private RestRequest CreateRequest(string resource) + { + var url = "https://telemetry.moonlightpanel.xyz/" + resource; + + var request = new RestRequest(url) + { + Timeout = 3000000 + }; + + return request; + } +} \ No newline at end of file diff --git a/Moonlight/App/ApiClients/Telemetry/TelemetryException.cs b/Moonlight/App/ApiClients/Telemetry/TelemetryException.cs new file mode 100644 index 00000000..7b66b710 --- /dev/null +++ b/Moonlight/App/ApiClients/Telemetry/TelemetryException.cs @@ -0,0 +1,32 @@ +using System.Runtime.Serialization; + +namespace Moonlight.App.ApiClients.Telemetry; + +[Serializable] +public class TelemetryException : Exception +{ + public int StatusCode { get; set; } + + public TelemetryException() + { + } + + public TelemetryException(string message, int statusCode) : base(message) + { + StatusCode = statusCode; + } + + public TelemetryException(string message) : base(message) + { + } + + public TelemetryException(string message, Exception inner) : base(message, inner) + { + } + + protected TelemetryException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Background/TelemetryService.cs b/Moonlight/App/Services/Background/TelemetryService.cs new file mode 100644 index 00000000..53e1131f --- /dev/null +++ b/Moonlight/App/Services/Background/TelemetryService.cs @@ -0,0 +1,62 @@ +using Moonlight.App.ApiClients.Telemetry; +using Moonlight.App.ApiClients.Telemetry.Requests; +using Moonlight.App.Database.Entities; +using Moonlight.App.Helpers; +using Moonlight.App.Repositories; + +namespace Moonlight.App.Services.Background; + +public class TelemetryService +{ + private readonly IServiceScopeFactory ServiceScopeFactory; + private readonly ConfigService ConfigService; + + public TelemetryService( + ConfigService configService, + IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + ConfigService = configService; + + if(!ConfigService.DebugMode) + Task.Run(Run); + } + + private async Task Run() + { + var timer = new PeriodicTimer(TimeSpan.FromMinutes(15)); + + while (true) + { + using var scope = ServiceScopeFactory.CreateScope(); + + var serversRepo = scope.ServiceProvider.GetRequiredService>(); + var nodesRepo = scope.ServiceProvider.GetRequiredService>(); + var usersRepo = scope.ServiceProvider.GetRequiredService>(); + var webspacesRepo = scope.ServiceProvider.GetRequiredService>(); + var databaseRepo = scope.ServiceProvider.GetRequiredService>(); + + var apiHelper = scope.ServiceProvider.GetRequiredService(); + + try + { + await apiHelper.Post("telemetry", new TelemetryData() + { + Servers = serversRepo.Get().Count(), + Databases = databaseRepo.Get().Count(), + Nodes = nodesRepo.Get().Count(), + Users = usersRepo.Get().Count(), + Webspaces = webspacesRepo.Get().Count(), + AppUrl = ConfigService.Get().Moonlight.AppUrl + }); + } + catch (Exception e) + { + Logger.Warn("Error sending telemetry"); + Logger.Warn(e); + } + + await timer.WaitForNextTickAsync(); + } + } +} \ No newline at end of file diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index 1a5160a3..fb850669 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -6,6 +6,7 @@ using Moonlight.App.ApiClients.CloudPanel; using Moonlight.App.ApiClients.Daemon; using Moonlight.App.ApiClients.Modrinth; using Moonlight.App.ApiClients.Paper; +using Moonlight.App.ApiClients.Telemetry; using Moonlight.App.ApiClients.Wings; using Moonlight.App.Database; using Moonlight.App.Diagnostics.HealthChecks; @@ -236,6 +237,7 @@ namespace Moonlight builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); // Background services builder.Services.AddSingleton(); @@ -243,6 +245,7 @@ namespace Moonlight builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Other builder.Services.AddSingleton(); @@ -287,6 +290,7 @@ namespace Moonlight _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); + _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); From 80eb210af039ef9701bc2a415c90c7318cb31023 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Fri, 7 Jul 2023 18:09:38 +0200 Subject: [PATCH 04/20] Add config option to prevent users from login and register --- Moonlight/App/Configuration/ConfigV1.cs | 16 ++++++++++++++-- Moonlight/App/Services/UserService.cs | 8 ++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Moonlight/App/Configuration/ConfigV1.cs b/Moonlight/App/Configuration/ConfigV1.cs index 053aa1c1..4c07e7d4 100644 --- a/Moonlight/App/Configuration/ConfigV1.cs +++ b/Moonlight/App/Configuration/ConfigV1.cs @@ -17,6 +17,8 @@ public class ConfigV1 [Description("The url moonlight is accesible with from the internet")] public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash"; + [JsonProperty("Auth")] public AuthData Auth { get; set; } = new(); + [JsonProperty("Database")] public DatabaseData Database { get; set; } = new(); [JsonProperty("DiscordBotApi")] public DiscordBotApiData DiscordBotApi { get; set; } = new(); @@ -39,8 +41,7 @@ public class ConfigV1 [JsonProperty("Subscriptions")] public SubscriptionsData Subscriptions { get; set; } = new(); - [JsonProperty("DiscordNotifications")] - public DiscordNotificationsData DiscordNotifications { get; set; } = new(); + [JsonProperty("DiscordNotifications")] public DiscordNotificationsData DiscordNotifications { get; set; } = new(); [JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new(); @@ -50,6 +51,17 @@ public class ConfigV1 [JsonProperty("Sentry")] public SentryData Sentry { get; set; } = new(); } + + public class AuthData + { + [JsonProperty("DenyLogin")] + [Description("Prevent every new login")] + public bool DenyLogin { get; set; } = false; + + [JsonProperty("DenyRegister")] + [Description("Prevent every new user to register")] + public bool DenyRegister { get; set; } = false; + } public class CleanupData { diff --git a/Moonlight/App/Services/UserService.cs b/Moonlight/App/Services/UserService.cs index 0869c3c8..2a611cb9 100644 --- a/Moonlight/App/Services/UserService.cs +++ b/Moonlight/App/Services/UserService.cs @@ -18,6 +18,7 @@ public class UserService private readonly IdentityService IdentityService; private readonly IpLocateService IpLocateService; private readonly DateTimeService DateTimeService; + private readonly ConfigService ConfigService; private readonly string JwtSecret; @@ -32,6 +33,7 @@ public class UserService { UserRepository = userRepository; TotpService = totpService; + ConfigService = configService; MailService = mailService; IdentityService = identityService; IpLocateService = ipLocateService; @@ -44,6 +46,9 @@ public class UserService public async Task Register(string email, string password, string firstname, string lastname) { + if (ConfigService.Get().Moonlight.Auth.DenyRegister) + throw new DisplayException("This operation was disabled"); + // Check if the email is already taken var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null; @@ -108,6 +113,9 @@ public class UserService public async Task Login(string email, string password, string totpCode = "") { + if (ConfigService.Get().Moonlight.Auth.DenyLogin) + throw new DisplayException("This operation was disabled"); + // First password check and check if totp is enabled var needTotp = await CheckTotp(email, password); From b8e39824b54eae516975a6aaabaebd96f3aa700f Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Fri, 7 Jul 2023 18:22:18 +0200 Subject: [PATCH 05/20] Added cloudflare enable option --- Moonlight/App/Services/DomainService.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Moonlight/App/Services/DomainService.cs b/Moonlight/App/Services/DomainService.cs index c56855f1..7eb0a0b1 100644 --- a/Moonlight/App/Services/DomainService.cs +++ b/Moonlight/App/Services/DomainService.cs @@ -20,6 +20,7 @@ namespace Moonlight.App.Services; public class DomainService { private readonly DomainRepository DomainRepository; + private readonly ConfigService ConfigService; private readonly SharedDomainRepository SharedDomainRepository; private readonly CloudFlareClient Client; private readonly string AccountId; @@ -29,6 +30,7 @@ public class DomainService DomainRepository domainRepository, SharedDomainRepository sharedDomainRepository) { + ConfigService = configService; DomainRepository = domainRepository; SharedDomainRepository = sharedDomainRepository; @@ -48,6 +50,9 @@ public class DomainService public Task Create(string domain, SharedDomain sharedDomain, User user) { + if (!ConfigService.Get().Moonlight.Domains.Enable) + throw new DisplayException("This operation is disabled"); + if (DomainRepository.Get().Where(x => x.SharedDomain.Id == sharedDomain.Id).Any(x => x.Name == domain)) throw new DisplayException("A domain with this name does already exist for this shared domain"); @@ -63,6 +68,9 @@ public class DomainService public Task Delete(Domain domain) { + if (!ConfigService.Get().Moonlight.Domains.Enable) + throw new DisplayException("This operation is disabled"); + DomainRepository.Delete(domain); return Task.CompletedTask; @@ -71,6 +79,9 @@ public class DomainService public async Task GetAvailableDomains() // This method returns all available domains which are not added as a shared domain { + if (!ConfigService.Get().Moonlight.Domains.Enable) + return Array.Empty(); + var domains = GetData( await Client.Zones.GetAsync(new() { @@ -93,6 +104,9 @@ public class DomainService public async Task GetDnsRecords(Domain d) { + if (!ConfigService.Get().Moonlight.Domains.Enable) + return Array.Empty(); + var domain = EnsureData(d); var records = new List(); @@ -166,6 +180,9 @@ public class DomainService public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord) { + if (!ConfigService.Get().Moonlight.Domains.Enable) + throw new DisplayException("This operation is disabled"); + var domain = EnsureData(d); var rname = $"{domain.Name}.{domain.SharedDomain.Name}"; @@ -225,6 +242,9 @@ public class DomainService public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord) { + if (!ConfigService.Get().Moonlight.Domains.Enable) + throw new DisplayException("This operation is disabled"); + var domain = EnsureData(d); var rname = $"{domain.Name}.{domain.SharedDomain.Name}"; @@ -255,6 +275,9 @@ public class DomainService public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord) { + if (!ConfigService.Get().Moonlight.Domains.Enable) + throw new DisplayException("This operation is disabled"); + var domain = EnsureData(d); GetData( From ba2de54c6001b3c53e70686eb2e815916d68a0f6 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner <68913099+Marcel-Baumgartner@users.noreply.github.com> Date: Fri, 7 Jul 2023 19:01:08 +0200 Subject: [PATCH 06/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f38f6e3..858f450c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ![Screen Shot](https://media.discordapp.net/attachments/1059911407170228234/1119793539732217876/image.png?width=1340&height=671) -Moonlight is a new free and open source alternative to pterodactyl allowing users to create their own hosting platform and host all sorts of gameservers in docker containers. With a simple migration from pterodactyl to moonlight (migrator will be open source soon) you can easily switch to moonlight and use its features like a server manager, cleanup system and automatic version switcher, just to name a few. +Moonlight is a new free and open source alternative to pterodactyl allowing users to create their own hosting platform and host all sorts of gameservers in docker containers. With a simple migration from pterodactyl to moonlight ([see guide](https://docs.moonlightpanel.xyz/migrating-from-pterodactyl)) you can easily switch to moonlight and use its features like a server manager, cleanup system and automatic version switcher, just to name a few. Moonlight's core features are From 384b6a3e7d2500d2518a1356c84d82658fa21961 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Fri, 7 Jul 2023 19:09:23 +0200 Subject: [PATCH 07/20] Fixed session view --- Moonlight/App/Services/Sessions/SessionClientService.cs | 4 ++++ Moonlight/Shared/Views/Admin/Users/Sessions.razor | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Moonlight/App/Services/Sessions/SessionClientService.cs b/Moonlight/App/Services/Sessions/SessionClientService.cs index c152ffda..e7040ba4 100644 --- a/Moonlight/App/Services/Sessions/SessionClientService.cs +++ b/Moonlight/App/Services/Sessions/SessionClientService.cs @@ -11,6 +11,8 @@ public class SessionClientService public readonly Guid Uuid = Guid.NewGuid(); public readonly DateTime CreateTimestamp = DateTime.UtcNow; public User? User { get; private set; } + public string Ip { get; private set; } = "N/A"; + public string Device { get; private set; } = "N/A"; public readonly IdentityService IdentityService; public readonly AlertService AlertService; @@ -39,6 +41,8 @@ public class SessionClientService public async Task Start() { User = await IdentityService.Get(); + Ip = IdentityService.GetIp(); + Device = IdentityService.GetDevice(); if (User != null) // Track users last visit { diff --git a/Moonlight/Shared/Views/Admin/Users/Sessions.razor b/Moonlight/Shared/Views/Admin/Users/Sessions.razor index 4d793cc5..a9df5cf1 100644 --- a/Moonlight/Shared/Views/Admin/Users/Sessions.razor +++ b/Moonlight/Shared/Views/Admin/Users/Sessions.razor @@ -60,13 +60,13 @@ From c866e89b725b03636a70060940066450250a376b Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Sat, 8 Jul 2023 02:50:13 +0200 Subject: [PATCH 08/20] Enhanced install console --- Moonlight/Shared/Views/Server/Index.razor | 31 +++++------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/Moonlight/Shared/Views/Server/Index.razor b/Moonlight/Shared/Views/Server/Index.razor index a146d133..3b7cabf3 100644 --- a/Moonlight/Shared/Views/Server/Index.razor +++ b/Moonlight/Shared/Views/Server/Index.razor @@ -26,7 +26,7 @@ @if (CurrentServer == null) { - + } else { @@ -34,33 +34,16 @@ { if (Console.ConsoleState == ConsoleState.Connected) { - if (Console.ServerState == ServerState.Installing) + if (Console.ServerState == ServerState.Installing || CurrentServer.Installing) {
-
-
- - Server installation is currently running - -
+
+ Server installation is currently running
- -
-
- } - else if (CurrentServer.Installing) - { -
-
-
-
- - Server installation is currently running - -
+
+
-
} @@ -104,7 +87,7 @@ - + From e4c21c74a51ce3ddb6b5b38f8c5af7a8e7ddb61f Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Sun, 9 Jul 2023 19:46:18 +0200 Subject: [PATCH 09/20] Added custom scroll bar --- Moonlight/wwwroot/assets/css/blazor.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Moonlight/wwwroot/assets/css/blazor.css b/Moonlight/wwwroot/assets/css/blazor.css index cbe7e5aa..0ce38631 100644 --- a/Moonlight/wwwroot/assets/css/blazor.css +++ b/Moonlight/wwwroot/assets/css/blazor.css @@ -41,6 +41,7 @@ .components-reconnect-rejected > .rejected { display: block; } + .components-reconnect-hide > div { display: none; } @@ -55,4 +56,17 @@ .components-reconnect-rejected { display: block; +} + +::-webkit-scrollbar { + width: 12px; + background: var(--kt-card-bg); +} + +::-webkit-scrollbar-thumb { + background: #6964E4; +} + +::-webkit-scrollbar-thumb:hover { + background: #6964E4; } \ No newline at end of file From 151bc82998b55d1d167c8e83cb1ab898fcab912e Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Sun, 9 Jul 2023 19:46:32 +0200 Subject: [PATCH 10/20] Added better server image seletor --- Moonlight/Shared/Views/Servers/Create.razor | 83 +++++++++++++++------ 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/Moonlight/Shared/Views/Servers/Create.razor b/Moonlight/Shared/Views/Servers/Create.razor index 30f687d6..853fcd42 100644 --- a/Moonlight/Shared/Views/Servers/Create.razor +++ b/Moonlight/Shared/Views/Servers/Create.razor @@ -48,7 +48,9 @@
- +
@(DeployNode.Name)
@if (Model.Image != null) @@ -56,12 +58,16 @@ var limit = Images[Model.Image];
- +
@(Model.Image.Name)
- +
@{ var cpu = limit.ReadValue("cpu"); @@ -76,12 +82,16 @@
- +
@(limit.ReadValue("memory")) MB
- +
@(limit.ReadValue("disk")) MB
} @@ -108,15 +118,6 @@
@if (Images.Any()) { - - - - @@ -125,13 +126,45 @@ {
- You reached the maximum amount of servers for every image of your subscription: @(Subscription == null ? SmartTranslateService.Translate("Default") : Subscription.Name) + We could not find any image in your subscription you have access to: @(Subscription == null ? SmartTranslateService.Translate("Default") : Subscription.Name)
}
+ +
+ @foreach (var keyValuePair in Images) + { + bool selected = Model.Image != null && Model.Image.Id == keyValuePair.Key.Id; + +
+ @if (ServerCounts[keyValuePair.Key] > keyValuePair.Value.Amount) + { +
+
+
@(keyValuePair.Key.Name)
+

+ @(keyValuePair.Key.Description) +

+
+
+ } + else + { + +
+
@(keyValuePair.Key.Name)
+

+ @(keyValuePair.Key.Description) +

+
+
+ } +
+ } +
} @@ -141,11 +174,12 @@ { [CascadingParameter] public User User { get; set; } - + private Node? DeployNode; private Subscription? Subscription; private Dictionary Images = new(); + private Dictionary ServerCounts = new(); private ServerOrderDataModel Model = new(); @@ -176,9 +210,9 @@ .Include(x => x.Image) .Where(x => x.Owner.Id == User.Id) .Count(x => x.Image.Id == image.Id); - - if(serversCount < limit.Amount) - Images.Add(image, limit); + + Images.Add(image, limit); + ServerCounts.Add(image, serversCount); } } } @@ -198,7 +232,7 @@ if (serversCount < limit.Amount) { - if(int.TryParse(limit.ReadValue("cpu"), out int cpu) && + if (int.TryParse(limit.ReadValue("cpu"), out int cpu) && int.TryParse(limit.ReadValue("memory"), out int memory) && int.TryParse(limit.ReadValue("disk"), out int disk)) { @@ -211,7 +245,7 @@ Model.Image, DeployNode ); - + NavigationManager.NavigateTo($"/server/{server.Uuid}"); } else @@ -221,4 +255,11 @@ } } } + + private async Task SelectImage(Image image) + { + Model.Image = image; + + await InvokeAsync(StateHasChanged); + } } \ No newline at end of file From 0488e83a38074146c1803f812db45c8a72de4f2f Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Mon, 10 Jul 2023 22:01:10 +0200 Subject: [PATCH 11/20] Implemented pterodactyl egg import function --- Moonlight/App/Helpers/EggConverter.cs | 71 +++++++++++++++++++ .../Views/Admin/Servers/Images/Index.razor | 65 +++++++++++++++-- 2 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 Moonlight/App/Helpers/EggConverter.cs diff --git a/Moonlight/App/Helpers/EggConverter.cs b/Moonlight/App/Helpers/EggConverter.cs new file mode 100644 index 00000000..d97647c2 --- /dev/null +++ b/Moonlight/App/Helpers/EggConverter.cs @@ -0,0 +1,71 @@ +using System.Text; +using Moonlight.App.Database.Entities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Moonlight.App.Helpers; + +public static class EggConverter +{ + public static Image Convert(string json) + { + var result = new Image(); + + var data = new ConfigurationBuilder().AddJsonStream( + new MemoryStream(Encoding.ASCII.GetBytes(json)) + ).Build(); + + result.Allocations = 1; + result.Description = data.GetValue("description") ?? ""; + result.Uuid = Guid.NewGuid(); + result.Startup = data.GetValue("startup") ?? ""; + result.Name = data.GetValue("name") ?? "Ptero Egg"; + + foreach (var variable in data.GetSection("variables").GetChildren()) + { + result.Variables.Add(new() + { + Key = variable.GetValue("env_variable") ?? "", + DefaultValue = variable.GetValue("default_value") ?? "" + }); + } + + var configData = data.GetSection("config"); + + result.ConfigFiles = configData.GetValue("files") ?? "{}"; + + var dImagesData = JObject.Parse(json); + var dImages = (JObject)dImagesData["docker_images"]!; + + foreach (var dockerImage in dImages) + { + var di = new DockerImage() + { + Default = dockerImage.Key == dImages.Properties().Last().Name, + Name = dockerImage.Value!.ToString() + }; + + result.DockerImages.Add(di); + } + + var installSection = data.GetSection("scripts").GetSection("installation"); + + result.InstallEntrypoint = installSection.GetValue("entrypoint") ?? "bash"; + result.InstallScript = installSection.GetValue("script") ?? ""; + result.InstallDockerImage = installSection.GetValue("container") ?? ""; + + var rawJson = configData.GetValue("startup"); + + var startupData = new ConfigurationBuilder().AddJsonStream( + new MemoryStream(Encoding.ASCII.GetBytes(rawJson!)) + ).Build(); + + result.StartupDetection = startupData.GetValue("done", "") ?? ""; + result.StopCommand = configData.GetValue("stop") ?? ""; + + result.TagsJson = "[]"; + result.BackgroundImageUrl = ""; + + return result; + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Views/Admin/Servers/Images/Index.razor b/Moonlight/Shared/Views/Admin/Servers/Images/Index.razor index d947bb4b..5fc6356c 100644 --- a/Moonlight/Shared/Views/Admin/Servers/Images/Index.razor +++ b/Moonlight/Shared/Views/Admin/Servers/Images/Index.razor @@ -32,7 +32,7 @@ New image