diff --git a/Moonlight/App/Helpers/Files/FileAccess.cs b/Moonlight/App/Helpers/Files/FileAccess.cs new file mode 100644 index 00000000..a900c462 --- /dev/null +++ b/Moonlight/App/Helpers/Files/FileAccess.cs @@ -0,0 +1,24 @@ +namespace Moonlight.App.Helpers.Files; + +public abstract class FileAccess : ICloneable +{ + public string CurrentPath { get; set; } = "/"; + + public abstract Task Ls(); + public abstract Task Cd(string dir); + public abstract Task Up(); + public abstract Task SetDir(string dir); + public abstract Task Read(FileData fileData); + public abstract Task Write(FileData fileData, string content); + public abstract Task Upload(string name, Stream stream, Action? progressUpdated = null); + public abstract Task MkDir(string name); + public abstract Task Pwd(); + public abstract Task DownloadUrl(FileData fileData); + public abstract Task DownloadStream(FileData fileData); + public abstract Task Delete(FileData fileData); + public abstract Task Move(FileData fileData, string newPath); + public abstract Task Compress(params FileData[] files); + public abstract Task Decompress(FileData fileData); + public abstract Task GetLaunchUrl(); + public abstract object Clone(); +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/Files/IFileAccess.cs b/Moonlight/App/Helpers/Files/IFileAccess.cs deleted file mode 100644 index 62b6ba5c..00000000 --- a/Moonlight/App/Helpers/Files/IFileAccess.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Moonlight.App.Helpers.Files; - -public interface IFileAccess -{ - public Task Ls(); - public Task Cd(string dir); - public Task Up(); - public Task SetDir(string dir); - public Task Read(FileData fileData); - public Task Write(FileData fileData, string content); - public Task Upload(string name, Stream stream, Action? progressUpdated = null); - public Task MkDir(string name); - public Task Pwd(); - public Task DownloadUrl(FileData fileData); - public Task DownloadStream(FileData fileData); - public Task Delete(FileData fileData); - public Task Move(FileData fileData, string newPath); - public Task GetLaunchUrl(); -} \ No newline at end of file diff --git a/Moonlight/App/Helpers/Files/WingsFileAccess.cs b/Moonlight/App/Helpers/Files/WingsFileAccess.cs index 2ddc57c8..b1c2296c 100644 --- a/Moonlight/App/Helpers/Files/WingsFileAccess.cs +++ b/Moonlight/App/Helpers/Files/WingsFileAccess.cs @@ -1,21 +1,32 @@ -using Moonlight.App.Database.Entities; +using System.Web; +using Moonlight.App.Database.Entities; +using Moonlight.App.Models.Wings.Requests; using Moonlight.App.Models.Wings.Resources; +using Moonlight.App.Services; +using RestSharp; namespace Moonlight.App.Helpers.Files; -public class WingsFileAccess : IFileAccess +public class WingsFileAccess : FileAccess { private readonly WingsApiHelper WingsApiHelper; private readonly WingsJwtHelper WingsJwtHelper; + private readonly ConfigService ConfigService; private readonly Server Server; + private readonly User User; - private string CurrentPath = "/"; - - public WingsFileAccess(WingsApiHelper wingsApiHelper, WingsJwtHelper wingsJwtHelper,Server server) + public WingsFileAccess( + WingsApiHelper wingsApiHelper, + WingsJwtHelper wingsJwtHelper, + Server server, + ConfigService configService, + User user) { WingsApiHelper = wingsApiHelper; WingsJwtHelper = wingsJwtHelper; Server = server; + ConfigService = configService; + User = user; if (server.Node == null) { @@ -23,9 +34,9 @@ public class WingsFileAccess : IFileAccess } } - public async Task Ls() + public override async Task Ls() { - var res = await WingsApiHelper.Get( + var res = await WingsApiHelper.Get( Server.Node, $"api/servers/{Server.Uuid}/files/list-directory?directory={CurrentPath}" ); @@ -45,7 +56,7 @@ public class WingsFileAccess : IFileAccess return x.ToArray(); } - public Task Cd(string dir) + public override Task Cd(string dir) { var x = Path.Combine(CurrentPath, dir).Replace("\\", "/") + "/"; x = x.Replace("//", "/"); @@ -54,65 +65,168 @@ public class WingsFileAccess : IFileAccess return Task.CompletedTask; } - public Task Up() + public override Task Up() { CurrentPath = Path.GetFullPath(Path.Combine(CurrentPath, "..")).Replace("\\", "/").Replace("C:", ""); return Task.CompletedTask; } - - public Task SetDir(string dir) + + public override Task SetDir(string dir) { CurrentPath = dir; return Task.CompletedTask; } - public async Task Read(FileData fileData) + public override async Task Read(FileData fileData) { - return await WingsApiHelper.GetRaw(Server.Node,$"api/servers/{Server.Uuid}/files/contents?file={CurrentPath}{fileData.Name}"); + return await WingsApiHelper.GetRaw(Server.Node, + $"api/servers/{Server.Uuid}/files/contents?file={CurrentPath}{fileData.Name}"); } - public async Task Write(FileData fileData, string content) + public override async Task Write(FileData fileData, string content) { - await WingsApiHelper.PostRaw(Server.Node,$"api/servers/{Server.Uuid}/files/write?file={CurrentPath}{fileData.Name}", content); + await WingsApiHelper.PostRaw(Server.Node, + $"api/servers/{Server.Uuid}/files/write?file={CurrentPath}{fileData.Name}", content); } - public Task Upload(string name, Stream stream, Action? progressUpdated = null) + public override async Task Upload(string name, Stream dataStream, Action? progressUpdated = null) { - throw new NotImplementedException(); + var token = WingsJwtHelper.Generate( + Server.Node.Token, + claims => { claims.Add("server_uuid", Server.Uuid.ToString()); } + ); + + var client = new RestClient(); + var request = new RestRequest(); + + if (Server.Node.Ssl) + request.Resource = + $"https://{Server.Node.Fqdn}:{Server.Node.HttpPort}/upload/file?token={token}&directory={CurrentPath}"; + else + request.Resource = + $"http://{Server.Node.Fqdn}:{Server.Node.HttpPort}/upload/file?token={token}&directory={CurrentPath}"; + + request.AddParameter("name", "files"); + request.AddParameter("filename", name); + request.AddHeader("Content-Type", "multipart/form-data"); + request.AddHeader("Origin", ConfigService.GetSection("Moonlight").GetValue("AppUrl")); + request.AddFile("files", () => + { + return new StreamProgressHelper(dataStream) + { + Progress = i => { progressUpdated?.Invoke(i); } + }; + }, name); + + await client.ExecutePostAsync(request); + + client.Dispose(); + dataStream.Close(); } - public Task MkDir(string name) + public override async Task MkDir(string name) { - throw new NotImplementedException(); + await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/create-directory", + new CreateDirectory() + { + Name = name, + Path = CurrentPath + } + ); } - public Task Pwd() + public override Task Pwd() { return Task.FromResult(CurrentPath); } - public Task DownloadUrl(FileData fileData) + public override Task DownloadUrl(FileData fileData) + { + var token = WingsJwtHelper.Generate(Server.Node.Token, claims => + { + claims.Add("server_uuid", Server.Uuid.ToString()); + claims.Add("file_path", CurrentPath + "/" + fileData.Name); + }); + + if (Server.Node.Ssl) + { + return Task.FromResult( + $"https://{Server.Node.Fqdn}:{Server.Node.HttpPort}/download/file?token={token}" + ); + } + else + { + return Task.FromResult( + $"http://{Server.Node.Fqdn}:{Server.Node.HttpPort}/download/file?token={token}" + ); + } + } + + public override Task DownloadStream(FileData fileData) { throw new NotImplementedException(); } - public Task DownloadStream(FileData fileData) + public override async Task Delete(FileData fileData) { - throw new NotImplementedException(); + await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/delete", new DeleteFiles() + { + Root = CurrentPath, + Files = new() + { + fileData.Name + } + }); } - public Task Delete(FileData fileData) + public override async Task Move(FileData fileData, string newPath) { - throw new NotImplementedException(); + var req = new RenameFiles() + { + Root = "/", + Files = new[] + { + new RenameFilesData() + { + From = (CurrentPath + fileData.Name), + To = newPath + } + } + }; + + await WingsApiHelper.Put(Server.Node, $"api/servers/{Server.Uuid}/files/rename", req); } - public Task Move(FileData fileData, string newPath) + public override async Task Compress(params FileData[] files) { - throw new NotImplementedException(); + var req = new CompressFiles() + { + Root = CurrentPath, + Files = files.Select(x => x.Name).ToArray() + }; + + await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/compress", req); } - public Task GetLaunchUrl() + public override async Task Decompress(FileData fileData) { - throw new NotImplementedException(); + var req = new DecompressFile() + { + Root = CurrentPath, + File = fileData.Name + }; + + await WingsApiHelper.Post(Server.Node, $"api/servers/{Server.Uuid}/files/decompress", req); + } + + public override Task GetLaunchUrl() + { + return Task.FromResult( + $"sftp://{User.Id}.{StringHelper.IntToStringWithLeadingZeros(Server.Id, 8)}@{Server.Node.Fqdn}:{Server.Node.SftpPort}"); + } + + public override object Clone() + { + return new WingsFileAccess(WingsApiHelper, WingsJwtHelper, Server, ConfigService, User); } } \ No newline at end of file diff --git a/Moonlight/App/Helpers/PaperApiHelper.cs b/Moonlight/App/Helpers/PaperApiHelper.cs index 232b4a8a..c8e1a3f8 100644 --- a/Moonlight/App/Helpers/PaperApiHelper.cs +++ b/Moonlight/App/Helpers/PaperApiHelper.cs @@ -24,11 +24,11 @@ public class PaperApiHelper else requrl = ApiUrl + "/" + url; - RestRequest request = new(requrl); + RestRequest request = new(requrl, Method.Get); request.AddHeader("Content-Type", "application/json"); - var response = await client.GetAsync(request); + var response = await client.ExecuteAsync(request); if (!response.IsSuccessful) { diff --git a/Moonlight/App/Helpers/WingsApiHelper.cs b/Moonlight/App/Helpers/WingsApiHelper.cs index 23900b4a..9611d645 100644 --- a/Moonlight/App/Helpers/WingsApiHelper.cs +++ b/Moonlight/App/Helpers/WingsApiHelper.cs @@ -14,24 +14,13 @@ public class WingsApiHelper Client = new(); } - private string GetApiUrl(Node node) - { - if(node.Ssl) - return $"https://{node.Fqdn}:{node.HttpPort}/"; - else - return $"http://{node.Fqdn}:{node.HttpPort}/"; - //return $"https://{node.Fqdn}:{node.HttpPort}/"; - } - public async Task Get(Node node, string resource) { - RestRequest request = new(GetApiUrl(node) + resource); + var request = CreateRequest(node, resource); - request.AddHeader("Content-Type", "application/json"); - request.AddHeader("Accept", "application/json"); - request.AddHeader("Authorization", "Bearer " + node.Token); + request.Method = Method.Get; - var response = await Client.GetAsync(request); + var response = await Client.ExecuteAsync(request); if (!response.IsSuccessful) { @@ -53,13 +42,11 @@ public class WingsApiHelper public async Task GetRaw(Node node, string resource) { - RestRequest request = new(GetApiUrl(node) + resource); + var request = CreateRequest(node, resource); - request.AddHeader("Content-Type", "application/json"); - request.AddHeader("Accept", "application/json"); - request.AddHeader("Authorization", "Bearer " + node.Token); + request.Method = Method.Get; - var response = await Client.GetAsync(request); + var response = await Client.ExecuteAsync(request); if (!response.IsSuccessful) { @@ -81,18 +68,16 @@ public class WingsApiHelper public async Task Post(Node node, string resource, object? body) { - RestRequest request = new(GetApiUrl(node) + resource); + var request = CreateRequest(node, resource); - request.AddHeader("Content-Type", "application/json"); - request.AddHeader("Accept", "application/json"); - request.AddHeader("Authorization", "Bearer " + node.Token); + request.Method = Method.Post; request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody ); - var response = await Client.PostAsync(request); + var response = await Client.ExecuteAsync(request); if (!response.IsSuccessful) { @@ -114,16 +99,14 @@ public class WingsApiHelper public async Task Post(Node node, string resource, object? body) { - RestRequest request = new(GetApiUrl(node) + resource); + var request = CreateRequest(node, resource); - request.AddHeader("Content-Type", "application/json"); - request.AddHeader("Accept", "application/json"); - request.AddHeader("Authorization", "Bearer " + node.Token); + request.Method = Method.Post; - if(body != null) + if(body != null) request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody); - var response = await Client.PostAsync(request); + var response = await Client.ExecuteAsync(request); if (!response.IsSuccessful) { @@ -143,15 +126,13 @@ public class WingsApiHelper public async Task PostRaw(Node node, string resource, object body) { - RestRequest request = new(GetApiUrl(node) + resource); + var request = CreateRequest(node, resource); - request.AddHeader("Content-Type", "application/json"); - request.AddHeader("Accept", "application/json"); - request.AddHeader("Authorization", "Bearer " + node.Token); + request.Method = Method.Post; request.AddParameter("text/plain", body, ParameterType.RequestBody); - var response = await Client.PostAsync(request); + var response = await Client.ExecuteAsync(request); if (!response.IsSuccessful) { @@ -171,16 +152,14 @@ public class WingsApiHelper public async Task Delete(Node node, string resource, object? body) { - RestRequest request = new(GetApiUrl(node) + resource); + var request = CreateRequest(node, resource); - request.AddHeader("Content-Type", "application/json"); - request.AddHeader("Accept", "application/json"); - request.AddHeader("Authorization", "Bearer " + node.Token); + request.Method = Method.Delete; - if(body != null) + if(body != null) request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody); - var response = await Client.DeleteAsync(request); + var response = await Client.ExecuteAsync(request); if (!response.IsSuccessful) { @@ -200,15 +179,13 @@ public class WingsApiHelper public async Task Put(Node node, string resource, object? body) { - RestRequest request = new(GetApiUrl(node) + resource); + var request = CreateRequest(node, resource); - request.AddHeader("Content-Type", "application/json"); - request.AddHeader("Accept", "application/json"); - request.AddHeader("Authorization", "Bearer " + node.Token); + request.Method = Method.Put; request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody); - var response = await Client.PutAsync(request); + var response = await Client.ExecuteAsync(request); if (!response.IsSuccessful) { @@ -225,4 +202,20 @@ public class WingsApiHelper } } } + + private RestRequest CreateRequest(Node node, string resource) + { + var url = (node.Ssl ? "https" : "http") + $"://{node.Fqdn}:{node.HttpPort}/" + resource; + + var request = new RestRequest(url) + { + Timeout = 60 * 15 + }; + + request.AddHeader("Content-Type", "application/json"); + request.AddHeader("Accept", "application/json"); + request.AddHeader("Authorization", "Bearer " + node.Token); + + return request; + } } \ No newline at end of file diff --git a/Moonlight/App/Models/Files/Accesses/WingsFileAccess.cs b/Moonlight/App/Models/Files/Accesses/WingsFileAccess.cs index 6d181e2a..1d64ab82 100644 --- a/Moonlight/App/Models/Files/Accesses/WingsFileAccess.cs +++ b/Moonlight/App/Models/Files/Accesses/WingsFileAccess.cs @@ -39,7 +39,7 @@ public class WingsFileAccess : IFileAccess public async Task GetDirectoryContent() { - var res = await WingsApiHelper.Get(Node, + var res = await WingsApiHelper.Get(Node, $"api/servers/{Server.Uuid}/files/list-directory?directory={Path}"); var x = new List(); @@ -130,7 +130,7 @@ public class WingsFileAccess : IFileAccess public async Task CreateDirectory(string name) { await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/create-directory", - new CreateDirectoryRequest() + new CreateDirectory() { Name = name, Path = Path @@ -171,7 +171,7 @@ public class WingsFileAccess : IFileAccess public async Task Delete(FileManagerObject managerObject) { - await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/delete", new DeleteFilesRequest() + await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/delete", new DeleteFiles() { Root = Path, Files = new() @@ -183,7 +183,7 @@ public class WingsFileAccess : IFileAccess public async Task Move(FileManagerObject managerObject, string newPath) { - await WingsApiHelper.Put(Node, $"api/servers/{Server.Uuid}/files/rename", new RenameFilesRequest() + await WingsApiHelper.Put(Node, $"api/servers/{Server.Uuid}/files/rename", new RenameFiles() { Root = "/", Files = new[] diff --git a/Moonlight/App/Models/Wings/Requests/CompressFiles.cs b/Moonlight/App/Models/Wings/Requests/CompressFiles.cs new file mode 100644 index 00000000..2c866671 --- /dev/null +++ b/Moonlight/App/Models/Wings/Requests/CompressFiles.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Moonlight.App.Models.Wings.Requests; + +public class CompressFiles +{ + [JsonProperty("root")] + public string Root { get; set; } + + [JsonProperty("files")] + public string[] Files { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Wings/Requests/CreateBackupRequest.cs b/Moonlight/App/Models/Wings/Requests/CreateBackup.cs similarity index 89% rename from Moonlight/App/Models/Wings/Requests/CreateBackupRequest.cs rename to Moonlight/App/Models/Wings/Requests/CreateBackup.cs index a0ce5b4a..779abf1f 100644 --- a/Moonlight/App/Models/Wings/Requests/CreateBackupRequest.cs +++ b/Moonlight/App/Models/Wings/Requests/CreateBackup.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Requests; -public class CreateBackupRequest +public class CreateBackup { [JsonProperty("adapter")] public string Adapter { get; set; } diff --git a/Moonlight/App/Models/Wings/Requests/CreateDirectoryRequest.cs b/Moonlight/App/Models/Wings/Requests/CreateDirectory.cs similarity index 85% rename from Moonlight/App/Models/Wings/Requests/CreateDirectoryRequest.cs rename to Moonlight/App/Models/Wings/Requests/CreateDirectory.cs index 62c95e30..1dc6617c 100644 --- a/Moonlight/App/Models/Wings/Requests/CreateDirectoryRequest.cs +++ b/Moonlight/App/Models/Wings/Requests/CreateDirectory.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Requests; -public class CreateDirectoryRequest +public class CreateDirectory { [JsonProperty("name")] public string Name { get; set; } diff --git a/Moonlight/App/Models/Wings/Requests/CreateServerRequest.cs b/Moonlight/App/Models/Wings/Requests/CreateServer.cs similarity index 87% rename from Moonlight/App/Models/Wings/Requests/CreateServerRequest.cs rename to Moonlight/App/Models/Wings/Requests/CreateServer.cs index 3288d888..7e939a4a 100644 --- a/Moonlight/App/Models/Wings/Requests/CreateServerRequest.cs +++ b/Moonlight/App/Models/Wings/Requests/CreateServer.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Requests; -public class CreateServerRequest +public class CreateServer { [JsonProperty("uuid")] public Guid Uuid { get; set; } diff --git a/Moonlight/App/Models/Wings/Requests/DecompressFile.cs b/Moonlight/App/Models/Wings/Requests/DecompressFile.cs new file mode 100644 index 00000000..5fb7073d --- /dev/null +++ b/Moonlight/App/Models/Wings/Requests/DecompressFile.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Moonlight.App.Models.Wings.Requests; + +public class DecompressFile +{ + [JsonProperty("root")] + public string Root { get; set; } + + [JsonProperty("file")] + public string File { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Wings/Requests/DeleteFilesRequest.cs b/Moonlight/App/Models/Wings/Requests/DeleteFiles.cs similarity index 87% rename from Moonlight/App/Models/Wings/Requests/DeleteFilesRequest.cs rename to Moonlight/App/Models/Wings/Requests/DeleteFiles.cs index c64487f6..9988ff0f 100644 --- a/Moonlight/App/Models/Wings/Requests/DeleteFilesRequest.cs +++ b/Moonlight/App/Models/Wings/Requests/DeleteFiles.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Requests; -public class DeleteFilesRequest +public class DeleteFiles { [JsonProperty("root")] public string Root { get; set; } diff --git a/Moonlight/App/Models/Wings/Requests/RenameFilesRequest.cs b/Moonlight/App/Models/Wings/Requests/RenameFiles.cs similarity index 92% rename from Moonlight/App/Models/Wings/Requests/RenameFilesRequest.cs rename to Moonlight/App/Models/Wings/Requests/RenameFiles.cs index 8355cff8..f565a32d 100644 --- a/Moonlight/App/Models/Wings/Requests/RenameFilesRequest.cs +++ b/Moonlight/App/Models/Wings/Requests/RenameFiles.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Requests; -public class RenameFilesRequest +public class RenameFiles { [JsonProperty("root")] public string Root { get; set; } diff --git a/Moonlight/App/Models/Wings/Requests/RestoreBackupRequest.cs b/Moonlight/App/Models/Wings/Requests/RestoreBackup.cs similarity index 90% rename from Moonlight/App/Models/Wings/Requests/RestoreBackupRequest.cs rename to Moonlight/App/Models/Wings/Requests/RestoreBackup.cs index 0dfa9cbd..fcd7b8a1 100644 --- a/Moonlight/App/Models/Wings/Requests/RestoreBackupRequest.cs +++ b/Moonlight/App/Models/Wings/Requests/RestoreBackup.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Requests; -public class RestoreBackupRequest +public class RestoreBackup { [JsonProperty("adapter")] public string Adapter { get; set; } diff --git a/Moonlight/App/Models/Wings/Requests/ServerPowerRequest.cs b/Moonlight/App/Models/Wings/Requests/ServerPower.cs similarity index 82% rename from Moonlight/App/Models/Wings/Requests/ServerPowerRequest.cs rename to Moonlight/App/Models/Wings/Requests/ServerPower.cs index 597d9e59..e877fb71 100644 --- a/Moonlight/App/Models/Wings/Requests/ServerPowerRequest.cs +++ b/Moonlight/App/Models/Wings/Requests/ServerPower.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Requests; -public class ServerPowerRequest +public class ServerPower { [JsonProperty("action")] public string Action { get; set; } diff --git a/Moonlight/App/Models/Wings/Resources/ListDirectoryRequest.cs b/Moonlight/App/Models/Wings/Resources/ListDirectory.cs similarity index 94% rename from Moonlight/App/Models/Wings/Resources/ListDirectoryRequest.cs rename to Moonlight/App/Models/Wings/Resources/ListDirectory.cs index 0a67bfa7..d99e15d7 100644 --- a/Moonlight/App/Models/Wings/Resources/ListDirectoryRequest.cs +++ b/Moonlight/App/Models/Wings/Resources/ListDirectory.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Resources; -public class ListDirectoryRequest +public class ListDirectory { [JsonProperty("name")] public string Name { get; set; } diff --git a/Moonlight/App/Models/Wings/Resources/ServerDetailsResponse.cs b/Moonlight/App/Models/Wings/Resources/ServerDetails.cs similarity index 78% rename from Moonlight/App/Models/Wings/Resources/ServerDetailsResponse.cs rename to Moonlight/App/Models/Wings/Resources/ServerDetails.cs index bc89a292..b14ac561 100644 --- a/Moonlight/App/Models/Wings/Resources/ServerDetailsResponse.cs +++ b/Moonlight/App/Models/Wings/Resources/ServerDetails.cs @@ -2,7 +2,7 @@ namespace Moonlight.App.Models.Wings.Resources; -public class ServerDetailsResponse +public class ServerDetails { [JsonProperty("state")] public string State { get; set; } @@ -11,9 +11,9 @@ public class ServerDetailsResponse public bool IsSuspended { get; set; } [JsonProperty("utilization")] - public ServerDetailsResponseUtilization Utilization { get; set; } + public ServerDetailsUtilization Utilization { get; set; } - public class ServerDetailsResponseUtilization + public class ServerDetailsUtilization { [JsonProperty("memory_bytes")] public long MemoryBytes { get; set; } @@ -25,7 +25,7 @@ public class ServerDetailsResponse public double CpuAbsolute { get; set; } [JsonProperty("network")] - public ServerDetailsResponseNetwork Network { get; set; } + public ServerDetailsNetwork Network { get; set; } [JsonProperty("uptime")] public long Uptime { get; set; } @@ -37,7 +37,7 @@ public class ServerDetailsResponse public long DiskBytes { get; set; } } - public class ServerDetailsResponseNetwork + public class ServerDetailsNetwork { [JsonProperty("rx_bytes")] public long RxBytes { get; set; } diff --git a/Moonlight/App/Services/Interop/ModalService.cs b/Moonlight/App/Services/Interop/ModalService.cs new file mode 100644 index 00000000..881fb2ec --- /dev/null +++ b/Moonlight/App/Services/Interop/ModalService.cs @@ -0,0 +1,23 @@ +using Microsoft.JSInterop; + +namespace Moonlight.App.Services.Interop; + +public class ModalService +{ + private readonly IJSRuntime JsRuntime; + + public ModalService(IJSRuntime jsRuntime) + { + JsRuntime = jsRuntime; + } + + public async Task Show(string name) + { + await JsRuntime.InvokeVoidAsync("moonlight.modals.show", name); + } + + public async Task Hide(string name) + { + await JsRuntime.InvokeVoidAsync("moonlight.modals.hide", name); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Interop/ToastService.cs b/Moonlight/App/Services/Interop/ToastService.cs index 3eff8a13..5e1839d3 100644 --- a/Moonlight/App/Services/Interop/ToastService.cs +++ b/Moonlight/App/Services/Interop/ToastService.cs @@ -30,4 +30,19 @@ public class ToastService { await JsRuntime.InvokeVoidAsync("showSuccessToast", message); } + + public async Task CreateProcessToast(string id, string text) + { + await JsRuntime.InvokeVoidAsync("createToast", id, text); + } + + public async Task UpdateProcessToast(string id, string text) + { + await JsRuntime.InvokeVoidAsync("modifyToast", id, text); + } + + public async Task RemoveProcessToast(string id) + { + await JsRuntime.InvokeVoidAsync("removeToast", id); + } } \ No newline at end of file diff --git a/Moonlight/App/Services/ServerService.cs b/Moonlight/App/Services/ServerService.cs index 0542a093..2f69fd7b 100644 --- a/Moonlight/App/Services/ServerService.cs +++ b/Moonlight/App/Services/ServerService.cs @@ -75,11 +75,11 @@ public class ServerService return s; } - public async Task GetDetails(Server s) + public async Task GetDetails(Server s) { Server server = EnsureNodeData(s); - return await WingsApiHelper.Get( + return await WingsApiHelper.Get( server.Node, $"api/servers/{server.Uuid}" ); @@ -91,7 +91,7 @@ public class ServerService var rawSignal = signal.ToString().ToLower(); - await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/power", new ServerPowerRequest() + await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/power", new ServerPower() { Action = rawSignal }); @@ -118,7 +118,7 @@ public class ServerService serverData.Backups.Add(backup); ServerRepository.Update(serverData); - await WingsApiHelper.Post(serverData.Node, $"api/servers/{serverData.Uuid}/backup", new CreateBackupRequest() + await WingsApiHelper.Post(serverData.Node, $"api/servers/{serverData.Uuid}/backup", new CreateBackup() { Adapter = "wings", Uuid = backup.Uuid, @@ -158,7 +158,7 @@ public class ServerService Server server = EnsureNodeData(s); await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/backup/{serverBackup.Uuid}/restore", - new RestoreBackupRequest() + new RestoreBackup() { Adapter = "wings" }); @@ -299,7 +299,7 @@ public class ServerService try { - await WingsApiHelper.Post(node, $"api/servers", new CreateServerRequest() + await WingsApiHelper.Post(node, $"api/servers", new CreateServer() { Uuid = newServerData.Uuid, StartOnCompletion = false diff --git a/Moonlight/Pages/_Layout.cshtml b/Moonlight/Pages/_Layout.cshtml index 523da771..b82364f4 100644 --- a/Moonlight/Pages/_Layout.cshtml +++ b/Moonlight/Pages/_Layout.cshtml @@ -113,5 +113,6 @@ + \ No newline at end of file diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index 45436097..5bf8797e 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -93,6 +93,7 @@ namespace Moonlight builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor b/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor index 64635cd0..f1f9eea1 100644 --- a/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor +++ b/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor @@ -1,6 +1,15 @@ @using Moonlight.App.Helpers.Files @using Moonlight.App.Helpers @using Logging.Net +@using Moonlight.App.Services +@using Moonlight.App.Services.Interop +@using BlazorDownloadFile + +@inject ToastService ToastService +@inject NavigationManager NavigationManager +@inject AlertService AlertService +@inject SmartTranslateService SmartTranslateService +@inject IBlazorDownloadFileService FileService @if (Editing) { @@ -14,35 +23,63 @@ } else { -
-
-
-
- @{ - var vx = "/"; +
+
+
+
+ +
+
+
+
+ @if (View != null && View.SelectedFiles.Any()) + { +
+ @(View.SelectedFiles.Length) selected +
+ + + + + + + + + } - / - - - - - - @{ - var cp = "/"; - var lp = "/"; - var pathParts = CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries); - foreach (var path in pathParts) - { - lp = cp; - @(path) - + else + { + - cp += path + "/"; - } + + + }
@@ -53,35 +90,141 @@ else + OnSelectionChanged="OnSelectionChanged" + OnElementClicked="OnElementClicked" + DisableScrolling="true">
+ + + } @code { [Parameter] - public IFileAccess Access { get; set; } + public FileAccess Access { get; set; } + // File Editor private bool Editing = false; private string EditorInitialData = ""; private string EditorLanguage = ""; private FileData EditingFile; private FileEditor Editor; - private FileView View; - private string CurrentPath = "/"; + // File View + private FileView? View; - private ContextAction[] Actions = + // File Move + private FileAccess MoveAccess; + private FileSelectModal FileSelectModal; + private FileData? SingleMoveFile = null; + + // Config + private ContextAction[] Actions = Array.Empty(); + + protected override void OnInitialized() { - new() + MoveAccess = (FileAccess)Access.Clone(); + + List actions = new(); + + actions.Add(new() { Id = "rename", Name = "Rename", - Action = (x) => { } - } - }; + Action = async (x) => + { + var name = await AlertService.Text( + SmartTranslateService.Translate("Rename"), + SmartTranslateService.Translate("Enter a new name"), + x.Name + ); + + if (name != x.Name) + { + await Access.Move(x, Access.CurrentPath + name); + } + + await View!.Refresh(); + } + }); + + actions.Add(new () + { + Id = "download", + Name = "Download", + Action = async (x) => + { + if (x.IsFile) + { + try + { + var stream = await Access.DownloadStream(x); + await ToastService.Info(SmartTranslateService.Translate("Starting download")); + await FileService.AddBuffer(stream); + await FileService.DownloadBinaryBuffers(x.Name); + } + catch (NotImplementedException) + { + var url = await Access.DownloadUrl(x); + NavigationManager.NavigateTo(url, true); + await ToastService.Info(SmartTranslateService.Translate("Starting download")); + } + } + } + }); + + actions.Add(new() + { + Id = "compress", + Name = "Compress", + Action = async (x) => + { + await Access.Compress(x); + await View!.Refresh(); + } + }); + + actions.Add(new () + { + Id = "decompress", + Name = "Decompress", + Action = async (x) => + { + await Access.Decompress(x); + await View!.Refresh(); + } + }); + + actions.Add(new() + { + Id = "move", + Name = "Move", + Action = async (x) => + { + SingleMoveFile = x; + await StartMoveFiles(); + } + }); + + actions.Add(new() + { + Id = "delete", + Name = "Delete", + Action = async (x) => + { + await Access.Delete(x); + await View!.Refresh(); + } + }); + + Actions = actions.ToArray(); + } private async Task OnElementClicked(FileData fileData) { @@ -92,22 +235,13 @@ else EditingFile = fileData; Editing = true; - await InvokeAsync(StateHasChanged); + await InvokeAsync(StateHasChanged); return true; } - else - { - return false; - } - } - - public async Task SetPath(string path) - { - await Access.SetDir(path); - CurrentPath = await Access.Pwd(); - + await InvokeAsync(StateHasChanged); + return false; } private async void Cancel(bool save = false) @@ -122,9 +256,74 @@ else await InvokeAsync(StateHasChanged); } - private async void OnPathChanged(string path) + private async Task Launch() { - CurrentPath = path; + var url = await Access.GetLaunchUrl(); + NavigationManager.NavigateTo(url, true); + } + + private async Task CreateFolder() + { + var name = await AlertService.Text( + SmartTranslateService.Translate("Create a new folder"), + SmartTranslateService.Translate("Enter a name"), + "" + ); + + if (string.IsNullOrEmpty(name)) + return; + + await Access.MkDir(name); + await View!.Refresh(); + } + + private async Task OnSelectionChanged() + { + await InvokeAsync(StateHasChanged); + } + + private async Task StartMoveFiles() + { + await FileSelectModal.Show(); + } + + private async Task DeleteMultiple() + { + foreach (var data in View!.SelectedFiles) + { + await Access.Delete(data); + } + + await View!.Refresh(); + } + + private async Task CompressMultiple() + { + await Access.Compress(View!.SelectedFiles); + + await View!.Refresh(); + } + + private async Task OnFileMoveSubmit(string path) + { + foreach (var sFile in View!.SelectedFiles) + { + await Access.Move(sFile, path + sFile.Name); + } + + if (SingleMoveFile != null) + { + await Access.Move(SingleMoveFile, path + SingleMoveFile.Name); + SingleMoveFile = null; + } + + await View.Refresh(); + } + + // This method can be called by every component to refresh the view + private async Task OnComponentStateChanged() + { + await View!.Refresh(); await InvokeAsync(StateHasChanged); } } \ No newline at end of file diff --git a/Moonlight/Shared/Components/FileManagerPartials/FilePath.razor b/Moonlight/Shared/Components/FileManagerPartials/FilePath.razor new file mode 100644 index 00000000..93982da1 --- /dev/null +++ b/Moonlight/Shared/Components/FileManagerPartials/FilePath.razor @@ -0,0 +1,47 @@ +@using Moonlight.App.Helpers.Files + +
+
+ @{ + var vx = "/"; + } + / + + + + + + @{ + var cp = "/"; + var lp = "/"; + var pathParts = Access.CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries); + foreach (var path in pathParts) + { + lp = cp; + @(path) + + + + + + + cp += path + "/"; + } + } +
+
+ +@code +{ + [Parameter] + public FileAccess Access { get; set; } + + [Parameter] + public Func? OnPathChanged { get; set; } + + public async Task SetPath(string path) + { + await Access.SetDir(path); + OnPathChanged?.Invoke(); + } +} diff --git a/Moonlight/Shared/Components/FileManagerPartials/FileSelectModal.razor b/Moonlight/Shared/Components/FileManagerPartials/FileSelectModal.razor new file mode 100644 index 00000000..9a9814f0 --- /dev/null +++ b/Moonlight/Shared/Components/FileManagerPartials/FileSelectModal.razor @@ -0,0 +1,132 @@ +@using Moonlight.App.Helpers.Files +@using Moonlight.App.Services +@using Moonlight.App.Services.Interop + +@inject ModalService ModalService +@inject SmartTranslateService SmartTranslateService + + + +@code +{ + [Parameter] + public FileAccess Access { get; set; } + + [Parameter] + public bool OnlyFolder { get; set; } = false; + + [Parameter] + public Func? Filter { get; set; } + + [Parameter] + public string Title { get; set; } = "Select file or folder"; + + [Parameter] + public Func? OnSubmit { get; set; } + + [Parameter] + public Func? OnCancel { get; set; } + + private int Id = 0; + private string Result = "/"; + + private FileView FileView; + + protected override void OnInitialized() + { + Id = this.GetHashCode(); + } + + public async Task Show() + { + // Reset + Result = "/"; + await Access.SetDir("/"); + await FileView.Refresh(); + + await ModalService.Show("fileView" + Id); + } + + public async Task Hide() + { + await Cancel(); + } + + private async Task Cancel() + { + await ModalService.Hide("fileView" + Id); + await OnCancel?.Invoke()!; + } + + private async Task Submit() + { + await ModalService.Hide("fileView" + Id); + await OnSubmit?.Invoke(Result)!; + } + + private bool DoFilter(FileData file) + { + if (OnlyFolder) + { + if (file.IsFile) + return false; + else + { + if (Filter != null) + return Filter.Invoke(file); + else + return true; + } + } + else + { + if (Filter != null) + return Filter.Invoke(file); + else + return true; + } + } + + private async Task OnElementClicked(FileData file) + { + Result = Access.CurrentPath + file.Name + (file.IsFile ? "" : "/"); + + if (!OnlyFolder && file.IsFile) + { + await Submit(); + } + + return false; + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/FileManagerPartials/FileUpload.razor b/Moonlight/Shared/Components/FileManagerPartials/FileUpload.razor new file mode 100644 index 00000000..5efc29f3 --- /dev/null +++ b/Moonlight/Shared/Components/FileManagerPartials/FileUpload.razor @@ -0,0 +1,90 @@ +@using Moonlight.App.Helpers.Files +@using Moonlight.App.Services +@using Moonlight.App.Services.Interop +@using Logging.Net + +@inject ToastService ToastService +@inject SmartTranslateService SmartTranslateService + +
-
+
@@ -38,7 +41,7 @@ @@ -115,29 +124,46 @@ - - @foreach (var action in ContextActions) - { - - @action.Name - - } - +@if (ContextActions.Any()) +{ + + @foreach (var action in ContextActions) + { + + @action.Name + + } + +} @code { [Parameter] - public IFileAccess Access { get; set; } + public FileAccess Access { get; set; } [Parameter] public Func>? OnElementClicked { get; set; } [Parameter] - public Action? OnPathChanged { get; set; } + public Func? OnSelectionChanged { get; set; } [Parameter] public ContextAction[] ContextActions { get; set; } = Array.Empty(); + [Parameter] + public bool HideSelect { get; set; } = false; + + [Parameter] + public bool DisableScrolling { get; set; } = false; + + [Parameter] + public Func? Filter { get; set; } + + public FileData[] SelectedFiles => ToggleStatusCache + .Where(x => x.Value) + .Select(x => x.Key) + .ToArray(); + private FileData[] Data = Array.Empty(); private Dictionary ToggleStatusCache = new(); @@ -145,8 +171,23 @@ public async Task Refresh() { - Data = await Access.Ls(); + var list = new List(); + + foreach (var fileData in await Access.Ls()) + { + if (Filter != null) + { + if(Filter.Invoke(fileData)) + list.Add(fileData); + } + else + list.Add(fileData); + } + + Data = list.ToArray(); + ToggleStatusCache.Clear(); + AllToggled = false; foreach (var fileData in Data) { @@ -154,6 +195,7 @@ } await InvokeAsync(StateHasChanged); + OnSelectionChanged?.Invoke(); } private async Task Load(LazyLoader arg) @@ -169,6 +211,7 @@ ToggleStatusCache.Add(fileData, status); await InvokeAsync(StateHasChanged); + OnSelectionChanged?.Invoke(); } private async Task SetToggleState(bool status) @@ -181,6 +224,7 @@ } await InvokeAsync(StateHasChanged); + OnSelectionChanged?.Invoke(); } private async Task OnClicked(FileData fileData) @@ -197,16 +241,25 @@ { await Access.Cd(fileData.Name); await Refresh(); - OnPathChanged?.Invoke(await Access.Pwd()); } } private async Task GoUp() { + if (OnElementClicked != null) + { + var canceled = await OnElementClicked.Invoke(new() + { + Name = "..", + IsFile = false + }); + + if (canceled) + return; + } + await Access.Up(); await Refresh(); - - OnPathChanged?.Invoke(await Access.Pwd()); } private Task OnContextMenuClick(ItemClickEventArgs eventArgs) diff --git a/Moonlight/Shared/Components/ServerControl/ServerFiles.razor b/Moonlight/Shared/Components/ServerControl/ServerFiles.razor index ed0e1d11..63a30dab 100644 --- a/Moonlight/Shared/Components/ServerControl/ServerFiles.razor +++ b/Moonlight/Shared/Components/ServerControl/ServerFiles.razor @@ -1,27 +1,27 @@ @using Moonlight.Shared.Components.FileManagerPartials -@using Moonlight.App.Services -@using Moonlight.App.Helpers -@using Moonlight.App.Models.Files -@using Moonlight.App.Services.Sessions @using Moonlight.App.Database.Entities +@using Moonlight.App.Helpers +@using Moonlight.App.Helpers.Files +@using Moonlight.App.Services -@inject ServerService ServerService -@inject IdentityService IdentityService +@inject WingsApiHelper WingsApiHelper +@inject WingsJwtHelper WingsJwtHelper +@inject ConfigService ConfigService - - - + @code { [CascadingParameter] public Server CurrentServer { get; set; } + + [CascadingParameter] + public User User { get; set; } - private IFileAccess FileAccess; + private FileAccess FileAccess; - private async Task Load(LazyLoader arg) + protected override void OnInitialized() { - var user = await IdentityService.Get(); // User for launch url - FileAccess = await ServerService.CreateFileAccess(CurrentServer, user); + FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, CurrentServer, ConfigService, User); } } diff --git a/Moonlight/Shared/Components/ServerControl/ServerSettings.razor b/Moonlight/Shared/Components/ServerControl/ServerSettings.razor index 77457bc2..f207dfd3 100644 --- a/Moonlight/Shared/Components/ServerControl/ServerSettings.razor +++ b/Moonlight/Shared/Components/ServerControl/ServerSettings.razor @@ -1,51 +1,64 @@ @using PteroConsole.NET @using Moonlight.App.Database.Entities @using Moonlight.Shared.Components.ServerControl.Settings +@using Microsoft.AspNetCore.Components.Rendering -
- @if (Tags.Contains("paperversion")) - { - - } - - @if (Tags.Contains("pythonversion")) - { - - } - -@{ - /* - * @if (Tags.Contains("pythonfile")) - { - - } - - @if (Tags.Contains("javascriptfile")) - { - - } - */ -} - - @if (Tags.Contains("javascriptversion")) - { - - } - - @if (Tags.Contains("join2start")) - { - - } -
+ +
+ @foreach (var setting in Settings) + { +
+

+ +

+
+
+ @(GetComponent(setting.Value)) +
+
+
+ } +
+
@code { - [CascadingParameter] - public PteroConsole Console { get; set; } - [CascadingParameter] public Server CurrentServer { get; set; } [CascadingParameter] public string[] Tags { get; set; } + + private Dictionary Settings = new(); + + private Task Load(LazyLoader lazyLoader) + { + if(Tags.Contains("paperversion")) + Settings.Add("Paper version", typeof(PaperVersionSetting)); + + if(Tags.Contains("join2start")) + Settings.Add("Join2Start", typeof(Join2StartSetting)); + + if(Tags.Contains("javascriptversion")) + Settings.Add("Javascript version", typeof(JavascriptVersionSetting)); + + if(Tags.Contains("javascriptfile")) + Settings.Add("Javascript file", typeof(JavascriptFileSetting)); + + if(Tags.Contains("pythonversion")) + Settings.Add("Python version", typeof(PythonVersionSetting)); + + if(Tags.Contains("pythonfile")) + Settings.Add("Python file", typeof(PythonFileSetting)); + + return Task.CompletedTask; + } + + private RenderFragment GetComponent(Type type) => builder => + { + builder.OpenComponent(0, type); + builder.CloseComponent(); + }; } \ No newline at end of file diff --git a/Moonlight/Shared/Components/ServerControl/Settings/JavascriptFileSetting.razor b/Moonlight/Shared/Components/ServerControl/Settings/JavascriptFileSetting.razor new file mode 100644 index 00000000..1d97402b --- /dev/null +++ b/Moonlight/Shared/Components/ServerControl/Settings/JavascriptFileSetting.razor @@ -0,0 +1,82 @@ +@using Task = System.Threading.Tasks.Task +@using Moonlight.App.Repositories.Servers +@using Moonlight.Shared.Components.FileManagerPartials +@using Moonlight.App.Database.Entities +@using Moonlight.App.Helpers +@using Moonlight.App.Helpers.Files +@using Moonlight.App.Services + +@inject ServerRepository ServerRepository +@inject WingsApiHelper WingsApiHelper +@inject SmartTranslateService SmartTranslateService + +
+
+ + + + + +
+
+ + + + +@code +{ + [CascadingParameter] + public Server CurrentServer { get; set; } + + private string PathAndFile; + private FileAccess Access; + + private FileSelectModal FileSelectModal; + private LazyLoader LazyLoader; + + protected override void OnInitialized() + { + Access = new WingsFileAccess(WingsApiHelper, + null!, + CurrentServer, + null!, + null! + ); + } + + private async Task Load(LazyLoader lazyLoader) + { + var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_JS_FILE"); + + PathAndFile = v != null ? v.Value : ""; + + await InvokeAsync(StateHasChanged); + } + + private async Task Show() + { + await FileSelectModal.Show(); + } + + private async Task OnSubmit(string path) + { + var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_JS_FILE"); + + if (v != null) + { + v.Value = path.TrimStart("/"[0]); + + ServerRepository.Update(CurrentServer); + } + + await LazyLoader.Reload(); + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/ServerControl/Settings/JavascriptVersionSetting.razor b/Moonlight/Shared/Components/ServerControl/Settings/JavascriptVersionSetting.razor index d456de23..74936d32 100644 --- a/Moonlight/Shared/Components/ServerControl/Settings/JavascriptVersionSetting.razor +++ b/Moonlight/Shared/Components/ServerControl/Settings/JavascriptVersionSetting.razor @@ -8,29 +8,34 @@ @inject ServerRepository ServerRepository @inject ImageRepository ImageRepository @inject SmartTranslateService TranslationService - +
- - + @foreach (var image in DockerImages) { - if (image == Image) + if (image.Id == SelectedImage.Id) { - + } else { - + } } - + +
@@ -40,44 +45,36 @@ [CascadingParameter] public Server CurrentServer { get; set; } - private string[] Images; - private string Image; - private LazyLoader LazyLoader; + private List DockerImages; + private DockerImage SelectedImage; - private async Task Load(LazyLoader lazyLoader) + private int ImageIndex { - //TODO: Check if this is a redundant call - var serverImage = ImageRepository + get => SelectedImage.Id; + set { SelectedImage = DockerImages.First(x => x.Id == value); } + } + + private Task Load(LazyLoader lazyLoader) + { + var image = ImageRepository .Get() .Include(x => x.DockerImages) .First(x => x.Id == CurrentServer.Image.Id); - - Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name); - - var res = new List(); - foreach (var image in serverImage.DockerImages) - { - res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name)); - } - Images = res.ToArray(); - await InvokeAsync(StateHasChanged); + DockerImages = image.DockerImages; + + SelectedImage = DockerImages[CurrentServer.DockerImageIndex]; + + return Task.CompletedTask; } private async Task Save() { - var serverImage = ImageRepository - .Get() - .Include(x => x.DockerImages) - .First(x => x.Id == CurrentServer.Image.Id); - - var allImages = serverImage.DockerImages; - var imageToUse = allImages.First(x => x.Name.EndsWith(Image)); - CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse); - - ServerRepository.Update(CurrentServer); + CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage); + ServerRepository.Update(CurrentServer); + await LazyLoader.Reload(); } } \ No newline at end of file diff --git a/Moonlight/Shared/Components/ServerControl/Settings/PaperVersionSetting.razor b/Moonlight/Shared/Components/ServerControl/Settings/PaperVersionSetting.razor index a5a2a862..1500f8f9 100644 --- a/Moonlight/Shared/Components/ServerControl/Settings/PaperVersionSetting.razor +++ b/Moonlight/Shared/Components/ServerControl/Settings/PaperVersionSetting.razor @@ -1,7 +1,5 @@ @using Moonlight.App.Services -@using Moonlight.Shared.Components.Partials @using Task = System.Threading.Tasks.Task -@using Logging.Net @using Microsoft.EntityFrameworkCore @using Moonlight.App.Database.Entities @using Moonlight.App.Repositories diff --git a/Moonlight/Shared/Components/ServerControl/Settings/PythonFileSetting.razor b/Moonlight/Shared/Components/ServerControl/Settings/PythonFileSetting.razor new file mode 100644 index 00000000..80e8ea9b --- /dev/null +++ b/Moonlight/Shared/Components/ServerControl/Settings/PythonFileSetting.razor @@ -0,0 +1,82 @@ +@using Task = System.Threading.Tasks.Task +@using Moonlight.App.Repositories.Servers +@using Moonlight.Shared.Components.FileManagerPartials +@using Moonlight.App.Database.Entities +@using Moonlight.App.Helpers +@using Moonlight.App.Helpers.Files +@using Moonlight.App.Services + +@inject ServerRepository ServerRepository +@inject WingsApiHelper WingsApiHelper +@inject SmartTranslateService SmartTranslateService + +
+
+ + + + + +
+
+ + + + +@code +{ + [CascadingParameter] + public Server CurrentServer { get; set; } + + private string PathAndFile; + private FileAccess Access; + + private FileSelectModal FileSelectModal; + private LazyLoader LazyLoader; + + protected override void OnInitialized() + { + Access = new WingsFileAccess(WingsApiHelper, + null!, + CurrentServer, + null!, + null! + ); + } + + private async Task Load(LazyLoader lazyLoader) + { + var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_PY_FILE"); + + PathAndFile = v != null ? v.Value : ""; + + await InvokeAsync(StateHasChanged); + } + + private async Task Show() + { + await FileSelectModal.Show(); + } + + private async Task OnSubmit(string path) + { + var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == "BOT_PY_FILE"); + + if (v != null) + { + v.Value = path.TrimStart("/"[0]); + + ServerRepository.Update(CurrentServer); + } + + await LazyLoader.Reload(); + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/ServerControl/Settings/PythonVersionSetting.razor b/Moonlight/Shared/Components/ServerControl/Settings/PythonVersionSetting.razor index 2cf83743..59697112 100644 --- a/Moonlight/Shared/Components/ServerControl/Settings/PythonVersionSetting.razor +++ b/Moonlight/Shared/Components/ServerControl/Settings/PythonVersionSetting.razor @@ -1,6 +1,4 @@ @using Moonlight.App.Services -@using Task = System.Threading.Tasks.Task -@using Moonlight.Shared.Components.Partials @using Moonlight.App.Helpers @using Moonlight.App.Repositories @using Moonlight.App.Repositories.Servers @@ -15,24 +13,29 @@
- + @foreach (var image in DockerImages) { - if (image == Image) + if (image.Id == SelectedImage.Id) { - + } else { - + } } - + +
@@ -42,43 +45,36 @@ [CascadingParameter] public Server CurrentServer { get; set; } - private string[] Images; - private string Image; - private LazyLoader LazyLoader; + private List DockerImages; + private DockerImage SelectedImage; - private async Task Load(LazyLoader lazyLoader) + private int ImageIndex { - var serverImage = ImageRepository + get => SelectedImage.Id; + set { SelectedImage = DockerImages.First(x => x.Id == value); } + } + + private Task Load(LazyLoader lazyLoader) + { + var image = ImageRepository .Get() .Include(x => x.DockerImages) .First(x => x.Id == CurrentServer.Image.Id); - - Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name); - - var res = new List(); - foreach (var image in serverImage.DockerImages) - { - res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name)); - } - Images = res.ToArray(); - await InvokeAsync(StateHasChanged); + DockerImages = image.DockerImages; + + SelectedImage = DockerImages[CurrentServer.DockerImageIndex]; + + return Task.CompletedTask; } private async Task Save() { - var serverImage = ImageRepository - .Get() - .Include(x => x.DockerImages) - .First(x => x.Id == CurrentServer.Image.Id); - - var allImages = serverImage.DockerImages; - var imageToUse = allImages.First(x => x.Name.EndsWith(Image)); - CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse); - - ServerRepository.Update(CurrentServer); + CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage); + ServerRepository.Update(CurrentServer); + await LazyLoader.Reload(); } } \ No newline at end of file diff --git a/Moonlight/Shared/Views/Server/Index.razor b/Moonlight/Shared/Views/Server/Index.razor index 94efbf46..a6a6567d 100644 --- a/Moonlight/Shared/Views/Server/Index.razor +++ b/Moonlight/Shared/Views/Server/Index.razor @@ -8,6 +8,7 @@ @using Moonlight.App.Database.Entities @using Moonlight.App.Helpers @using Moonlight.App.Repositories +@using Moonlight.App.Services @using Moonlight.Shared.Components.Xterm @using Moonlight.Shared.Components.ServerControl @using Newtonsoft.Json @@ -15,6 +16,10 @@ @inject ImageRepository ImageRepository @inject ServerRepository ServerRepository @inject WingsConsoleHelper WingsConsoleHelper +@inject MessageService MessageService +@inject NavigationManager NavigationManager + +@implements IDisposable @if (CurrentServer == null) @@ -36,6 +41,21 @@ if (Console.ConnectionState == ConnectionState.Connected) { if (Console.ServerState == ServerState.Installing) + { +
+
+
+
+ + Server installation is currently running + +
+
+ +
+
+ } + else if (CurrentServer.Installing) {
@@ -72,7 +92,7 @@ case "network": index = 3; break; - case "plugins": + case "addons": index = 4; break; case "settings": @@ -207,10 +227,28 @@ await lazyLoader.SetText("Connecting to console"); await WingsConsoleHelper.ConnectWings(Console!, CurrentServer); + + MessageService.Subscribe($"server.{CurrentServer.Uuid}.installcomplete", this, server => + { + Task.Run(() => + { + NavigationManager.NavigateTo(NavigationManager.Uri); + }); + + return Task.CompletedTask; + }); } else { Logger.Debug("Server is null"); } } + + public void Dispose() + { + if (CurrentServer != null) + { + MessageService.Unsubscribe($"server.{CurrentServer.Uuid}.installcomplete", this); + } + } } \ No newline at end of file diff --git a/Moonlight/Shared/Views/Test.razor b/Moonlight/Shared/Views/Test.razor index ce2ede87..eafabd5f 100644 --- a/Moonlight/Shared/Views/Test.razor +++ b/Moonlight/Shared/Views/Test.razor @@ -5,10 +5,13 @@ @using Moonlight.App.Helpers.Files @using Microsoft.EntityFrameworkCore @using Moonlight.App.Helpers +@using Moonlight.App.Services +@using User = Moonlight.App.Database.Entities.User @inject ServerRepository ServerRepository @inject WingsApiHelper WingsApiHelper @inject WingsJwtHelper WingsJwtHelper +@inject ConfigService ConfigService @@ -17,7 +20,10 @@ @code { - private IFileAccess FileAccess; + [CascadingParameter] + public User User { get; set; } + + private FileAccess FileAccess; private Task Load(LazyLoader arg) { @@ -26,7 +32,7 @@ .Include(x => x.Node) .First(); - FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, server); + FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, server, ConfigService, User); return Task.CompletedTask; } diff --git a/Moonlight/resources/lang/de_de.lang b/Moonlight/resources/lang/de_de.lang index 6bb3356e..25a1ae6f 100644 --- a/Moonlight/resources/lang/de_de.lang +++ b/Moonlight/resources/lang/de_de.lang @@ -411,3 +411,24 @@ Server not found;Server not found A server with that id cannot be found or you have no access for this server;A server with that id cannot be found or you have no access for this server Addons;Addons Go up;Go up +Uploading files;Uploading files +complete;complete +Upload complete;Upload complete +Moving;Moving +selected;selected +Select folder to move the file(s) to;Select folder to move the file(s) to +Submit;Submit +Processing;Processing +Error from daemon;Error from daemon +Enter a new name;Enter a new name +The uploaded file should not be bigger than 100MB;The uploaded file should not be bigger than 100MB +Compress;Compress +Compressing;Compressing +Decompress;Decompress +Paper version;Paper version +Error creating server on wings;Error creating server on wings +Javascript version;Javascript version +Javascript file;Javascript file +Select javascript file to execute on start;Select javascript file to execute on start +Javascript Version;Javascript Version +Join2Start;Join2Start diff --git a/Moonlight/wwwroot/assets/js/moonlight.js b/Moonlight/wwwroot/assets/js/moonlight.js new file mode 100644 index 00000000..55e11d6f --- /dev/null +++ b/Moonlight/wwwroot/assets/js/moonlight.js @@ -0,0 +1,13 @@ +window.moonlight = +{ + modals: { + show: function (name) + { + $('#' + name).modal('show'); + }, + hide: function (name) + { + $('#' + name).modal('hide'); + } + } +}; \ No newline at end of file diff --git a/Moonlight/wwwroot/assets/js/toastUtils.js b/Moonlight/wwwroot/assets/js/toastUtils.js index cffa3171..cbccb040 100644 --- a/Moonlight/wwwroot/assets/js/toastUtils.js +++ b/Moonlight/wwwroot/assets/js/toastUtils.js @@ -1,19 +1,52 @@ -window.showInfoToast = function (msg) -{ +window.showInfoToast = function (msg) { toastr['info'](msg); } -window.showErrorToast = function (msg) -{ +window.showErrorToast = function (msg) { toastr['error'](msg); } -window.showSuccessToast = function (msg) -{ +window.showSuccessToast = function (msg) { toastr['success'](msg); } -window.showWarningToast = function (msg) -{ +window.showWarningToast = function (msg) { toastr['warning'](msg); +} + +window.createToast = function (id, text) { + var toast = toastr.success(text, '', + { + closeButton: true, + progressBar: false, + tapToDismiss: false, + timeOut: 0, + extendedTimeOut: 0, + positionClass: "toastr-bottom-right", + preventDuplicates: false, + onclick: function () { + toastr.clear(toast); + } + }); + var toastElement = toast[0]; + toastElement.setAttribute('data-toast-id', id); + toastElement.classList.add("bg-secondary"); +} + +window.modifyToast = function (id, newText) { + var toast = document.querySelector('[data-toast-id="' + id + '"]'); + + if (toast) { + var toastMessage = toast.lastChild; + if (toastMessage) { + toastMessage.innerHTML = newText; + } + } +} + +window.removeToast = function (id) { + var toast = document.querySelector('[data-toast-id="' + id + '"]'); + if (toast) { + toast.childNodes.item(1).click(); + } } \ No newline at end of file