diff --git a/Moonlight/App/ApiClients/Modrinth/ModrinthApiHelper.cs b/Moonlight/App/ApiClients/Modrinth/ModrinthApiHelper.cs new file mode 100644 index 00000000..b354095b --- /dev/null +++ b/Moonlight/App/ApiClients/Modrinth/ModrinthApiHelper.cs @@ -0,0 +1,59 @@ +using Logging.Net; +using Newtonsoft.Json; +using RestSharp; + +namespace Moonlight.App.ApiClients.Modrinth; + +public class ModrinthApiHelper +{ + private readonly RestClient Client; + + public ModrinthApiHelper() + { + Client = new(); + Client.AddDefaultParameter( + new HeaderParameter("User-Agent", "Moonlight-Panel/Moonlight (admin@endelon-hosting.de)") + ); + } + + public async Task Get(string resource) + { + var request = CreateRequest(resource); + + request.Method = Method.Get; + + var response = await Client.ExecuteAsync(request); + + if (!response.IsSuccessful) + { + if (response.StatusCode != 0) + { + throw new ModrinthException( + $"An error occured: ({response.StatusCode}) {response.Content}", + (int)response.StatusCode + ); + } + else + { + throw new Exception($"An internal error occured: {response.ErrorMessage}"); + } + } + + return JsonConvert.DeserializeObject(response.Content!)!; + } + + private RestRequest CreateRequest(string resource) + { + var url = "https://api.modrinth.com/v2/" + resource; + + var request = new RestRequest(url) + { + Timeout = 60 * 15 + }; + + request.AddHeader("Content-Type", "application/json"); + request.AddHeader("Accept", "application/json"); + + return request; + } +} \ No newline at end of file diff --git a/Moonlight/App/ApiClients/Modrinth/ModrinthException.cs b/Moonlight/App/ApiClients/Modrinth/ModrinthException.cs new file mode 100644 index 00000000..c6959359 --- /dev/null +++ b/Moonlight/App/ApiClients/Modrinth/ModrinthException.cs @@ -0,0 +1,19 @@ +namespace Moonlight.App.ApiClients.Modrinth; + +public class ModrinthException : Exception +{ + public int StatusCode { get; set; } + + public ModrinthException() + { + } + + public ModrinthException(string message, int statusCode) : base(message) + { + StatusCode = statusCode; + } + + public ModrinthException(string message, Exception inner) : base(message, inner) + { + } +} \ No newline at end of file diff --git a/Moonlight/App/ApiClients/Modrinth/Resources/Pagination.cs b/Moonlight/App/ApiClients/Modrinth/Resources/Pagination.cs new file mode 100644 index 00000000..b462e5a3 --- /dev/null +++ b/Moonlight/App/ApiClients/Modrinth/Resources/Pagination.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Moonlight.App.ApiClients.Modrinth.Resources; + +public class Pagination +{ + [JsonProperty("hits")] public Project[] Hits { get; set; } + + [JsonProperty("offset")] public long Offset { get; set; } + + [JsonProperty("limit")] public long Limit { get; set; } + + [JsonProperty("total_hits")] public long TotalHits { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/ApiClients/Modrinth/Resources/Project.cs b/Moonlight/App/ApiClients/Modrinth/Resources/Project.cs new file mode 100644 index 00000000..6ae7a670 --- /dev/null +++ b/Moonlight/App/ApiClients/Modrinth/Resources/Project.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; + +namespace Moonlight.App.ApiClients.Modrinth.Resources; + +public class Project +{ + [JsonProperty("project_id")] public string ProjectId { get; set; } + + [JsonProperty("project_type")] public string ProjectType { get; set; } + + [JsonProperty("slug")] public string Slug { get; set; } + + [JsonProperty("author")] public string Author { get; set; } + + [JsonProperty("title")] public string Title { get; set; } + + [JsonProperty("description")] public string Description { get; set; } + + [JsonProperty("categories")] public string[] Categories { get; set; } + + [JsonProperty("display_categories")] public string[] DisplayCategories { get; set; } + + [JsonProperty("versions")] public string[] Versions { get; set; } + + [JsonProperty("downloads")] public long Downloads { get; set; } + + [JsonProperty("follows")] public long Follows { get; set; } + + [JsonProperty("icon_url")] public string IconUrl { get; set; } + + [JsonProperty("date_created")] public DateTimeOffset DateCreated { get; set; } + + [JsonProperty("date_modified")] public DateTimeOffset DateModified { get; set; } + + [JsonProperty("latest_version")] public string LatestVersion { get; set; } + + [JsonProperty("license")] public string License { get; set; } + + [JsonProperty("client_side")] public string ClientSide { get; set; } + + [JsonProperty("server_side")] public string ServerSide { get; set; } + + [JsonProperty("gallery")] public Uri[] Gallery { get; set; } + + [JsonProperty("featured_gallery")] public Uri FeaturedGallery { get; set; } + + [JsonProperty("color")] public long? Color { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/ApiClients/Modrinth/Resources/Version.cs b/Moonlight/App/ApiClients/Modrinth/Resources/Version.cs new file mode 100644 index 00000000..69c13326 --- /dev/null +++ b/Moonlight/App/ApiClients/Modrinth/Resources/Version.cs @@ -0,0 +1,57 @@ +using Newtonsoft.Json; + +namespace Moonlight.App.ApiClients.Modrinth.Resources; + +public class Version +{ + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("version_number")] + public string VersionNumber { get; set; } + + [JsonProperty("changelog")] + public string Changelog { get; set; } + + [JsonProperty("dependencies")] + public object[] Dependencies { get; set; } + + [JsonProperty("game_versions")] + public object[] GameVersions { get; set; } + + [JsonProperty("version_type")] + public string VersionType { get; set; } + + [JsonProperty("loaders")] + public object[] Loaders { get; set; } + + [JsonProperty("featured")] + public bool Featured { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("requested_status")] + public string RequestedStatus { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("project_id")] + public string ProjectId { get; set; } + + [JsonProperty("author_id")] + public string AuthorId { get; set; } + + [JsonProperty("date_published")] + public DateTime DatePublished { get; set; } + + [JsonProperty("downloads")] + public long Downloads { get; set; } + + [JsonProperty("changelog_url")] + public object ChangelogUrl { get; set; } + + [JsonProperty("files")] + public VersionFile[] Files { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/ApiClients/Modrinth/Resources/VersionFile.cs b/Moonlight/App/ApiClients/Modrinth/Resources/VersionFile.cs new file mode 100644 index 00000000..07ccfa63 --- /dev/null +++ b/Moonlight/App/ApiClients/Modrinth/Resources/VersionFile.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Moonlight.App.ApiClients.Modrinth.Resources; + +public class VersionFile +{ + [JsonProperty("url")] + public Uri Url { get; set; } + + [JsonProperty("filename")] + public string Filename { get; set; } + + [JsonProperty("primary")] + public bool Primary { get; set; } + + [JsonProperty("size")] + public long Size { get; set; } + + [JsonProperty("file_type")] + public string FileType { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Addon/ServerAddonPluginService.cs b/Moonlight/App/Services/Addon/ServerAddonPluginService.cs new file mode 100644 index 00000000..dc00b72a --- /dev/null +++ b/Moonlight/App/Services/Addon/ServerAddonPluginService.cs @@ -0,0 +1,83 @@ +using Moonlight.App.ApiClients.Modrinth; +using Moonlight.App.ApiClients.Modrinth.Resources; +using Moonlight.App.Exceptions; +using FileAccess = Moonlight.App.Helpers.Files.FileAccess; +using Version = Moonlight.App.ApiClients.Modrinth.Resources.Version; + +namespace Moonlight.App.Services.Addon; + +public class ServerAddonPluginService +{ + private readonly ModrinthApiHelper ModrinthApiHelper; + private readonly ServerService ServerService; + + public ServerAddonPluginService(ModrinthApiHelper modrinthApiHelper) + { + ModrinthApiHelper = modrinthApiHelper; + } + + public async Task GetPluginsForVersion(string version, string search = "") + { + string resource; + var filter = + "[[\"categories:\'bukkit\'\",\"categories:\'paper\'\",\"categories:\'spigot\'\"],[\"versions:" + version + "\"],[\"project_type:mod\"]]"; + + if (string.IsNullOrEmpty(search)) + resource = "search?limit=21&index=relevance&facets=" + filter; + else + resource = $"search?query={search}&limit=21&index=relevance&facets=" + filter; + + var result = await ModrinthApiHelper.Get(resource); + + return result.Hits; + } + + public async Task InstallPlugin(FileAccess fileAccess, string version, Project project, Action? onStateUpdated = null) + { + // Resolve plugin download + + onStateUpdated?.Invoke($"Resolving {project.Slug}"); + + var filter = "game_versions=[\"" + version + "\"]&loaders=[\"bukkit\", \"paper\", \"spigot\"]"; + + var versions = await ModrinthApiHelper.Get( + $"project/{project.Slug}/version?" + filter); + + if (!versions.Any()) + throw new DisplayException("No plugin download for your minecraft version found"); + + var installVersion = versions.OrderByDescending(x => x.DatePublished).First(); + var fileToInstall = installVersion.Files.First(); + + // Download plugin in a stream cached mode + + var httpClient = new HttpClient(); + var stream = await httpClient.GetStreamAsync(fileToInstall.Url); + var dataStream = new MemoryStream(1024 * 1024 * 40); + await stream.CopyToAsync(dataStream); + stream.Close(); + dataStream.Position = 0; + + // Install plugin + + await fileAccess.SetDir("/"); + + try + { + await fileAccess.MkDir("plugins"); + } + catch (Exception) + { + // Ignored + } + + await fileAccess.SetDir("plugins"); + + onStateUpdated?.Invoke($"Installing {project.Slug}"); + await fileAccess.Upload(fileToInstall.Filename, dataStream); + + await dataStream.DisposeAsync(); + + //TODO: At some point of time, create a dependency resolver + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/ServerService.cs b/Moonlight/App/Services/ServerService.cs index 89721699..071b8661 100644 --- a/Moonlight/App/Services/ServerService.cs +++ b/Moonlight/App/Services/ServerService.cs @@ -370,6 +370,9 @@ public class ServerService await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null); + server.Installing = true; + ServerRepository.Update(server); + await AuditLogService.Log(AuditLogType.ReinstallServer, x => { x.Add(server.Uuid); }); } diff --git a/Moonlight/Moonlight.csproj b/Moonlight/Moonlight.csproj index 3e10c132..4f49f9e0 100644 --- a/Moonlight/Moonlight.csproj +++ b/Moonlight/Moonlight.csproj @@ -79,4 +79,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index 5fd66bfb..c50a2560 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -5,6 +5,7 @@ using HealthChecks.UI.Client; using Logging.Net; using Moonlight.App.ApiClients.CloudPanel; using Moonlight.App.ApiClients.Daemon; +using Moonlight.App.ApiClients.Modrinth; using Moonlight.App.ApiClients.Paper; using Moonlight.App.ApiClients.Wings; using Moonlight.App.Database; @@ -18,6 +19,7 @@ using Moonlight.App.Repositories.Domains; using Moonlight.App.Repositories.LogEntries; using Moonlight.App.Repositories.Servers; using Moonlight.App.Services; +using Moonlight.App.Services.Addon; using Moonlight.App.Services.Background; using Moonlight.App.Services.DiscordBot; using Moonlight.App.Services.Files; @@ -132,6 +134,7 @@ namespace Moonlight builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -159,6 +162,7 @@ namespace Moonlight builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); // Background services builder.Services.AddSingleton(); diff --git a/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor b/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor index 6a417c56..b76369ad 100644 --- a/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor +++ b/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor @@ -3,10 +3,13 @@ @using Moonlight.App.Services @using Logging.Net @using Moonlight.App.ApiClients.CloudPanel +@using Moonlight.App.ApiClients.Daemon +@using Moonlight.App.ApiClients.Modrinth @using Moonlight.App.ApiClients.Wings @inherits ErrorBoundaryBase @inject AlertService AlertService +@inject ConfigService ConfigService @inject SmartTranslateService SmartTranslateService @if (Crashed) @@ -37,12 +40,14 @@ else protected override async Task OnErrorAsync(Exception exception) { - Logger.Warn(exception); + if (ConfigService.DebugMode) + { + Logger.Warn(exception); + } if (exception is DisplayException displayException) { await AlertService.Error( - SmartTranslateService.Translate("Error"), SmartTranslateService.Translate(displayException.Message) ); } @@ -56,7 +61,7 @@ else else if (exception is WingsException wingsException) { await AlertService.Error( - SmartTranslateService.Translate("Error from daemon"), + SmartTranslateService.Translate("Error from wings"), wingsException.Message ); @@ -64,6 +69,22 @@ else Logger.Warn($"Wings exception status code: {wingsException.StatusCode}"); } + else if (exception is DaemonException daemonException) + { + await AlertService.Error( + SmartTranslateService.Translate("Error from daemon"), + daemonException.Message + ); + + Logger.Warn($"Wings exception status code: {daemonException.StatusCode}"); + } + else if (exception is ModrinthException modrinthException) + { + await AlertService.Error( + SmartTranslateService.Translate("Error from modrinth"), + modrinthException.Message + ); + } else if (exception is CloudPanelException cloudPanelException) { await AlertService.Error( @@ -77,6 +98,7 @@ else } else { + Logger.Warn(exception); Crashed = true; await InvokeAsync(StateHasChanged); } diff --git a/Moonlight/Shared/Components/ServerControl/ServerAddons.razor b/Moonlight/Shared/Components/ServerControl/ServerAddons.razor deleted file mode 100644 index cb51e2a4..00000000 --- a/Moonlight/Shared/Components/ServerControl/ServerAddons.razor +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
-

Addons

-
- This feature is currently not available -
-
-
-
\ No newline at end of file diff --git a/Moonlight/Shared/Views/Server/Index.razor b/Moonlight/Shared/Views/Server/Index.razor index 2a83a246..ddfad9c1 100644 --- a/Moonlight/Shared/Views/Server/Index.razor +++ b/Moonlight/Shared/Views/Server/Index.razor @@ -5,14 +5,12 @@ @using Logging.Net @using Moonlight.App.Database.Entities @using Moonlight.App.Events -@using Moonlight.App.Helpers @using Moonlight.App.Helpers.Wings @using Moonlight.App.Helpers.Wings.Enums @using Moonlight.App.Repositories @using Moonlight.App.Services @using Moonlight.App.Services.Sessions @using Moonlight.Shared.Components.Xterm -@using Moonlight.Shared.Components.ServerControl @using Newtonsoft.Json @inject ImageRepository ImageRepository @@ -106,61 +104,38 @@ - - - - @{ - var index = 0; - - switch (Route) - { - case "files": - index = 1; - break; - case "backups": - index = 2; - break; - case "network": - index = 3; - break; - case "addons": - index = 4; - break; - case "settings": - index = 5; - break; - default: - index = 0; - break; - } - } - - - @switch (Route) - { - case "files": - - break; - case "backups": - - break; - case "network": - - break; - case "addons": - - break; - case "settings": - - break; - default: - - break; - } - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Moonlight/Shared/Views/Server/ServerAddons.razor b/Moonlight/Shared/Views/Server/ServerAddons.razor new file mode 100644 index 00000000..9b717527 --- /dev/null +++ b/Moonlight/Shared/Views/Server/ServerAddons.razor @@ -0,0 +1,158 @@ +@using Moonlight.App.Database.Entities +@using Moonlight.App.Services +@using Moonlight.App.Services.Addon +@using Moonlight.App.ApiClients.Modrinth.Resources +@using Logging.Net +@using Moonlight.App.Services.Interop + +@inject ServerAddonPluginService AddonPluginService +@inject SmartTranslateService SmartTranslateService +@inject ServerService ServerService +@inject ToastService ToastService + +@if (Tags.Contains("addon-plugins")) +{ +
+
+ + Plugins + +
+
+ + + + +
+
+
+
+ + @foreach (var pluginsPart in Plugins.Chunk(3)) + { +
+ @foreach (var plugin in pluginsPart) + { +
+
+
+ @(plugin.Title) +
+
+ @(plugin.Title) +

+ @(plugin.Description) +

+ + +
+
+
+ } +
+ } +
+
+
+} +else +{ +
+
+
+

+ Addons +

+
+ This feature is not available for @(CurrentServer.Image.Name) +
+
+
+
+} + +@code +{ + [CascadingParameter] + public Server CurrentServer { get; set; } + + [CascadingParameter] + public string[] Tags { get; set; } + + private string PluginsSearch = ""; + private Project[] Plugins = Array.Empty(); + private LazyLoader PluginsLazyLoader; + private bool IsPluginInstalling = false; + + private async Task LoadPlugins(LazyLoader lazyLoader) + { + await lazyLoader.SetText(SmartTranslateService.Translate("Searching")); + + var version = CurrentServer.Variables.First(x => x.Key == "MINECRAFT_VERSION").Value; + + if (string.IsNullOrEmpty(version) || version == "latest") + version = "1.20.1"; // This should NOT be called at any time if all the images have the correct tags + + Plugins = await AddonPluginService.GetPluginsForVersion(version, PluginsSearch); + } + + private async Task SearchPlugins() + { + await PluginsLazyLoader.Reload(); + } + + private async Task InstallPlugin(Project project) + { + if (IsPluginInstalling) + { + await ToastService.Error( + SmartTranslateService.Translate("Please wait until the other plugin is installed")); + return; + } + + IsPluginInstalling = true; + + try + { + var fileAccess = await ServerService.CreateFileAccess(CurrentServer, null!); + + var version = CurrentServer.Variables.First(x => x.Key == "MINECRAFT_VERSION").Value; + + if (string.IsNullOrEmpty(version) || version == "latest") + version = "1.20.1"; // This should NOT be called at any time if all the images have the correct tags + + await ToastService.CreateProcessToast("pluginDownload", "Preparing"); + + await AddonPluginService.InstallPlugin(fileAccess, version, project, delegate(string s) + { + Task.Run(async () => + { + await ToastService.UpdateProcessToast("pluginDownload", s); + }); + }); + + await ToastService.Success( + SmartTranslateService.Translate("Successfully installed " + project.Slug) + ); + } + catch (Exception e) + { + Logger.Info(e.Message); + throw; + } + finally + { + IsPluginInstalling = false; + await ToastService.RemoveProcessToast("pluginDownload"); + } + } +} + diff --git a/Moonlight/Shared/Components/ServerControl/ServerBackups.razor b/Moonlight/Shared/Views/Server/ServerBackups.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/ServerBackups.razor rename to Moonlight/Shared/Views/Server/ServerBackups.razor diff --git a/Moonlight/Shared/Components/ServerControl/ServerConsole.razor b/Moonlight/Shared/Views/Server/ServerConsole.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/ServerConsole.razor rename to Moonlight/Shared/Views/Server/ServerConsole.razor diff --git a/Moonlight/Shared/Components/ServerControl/ServerFiles.razor b/Moonlight/Shared/Views/Server/ServerFiles.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/ServerFiles.razor rename to Moonlight/Shared/Views/Server/ServerFiles.razor diff --git a/Moonlight/Shared/Components/ServerControl/ServerNavigation.razor b/Moonlight/Shared/Views/Server/ServerNavigation.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/ServerNavigation.razor rename to Moonlight/Shared/Views/Server/ServerNavigation.razor diff --git a/Moonlight/Shared/Components/ServerControl/ServerNetwork.razor b/Moonlight/Shared/Views/Server/ServerNetwork.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/ServerNetwork.razor rename to Moonlight/Shared/Views/Server/ServerNetwork.razor diff --git a/Moonlight/Shared/Components/ServerControl/ServerSettings.razor b/Moonlight/Shared/Views/Server/ServerSettings.razor similarity index 98% rename from Moonlight/Shared/Components/ServerControl/ServerSettings.razor rename to Moonlight/Shared/Views/Server/ServerSettings.razor index 3afd4ea1..1fba9e2e 100644 --- a/Moonlight/Shared/Components/ServerControl/ServerSettings.razor +++ b/Moonlight/Shared/Views/Server/ServerSettings.razor @@ -1,5 +1,5 @@ @using Moonlight.App.Database.Entities -@using Moonlight.Shared.Components.ServerControl.Settings +@using Moonlight.Shared.Views.Server.Settings @using Microsoft.AspNetCore.Components.Rendering diff --git a/Moonlight/Shared/Components/ServerControl/Settings/DotnetFileSetting.razor b/Moonlight/Shared/Views/Server/Settings/DotnetFileSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/DotnetFileSetting.razor rename to Moonlight/Shared/Views/Server/Settings/DotnetFileSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/DotnetVersionSetting.razor b/Moonlight/Shared/Views/Server/Settings/DotnetVersionSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/DotnetVersionSetting.razor rename to Moonlight/Shared/Views/Server/Settings/DotnetVersionSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/FabricVersionSetting.razor b/Moonlight/Shared/Views/Server/Settings/FabricVersionSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/FabricVersionSetting.razor rename to Moonlight/Shared/Views/Server/Settings/FabricVersionSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/ForgeVersionSetting.razor b/Moonlight/Shared/Views/Server/Settings/ForgeVersionSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/ForgeVersionSetting.razor rename to Moonlight/Shared/Views/Server/Settings/ForgeVersionSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/JavaFileSetting.razor b/Moonlight/Shared/Views/Server/Settings/JavaFileSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/JavaFileSetting.razor rename to Moonlight/Shared/Views/Server/Settings/JavaFileSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/JavaRuntimeVersionSetting.razor b/Moonlight/Shared/Views/Server/Settings/JavaRuntimeVersionSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/JavaRuntimeVersionSetting.razor rename to Moonlight/Shared/Views/Server/Settings/JavaRuntimeVersionSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/JavascriptFileSetting.razor b/Moonlight/Shared/Views/Server/Settings/JavascriptFileSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/JavascriptFileSetting.razor rename to Moonlight/Shared/Views/Server/Settings/JavascriptFileSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/JavascriptVersionSetting.razor b/Moonlight/Shared/Views/Server/Settings/JavascriptVersionSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/JavascriptVersionSetting.razor rename to Moonlight/Shared/Views/Server/Settings/JavascriptVersionSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/Join2StartSetting.razor b/Moonlight/Shared/Views/Server/Settings/Join2StartSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/Join2StartSetting.razor rename to Moonlight/Shared/Views/Server/Settings/Join2StartSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/PaperVersionSetting.razor b/Moonlight/Shared/Views/Server/Settings/PaperVersionSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/PaperVersionSetting.razor rename to Moonlight/Shared/Views/Server/Settings/PaperVersionSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/PythonFileSetting.razor b/Moonlight/Shared/Views/Server/Settings/PythonFileSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/PythonFileSetting.razor rename to Moonlight/Shared/Views/Server/Settings/PythonFileSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/PythonVersionSetting.razor b/Moonlight/Shared/Views/Server/Settings/PythonVersionSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/PythonVersionSetting.razor rename to Moonlight/Shared/Views/Server/Settings/PythonVersionSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/ServerDeleteSetting.razor b/Moonlight/Shared/Views/Server/Settings/ServerDeleteSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/ServerDeleteSetting.razor rename to Moonlight/Shared/Views/Server/Settings/ServerDeleteSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/ServerRenameSetting.razor b/Moonlight/Shared/Views/Server/Settings/ServerRenameSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/ServerRenameSetting.razor rename to Moonlight/Shared/Views/Server/Settings/ServerRenameSetting.razor diff --git a/Moonlight/Shared/Components/ServerControl/Settings/ServerResetSetting.razor b/Moonlight/Shared/Views/Server/Settings/ServerResetSetting.razor similarity index 100% rename from Moonlight/Shared/Components/ServerControl/Settings/ServerResetSetting.razor rename to Moonlight/Shared/Views/Server/Settings/ServerResetSetting.razor