Migrated server logic. Added all server endpoints. Migrated some more stuff
This commit is contained in:
parent
95999eae26
commit
c3eadf9133
82 changed files with 5553 additions and 186 deletions
|
@ -8,7 +8,7 @@ using Moonlight.App.Database;
|
|||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.DatabaseMigrations
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20230217152643_MigratedSomeModels")]
|
|
@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.DatabaseMigrations
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MigratedSomeModels : Migration
|
32
Moonlight/App/Exceptions/WingsException.cs
Normal file
32
Moonlight/App/Exceptions/WingsException.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Moonlight.App.Exceptions.Wings;
|
||||
|
||||
[Serializable]
|
||||
public class WingsException : Exception
|
||||
{
|
||||
public int StatusCode { private get; set; }
|
||||
|
||||
public WingsException()
|
||||
{
|
||||
}
|
||||
|
||||
public WingsException(string message, int statusCode) : base(message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
|
||||
public WingsException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public WingsException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected WingsException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
44
Moonlight/App/Helpers/MonacoTypeHelper.cs
Normal file
44
Moonlight/App/Helpers/MonacoTypeHelper.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class MonacoTypeHelper
|
||||
{
|
||||
public static string GetEditorType(string file)
|
||||
{
|
||||
var extension = Path.GetExtension(file);
|
||||
extension = extension.TrimStart("."[0]);
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
case "bat":
|
||||
return "bat";
|
||||
case "cs":
|
||||
return "csharp";
|
||||
case "css":
|
||||
return "css";
|
||||
case "html":
|
||||
return "html";
|
||||
case "java":
|
||||
return "java";
|
||||
case "js":
|
||||
return "javascript";
|
||||
case "ini":
|
||||
return "ini";
|
||||
case "json":
|
||||
return "json";
|
||||
case "lua":
|
||||
return "lua";
|
||||
case "php":
|
||||
return "php";
|
||||
case "py":
|
||||
return "python";
|
||||
case "sh":
|
||||
return "shell";
|
||||
case "xml":
|
||||
return "xml";
|
||||
case "yml":
|
||||
return "yaml";
|
||||
default:
|
||||
return "plaintext";
|
||||
}
|
||||
}
|
||||
}
|
48
Moonlight/App/Helpers/PaperApiHelper.cs
Normal file
48
Moonlight/App/Helpers/PaperApiHelper.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class PaperApiHelper
|
||||
{
|
||||
private string ApiUrl { get; set; }
|
||||
|
||||
public PaperApiHelper()
|
||||
{
|
||||
ApiUrl = "https://api.papermc.io/v2/projects/";
|
||||
}
|
||||
|
||||
public async Task<T> Get<T>(string url)
|
||||
{
|
||||
RestClient client = new();
|
||||
|
||||
string requrl = "NONSET";
|
||||
|
||||
if (ApiUrl.EndsWith("/"))
|
||||
requrl = ApiUrl + url;
|
||||
else
|
||||
requrl = ApiUrl + "/" + url;
|
||||
|
||||
RestRequest request = new(requrl);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
||||
var response = await client.GetAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new Exception(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(response.Content);
|
||||
}
|
||||
}
|
32
Moonlight/App/Helpers/ParseHelper.cs
Normal file
32
Moonlight/App/Helpers/ParseHelper.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public static class ParseHelper
|
||||
{
|
||||
public static int MinecraftToInt(string raw)
|
||||
{
|
||||
var versionWithoutPre = raw.Split("-")[0];
|
||||
|
||||
if (versionWithoutPre.Count(x => x == "."[0]) == 1)
|
||||
versionWithoutPre += ".0";
|
||||
|
||||
return int.Parse(versionWithoutPre.Replace(".", ""));
|
||||
}
|
||||
|
||||
public static string FirstPartStartingWithNumber(string raw)
|
||||
{
|
||||
var numbers = "0123456789";
|
||||
var res = "";
|
||||
var found = false;
|
||||
|
||||
foreach (var p in raw)
|
||||
{
|
||||
if (!found)
|
||||
found = numbers.Contains(p);
|
||||
|
||||
if (found)
|
||||
res += p;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
40
Moonlight/App/Helpers/StreamProgressHelper.cs
Normal file
40
Moonlight/App/Helpers/StreamProgressHelper.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class StreamProgressHelper : Stream
|
||||
{
|
||||
public Action<int>? Progress { get; set; }
|
||||
private int lastPercent = -1;
|
||||
|
||||
Stream InnerStream { get; init; }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var result = InnerStream.ReadAsync(buffer, offset, count).Result;
|
||||
|
||||
int percentComplete = (int)Math.Round((double)(100 * Position) / Length);
|
||||
|
||||
if (lastPercent != percentComplete)
|
||||
{
|
||||
Progress?.Invoke(percentComplete);
|
||||
lastPercent = percentComplete;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
InnerStream.WriteAsync(buffer, offset, count);
|
||||
}
|
||||
public override bool CanRead => InnerStream.CanRead;
|
||||
public override bool CanSeek => InnerStream.CanSeek;
|
||||
public override bool CanWrite => InnerStream.CanWrite;
|
||||
public override long Length => InnerStream.Length;
|
||||
public override long Position { get => InnerStream.Position; set => InnerStream.Position = value; }
|
||||
public StreamProgressHelper(Stream s)
|
||||
{
|
||||
this.InnerStream = s;
|
||||
}
|
||||
public override void Flush() => InnerStream.Flush();
|
||||
public override long Seek(long offset, SeekOrigin origin) => InnerStream.Seek(offset, origin);
|
||||
public override void SetLength(long value)=> InnerStream.SetLength(value);
|
||||
}
|
228
Moonlight/App/Helpers/WingsApiHelper.cs
Normal file
228
Moonlight/App/Helpers/WingsApiHelper.cs
Normal file
|
@ -0,0 +1,228 @@
|
|||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions.Wings;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class WingsApiHelper
|
||||
{
|
||||
private readonly RestClient Client;
|
||||
|
||||
public WingsApiHelper()
|
||||
{
|
||||
Client = new();
|
||||
}
|
||||
|
||||
private string GetApiUrl(Node node)
|
||||
{
|
||||
if(node.Ssl)
|
||||
return $"https://{node.Fqdn}:{node.HttpPort}/";
|
||||
else
|
||||
return $"http://{node.Fqdn}:{node.HttpPort}/";
|
||||
//return $"https://{node.Fqdn}:{node.HttpPort}/";
|
||||
}
|
||||
|
||||
public async Task<T> Get<T>(Node node, string resource)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
var response = await Client.GetAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new WingsException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(response.Content!)!;
|
||||
}
|
||||
|
||||
public async Task<string> GetRaw(Node node, string resource)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
var response = await Client.GetAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new WingsException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return response.Content!;
|
||||
}
|
||||
|
||||
public async Task<T> Post<T>(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
request.AddParameter("text/plain",
|
||||
JsonConvert.SerializeObject(body),
|
||||
ParameterType.RequestBody
|
||||
);
|
||||
|
||||
var response = await Client.PostAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new WingsException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(response.Content!)!;
|
||||
}
|
||||
|
||||
public async Task Post(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
if(body != null)
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.PostAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new WingsException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PostRaw(Node node, string resource, object body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
request.AddParameter("text/plain", body, ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.PostAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new WingsException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Delete(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
if(body != null)
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.DeleteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new WingsException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Put(Node node, string resource, object? body)
|
||||
{
|
||||
RestRequest request = new(GetApiUrl(node) + resource);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Bearer " + node.Token);
|
||||
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.PutAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new WingsException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
104
Moonlight/App/Helpers/WingsConsoleHelper.cs
Normal file
104
Moonlight/App/Helpers/WingsConsoleHelper.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using JWT.Algorithms;
|
||||
using JWT.Builder;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class WingsConsoleHelper
|
||||
{
|
||||
private readonly ServerRepository ServerRepository;
|
||||
private readonly WingsJwtHelper WingsJwtHelper;
|
||||
private readonly string AppUrl;
|
||||
|
||||
public WingsConsoleHelper(
|
||||
ServerRepository serverRepository,
|
||||
ConfigService configService,
|
||||
WingsJwtHelper wingsJwtHelper)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
WingsJwtHelper = wingsJwtHelper;
|
||||
|
||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||
}
|
||||
|
||||
public async Task ConnectWings(PteroConsole.NET.PteroConsole pteroConsole, Server server)
|
||||
{
|
||||
var serverData = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.First(x => x.Id == server.Id);
|
||||
|
||||
var token = GenerateToken(serverData);
|
||||
|
||||
if (serverData.Node.Ssl)
|
||||
{
|
||||
await pteroConsole.Connect(
|
||||
AppUrl,
|
||||
$"wss://{serverData.Node.Fqdn}:{serverData.Node.HttpPort}/api/servers/{serverData.Uuid}/ws",
|
||||
token
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
await pteroConsole.Connect(
|
||||
AppUrl,
|
||||
$"ws://{serverData.Node.Fqdn}:{serverData.Node.HttpPort}/api/servers/{serverData.Uuid}/ws",
|
||||
token
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public string GenerateToken(Server server)
|
||||
{
|
||||
var serverData = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.First(x => x.Id == server.Id);
|
||||
|
||||
var userid = 1;
|
||||
var secret = serverData.Node.Token;
|
||||
|
||||
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
var inputBytes = Encoding.ASCII.GetBytes(userid + serverData.Uuid.ToString());
|
||||
var outputBytes = md5.ComputeHash(inputBytes);
|
||||
|
||||
var identifier = Convert.ToHexString(outputBytes).ToLower();
|
||||
var weirdId = StringHelper.GenerateString(16);
|
||||
|
||||
var token = JwtBuilder.Create()
|
||||
.AddHeader("jti", identifier)
|
||||
.WithAlgorithm(new HMACSHA256Algorithm())
|
||||
.WithSecret(secret)
|
||||
.AddClaim("user_id", userid)
|
||||
.AddClaim("server_uuid", serverData.Uuid.ToString())
|
||||
.AddClaim("permissions", new[]
|
||||
{
|
||||
"*",
|
||||
"admin.websocket.errors",
|
||||
"admin.websocket.install",
|
||||
"admin.websocket.transfer"
|
||||
})
|
||||
.AddClaim("jti", identifier)
|
||||
.AddClaim("unique_id", weirdId)
|
||||
.AddClaim("iat", DateTimeOffset.Now.ToUnixTimeSeconds())
|
||||
.AddClaim("nbf", DateTimeOffset.Now.AddSeconds(-10).ToUnixTimeSeconds())
|
||||
.AddClaim("exp", DateTimeOffset.Now.AddMinutes(10).ToUnixTimeSeconds())
|
||||
.AddClaim("iss", AppUrl)
|
||||
.AddClaim("aud", new[]
|
||||
{
|
||||
serverData.Node.Ssl ? $"https://{serverData.Node.Fqdn}" : $"http://{serverData.Node.Fqdn}"
|
||||
})
|
||||
.MustVerifySignature()
|
||||
.Encode();
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
56
Moonlight/App/Helpers/WingsJwtHelper.cs
Normal file
56
Moonlight/App/Helpers/WingsJwtHelper.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using JWT.Algorithms;
|
||||
using JWT.Builder;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class WingsJwtHelper
|
||||
{
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly string AppUrl;
|
||||
|
||||
public WingsJwtHelper(ConfigService configService)
|
||||
{
|
||||
ConfigService = configService;
|
||||
|
||||
AppUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||
}
|
||||
|
||||
public string Generate(string secret, Action<Dictionary<string, string>> claimsAction)
|
||||
{
|
||||
var userid = 1;
|
||||
|
||||
using MD5 md5 = MD5.Create();
|
||||
var inputBytes = Encoding.ASCII.GetBytes(userid + Guid.NewGuid().ToString());
|
||||
var outputBytes = md5.ComputeHash(inputBytes);
|
||||
|
||||
var identifier = Convert.ToHexString(outputBytes).ToLower();
|
||||
var weirdId = StringHelper.GenerateString(16);
|
||||
|
||||
var builder = JwtBuilder.Create()
|
||||
.AddHeader("jti", identifier)
|
||||
.WithAlgorithm(new HMACSHA256Algorithm())
|
||||
.WithSecret(secret)
|
||||
.AddClaim("user_id", userid)
|
||||
.AddClaim("jti", identifier)
|
||||
.AddClaim("unique_id", weirdId)
|
||||
.AddClaim("iat", DateTimeOffset.Now.ToUnixTimeSeconds())
|
||||
.AddClaim("nbf", DateTimeOffset.Now.AddSeconds(-10).ToUnixTimeSeconds())
|
||||
.AddClaim("exp", DateTimeOffset.Now.AddMinutes(10).ToUnixTimeSeconds())
|
||||
.AddClaim("iss", AppUrl)
|
||||
.MustVerifySignature();
|
||||
|
||||
var additionalClaims = new Dictionary<string, string>();
|
||||
|
||||
claimsAction.Invoke(additionalClaims);
|
||||
|
||||
foreach (var claim in additionalClaims)
|
||||
{
|
||||
builder = builder.AddClaim(claim.Key, claim.Value);
|
||||
}
|
||||
|
||||
return builder.Encode();
|
||||
}
|
||||
}
|
131
Moonlight/App/Helpers/WingsServerConverter.cs
Normal file
131
Moonlight/App/Helpers/WingsServerConverter.cs
Normal file
|
@ -0,0 +1,131 @@
|
|||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Http.Resources.Wings;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class WingsServerConverter
|
||||
{
|
||||
private readonly ServerRepository ServerRepository;
|
||||
private readonly ImageRepository ImageRepository;
|
||||
|
||||
public WingsServerConverter(ServerRepository serverRepository, ImageRepository imageRepository)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
ImageRepository = imageRepository;
|
||||
}
|
||||
|
||||
public WingsServer FromServer(Server s)
|
||||
{
|
||||
var server = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Backups)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Image)
|
||||
.Include(x => x.MainAllocation)
|
||||
.First(x => x.Id == s.Id);
|
||||
|
||||
var wingsServer = new WingsServer
|
||||
{
|
||||
Uuid = server.Uuid
|
||||
};
|
||||
|
||||
// Allocations
|
||||
var def = server.MainAllocation;
|
||||
|
||||
wingsServer.Settings.Allocations.Default.Ip = "0.0.0.0";
|
||||
wingsServer.Settings.Allocations.Default.Port = def.Port;
|
||||
|
||||
foreach (var a in server.Allocations)
|
||||
{
|
||||
wingsServer.Settings.Allocations.Mappings.Ports.Add(a.Port);
|
||||
}
|
||||
|
||||
// Build
|
||||
wingsServer.Settings.Build.Swap = server.Memory * 2;
|
||||
wingsServer.Settings.Build.Threads = null!;
|
||||
wingsServer.Settings.Build.Cpu_Limit = server.Cpu;
|
||||
wingsServer.Settings.Build.Disk_Space = server.Disk;
|
||||
wingsServer.Settings.Build.Io_Weight = 500;
|
||||
wingsServer.Settings.Build.Memory_Limit = server.Memory;
|
||||
wingsServer.Settings.Build.Oom_Disabled = true;
|
||||
|
||||
var image = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == server.Image.Id);
|
||||
|
||||
// Container
|
||||
wingsServer.Settings.Container.Image = image.DockerImages[server.DockerImageIndex].Name;
|
||||
|
||||
// Egg
|
||||
wingsServer.Settings.Egg.Id = image.Uuid;
|
||||
|
||||
// Settings
|
||||
wingsServer.Settings.Skip_Egg_Scripts = false;
|
||||
wingsServer.Settings.Suspended = false; //TODO: Implement
|
||||
wingsServer.Settings.Invocation = string.IsNullOrEmpty(server.OverrideStartup) ? image.Startup : server.OverrideStartup;
|
||||
wingsServer.Settings.Uuid = server.Uuid;
|
||||
|
||||
|
||||
// Environment
|
||||
foreach (var v in server.Variables)
|
||||
{
|
||||
if (!wingsServer.Settings.Environment.ContainsKey(v.Key))
|
||||
{
|
||||
wingsServer.Settings.Environment.Add(v.Key, v.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop
|
||||
if (image.StopCommand.StartsWith("!"))
|
||||
{
|
||||
wingsServer.Process_Configuration.Stop.Type = "stop";
|
||||
wingsServer.Process_Configuration.Stop.Value = null!;
|
||||
}
|
||||
else
|
||||
{
|
||||
wingsServer.Process_Configuration.Stop.Type = "command";
|
||||
wingsServer.Process_Configuration.Stop.Value = image.StopCommand;
|
||||
}
|
||||
|
||||
// Done
|
||||
|
||||
wingsServer.Process_Configuration.Startup.Done = new() { image.StartupDetection };
|
||||
wingsServer.Process_Configuration.Startup.Strip_Ansi = false;
|
||||
wingsServer.Process_Configuration.Startup.User_Interaction = new();
|
||||
|
||||
// Configs
|
||||
var configData = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(image.ConfigFiles!))
|
||||
).Build();
|
||||
|
||||
foreach (var child in configData.GetChildren())
|
||||
{
|
||||
List<WingsServer.WingsServerReplace> replaces = new();
|
||||
|
||||
foreach (var section in child.GetSection("find").GetChildren())
|
||||
{
|
||||
replaces.Add(new()
|
||||
{
|
||||
Match = section.Key,
|
||||
Replace_With = section.Value
|
||||
.Replace("{{server.build.default.port}}", def.Port.ToString())
|
||||
});
|
||||
}
|
||||
|
||||
wingsServer.Process_Configuration.Configs.Add(new()
|
||||
{
|
||||
Parser = child.GetValue<string>("parser"),
|
||||
File = child.Key,
|
||||
Replace = replaces
|
||||
});
|
||||
}
|
||||
|
||||
return wingsServer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api.Remote;
|
||||
|
||||
[Route("api/remote/activity")]
|
||||
[ApiController]
|
||||
public class ActivityController : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
public ActionResult SaveActivity()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
}
|
202
Moonlight/App/Http/Controllers/Api/Remote/ServersController.cs
Normal file
202
Moonlight/App/Http/Controllers/Api/Remote/ServersController.cs
Normal file
|
@ -0,0 +1,202 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Http.Resources.Wings;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api.Remote;
|
||||
|
||||
[Route("api/remote/servers")]
|
||||
[ApiController]
|
||||
public class ServersController : Controller
|
||||
{
|
||||
private readonly WingsServerConverter Converter;
|
||||
private readonly ServerRepository ServerRepository;
|
||||
private readonly NodeRepository NodeRepository;
|
||||
private readonly MessageService MessageService;
|
||||
|
||||
public ServersController(
|
||||
WingsServerConverter converter,
|
||||
ServerRepository serverRepository,
|
||||
NodeRepository nodeRepository,
|
||||
MessageService messageService)
|
||||
{
|
||||
Converter = converter;
|
||||
ServerRepository = serverRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
MessageService = messageService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<PaginationResult<WingsServer>>> GetServers(
|
||||
[FromQuery(Name = "page")] int page,
|
||||
[FromQuery(Name = "per_page")] int perPage)
|
||||
{
|
||||
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
var id = tokenData.Split(".")[0];
|
||||
var token = tokenData.Split(".")[1];
|
||||
|
||||
var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id);
|
||||
|
||||
if (node == null)
|
||||
return NotFound();
|
||||
|
||||
if (token != node.Token)
|
||||
return Unauthorized();
|
||||
|
||||
var servers = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.ToArray();
|
||||
|
||||
List<WingsServer> wingsServers = new();
|
||||
int totalPages = 1;
|
||||
|
||||
if (servers.Length > 0)
|
||||
{
|
||||
var slice = servers.Chunk(perPage).ToArray();
|
||||
var part = slice[page];
|
||||
|
||||
foreach (var server in part)
|
||||
{
|
||||
wingsServers.Add(Converter.FromServer(server));
|
||||
}
|
||||
|
||||
totalPages = slice.Length - 1;
|
||||
}
|
||||
|
||||
await MessageService.Emit($"wings.{node.Id}.serverlist", node);
|
||||
|
||||
//Logger.Debug($"[BRIDGE] Node '{node.Name}' is requesting server list page {page} with {perPage} items per page");
|
||||
|
||||
return PaginationResult<WingsServer>.CreatePagination(
|
||||
wingsServers.ToArray(),
|
||||
page,
|
||||
perPage,
|
||||
totalPages,
|
||||
servers.Length
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("reset")]
|
||||
public async Task<ActionResult> Reset()
|
||||
{
|
||||
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
var id = tokenData.Split(".")[0];
|
||||
var token = tokenData.Split(".")[1];
|
||||
|
||||
var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id);
|
||||
|
||||
if (node == null)
|
||||
return NotFound();
|
||||
|
||||
if (token != node.Token)
|
||||
return Unauthorized();
|
||||
|
||||
await MessageService.Emit($"wings.{node.Id}.statereset", node);
|
||||
|
||||
foreach (var server in ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.ToArray()
|
||||
)
|
||||
{
|
||||
if (server.Installing)
|
||||
{
|
||||
server.Installing = false;
|
||||
ServerRepository.Update(server);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("{uuid}")]
|
||||
public async Task<ActionResult<WingsServer>> GetServer(Guid uuid)
|
||||
{
|
||||
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
var id = tokenData.Split(".")[0];
|
||||
var token = tokenData.Split(".")[1];
|
||||
|
||||
var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id);
|
||||
|
||||
if (node == null)
|
||||
return NotFound();
|
||||
|
||||
if (token != node.Token)
|
||||
return Unauthorized();
|
||||
|
||||
var server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == uuid);
|
||||
|
||||
if (server == null)
|
||||
return NotFound();
|
||||
|
||||
await MessageService.Emit($"wings.{node.Id}.serverfetch", server);
|
||||
|
||||
return Converter.FromServer(server);
|
||||
}
|
||||
|
||||
[HttpGet("{uuid}/install")]
|
||||
public async Task<ActionResult<WingsServerInstall>> GetServerInstall(Guid uuid)
|
||||
{
|
||||
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
var id = tokenData.Split(".")[0];
|
||||
var token = tokenData.Split(".")[1];
|
||||
|
||||
var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id);
|
||||
|
||||
if (node == null)
|
||||
return NotFound();
|
||||
|
||||
if (token != node.Token)
|
||||
return Unauthorized();
|
||||
|
||||
var server = ServerRepository.Get().Include(x => x.Image).FirstOrDefault(x => x.Uuid == uuid);
|
||||
|
||||
if (server == null)
|
||||
return NotFound();
|
||||
|
||||
await MessageService.Emit($"wings.{node.Id}.serverinstallfetch", server);
|
||||
|
||||
return new WingsServerInstall()
|
||||
{
|
||||
Entrypoint = server.Image.InstallEntrypoint,
|
||||
Script = server.Image.InstallScript!,
|
||||
Container_Image = server.Image.InstallDockerImage
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("{uuid}/install")]
|
||||
public async Task<ActionResult> SetInstallState(Guid uuid)
|
||||
{
|
||||
var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", "");
|
||||
var id = tokenData.Split(".")[0];
|
||||
var token = tokenData.Split(".")[1];
|
||||
|
||||
var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id);
|
||||
|
||||
if (node == null)
|
||||
return NotFound();
|
||||
|
||||
if (token != node.Token)
|
||||
return Unauthorized();
|
||||
|
||||
var server = ServerRepository.Get().Include(x => x.Image).FirstOrDefault(x => x.Uuid == uuid);
|
||||
|
||||
if (server == null)
|
||||
return NotFound();
|
||||
|
||||
server.Installing = false;
|
||||
ServerRepository.Update(server);
|
||||
|
||||
await MessageService.Emit($"wings.{node.Id}.serverinstallcomplete", server);
|
||||
await MessageService.Emit($"server.{server.Uuid}.installcomplete", server);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Moonlight.App.Http.Requests.Wings;
|
||||
|
||||
public class ReportBackupCompleteRequest
|
||||
{
|
||||
public bool Successful { get; set; }
|
||||
public long Size { get; set; }
|
||||
}
|
9
Moonlight/App/Http/Requests/Wings/SftpLoginRequest.cs
Normal file
9
Moonlight/App/Http/Requests/Wings/SftpLoginRequest.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Moonlight.App.Http.Requests.Wings;
|
||||
|
||||
public class SftpLoginRequest
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Ip { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
47
Moonlight/App/Http/Resources/Wings/PaginationResult.cs
Normal file
47
Moonlight/App/Http/Resources/Wings/PaginationResult.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Http.Resources.Wings;
|
||||
|
||||
public class PaginationResult<T>
|
||||
{
|
||||
[JsonProperty("data")]
|
||||
public List<T> Data { get; set; }
|
||||
|
||||
[JsonProperty("meta")]
|
||||
public MetaData Meta { get; set; }
|
||||
|
||||
public PaginationResult()
|
||||
{
|
||||
Data = new List<T>();
|
||||
Meta = new();
|
||||
}
|
||||
|
||||
public static PaginationResult<T> CreatePagination(T[] data, int page, int perPage, int totalPages, int totalItems)
|
||||
{
|
||||
var res = new PaginationResult<T>();
|
||||
|
||||
foreach (var i in data)
|
||||
{
|
||||
res.Data.Add(i);
|
||||
}
|
||||
|
||||
res.Meta.Current_Page = page;
|
||||
res.Meta.Total_Pages = totalPages;
|
||||
res.Meta.Count = data.Length;
|
||||
res.Meta.Total = totalItems;
|
||||
res.Meta.Per_Page = perPage;
|
||||
res.Meta.Last_Page = totalPages;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public class MetaData
|
||||
{
|
||||
public int Total { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int Per_Page { get; set; }
|
||||
public int Current_Page { get; set; }
|
||||
public int Last_Page { get; set; }
|
||||
public int Total_Pages { get; set; }
|
||||
}
|
||||
}
|
8
Moonlight/App/Http/Resources/Wings/SftpLoginResult.cs
Normal file
8
Moonlight/App/Http/Resources/Wings/SftpLoginResult.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Http.Resources.Wings;
|
||||
|
||||
public class SftpLoginResult
|
||||
{
|
||||
public string Server { get; set; }
|
||||
public string User { get; set; }
|
||||
public List<string> Permissions { get; set; }
|
||||
}
|
154
Moonlight/App/Http/Resources/Wings/WingsServer.cs
Normal file
154
Moonlight/App/Http/Resources/Wings/WingsServer.cs
Normal file
|
@ -0,0 +1,154 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Moonlight.App.Http.Resources.Wings;
|
||||
|
||||
public class WingsServer
|
||||
{
|
||||
[JsonPropertyName("uuid")]
|
||||
public Guid Uuid { get; set; }
|
||||
|
||||
[JsonPropertyName("settings")] public WingsServerSettings Settings { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("process_configuration")]
|
||||
public WingsServerProcessConfiguration Process_Configuration { get; set; } = new();
|
||||
|
||||
public class WingsServerProcessConfiguration
|
||||
{
|
||||
[JsonPropertyName("startup")] public WingsServerStartup Startup { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("stop")] public WingsServerStop Stop { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("configs")] public List<WingsServerConfig> Configs { get; set; } = new();
|
||||
}
|
||||
|
||||
public class WingsServerConfig
|
||||
{
|
||||
[JsonPropertyName("parser")]
|
||||
public string Parser { get; set; }
|
||||
|
||||
[JsonPropertyName("file")]
|
||||
public string File { get; set; }
|
||||
|
||||
[JsonPropertyName("replace")] public List<WingsServerReplace> Replace { get; set; } = new();
|
||||
}
|
||||
|
||||
public class WingsServerReplace
|
||||
{
|
||||
[JsonPropertyName("match")]
|
||||
public string Match { get; set; }
|
||||
|
||||
[JsonPropertyName("replace_with")]
|
||||
public string Replace_With { get; set; }
|
||||
}
|
||||
|
||||
public class WingsServerStartup
|
||||
{
|
||||
[JsonPropertyName("done")] public List<string> Done { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("user_interaction")] public List<object> User_Interaction { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("strip_ansi")]
|
||||
public bool Strip_Ansi { get; set; }
|
||||
}
|
||||
|
||||
public class WingsServerStop
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class WingsServerSettings
|
||||
{
|
||||
[JsonPropertyName("uuid")]
|
||||
public Guid Uuid { get; set; }
|
||||
|
||||
[JsonPropertyName("suspended")]
|
||||
public bool Suspended { get; set; }
|
||||
|
||||
[JsonPropertyName("environment")] public Dictionary<string, string> Environment { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("invocation")]
|
||||
public string Invocation { get; set; }
|
||||
|
||||
[JsonPropertyName("skip_egg_scripts")]
|
||||
public bool Skip_Egg_Scripts { get; set; }
|
||||
|
||||
[JsonPropertyName("build")] public WingsServerBuild Build { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("container")] public WingsServerContainer Container { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("allocations")] public WingsServerAllocations Allocations { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("mounts")] public List<object> Mounts { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("egg")] public WingsServerEgg Egg { get; set; } = new();
|
||||
}
|
||||
|
||||
public class WingsServerAllocations
|
||||
{
|
||||
[JsonPropertyName("default")] public WingsServerDefault Default { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("mappings")] public WingsServerMappings Mappings { get; set; } = new();
|
||||
}
|
||||
|
||||
public class WingsServerDefault
|
||||
{
|
||||
[JsonPropertyName("ip")]
|
||||
public string Ip { get; set; }
|
||||
|
||||
[JsonPropertyName("port")]
|
||||
public long Port { get; set; }
|
||||
}
|
||||
|
||||
public class WingsServerMappings
|
||||
{
|
||||
[JsonPropertyName("0.0.0.0")] public List<long> Ports { get; set; } = new();
|
||||
}
|
||||
|
||||
public class WingsServerBuild
|
||||
{
|
||||
[JsonPropertyName("memory_limit")]
|
||||
public long Memory_Limit { get; set; }
|
||||
|
||||
[JsonPropertyName("swap")]
|
||||
public long Swap { get; set; }
|
||||
|
||||
[JsonPropertyName("io_weight")]
|
||||
public long Io_Weight { get; set; }
|
||||
|
||||
[JsonPropertyName("cpu_limit")]
|
||||
public long Cpu_Limit { get; set; }
|
||||
|
||||
[JsonPropertyName("threads")]
|
||||
public object Threads { get; set; }
|
||||
|
||||
[JsonPropertyName("disk_space")]
|
||||
public long Disk_Space { get; set; }
|
||||
|
||||
[JsonPropertyName("oom_disabled")]
|
||||
public bool Oom_Disabled { get; set; }
|
||||
}
|
||||
|
||||
public class WingsServerContainer
|
||||
{
|
||||
[JsonPropertyName("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
[JsonPropertyName("oom_disabled")]
|
||||
public bool Oom_Disabled { get; set; }
|
||||
|
||||
[JsonPropertyName("requires_rebuild")]
|
||||
public bool Requires_Rebuild { get; set; }
|
||||
}
|
||||
|
||||
public class WingsServerEgg
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonPropertyName("file_denylist")] public List<object> File_Denylist { get; set; } = new();
|
||||
}
|
||||
}
|
8
Moonlight/App/Http/Resources/Wings/WingsServerInstall.cs
Normal file
8
Moonlight/App/Http/Resources/Wings/WingsServerInstall.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Http.Resources.Wings;
|
||||
|
||||
public class WingsServerInstall
|
||||
{
|
||||
public string Container_Image { get; set; }
|
||||
public string Entrypoint { get; set; }
|
||||
public string Script { get; set; }
|
||||
}
|
83
Moonlight/App/MessageSystem/MessageSender.cs
Normal file
83
Moonlight/App/MessageSystem/MessageSender.cs
Normal file
|
@ -0,0 +1,83 @@
|
|||
using System.Diagnostics;
|
||||
using Logging.Net;
|
||||
|
||||
namespace Moonlight.App.MessageSystem;
|
||||
|
||||
public class MessageSender
|
||||
{
|
||||
private readonly List<MessageSubscriber> Subscribers;
|
||||
|
||||
public bool Debug { get; set; }
|
||||
public TimeSpan TookToLongTime { get; set; } = TimeSpan.FromSeconds(1);
|
||||
|
||||
public MessageSender()
|
||||
{
|
||||
Subscribers = new();
|
||||
}
|
||||
|
||||
public void Subscribe<T, K>(string name, object bind, Func<K, Task> method)
|
||||
{
|
||||
lock (Subscribers)
|
||||
{
|
||||
Subscribers.Add(new ()
|
||||
{
|
||||
Name = name,
|
||||
Action = method,
|
||||
Type = typeof(T),
|
||||
Bind = bind
|
||||
});
|
||||
}
|
||||
|
||||
if(Debug)
|
||||
Logger.Debug($"{bind} subscribed to '{name}'");
|
||||
}
|
||||
|
||||
public void Unsubscribe(string name, object bind)
|
||||
{
|
||||
lock (Subscribers)
|
||||
{
|
||||
Subscribers.RemoveAll(x => x.Bind == bind);
|
||||
}
|
||||
|
||||
if(Debug)
|
||||
Logger.Debug($"{bind} unsubscribed from '{name}'");
|
||||
}
|
||||
|
||||
public Task Emit(string name, object? value, bool disableWarning = false)
|
||||
{
|
||||
lock (Subscribers)
|
||||
{
|
||||
foreach (var subscriber in Subscribers)
|
||||
{
|
||||
if (subscriber.Name == name)
|
||||
{
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
|
||||
var del = (Delegate)subscriber.Action;
|
||||
|
||||
((Task)del.DynamicInvoke(value)!).Wait();
|
||||
|
||||
stopWatch.Stop();
|
||||
|
||||
if (!disableWarning)
|
||||
{
|
||||
if (stopWatch.Elapsed.TotalMilliseconds > TookToLongTime.TotalMilliseconds)
|
||||
{
|
||||
Logger.Warn(
|
||||
$"Subscriber {subscriber.Type.Name} for event '{name}' took long to process. {stopWatch.Elapsed.TotalMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug)
|
||||
{
|
||||
Logger.Debug(
|
||||
$"Subscriber {subscriber.Type.Name} for event '{name}' took {stopWatch.Elapsed.TotalMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
9
Moonlight/App/MessageSystem/MessageSubscriber.cs
Normal file
9
Moonlight/App/MessageSystem/MessageSubscriber.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Moonlight.App.MessageSystem;
|
||||
|
||||
public class MessageSubscriber
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public object Action { get; set; }
|
||||
public Type Type { get; set; }
|
||||
public object Bind { get; set; }
|
||||
}
|
205
Moonlight/App/Models/Files/Accesses/WingsFileAccess.cs
Normal file
205
Moonlight/App/Models/Files/Accesses/WingsFileAccess.cs
Normal file
|
@ -0,0 +1,205 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using JWT.Algorithms;
|
||||
using JWT.Builder;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Wings.Requests;
|
||||
using Moonlight.App.Models.Wings.Resources;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.Models.Files.Accesses;
|
||||
|
||||
public class WingsFileAccess : IFileAccess
|
||||
{
|
||||
private readonly WingsApiHelper WingsApiHelper;
|
||||
private readonly WingsJwtHelper WingsJwtHelper;
|
||||
private readonly Database.Entities.Node Node;
|
||||
private readonly Server Server;
|
||||
private readonly User User;
|
||||
private readonly string AppUrl;
|
||||
|
||||
private string Path { get; set; } = "/";
|
||||
|
||||
public WingsFileAccess(
|
||||
WingsApiHelper wingsApiHelper,
|
||||
Server server,
|
||||
User user,
|
||||
WingsJwtHelper wingsJwtHelper,
|
||||
string appUrl)
|
||||
{
|
||||
WingsApiHelper = wingsApiHelper;
|
||||
Node = server.Node;
|
||||
Server = server;
|
||||
User = user;
|
||||
WingsJwtHelper = wingsJwtHelper;
|
||||
AppUrl = appUrl;
|
||||
}
|
||||
|
||||
public async Task<FileManagerObject[]> GetDirectoryContent()
|
||||
{
|
||||
var res = await WingsApiHelper.Get<ListDirectoryRequest[]>(Node,
|
||||
$"api/servers/{Server.Uuid}/files/list-directory?directory={Path}");
|
||||
|
||||
var x = new List<FileManagerObject>();
|
||||
|
||||
foreach (var response in res)
|
||||
{
|
||||
x.Add(new()
|
||||
{
|
||||
Name = response.Name,
|
||||
Size = response.File ? response.Size : 0,
|
||||
CreatedAt = response.Created.Date,
|
||||
IsFile = response.File,
|
||||
UpdatedAt = response.Modified.Date
|
||||
});
|
||||
}
|
||||
|
||||
return x.ToArray();
|
||||
}
|
||||
|
||||
public Task ChangeDirectory(string s)
|
||||
{
|
||||
var x = System.IO.Path.Combine(Path, s).Replace("\\", "/") + "/";
|
||||
x = x.Replace("//", "/");
|
||||
Path = x;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SetDirectory(string s)
|
||||
{
|
||||
Path = s;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task GoUp()
|
||||
{
|
||||
Path = System.IO.Path.GetFullPath(System.IO.Path.Combine(Path, "..")).Replace("\\", "/").Replace("C:", "");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<string> ReadFile(FileManagerObject fileManagerObject)
|
||||
{
|
||||
return await WingsApiHelper.GetRaw(Node,
|
||||
$"api/servers/{Server.Uuid}/files/contents?file={Path}{fileManagerObject.Name}");
|
||||
}
|
||||
|
||||
public async Task WriteFile(FileManagerObject fileManagerObject, string content)
|
||||
{
|
||||
await WingsApiHelper.PostRaw(Node,
|
||||
$"api/servers/{Server.Uuid}/files/write?file={Path}{fileManagerObject.Name}", content);
|
||||
}
|
||||
|
||||
public async Task UploadFile(string name, Stream dataStream, Action<int> onProgress)
|
||||
{
|
||||
var token = WingsJwtHelper.Generate(Node.Token,
|
||||
claims => { claims.Add("server_uuid", Server.Uuid.ToString()); });
|
||||
|
||||
var client = new RestClient();
|
||||
var request = new RestRequest();
|
||||
|
||||
if (Node.Ssl)
|
||||
request.Resource = $"https://{Node.Fqdn}:{Node.HttpPort}/upload/file?token={token}&directory={Path}";
|
||||
else
|
||||
request.Resource = $"http://{Node.Fqdn}:{Node.HttpPort}/upload/file?token={token}&directory={Path}";
|
||||
|
||||
request.AddParameter("name", "files");
|
||||
request.AddParameter("filename", name);
|
||||
request.AddHeader("Content-Type", "multipart/form-data");
|
||||
request.AddHeader("Origin", AppUrl);
|
||||
request.AddFile("files", () =>
|
||||
{
|
||||
return new StreamProgressHelper(dataStream)
|
||||
{
|
||||
Progress = i =>
|
||||
{
|
||||
if (onProgress != null)
|
||||
onProgress.Invoke(i);
|
||||
}
|
||||
};
|
||||
}, name);
|
||||
|
||||
await client.ExecutePostAsync(request);
|
||||
|
||||
client.Dispose();
|
||||
dataStream.Close();
|
||||
}
|
||||
|
||||
public async Task CreateDirectory(string name)
|
||||
{
|
||||
await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/create-directory",
|
||||
new CreateDirectoryRequest()
|
||||
{
|
||||
Name = name,
|
||||
Path = Path
|
||||
});
|
||||
}
|
||||
|
||||
public Task<string> GetCurrentPath()
|
||||
{
|
||||
return Task.FromResult(Path);
|
||||
}
|
||||
|
||||
public Task<Stream> GetDownloadStream(FileManagerObject managerObject)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<string> GetDownloadUrl(FileManagerObject managerObject)
|
||||
{
|
||||
var token = WingsJwtHelper.Generate(Node.Token, claims =>
|
||||
{
|
||||
claims.Add("server_uuid", Server.Uuid.ToString());
|
||||
claims.Add("file_path", HttpUtility.UrlDecode(Path + "/" + managerObject.Name));
|
||||
});
|
||||
|
||||
if (Node.Ssl)
|
||||
{
|
||||
return Task.FromResult(
|
||||
$"https://{Node.Fqdn}:{Node.HttpPort}/download/file?token={token}"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(
|
||||
$"http://{Node.Fqdn}:{Node.HttpPort}/download/file?token={token}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Delete(FileManagerObject managerObject)
|
||||
{
|
||||
await WingsApiHelper.Post(Node, $"api/servers/{Server.Uuid}/files/delete", new DeleteFilesRequest()
|
||||
{
|
||||
Root = Path,
|
||||
Files = new()
|
||||
{
|
||||
managerObject.Name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task Move(FileManagerObject managerObject, string newPath)
|
||||
{
|
||||
await WingsApiHelper.Put(Node, $"api/servers/{Server.Uuid}/files/rename", new RenameFilesRequest()
|
||||
{
|
||||
Root = "/",
|
||||
Files = new[]
|
||||
{
|
||||
new RenameFilesData()
|
||||
{
|
||||
From = Path + managerObject.Name,
|
||||
To = newPath
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Task<string> GetLaunchUrl()
|
||||
{
|
||||
return Task.FromResult(
|
||||
$"sftp://{User.Id}.{StringHelper.IntToStringWithLeadingZeros(Server.Id, 8)}@{Node.Fqdn}:{Node.SftpPort}");
|
||||
}
|
||||
}
|
10
Moonlight/App/Models/Files/FileManagerObject.cs
Normal file
10
Moonlight/App/Models/Files/FileManagerObject.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Moonlight.App.Models.Files;
|
||||
|
||||
public class FileManagerObject
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool IsFile { get; set; }
|
||||
public long Size { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
19
Moonlight/App/Models/Files/IFileAccess.cs
Normal file
19
Moonlight/App/Models/Files/IFileAccess.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace Moonlight.App.Models.Files;
|
||||
|
||||
public interface IFileAccess
|
||||
{
|
||||
public Task<FileManagerObject[]> GetDirectoryContent();
|
||||
public Task ChangeDirectory(string s);
|
||||
public Task SetDirectory(string s);
|
||||
public Task GoUp();
|
||||
public Task<string> ReadFile(FileManagerObject fileManagerObject);
|
||||
public Task WriteFile(FileManagerObject fileManagerObject, string content);
|
||||
public Task UploadFile(string name, Stream stream, Action<int> progressUpdated);
|
||||
public Task CreateDirectory(string name);
|
||||
public Task<string> GetCurrentPath();
|
||||
public Task<Stream> GetDownloadStream(FileManagerObject managerObject);
|
||||
public Task<string> GetDownloadUrl(FileManagerObject managerObject);
|
||||
public Task Delete(FileManagerObject managerObject);
|
||||
public Task Move(FileManagerObject managerObject, string newPath);
|
||||
public Task<string> GetLaunchUrl();
|
||||
}
|
18
Moonlight/App/Models/Paper/Resources/PaperBuilds.cs
Normal file
18
Moonlight/App/Models/Paper/Resources/PaperBuilds.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Paper.Resources;
|
||||
|
||||
public class PaperBuilds
|
||||
{
|
||||
[JsonProperty("project_id")]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
[JsonProperty("project_name")]
|
||||
public string ProjectName { get; set; }
|
||||
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
|
||||
[JsonProperty("builds")]
|
||||
public List<string> Builds { get; set; }
|
||||
}
|
18
Moonlight/App/Models/Paper/Resources/PaperVersions.cs
Normal file
18
Moonlight/App/Models/Paper/Resources/PaperVersions.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Paper.Resources;
|
||||
|
||||
public class PaperVersions
|
||||
{
|
||||
[JsonProperty("project_id")]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
[JsonProperty("project_name")]
|
||||
public string ProjectName { get; set; }
|
||||
|
||||
[JsonProperty("version_groups")]
|
||||
public List<string> VersionGroups { get; set; }
|
||||
|
||||
[JsonProperty("versions")]
|
||||
public List<string> Versions { get; set; }
|
||||
}
|
9
Moonlight/App/Models/Wings/PowerSignal.cs
Normal file
9
Moonlight/App/Models/Wings/PowerSignal.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Moonlight.App.Models.Wings;
|
||||
|
||||
public enum PowerSignal
|
||||
{
|
||||
Start,
|
||||
Stop,
|
||||
Kill,
|
||||
Restart
|
||||
}
|
15
Moonlight/App/Models/Wings/Requests/CreateBackupRequest.cs
Normal file
15
Moonlight/App/Models/Wings/Requests/CreateBackupRequest.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class CreateBackupRequest
|
||||
{
|
||||
[JsonProperty("adapter")]
|
||||
public string Adapter { get; set; }
|
||||
|
||||
[JsonProperty("uuid")]
|
||||
public Guid Uuid { get; set; }
|
||||
|
||||
[JsonProperty("ignore")]
|
||||
public string Ignore { get; set; }
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class CreateDirectoryRequest
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("path")]
|
||||
public string Path { get; set; }
|
||||
}
|
12
Moonlight/App/Models/Wings/Requests/CreateServerRequest.cs
Normal file
12
Moonlight/App/Models/Wings/Requests/CreateServerRequest.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class CreateServerRequest
|
||||
{
|
||||
[JsonProperty("uuid")]
|
||||
public Guid Uuid { get; set; }
|
||||
|
||||
[JsonProperty("start_on_completion")]
|
||||
public bool StartOnCompletion { get; set; }
|
||||
}
|
11
Moonlight/App/Models/Wings/Requests/DeleteFilesRequest.cs
Normal file
11
Moonlight/App/Models/Wings/Requests/DeleteFilesRequest.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class DeleteFilesRequest
|
||||
{
|
||||
[JsonProperty("root")]
|
||||
public string Root { get; set; }
|
||||
|
||||
[JsonProperty("files")] public List<string> Files { get; set; } = new();
|
||||
}
|
20
Moonlight/App/Models/Wings/Requests/RenameFilesRequest.cs
Normal file
20
Moonlight/App/Models/Wings/Requests/RenameFilesRequest.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class RenameFilesRequest
|
||||
{
|
||||
[JsonProperty("root")]
|
||||
public string Root { get; set; }
|
||||
|
||||
[JsonProperty("files")] public RenameFilesData[] Files { get; set; }
|
||||
}
|
||||
|
||||
public class RenameFilesData
|
||||
{
|
||||
[JsonProperty("from")]
|
||||
public string From { get; set; }
|
||||
|
||||
[JsonProperty("to")]
|
||||
public string To { get; set; }
|
||||
}
|
12
Moonlight/App/Models/Wings/Requests/RestoreBackupRequest.cs
Normal file
12
Moonlight/App/Models/Wings/Requests/RestoreBackupRequest.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class RestoreBackupRequest
|
||||
{
|
||||
[JsonProperty("adapter")]
|
||||
public string Adapter { get; set; }
|
||||
|
||||
[JsonProperty("truncate_directory")] public bool TruncateDirectory { get; set; } = false;
|
||||
[JsonProperty("download_url")] public string DownloadUrl { get; set; } = "";
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Requests;
|
||||
|
||||
public class ServerPowerRequest
|
||||
{
|
||||
[JsonProperty("action")]
|
||||
public string Action { get; set; }
|
||||
}
|
30
Moonlight/App/Models/Wings/Resources/ListDirectoryRequest.cs
Normal file
30
Moonlight/App/Models/Wings/Resources/ListDirectoryRequest.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Resources;
|
||||
|
||||
public class ListDirectoryRequest
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("created")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
[JsonProperty("modified")]
|
||||
public DateTimeOffset Modified { get; set; }
|
||||
|
||||
[JsonProperty("size")]
|
||||
public long Size { get; set; }
|
||||
|
||||
[JsonProperty("directory")]
|
||||
public bool Directory { get; set; }
|
||||
|
||||
[JsonProperty("file")]
|
||||
public bool File { get; set; }
|
||||
|
||||
[JsonProperty("symlink")]
|
||||
public bool Symlink { get; set; }
|
||||
|
||||
[JsonProperty("mime")]
|
||||
public string Mime { get; set; }
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Resources;
|
||||
|
||||
public class ServerDetailsResponse
|
||||
{
|
||||
[JsonProperty("state")]
|
||||
public string State { get; set; }
|
||||
|
||||
[JsonProperty("is_suspended")]
|
||||
public bool IsSuspended { get; set; }
|
||||
|
||||
[JsonProperty("utilization")]
|
||||
public ServerDetailsResponseUtilization Utilization { get; set; }
|
||||
|
||||
public class ServerDetailsResponseUtilization
|
||||
{
|
||||
[JsonProperty("memory_bytes")]
|
||||
public long MemoryBytes { get; set; }
|
||||
|
||||
[JsonProperty("memory_limit_bytes")]
|
||||
public long MemoryLimitBytes { get; set; }
|
||||
|
||||
[JsonProperty("cpu_absolute")]
|
||||
public double CpuAbsolute { get; set; }
|
||||
|
||||
[JsonProperty("network")]
|
||||
public ServerDetailsResponseNetwork Network { get; set; }
|
||||
|
||||
[JsonProperty("uptime")]
|
||||
public long Uptime { get; set; }
|
||||
|
||||
[JsonProperty("state")]
|
||||
public string State { get; set; }
|
||||
|
||||
[JsonProperty("disk_bytes")]
|
||||
public long DiskBytes { get; set; }
|
||||
}
|
||||
|
||||
public class ServerDetailsResponseNetwork
|
||||
{
|
||||
[JsonProperty("rx_bytes")]
|
||||
public long RxBytes { get; set; }
|
||||
|
||||
[JsonProperty("tx_bytes")]
|
||||
public long TxBytes { get; set; }
|
||||
}
|
||||
}
|
21
Moonlight/App/Models/Wings/Resources/SystemStatus.cs
Normal file
21
Moonlight/App/Models/Wings/Resources/SystemStatus.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Models.Wings.Resources;
|
||||
|
||||
public class SystemStatus
|
||||
{
|
||||
[JsonProperty("architecture")]
|
||||
public string Architecture { get; set; }
|
||||
|
||||
[JsonProperty("cpu_count")]
|
||||
public long CpuCount { get; set; }
|
||||
|
||||
[JsonProperty("kernel_version")]
|
||||
public string KernelVersion { get; set; }
|
||||
|
||||
[JsonProperty("os")]
|
||||
public string Os { get; set; }
|
||||
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
}
|
44
Moonlight/App/Repositories/ImageRepository.cs
Normal file
44
Moonlight/App/Repositories/ImageRepository.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Repositories;
|
||||
|
||||
public class ImageRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public ImageRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public DbSet<Image> Get()
|
||||
{
|
||||
return DataContext.Images;
|
||||
}
|
||||
|
||||
public Image Add(Image image)
|
||||
{
|
||||
var x = DataContext.Images.Add(image);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public void Update(Image image)
|
||||
{
|
||||
DataContext.Images.Update(image);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(Image image)
|
||||
{
|
||||
DataContext.Images.Remove(image);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
11
Moonlight/App/Services/MessageService.cs
Normal file
11
Moonlight/App/Services/MessageService.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Moonlight.App.MessageSystem;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class MessageService : MessageSender
|
||||
{
|
||||
public MessageService()
|
||||
{
|
||||
Debug = true;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,23 @@
|
|||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Wings.Resources;
|
||||
using Moonlight.App.Repositories;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class NodeService
|
||||
{
|
||||
private readonly NodeRepository NodeRepository;
|
||||
private readonly WingsApiHelper WingsApiHelper;
|
||||
|
||||
public NodeService(NodeRepository nodeRepository)
|
||||
public NodeService(NodeRepository nodeRepository, WingsApiHelper wingsApiHelper)
|
||||
{
|
||||
NodeRepository = nodeRepository;
|
||||
WingsApiHelper = wingsApiHelper;
|
||||
}
|
||||
|
||||
public async Task<SystemStatus> GetStatus(Node node)
|
||||
{
|
||||
return await WingsApiHelper.Get<SystemStatus>(node, "api/system");
|
||||
}
|
||||
}
|
28
Moonlight/App/Services/PaperService.cs
Normal file
28
Moonlight/App/Services/PaperService.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Paper.Resources;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class PaperService
|
||||
{
|
||||
private readonly PaperApiHelper ApiHelper;
|
||||
|
||||
public PaperService(PaperApiHelper apiHelper)
|
||||
{
|
||||
ApiHelper = apiHelper;
|
||||
}
|
||||
|
||||
public async Task<string[]> GetVersions()
|
||||
{
|
||||
var data = await ApiHelper.Get<PaperVersions>("paper");
|
||||
|
||||
return data.Versions.ToArray();
|
||||
}
|
||||
|
||||
public async Task<string[]> GetBuilds(string version)
|
||||
{
|
||||
var data = await ApiHelper.Get<PaperBuilds>($"paper/versions/{version}");
|
||||
|
||||
return data.Builds.ToArray();
|
||||
}
|
||||
}
|
326
Moonlight/App/Services/ServerService.cs
Normal file
326
Moonlight/App/Services/ServerService.cs
Normal file
|
@ -0,0 +1,326 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using JWT.Algorithms;
|
||||
using JWT.Builder;
|
||||
using Logging.Net;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Files;
|
||||
using Moonlight.App.Models.Files.Accesses;
|
||||
using Moonlight.App.Models.Wings;
|
||||
using Moonlight.App.Models.Wings.Requests;
|
||||
using Moonlight.App.Models.Wings.Resources;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class ServerService
|
||||
{
|
||||
private readonly ServerRepository ServerRepository;
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly ImageRepository ImageRepository;
|
||||
private readonly NodeRepository NodeRepository;
|
||||
private readonly WingsApiHelper WingsApiHelper;
|
||||
private readonly MessageService MessageService;
|
||||
private readonly UserService UserService;
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly WingsJwtHelper WingsJwtHelper;
|
||||
private readonly string AppUrl;
|
||||
|
||||
public ServerService(
|
||||
ServerRepository serverRepository,
|
||||
WingsApiHelper wingsApiHelper,
|
||||
UserRepository userRepository,
|
||||
ImageRepository imageRepository,
|
||||
NodeRepository nodeRepository,
|
||||
MessageService messageService,
|
||||
UserService userService,
|
||||
ConfigService configService,
|
||||
WingsJwtHelper wingsJwtHelper)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
WingsApiHelper = wingsApiHelper;
|
||||
UserRepository = userRepository;
|
||||
ImageRepository = imageRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
MessageService = messageService;
|
||||
UserService = userService;
|
||||
ConfigService = configService;
|
||||
WingsJwtHelper = wingsJwtHelper;
|
||||
|
||||
AppUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||
}
|
||||
|
||||
private Server EnsureNodeData(Server s)
|
||||
{
|
||||
if (s.Node == null) // Ensure node data is available
|
||||
{
|
||||
return ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.First(x => x.Id == s.Id);
|
||||
}
|
||||
else
|
||||
return s;
|
||||
}
|
||||
|
||||
public async Task<ServerDetailsResponse> GetDetails(Server s)
|
||||
{
|
||||
Server server = EnsureNodeData(s);
|
||||
|
||||
return await WingsApiHelper.Get<ServerDetailsResponse>(
|
||||
server.Node,
|
||||
$"api/servers/{server.Uuid}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task SetPowerState(Server s, PowerSignal signal)
|
||||
{
|
||||
Server server = EnsureNodeData(s);
|
||||
|
||||
var rawSignal = signal.ToString().ToLower();
|
||||
|
||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/power", new ServerPowerRequest()
|
||||
{
|
||||
Action = rawSignal
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<ServerBackup> CreateBackup(Server server)
|
||||
{
|
||||
var serverData = ServerRepository // Ensure data
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Backups)
|
||||
.First(x => x.Id == server.Id);
|
||||
|
||||
var backup = new ServerBackup()
|
||||
{
|
||||
Name = $"Created at {DateTime.Now.ToShortDateString()} {DateTime.Now.ToShortTimeString()}",
|
||||
Uuid = Guid.NewGuid(),
|
||||
CreatedAt = DateTime.Now,
|
||||
Created = false
|
||||
};
|
||||
|
||||
serverData.Backups.Add(backup);
|
||||
ServerRepository.Update(serverData);
|
||||
|
||||
await WingsApiHelper.Post(serverData.Node, $"api/servers/{serverData.Uuid}/backup", new CreateBackupRequest()
|
||||
{
|
||||
Adapter = "wings",
|
||||
Uuid = backup.Uuid,
|
||||
Ignore = ""
|
||||
});
|
||||
|
||||
return backup;
|
||||
}
|
||||
|
||||
public Task<ServerBackup[]> GetBackups(Server server, bool forceReload = false)
|
||||
{
|
||||
if (forceReload) //TODO: Find an alternative to avoid cache and the creation of a new db context
|
||||
{
|
||||
var serverData = new ServerRepository(new DataContext(ConfigService))
|
||||
.Get()
|
||||
.Include(x => x.Backups)
|
||||
.First(x => x.Id == server.Id);
|
||||
|
||||
return Task.FromResult(serverData.Backups.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
var serverData = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Backups)
|
||||
.First(x => x.Id == server.Id);
|
||||
|
||||
return Task.FromResult(serverData.Backups.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RestoreBackup(Server s, ServerBackup serverBackup)
|
||||
{
|
||||
Server server = EnsureNodeData(s);
|
||||
|
||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/backup/{serverBackup.Uuid}/restore",
|
||||
new RestoreBackupRequest()
|
||||
{
|
||||
Adapter = "wings"
|
||||
});
|
||||
}
|
||||
|
||||
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
|
||||
{
|
||||
var serverData = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Backups)
|
||||
.First(x => x.Id == server.Id);
|
||||
|
||||
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
|
||||
null);
|
||||
|
||||
var backup = serverData.Backups.First(x => x.Uuid == serverBackup.Uuid);
|
||||
serverData.Backups.Remove(backup);
|
||||
|
||||
ServerRepository.Update(serverData);
|
||||
|
||||
await MessageService.Emit("wings.backups.delete", backup);
|
||||
}
|
||||
|
||||
public Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
|
||||
{
|
||||
Server server = EnsureNodeData(s);
|
||||
|
||||
var token = WingsJwtHelper.Generate(server.Node.Token, claims =>
|
||||
{
|
||||
claims.Add("server_uuid", server.Uuid.ToString());
|
||||
claims.Add("backup_uuid", serverBackup.Uuid.ToString());
|
||||
});
|
||||
|
||||
return Task.FromResult(
|
||||
$"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}"
|
||||
);
|
||||
}
|
||||
|
||||
public Task<IFileAccess> CreateFileAccess(Server s, User user) // We need the user to create the launch url
|
||||
{
|
||||
Server server = EnsureNodeData(s);
|
||||
|
||||
return Task.FromResult(
|
||||
(IFileAccess)new WingsFileAccess(
|
||||
WingsApiHelper,
|
||||
server,
|
||||
user,
|
||||
WingsJwtHelper,
|
||||
AppUrl
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null, Action<Server>? modifyDetails = null)
|
||||
{
|
||||
var user = UserRepository
|
||||
.Get()
|
||||
.First(x => x.Id == u.Id);
|
||||
|
||||
var image = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == i.Id);
|
||||
|
||||
Node node;
|
||||
|
||||
if (n == null)
|
||||
{
|
||||
node = NodeRepository.Get().Include(x => x.Allocations).First(); //TODO: Smart deploy
|
||||
}
|
||||
else
|
||||
{
|
||||
node = NodeRepository
|
||||
.Get()
|
||||
.Include(x => x.Allocations)
|
||||
.First(x => x.Id == n.Id);
|
||||
}
|
||||
|
||||
NodeAllocation freeAllo;
|
||||
|
||||
try
|
||||
{
|
||||
freeAllo = node.Allocations.First(a => !ServerRepository.Get()
|
||||
.SelectMany(s => s.Allocations)
|
||||
.Any(b => b.Id == a.Id)); // Thank you ChatGPT <3
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new DisplayException("No allocation found");
|
||||
}
|
||||
|
||||
if (freeAllo == null)
|
||||
throw new DisplayException("No allocation found");
|
||||
|
||||
var server = new Server()
|
||||
{
|
||||
Cpu = cpu,
|
||||
Memory = memory,
|
||||
Disk = disk,
|
||||
Name = name,
|
||||
Image = image,
|
||||
Owner = user,
|
||||
Node = node,
|
||||
Uuid = Guid.NewGuid(),
|
||||
MainAllocation = freeAllo,
|
||||
Allocations = new()
|
||||
{
|
||||
freeAllo
|
||||
},
|
||||
Backups = new(),
|
||||
OverrideStartup = "",
|
||||
DockerImageIndex = image.DockerImages.FindIndex(x => x.Default)
|
||||
};
|
||||
|
||||
foreach (var imageVariable in image.Variables)
|
||||
{
|
||||
server.Variables.Add(new()
|
||||
{
|
||||
Key = imageVariable.Key,
|
||||
Value = imageVariable.DefaultValue
|
||||
});
|
||||
}
|
||||
|
||||
if(modifyDetails != null)
|
||||
modifyDetails.Invoke(server);
|
||||
|
||||
var newServerData = ServerRepository.Add(server);
|
||||
|
||||
try
|
||||
{
|
||||
await WingsApiHelper.Post(node, $"api/servers", new CreateServerRequest()
|
||||
{
|
||||
Uuid = newServerData.Uuid,
|
||||
StartOnCompletion = false
|
||||
});
|
||||
|
||||
return newServerData;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("Error creating server on wings. Deleting db model");
|
||||
Logger.Error(e);
|
||||
|
||||
ServerRepository.Delete(newServerData);
|
||||
|
||||
throw new Exception("Error creating server on wings");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Reinstall(Server s)
|
||||
{
|
||||
Server server = EnsureNodeData(s);
|
||||
|
||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null);
|
||||
}
|
||||
|
||||
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
|
||||
{
|
||||
var server = ServerRepository.Get().FirstOrDefault(x => x.Id == serverId);
|
||||
|
||||
if (server == null) //TODO: Logging
|
||||
throw new Exception("Server not found");
|
||||
|
||||
var user = await UserService.SftpLogin(id, password);
|
||||
|
||||
if (server.Owner.Id == user.Id)
|
||||
{
|
||||
return server;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("User and owner id do not match");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,8 @@
|
|||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
<PackageReference Include="Blazor.ContextMenu" Version="1.15.0" />
|
||||
<PackageReference Include="BlazorMonaco" Version="3.0.0" />
|
||||
<PackageReference Include="Blazored.Typeahead" Version="4.7.0" />
|
||||
<PackageReference Include="BlazorMonaco" Version="2.1.0" />
|
||||
<PackageReference Include="BlazorTable" Version="1.17.0" />
|
||||
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
|
||||
<PackageReference Include="Discord.Net" Version="3.9.0" />
|
||||
|
@ -56,10 +57,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="App\Database\Migrations" />
|
||||
<Folder Include="App\Http\Middleware" />
|
||||
<Folder Include="App\Http\Requests" />
|
||||
<Folder Include="App\Http\Resources" />
|
||||
<Folder Include="App\Models\AuditLogData" />
|
||||
<Folder Include="resources\lang" />
|
||||
<Folder Include="wwwroot\assets\media" />
|
||||
|
|
|
@ -38,13 +38,14 @@
|
|||
<link rel="stylesheet" type="text/css" href="/assets/css/style.bundle.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/flashbang.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/snow.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/invisiblea.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/utils.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/boxicons.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/assets/css/blazor.css"/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/_content/Blazor.ContextMenu/blazorContextMenu.min.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/_content/Blazored.Typeahead/blazored-typeahead.css" />
|
||||
|
||||
<link href="/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css"/>
|
||||
|
||||
|
@ -80,22 +81,26 @@
|
|||
</div>
|
||||
|
||||
<script src="/_framework/blazor.server.js"></script>
|
||||
|
||||
<script src="/assets/plugins/global/plugins.bundle.js"></script>
|
||||
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
|
||||
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
||||
<script src="/_content/BlazorInputFile/inputfile.js"></script>
|
||||
<script src="/_content/BlazorMonaco/jsInterop.js"></script>
|
||||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
||||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
|
||||
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
|
||||
<script src="/_content/Blazored.Typeahead/blazored-typeahead.js"></script>
|
||||
|
||||
<script src="https://www.google.com/recaptcha/api.js"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
|
||||
<script src="http://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script>
|
||||
<script src="http://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script>
|
||||
<script src="http://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
|
||||
|
||||
<script src="/assets/js/xtermAddons.js"></script>
|
||||
|
||||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
||||
<script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
|
||||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
<script src="/_content/BlazorMonaco/jsInterop.js"></script>
|
||||
|
||||
<script src="/assets/js/scripts.bundle.js"></script>
|
||||
<script src="/assets/js/flashbang.js"></script>
|
||||
|
@ -107,6 +112,5 @@
|
|||
<script src="/assets/js/loggingUtils.js"></script>
|
||||
<script src="/assets/js/snow.js"></script>
|
||||
<script src="/assets/js/recaptcha.js"></script>
|
||||
<script src="/assets/js/xtermAddons.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -35,6 +35,7 @@ namespace Moonlight
|
|||
builder.Services.AddScoped<ServerBackupRepository>();
|
||||
builder.Services.AddScoped<AuditLogRepository>();
|
||||
builder.Services.AddScoped<DatabaseRepository>();
|
||||
builder.Services.AddScoped<ImageRepository>();
|
||||
|
||||
// Services
|
||||
builder.Services.AddSingleton<ConfigService>();
|
||||
|
@ -47,12 +48,22 @@ namespace Moonlight
|
|||
builder.Services.AddScoped<UserService>();
|
||||
builder.Services.AddScoped<TotpService>();
|
||||
builder.Services.AddScoped<ToastService>();
|
||||
builder.Services.AddScoped<NodeService>();
|
||||
builder.Services.AddSingleton<MessageService>();
|
||||
builder.Services.AddScoped<ServerService>();
|
||||
builder.Services.AddSingleton<PaperService>();
|
||||
builder.Services.AddScoped<ClipboardService>();
|
||||
|
||||
builder.Services.AddScoped<AuditLogService>();
|
||||
builder.Services.AddScoped<SystemAuditLogService>();
|
||||
|
||||
// Helpers
|
||||
builder.Services.AddSingleton<SmartTranslateHelper>();
|
||||
builder.Services.AddScoped<WingsApiHelper>();
|
||||
builder.Services.AddScoped<WingsServerConverter>();
|
||||
builder.Services.AddSingleton<WingsJwtHelper>();
|
||||
builder.Services.AddScoped<WingsConsoleHelper>();
|
||||
builder.Services.AddSingleton<PaperApiHelper>();
|
||||
|
||||
// Third party services
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
@using BlazorMonaco
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="card-body">
|
||||
<MonacoEditor CssClass="h-100" @ref="Editor" Id="vseditor" ConstructionOptions="(x) => EditorOptions"/>
|
||||
</div>
|
||||
|
||||
<div class="card-footer pt-0">
|
||||
<div class="btn-group">
|
||||
<WButton
|
||||
Text="@(TranslationService.Translate("Save"))"
|
||||
WorkingText="@(TranslationService.Translate("Saving"))"
|
||||
OnClick="Submit"></WButton>
|
||||
<WButton
|
||||
CssClasses="btn-danger"
|
||||
Text="@(TranslationService.Translate("Cancel"))"
|
||||
WorkingText="@(TranslationService.Translate("Canceling"))"
|
||||
OnClick="Cancel"></WButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public string InitialData { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Language { get; set; }
|
||||
|
||||
// Events
|
||||
[Parameter]
|
||||
public Action<string> OnSubmit { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action OnCancel { get; set; }
|
||||
|
||||
// Monaco Editor
|
||||
private MonacoEditor Editor;
|
||||
private StandaloneEditorConstructionOptions EditorOptions;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
EditorOptions = new()
|
||||
{
|
||||
AutomaticLayout = true,
|
||||
Language = "plaintext",
|
||||
Value = "Wird geladen",
|
||||
Theme = "vs-dark",
|
||||
Contextmenu = false,
|
||||
Minimap = new()
|
||||
{
|
||||
Enabled = false
|
||||
},
|
||||
AutoIndent = true
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () =>
|
||||
{
|
||||
EditorOptions.Language = Language;
|
||||
|
||||
var model = await Editor.GetModel();
|
||||
await MonacoEditorBase.SetModelLanguage(model, EditorOptions.Language);
|
||||
await Editor.SetPosition(new Position()
|
||||
{
|
||||
Column = 0,
|
||||
LineNumber = 1
|
||||
});
|
||||
|
||||
await Editor.SetValue(InitialData);
|
||||
|
||||
await Editor.Layout(new Dimension()
|
||||
{
|
||||
Height = 500,
|
||||
Width = 1000
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
var data = await Editor.GetValue();
|
||||
await InvokeAsync(() => OnSubmit?.Invoke(data));
|
||||
}
|
||||
|
||||
private async Task Cancel()
|
||||
{
|
||||
await InvokeAsync(() => OnCancel?.Invoke());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,513 @@
|
|||
@using Moonlight.App.Helpers
|
||||
@using BlazorContextMenu
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Models.Files
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="card card-flush">
|
||||
@if (Editing == null)
|
||||
{
|
||||
<div class="card-header pt-8">
|
||||
<div class="card-title">
|
||||
<div class="d-flex align-items-center position-relative my-1">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-6">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.5" x="17.0365" y="15.1223" width="8.15546" height="2" rx="1" transform="rotate(45 17.0365 15.1223)" fill="currentColor"></rect>
|
||||
<path d="M11 19C6.55556 19 3 15.4444 3 11C3 6.55556 6.55556 3 11 3C15.4444 3 19 6.55556 19 11C19 15.4444 15.4444 19 11 19ZM11 5C7.53333 5 5 7.53333 5 11C5 14.4667 7.53333 17 11 17C14.4667 17 17 14.4667 17 11C17 7.53333 14.4667 5 11 5Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" @bind="Search" @bind:event="oninput" class="form-control form-control-solid w-250px ps-15" placeholder="@(TranslationService.Translate("Search files and folders"))">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
@if (SelectedFiles.Count == 0)
|
||||
{
|
||||
<div class="d-flex justify-content-end">
|
||||
<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">
|
||||
<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>
|
||||
</span>
|
||||
<TL>Launch WinSCP</TL>
|
||||
</button>
|
||||
|
||||
<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>
|
||||
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||
<label for="fileUpload" class="btn btn-primary me-3 pt-5 @(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>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<div class="fw-bold me-5">
|
||||
<span class="me-2">
|
||||
@(SelectedFiles.Count)
|
||||
</span>
|
||||
<TL>Selected</TL>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary me-3">
|
||||
<TL>Move deleted</TL>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger">
|
||||
<TL>Delete selected</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-stack">
|
||||
<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 = 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>
|
||||
</div>
|
||||
<div class="dt-bootstrap4 no-footer">
|
||||
@if (Loading)
|
||||
{
|
||||
<div class="mt-5 alert alert-info">
|
||||
<span>
|
||||
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-gray-400 fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="w-10px pe-2">
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid me-3">
|
||||
<input class="form-check-input" type="checkbox" @onchange="OnAllFileToggle">
|
||||
</div>
|
||||
</th>
|
||||
<th class="min-w-250px">
|
||||
<TL>File name</TL>
|
||||
</th>
|
||||
<th class="min-w-10px">
|
||||
<TL>File size</TL>
|
||||
</th>
|
||||
<th class="min-w-125px">
|
||||
<TL>Last modified</TL>
|
||||
</th>
|
||||
<th class="w-125px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-600">
|
||||
@foreach (var obj in Objects.Where(x => x.Name.Contains(Search)))
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
||||
<input class="form-check-input" type="checkbox" @onchange="(e) => OnFileToggle(e, obj)">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
@if (obj.IsFile)
|
||||
{
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M19 22H5C4.4 22 4 21.6 4 21V3C4 2.4 4.4 2 5 2H14L20 8V21C20 21.6 19.6 22 19 22Z" fill="currentColor"></path>
|
||||
<path d="M15 8H20L14 2V7C14 7.6 14.4 8 15 8Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="svg-icon svg-icon-2x svg-icon-primary me-4">
|
||||
<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="M9.2 3H3C2.4 3 2 3.4 2 4V19C2 19.6 2.4 20 3 20H21C21.6 20 22 19.6 22 19V7C22 6.4 21.6 6 21 6H12L10.4 3.60001C10.2 3.20001 9.7 3 9.2 3Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
}
|
||||
|
||||
@if (obj.IsFile)
|
||||
{
|
||||
<a href="#" @onclick:preventDefault @onclick="() => OpenFile(obj)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="#" @onclick:preventDefault @onclick="() => CdPath(obj.Name)" class="text-gray-800 text-hover-primary">@(obj.Name)</a>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@(Formatter.FormatSize(obj.Size))
|
||||
</td>
|
||||
<td>
|
||||
@(obj.UpdatedAt.ToShortDateString()) @(obj.UpdatedAt.ToShortTimeString())
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="ms-2">
|
||||
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="obj">
|
||||
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
||||
<span class="svg-icon svg-icon-5 m-0">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Loading)
|
||||
{
|
||||
<div class="mt-5 alert alert-info">
|
||||
<span>
|
||||
<TL>Loading</TL> <span class="spinner-grow align-middle ms-2"></span>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<FileEditor OnSubmit="SaveFile" OnCancel="CloseFile" InitialData="@(InitialEditorData)" Language="@(Language)"></FileEditor>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
|
||||
<Item Id="rename" OnClick="OnContextMenuClick"><TL>Rename</TL></Item>
|
||||
<Item Id="move" OnClick="OnContextMenuClick"><TL>Move</TL></Item>
|
||||
<Item Id="archive" OnClick="OnContextMenuClick"><TL>Archive</TL></Item>
|
||||
<Item Id="unarchive" OnClick="OnContextMenuClick"><TL>Unarchive</TL></Item>
|
||||
<Item Id="download" OnClick="OnContextMenuClick"><TL>Download</TL></Item>
|
||||
<Item Id="delete" OnClick="OnContextMenuClick"><TL>Delete</TL></Item>
|
||||
</ContextMenu>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public IFileAccess FileAccess { get; set; }
|
||||
|
||||
// Data
|
||||
|
||||
private List<FileManagerObject> SelectedFiles { get; set; } = new();
|
||||
private List<FileManagerObject> Objects { get; set; } = new();
|
||||
private string CurrentPath = "";
|
||||
|
||||
// Search
|
||||
private string SearchValue = "";
|
||||
|
||||
private string Search
|
||||
{
|
||||
get { return SearchValue; }
|
||||
set
|
||||
{
|
||||
SearchValue = value;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// States
|
||||
private bool Loading = false;
|
||||
|
||||
// States - Editor
|
||||
private FileManagerObject? Editing = null;
|
||||
private string InitialEditorData = "";
|
||||
private string Language = "plaintext";
|
||||
|
||||
// States - File Upload
|
||||
private bool Uploading = false;
|
||||
private int Percent = 0;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await RefreshActive();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshActive()
|
||||
{
|
||||
Loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Refresh(false);
|
||||
|
||||
Loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Refresh(bool rerender = true)
|
||||
{
|
||||
SelectedFiles.Clear();
|
||||
Objects.Clear();
|
||||
CurrentPath = await FileAccess.GetCurrentPath();
|
||||
|
||||
var data = await FileAccess.GetDirectoryContent();
|
||||
Objects = data.ToList();
|
||||
|
||||
if (rerender)
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task CdPath(string path)
|
||||
{
|
||||
await FileAccess.ChangeDirectory(path);
|
||||
await RefreshActive();
|
||||
}
|
||||
|
||||
private async Task SetPath(string path)
|
||||
{
|
||||
await FileAccess.SetDirectory(path);
|
||||
await RefreshActive();
|
||||
}
|
||||
|
||||
private async Task OnContextMenuClick(ItemClickEventArgs e)
|
||||
{
|
||||
var data = e.Data as FileManagerObject;
|
||||
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
switch (e.MenuItem.Id)
|
||||
{
|
||||
case "delete":
|
||||
await FileAccess.Delete(data);
|
||||
break;
|
||||
case "download":
|
||||
if (data.IsFile)
|
||||
{
|
||||
// First we try to download via stream
|
||||
|
||||
try
|
||||
{
|
||||
var url = await FileAccess.GetDownloadUrl(data);
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
await ToastService.Info(TranslationService.Translate("Starting download"));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("Error starting download"));
|
||||
Logger.Error("Error downloading file");
|
||||
Logger.Error(exception.Message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "rename":
|
||||
var newName = await AlertService.Text(TranslationService.Translate("Rename"), TranslationService.Translate("Enter a new name"), data.Name);
|
||||
var path = await FileAccess.GetCurrentPath();
|
||||
await FileAccess.Move(data, path + "/" + newName);
|
||||
break;
|
||||
}
|
||||
|
||||
await Refresh(false);
|
||||
}
|
||||
|
||||
private async Task OnFileToggle(ChangeEventArgs obj, FileManagerObject o)
|
||||
{
|
||||
if ((bool)obj.Value)
|
||||
{
|
||||
if (SelectedFiles.Contains(o))
|
||||
return;
|
||||
|
||||
SelectedFiles.Add(o);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!SelectedFiles.Contains(o))
|
||||
return;
|
||||
|
||||
SelectedFiles.Remove(o);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnAllFileToggle(ChangeEventArgs obj)
|
||||
{
|
||||
if ((bool)obj.Value)
|
||||
{
|
||||
foreach (var o in Objects)
|
||||
{
|
||||
if (SelectedFiles.Contains(o))
|
||||
continue;
|
||||
|
||||
SelectedFiles.Add(o);
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var o in Objects)
|
||||
{
|
||||
if (!SelectedFiles.Contains(o))
|
||||
continue;
|
||||
|
||||
SelectedFiles.Remove(o);
|
||||
}
|
||||
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateFolder()
|
||||
{
|
||||
var name = await AlertService.Text(TranslationService.Translate("Create a new folder"), TranslationService.Translate("Enter a name"), "");
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return;
|
||||
|
||||
await FileAccess.CreateDirectory(name);
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private async void SaveFile(string data)
|
||||
{
|
||||
if (Editing == null)
|
||||
return;
|
||||
|
||||
await FileAccess.WriteFile(Editing, data);
|
||||
Editing = null;
|
||||
await Refresh();
|
||||
}
|
||||
|
||||
private async Task OpenFile(FileManagerObject o)
|
||||
{
|
||||
Editing = o;
|
||||
Loading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
InitialEditorData = await FileAccess.ReadFile(Editing);
|
||||
Language = MonacoTypeHelper.GetEditorType(Editing.Name);
|
||||
|
||||
Loading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async void CloseFile()
|
||||
{
|
||||
Editing = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async void Launch()
|
||||
{
|
||||
NavigationManager.NavigateTo(await FileAccess.GetLaunchUrl());
|
||||
}
|
||||
|
||||
private async Task OnFileChanged(InputFileChangeEventArgs arg)
|
||||
{
|
||||
Uploading = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
foreach (var browserFile in arg.GetMultipleFiles())
|
||||
{
|
||||
if (browserFile.Size < 1024 * 1024 * 100)
|
||||
{
|
||||
Percent = 0;
|
||||
|
||||
try
|
||||
{
|
||||
await FileAccess.UploadFile(
|
||||
browserFile.Name,
|
||||
browserFile.OpenReadStream(1024 * 1024 * 100),
|
||||
async (i) =>
|
||||
{
|
||||
Percent = i;
|
||||
|
||||
Task.Run(() => { InvokeAsync(StateHasChanged); });
|
||||
});
|
||||
|
||||
await Refresh();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("An unknown error occured while uploading a file"));
|
||||
Logger.Error("Error uploading file");
|
||||
Logger.Error(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToastService.Error(TranslationService.Translate("The uploaded file should not be bigger than 100MB"));
|
||||
}
|
||||
}
|
||||
|
||||
Uploading = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await ToastService.Success(TranslationService.Translate("File upload complete"));
|
||||
}
|
||||
}
|
306
Moonlight/Shared/Components/ServerControl/ServerBackups.razor
Normal file
306
Moonlight/Shared/Components/ServerControl/ServerBackups.razor
Normal file
|
@ -0,0 +1,306 @@
|
|||
@using PteroConsole.NET
|
||||
@using Moonlight.App.Services
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.App.Helpers
|
||||
@using Logging.Net
|
||||
@using BlazorContextMenu
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services.Interop
|
||||
|
||||
@inject ServerService ServerService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@inject ClipboardService ClipboardService
|
||||
@inject MessageService MessageService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Refresh">
|
||||
@if (3 > AllBackups.Length)
|
||||
{
|
||||
<WButton
|
||||
Text="@(SmartTranslateService.Translate("Create"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||
CssClasses="btn-primary mb-2"
|
||||
OnClick="Create"></WButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-primary disabled mb-2" disabled=""><TL>Delete</TL></button>
|
||||
}
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-600">
|
||||
@foreach (var backup in AllBackups)
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="ms-5">
|
||||
<a class="text-gray-800 text-hover-primary fs-5 fw-bold">@(backup.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end pe-0">
|
||||
<span class="fw-bold">@(backup.Uuid)</span>
|
||||
</td>
|
||||
<td class="text-end pe-0">
|
||||
<span class="fw-bold">@(Formatter.FormatSize(backup.Bytes))</span>
|
||||
</td>
|
||||
<td class="text-end pe-0">
|
||||
<span class="fw-bold ms-3">@backup.CreatedAt.Date.ToLongDateString(), @backup.CreatedAt.Date.ToLongTimeString()</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@if (backup.Created)
|
||||
{
|
||||
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="backup">
|
||||
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
||||
<span class="svg-icon svg-icon-5 m-0">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="fw-bold">
|
||||
<TL>Backup is going to be created</TL>
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ContextMenu Id="triggerMenu" CssClass="bg-secondary z-10">
|
||||
<Item Id="restore" OnClick="OnContextMenuClick">
|
||||
<TL>Restore</TL>
|
||||
</Item>
|
||||
<Item Id="copyurl" OnClick="OnContextMenuClick">
|
||||
<TL>Copy url</TL>
|
||||
</Item>
|
||||
<Item Id="download" OnClick="OnContextMenuClick">
|
||||
<TL>Download</TL>
|
||||
</Item>
|
||||
<Item Id="delete" OnClick="OnContextMenuClick">
|
||||
<TL>Delete</TL>
|
||||
</Item>
|
||||
</ContextMenu>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public PteroConsole Console { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private ServerBackup[]? AllBackups;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.create", this, async (backup) =>
|
||||
{
|
||||
if (AllBackups == null)
|
||||
return;
|
||||
|
||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
await ToastService.Success(SmartTranslateService.Translate("Backup successfully created"));
|
||||
await LazyLoader.Reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.createfailed", this, async (backup) =>
|
||||
{
|
||||
if (AllBackups == null)
|
||||
return;
|
||||
|
||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await ToastService.Error(SmartTranslateService.Translate("Backup creation failed"));
|
||||
await LazyLoader.Reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.delete", this, async (backup) =>
|
||||
{
|
||||
if (AllBackups == null)
|
||||
return;
|
||||
|
||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await ToastService.Success(SmartTranslateService.Translate("Backup successfully deleted"));
|
||||
await LazyLoader.Reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.restore", this, async (backup) =>
|
||||
{
|
||||
if (AllBackups == null)
|
||||
return;
|
||||
|
||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||
{
|
||||
Task.Run(async () => { await ToastService.Success(SmartTranslateService.Translate("Backup successfully restored")); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task Refresh(LazyLoader lazyLoader)
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await lazyLoader.SetText(SmartTranslateService.Translate("Loading backups"));
|
||||
AllBackups = await ServerService.GetBackups(CurrentServer, true);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Download(ServerBackup serverBackup)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
||||
|
||||
NavigationManager.NavigateTo(url);
|
||||
await ToastService.Success(SmartTranslateService.Translate("Backup download successfully started"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error starting backup download");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("Backup download failed"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyUrl(ServerBackup serverBackup)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
||||
|
||||
await ClipboardService.CopyToClipboard(url);
|
||||
await AlertService.Success(
|
||||
SmartTranslateService.Translate("Success"),
|
||||
SmartTranslateService.Translate("Backup URL successfully copied to your clipboard"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error copying backup url");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while generating backup url"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Delete(ServerBackup serverBackup)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ToastService.Info(SmartTranslateService.Translate("Backup deletion started"));
|
||||
await ServerService.DeleteBackup(CurrentServer, serverBackup);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error deleting backup");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while starting backup deletion"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Restore(ServerBackup serverBackup)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ServerService.RestoreBackup(CurrentServer, serverBackup);
|
||||
|
||||
await ToastService.Info(SmartTranslateService.Translate("Backup restore started"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error restoring backup");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while restoring a backup"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Create()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ToastService.Info(SmartTranslateService.Translate("Started backup creation"));
|
||||
var backup = await ServerService.CreateBackup(CurrentServer);
|
||||
|
||||
/*
|
||||
|
||||
// Modify the backup list so no reload needed. Also the create event will work
|
||||
var list = AllBackups!.ToList();
|
||||
list.Add(backup);
|
||||
AllBackups = list.ToArray();
|
||||
|
||||
*/
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error creating backup");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error has occured while creating a backup"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnContextMenuClick(ItemClickEventArgs args)
|
||||
{
|
||||
var backup = (ServerBackup)args.Data;
|
||||
|
||||
switch (args.MenuItem.Id)
|
||||
{
|
||||
case "delete":
|
||||
await Delete(backup);
|
||||
break;
|
||||
case "copyurl":
|
||||
await CopyUrl(backup);
|
||||
break;
|
||||
case "restore":
|
||||
await Restore(backup);
|
||||
break;
|
||||
case "download":
|
||||
await Download(backup);
|
||||
break;
|
||||
}
|
||||
|
||||
await Refresh(LazyLoader);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
MessageService.Unsubscribe("wings.backups.create", this);
|
||||
MessageService.Unsubscribe("wings.backups.createfailed", this);
|
||||
MessageService.Unsubscribe("wings.backups.restore", this);
|
||||
MessageService.Unsubscribe("wings.backups.delete", this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
@using PteroConsole.NET
|
||||
@using PteroConsole.NET.Enums
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using Moonlight.Shared.Components.Xterm
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
@inject ClipboardService ClipboardService
|
||||
@inject AlertService AlertService
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="row g-5 g-xl-10 mb-xl-10">
|
||||
<Terminal @ref="Terminal" RunOnFirstRender="RunOnFirstRender"></Terminal>
|
||||
|
||||
<div class="mt-3 row">
|
||||
<div class="ms-2 input-group">
|
||||
<script suppress-error="BL9992">
|
||||
function checkEnter(event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
document.getElementById("sendCmd").click();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<input @bind="@CommandInput" class="form-control" onkeyup="checkEnter(event)" placeholder="@(TranslationService.Translate("Enter command"))"/>
|
||||
|
||||
<button id="sendCmd" @onclick="SendCommand" class="input-group-text btn btn-primary">@(TranslationService.Translate("Execute"))</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public PteroConsole Console { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private Terminal? Terminal;
|
||||
|
||||
private string CommandInput = "";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Console.OnMessage += OnMessage;
|
||||
}
|
||||
|
||||
private async void OnMessage(object? sender, string e)
|
||||
{
|
||||
if (Terminal != null)
|
||||
{
|
||||
var s = e;
|
||||
|
||||
s = s.Replace("Pterodactyl Daemon", "Moonlight Daemon");
|
||||
s = s.Replace("Checking server disk space usage, this could take a few seconds...", TranslationService.Translate("Checking disk space"));
|
||||
s = s.Replace("Updating process configuration files...", TranslationService.Translate("Updating config files"));
|
||||
s = s.Replace("Ensuring file permissions are set correctly, this could take a few seconds...", TranslationService.Translate("Checking file permissions"));
|
||||
s = s.Replace("Pulling Docker container image, this could take a few minutes to complete...", TranslationService.Translate("Downloading server image"));
|
||||
s = s.Replace("Finished pulling Docker container image", TranslationService.Translate("Downloaded server image"));
|
||||
s = s.Replace("container@pterodactyl~", "server@moonlight >");
|
||||
|
||||
await Terminal.WriteLine(s);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Console.OnMessage -= OnMessage;
|
||||
Terminal!.Dispose();
|
||||
}
|
||||
|
||||
private async Task SendCommand()
|
||||
{
|
||||
CommandInput = CommandInput.Replace("\n", "");
|
||||
await Console.EnterCommand(CommandInput);
|
||||
CommandInput = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void RunOnFirstRender()
|
||||
{
|
||||
lock (Console.MessageCache)
|
||||
{
|
||||
foreach (var message in Console.MessageCache.TakeLast(30))
|
||||
{
|
||||
OnMessage(null, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
Moonlight/Shared/Components/ServerControl/ServerFiles.razor
Normal file
27
Moonlight/Shared/Components/ServerControl/ServerFiles.razor
Normal file
|
@ -0,0 +1,27 @@
|
|||
@using Moonlight.Shared.Components.FileManagerPartials
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Models.Files
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using Moonlight.App.Database.Entities
|
||||
|
||||
@inject ServerService ServerService
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<FileManager FileAccess="FileAccess"></FileManager>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private IFileAccess FileAccess;
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
{
|
||||
var user = await IdentityService.Get(); // User for launch url
|
||||
FileAccess = await ServerService.CreateFileAccess(CurrentServer, user);
|
||||
}
|
||||
}
|
208
Moonlight/Shared/Components/ServerControl/ServerNavigation.razor
Normal file
208
Moonlight/Shared/Components/ServerControl/ServerNavigation.razor
Normal file
|
@ -0,0 +1,208 @@
|
|||
@using PteroConsole.NET
|
||||
@using PteroConsole.NET.Enums
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="align-items-center">
|
||||
<div class="row">
|
||||
<div class="card card-body me-6">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="symbol symbol-circle me-5">
|
||||
<div class="symbol-label bg-transparent text-primary border border-secondary border-dashed">
|
||||
<i class="bx bx-server bx-md"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<div class="mb-1 fs-4">@(CurrentServer.Name)</div>
|
||||
<div class="text-muted fs-5">@(CurrentServer.Cpu <= 100 ? Math.Round(CurrentServer.Cpu / 100f, 2) + $" {TranslationService.Translate("Core")}" : Math.Round(CurrentServer.Cpu / 100f, 2) + $" {TranslationService.Translate("Cores")}") / @(Math.Round(CurrentServer.Memory / 1024f, 2)) GB @(TranslationService.Translate("Memory")) / @(Math.Round(CurrentServer.Disk / 1024f, 2)) GB @(TranslationService.Translate("Disk")) / @(CurrentServer.Node.Name) - <span class="text-muted">@(CurrentServer.Image.Name)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 d-flex flex-column flex-end mb-1">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="w-100 nav-link btn btn-sm btn-success fw-bold px-4 me-1 @(Console.ServerState == ServerState.Offline ? "" : "disabled")" aria-selected="true" role="tab" @onclick="Start"><TL>Start</TL></button>
|
||||
<button class="w-100 nav-link btn btn-sm btn-primary fw-bold px-4 me-1 @(Console.ServerState == ServerState.Running ? "" : "disabled")" aria-selected="true" role="tab" @onclick="Restart"><TL>Restart</TL></button>
|
||||
@if (Console.ServerState == ServerState.Stopping)
|
||||
{
|
||||
<button class="w-100 nav-link btn btn-sm btn-danger fw-bold px-4 me-1" aria-selected="true" role="tab" @onclick="Kill"><TL>Kill</TL></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="w-100 nav-link btn btn-sm btn-danger fw-bold px-4 me-1 @(Console.ServerState == ServerState.Running || Console.ServerState == ServerState.Starting ? "" : "disabled")"
|
||||
aria-selected="true" role="tab" @onclick="Stop">
|
||||
<TL>Stop</TL>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="separator my-5"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Shared IP</TL>:</span>
|
||||
<span class="ms-1 text-muted">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
|
||||
</div>
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
||||
<span class="ms-1 text-muted">@(CurrentServer.Id)</span>
|
||||
</div>
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Status</TL>:</span>
|
||||
<span class="ms-1 text-muted">
|
||||
@switch (Console.ServerState)
|
||||
{
|
||||
case ServerState.Offline:
|
||||
<span class="text-danger"><TL>Offline</TL></span>
|
||||
break;
|
||||
|
||||
case ServerState.Starting:
|
||||
<span class="text-warning"><TL>Starting</TL></span>
|
||||
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.ServerResource.Uptime)))</span>
|
||||
break;
|
||||
|
||||
case ServerState.Stopping:
|
||||
<span class="text-warning"><TL>Stopping</TL></span>
|
||||
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.ServerResource.Uptime)))</span>
|
||||
break;
|
||||
|
||||
case ServerState.Running:
|
||||
<span class="text-success"><TL>Online</TL></span>
|
||||
<span class="text-gray-700 pt-1 fw-semibold">(@(Formatter.FormatUptime(Console.ServerResource.Uptime)))</span>
|
||||
break;
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Cpu</TL>:</span>
|
||||
<span class="ms-1 text-muted">@(Math.Round(Console.ServerResource.CpuAbsolute, 2))%</span>
|
||||
</div>
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Memory</TL>:</span>
|
||||
<span class="ms-1 text-muted">@(Formatter.FormatSize(Console.ServerResource.MemoryBytes)) / @(Formatter.FormatSize(Console.ServerResource.MemoryLimitBytes))</span>
|
||||
</div>
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Disk</TL>:</span>
|
||||
<span class="ms-1 text-muted">@(Formatter.FormatSize(Console.ServerResource.DiskBytes)) / @(Math.Round(CurrentServer.Disk / 1024f, 2)) GB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 row">
|
||||
<div class="d-flex flex-column flex-md-row card card-body p-10">
|
||||
<ul class="nav nav-tabs nav-pills flex-row border-0 flex-md-column me-5 mb-3 mb-md-0 fs-6 min-w-lg-200px">
|
||||
<li class="nav-item w-100 me-0 mb-md-2">
|
||||
<a href="/server/@(CurrentServer.Uuid)/" class="nav-link w-100 btn btn-flex @(Index == 0 ? "active" : "") btn-active-light-primary">
|
||||
<i class="bx bx-terminal bx-sm me-2"></i>
|
||||
<span class="d-flex flex-column align-items-start">
|
||||
<span class="fs-5"><TL>Console</TL></span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100 me-0 mb-md-2">
|
||||
<a href="/server/@(CurrentServer.Uuid)/files" class="nav-link w-100 btn btn-flex @(Index == 1 ? "active" : "") btn-active-light-primary">
|
||||
<i class="bx bx-folder bx-sm me-2"></i>
|
||||
<span class="d-flex flex-column align-items-start">
|
||||
<span class="fs-5"><TL>Files</TL></span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100 me-0 mb-md-2">
|
||||
<a href="/server/@(CurrentServer.Uuid)/backups" class="nav-link w-100 btn btn-flex @(Index == 2 ? "active" : "") btn-active-light-primary">
|
||||
<i class="bx bx-box bx-sm me-2"></i>
|
||||
<span class="d-flex flex-column align-items-start">
|
||||
<span class="fs-5"><TL>Backups</TL></span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100 me-0 mb-md-2">
|
||||
<a href="/server/@(CurrentServer.Uuid)/network" class="nav-link w-100 btn btn-flex @(Index == 3 ? "active" : "") btn-active-light-primary">
|
||||
<i class="bx bx-wifi bx-sm me-2"></i>
|
||||
<span class="d-flex flex-column align-items-start">
|
||||
<span class="fs-5"><TL>Network</TL></span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100 me-0 mb-md-2">
|
||||
<a href="/server/@(CurrentServer.Uuid)/plugins" class="nav-link w-100 btn btn-flex @(Index == 4 ? "active" : "") btn-active-light-primary">
|
||||
<i class="bx bx-plug bx-sm me-2"></i>
|
||||
<span class="d-flex flex-column align-items-start">
|
||||
<span class="fs-5"><TL>Plugins</TL></span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item w-100 me-0 mb-md-2">
|
||||
<a href="/server/@(CurrentServer.Uuid)/settings" class="nav-link w-100 btn btn-flex @(Index == 5 ? "active" : "") btn-active-light-primary">
|
||||
<i class="bx bx-cog bx-sm me-2"></i>
|
||||
<span class="d-flex flex-column align-items-start">
|
||||
<span class="fs-5"><TL>Settings</TL></span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content w-100">
|
||||
<div class="tab-pane fade show active">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public PteroConsole Console { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int Index { get; set; } = 0;
|
||||
|
||||
//TODO: NodeIpService which loads and caches raw ips for nodes (maybe)
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Console.OnServerStateUpdated += async (sender, state) => { await InvokeAsync(StateHasChanged); };
|
||||
Console.OnServerResourceUpdated += async (sender, x) => { await InvokeAsync(StateHasChanged); };
|
||||
}
|
||||
|
||||
#region Power Actions
|
||||
|
||||
private async Task Start()
|
||||
{
|
||||
await Console.SetPowerState("start");
|
||||
}
|
||||
|
||||
private async Task Stop()
|
||||
{
|
||||
await Console.SetPowerState("stop");
|
||||
}
|
||||
|
||||
private async Task Kill()
|
||||
{
|
||||
await Console.SetPowerState("kill");
|
||||
}
|
||||
|
||||
private async Task Restart()
|
||||
{
|
||||
await Console.SetPowerState("restart");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Database.Entities
|
||||
|
||||
@inject NodeRepository NodeRepository
|
||||
|
||||
@foreach (var allocation in CurrentServer.Allocations)
|
||||
{
|
||||
<div class="row align-items-center">
|
||||
<div class="col-1 me-4">
|
||||
<i class="text-primary bx bx-wifi bx-md"></i>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<span class="h4">
|
||||
@(CurrentServer.Node.Fqdn):@(allocation.Port)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-1 align-items-end">
|
||||
@if (allocation.Id == CurrentServer.MainAllocation.Id)
|
||||
{
|
||||
<button class="btn btn-primary">
|
||||
<TL>Primary</TL>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-danger">
|
||||
<TL>Delete</TL>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<div class="alert alert-primary d-flex rounded p-6">
|
||||
<div class="d-flex flex-stack flex-grow-1 flex-wrap flex-md-nowrap">
|
||||
<div class="mb-3 mb-md-0 fw-semibold">
|
||||
<h4 class="text-gray-900 fw-bold"><TL>Plugins</TL></h4>
|
||||
<div class="fs-6 text-gray-700 pe-7">
|
||||
<TL>This feature is currently not available</TL>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,51 @@
|
|||
@using PteroConsole.NET
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.Shared.Components.ServerControl.Settings
|
||||
|
||||
<div class="row mb-5">
|
||||
@if (Tags.Contains("paperversion"))
|
||||
{
|
||||
<PaperVersionSetting></PaperVersionSetting>
|
||||
}
|
||||
|
||||
@if (Tags.Contains("pythonversion"))
|
||||
{
|
||||
<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
|
||||
{
|
||||
[CascadingParameter]
|
||||
public PteroConsole Console { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public string[] Tags { get; set; }
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Database.Entities
|
||||
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject ImageRepository ImageRepository
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<label class="mb-2 form-label"><TL>Javascript Version</TL></label>
|
||||
<select class="mb-2 form-select" @bind="Image">
|
||||
@foreach (var image in Images)
|
||||
{
|
||||
if (image == Image)
|
||||
{
|
||||
<option value="@(image)" selected="">@(image)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(image)">@(image)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<WButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary"></WButton>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private string[] Images;
|
||||
private string Image;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
//TODO: Check if this is a redundant call
|
||||
var serverImage = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
|
||||
|
||||
var res = new List<string>();
|
||||
foreach (var image in serverImage.DockerImages)
|
||||
{
|
||||
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
|
||||
}
|
||||
Images = res.ToArray();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
var serverImage = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
var allImages = serverImage.DockerImages;
|
||||
var imageToUse = allImages.First(x => x.Name.EndsWith(Image));
|
||||
CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Database.Entities
|
||||
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<LazyLoader @ref="Loader" Load="Load">
|
||||
<div class="form-check form-check-custom form-check-solid mb-3">
|
||||
<input @bind="Value" class="form-check-input" type="checkbox" value="1" id="j2sCheck"/>
|
||||
<label class="form-check-label" for="j2sCheck">
|
||||
<TL>Join2Start</TL>
|
||||
</label>
|
||||
</div>
|
||||
<WButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary"></WButton>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private bool Value;
|
||||
|
||||
private LazyLoader Loader;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
Value = CurrentServer.Variables.First(x => x.Key == "J2S").Value == "1";
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
CurrentServer.Variables.First(x => x.Key == "J2S").Value = Value ? "1" : "0";
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
|
||||
await Loader.Reload();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Logging.Net
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
|
||||
@inject ServerService ServerService
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject ImageRepository ImageRepository
|
||||
@inject PaperService PaperService
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<LazyLoader Load="Load">
|
||||
<label class="mb-2 form-label"><TL>Minecraft version</TL></label>
|
||||
<select class="mb-2 form-select" @bind="InputVersion">
|
||||
@foreach (var version in Versions)
|
||||
{
|
||||
if (version == Version)
|
||||
{
|
||||
<option value="@(version)" selected="">@(version)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(version)">@(version)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<label class="mb-2 form-label"><TL>Build version</TL></label>
|
||||
<select class="mb-2 form-select" @bind="InputBuild">
|
||||
@foreach (var build in Builds)
|
||||
{
|
||||
if (build == Build)
|
||||
{
|
||||
<option value="@(build)" selected="">@(build)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(build)">@(build)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<WorkerButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary"></WorkerButton>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private string[] Versions;
|
||||
private string Version;
|
||||
|
||||
private string[] Builds;
|
||||
private string Build;
|
||||
|
||||
// Form
|
||||
|
||||
private string InputVersion
|
||||
{
|
||||
get { return Version; }
|
||||
set
|
||||
{
|
||||
Version = value;
|
||||
RefreshBuilds();
|
||||
Build = Builds.First();
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private string InputBuild
|
||||
{
|
||||
get { return Build; }
|
||||
set { Build = value; }
|
||||
}
|
||||
|
||||
private async Task RefreshVersions()
|
||||
{
|
||||
Versions = (await PaperService.GetVersions()).Reverse().ToArray();
|
||||
}
|
||||
|
||||
private async Task RefreshBuilds()
|
||||
{
|
||||
Builds = (await PaperService.GetBuilds(Version)).Reverse().ToArray();
|
||||
}
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
var vars = CurrentServer.Variables;
|
||||
|
||||
await RefreshVersions();
|
||||
|
||||
Version = vars.First(x => x.Key == "MINECRAFT_VERSION").Value;
|
||||
Build = vars.First(x => x.Key == "BUILD_NUMBER").Value;
|
||||
|
||||
if (string.IsNullOrEmpty(Version))
|
||||
Version = "latest";
|
||||
|
||||
if (string.IsNullOrEmpty(Build))
|
||||
Version = "latest";
|
||||
|
||||
if (Version == "latest") // Live migration
|
||||
{
|
||||
Version = Versions.First();
|
||||
|
||||
CurrentServer.Variables.First(x => x.Key == "MINECRAFT_VERSION").Value = Version;
|
||||
ServerRepository.Update(CurrentServer);
|
||||
}
|
||||
|
||||
await RefreshBuilds();
|
||||
|
||||
if (Build == "latest") // Live migration
|
||||
{
|
||||
Build = Builds.First();
|
||||
|
||||
CurrentServer.Variables.First(x => x.Key == "BUILD_NUMBER").Value = Build;
|
||||
ServerRepository.Update(CurrentServer);
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
CurrentServer.Variables.First(x => x.Key == "MINECRAFT_VERSION").Value = Version;
|
||||
CurrentServer.Variables.First(x => x.Key == "BUILD_NUMBER").Value = Build;
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
|
||||
var versionWithoutPre = Version.Split("-")[0];
|
||||
|
||||
if (versionWithoutPre.Count(x => x == "."[0]) == 1)
|
||||
versionWithoutPre += ".0";
|
||||
|
||||
var version = int.Parse(versionWithoutPre.Replace(".", ""));
|
||||
|
||||
var serverImage = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
var dockerImages = serverImage.DockerImages;
|
||||
|
||||
var dockerImageToUpdate = dockerImages.Last();
|
||||
|
||||
if (version < 1130)
|
||||
{
|
||||
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("8"));
|
||||
}
|
||||
|
||||
if (version >= 1130)
|
||||
{
|
||||
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("11"));
|
||||
}
|
||||
|
||||
if (version >= 1170)
|
||||
{
|
||||
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("16"));
|
||||
}
|
||||
|
||||
if (version >= 1190)
|
||||
{
|
||||
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("17"));
|
||||
}
|
||||
|
||||
CurrentServer.DockerImageIndex = dockerImages.IndexOf(dockerImageToUpdate);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
|
||||
await ServerService.Reinstall(CurrentServer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
@using Moonlight.App.Services
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.Shared.Components.Partials
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Database.Entities
|
||||
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject ImageRepository ImageRepository
|
||||
@inject SmartTranslateService TranslationService
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<label class="mb-2 form-label"><TL>Python version</TL></label>
|
||||
<select class="mb-2 form-select" @bind="Image">
|
||||
@foreach (var image in Images)
|
||||
{
|
||||
if (image == Image)
|
||||
{
|
||||
<option value="@(image)" selected="">@(image)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(image)">@(image)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<WButton
|
||||
OnClick="Save"
|
||||
Text="@(TranslationService.Translate("Change"))"
|
||||
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||
CssClasses="btn-primary"></WButton>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
private string[] Images;
|
||||
private string Image;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
var serverImage = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
Image = ParseHelper.FirstPartStartingWithNumber(serverImage.DockerImages.First(x => x.Id == CurrentServer.DockerImageIndex).Name);
|
||||
|
||||
var res = new List<string>();
|
||||
foreach (var image in serverImage.DockerImages)
|
||||
{
|
||||
res.Add(ParseHelper.FirstPartStartingWithNumber(image.Name));
|
||||
}
|
||||
Images = res.ToArray();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
var serverImage = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
var allImages = serverImage.DockerImages;
|
||||
var imageToUse = allImages.First(x => x.Name.EndsWith(Image));
|
||||
CurrentServer.DockerImageIndex = allImages.IndexOf(imageToUse);
|
||||
|
||||
ServerRepository.Update(CurrentServer);
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
55
Moonlight/Shared/Components/Xterm/Terminal.razor
Normal file
55
Moonlight/Shared/Components/Xterm/Terminal.razor
Normal file
|
@ -0,0 +1,55 @@
|
|||
@using XtermBlazor
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
<Xterm
|
||||
@ref="Xterm"
|
||||
Options="TerminalOptions"
|
||||
AddonIds="@(new[] { "xterm-addon-fit", "xterm-addon-search", "xterm-addon-web-links" })"
|
||||
OnFirstRender="OnFirstRender">
|
||||
</Xterm>
|
||||
|
||||
@code
|
||||
{
|
||||
private Xterm Xterm;
|
||||
|
||||
[Parameter]
|
||||
public Action RunOnFirstRender { get; set; }
|
||||
|
||||
private TerminalOptions TerminalOptions = new TerminalOptions
|
||||
{
|
||||
CursorBlink = false,
|
||||
CursorStyle = CursorStyle.Underline,
|
||||
CursorWidth = 1,
|
||||
DisableStdin = true,
|
||||
FontFamily = "monospace"
|
||||
};
|
||||
|
||||
public async Task WriteLine(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Xterm.WriteLine(message);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async void Dispose()
|
||||
{
|
||||
await Xterm.DisposeAsync();
|
||||
}
|
||||
|
||||
private async void OnFirstRender()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Xterm.InvokeAddonFunctionVoidAsync("xterm-addon-fit", "fit");
|
||||
RunOnFirstRender.Invoke();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,13 +2,15 @@
|
|||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using BlazorTable
|
||||
|
||||
@inject NodeRepository NodeRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
<LazyLoader Load="Load" @ref="LazyLoader">
|
||||
@if (Node == null)
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
|
@ -17,49 +19,50 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex flex-center">
|
||||
<div class="d-flex">
|
||||
<div class="flex-column">
|
||||
<div class="card rounded-3 w-md-550px">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-center flex-column-fluid">
|
||||
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework">
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Nodename</TL>
|
||||
</label>
|
||||
<input @bind="Node.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>FQDN</TL>
|
||||
</label>
|
||||
<input @bind="Node.Fqdn" type="text" placeholder="@(SmartTranslateService.Translate("FQDN"))" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Token Id</TL>
|
||||
</label>
|
||||
<input @bind="Node.TokenId" type="text" placeholder="@(SmartTranslateService.Translate("Toekn Id"))" class="form-control bg-transparent">
|
||||
<input @bind="Node.TokenId" type="text" placeholder="@(SmartTranslateService.Translate("Token Id"))" class="blur-unless-hover form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Token</TL>
|
||||
</label>
|
||||
<input @bind="Node.Token" type="text" placeholder="@(SmartTranslateService.Translate("Token"))" class="form-control bg-transparent">
|
||||
<input @bind="Node.Token" type="text" placeholder="@(SmartTranslateService.Translate("Token"))" class="blur-unless-hover form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Http port</TL>
|
||||
</label>
|
||||
<input @bind="Node.HttpPort" type="number" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Sftp port</TL>
|
||||
</label>
|
||||
<input @bind="Node.SftpPort" type="number" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Moonlight daemon port</TL>
|
||||
</label>
|
||||
<input @bind="Node.MoonlightDaemonPort" type="number" class="form-control bg-transparent">
|
||||
|
@ -80,12 +83,48 @@
|
|||
<div class="fv-row mb-9">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||
CssClasses="btn-primary"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Save">
|
||||
</WButton>
|
||||
<a href="/admin/nodes" class="btn btn-primary">
|
||||
<TL>Back</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-column w-100">
|
||||
<div class="ms-5 card card-body">
|
||||
<div class="form w-100">
|
||||
<div class="mb-8 row g-3">
|
||||
<div class="col-auto">
|
||||
<input @bind="Port" type="number" class="col-auto form-control bg-transparent">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Add"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Adding"))"
|
||||
CssClasses="col-auto btn-success"
|
||||
OnClick="CreateAllocation">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table TableItem="NodeAllocation" Items="Node.Allocations" PageSize="25" TableHeadClass="border-bottom border-gray-200 fs-6 text-gray-600 fw-bold bg-light bg-opacity-75">
|
||||
<Column TableItem="NodeAllocation" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="NodeAllocation" Title="@(SmartTranslateService.Translate("Port"))" Field="@(x => x.Port)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="NodeAllocation" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="20%">
|
||||
<Template>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-danger"
|
||||
OnClick="() => DeleteAllocation(context)">
|
||||
</WButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -99,17 +138,49 @@
|
|||
public int Id { get; set; }
|
||||
|
||||
private Node? Node;
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
private int Port = 2000;
|
||||
|
||||
private Task Load(LazyLoader arg)
|
||||
{
|
||||
Node = NodeRepository.Get().FirstOrDefault(x => x.Id == Id);
|
||||
Node = NodeRepository
|
||||
.Get()
|
||||
.Include(x => x.Allocations)
|
||||
.FirstOrDefault(x => x.Id == Id);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Save()
|
||||
{
|
||||
NodeRepository.Update(Node);
|
||||
NodeRepository.Update(Node!);
|
||||
NavigationManager.NavigateTo("/admin/nodes");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task DeleteAllocation(NodeAllocation nodeAllocation)
|
||||
{
|
||||
//TODO: Check if a server is using the allocation
|
||||
Node!.Allocations.Remove(nodeAllocation);
|
||||
NodeRepository.Update(Node);
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
|
||||
private async Task CreateAllocation()
|
||||
{
|
||||
var nodeAllocation = new NodeAllocation()
|
||||
{
|
||||
Port = Port
|
||||
};
|
||||
|
||||
Node!.Allocations.Add(nodeAllocation);
|
||||
NodeRepository.Update(Node);
|
||||
|
||||
Port = 2000;
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
|
@ -3,24 +3,33 @@
|
|||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Models.Node
|
||||
@using Moonlight.App.Models.Wings.Resources
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using Logging.Net
|
||||
|
||||
@inject NodeRepository NodeRepository
|
||||
@inject AlertService AlertService
|
||||
@inject NodeService NodeService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body">
|
||||
<a class="btn btn-primary" href="/admin/nodes/new"><TL>Add a new node</TL></a>
|
||||
<a class="btn btn-primary" href="/admin/nodes/new">
|
||||
<TL>Add a new node</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<LazyLoader Load="Load">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (!Nodes.Any())
|
||||
{
|
||||
<div class="card card-body">
|
||||
<div class="alert alert-info">
|
||||
<TL>No nodes found. Start with adding a new node</TL>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -34,6 +43,32 @@
|
|||
</h4>
|
||||
</div>
|
||||
<div class="card-body pt-6">
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="d-flex align-items-center me-3">
|
||||
<div class="flex-grow-1">
|
||||
<span class="text-gray-800 fs-5 fw-bold lh-0">
|
||||
<TL>Status</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center w-100 mw-125px">
|
||||
<span class="text-white-400 fw-semibold">
|
||||
@{
|
||||
var ss = StatusCache.ContainsKey(node) ? StatusCache[node] : null;
|
||||
}
|
||||
|
||||
@if (ss == null)
|
||||
{
|
||||
<span class="text-danger">Offline</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-success">Online (@(ss.Version))</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator separator-dashed my-3"></div>
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="d-flex align-items-center me-3">
|
||||
<div class="flex-grow-1">
|
||||
|
@ -126,10 +161,14 @@
|
|||
<a class="btn btn-primary" href="/admin/nodes/edit/@(node.Id)">
|
||||
<TL>Edit</TL>
|
||||
</a>
|
||||
<a class="btn btn-success" href="/admin/nodes/setup/@(node.Id)">
|
||||
<TL>Setup</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-danger"
|
||||
OnClick="() => Delete(node)"></WButton>
|
||||
OnClick="() => Delete(node)">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -145,21 +184,80 @@
|
|||
{
|
||||
private Node[] Nodes;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private Dictionary<Node, CpuStats> CpuCache = new();
|
||||
private Dictionary<Node, MemoryStats> MemoryCache = new();
|
||||
private Dictionary<Node, DiskStats> DiskCache = new();
|
||||
private Dictionary<Node, SystemStatus?> StatusCache = new();
|
||||
|
||||
private Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
CpuCache.Clear();
|
||||
MemoryCache.Clear();
|
||||
DiskCache.Clear();
|
||||
|
||||
lock (StatusCache)
|
||||
{
|
||||
StatusCache.Clear();
|
||||
}
|
||||
|
||||
Nodes = NodeRepository.Get().ToArray();
|
||||
|
||||
Task.Run(() => { });
|
||||
Task.Run(() =>
|
||||
{
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var ss = await NodeService.GetStatus(node);
|
||||
|
||||
lock (StatusCache)
|
||||
{
|
||||
StatusCache.Add(node, ss);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Debug(e.Message);
|
||||
|
||||
lock (StatusCache)
|
||||
{
|
||||
StatusCache.Add(node, null);
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Delete(Node node)
|
||||
private async Task Delete(Node node)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
var b = await AlertService.YesNo(
|
||||
SmartTranslateService.Translate("Delete this node?"),
|
||||
SmartTranslateService.Translate("Do you really want to delete this node"),
|
||||
SmartTranslateService.Translate("Yes"),
|
||||
SmartTranslateService.Translate("No")
|
||||
);
|
||||
|
||||
if (b)
|
||||
{
|
||||
if (node.Allocations.Any())
|
||||
await AlertService.Error(
|
||||
SmartTranslateService.Translate("Error"),
|
||||
SmartTranslateService.Translate("Delete all allocations before deleting the node")
|
||||
);
|
||||
else
|
||||
{
|
||||
NodeRepository.Delete(node);
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,31 +16,31 @@
|
|||
<div class="d-flex flex-center flex-column-fluid">
|
||||
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework">
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Nodename</TL>
|
||||
</label>
|
||||
<input @bind="NewNode.Name" type="text" placeholder="@(SmartTranslateService.Translate("Nodename"))" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>FQDN</TL>
|
||||
</label>
|
||||
<input @bind="NewNode.Fqdn" type="text" placeholder="@(SmartTranslateService.Translate("FQDN"))" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Http port</TL>
|
||||
</label>
|
||||
<input @bind="NewNode.HttpPort" type="number" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Sftp port</TL>
|
||||
</label>
|
||||
<input @bind="NewNode.SftpPort" type="number" class="form-control bg-transparent">
|
||||
</div>
|
||||
<div class="fv-row mb-8">
|
||||
<label>
|
||||
<label class="form-label">
|
||||
<TL>Moonlight daemon port</TL>
|
||||
</label>
|
||||
<input @bind="NewNode.MoonlightDaemonPort" type="number" class="form-control bg-transparent">
|
||||
|
@ -61,9 +61,12 @@
|
|||
<div class="fv-row mb-9">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||
CssClasses="btn-primary"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Create">
|
||||
</WButton>
|
||||
<a href="/admin/nodes" class="btn btn-primary">
|
||||
<TL>Back</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
159
Moonlight/Shared/Views/Admin/Nodes/Setup.razor
Normal file
159
Moonlight/Shared/Views/Admin/Nodes/Setup.razor
Normal file
|
@ -0,0 +1,159 @@
|
|||
@page "/admin/nodes/setup/{id:int}"
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject NodeRepository NodeRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject ConfigService ConfigService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@{
|
||||
var appUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||
}
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
@if (Node == null)
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
<TL>No node with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="">
|
||||
<div class="card rounded-3">
|
||||
<div class="card-body">
|
||||
<p class="fs-5">
|
||||
<TL>Before configuring this node, install the daemon</TL><br/>
|
||||
<a href="https://endelon-hosting.gitbook.io/moonlight-documentation/install-the-daemon">How to install the daemon</a><br/>
|
||||
<TL>Open a ssh connection to your node and enter</TL><br/>
|
||||
<span class="fw-bold">nano /etc/pterodactyl/config.yml</span><br/>
|
||||
<TL>and paste the config below. Then press STRG+O and STRG+X to save</TL>
|
||||
</p>
|
||||
<p class="mb-5 bg-light">
|
||||
debug: false<br>
|
||||
app_name: Moonlight<br>
|
||||
uuid: @(Guid.NewGuid())<br/>
|
||||
token_id: @(Node.TokenId)<br/>
|
||||
token: @(Node.Token)<br/>
|
||||
api:<br/>
|
||||
host: 0.0.0.0<br/>
|
||||
port: @(Node.HttpPort)<br/>
|
||||
ssl:<br/>
|
||||
enabled: false<br/>
|
||||
cert: /etc/letsencrypt/live/@(Node.Fqdn)/fullchain.pem<br/>
|
||||
key: /etc/letsencrypt/live/@(Node.Fqdn)/privkey.pem<br/>
|
||||
disable_remote_download: false<br/>
|
||||
upload_limit: 100<br/>
|
||||
trusted_proxies: []<br/>
|
||||
system:<br/>
|
||||
root_directory: /var/lib/pterodactyl<br/>
|
||||
log_directory: /var/log/pterodactyl<br/>
|
||||
data: /var/lib/pterodactyl/volumes<br/>
|
||||
archive_directory: /var/lib/pterodactyl/archives<br/>
|
||||
backup_directory: /var/lib/pterodactyl/backups<br/>
|
||||
tmp_directory: /tmp/pterodactyl<br/>
|
||||
username: pterodactyl<br/>
|
||||
timezone: Europe/Berlin<br/>
|
||||
user:<br/>
|
||||
rootless:<br/>
|
||||
enabled: false<br/>
|
||||
container_uid: 0<br/>
|
||||
container_gid: 0<br/>
|
||||
uid: 999<br/>
|
||||
gid: 999<br/>
|
||||
disk_check_interval: 150<br/>
|
||||
activity_send_interval: 60<br/>
|
||||
activity_send_count: 100<br/>
|
||||
check_permissions_on_boot: true<br/>
|
||||
enable_log_rotate: true<br/>
|
||||
websocket_log_count: 150<br/>
|
||||
sftp:<br/>
|
||||
bind_address: 0.0.0.0<br/>
|
||||
bind_port: @(Node.SftpPort)<br/>
|
||||
read_only: false<br/>
|
||||
crash_detection:<br/>
|
||||
enabled: true<br/>
|
||||
detect_clean_exit_as_crash: true<br/>
|
||||
timeout: 60<br/>
|
||||
backups:<br/>
|
||||
write_limit: 0<br/>
|
||||
compression_level: best_speed<br/>
|
||||
transfers:<br/>
|
||||
download_limit: 0<br/>
|
||||
docker:<br/>
|
||||
network:<br/>
|
||||
interface: 172.18.0.1<br/>
|
||||
dns:<br/>
|
||||
- 1.1.1.1<br/>
|
||||
- 1.0.0.1<br/>
|
||||
name: pterodactyl_nw<br/>
|
||||
ispn: false<br/>
|
||||
driver: bridge<br/>
|
||||
network_mode: pterodactyl_nw<br/>
|
||||
is_internal: false<br/>
|
||||
enable_icc: true<br/>
|
||||
network_mtu: 1500<br/>
|
||||
interfaces:<br/>
|
||||
v4:<br/>
|
||||
subnet: 172.18.0.0/16<br/>
|
||||
gateway: 172.18.0.1<br/>
|
||||
v6:<br/>
|
||||
subnet: fdba:17c8:6c94::/64<br/>
|
||||
gateway: fdba:17c8:6c94::1011<br/>
|
||||
domainname: ""<br/>
|
||||
registries: {}<br/>
|
||||
tmpfs_size: 100<br/>
|
||||
container_pid_limit: 512<br/>
|
||||
installer_limits:<br/>
|
||||
memory: 1024<br/>
|
||||
cpu: 100<br/>
|
||||
overhead:<br/>
|
||||
override: false<br/>
|
||||
default_multiplier: 1.05<br/>
|
||||
multipliers: {}<br/>
|
||||
use_performant_inspect: true<br/>
|
||||
userns_mode: ""<br/>
|
||||
log_config:<br/>
|
||||
type: local<br/>
|
||||
config:<br/>
|
||||
compress: "false"<br/>
|
||||
max-file: "1"<br/>
|
||||
max-size: 5m<br/>
|
||||
mode: non-blocking<br/>
|
||||
throttles:<br/>
|
||||
enabled: true<br/>
|
||||
lines: 2000<br/>
|
||||
line_reset_interval: 100<br/>
|
||||
remote: @(appUrl)<br/>
|
||||
remote_query:<br/>
|
||||
timeout: 30<br/>
|
||||
boot_servers_per_page: 50<br/>
|
||||
allowed_mounts: []<br/>
|
||||
allowed_origins:<br/>
|
||||
- '*'
|
||||
</p>
|
||||
<a href="/admin/nodes" class="btn btn-primary">
|
||||
<TL>Back</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
private Node? Node;
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
{
|
||||
Node = NodeRepository.Get().FirstOrDefault(x => x.Id == Id);
|
||||
}
|
||||
}
|
198
Moonlight/Shared/Views/Admin/Servers/Edit.razor
Normal file
198
Moonlight/Shared/Views/Admin/Servers/Edit.razor
Normal file
|
@ -0,0 +1,198 @@
|
|||
@page "/admin/servers/edit/{id:int}"
|
||||
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Repositories
|
||||
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject ImageRepository ImageRepository
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (Server == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No server with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Identifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-id-card"></i>
|
||||
</span>
|
||||
<input @bind="Server.Id" type="number" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>UuidIdentifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-id-card"></i>
|
||||
</span>
|
||||
<input @bind="Server.Uuid" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Server name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-purchase-tag-alt"></i>
|
||||
</span>
|
||||
<input @bind="Server.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))" aria-label="Servername">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Cpu cores</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-chip"></i>
|
||||
</span>
|
||||
<input @bind="Server.Cpu" type="number" class="form-control">
|
||||
<span class="input-group-text">
|
||||
<TL>CPU Cores (100% = 1 Core)</TL>
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Memory</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-microchip"></i>
|
||||
</span>
|
||||
<input @bind="Server.Memory" type="number" class="form-control">
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Disk</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-hdd"></i>
|
||||
</span>
|
||||
<input @bind="Server.Disk" type="number" class="form-control">
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Override startup command</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-terminal"></i>
|
||||
</span>
|
||||
<input @bind="Server.OverrideStartup" type="text" class="form-control" placeholder="@(Server.Image.Startup)">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Docker image</TL>
|
||||
</label>
|
||||
<select @bind="Server.DockerImageIndex" class="form-select">
|
||||
@foreach (var image in DockerImages)
|
||||
{
|
||||
<option value="@(DockerImages.IndexOf(image))">@(image.Name)</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
@foreach (var vars in Server.Variables.Chunk(4))
|
||||
{
|
||||
<div class="row mb-3">
|
||||
@foreach (var variable in vars)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Value</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-primary" href="/admin/servers">Back</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Save">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private Server? Server;
|
||||
private List<DockerImage> DockerImages;
|
||||
private List<Image> Images;
|
||||
|
||||
private Task Load(LazyLoader arg)
|
||||
{
|
||||
Server = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Variables)
|
||||
.FirstOrDefault(x => x.Id == Id);
|
||||
|
||||
if (Server != null)
|
||||
{
|
||||
Images = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.DockerImages)
|
||||
.ToList();
|
||||
|
||||
DockerImages = Images
|
||||
.First(x => x.Id == Server.Image.Id).DockerImages
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
ServerRepository.Update(Server);
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
69
Moonlight/Shared/Views/Admin/Servers/Index.razor
Normal file
69
Moonlight/Shared/Views/Admin/Servers/Index.razor
Normal file
|
@ -0,0 +1,69 @@
|
|||
@page "/admin/servers"
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using BlazorTable
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body">
|
||||
<a href="/admin/servers/new" class="btn btn-success">
|
||||
<TL>Create new server</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card card-body">
|
||||
@if (Servers.Any())
|
||||
{
|
||||
<Table TableItem="Server" Items="Servers" PageSize="25" TableHeadClass="border-bottom border-gray-200 fs-6 text-gray-600 fw-bold bg-light bg-opacity-75">
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="20%">
|
||||
<Template>
|
||||
<a href="/admin/users/@(context.Owner.Id)/">@context.Owner.Email</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="20%">
|
||||
<Template>
|
||||
<a href="/admin/servers/edit/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Manage"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No servers found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
private Server[] Servers;
|
||||
|
||||
private Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
Servers = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Owner)
|
||||
.ToArray();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
300
Moonlight/Shared/Views/Admin/Servers/New.razor
Normal file
300
Moonlight/Shared/Views/Admin/Servers/New.razor
Normal file
|
@ -0,0 +1,300 @@
|
|||
@page "/admin/servers/new"
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Exceptions
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using Logging.Net
|
||||
@using Blazored.Typeahead
|
||||
|
||||
@inject NodeRepository NodeRepository
|
||||
@inject ImageRepository ImageRepository
|
||||
@inject ServerService ServerService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject UserRepository UserRepository
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Server name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-purchase-tag-alt"></i>
|
||||
</span>
|
||||
<input @bind="Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))" aria-label="Servername">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Server name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<div class="form-select">
|
||||
<BlazoredTypeahead SearchMethod="SearchUsers"
|
||||
@bind-Value="User">
|
||||
<SelectedTemplate>
|
||||
@(context.Email)
|
||||
</SelectedTemplate>
|
||||
<ResultTemplate>
|
||||
@(context.Email)
|
||||
</ResultTemplate>
|
||||
</BlazoredTypeahead>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Cpu cores</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-chip"></i>
|
||||
</span>
|
||||
<input @bind="Cpu" type="number" class="form-control">
|
||||
<span class="input-group-text">
|
||||
<TL>CPU Cores (100% = 1 Core)</TL>
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Memory</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-microchip"></i>
|
||||
</span>
|
||||
<input @bind="Memory" type="number" class="form-control">
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Disk</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-hdd"></i>
|
||||
</span>
|
||||
<input @bind="Disk" type="number" class="form-control">
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Image</TL>
|
||||
</label>
|
||||
<select @bind="ImageIndex" class="form-select mb-5">
|
||||
@foreach (var image in Images)
|
||||
{
|
||||
<option value="@(Images.IndexOf(image))">@(image.Name)</option>
|
||||
}
|
||||
</select>
|
||||
@if (Image != null)
|
||||
{
|
||||
<label class="form-label">
|
||||
<TL>Override startup</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-terminal"></i>
|
||||
</span>
|
||||
<input @bind="OverrideStartup" type="text" class="form-control" placeholder="@(Image.Startup)">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Docker image</TL>
|
||||
</label>
|
||||
<select @bind="DockerImageIndex" class="form-select">
|
||||
@foreach (var image in Image.DockerImages)
|
||||
{
|
||||
<option value="@(Image.DockerImages.IndexOf(image))">@(image.Name)</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Image != null)
|
||||
{
|
||||
<div class="mt-9 row d-flex">
|
||||
@foreach (var vars in ServerVariables.Chunk(4))
|
||||
{
|
||||
<div class="row mb-3">
|
||||
@foreach (var variable in vars)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<label class="form-label"><TL>Name</TL></label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label"><TL>Value</TL></label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-primary" href="/admin/servers">
|
||||
<TL>Back</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Create">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
private List<Image> Images;
|
||||
private Node[] Nodes;
|
||||
private User[] Users;
|
||||
|
||||
private string Name = "";
|
||||
|
||||
private int Cpu = 100;
|
||||
private int Memory = 4096;
|
||||
private int Disk = 10240;
|
||||
|
||||
private string OverrideStartup = "";
|
||||
private int DockerImageIndex = 0;
|
||||
|
||||
private Image? Image;
|
||||
private User? User;
|
||||
|
||||
private ServerVariable[] ServerVariables = Array.Empty<ServerVariable>();
|
||||
|
||||
private int ImageIndex
|
||||
{
|
||||
get => Image == null ? 0 : Images.IndexOf(Image);
|
||||
set
|
||||
{
|
||||
Image = Images[value];
|
||||
|
||||
if (Image == null)
|
||||
ServerVariables = Array.Empty<ServerVariable>();
|
||||
else
|
||||
RebuildVariables();
|
||||
|
||||
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildVariables()
|
||||
{
|
||||
var list = new List<ServerVariable>();
|
||||
|
||||
foreach (var variable in Image.Variables)
|
||||
{
|
||||
list.Add(new()
|
||||
{
|
||||
Key = variable.Key,
|
||||
Value = variable.DefaultValue
|
||||
});
|
||||
}
|
||||
|
||||
ServerVariables = list.ToArray();
|
||||
}
|
||||
|
||||
private Task<IEnumerable<User>> SearchUsers(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return Task.FromResult(Array.Empty<User>().Cast<User>());
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(Users.Where(x => x.Email.ToLower().StartsWith(input)));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
await lazyLoader.SetText("Loading images");
|
||||
|
||||
Images = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.DockerImages)
|
||||
.ToList();
|
||||
|
||||
await lazyLoader.SetText("Loading nodes");
|
||||
|
||||
Nodes = NodeRepository.Get().ToArray();
|
||||
|
||||
await lazyLoader.SetText("Loading users");
|
||||
|
||||
Users = UserRepository.Get().ToArray();
|
||||
User = Users.FirstOrDefault();
|
||||
|
||||
Image = Images.FirstOrDefault();
|
||||
|
||||
RebuildVariables();
|
||||
|
||||
if (Image != null)
|
||||
DockerImageIndex = Image.DockerImages.Count - 1;
|
||||
}
|
||||
|
||||
private async Task Create()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ServerService.Create(Name, Cpu, Memory, Disk, User, Image, null, server =>
|
||||
{
|
||||
server.OverrideStartup = OverrideStartup;
|
||||
server.DockerImageIndex = DockerImageIndex;
|
||||
|
||||
foreach (var serverVariable in ServerVariables)
|
||||
{
|
||||
server.Variables
|
||||
.First(x => x.Key == serverVariable.Key).Value = serverVariable.Value;
|
||||
}
|
||||
});
|
||||
|
||||
await ToastService.Success(SmartTranslateService.Translate("Server successfully created"));
|
||||
NavigationManager.NavigateTo("/admin/servers");
|
||||
}
|
||||
catch (DisplayException e)
|
||||
{
|
||||
await AlertService.Error(
|
||||
SmartTranslateService.Translate("Error"),
|
||||
SmartTranslateService.Translate(e.Message)
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("Error creating server");
|
||||
Logger.Error(e);
|
||||
|
||||
await AlertService.Error(
|
||||
SmartTranslateService.Translate("Error"),
|
||||
SmartTranslateService.Translate("An unknown error occured")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
@page "/"
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Services.Sessions
|
||||
|
||||
@inject DatabaseRepository DatabaseRepository
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<div class="row mb-5">
|
||||
|
@ -222,11 +225,18 @@
|
|||
private int DatabaseCount = 0;
|
||||
private int ServerCount = 0;
|
||||
|
||||
private Task Load(LazyLoader lazyLoader)
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
DatabaseCount = DatabaseRepository.Get().Count();
|
||||
ServerCount = ServerRepository.Get().Count();
|
||||
var user = await IdentityService.Get();
|
||||
|
||||
return Task.CompletedTask;
|
||||
DatabaseCount = DatabaseRepository
|
||||
.Get()
|
||||
.Include(x => x.Owner)
|
||||
.Count(x => x.Owner.Id == user.Id);
|
||||
|
||||
ServerCount = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Owner)
|
||||
.Count(x => x.Owner.Id == user.Id);
|
||||
}
|
||||
}
|
215
Moonlight/Shared/Views/Server/Index.razor
Normal file
215
Moonlight/Shared/Views/Server/Index.razor
Normal file
|
@ -0,0 +1,215 @@
|
|||
@page "/server/{ServerUuid}/{Route?}"
|
||||
@using PteroConsole.NET
|
||||
@using Task = System.Threading.Tasks.Task
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using PteroConsole.NET.Enums
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using Moonlight.Shared.Components.Xterm
|
||||
@using Moonlight.Shared.Components.ServerControl
|
||||
|
||||
@inject ImageRepository ImageRepository
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject WingsConsoleHelper WingsConsoleHelper
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
<LazyLoader Load="LoadData">
|
||||
@if (CurrentServer == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>Server not found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Console.ConnectionState == ConnectionState.Connected)
|
||||
{
|
||||
if (Console.ServerState == ServerState.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
|
||||
{
|
||||
<CascadingValue Value="Console">
|
||||
<CascadingValue Value="CurrentServer">
|
||||
<CascadingValue Value="Tags">
|
||||
<CascadingValue Value="Node">
|
||||
<CascadingValue Value="Image">
|
||||
<CascadingValue Value="NodeAllocation">
|
||||
@{
|
||||
var index = 0;
|
||||
|
||||
switch (Route)
|
||||
{
|
||||
case "files":
|
||||
index = 1;
|
||||
break;
|
||||
case "backups":
|
||||
index = 2;
|
||||
break;
|
||||
case "network":
|
||||
index = 3;
|
||||
break;
|
||||
case "plugins":
|
||||
index = 4;
|
||||
break;
|
||||
case "settings":
|
||||
index = 5;
|
||||
break;
|
||||
default:
|
||||
index = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
<ServerNavigation Index="index">
|
||||
@switch (Route)
|
||||
{
|
||||
case "files":
|
||||
<ServerFiles></ServerFiles>
|
||||
break;
|
||||
case "backups":
|
||||
<ServerBackups></ServerBackups>
|
||||
break;
|
||||
case "network":
|
||||
<ServerNetwork></ServerNetwork>
|
||||
break;
|
||||
case "plugins":
|
||||
<ServerPlugins></ServerPlugins>
|
||||
break;
|
||||
case "settings":
|
||||
<ServerSettings></ServerSettings>
|
||||
break;
|
||||
default:
|
||||
<ServerConsole></ServerConsole>
|
||||
break;
|
||||
}
|
||||
</ServerNavigation>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>Connecting</TL>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
||||
[Parameter]
|
||||
public string ServerUuid { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Route { get; set; }
|
||||
|
||||
private PteroConsole? Console;
|
||||
private Server? CurrentServer;
|
||||
private Node Node;
|
||||
private Image Image;
|
||||
private NodeAllocation NodeAllocation;
|
||||
private string[] Tags;
|
||||
|
||||
private Terminal? InstallConsole;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Console = new();
|
||||
|
||||
Console.OnConnectionStateUpdated += (_, _) => { InvokeAsync(StateHasChanged); };
|
||||
Console.OnServerResourceUpdated += async (_, _) => { await InvokeAsync(StateHasChanged); };
|
||||
Console.OnServerStateUpdated += async (_, _) => { await InvokeAsync(StateHasChanged); };
|
||||
|
||||
Console.RequestToken += (_) => WingsConsoleHelper.GenerateToken(CurrentServer!);
|
||||
|
||||
Console.OnMessage += async (_, s) =>
|
||||
{
|
||||
if (Console.ServerState == ServerState.Installing)
|
||||
{
|
||||
if (InstallConsole != null)
|
||||
{
|
||||
await InstallConsole.WriteLine(s);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task LoadData(LazyLoader lazyLoader)
|
||||
{
|
||||
await lazyLoader.SetText("Requesting server data");
|
||||
|
||||
try
|
||||
{
|
||||
var uuid = Guid.Parse(ServerUuid);
|
||||
|
||||
var user = await IdentityService.Get();
|
||||
|
||||
CurrentServer = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Image)
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.MainAllocation)
|
||||
.Include(x => x.Owner)
|
||||
.First(x => x.Uuid == uuid);
|
||||
|
||||
if (CurrentServer.Owner.Id != user!.Id)
|
||||
CurrentServer = null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (CurrentServer != null)
|
||||
{
|
||||
await lazyLoader.SetText("Requesting tags");
|
||||
|
||||
var tags = new List<string>();
|
||||
var image = ImageRepository
|
||||
.Get()
|
||||
.Include(x => x.Tags)
|
||||
.First(x => x.Id == CurrentServer.Image.Id);
|
||||
|
||||
foreach (var tag in image.Tags)
|
||||
{
|
||||
tags.Add(tag.Name);
|
||||
}
|
||||
|
||||
Tags = tags.ToArray();
|
||||
Image = image;
|
||||
|
||||
await lazyLoader.SetText("Connecting to console");
|
||||
|
||||
await WingsConsoleHelper.ConnectWings(Console!, CurrentServer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Server is null");
|
||||
}
|
||||
}
|
||||
}
|
127
Moonlight/Shared/Views/Servers.razor
Normal file
127
Moonlight/Shared/Views/Servers.razor
Normal file
|
@ -0,0 +1,127 @@
|
|||
@page "/servers"
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject IdentityService IdentityService
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject IServiceScopeFactory ServiceScopeFactory
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
@if (AllServers.Any())
|
||||
{
|
||||
@foreach (var server in AllServers)
|
||||
{
|
||||
<div class="row px-5 mb-5">
|
||||
<a class="card card-body" href="/server/@(server.Uuid)">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="symbol symbol-50px me-3">
|
||||
<i class="bx bx-md bx-server"></i>
|
||||
</div>
|
||||
<div class="d-flex justify-content-start flex-column">
|
||||
<a href="/server/@(server.Uuid)" class="text-gray-800 text-hover-primary mb-1 fs-5">
|
||||
@(server.Name)
|
||||
</a>
|
||||
<span class="text-gray-400 fw-semibold d-block fs-6">
|
||||
@(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) <span class="text-gray-700">- @(server.Image.Name)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-sm-block col my-auto fs-6">
|
||||
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
||||
</div>
|
||||
<div class="d-none d-sm-block col my-auto fs-6">
|
||||
@if (StatusCache.ContainsKey(server))
|
||||
{
|
||||
var status = StatusCache[server];
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case "offline":
|
||||
<span class="text-danger"><TL>Offline</TL></span>
|
||||
break;
|
||||
case "stopping":
|
||||
<span class="text-warning"><TL>Stopping</TL></span>
|
||||
break;
|
||||
case "starting":
|
||||
<span class="text-warning"><TL>Starting</TL></span>
|
||||
break;
|
||||
case "running":
|
||||
<span class="text-success"><TL>Running</TL></span>
|
||||
break;
|
||||
case "failed":
|
||||
<span class="text-gray-400"><TL>Failed</TL></span>
|
||||
break;
|
||||
default:
|
||||
<span class="text-danger"><TL>Offline</TL></span>
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-gray-400"><TL>Loading</TL></span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
No servers found
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
private App.Database.Entities.Server[] AllServers;
|
||||
private readonly Dictionary<App.Database.Entities.Server, string> StatusCache = new();
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
{
|
||||
var user = await IdentityService.Get();
|
||||
|
||||
AllServers = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Owner)
|
||||
.Include(x => x.MainAllocation)
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Image)
|
||||
.Where(x => x.Owner.Id == user.Id)
|
||||
.ToArray();
|
||||
|
||||
foreach (var server in AllServers)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
||||
|
||||
AddStatus(server, (await serverService.GetDetails(server)).State);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AddStatus(server, "failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void AddStatus(App.Database.Entities.Server server, string status)
|
||||
{
|
||||
lock (StatusCache)
|
||||
{
|
||||
StatusCache.Add(server, status);
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// Dieser Code wurde von einem Tool generiert.
|
||||
// Laufzeitversion:4.0.30319.42000
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
||||
// der Code erneut generiert wird.
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -51,6 +51,14 @@ build_metadata.AdditionalFiles.CssScope =
|
|||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRXJyb3JCb3VuZGFyaWVzXFBhZ2VFcnJvckJvdW5kYXJ5LnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/FileManagerPartials/FileEditor.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRmlsZU1hbmFnZXJQYXJ0aWFsc1xGaWxlRWRpdG9yLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRmlsZU1hbmFnZXJQYXJ0aWFsc1xGaWxlTWFuYWdlci5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/Forms/WButton.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRm9ybXNcV0J1dHRvbi5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
@ -87,6 +95,50 @@ build_metadata.AdditionalFiles.CssScope =
|
|||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcUGFydGlhbHNcVEwucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/ServerBackups.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXJ2ZXJCYWNrdXBzLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/ServerConsole.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXJ2ZXJDb25zb2xlLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/ServerFiles.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXJ2ZXJGaWxlcy5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/ServerNavigation.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXJ2ZXJOYXZpZ2F0aW9uLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/ServerNetwork.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXJ2ZXJOZXR3b3JrLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/ServerPlugins.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXJ2ZXJQbHVnaW5zLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/ServerSettings.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXJ2ZXJTZXR0aW5ncy5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/Settings/JavascriptVersionSetting.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXR0aW5nc1xKYXZhc2NyaXB0VmVyc2lvblNldHRpbmcucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/Settings/Join2StartSetting.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXR0aW5nc1xKb2luMlN0YXJ0U2V0dGluZy5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/Settings/PaperVersionSetting.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXR0aW5nc1xQYXBlclZlcnNpb25TZXR0aW5nLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/ServerControl/Settings/PythonVersionSetting.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU2VydmVyQ29udHJvbFxTZXR0aW5nc1xQeXRob25WZXJzaW9uU2V0dGluZy5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/StateLogic/IsSetup.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU3RhdGVMb2dpY1xJc1NldHVwLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
@ -99,6 +151,10 @@ build_metadata.AdditionalFiles.CssScope =
|
|||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcU3RhdGVMb2dpY1xPbmx5QWRtaW4ucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Components/Xterm/Terminal.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcWHRlcm1cVGVybWluYWwucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Layouts/MainLayout.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXExheW91dHNcTWFpbkxheW91dC5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
@ -123,10 +179,34 @@ build_metadata.AdditionalFiles.CssScope =
|
|||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXE5vZGVzXE5ldy5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Admin/Nodes/Setup.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXE5vZGVzXFNldHVwLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Admin/Servers/Edit.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFNlcnZlcnNcRWRpdC5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Admin/Servers/Index.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFNlcnZlcnNcSW5kZXgucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Admin/Servers/New.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFNlcnZlcnNcTmV3LnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Index.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEluZGV4LnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Servers.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXFNlcnZlcnMucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Server/Index.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXFNlcnZlclxJbmRleC5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/Users/marce/source/repos/MoonlightPublic/Moonlight/Moonlight/Shared/Views/Setup/Features.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXFNldHVwXEZlYXR1cmVzLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// Dieser Code wurde von einem Tool generiert.
|
||||
// Laufzeitversion:4.0.30319.42000
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
||||
// der Code erneut generiert wird.
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -60,12 +60,16 @@
|
|||
},
|
||||
"BlazorMonaco": {
|
||||
"target": "Package",
|
||||
"version": "[3.0.0, )"
|
||||
"version": "[2.1.0, )"
|
||||
},
|
||||
"BlazorTable": {
|
||||
"target": "Package",
|
||||
"version": "[1.17.0, )"
|
||||
},
|
||||
"Blazored.Typeahead": {
|
||||
"target": "Package",
|
||||
"version": "[4.7.0, )"
|
||||
},
|
||||
"CurrieTechnologies.Razor.SweetAlert2": {
|
||||
"target": "Package",
|
||||
"version": "[5.4.0, )"
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore.design\7.0.3\build\net6.0\Microsoft.EntityFrameworkCore.Design.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore.design\7.0.3\build\net6.0\Microsoft.EntityFrameworkCore.Design.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)currietechnologies.razor.sweetalert2\5.4.0\buildTransitive\CurrieTechnologies.Razor.SweetAlert2.props" Condition="Exists('$(NuGetPackageRoot)currietechnologies.razor.sweetalert2\5.4.0\buildTransitive\CurrieTechnologies.Razor.SweetAlert2.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazortable\1.17.0\buildTransitive\BlazorTable.props" Condition="Exists('$(NuGetPackageRoot)blazortable\1.17.0\buildTransitive\BlazorTable.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazormonaco\3.0.0\buildTransitive\BlazorMonaco.props" Condition="Exists('$(NuGetPackageRoot)blazormonaco\3.0.0\buildTransitive\BlazorMonaco.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazormonaco\2.1.0\buildTransitive\BlazorMonaco.props" Condition="Exists('$(NuGetPackageRoot)blazormonaco\2.1.0\buildTransitive\BlazorMonaco.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazored.typeahead\4.7.0\buildTransitive\Blazored.Typeahead.props" Condition="Exists('$(NuGetPackageRoot)blazored.typeahead\4.7.0\buildTransitive\Blazored.Typeahead.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazor.contextmenu\1.15.0\buildTransitive\Blazor.ContextMenu.props" Condition="Exists('$(NuGetPackageRoot)blazor.contextmenu\1.15.0\buildTransitive\Blazor.ContextMenu.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
|
|
|
@ -56,17 +56,36 @@
|
|||
"buildMultiTargeting/Blazor.ContextMenu.props": {}
|
||||
}
|
||||
},
|
||||
"BlazorMonaco/3.0.0": {
|
||||
"Blazored.Typeahead/4.7.0": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "6.0.0",
|
||||
"Microsoft.AspNetCore.Components.Web": "6.0.0"
|
||||
"Microsoft.AspNetCore.Components": "6.0.3",
|
||||
"Microsoft.AspNetCore.Components.Web": "6.0.3"
|
||||
},
|
||||
"compile": {
|
||||
"lib/net6.0/BlazorMonaco.dll": {}
|
||||
"lib/net6.0/Blazored.Typeahead.dll": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/BlazorMonaco.dll": {}
|
||||
"lib/net6.0/Blazored.Typeahead.dll": {}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/Blazored.Typeahead.props": {}
|
||||
},
|
||||
"buildMultiTargeting": {
|
||||
"buildMultiTargeting/Blazored.Typeahead.props": {}
|
||||
}
|
||||
},
|
||||
"BlazorMonaco/2.1.0": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Components": "5.0.0",
|
||||
"Microsoft.AspNetCore.Components.Web": "5.0.0"
|
||||
},
|
||||
"compile": {
|
||||
"lib/net5.0/BlazorMonaco.dll": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net5.0/BlazorMonaco.dll": {}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/BlazorMonaco.props": {}
|
||||
|
@ -960,15 +979,33 @@
|
|||
"staticwebassets/blazorContextMenu.min.js"
|
||||
]
|
||||
},
|
||||
"BlazorMonaco/3.0.0": {
|
||||
"sha512": "GD2IJR4ke05Hj5koO2Mk4cEToDZI7dXnDhtb8H8eqMiCjlROX1DGBSt3K1c2C14mBtFGH4swJCgmHBDxU1R+MQ==",
|
||||
"Blazored.Typeahead/4.7.0": {
|
||||
"sha512": "fTN4Bt9rwEE/d33FXFd+h/DBjVtsFJLRf9B8mu0zJWMWG5Mk2tU2il3aK0+laUxNgBNBgEL0jW/831I+oapd6A==",
|
||||
"type": "package",
|
||||
"path": "blazormonaco/3.0.0",
|
||||
"path": "blazored.typeahead/4.7.0",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"README.md",
|
||||
"blazormonaco.3.0.0.nupkg.sha512",
|
||||
"blazored.typeahead.4.7.0.nupkg.sha512",
|
||||
"blazored.typeahead.nuspec",
|
||||
"build/Blazored.Typeahead.props",
|
||||
"build/Microsoft.AspNetCore.StaticWebAssets.props",
|
||||
"buildMultiTargeting/Blazored.Typeahead.props",
|
||||
"buildTransitive/Blazored.Typeahead.props",
|
||||
"icon.png",
|
||||
"lib/net6.0/Blazored.Typeahead.dll",
|
||||
"staticwebassets/blazored-typeahead.css",
|
||||
"staticwebassets/blazored-typeahead.js"
|
||||
]
|
||||
},
|
||||
"BlazorMonaco/2.1.0": {
|
||||
"sha512": "OWzHwymmcArn4Nn62LwUi0C5O7T5rtcIB71mqCaS7vrHoQP9rJMMSMDytxAxCfOq85Auj84kumOA9CpAWi23FQ==",
|
||||
"type": "package",
|
||||
"path": "blazormonaco/2.1.0",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"blazormonaco.2.1.0.nupkg.sha512",
|
||||
"blazormonaco.nuspec",
|
||||
"build/BlazorMonaco.props",
|
||||
"build/Microsoft.AspNetCore.StaticWebAssets.props",
|
||||
|
@ -976,20 +1013,8 @@
|
|||
"buildTransitive/BlazorMonaco.props",
|
||||
"icon.png",
|
||||
"lib/net5.0/BlazorMonaco.dll",
|
||||
"lib/net6.0/BlazorMonaco.dll",
|
||||
"lib/net7.0/BlazorMonaco.dll",
|
||||
"lib/netstandard2.0/BlazorMonaco.dll",
|
||||
"staticwebassets/jsInterop.js",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.de.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.es.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.fr.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.it.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.ja.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.ko.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.ru.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.zh-cn.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/common/worker/simpleWorker.nls.zh-tw.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/base/worker/workerMain.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/editor/editor.main.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/editor/editor.main.nls.de.js.map",
|
||||
|
@ -1004,26 +1029,6 @@
|
|||
"staticwebassets/lib/monaco-editor/min-maps/vs/editor/editor.main.nls.zh-tw.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min-maps/vs/loader.js.map",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/browser/ui/codicons/codicon/codicon.ttf",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.de.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.de.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.es.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.es.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.fr.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.fr.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.it.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.it.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.ja.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.ja.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.ko.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.ko.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.ru.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.ru.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.zh-cn.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.zh-cn.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.zh-tw.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/common/worker/simpleWorker.nls.zh-tw.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/worker/workerMain.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/base/worker/workerMain.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/abap/abap.js",
|
||||
|
@ -1034,8 +1039,6 @@
|
|||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/azcli/azcli.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/bat/bat.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/bat/bat.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/bicep/bicep.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/bicep/bicep.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/cameligo/cameligo.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/cameligo/cameligo.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/clojure/clojure.js",
|
||||
|
@ -1050,20 +1053,12 @@
|
|||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/csp/csp.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/css/css.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/css/css.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/cypher/cypher.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/cypher/cypher.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/dart/dart.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/dart/dart.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/dockerfile/dockerfile.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/dockerfile/dockerfile.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/ecl/ecl.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/ecl/ecl.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/elixir/elixir.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/elixir/elixir.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/flow9/flow9.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/flow9/flow9.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/freemarker2/freemarker2.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/freemarker2/freemarker2.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/fsharp/fsharp.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/fsharp/fsharp.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/go/go.js",
|
||||
|
@ -1090,8 +1085,6 @@
|
|||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/less/less.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/lexon/lexon.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/lexon/lexon.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/liquid/liquid.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/liquid/liquid.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/lua/lua.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/lua/lua.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/m3/m3.js",
|
||||
|
@ -1116,22 +1109,16 @@
|
|||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/pgsql/pgsql.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/php/php.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/php/php.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/pla/pla.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/pla/pla.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/postiats/postiats.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/postiats/postiats.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/powerquery/powerquery.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/powerquery/powerquery.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/powershell/powershell.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/powershell/powershell.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/protobuf/protobuf.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/protobuf/protobuf.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/pug/pug.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/pug/pug.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/python/python.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/python/python.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/qsharp/qsharp.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/qsharp/qsharp.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/r/r.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/r/r.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/razor/razor.js",
|
||||
|
@ -1160,8 +1147,6 @@
|
|||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/solidity/solidity.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sophia/sophia.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sophia/sophia.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sparql/sparql.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sparql/sparql.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sql/sql.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/sql/sql.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/basic-languages/st/st.js",
|
||||
|
@ -1185,7 +1170,6 @@
|
|||
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.css",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.min.css",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.nls.de.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.nls.de.min.js",
|
||||
"staticwebassets/lib/monaco-editor/min/vs/editor/editor.main.nls.es.js",
|
||||
|
@ -2793,8 +2777,9 @@
|
|||
"BCrypt.Net-Next >= 4.0.3",
|
||||
"Ben.Demystifier >= 0.4.1",
|
||||
"Blazor.ContextMenu >= 1.15.0",
|
||||
"BlazorMonaco >= 3.0.0",
|
||||
"BlazorMonaco >= 2.1.0",
|
||||
"BlazorTable >= 1.17.0",
|
||||
"Blazored.Typeahead >= 4.7.0",
|
||||
"CurrieTechnologies.Razor.SweetAlert2 >= 5.4.0",
|
||||
"Discord.Net >= 3.9.0",
|
||||
"GravatarSharp.Core >= 1.0.1.2",
|
||||
|
@ -2876,12 +2861,16 @@
|
|||
},
|
||||
"BlazorMonaco": {
|
||||
"target": "Package",
|
||||
"version": "[3.0.0, )"
|
||||
"version": "[2.1.0, )"
|
||||
},
|
||||
"BlazorTable": {
|
||||
"target": "Package",
|
||||
"version": "[1.17.0, )"
|
||||
},
|
||||
"Blazored.Typeahead": {
|
||||
"target": "Package",
|
||||
"version": "[4.7.0, )"
|
||||
},
|
||||
"CurrieTechnologies.Razor.SweetAlert2": {
|
||||
"target": "Package",
|
||||
"version": "[5.4.0, )"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "pQgIHNaG7q9d70t4chS097wnXaBcGVs4M1KNaxW4kyPiCyrsd3TNRJ260ThoX5FTLByZYWNfqf9FNIzWLSOXTQ==",
|
||||
"dgSpecHash": "tg/pcbZFjC1DzdXLz1U4t3D9RlK8DsBcivE40sezA9KD921KFGrw4MATZDqmNFjOQaQFeekGMS+4SukWB5eJWA==",
|
||||
"success": true,
|
||||
"projectFilePath": "C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\Moonlight.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
@ -8,7 +8,8 @@
|
|||
"C:\\Users\\marce\\.nuget\\packages\\bcrypt.net-next\\4.0.3\\bcrypt.net-next.4.0.3.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\ben.demystifier\\0.4.1\\ben.demystifier.0.4.1.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\blazor.contextmenu\\1.15.0\\blazor.contextmenu.1.15.0.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\blazormonaco\\3.0.0\\blazormonaco.3.0.0.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\blazored.typeahead\\4.7.0\\blazored.typeahead.4.7.0.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\blazormonaco\\2.1.0\\blazormonaco.2.1.0.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\blazortable\\1.17.0\\blazortable.1.17.0.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\currietechnologies.razor.sweetalert2\\5.4.0\\currietechnologies.razor.sweetalert2.5.4.0.nupkg.sha512",
|
||||
"C:\\Users\\marce\\.nuget\\packages\\discord.net\\3.9.0\\discord.net.3.9.0.nupkg.sha512",
|
||||
|
|
|
@ -1 +1 @@
|
|||
"restore":{"projectUniqueName":"C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\Moonlight.csproj","projectName":"Moonlight","projectPath":"C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\Moonlight.csproj","outputPath":"C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\obj\\","projectStyle":"PackageReference","fallbackFolders":["C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages","C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"],"originalTargetFrameworks":["net6.0"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"C:\\Users\\marce\\source\\repos\\Logging.Net\\LoggingNet\\LoggingNet\\bin\\Release\\net5.0\\newpublish":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net6.0":{"targetAlias":"net6.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]}}"frameworks":{"net6.0":{"targetAlias":"net6.0","dependencies":{"BCrypt.Net-Next":{"target":"Package","version":"[4.0.3, )"},"Ben.Demystifier":{"target":"Package","version":"[0.4.1, )"},"Blazor.ContextMenu":{"target":"Package","version":"[1.15.0, )"},"BlazorMonaco":{"target":"Package","version":"[3.0.0, )"},"BlazorTable":{"target":"Package","version":"[1.17.0, )"},"CurrieTechnologies.Razor.SweetAlert2":{"target":"Package","version":"[5.4.0, )"},"Discord.Net":{"target":"Package","version":"[3.9.0, )"},"GravatarSharp.Core":{"target":"Package","version":"[1.0.1.2, )"},"JWT":{"target":"Package","version":"[10.0.2, )"},"Logging.Net":{"target":"Package","version":"[1.1.0, )"},"Microsoft.EntityFrameworkCore.Design":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[7.0.3, )"},"Microsoft.VisualStudio.Azure.Containers.Tools.Targets":{"target":"Package","version":"[1.15.1, )"},"MimeTypes":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[2.4.0, )"},"MineStat":{"target":"Package","version":"[3.1.1, )"},"Newtonsoft.Json":{"target":"Package","version":"[13.0.3-beta1, )"},"Otp.NET":{"target":"Package","version":"[1.3.0, )"},"Pomelo.EntityFrameworkCore.MySql":{"target":"Package","version":"[7.0.0, )"},"PteroConsole.NET":{"target":"Package","version":"[1.0.4, )"},"QRCoder":{"target":"Package","version":"[1.4.3, )"},"RestSharp":{"target":"Package","version":"[109.0.0-preview.1, )"},"UAParser":{"target":"Package","version":"[3.1.47, )"},"XtermBlazor":{"target":"Package","version":"[1.6.1, )"},"aaPanelSharp":{"target":"Package","version":"[1.0.0, )"}},"imports":["net461","net462","net47","net471","net472","net48"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.AspNetCore.App":{"privateAssets":"none"},"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\6.0.300\\RuntimeIdentifierGraph.json"}}
|
||||
"restore":{"projectUniqueName":"C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\Moonlight.csproj","projectName":"Moonlight","projectPath":"C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\Moonlight.csproj","outputPath":"C:\\Users\\marce\\source\\repos\\MoonlightPublic\\Moonlight\\Moonlight\\obj\\","projectStyle":"PackageReference","fallbackFolders":["C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages","C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"],"originalTargetFrameworks":["net6.0"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"C:\\Users\\marce\\source\\repos\\Logging.Net\\LoggingNet\\LoggingNet\\bin\\Release\\net5.0\\newpublish":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net6.0":{"targetAlias":"net6.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]}}"frameworks":{"net6.0":{"targetAlias":"net6.0","dependencies":{"BCrypt.Net-Next":{"target":"Package","version":"[4.0.3, )"},"Ben.Demystifier":{"target":"Package","version":"[0.4.1, )"},"Blazor.ContextMenu":{"target":"Package","version":"[1.15.0, )"},"BlazorMonaco":{"target":"Package","version":"[2.1.0, )"},"BlazorTable":{"target":"Package","version":"[1.17.0, )"},"Blazored.Typeahead":{"target":"Package","version":"[4.7.0, )"},"CurrieTechnologies.Razor.SweetAlert2":{"target":"Package","version":"[5.4.0, )"},"Discord.Net":{"target":"Package","version":"[3.9.0, )"},"GravatarSharp.Core":{"target":"Package","version":"[1.0.1.2, )"},"JWT":{"target":"Package","version":"[10.0.2, )"},"Logging.Net":{"target":"Package","version":"[1.1.0, )"},"Microsoft.EntityFrameworkCore.Design":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[7.0.3, )"},"Microsoft.VisualStudio.Azure.Containers.Tools.Targets":{"target":"Package","version":"[1.15.1, )"},"MimeTypes":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[2.4.0, )"},"MineStat":{"target":"Package","version":"[3.1.1, )"},"Newtonsoft.Json":{"target":"Package","version":"[13.0.3-beta1, )"},"Otp.NET":{"target":"Package","version":"[1.3.0, )"},"Pomelo.EntityFrameworkCore.MySql":{"target":"Package","version":"[7.0.0, )"},"PteroConsole.NET":{"target":"Package","version":"[1.0.4, )"},"QRCoder":{"target":"Package","version":"[1.4.3, )"},"RestSharp":{"target":"Package","version":"[109.0.0-preview.1, )"},"UAParser":{"target":"Package","version":"[3.1.47, )"},"XtermBlazor":{"target":"Package","version":"[1.6.1, )"},"aaPanelSharp":{"target":"Package","version":"[1.0.0, )"}},"imports":["net461","net462","net47","net471","net472","net48"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.AspNetCore.App":{"privateAssets":"none"},"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\6.0.300\\RuntimeIdentifierGraph.json"}}
|
|
@ -119,7 +119,77 @@ Add a new node;Add a new node
|
|||
Delete;Delete
|
||||
Deleting;Deleting
|
||||
Edit;Edit
|
||||
Toekn Id;Toekn Id
|
||||
Token Id;Token Id
|
||||
Token;Token
|
||||
Save;Save
|
||||
Token Id;Token Id
|
||||
Setup;Setup
|
||||
Open a ssh connection to your node and enter;Open a ssh connection to your node and enter
|
||||
and paste the config below. Then press STRG+O and STRG+X to save;and paste the config below. Then press STRG+O and STRG+X to save
|
||||
Before configuring this node, install the daemon;Before configuring this node, install the daemon
|
||||
Delete this node?;Delete this node?
|
||||
Do you really want to delete this node;Do you really want to delete this node
|
||||
Yes;Yes
|
||||
No;No
|
||||
Status;Status
|
||||
Adding;Adding
|
||||
Port;Port
|
||||
Id;Id
|
||||
Manage;Manage
|
||||
Create new server;Create new server
|
||||
No servers found;No servers found
|
||||
Server name;Server name
|
||||
Cpu cores;Cpu cores
|
||||
Disk;Disk
|
||||
Image;Image
|
||||
Override startup;Override startup
|
||||
Docker image;Docker image
|
||||
CPU Cores (100% = 1 Core);CPU Cores (100% = 1 Core)
|
||||
Server successfully created;Server successfully created
|
||||
Name;Name
|
||||
Cores;Cores
|
||||
Owner;Owner
|
||||
Value;Value
|
||||
An unknown error occured;An unknown error occured
|
||||
No allocation found;No allocation found
|
||||
Identifier;Identifier
|
||||
UuidIdentifier;UuidIdentifier
|
||||
Override startup command;Override startup command
|
||||
Loading;Loading
|
||||
Offline;Offline
|
||||
Connecting;Connecting
|
||||
Start;Start
|
||||
Restart;Restart
|
||||
Stop;Stop
|
||||
Shared IP;Shared IP
|
||||
Server ID;Server ID
|
||||
Cpu;Cpu
|
||||
Console;Console
|
||||
Files;Files
|
||||
Backups;Backups
|
||||
Network;Network
|
||||
Plugins;Plugins
|
||||
Settings;Settings
|
||||
Enter command;Enter command
|
||||
Execute;Execute
|
||||
Checking disk space;Checking disk space
|
||||
Updating config files;Updating config files
|
||||
Checking file permissions;Checking file permissions
|
||||
Downloading server image;Downloading server image
|
||||
Downloaded server image;Downloaded server image
|
||||
Starting;Starting
|
||||
Online;Online
|
||||
Kill;Kill
|
||||
Stopping;Stopping
|
||||
Search files and folders;Search files and folders
|
||||
Launch WinSCP;Launch WinSCP
|
||||
New folder;New folder
|
||||
Upload;Upload
|
||||
File name;File name
|
||||
File size;File size
|
||||
Last modified;Last modified
|
||||
Cancel;Cancel
|
||||
Canceling;Canceling
|
||||
Running;Running
|
||||
Loading backups;Loading backups
|
||||
Started backup creation;Started backup creation
|
||||
Backup is going to be created;Backup is going to be created
|
||||
|
|
|
@ -7,3 +7,11 @@
|
|||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.blur-unless-hover {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.blur-unless-hover:hover {
|
||||
filter: none;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
var require = { paths: { vs: '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } };
|
Loading…
Reference in a new issue