New file manager complete. Server settings for py, js, mc complete. Fixes
This commit is contained in:
24
Moonlight/App/Helpers/Files/FileAccess.cs
Normal file
24
Moonlight/App/Helpers/Files/FileAccess.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Moonlight.App.Helpers.Files;
|
||||||
|
|
||||||
|
public abstract class FileAccess : ICloneable
|
||||||
|
{
|
||||||
|
public string CurrentPath { get; set; } = "/";
|
||||||
|
|
||||||
|
public abstract Task<FileData[]> Ls();
|
||||||
|
public abstract Task Cd(string dir);
|
||||||
|
public abstract Task Up();
|
||||||
|
public abstract Task SetDir(string dir);
|
||||||
|
public abstract Task<string> Read(FileData fileData);
|
||||||
|
public abstract Task Write(FileData fileData, string content);
|
||||||
|
public abstract Task Upload(string name, Stream stream, Action<int>? progressUpdated = null);
|
||||||
|
public abstract Task MkDir(string name);
|
||||||
|
public abstract Task<string> Pwd();
|
||||||
|
public abstract Task<string> DownloadUrl(FileData fileData);
|
||||||
|
public abstract Task<Stream> 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<string> GetLaunchUrl();
|
||||||
|
public abstract object Clone();
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace Moonlight.App.Helpers.Files;
|
|
||||||
|
|
||||||
public interface IFileAccess
|
|
||||||
{
|
|
||||||
public Task<FileData[]> Ls();
|
|
||||||
public Task Cd(string dir);
|
|
||||||
public Task Up();
|
|
||||||
public Task SetDir(string dir);
|
|
||||||
public Task<string> Read(FileData fileData);
|
|
||||||
public Task Write(FileData fileData, string content);
|
|
||||||
public Task Upload(string name, Stream stream, Action<int>? progressUpdated = null);
|
|
||||||
public Task MkDir(string name);
|
|
||||||
public Task<string> Pwd();
|
|
||||||
public Task<string> DownloadUrl(FileData fileData);
|
|
||||||
public Task<Stream> DownloadStream(FileData fileData);
|
|
||||||
public Task Delete(FileData fileData);
|
|
||||||
public Task Move(FileData fileData, string newPath);
|
|
||||||
public Task<string> GetLaunchUrl();
|
|
||||||
}
|
|
||||||
@@ -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.Models.Wings.Resources;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers.Files;
|
namespace Moonlight.App.Helpers.Files;
|
||||||
|
|
||||||
public class WingsFileAccess : IFileAccess
|
public class WingsFileAccess : FileAccess
|
||||||
{
|
{
|
||||||
private readonly WingsApiHelper WingsApiHelper;
|
private readonly WingsApiHelper WingsApiHelper;
|
||||||
private readonly WingsJwtHelper WingsJwtHelper;
|
private readonly WingsJwtHelper WingsJwtHelper;
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
private readonly Server Server;
|
private readonly Server Server;
|
||||||
|
private readonly User User;
|
||||||
|
|
||||||
private string CurrentPath = "/";
|
public WingsFileAccess(
|
||||||
|
WingsApiHelper wingsApiHelper,
|
||||||
public WingsFileAccess(WingsApiHelper wingsApiHelper, WingsJwtHelper wingsJwtHelper,Server server)
|
WingsJwtHelper wingsJwtHelper,
|
||||||
|
Server server,
|
||||||
|
ConfigService configService,
|
||||||
|
User user)
|
||||||
{
|
{
|
||||||
WingsApiHelper = wingsApiHelper;
|
WingsApiHelper = wingsApiHelper;
|
||||||
WingsJwtHelper = wingsJwtHelper;
|
WingsJwtHelper = wingsJwtHelper;
|
||||||
Server = server;
|
Server = server;
|
||||||
|
ConfigService = configService;
|
||||||
|
User = user;
|
||||||
|
|
||||||
if (server.Node == null)
|
if (server.Node == null)
|
||||||
{
|
{
|
||||||
@@ -23,9 +34,9 @@ public class WingsFileAccess : IFileAccess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FileData[]> Ls()
|
public override async Task<FileData[]> Ls()
|
||||||
{
|
{
|
||||||
var res = await WingsApiHelper.Get<ListDirectoryRequest[]>(
|
var res = await WingsApiHelper.Get<ListDirectory[]>(
|
||||||
Server.Node,
|
Server.Node,
|
||||||
$"api/servers/{Server.Uuid}/files/list-directory?directory={CurrentPath}"
|
$"api/servers/{Server.Uuid}/files/list-directory?directory={CurrentPath}"
|
||||||
);
|
);
|
||||||
@@ -45,7 +56,7 @@ public class WingsFileAccess : IFileAccess
|
|||||||
return x.ToArray();
|
return x.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Cd(string dir)
|
public override Task Cd(string dir)
|
||||||
{
|
{
|
||||||
var x = Path.Combine(CurrentPath, dir).Replace("\\", "/") + "/";
|
var x = Path.Combine(CurrentPath, dir).Replace("\\", "/") + "/";
|
||||||
x = x.Replace("//", "/");
|
x = x.Replace("//", "/");
|
||||||
@@ -54,65 +65,168 @@ public class WingsFileAccess : IFileAccess
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Up()
|
public override Task Up()
|
||||||
{
|
{
|
||||||
CurrentPath = Path.GetFullPath(Path.Combine(CurrentPath, "..")).Replace("\\", "/").Replace("C:", "");
|
CurrentPath = Path.GetFullPath(Path.Combine(CurrentPath, "..")).Replace("\\", "/").Replace("C:", "");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SetDir(string dir)
|
public override Task SetDir(string dir)
|
||||||
{
|
{
|
||||||
CurrentPath = dir;
|
CurrentPath = dir;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Read(FileData fileData)
|
public override async Task<string> 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<int>? progressUpdated = null)
|
public override async Task Upload(string name, Stream dataStream, Action<int>? 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<string>("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<string> Pwd()
|
public override Task<string> Pwd()
|
||||||
{
|
{
|
||||||
return Task.FromResult(CurrentPath);
|
return Task.FromResult(CurrentPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> DownloadUrl(FileData fileData)
|
public override Task<string> 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<Stream> DownloadStream(FileData fileData)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Stream> 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<string> 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<string> 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,11 +24,11 @@ public class PaperApiHelper
|
|||||||
else
|
else
|
||||||
requrl = ApiUrl + "/" + url;
|
requrl = ApiUrl + "/" + url;
|
||||||
|
|
||||||
RestRequest request = new(requrl);
|
RestRequest request = new(requrl, Method.Get);
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
var response = await client.GetAsync(request);
|
var response = await client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,24 +14,13 @@ public class WingsApiHelper
|
|||||||
Client = new();
|
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<T> Get<T>(Node node, string resource)
|
public async Task<T> Get<T>(Node node, string resource)
|
||||||
{
|
{
|
||||||
RestRequest request = new(GetApiUrl(node) + resource);
|
var request = CreateRequest(node, resource);
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.Method = Method.Get;
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
|
||||||
|
|
||||||
var response = await Client.GetAsync(request);
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -53,13 +42,11 @@ public class WingsApiHelper
|
|||||||
|
|
||||||
public async Task<string> GetRaw(Node node, string resource)
|
public async Task<string> GetRaw(Node node, string resource)
|
||||||
{
|
{
|
||||||
RestRequest request = new(GetApiUrl(node) + resource);
|
var request = CreateRequest(node, resource);
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.Method = Method.Get;
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
|
||||||
|
|
||||||
var response = await Client.GetAsync(request);
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -81,18 +68,16 @@ public class WingsApiHelper
|
|||||||
|
|
||||||
public async Task<T> Post<T>(Node node, string resource, object? body)
|
public async Task<T> Post<T>(Node node, string resource, object? body)
|
||||||
{
|
{
|
||||||
RestRequest request = new(GetApiUrl(node) + resource);
|
var request = CreateRequest(node, resource);
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.Method = Method.Post;
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
|
||||||
|
|
||||||
request.AddParameter("text/plain",
|
request.AddParameter("text/plain",
|
||||||
JsonConvert.SerializeObject(body),
|
JsonConvert.SerializeObject(body),
|
||||||
ParameterType.RequestBody
|
ParameterType.RequestBody
|
||||||
);
|
);
|
||||||
|
|
||||||
var response = await Client.PostAsync(request);
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -114,16 +99,14 @@ public class WingsApiHelper
|
|||||||
|
|
||||||
public async Task Post(Node node, string resource, object? body)
|
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.Method = Method.Post;
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
|
||||||
|
|
||||||
if(body != null)
|
if(body != null)
|
||||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||||
|
|
||||||
var response = await Client.PostAsync(request);
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -143,15 +126,13 @@ public class WingsApiHelper
|
|||||||
|
|
||||||
public async Task PostRaw(Node node, string resource, object body)
|
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.Method = Method.Post;
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
|
||||||
|
|
||||||
request.AddParameter("text/plain", body, ParameterType.RequestBody);
|
request.AddParameter("text/plain", body, ParameterType.RequestBody);
|
||||||
|
|
||||||
var response = await Client.PostAsync(request);
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -171,16 +152,14 @@ public class WingsApiHelper
|
|||||||
|
|
||||||
public async Task Delete(Node node, string resource, object? body)
|
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.Method = Method.Delete;
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
|
||||||
|
|
||||||
if(body != null)
|
if(body != null)
|
||||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||||
|
|
||||||
var response = await Client.DeleteAsync(request);
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -200,15 +179,13 @@ public class WingsApiHelper
|
|||||||
|
|
||||||
public async Task Put(Node node, string resource, object? body)
|
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.Method = Method.Put;
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
|
||||||
|
|
||||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||||
|
|
||||||
var response = await Client.PutAsync(request);
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ public class WingsFileAccess : IFileAccess
|
|||||||
|
|
||||||
public async Task<FileManagerObject[]> GetDirectoryContent()
|
public async Task<FileManagerObject[]> GetDirectoryContent()
|
||||||
{
|
{
|
||||||
var res = await WingsApiHelper.Get<ListDirectoryRequest[]>(Node,
|
var res = await WingsApiHelper.Get<ListDirectory[]>(Node,
|
||||||
$"api/servers/{Server.Uuid}/files/list-directory?directory={Path}");
|
$"api/servers/{Server.Uuid}/files/list-directory?directory={Path}");
|
||||||
|
|
||||||
var x = new List<FileManagerObject>();
|
var x = new List<FileManagerObject>();
|
||||||
@@ -130,7 +130,7 @@ public class WingsFileAccess : IFileAccess
|
|||||||
public async Task CreateDirectory(string name)
|
public async Task CreateDirectory(string name)
|
||||||
{
|
{
|
||||||
await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/create-directory",
|
await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/create-directory",
|
||||||
new CreateDirectoryRequest()
|
new CreateDirectory()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Path = Path
|
Path = Path
|
||||||
@@ -171,7 +171,7 @@ public class WingsFileAccess : IFileAccess
|
|||||||
|
|
||||||
public async Task Delete(FileManagerObject managerObject)
|
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,
|
Root = Path,
|
||||||
Files = new()
|
Files = new()
|
||||||
@@ -183,7 +183,7 @@ public class WingsFileAccess : IFileAccess
|
|||||||
|
|
||||||
public async Task Move(FileManagerObject managerObject, string newPath)
|
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 = "/",
|
Root = "/",
|
||||||
Files = new[]
|
Files = new[]
|
||||||
|
|||||||
12
Moonlight/App/Models/Wings/Requests/CompressFiles.cs
Normal file
12
Moonlight/App/Models/Wings/Requests/CompressFiles.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Requests;
|
namespace Moonlight.App.Models.Wings.Requests;
|
||||||
|
|
||||||
public class CreateBackupRequest
|
public class CreateBackup
|
||||||
{
|
{
|
||||||
[JsonProperty("adapter")]
|
[JsonProperty("adapter")]
|
||||||
public string Adapter { get; set; }
|
public string Adapter { get; set; }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Requests;
|
namespace Moonlight.App.Models.Wings.Requests;
|
||||||
|
|
||||||
public class CreateDirectoryRequest
|
public class CreateDirectory
|
||||||
{
|
{
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Requests;
|
namespace Moonlight.App.Models.Wings.Requests;
|
||||||
|
|
||||||
public class CreateServerRequest
|
public class CreateServer
|
||||||
{
|
{
|
||||||
[JsonProperty("uuid")]
|
[JsonProperty("uuid")]
|
||||||
public Guid Uuid { get; set; }
|
public Guid Uuid { get; set; }
|
||||||
12
Moonlight/App/Models/Wings/Requests/DecompressFile.cs
Normal file
12
Moonlight/App/Models/Wings/Requests/DecompressFile.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Requests;
|
namespace Moonlight.App.Models.Wings.Requests;
|
||||||
|
|
||||||
public class DeleteFilesRequest
|
public class DeleteFiles
|
||||||
{
|
{
|
||||||
[JsonProperty("root")]
|
[JsonProperty("root")]
|
||||||
public string Root { get; set; }
|
public string Root { get; set; }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Requests;
|
namespace Moonlight.App.Models.Wings.Requests;
|
||||||
|
|
||||||
public class RenameFilesRequest
|
public class RenameFiles
|
||||||
{
|
{
|
||||||
[JsonProperty("root")]
|
[JsonProperty("root")]
|
||||||
public string Root { get; set; }
|
public string Root { get; set; }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Requests;
|
namespace Moonlight.App.Models.Wings.Requests;
|
||||||
|
|
||||||
public class RestoreBackupRequest
|
public class RestoreBackup
|
||||||
{
|
{
|
||||||
[JsonProperty("adapter")]
|
[JsonProperty("adapter")]
|
||||||
public string Adapter { get; set; }
|
public string Adapter { get; set; }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Requests;
|
namespace Moonlight.App.Models.Wings.Requests;
|
||||||
|
|
||||||
public class ServerPowerRequest
|
public class ServerPower
|
||||||
{
|
{
|
||||||
[JsonProperty("action")]
|
[JsonProperty("action")]
|
||||||
public string Action { get; set; }
|
public string Action { get; set; }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Resources;
|
namespace Moonlight.App.Models.Wings.Resources;
|
||||||
|
|
||||||
public class ListDirectoryRequest
|
public class ListDirectory
|
||||||
{
|
{
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Moonlight.App.Models.Wings.Resources;
|
namespace Moonlight.App.Models.Wings.Resources;
|
||||||
|
|
||||||
public class ServerDetailsResponse
|
public class ServerDetails
|
||||||
{
|
{
|
||||||
[JsonProperty("state")]
|
[JsonProperty("state")]
|
||||||
public string State { get; set; }
|
public string State { get; set; }
|
||||||
@@ -11,9 +11,9 @@ public class ServerDetailsResponse
|
|||||||
public bool IsSuspended { get; set; }
|
public bool IsSuspended { get; set; }
|
||||||
|
|
||||||
[JsonProperty("utilization")]
|
[JsonProperty("utilization")]
|
||||||
public ServerDetailsResponseUtilization Utilization { get; set; }
|
public ServerDetailsUtilization Utilization { get; set; }
|
||||||
|
|
||||||
public class ServerDetailsResponseUtilization
|
public class ServerDetailsUtilization
|
||||||
{
|
{
|
||||||
[JsonProperty("memory_bytes")]
|
[JsonProperty("memory_bytes")]
|
||||||
public long MemoryBytes { get; set; }
|
public long MemoryBytes { get; set; }
|
||||||
@@ -25,7 +25,7 @@ public class ServerDetailsResponse
|
|||||||
public double CpuAbsolute { get; set; }
|
public double CpuAbsolute { get; set; }
|
||||||
|
|
||||||
[JsonProperty("network")]
|
[JsonProperty("network")]
|
||||||
public ServerDetailsResponseNetwork Network { get; set; }
|
public ServerDetailsNetwork Network { get; set; }
|
||||||
|
|
||||||
[JsonProperty("uptime")]
|
[JsonProperty("uptime")]
|
||||||
public long Uptime { get; set; }
|
public long Uptime { get; set; }
|
||||||
@@ -37,7 +37,7 @@ public class ServerDetailsResponse
|
|||||||
public long DiskBytes { get; set; }
|
public long DiskBytes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ServerDetailsResponseNetwork
|
public class ServerDetailsNetwork
|
||||||
{
|
{
|
||||||
[JsonProperty("rx_bytes")]
|
[JsonProperty("rx_bytes")]
|
||||||
public long RxBytes { get; set; }
|
public long RxBytes { get; set; }
|
||||||
23
Moonlight/App/Services/Interop/ModalService.cs
Normal file
23
Moonlight/App/Services/Interop/ModalService.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,4 +30,19 @@ public class ToastService
|
|||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("showSuccessToast", message);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -75,11 +75,11 @@ public class ServerService
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerDetailsResponse> GetDetails(Server s)
|
public async Task<ServerDetails> GetDetails(Server s)
|
||||||
{
|
{
|
||||||
Server server = EnsureNodeData(s);
|
Server server = EnsureNodeData(s);
|
||||||
|
|
||||||
return await WingsApiHelper.Get<ServerDetailsResponse>(
|
return await WingsApiHelper.Get<ServerDetails>(
|
||||||
server.Node,
|
server.Node,
|
||||||
$"api/servers/{server.Uuid}"
|
$"api/servers/{server.Uuid}"
|
||||||
);
|
);
|
||||||
@@ -91,7 +91,7 @@ public class ServerService
|
|||||||
|
|
||||||
var rawSignal = signal.ToString().ToLower();
|
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
|
Action = rawSignal
|
||||||
});
|
});
|
||||||
@@ -118,7 +118,7 @@ public class ServerService
|
|||||||
serverData.Backups.Add(backup);
|
serverData.Backups.Add(backup);
|
||||||
ServerRepository.Update(serverData);
|
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",
|
Adapter = "wings",
|
||||||
Uuid = backup.Uuid,
|
Uuid = backup.Uuid,
|
||||||
@@ -158,7 +158,7 @@ public class ServerService
|
|||||||
Server server = EnsureNodeData(s);
|
Server server = EnsureNodeData(s);
|
||||||
|
|
||||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/backup/{serverBackup.Uuid}/restore",
|
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/backup/{serverBackup.Uuid}/restore",
|
||||||
new RestoreBackupRequest()
|
new RestoreBackup()
|
||||||
{
|
{
|
||||||
Adapter = "wings"
|
Adapter = "wings"
|
||||||
});
|
});
|
||||||
@@ -299,7 +299,7 @@ public class ServerService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await WingsApiHelper.Post(node, $"api/servers", new CreateServerRequest()
|
await WingsApiHelper.Post(node, $"api/servers", new CreateServer()
|
||||||
{
|
{
|
||||||
Uuid = newServerData.Uuid,
|
Uuid = newServerData.Uuid,
|
||||||
StartOnCompletion = false
|
StartOnCompletion = false
|
||||||
|
|||||||
@@ -113,5 +113,6 @@
|
|||||||
<script src="/assets/js/loggingUtils.js"></script>
|
<script src="/assets/js/loggingUtils.js"></script>
|
||||||
<script src="/assets/js/snow.js"></script>
|
<script src="/assets/js/snow.js"></script>
|
||||||
<script src="/assets/js/recaptcha.js"></script>
|
<script src="/assets/js/recaptcha.js"></script>
|
||||||
|
<script src="/assets/js/moonlight.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -93,6 +93,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<NotificationServerService>();
|
builder.Services.AddSingleton<NotificationServerService>();
|
||||||
builder.Services.AddScoped<NotificationAdminService>();
|
builder.Services.AddScoped<NotificationAdminService>();
|
||||||
builder.Services.AddScoped<NotificationClientService>();
|
builder.Services.AddScoped<NotificationClientService>();
|
||||||
|
builder.Services.AddScoped<ModalService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<GoogleOAuth2Service>();
|
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||||
builder.Services.AddScoped<DiscordOAuth2Service>();
|
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Logging.Net
|
@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)
|
@if (Editing)
|
||||||
{
|
{
|
||||||
@@ -14,35 +23,63 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="card card-body mb-7">
|
<div class="card mb-7">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title">
|
||||||
<div class="d-flex flex-stack">
|
<div class="d-flex flex-stack">
|
||||||
<div class="badge badge-lg badge-light-primary">
|
<FilePath Access="Access" OnPathChanged="OnComponentStateChanged" />
|
||||||
<div class="d-flex align-items-center flex-wrap">
|
</div>
|
||||||
@{
|
</div>
|
||||||
var vx = "/";
|
<div class="card-toolbar">
|
||||||
}
|
<div class="d-flex justify-content-end align-items-center">
|
||||||
<a @onclick:preventDefault @onclick="() => SetPath(vx)" href="#">/</a>
|
@if (View != null && View.SelectedFiles.Any())
|
||||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
@{
|
|
||||||
var cp = "/";
|
|
||||||
var lp = "/";
|
|
||||||
var pathParts = CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
foreach (var path in pathParts)
|
|
||||||
{
|
{
|
||||||
lp = cp;
|
<div class="fw-bold me-5">
|
||||||
<a @onclick:preventDefault @onclick="() => SetPath(lp)" href="#">@(path)</a>
|
<span class="me-2">@(View.SelectedFiles.Length) <TL>selected</TL></span>
|
||||||
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
</div>
|
||||||
|
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Move"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Moving"))"
|
||||||
|
CssClasses="btn-primary me-3"
|
||||||
|
OnClick="StartMoveFiles">
|
||||||
|
</WButton>
|
||||||
|
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Compress"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Compressing"))"
|
||||||
|
CssClasses="btn-primary me-3"
|
||||||
|
OnClick="CompressMultiple">
|
||||||
|
</WButton>
|
||||||
|
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="DeleteMultiple">
|
||||||
|
</WButton>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" @onclick="Launch" class="btn btn-light-primary me-3">
|
||||||
|
<span class="svg-icon svg-icon-muted svg-icon-2hx">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
<path opacity="0.3" d="M5 16C3.3 16 2 14.7 2 13C2 11.3 3.3 10 5 10H5.1C5 9.7 5 9.3 5 9C5 6.2 7.2 4 10 4C11.9 4 13.5 5 14.3 6.5C14.8 6.2 15.4 6 16 6C17.7 6 19 7.3 19 9C19 9.4 18.9 9.7 18.8 10C18.9 10 18.9 10 19 10C20.7 10 22 11.3 22 13C22 14.7 20.7 16 19 16H5ZM8 13.6H16L12.7 10.3C12.3 9.89999 11.7 9.89999 11.3 10.3L8 13.6Z" fill="currentColor"/>
|
||||||
|
<path d="M11 13.6V19C11 19.6 11.4 20 12 20C12.6 20 13 19.6 13 19V13.6H11Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
|
<TL>Launch WinSCP</TL>
|
||||||
|
</button>
|
||||||
|
|
||||||
cp += path + "/";
|
<button type="button" @onclick="CreateFolder" class="btn btn-light-primary me-3">
|
||||||
}
|
<span class="svg-icon svg-icon-2">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||||
|
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.2C9.7 3 10.2 3.20001 10.4 3.60001ZM16 12H13V9C13 8.4 12.6 8 12 8C11.4 8 11 8.4 11 9V12H8C7.4 12 7 12.4 7 13C7 13.6 7.4 14 8 14H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||||
|
<path opacity="0.3" d="M11 14H8C7.4 14 7 13.6 7 13C7 12.4 7.4 12 8 12H11V14ZM16 12H13V14H16C16.6 14 17 13.6 17 13C17 12.4 16.6 12 16 12Z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<TL>New folder</TL>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<FileUpload Access="Access" OnUploadComplete="OnComponentStateChanged" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,35 +90,141 @@ else
|
|||||||
<FileView @ref="View"
|
<FileView @ref="View"
|
||||||
Access="Access"
|
Access="Access"
|
||||||
ContextActions="Actions"
|
ContextActions="Actions"
|
||||||
OnPathChanged="OnPathChanged"
|
OnSelectionChanged="OnSelectionChanged"
|
||||||
OnElementClicked="OnElementClicked">
|
OnElementClicked="OnElementClicked"
|
||||||
|
DisableScrolling="true">
|
||||||
</FileView>
|
</FileView>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<FileSelectModal @ref="FileSelectModal"
|
||||||
|
OnlyFolder="true"
|
||||||
|
Title="@(SmartTranslateService.Translate("Select folder to move the file(s) to"))"
|
||||||
|
Access="MoveAccess"
|
||||||
|
OnSubmit="OnFileMoveSubmit">
|
||||||
|
</FileSelectModal>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public IFileAccess Access { get; set; }
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
|
// File Editor
|
||||||
private bool Editing = false;
|
private bool Editing = false;
|
||||||
private string EditorInitialData = "";
|
private string EditorInitialData = "";
|
||||||
private string EditorLanguage = "";
|
private string EditorLanguage = "";
|
||||||
private FileData EditingFile;
|
private FileData EditingFile;
|
||||||
private FileEditor Editor;
|
private FileEditor Editor;
|
||||||
|
|
||||||
private FileView View;
|
// File View
|
||||||
private string CurrentPath = "/";
|
private FileView? View;
|
||||||
|
|
||||||
private ContextAction[] Actions =
|
// File Move
|
||||||
|
private FileAccess MoveAccess;
|
||||||
|
private FileSelectModal FileSelectModal;
|
||||||
|
private FileData? SingleMoveFile = null;
|
||||||
|
|
||||||
|
// Config
|
||||||
|
private ContextAction[] Actions = Array.Empty<ContextAction>();
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
new()
|
MoveAccess = (FileAccess)Access.Clone();
|
||||||
|
|
||||||
|
List<ContextAction> actions = new();
|
||||||
|
|
||||||
|
actions.Add(new()
|
||||||
{
|
{
|
||||||
Id = "rename",
|
Id = "rename",
|
||||||
Name = "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<bool> OnElementClicked(FileData fileData)
|
private async Task<bool> OnElementClicked(FileData fileData)
|
||||||
{
|
{
|
||||||
@@ -92,22 +235,13 @@ else
|
|||||||
EditingFile = fileData;
|
EditingFile = fileData;
|
||||||
|
|
||||||
Editing = true;
|
Editing = true;
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetPath(string path)
|
|
||||||
{
|
|
||||||
await Access.SetDir(path);
|
|
||||||
CurrentPath = await Access.Pwd();
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Cancel(bool save = false)
|
private async void Cancel(bool save = false)
|
||||||
@@ -122,9 +256,74 @@ else
|
|||||||
await InvokeAsync(StateHasChanged);
|
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);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
@using Moonlight.App.Helpers.Files
|
||||||
|
|
||||||
|
<div class="badge badge-lg badge-light-primary">
|
||||||
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
|
@{
|
||||||
|
var vx = "/";
|
||||||
|
}
|
||||||
|
<a @onclick:preventDefault @onclick="() => SetPath(vx)" href="#">/</a>
|
||||||
|
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
@{
|
||||||
|
var cp = "/";
|
||||||
|
var lp = "/";
|
||||||
|
var pathParts = Access.CurrentPath.Replace("\\", "/").Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var path in pathParts)
|
||||||
|
{
|
||||||
|
lp = cp;
|
||||||
|
<a @onclick:preventDefault @onclick="() => SetPath(lp)" href="#">@(path)</a>
|
||||||
|
<span class="svg-icon svg-icon-2x svg-icon-primary mx-1">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12.6343 12.5657L8.45001 16.75C8.0358 17.1642 8.0358 17.8358 8.45001 18.25C8.86423 18.6642 9.5358 18.6642 9.95001 18.25L15.4929 12.7071C15.8834 12.3166 15.8834 11.6834 15.4929 11.2929L9.95001 5.75C9.5358 5.33579 8.86423 5.33579 8.45001 5.75C8.0358 6.16421 8.0358 6.83579 8.45001 7.25L12.6343 11.4343C12.9467 11.7467 12.9467 12.2533 12.6343 12.5657Z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
cp += path + "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<Task>? OnPathChanged { get; set; }
|
||||||
|
|
||||||
|
public async Task SetPath(string path)
|
||||||
|
{
|
||||||
|
await Access.SetDir(path);
|
||||||
|
OnPathChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
@using Moonlight.App.Helpers.Files
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject ModalService ModalService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
|
<div class="modal" id="fileView@(Id)" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
@(Title)
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<FileView @ref="FileView"
|
||||||
|
Access="Access"
|
||||||
|
HideSelect="true"
|
||||||
|
Filter="DoFilter"
|
||||||
|
OnElementClicked="OnElementClicked">
|
||||||
|
</FileView>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Submit"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Processing"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="Submit">
|
||||||
|
</WButton>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Cancel"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Processing"))"
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="Cancel">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool OnlyFolder { get; set; } = false;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<FileData, bool>? Filter { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Title { get; set; } = "Select file or folder";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<string, Task>? OnSubmit { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<Task>? 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<bool> OnElementClicked(FileData file)
|
||||||
|
{
|
||||||
|
Result = Access.CurrentPath + file.Name + (file.IsFile ? "" : "/");
|
||||||
|
|
||||||
|
if (!OnlyFolder && file.IsFile)
|
||||||
|
{
|
||||||
|
await Submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||||
|
<label for="fileUpload" class="btn btn-primary me-3 @(Uploading ? "disabled" : "")">
|
||||||
|
@if (Uploading)
|
||||||
|
{
|
||||||
|
<span>@(Percent)%</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="svg-icon svg-icon-2">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||||
|
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||||
|
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<TL>Upload</TL>
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<Task> OnUploadComplete { get; set; }
|
||||||
|
|
||||||
|
private bool Uploading = false;
|
||||||
|
private int Percent = 0;
|
||||||
|
|
||||||
|
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||||
|
{
|
||||||
|
await ToastService.CreateProcessToast("upload", SmartTranslateService.Translate("Uploading files"));
|
||||||
|
|
||||||
|
Uploading = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
foreach (var browserFile in arg.GetMultipleFiles())
|
||||||
|
{
|
||||||
|
if (browserFile.Size < 1024 * 1024 * 100)
|
||||||
|
{
|
||||||
|
Percent = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Access.Upload(
|
||||||
|
browserFile.Name,
|
||||||
|
browserFile.OpenReadStream(1024 * 1024 * 100),
|
||||||
|
async (i) =>
|
||||||
|
{
|
||||||
|
Percent = i;
|
||||||
|
|
||||||
|
Task.Run(() => { InvokeAsync(StateHasChanged); });
|
||||||
|
});
|
||||||
|
|
||||||
|
OnUploadComplete?.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while uploading a file"));
|
||||||
|
Logger.Error("Error uploading file");
|
||||||
|
Logger.Error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
await ToastService.UpdateProcessToast("upload", $"{i}/{arg.GetMultipleFiles().Count} {SmartTranslateService.Translate("complete")}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ToastService.Error(SmartTranslateService.Translate("The uploaded file should not be bigger than 100MB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uploading = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
await ToastService.UpdateProcessToast("upload", SmartTranslateService.Translate("Upload complete"));
|
||||||
|
await ToastService.RemoveProcessToast("upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
|
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
|
||||||
<th class="w-10px pe-2 sorting_disabled">
|
<th class="w-10px pe-2 sorting_disabled">
|
||||||
|
@if (!HideSelect)
|
||||||
|
{
|
||||||
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
|
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
|
||||||
@if (AllToggled)
|
@if (AllToggled)
|
||||||
{
|
{
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
<input @onclick="() => SetToggleState(true)" class="form-check-input" type="checkbox">
|
<input @onclick="() => SetToggleState(true)" class="form-check-input" type="checkbox">
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</th>
|
</th>
|
||||||
<th class="min-w-250px sorting_disabled">Name</th>
|
<th class="min-w-250px sorting_disabled">Name</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -28,7 +31,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dataTables_scrollBody" style="position: relative; overflow: auto; max-height: 700px; width: 100%;">
|
<div class="dataTables_scrollBody" style="@(DisableScrolling ? "" : "position: relative; overflow: auto; max-height: 700px; width: 100%;")">
|
||||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
|
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
|
||||||
<tbody class="fw-semibold text-gray-600">
|
<tbody class="fw-semibold text-gray-600">
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
@@ -38,7 +41,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="icon-wrapper">
|
<span class="icon-wrapper">
|
||||||
<i class="bx bx-md bx-folder text-primary"></i>
|
<i class="bx bx-md bx-up-arrow-alt text-primary"></i>
|
||||||
</span>
|
</span>
|
||||||
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
|
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
|
||||||
<TL>Go up</TL>
|
<TL>Go up</TL>
|
||||||
@@ -58,6 +61,8 @@
|
|||||||
{
|
{
|
||||||
<tr class="even">
|
<tr class="even">
|
||||||
<td class="w-10px">
|
<td class="w-10px">
|
||||||
|
@if (!HideSelect)
|
||||||
|
{
|
||||||
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
||||||
@{
|
@{
|
||||||
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
|
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
|
||||||
@@ -72,6 +77,7 @@
|
|||||||
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
|
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@@ -92,6 +98,8 @@
|
|||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<div class="ms-2 me-7">
|
<div class="ms-2 me-7">
|
||||||
|
@if (ContextActions.Any())
|
||||||
|
{
|
||||||
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
|
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
|
||||||
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
||||||
<span class="svg-icon svg-icon-5 m-0">
|
<span class="svg-icon svg-icon-5 m-0">
|
||||||
@@ -103,6 +111,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -115,29 +124,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
|
@if (ContextActions.Any())
|
||||||
|
{
|
||||||
|
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
|
||||||
@foreach (var action in ContextActions)
|
@foreach (var action in ContextActions)
|
||||||
{
|
{
|
||||||
<Item Id="@action.Id" OnClick="OnContextMenuClick">
|
<Item Id="@action.Id" OnClick="OnContextMenuClick">
|
||||||
<TL>@action.Name</TL>
|
<TL>@action.Name</TL>
|
||||||
</Item>
|
</Item>
|
||||||
}
|
}
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
|
}
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public IFileAccess Access { get; set; }
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
|
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Action<string>? OnPathChanged { get; set; }
|
public Func<Task>? OnSelectionChanged { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public ContextAction[] ContextActions { get; set; } = Array.Empty<ContextAction>();
|
public ContextAction[] ContextActions { get; set; } = Array.Empty<ContextAction>();
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool HideSelect { get; set; } = false;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool DisableScrolling { get; set; } = false;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<FileData, bool>? Filter { get; set; }
|
||||||
|
|
||||||
|
public FileData[] SelectedFiles => ToggleStatusCache
|
||||||
|
.Where(x => x.Value)
|
||||||
|
.Select(x => x.Key)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
private FileData[] Data = Array.Empty<FileData>();
|
private FileData[] Data = Array.Empty<FileData>();
|
||||||
|
|
||||||
private Dictionary<FileData, bool> ToggleStatusCache = new();
|
private Dictionary<FileData, bool> ToggleStatusCache = new();
|
||||||
@@ -145,8 +171,23 @@
|
|||||||
|
|
||||||
public async Task Refresh()
|
public async Task Refresh()
|
||||||
{
|
{
|
||||||
Data = await Access.Ls();
|
var list = new List<FileData>();
|
||||||
|
|
||||||
|
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();
|
ToggleStatusCache.Clear();
|
||||||
|
AllToggled = false;
|
||||||
|
|
||||||
foreach (var fileData in Data)
|
foreach (var fileData in Data)
|
||||||
{
|
{
|
||||||
@@ -154,6 +195,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
OnSelectionChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Load(LazyLoader arg)
|
private async Task Load(LazyLoader arg)
|
||||||
@@ -169,6 +211,7 @@
|
|||||||
ToggleStatusCache.Add(fileData, status);
|
ToggleStatusCache.Add(fileData, status);
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
OnSelectionChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetToggleState(bool status)
|
private async Task SetToggleState(bool status)
|
||||||
@@ -181,6 +224,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
OnSelectionChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnClicked(FileData fileData)
|
private async Task OnClicked(FileData fileData)
|
||||||
@@ -197,16 +241,25 @@
|
|||||||
{
|
{
|
||||||
await Access.Cd(fileData.Name);
|
await Access.Cd(fileData.Name);
|
||||||
await Refresh();
|
await Refresh();
|
||||||
OnPathChanged?.Invoke(await Access.Pwd());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GoUp()
|
private async Task GoUp()
|
||||||
{
|
{
|
||||||
|
if (OnElementClicked != null)
|
||||||
|
{
|
||||||
|
var canceled = await OnElementClicked.Invoke(new()
|
||||||
|
{
|
||||||
|
Name = "..",
|
||||||
|
IsFile = false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await Access.Up();
|
await Access.Up();
|
||||||
await Refresh();
|
await Refresh();
|
||||||
|
|
||||||
OnPathChanged?.Invoke(await Access.Pwd());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task OnContextMenuClick(ItemClickEventArgs eventArgs)
|
private Task OnContextMenuClick(ItemClickEventArgs eventArgs)
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
@using Moonlight.Shared.Components.FileManagerPartials
|
@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.Database.Entities
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Helpers.Files
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
@inject ServerService ServerService
|
@inject WingsApiHelper WingsApiHelper
|
||||||
@inject IdentityService IdentityService
|
@inject WingsJwtHelper WingsJwtHelper
|
||||||
|
@inject ConfigService ConfigService
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
<FileManager Access="FileAccess"></FileManager>
|
||||||
<FileManager2 FileAccess="FileAccess"></FileManager2>
|
|
||||||
</LazyLoader>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Server CurrentServer { get; set; }
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
private IFileAccess FileAccess;
|
[CascadingParameter]
|
||||||
|
public User User { get; set; }
|
||||||
|
|
||||||
private async Task Load(LazyLoader arg)
|
private FileAccess FileAccess;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
var user = await IdentityService.Get(); // User for launch url
|
FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, CurrentServer, ConfigService, User);
|
||||||
FileAccess = await ServerService.CreateFileAccess(CurrentServer, user);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,64 @@
|
|||||||
@using PteroConsole.NET
|
@using PteroConsole.NET
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.Shared.Components.ServerControl.Settings
|
@using Moonlight.Shared.Components.ServerControl.Settings
|
||||||
|
@using Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
<div class="row mb-5">
|
<LazyLoader Load="Load">
|
||||||
@if (Tags.Contains("paperversion"))
|
<div class="accordion" id="serverSetting">
|
||||||
|
@foreach (var setting in Settings)
|
||||||
{
|
{
|
||||||
<PaperVersionSetting></PaperVersionSetting>
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="serverSetting-header@(setting.GetHashCode())">
|
||||||
|
<button class="accordion-button fs-4 fw-semibold" type="button" data-bs-toggle="collapse" data-bs-target="#serverSetting-body@(setting.GetHashCode())" aria-expanded="true" aria-controls="serverSetting-body@(setting.GetHashCode())">
|
||||||
|
<TL>@(setting.Key)</TL>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="serverSetting-body@(setting.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="serverSetting-header@(setting.GetHashCode())" data-bs-parent="#serverSetting">
|
||||||
|
<div class="accordion-body">
|
||||||
|
@(GetComponent(setting.Value))
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
@if (Tags.Contains("pythonversion"))
|
</LazyLoader>
|
||||||
{
|
|
||||||
<PythonVersionSetting></PythonVersionSetting>
|
|
||||||
}
|
|
||||||
|
|
||||||
@{
|
|
||||||
/*
|
|
||||||
* @if (Tags.Contains("pythonfile"))
|
|
||||||
{
|
|
||||||
<PythonFileSetting></PythonFileSetting>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Tags.Contains("javascriptfile"))
|
|
||||||
{
|
|
||||||
<JavascriptFileSetting></JavascriptFileSetting>
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Tags.Contains("javascriptversion"))
|
|
||||||
{
|
|
||||||
<JavascriptVersionSetting></JavascriptVersionSetting>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Tags.Contains("join2start"))
|
|
||||||
{
|
|
||||||
<Join2StartSetting></Join2StartSetting>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[CascadingParameter]
|
|
||||||
public PteroConsole Console { get; set; }
|
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Server CurrentServer { get; set; }
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public string[] Tags { get; set; }
|
public string[] Tags { get; set; }
|
||||||
|
|
||||||
|
private Dictionary<string, Type> 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();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card card-body">
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
<label class="mb-2 form-label">
|
||||||
|
<TL>Javascript file</TL>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="mb-2 form-control disabled" disabled="" value="@(PathAndFile)"/>
|
||||||
|
<button @onclick="Show" class="btn btn-primary"><TL>Change</TL></button>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FileSelectModal @ref="FileSelectModal"
|
||||||
|
Access="Access"
|
||||||
|
Filter="@(x => !x.IsFile || x.Name.EndsWith(".js"))"
|
||||||
|
Title="@(SmartTranslateService.Translate("Select javascript file to execute on start"))"
|
||||||
|
OnlyFolder="false"
|
||||||
|
OnCancel="() => { return Task.CompletedTask; }"
|
||||||
|
OnSubmit="OnSubmit">
|
||||||
|
</FileSelectModal>
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,17 +12,21 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<label class="mb-2 form-label"><TL>Javascript Version</TL></label>
|
<label class="mb-2 form-label"><TL>Javascript version</TL></label>
|
||||||
<select class="mb-2 form-select" @bind="Image">
|
<select @bind="ImageIndex" class="form-select mb-2">
|
||||||
@foreach (var image in Images)
|
@foreach (var image in DockerImages)
|
||||||
{
|
{
|
||||||
if (image == Image)
|
if (image.Id == SelectedImage.Id)
|
||||||
{
|
{
|
||||||
<option value="@(image)" selected="">@(image)</option>
|
<option value="@(image.Id)" selected="selected">
|
||||||
|
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||||
|
</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@(image)">@(image)</option>
|
<option value="@(image.Id)">
|
||||||
|
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||||
|
</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@@ -30,7 +34,8 @@
|
|||||||
OnClick="Save"
|
OnClick="Save"
|
||||||
Text="@(TranslationService.Translate("Change"))"
|
Text="@(TranslationService.Translate("Change"))"
|
||||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||||
CssClasses="btn-primary"></WButton>
|
CssClasses="btn-primary">
|
||||||
|
</WButton>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,41 +45,33 @@
|
|||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Server CurrentServer { get; set; }
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
private string[] Images;
|
|
||||||
private string Image;
|
|
||||||
|
|
||||||
private LazyLoader LazyLoader;
|
private LazyLoader LazyLoader;
|
||||||
|
private List<DockerImage> DockerImages;
|
||||||
|
private DockerImage SelectedImage;
|
||||||
|
|
||||||
private async Task Load(LazyLoader lazyLoader)
|
private int ImageIndex
|
||||||
{
|
{
|
||||||
//TODO: Check if this is a redundant call
|
get => SelectedImage.Id;
|
||||||
var serverImage = ImageRepository
|
set { SelectedImage = DockerImages.First(x => x.Id == value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
var image = ImageRepository
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.DockerImages)
|
.Include(x => x.DockerImages)
|
||||||
.First(x => x.Id == CurrentServer.Image.Id);
|
.First(x => x.Id == CurrentServer.Image.Id);
|
||||||
|
|
||||||
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
|
DockerImages = image.DockerImages;
|
||||||
|
|
||||||
var res = new List<string>();
|
SelectedImage = DockerImages[CurrentServer.DockerImageIndex];
|
||||||
foreach (var image in serverImage.DockerImages)
|
|
||||||
{
|
|
||||||
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
|
|
||||||
}
|
|
||||||
Images = res.ToArray();
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Save()
|
private async Task Save()
|
||||||
{
|
{
|
||||||
var serverImage = ImageRepository
|
CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage);
|
||||||
.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);
|
ServerRepository.Update(CurrentServer);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.Shared.Components.Partials
|
|
||||||
@using Task = System.Threading.Tasks.Task
|
@using Task = System.Threading.Tasks.Task
|
||||||
@using Logging.Net
|
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card card-body">
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
<label class="mb-2 form-label">
|
||||||
|
<TL>Python file</TL>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="mb-2 form-control disabled" disabled="" value="@(PathAndFile)"/>
|
||||||
|
<button @onclick="Show" class="btn btn-primary"><TL>Change</TL></button>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FileSelectModal @ref="FileSelectModal"
|
||||||
|
Access="Access"
|
||||||
|
Filter="@(x => !x.IsFile || x.Name.EndsWith(".py"))"
|
||||||
|
Title="@(SmartTranslateService.Translate("Select python file to execute on start"))"
|
||||||
|
OnlyFolder="false"
|
||||||
|
OnCancel="() => { return Task.CompletedTask; }"
|
||||||
|
OnSubmit="OnSubmit">
|
||||||
|
</FileSelectModal>
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Task = System.Threading.Tasks.Task
|
|
||||||
@using Moonlight.Shared.Components.Partials
|
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Repositories.Servers
|
@using Moonlight.App.Repositories.Servers
|
||||||
@@ -15,16 +13,20 @@
|
|||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<label class="mb-2 form-label"><TL>Python version</TL></label>
|
<label class="mb-2 form-label"><TL>Python version</TL></label>
|
||||||
<select class="mb-2 form-select" @bind="Image">
|
<select @bind="ImageIndex" class="form-select mb-2">
|
||||||
@foreach (var image in Images)
|
@foreach (var image in DockerImages)
|
||||||
{
|
{
|
||||||
if (image == Image)
|
if (image.Id == SelectedImage.Id)
|
||||||
{
|
{
|
||||||
<option value="@(image)" selected="">@(image)</option>
|
<option value="@(image.Id)" selected="selected">
|
||||||
|
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||||
|
</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<option value="@(image)">@(image)</option>
|
<option value="@(image.Id)">
|
||||||
|
@(ParseHelper.FirstPartStartingWithNumber(image.Name))
|
||||||
|
</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@@ -32,7 +34,8 @@
|
|||||||
OnClick="Save"
|
OnClick="Save"
|
||||||
Text="@(TranslationService.Translate("Change"))"
|
Text="@(TranslationService.Translate("Change"))"
|
||||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||||
CssClasses="btn-primary"></WButton>
|
CssClasses="btn-primary">
|
||||||
|
</WButton>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,40 +45,33 @@
|
|||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Server CurrentServer { get; set; }
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
private string[] Images;
|
|
||||||
private string Image;
|
|
||||||
|
|
||||||
private LazyLoader LazyLoader;
|
private LazyLoader LazyLoader;
|
||||||
|
private List<DockerImage> 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()
|
.Get()
|
||||||
.Include(x => x.DockerImages)
|
.Include(x => x.DockerImages)
|
||||||
.First(x => x.Id == CurrentServer.Image.Id);
|
.First(x => x.Id == CurrentServer.Image.Id);
|
||||||
|
|
||||||
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
|
DockerImages = image.DockerImages;
|
||||||
|
|
||||||
var res = new List<string>();
|
SelectedImage = DockerImages[CurrentServer.DockerImageIndex];
|
||||||
foreach (var image in serverImage.DockerImages)
|
|
||||||
{
|
|
||||||
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
|
|
||||||
}
|
|
||||||
Images = res.ToArray();
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Save()
|
private async Task Save()
|
||||||
{
|
{
|
||||||
var serverImage = ImageRepository
|
CurrentServer.DockerImageIndex = DockerImages.IndexOf(SelectedImage);
|
||||||
.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);
|
ServerRepository.Update(CurrentServer);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
@using Moonlight.App.Repositories
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.Shared.Components.Xterm
|
@using Moonlight.Shared.Components.Xterm
|
||||||
@using Moonlight.Shared.Components.ServerControl
|
@using Moonlight.Shared.Components.ServerControl
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
@@ -15,6 +16,10 @@
|
|||||||
@inject ImageRepository ImageRepository
|
@inject ImageRepository ImageRepository
|
||||||
@inject ServerRepository ServerRepository
|
@inject ServerRepository ServerRepository
|
||||||
@inject WingsConsoleHelper WingsConsoleHelper
|
@inject WingsConsoleHelper WingsConsoleHelper
|
||||||
|
@inject MessageService MessageService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
<LazyLoader Load="LoadData">
|
<LazyLoader Load="LoadData">
|
||||||
@if (CurrentServer == null)
|
@if (CurrentServer == null)
|
||||||
@@ -50,6 +55,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
else if (CurrentServer.Installing)
|
||||||
|
{
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-10">
|
||||||
|
<div class="fs-2hx fw-bold text-gray-800 text-center mb-13">
|
||||||
|
<span class="me-2">
|
||||||
|
<TL>Server installation is currently running</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Terminal @ref="InstallConsole"></Terminal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<CascadingValue Value="Console">
|
<CascadingValue Value="Console">
|
||||||
@@ -72,7 +92,7 @@
|
|||||||
case "network":
|
case "network":
|
||||||
index = 3;
|
index = 3;
|
||||||
break;
|
break;
|
||||||
case "plugins":
|
case "addons":
|
||||||
index = 4;
|
index = 4;
|
||||||
break;
|
break;
|
||||||
case "settings":
|
case "settings":
|
||||||
@@ -207,10 +227,28 @@
|
|||||||
await lazyLoader.SetText("Connecting to console");
|
await lazyLoader.SetText("Connecting to console");
|
||||||
|
|
||||||
await WingsConsoleHelper.ConnectWings(Console!, CurrentServer);
|
await WingsConsoleHelper.ConnectWings(Console!, CurrentServer);
|
||||||
|
|
||||||
|
MessageService.Subscribe<Index, Server>($"server.{CurrentServer.Uuid}.installcomplete", this, server =>
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Debug("Server is null");
|
Logger.Debug("Server is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (CurrentServer != null)
|
||||||
|
{
|
||||||
|
MessageService.Unsubscribe($"server.{CurrentServer.Uuid}.installcomplete", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,13 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using User = Moonlight.App.Database.Entities.User
|
||||||
|
|
||||||
@inject ServerRepository ServerRepository
|
@inject ServerRepository ServerRepository
|
||||||
@inject WingsApiHelper WingsApiHelper
|
@inject WingsApiHelper WingsApiHelper
|
||||||
@inject WingsJwtHelper WingsJwtHelper
|
@inject WingsJwtHelper WingsJwtHelper
|
||||||
|
@inject ConfigService ConfigService
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<FileManager Access="FileAccess">
|
<FileManager Access="FileAccess">
|
||||||
@@ -17,7 +20,10 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private IFileAccess FileAccess;
|
[CascadingParameter]
|
||||||
|
public User User { get; set; }
|
||||||
|
|
||||||
|
private FileAccess FileAccess;
|
||||||
|
|
||||||
private Task Load(LazyLoader arg)
|
private Task Load(LazyLoader arg)
|
||||||
{
|
{
|
||||||
@@ -26,7 +32,7 @@
|
|||||||
.Include(x => x.Node)
|
.Include(x => x.Node)
|
||||||
.First();
|
.First();
|
||||||
|
|
||||||
FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, server);
|
FileAccess = new WingsFileAccess(WingsApiHelper, WingsJwtHelper, server, ConfigService, User);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
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
|
Addons;Addons
|
||||||
Go up;Go up
|
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
|
||||||
|
|||||||
13
Moonlight/wwwroot/assets/js/moonlight.js
Normal file
13
Moonlight/wwwroot/assets/js/moonlight.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
window.moonlight =
|
||||||
|
{
|
||||||
|
modals: {
|
||||||
|
show: function (name)
|
||||||
|
{
|
||||||
|
$('#' + name).modal('show');
|
||||||
|
},
|
||||||
|
hide: function (name)
|
||||||
|
{
|
||||||
|
$('#' + name).modal('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,19 +1,52 @@
|
|||||||
window.showInfoToast = function (msg)
|
window.showInfoToast = function (msg) {
|
||||||
{
|
|
||||||
toastr['info'](msg);
|
toastr['info'](msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.showErrorToast = function (msg)
|
window.showErrorToast = function (msg) {
|
||||||
{
|
|
||||||
toastr['error'](msg);
|
toastr['error'](msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.showSuccessToast = function (msg)
|
window.showSuccessToast = function (msg) {
|
||||||
{
|
|
||||||
toastr['success'](msg);
|
toastr['success'](msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.showWarningToast = function (msg)
|
window.showWarningToast = function (msg) {
|
||||||
{
|
|
||||||
toastr['warning'](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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user